mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-13 12:39:14 +00:00
Compare commits
6 Commits
alpha
...
dialog-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f621d3890a | ||
|
|
fb94eaea42 | ||
|
|
5b40020acb | ||
|
|
95819703f5 | ||
|
|
23dfdd907d | ||
|
|
f4e46ac422 |
@@ -1,7 +1,7 @@
|
||||
{# Getting started #}
|
||||
<h2>Getting Started</h2>
|
||||
<ul>
|
||||
<li><a href="/docs/">Installation</a></li>
|
||||
<li><a href="/docs/installation">Installation</a></li>
|
||||
<li><a href="/docs/usage">Usage</a></li>
|
||||
<li><a href="/docs/customizing">Customizing</a></li>
|
||||
<li><a href="/docs/form-controls">Form Controls</a></li>
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
{% set paletteId = palette.fileSlug or page.fileSlug %}
|
||||
{% set suffixes = ['-80', '', '-20'] %}
|
||||
{% set width = 20 %}
|
||||
{% set height = 12 %}
|
||||
{% set height_core = 20 %}
|
||||
{% set gap_x = 4 %}
|
||||
{% set gap_y = 4 %}
|
||||
|
||||
<wa-scoped class="palette-icon-host">
|
||||
<template>
|
||||
<link rel="stylesheet" href="/dist/styles/color/{{ paletteId }}.css">
|
||||
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
|
||||
{% set total_width = (width + gap_x) * hues|length %}
|
||||
{% set total_height = (height + gap_y) * suffixes|length + (height_core - height) %}
|
||||
<svg viewBox="0 0 {{ total_width }} {{ total_height }}" fill="none" xmlns="http://www.w3.org/2000/svg" class="wa-palette-{{ paletteId }} palette-icon">
|
||||
<style>
|
||||
@import url('/dist/styles/color/{{ paletteId }}.css') layer(palette.{{ paletteId }});
|
||||
.palette-icon {
|
||||
height: 8ch;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="palette-icon" style="--hues: {{ hues|length }}; --suffixes: {{ suffixes|length }}">
|
||||
{% for hue in hues -%}
|
||||
{% set hueIndex = loop.index %}
|
||||
{% for suffix in suffixes -%}
|
||||
<div class="swatch"
|
||||
data-hue="{{ hue }}" data-suffix="{{ suffix }}"
|
||||
style="--color: var(--wa-color-{{ hue }}{{ suffix }}); grid-column: {{ hueIndex }}; grid-row: {{ loop.index }}"> </div>
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
</div>
|
||||
</template>
|
||||
</wa-scoped>
|
||||
{% for hue in hues -%}
|
||||
{% set hueIndex = loop.index0 %}
|
||||
{% set y = 0 %}
|
||||
{% for suffix in suffixes -%}
|
||||
{% set swatch_height = height if suffix else height_core %}
|
||||
|
||||
<rect x="{{ hueIndex * (width + gap_x) }}" y="{{ y }}"
|
||||
width="{{ width }}" height="{{ swatch_height }}"
|
||||
fill="var(--wa-color-{{ hue }}{{ suffix }})" rx="2" />
|
||||
{% set y = y + swatch_height + gap_y %}
|
||||
{%- endfor %}
|
||||
{% endfor %}
|
||||
</svg>
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-2">
|
||||
<wa-input value="Input" size="small" inert></wa-input>
|
||||
<wa-button size="small" variant="brand" inert>Go</wa-button>
|
||||
<wa-input value="Input" size="small"></wa-input>
|
||||
<wa-button size="small" variant="brand">Go</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { mkdir, writeFile } from 'fs/promises';
|
||||
import lunr from 'lunr';
|
||||
import { parse } from 'node-html-parser';
|
||||
import * as path from 'path';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
function collapseWhitespace(string) {
|
||||
@@ -53,9 +52,8 @@ export function searchPlugin(options = {}) {
|
||||
return content;
|
||||
});
|
||||
|
||||
eleventyConfig.on('eleventy.after', ({ directories }) => {
|
||||
const { output } = directories;
|
||||
const outputFilename = path.resolve(join(output, 'search.json'));
|
||||
eleventyConfig.on('eleventy.after', ({ dir }) => {
|
||||
const outputFilename = join(dir.output, 'search.json');
|
||||
const map = [];
|
||||
const searchIndex = lunr(async function () {
|
||||
let index = 0;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Low-level utility to encapsulate a bit of HTML (mainly to apply certain stylesheets to it without them leaking to the rest of the page)
|
||||
* Usage: <wa-scoped><template><!-- your HTML here --></template></wa-scoped>
|
||||
*/
|
||||
import { discover } from '/dist/webawesome.js';
|
||||
|
||||
// Map of <wa-scoped> elements to any global <style> elements they’ve created
|
||||
const imports = new Set();
|
||||
const fontFaceRules = new Set();
|
||||
|
||||
@@ -52,8 +52,6 @@ export default class WaScoped extends HTMLElement {
|
||||
this.#fixStyles();
|
||||
this.#applyDarkMode();
|
||||
|
||||
discover(this.shadowRoot);
|
||||
|
||||
this.observer.observe(this, { childList: true, subtree: true, characterData: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ document.addEventListener('click', event => {
|
||||
const code = codeExample.querySelector('code');
|
||||
const cdnUrl = document.documentElement.dataset.cdnUrl;
|
||||
const html =
|
||||
`<script data-fa-kit-code="b10bfbde90" type="module" src="${cdnUrl}webawesome.loader.js"></script>\n` +
|
||||
`<script type="module" src="${cdnUrl}webawesome.loader.js"></script>\n` +
|
||||
`<link rel="stylesheet" href="${cdnUrl}styles/themes/default.css">\n` +
|
||||
`<link rel="stylesheet" href="${cdnUrl}styles/webawesome.css">\n` +
|
||||
`<link rel="stylesheet" href="${cdnUrl}styles/utilities.css">\n\n` +
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
let initialPageLoadComplete = document.readyState === 'complete';
|
||||
let initialPageLoadComplete = false;
|
||||
|
||||
if (!initialPageLoadComplete) {
|
||||
window.addEventListener('load', () => {
|
||||
initialPageLoadComplete = true;
|
||||
});
|
||||
}
|
||||
window.addEventListener('load', () => {
|
||||
initialPageLoadComplete = true;
|
||||
});
|
||||
|
||||
// Helper for view transitions
|
||||
export function domChange(fn, { behavior = 'smooth', ignoreInitialLoad = true } = {}) {
|
||||
|
||||
@@ -1,49 +1,25 @@
|
||||
wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
|
||||
min-width: 22ch;
|
||||
|
||||
&::part(header) {
|
||||
/* We want to add a background color, so any spacing needs to go on .theme-icon */
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
min-block-size: 0;
|
||||
}
|
||||
|
||||
[slot='header'] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-icon-host,
|
||||
.palette-icon-host {
|
||||
flex: 1;
|
||||
border-radius: inherit;
|
||||
|
||||
&[slot='header'],
|
||||
[slot='header']:has(&) {
|
||||
flex: 1;
|
||||
border-radius: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.palette-icon {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--hues, 9), 1fr);
|
||||
gap: var(--wa-space-3xs);
|
||||
min-width: 20ch;
|
||||
min-height: 9ch;
|
||||
align-content: center;
|
||||
|
||||
.swatch {
|
||||
height: 0.7em;
|
||||
background: var(--color);
|
||||
border-radius: var(--wa-border-radius-s);
|
||||
|
||||
&[data-suffix=''] {
|
||||
height: 1.1em;
|
||||
}
|
||||
}
|
||||
.theme-icon-host {
|
||||
display: block;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
min-width: 18ch;
|
||||
padding: var(--wa-space-xs) var(--wa-space-m);
|
||||
border-radius: inherit;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -885,4 +885,4 @@ If you don’t want to use [native styles](/docs/native/), you can include this
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="{% cdnUrl 'styles/components/page.css' %}" />
|
||||
```
|
||||
```
|
||||
|
||||
@@ -5,11 +5,8 @@ import { sort } from '../_utils/filters.js';
|
||||
|
||||
export default {
|
||||
eleventyComputed: {
|
||||
/**
|
||||
* Default parent slug. Can be overridden by explicitly setting parent in the data.
|
||||
* It can be either the URL slug of a page in the same directory or a parent directory.
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
// Default parent. Can be overridden by explicitly setting parent in the data.
|
||||
// parent can refer to either an ancestor page in the URL or another page in the same directory
|
||||
parent(data) {
|
||||
let { parent, page } = data;
|
||||
|
||||
@@ -20,28 +17,16 @@ export default {
|
||||
return page.url.split('/').filter(Boolean).at(-2);
|
||||
},
|
||||
|
||||
/**
|
||||
* URL of parent page
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
parentUrl(data) {
|
||||
let { parent, page } = data;
|
||||
return getParentUrl(page.url, parent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Collection item of parent page
|
||||
* @returns {object | undefined} Parent page item
|
||||
*/
|
||||
parentItem(data) {
|
||||
let { parentUrl } = data;
|
||||
return data.collections.all.find(item => item.url === parentUrl);
|
||||
},
|
||||
|
||||
/**
|
||||
* Child pages of current page
|
||||
* @returns {object[]} Array of child pages
|
||||
*/
|
||||
children(data) {
|
||||
let { collections, page, parentOf } = data;
|
||||
|
||||
@@ -63,17 +48,7 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve a parent slug against a page URL
|
||||
* @param {string} url - The URL of the page
|
||||
* @param {string} parent - The slug of the parent page
|
||||
* @returns {string} The resolved URL of the parent page
|
||||
*/
|
||||
function getParentUrl(url, parent) {
|
||||
if (!parent) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let parts = url.split('/').filter(Boolean);
|
||||
let ancestorIndex = parts.findLastIndex(part => part === parent);
|
||||
let retParts = parts.slice();
|
||||
@@ -89,18 +64,15 @@ function getParentUrl(url, parent) {
|
||||
let ret = retParts.join('/');
|
||||
|
||||
if (url.startsWith('/')) {
|
||||
// If the current page starts with a slash, make sure the parent does too
|
||||
// This is pretty much always the case with 11ty page URLs
|
||||
ret = '/' + ret;
|
||||
}
|
||||
|
||||
if (!retParts.at(-1)?.includes('.') && !ret.endsWith('/')) {
|
||||
if (!retParts.at(-1).includes('.') && !ret.endsWith('/')) {
|
||||
// If no extension, make sure to end with a slash
|
||||
ret += '/';
|
||||
}
|
||||
|
||||
if (ret === '/docs/') {
|
||||
// We don't want anyone's parent to be "Installation"!
|
||||
ret = '/';
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ To get everything included in Web Awesome, add the following code to the `<head>
|
||||
|
||||
This snippet includes three parts:
|
||||
1. **The default theme**, a stylesheet that gives a cohesive look to Web Awesome components with both light and dark modes
|
||||
2. **Web Awesome styles**, an optional stylesheet that [styles native HTML elements](/docs/native) and includes [utility classes](/docs/utilities) you can use in your project
|
||||
2. **Web Awesome styles**, an optional stylesheet that [styles native HTML elements](/docs/native) and includes [utility classes](/docs/utilities) you can use in your project
|
||||
3. **The autoloader**, a lightweight script watches the DOM for unregistered Web Awesome elements and lazy loads them for you — even if they're added dynamically
|
||||
|
||||
Now you can [start using Web Awesome!](/docs/usage)
|
||||
@@ -58,7 +58,7 @@ Font Awesome users can set their kit code to unlock Font Awesome Pro icons. You
|
||||
|
||||
## Advanced Setup
|
||||
|
||||
The autoloader is the easiest way to use Web Awesome, but different projects (or your own preferences!) may require different installation methods.
|
||||
The autoloader is the easiest way to use Web Awesome, but different projects (or your own preferences!) may require different installation methods.
|
||||
|
||||
### Installing via npm
|
||||
|
||||
@@ -122,4 +122,4 @@ Most of the magic behind assets is handled internally by Web Awesome, but if you
|
||||
// Get the path to an asset, e.g. /path/to/assets/file.ext
|
||||
const assetPath = getBasePath('file.ext');
|
||||
</script>
|
||||
```
|
||||
```
|
||||
@@ -12,37 +12,27 @@ Components with the <wa-badge variant="warning" pill>Experimental</wa-badge> bad
|
||||
During the alpha period, things might break! We take breaking changes very seriously, but sometimes they're necessary to make the final product that much better. We appreciate your patience!
|
||||
:::
|
||||
|
||||
## 3.0.0-alpha.12
|
||||
## Next
|
||||
|
||||
- Fixed the search dialog's styles so it doesn't jump around as you search
|
||||
- Removed close watcher logic to backdrop hide animation bugs in `<wa-dialog>` and `<wa-drawer>`; this logic is already handled and we'll revisit `CloseWatcher` when browser support is better and behaviors are consistent
|
||||
- Revert `<wa-dialog>` structure and CSS to fix clipped content in dialogs (WA-A #123) and light dismiss in iOS Safari (WA-A #201)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Added `appearance` to [`<wa-details>`](/docs/components/details) and [`<wa-card>`](/docs/components/card) and support for the [appearance utilities](/docs/utilities/appearance/) in the [`<details>` native styles](/docs/native/details).
|
||||
- Added an `orange` scale to all color palettes
|
||||
- Added the [`.wa-cloak` utility](/docs/utilities/fouce) to prevent FOUCE
|
||||
- Added the [`allDefined()` utility](/docs/usage/#all-defined) for awaiting component registration
|
||||
- Added the `.wa-cloak` utility to prevent FOUCE
|
||||
- Added the `allDefined()` utility for awaiting component registration
|
||||
|
||||
### Bug fixes
|
||||
### Bugfixes
|
||||
|
||||
- Specifying inherited CSS properties on `<wa-tooltip>` now works as expected ([thanks Dennis!](https://github.com/shoelace-style/webawesome-alpha/discussions/203))
|
||||
- Fixed a bug in `<wa-select>` that made it hard to use with VueJS, Svelte, and many other frameworks
|
||||
- Fixed a bug in `<wa-select multiple>` that sometimes resulted in empty `<div>` elements being output
|
||||
- Fixed a bug where changing a `<wa-option>` label wouldn't update the display label in `<wa-select>`
|
||||
- Added default spacing to icons slotted into `<wa-tab>`
|
||||
- Lots of fixes around pill-shaped elements:
|
||||
- Fixed the `wa-pill` class for text fields
|
||||
- Fixed `pill` style for `<wa-input>` and `<wa-radio-button>` elements
|
||||
- Fixed a bug in `<wa-radio-button>` that prevented active buttons from receiving the correct styles
|
||||
- Fixed a bug in `<wa-button>` that prevented the focus ring from showing in Safari
|
||||
- Fixed alignment of `<wa-dropdown>` inside button groups
|
||||
- Removed close watcher logic to backdrop hide animation bugs in `<wa-dialog>` and `<wa-drawer>`; this logic is already handled and we'll revisit `CloseWatcher` when browser support is better and behaviors are consistent
|
||||
- Revert `<wa-dialog>` structure and CSS to fix clipped content in dialogs (WA-A #123) and light dismiss in iOS Safari (WA-A #201)
|
||||
- Fixed the `wa-pill` class for text fields
|
||||
- Fixed `pill` style for `<wa-input>` elements
|
||||
- Fixed a bug in `<wa-color-picker>` that prevented light dismiss from working when clicking immediately above the color picker dropdown
|
||||
- Fixed a bug in `<wa-progress>` that prevented Safari from animation progress changes
|
||||
- Fixed the missing indeterminate icon in [native checkbox styles](/docs/native/checkbox)
|
||||
- Fixed a bug in `<wa-radio>` where elements would stack instead of display inline
|
||||
- Docs fixes:
|
||||
- Fixed the search dialog's styles so it doesn't jump around as you search
|
||||
- Theme cards now have icons
|
||||
- Fixed a bug in `<wa-select multiple>` that sometimes resulted in empty `<div>` elements being output
|
||||
|
||||
## 3.0.0-alpha.11
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ If you're new to custom elements, often referred to as "web components," this se
|
||||
|
||||
Unlike traditional frameworks, custom elements don't have a centralized initialization phase. This means you need to verify that a custom element has been properly registered before attempting to interact with its properties or methods.
|
||||
|
||||
### Individual components: `customElements.whenDefined()` { #when-defined}
|
||||
|
||||
You can use the [`customElements.whenDefined()`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/whenDefined) method to ensure a specific component is ready:
|
||||
|
||||
```ts
|
||||
@@ -23,8 +21,6 @@ await customElements.whenDefined('wa-button');
|
||||
const button = document.querySelector('wa-button');
|
||||
```
|
||||
|
||||
### All Web Awesome components: `allDefined()` { #all-defined }
|
||||
|
||||
When working with multiple components, checking each one individually can become tedious. For convenience, Web Awesome provides the `allDefined()` function which automatically detects and waits for all Web Awesome components in the DOM to be initialized before resolving.
|
||||
|
||||
```ts
|
||||
@@ -36,33 +32,6 @@ await allDefined();
|
||||
// All Web Awesome components on the page are ready!
|
||||
```
|
||||
|
||||
#### Advanced Usage
|
||||
|
||||
By default, `allDefined()` will wait for all `wa-` prefixed custom elements within the current `document` to be registered.
|
||||
You can customize this behavior by passing in options:
|
||||
- `root` allows you to pass in a different element to search within, or a different document entirely (defaults to `document`).
|
||||
- `match` allows you to specify a custom function to determine which elements to wait for. This function should return `true` for elements you want to wait for and `false` for those you don't.
|
||||
- `additionalElements` allows you to wait for custom elements to be defined that may not be present in the DOM at the time `allDefined()` is called. This can be useful for elements that are loaded dynamically via JS.
|
||||
|
||||
Here is an example of using `match` and `root` to await registration of Web Awesome components inside an element with an id of `sidebar`, plus a `<my-component>` element if present in the DOM, and `<wa-slider>` and `<other-slider>` elements whether present in the DOM or not:
|
||||
|
||||
```js
|
||||
import { allDefined } from '/dist/webawesome.js';
|
||||
|
||||
await allDefined({
|
||||
match: tagName => tagName.startsWith('wa-') || tagName === 'my-component',
|
||||
root: document.getElementById('sidebar'),
|
||||
additionalElements: ['wa-slider', 'other-slider']
|
||||
});
|
||||
```
|
||||
|
||||
:::warning
|
||||
`additionalElements` will only wait for elements to be registered — it will not load them.
|
||||
If you're using the autoloader plus custom JS to inject HTML dynamically, **you need to make sure your JS runs _before_ the `await allDefined()` call**,
|
||||
otherwise you could run into a chicken and egg issue:
|
||||
since the autoloader will not load elements until they are present in the DOM, the promise will never resolve and your JS to inject them will not run.
|
||||
:::
|
||||
|
||||
## Attributes & Properties
|
||||
|
||||
Many components have properties that can be set using attributes. For example, buttons accept a `size` attribute that maps to the `size` property which dictates the button's size.
|
||||
@@ -143,6 +112,49 @@ For example, `<button>` and `<wa-button>` both have a `type` attribute, but the
|
||||
**Don't make assumptions about a component's API!** To prevent unexpected behaviors, please take the time to review the documentation and make sure you understand what each attribute, property, method, and event is intended to do.
|
||||
:::
|
||||
|
||||
## Waiting for Components to Load
|
||||
|
||||
Web components are registered with JavaScript, so depending on how and when you load Web Awesome, you may notice a [Flash of Undefined Custom Elements (FOUCE)](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/) when the page loads. There are a couple ways to prevent this, both of which are described in the linked article.
|
||||
|
||||
One option is to use the [`:defined`](https://developer.mozilla.org/en-US/docs/Web/CSS/:defined) CSS pseudo-class to "hide" custom elements that haven't been registered yet. You can scope it to specific tags or you can hide all undefined custom elements as shown below.
|
||||
|
||||
```css
|
||||
:not(:defined) {
|
||||
visibility: hidden;
|
||||
}
|
||||
```
|
||||
|
||||
As soon as a custom element is registered, it will immediately appear with all of its styles, effectively eliminating FOUCE. Note the use of `visibility: hidden` instead of `display: none` to reduce shifting as elements are registered. The drawback to this approach is that custom elements can potentially appear one by one instead of all at the same time.
|
||||
|
||||
Another option is to use [`customElements.whenDefined()`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/whenDefined), which returns a promise that resolves when the specified element gets registered. You'll probably want to use it with [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) in case an element fails to load for some reason.
|
||||
|
||||
A clever way to use this method is to hide the `<body>` with `opacity: 0` and add a class that fades it in as soon as all your custom elements are defined.
|
||||
|
||||
```html
|
||||
<style>
|
||||
body {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
body.ready {
|
||||
opacity: 1;
|
||||
transition: 0.25s opacity;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="module">
|
||||
await Promise.allSettled([
|
||||
customElements.whenDefined('wa-button'),
|
||||
customElements.whenDefined('wa-card'),
|
||||
customElements.whenDefined('wa-rating')
|
||||
]);
|
||||
|
||||
// Button, card, and rating are registered now! Add
|
||||
// the `ready` class so the UI fades in.
|
||||
document.body.classList.add('ready');
|
||||
</script>
|
||||
```
|
||||
|
||||
## Component Rendering and Updating
|
||||
|
||||
Web Awesome components are built with [Lit](https://lit.dev/), a tiny library that makes authoring custom elements easier, more maintainable, and a lot of fun! As a Web Awesome user, here is some helpful information about rendering and updating you should probably be aware of.
|
||||
|
||||
@@ -3,14 +3,12 @@ title: Reduce FOUCE
|
||||
description: Utility to improve the loading experience by hiding non-prerendered custom elements until they are registered.
|
||||
file: styles/utilities/fouce.css
|
||||
icon: spinner
|
||||
snippet: .wa-cloak
|
||||
---
|
||||
|
||||
Often, components are shown before their logic and styles have had a chance to load, also known as a [Flash of Undefined Custom Elements](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/).
|
||||
The FOUCE style utility (which is automatically applied if you use our [style utilities](/docs/utilities/)) automatically takes care of hiding custom elements until **both they and their contents** have been registered, up to a maximum of two seconds.
|
||||
While convenient, autoloading can lead to a [Flash of Undefined Custom Elements](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/).
|
||||
The [FOUCE style utility](/docs/utilities/fouce/#opting-in) (which is automatically applied if you use the [Web Awesome utilities](/docs/utilities/)) takes care of hiding custom elements until they and their contents have been registered, up to a maximum of two seconds.
|
||||
|
||||
In many cases, this is not enough, and you may wish to hide a broader wrapper element or even the entire page until all WA elements within it have loaded.
|
||||
To do that, you can add the `wa-cloak` class to any element on the page or even apply it to the whole page by placing the class on the `<html>` element:
|
||||
In many cases, this is not enough, and you may wish to hide a broader wrapper element or even the entire page until all WA elements within it have loaded. To do that, you can add the `wa-reduce-fouce` class to any element on the page or even apply it to the whole page by placing the class on the `<html>` element.
|
||||
|
||||
```html
|
||||
<html class="wa-cloak">
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@shoelace-style/webawesome",
|
||||
"version": "3.0.0-alpha.12",
|
||||
"version": "3.0.0-alpha.11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@shoelace-style/webawesome",
|
||||
"version": "3.0.0-alpha.12",
|
||||
"version": "3.0.0-alpha.11",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@shoelace-style/webawesome",
|
||||
"description": "A forward-thinking library of web components.",
|
||||
"version": "3.0.0-alpha.12",
|
||||
"version": "3.0.0-alpha.11",
|
||||
"homepage": "https://webawesome.com/",
|
||||
"author": "Web Awesome",
|
||||
"license": "MIT",
|
||||
@@ -52,8 +52,8 @@
|
||||
"start:alpha": "node scripts/build.js --alpha --develop",
|
||||
"publish-alpha-cdn": "./publish-alpha-cdn.sh",
|
||||
"create": "plop --plopfile scripts/plop/plopfile.js",
|
||||
"test": "CSR_ONLY=\"true\" web-test-runner --group default",
|
||||
"test:component": "CSR_ONLY=\"true\" web-test-runner -- --watch --group",
|
||||
"test": "web-test-runner --group default",
|
||||
"test:component": "web-test-runner -- --watch --group",
|
||||
"test:contrast": "cd src/styles/color && node contrast.test.js",
|
||||
"test:watch": "web-test-runner --watch --group default",
|
||||
"prettier": "prettier --check --log-level=warn .",
|
||||
|
||||
@@ -106,11 +106,6 @@ export default class WaOption extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
private handleDefaultSlotChange() {
|
||||
// Tell the controller to update the label
|
||||
if (customElements.get('wa-select')) {
|
||||
this.closest('wa-select')?.selectionChanged();
|
||||
}
|
||||
|
||||
this.updateDefaultLabel();
|
||||
|
||||
if (this.isInitialized) {
|
||||
@@ -128,7 +123,7 @@ export default class WaOption extends WebAwesomeElement {
|
||||
|
||||
private handleHover = (event: Event) => {
|
||||
// We need this because Safari doesn't honor :hover styles while dragging
|
||||
// Test case: https://codepen.io/leaverou/pen/VYZOOjy
|
||||
// Testcase: https://codepen.io/leaverou/pen/VYZOOjy
|
||||
if (event.type === 'mouseenter') {
|
||||
this.toggleCustomState('hover', true);
|
||||
} else if (event.type === 'mouseleave') {
|
||||
|
||||
@@ -97,7 +97,6 @@ slot:not([name]) {
|
||||
:host([disable-sticky~='menu']) [part~='menu'] {
|
||||
position: static;
|
||||
overflow: unset;
|
||||
z-index: unset;
|
||||
}
|
||||
|
||||
:host([disable-sticky~='aside']) [part~='aside'],
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
|
||||
.indicator {
|
||||
width: var(--percentage);
|
||||
width: calc(var(--value, 0) * 1%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -26,7 +26,9 @@
|
||||
overflow: hidden;
|
||||
line-height: 1;
|
||||
font-weight: var(--wa-font-weight-semibold);
|
||||
transition: all var(--wa-transition-slow, 200ms) var(--wa-transition-easing, ease);
|
||||
transition: var(--wa-transition-slow, 200ms) var(--wa-transition-easing, ease);
|
||||
transition: 1s;
|
||||
/* transition-property: width, background; */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,8 @@ describe('<wa-progress-bar>', () => {
|
||||
expect(base.getAttribute('aria-valuenow')).to.equal('25');
|
||||
});
|
||||
|
||||
it('uses the value parameter to set the custom property on the indicator', async () => {
|
||||
await new Promise(requestAnimationFrame);
|
||||
expect(el.style.getPropertyValue('--percentage')).to.equal('25%');
|
||||
it('uses the value parameter to set the custom property on the indicator', () => {
|
||||
expect(indicator.style.getPropertyValue('--value')).to.equal('25');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import type { PropertyValues } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { clamp } from '../../internal/math.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import styles from './progress-bar.css';
|
||||
@@ -35,16 +33,6 @@ export default class WaProgressBar extends WebAwesomeElement {
|
||||
/** A custom label for assistive devices. */
|
||||
@property() label = '';
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has('value')) {
|
||||
// Wait a cycle before setting it so Safari animates it.
|
||||
// https://github.com/shoelace-style/webawesome/issues/356
|
||||
requestAnimationFrame(() => {
|
||||
this.style.setProperty('--percentage', `${clamp(this.value, 0, 100)}%`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div
|
||||
@@ -57,7 +45,7 @@ export default class WaProgressBar extends WebAwesomeElement {
|
||||
aria-valuemax="100"
|
||||
aria-valuenow=${this.indeterminate ? '0' : this.value}
|
||||
>
|
||||
<div part="indicator" class="indicator">
|
||||
<div part="indicator" class="indicator" style="--value: ${this.value}">
|
||||
${!this.indeterminate ? html` <slot part="label" class="label"></slot> ` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
/*
|
||||
* Checked buttons
|
||||
*/
|
||||
|
||||
:host([checked]) {
|
||||
--indicator-color: var(--wa-form-control-activated-color);
|
||||
--indicator-width: var(--wa-border-width-s);
|
||||
@@ -42,41 +43,3 @@
|
||||
--border-color: var(--indicator-color);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Active buttons
|
||||
*/
|
||||
button:active {
|
||||
--text-color-active: var(--wa-form-control-activated-color);
|
||||
--border-color-active: var(--wa-form-control-activated-color);
|
||||
}
|
||||
|
||||
/* Horizontal radio pill buttons */
|
||||
:host([data-wa-radio-horizontal][data-wa-radio-first]:not([data-wa-radio-last])) .wa-pill {
|
||||
border-start-end-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
|
||||
:host([data-wa-radio-horizontal][data-wa-radio-inner]) .wa-pill {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
:host([data-wa-radio-horizontal][data-wa-radio-last]:not([data-wa-radio-first])) .wa-pill {
|
||||
border-start-start-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
}
|
||||
|
||||
/* Vertical radio pill buttons */
|
||||
:host([data-wa-radio-vertical][data-wa-radio-first]:not([data-wa-radio-last])) .wa-pill {
|
||||
border-end-start-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
|
||||
:host([data-wa-radio-vertical][data-wa-radio-inner]) .wa-pill {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
:host([data-wa-radio-vertical][data-wa-radio-last]:not([data-wa-radio-first])) .wa-pill {
|
||||
border-start-start-radius: 0;
|
||||
border-start-end-radius: 0;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import nativeStyles from '../../styles/native/button.css';
|
||||
import appearanceStyles from '../../styles/utilities/appearance.css';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
import variantStyles from '../../styles/utilities/variants.css';
|
||||
import buttonStyles from '../button/button.css';
|
||||
import styles from './radio-button.css';
|
||||
|
||||
/**
|
||||
@@ -48,7 +49,7 @@ import styles from './radio-button.css';
|
||||
*/
|
||||
@customElement('wa-radio-button')
|
||||
export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
|
||||
static shadowStyle = [variantStyles, appearanceStyles, sizeStyles, nativeStyles, styles];
|
||||
static shadowStyle = [variantStyles, appearanceStyles, sizeStyles, nativeStyles, buttonStyles, styles];
|
||||
static rectProxy = 'input';
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');
|
||||
|
||||
@@ -38,6 +38,8 @@ import styles from './radio-group.css';
|
||||
* @csspart form-control-input - The input's wrapper.
|
||||
* @csspart radios - The wrapper than surrounds radio items, styled as a flex container by default.
|
||||
* @csspart hint - The hint's wrapper.
|
||||
* @csspart button-group - The button group that wraps radio buttons.
|
||||
* @csspart button-group__base - The button group's `base` part.
|
||||
*/
|
||||
@customElement('wa-radio-group')
|
||||
export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
@@ -63,6 +65,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
|
||||
|
||||
@state() private hasButtonGroup = false;
|
||||
@state() private hasRadioButtons = false;
|
||||
|
||||
/**
|
||||
@@ -147,7 +150,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
clickedRadio.checked = true;
|
||||
|
||||
const radios = this.getAllRadios();
|
||||
const hasRadioButtons = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
|
||||
const hasButtonGroup = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
|
||||
for (const radio of radios) {
|
||||
if (clickedRadio === radio) {
|
||||
continue;
|
||||
@@ -155,7 +158,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
radio.checked = false;
|
||||
|
||||
if (!hasRadioButtons) {
|
||||
if (!hasButtonGroup) {
|
||||
radio.setAttribute('tabindex', '-1');
|
||||
}
|
||||
}
|
||||
@@ -180,15 +183,6 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
// Detect the presence of radio buttons
|
||||
this.hasRadioButtons = radios.some(radio => radio.localName === 'wa-radio-button');
|
||||
|
||||
// Add data attributes to support styling
|
||||
radios.forEach((radio, index) => {
|
||||
radio.toggleAttribute('data-wa-radio-horizontal', this.orientation !== 'vertical');
|
||||
radio.toggleAttribute('data-wa-radio-vertical', this.orientation === 'vertical');
|
||||
radio.toggleAttribute('data-wa-radio-first', index === 0);
|
||||
radio.toggleAttribute('data-wa-radio-inner', index !== 0 && index !== radios.length - 1);
|
||||
radio.toggleAttribute('data-wa-radio-last', index === radios.length - 1);
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
// Sync the checked state and size
|
||||
radios.map(async radio => {
|
||||
@@ -202,8 +196,10 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
}),
|
||||
);
|
||||
|
||||
this.hasButtonGroup = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
|
||||
|
||||
if (radios.length > 0 && !radios.some(radio => radio.checked)) {
|
||||
if (this.hasRadioButtons) {
|
||||
if (this.hasButtonGroup) {
|
||||
const buttonRadio = radios[0].shadowRoot?.querySelector('button');
|
||||
|
||||
if (buttonRadio) {
|
||||
@@ -214,7 +210,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasRadioButtons) {
|
||||
if (this.hasButtonGroup) {
|
||||
const buttonGroup = this.shadowRoot?.querySelector('wa-button-group');
|
||||
|
||||
if (buttonGroup) {
|
||||
@@ -280,12 +276,12 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
const hasRadioButtons = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
|
||||
const hasButtonGroup = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
|
||||
|
||||
this.getAllRadios().forEach(radio => {
|
||||
radio.checked = false;
|
||||
|
||||
if (!hasRadioButtons) {
|
||||
if (!hasButtonGroup) {
|
||||
radio.setAttribute('tabindex', '-1');
|
||||
}
|
||||
});
|
||||
@@ -293,7 +289,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
this.value = radios[index].value;
|
||||
radios[index].checked = true;
|
||||
|
||||
if (!hasRadioButtons) {
|
||||
if (!hasButtonGroup) {
|
||||
radios[index].setAttribute('tabindex', '0');
|
||||
radios[index].focus();
|
||||
} else {
|
||||
@@ -355,7 +351,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
<slot
|
||||
part="form-control-input"
|
||||
class=${classMap({
|
||||
'wa-button-group': this.hasRadioButtons,
|
||||
'wa-button-group': this.hasButtonGroup,
|
||||
'wa-button-group-vertical': this.orientation === 'vertical',
|
||||
})}
|
||||
@slotchange=${this.syncRadioElements}
|
||||
|
||||
@@ -23,10 +23,6 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[part~='label'] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
[part~='hint'] {
|
||||
grid-column: 2;
|
||||
margin-block-start: var(--wa-space-3xs);
|
||||
|
||||
@@ -22,7 +22,6 @@ import appearanceStyles from '../../styles/utilities/appearance.css';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import '../icon/icon.js';
|
||||
import '../option/option.js';
|
||||
import type WaOption from '../option/option.js';
|
||||
import '../popup/popup.js';
|
||||
import type WaPopup from '../popup/popup.js';
|
||||
@@ -38,7 +37,6 @@ import styles from './select.css';
|
||||
* @dependency wa-icon
|
||||
* @dependency wa-popup
|
||||
* @dependency wa-tag
|
||||
* @dependency wa-option
|
||||
*
|
||||
* @slot - The listbox options. Must be `<wa-option>` elements. You can use `<wa-divider>` to group items visually.
|
||||
* @slot label - The input's label. Alternatively, you can use the `label` attribute.
|
||||
@@ -672,9 +670,9 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
this.selectionChanged();
|
||||
}
|
||||
|
||||
// @internal This method must be called whenever the selection changes. It will update the selected options cache, the
|
||||
// current value, and the display value. The option component uses it internally to update labels as they change.
|
||||
public selectionChanged() {
|
||||
// This method must be called whenever the selection changes. It will update the selected options cache, the current
|
||||
// value, and the display value
|
||||
private selectionChanged() {
|
||||
const options = this.getAllOptions();
|
||||
|
||||
// Update selected options cache
|
||||
@@ -713,7 +711,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
this.updateValidity();
|
||||
});
|
||||
}
|
||||
|
||||
protected get tags() {
|
||||
return this.selectedOptions.map((option, index) => {
|
||||
if (index < this.maxOptionsVisible || this.maxOptionsVisible <= 0) {
|
||||
|
||||
@@ -15,14 +15,6 @@
|
||||
-webkit-user-select: none;
|
||||
cursor: pointer;
|
||||
transition: color var(--wa-transition-fast) var(--wa-transition-easing);
|
||||
|
||||
::slotted(wa-icon:first-child) {
|
||||
margin-inline-end: var(--wa-space-xs);
|
||||
}
|
||||
|
||||
::slotted(wa-icon:last-child) {
|
||||
margin-inline-start: var(--wa-space-xs);
|
||||
}
|
||||
}
|
||||
|
||||
:host(:hover:not([disabled])) .tab {
|
||||
|
||||
@@ -81,6 +81,10 @@ input:is([type='button'], [type='reset'], [type='submit']),
|
||||
* States
|
||||
*/
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
@@ -99,11 +103,6 @@ input:is([type='button'], [type='reset'], [type='submit']),
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep it last so Safari doesn't stop parsing this block */
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,16 +38,6 @@ input[type='checkbox']:where(:not(:host *)) {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:indeterminate::after {
|
||||
background-color: currentColor;
|
||||
content: '';
|
||||
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><path d="M431 256c0 17.7-14.3 32-32 32H49c-17.7 0-32-14.3-32-32s14.3-32 32-32h350c17.7 0 32 14.3 32 32z"/></svg>')
|
||||
center no-repeat;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox']:where(:not(:host *)),
|
||||
|
||||
@@ -20,11 +20,6 @@
|
||||
}
|
||||
|
||||
/* Horizontal */
|
||||
.wa-button-group[aria-orientation='horizontal'] {
|
||||
/* TODO - see https://github.com/shoelace-style/webawesome/issues/374 */
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.wa-button-group:not([aria-orientation='vertical']):not(.wa-button-group-vertical) {
|
||||
> :not(:first-child),
|
||||
&::slotted(:not(:first-child)) {
|
||||
|
||||
@@ -3,12 +3,3 @@ import { startLoader } from './webawesome.js';
|
||||
export * from './webawesome.js';
|
||||
|
||||
startLoader();
|
||||
|
||||
// Remove `wa-cloak` when the autoloader finishes OR after two seconds. This prevents the entire screen from flashing
|
||||
// when unregistered components get added later on.
|
||||
Promise.race([
|
||||
new Promise(resolve => document.addEventListener('wa-discovery-complete', resolve)),
|
||||
new Promise(resolve => setTimeout(resolve, 2000)),
|
||||
]).then(() => {
|
||||
document.querySelectorAll('.wa-cloak').forEach(el => el.classList.remove('wa-cloak'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user