mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 12:09:26 +00:00
Add FOUCE utilities (#686)
* add fouce utilities * add comment * Update docs/docs/installation.md Co-authored-by: Lea Verou <lea@verou.me> * commit PR suggestion * rename wa-reduce-fouce to wa-cloak * remove class as requested * add cloak class * wait a cycle * move turbo to same file * reduce fade * disable SSR and add Turbo FOUCE helper * disable SSR * fix test suite * workflow dispatch * update fouce util * no need to remove cloak class * simplify fouce util * add allDefined util * update changelog --------- Co-authored-by: Lea Verou <lea@verou.me> Co-authored-by: konnorrogers <konnor5456@gmail.com>
This commit is contained in:
@@ -1,20 +1,17 @@
|
||||
/*
|
||||
* Utility to minimize FOUCE and show custom elements only after they're registered
|
||||
*/
|
||||
:not(:defined),
|
||||
:state(wa-defined):has(:not(:defined)),
|
||||
.wa-cloak:has(:not(:defined)) {
|
||||
animation: 2s step-end wa-fouce-cloak;
|
||||
}
|
||||
|
||||
@keyframes wa-fade-in {
|
||||
@keyframes wa-fouce-cloak {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:not(:defined),
|
||||
:state(wa-defined):has(:not(:defined)) {
|
||||
/* The clamp() ensures that if --wa-fouce-timeout is set to 0s, the whole effect is disabled */
|
||||
--wa-fouce-animation: clamp(0s, var(--wa-fouce-fade, 200ms), var(--wa-fouce-timeout, 2s)) var(--wa-fouce-timeout, 2s)
|
||||
wa-fade-in both;
|
||||
|
||||
&:where(:not([did-ssr], .wa-fouce-off, .wa-fouce-off *)) {
|
||||
animation: var(--wa-fouce-animation);
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,12 @@ export async function discover(root: Element | ShadowRoot) {
|
||||
console.warn(imp.reason); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
// Wait a cycle to allow the first Lit update to run
|
||||
await new Promise(requestAnimationFrame);
|
||||
|
||||
// Dispatch an event when discovery is complete.
|
||||
document.dispatchEvent(new CustomEvent('wa-discovery-complete'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,3 +75,22 @@ function register(tagName: string): Promise<void> {
|
||||
import(path).then(() => resolve()).catch(() => reject(new Error(`Unable to autoload <${tagName}> from ${path}`)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts as a middleware for Turbo's `turbo:before-render` event to ensure components are auto-loaded before showing the
|
||||
* next page, eliminating page-to-page FOUCE in a Turbo environment.
|
||||
*/
|
||||
export function preventTurboFouce(timeout = 2000) {
|
||||
document.addEventListener('turbo:before-render', async (event: CustomEvent) => {
|
||||
const newBody = event.detail.newBody;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
// Wait until all elements are registered or two seconds, whichever comes first
|
||||
await Promise.race([discover(newBody), new Promise(resolve => setTimeout(resolve, timeout))]);
|
||||
} finally {
|
||||
event.detail.resume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
64
src/utilities/defined.ts
Normal file
64
src/utilities/defined.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
interface AllDefinedOptions {
|
||||
/**
|
||||
* A callback that accepts a custom element tag name and returns `true` if the custom element should be defined before
|
||||
* resolving or `false` to skip it. The tag name is always in lowercase.
|
||||
*/
|
||||
match: (tagName: string) => boolean;
|
||||
|
||||
/**
|
||||
* To wait for additional custom elements that may not be on the page when the function is called, provide them here.
|
||||
*/
|
||||
additionalElements: string | string[];
|
||||
|
||||
/**
|
||||
* The root in which to look for custom elements. Defaults to `document`. By design, shadow roots are not traversed,
|
||||
* but you can call this function and set `root` to a custom element's shadow root if needed.
|
||||
*/
|
||||
root: Document | ShadowRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for custom elements that are currently on the page to be registered before resolving. This is sugar for
|
||||
* awaiting `customElements.whenDefined()` multiple times. By default, the function waits for all undefined Web Awesome
|
||||
* elements, but you can pass a custom match function to wait for other custom elements instead.
|
||||
*
|
||||
* The function returns with `Promise.all()`, so any loading errors will cause it to reject. Make sure you handle errors
|
||||
* accordingly using a try/catch block or a `.catch()`.
|
||||
*
|
||||
* @example
|
||||
* // Wait for Web Awesome elements
|
||||
* await allDefined();
|
||||
*
|
||||
* // Wait for all custom elements that start with `foo-` as well as the `<bar-button>` element
|
||||
* await allDefined({
|
||||
* match: tagName => tagName.startsWith('foo-'),
|
||||
* additionalElements: ['bar-button', 'baz-dialog']
|
||||
* });
|
||||
*/
|
||||
export async function allDefined(options?: Partial<AllDefinedOptions>) {
|
||||
const opts: AllDefinedOptions = {
|
||||
match: tagName => tagName.startsWith('wa-'),
|
||||
additionalElements: [],
|
||||
root: document,
|
||||
...options,
|
||||
};
|
||||
|
||||
// Ensure additional elements is an array
|
||||
const additionalElements = Array.isArray(opts.additionalElements)
|
||||
? opts.additionalElements
|
||||
: [opts.additionalElements];
|
||||
|
||||
// Discover undefined elements in the document
|
||||
const undefinedElements = [...opts.root.querySelectorAll(':not(:defined)')]
|
||||
.map(el => el.localName)
|
||||
.filter((tag, index, arr) => arr.indexOf(tag) === index) // make it unique
|
||||
.filter(tag => opts.match(tag)); // make sure it matches
|
||||
|
||||
const tagsToAwait = [...undefinedElements, ...additionalElements];
|
||||
|
||||
// Wait for all to be registered
|
||||
await Promise.all(tagsToAwait.map(tag => customElements.whenDefined(tag)));
|
||||
|
||||
// Wait a cycle for the first update
|
||||
await new Promise(requestAnimationFrame);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export { registerIconLibrary, unregisterIconLibrary } from './components/icon/library.js';
|
||||
export { discover, startLoader, stopLoader } from './utilities/autoloader.js';
|
||||
export { discover, preventTurboFouce, startLoader, stopLoader } from './utilities/autoloader.js';
|
||||
export { getBasePath, getKitCode, setBasePath, setKitCode } from './utilities/base-path.js';
|
||||
export { allDefined } from './utilities/defined.js';
|
||||
export { registerTranslation } from './utilities/localize.js';
|
||||
|
||||
// Utilities
|
||||
|
||||
Reference in New Issue
Block a user