|
| 1 | +/** |
| 2 | + * @fileoverview Provides primitives to easily implement the on-demand |
| 3 | + * definitions community protocol. |
| 4 | + * |
| 5 | + * @see https://github.com/webcomponents-cg/community-protocols/pull/67 |
| 6 | + */ |
| 7 | + |
| 8 | +/** |
| 9 | + * Defines the custom element. |
| 10 | + * |
| 11 | + * @param registry The registry to define the custom element in. Defaults to the |
| 12 | + * global {@link customElements} registry. |
| 13 | + * @param tagName The tag name to define the custom element as. Uses a default |
| 14 | + * tag name when not specified. Using an explicit tag name is only supported |
| 15 | + * when using a non-global registry |
| 16 | + */ |
| 17 | +export type Define = |
| 18 | + (registry?: CustomElementRegistry, tagName?: string) => void; |
| 19 | + |
| 20 | +/** |
| 21 | + * A class definition which implements the on-demand definitions community |
| 22 | + * protocol. |
| 23 | + * |
| 24 | + * Note that because `define` is static, this type should be applied to the |
| 25 | + * custom element class type, not the instance type. |
| 26 | + * |
| 27 | + * ```typescript |
| 28 | + * class MyElement extends HTMLElement { |
| 29 | + * static define() { ... } |
| 30 | + * } |
| 31 | + * |
| 32 | + * const definable = MyElement as Defineable; |
| 33 | + * ``` |
| 34 | + */ |
| 35 | +export interface Defineable { |
| 36 | + /** |
| 37 | + * Defines the custom element. |
| 38 | + * |
| 39 | + * @param registry The registry to define the custom element in. Defaults to |
| 40 | + * the global {@link customElements} registry. |
| 41 | + * @param tagName The tag name to define the custom element as. Uses a default |
| 42 | + * tag name when not specified. Using an explicit tag name is only |
| 43 | + * supported when a using non-global registry |
| 44 | + */ |
| 45 | + define: Define; |
| 46 | +} |
| 47 | + |
| 48 | +/** |
| 49 | + * Defines the provided custom element in the global registry if that element |
| 50 | + * implements the on-demand definitions community protocol. |
| 51 | + * |
| 52 | + * @param Clazz The custom element class to define. |
| 53 | + */ |
| 54 | +export function defineIfSupported(Clazz: typeof Element): void { |
| 55 | + (Clazz as Partial<Defineable>).define?.(); |
| 56 | +} |
| 57 | + |
| 58 | +/** |
| 59 | + * Creates a {@link Define} function which defines the given custom element with |
| 60 | + * the default tag name. The returned function should be used as the static |
| 61 | + * `define` function in a {@link Defineable} custom element. |
| 62 | + * |
| 63 | + * @param defaultTagName The tag name to use in the global registry and by |
| 64 | + * default for scoped registries. |
| 65 | + * @param Clazz The custom element class to define. |
| 66 | + * @param options Options for the {@link CustomElementRegistry.prototype.define} |
| 67 | + * call. |
| 68 | + */ |
| 69 | +export function createDefine( |
| 70 | + defaultTagName: string, |
| 71 | + Clazz: typeof HTMLElement, |
| 72 | + options?: ElementDefinitionOptions, |
| 73 | +): Define { |
| 74 | + return (registry = customElements, tagName = defaultTagName) => { |
| 75 | + // Tag name can only be modified when not in the global registry. |
| 76 | + if (registry === customElements && tagName !== defaultTagName) { |
| 77 | + throw new Error('Cannot use a non-default tag name in the global custom element registry.'); |
| 78 | + } |
| 79 | + |
| 80 | + // Check if the tag name was already defined by another class. |
| 81 | + const existing = registry.get(tagName); |
| 82 | + if (existing) { |
| 83 | + if (existing === Clazz) { |
| 84 | + return; // Already defined as the correct class, no-op. |
| 85 | + } else { |
| 86 | + throw new Error(`Tag name \`${tagName}\` already defined as \`${ |
| 87 | + existing.name}\`.`); |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + // Define the class. |
| 92 | + registry.define(tagName, Clazz, options); |
| 93 | + }; |
| 94 | +} |
0 commit comments