UNPKG

vite-awesome-svg-loader

Version:

A universal Vite SVG loader. Imports SVGs as source code, base64 and data URI. Preserves stroke width. Replaces colors with currentColor or custom colors. Creates SVG sprites. Optimizes SVGs.

348 lines (347 loc) 13 kB
import { CustomElement } from 'typed-custom-elements'; /** Describes {@link WebComponent} property */ export interface WebComponentProp { /** Property name */ name: string; /** Default value */ default?: string; } /** Basic custom element's definition options that should be passed to {@link WebComponent.define} */ export interface BasicWebComponentDefinitionOptions { /** Custom element tag name */ tag: string; /** * Where to define given element * * @default customElements */ registry?: CustomElementRegistry; /** * Whether to ignore an error that is thrown when an element is defined second time under a different tag. * * This option is intended for when a web component uses another web component which may not be defined before * its construction. * * @default false */ noRedefinitionError?: boolean; } /** * Definition options that should be used by concrete web components. Components must provide default values * if necessary. */ export interface WebComponentDefinitionOptions extends Partial<BasicWebComponentDefinitionOptions> { } /** * Current component state. Possible values: * * 1. `uninitialized` - Initialization has not yet run. * 1. `initialization` - Component is initializing its internal state. * 1. `attrs-first-change` - Component is calling {@link WebComponent.attributeChangedCallback} for the first time right * after initialization is completed. * 1. `normal` - Normal state. */ export type WebComponentState = "uninitialized" | "initialization" | "attrs-first-change" | "normal"; /** Resolved and normalized property config */ export interface ResolvedWebComponentProp extends Omit<WebComponentProp, "name"> { } /** * ### Introduction * * This is the base web components class and the whole web components implementation. * * This is just a * [custom element](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) * with a number of necessary features missing from the standard custom elements. * * ### Implementation goals * * This web components implementation was designed with the following goals in mind: * * 1. Improve upon custom elements: * 1. Implement necessary features that are missing from the standard custom elements. * 1. Maintain compliance with the custom elements specification. * 1. Maintain compatibility with other libraries and frameworks without additional setup. * 1. Keep implementation minimal and size small: * 1. Don't pollute application's bundle with the code that most likely won't be used. * 1. Don't implement features for the sake of having features. * 1. Achieve maximum performance. * * As you can see, this is **not** a framework. There are no quality-of-life abstractions and features found in major * frameworks like [Lit](https://lit.dev/). Ultimately, this is a bare minimum to ship off web components integration. * * ### When to use this * * When you are: * * - Occasionally using web components (or just `vite-awesome-svg-loader` alone). * - Building a wrapper around `vite-awesome-svg-loader`. * - Having other reasons to use a **minimal** web components implementation like this one. * * ### When to NOT use this * * When you are: * * - Planning or already using a dedicated library for managing web components. * * Just use that library. * * - Planning to build more web components. * * Consider using a dedicated library. It will pay off in the long run. * * ### Behavioral differences from custom elements * * 1. All attributes are observed automatically. No need for {@link WebComponent.observedAttributes}. * * 1. {@link WebComponent.attributeChangedCallback} is called when component has initialized and when any attribute's * value has **actually** changed. Setting same value doesn't count as a change. * * 1. Properties can be synchronized with attributes by defining them in {@link WebComponent.props}. * * ### Core principles * * 1. Use constructors to initialize state. But do **not** use properties defined in {@link WebComponent.props} * in a constructor because that will trigger an attribute setter which renders constructor invalid * (per custom elements specification). * * 1. Use {@link WebComponent.connectedCallback} to initialize markup. * * 1. Use {@link WebComponent.attributeChangedCallback} to track property/attribute changes. Do **not** define your own * getters and setters, they will be overridden. * * 1. Prefer inheritance over composition. Yes, this is an anti-pattern. Web components are actual elements. * The more components you nest, the larger and slower to process DOM becomes. If you want composition, either sacrifice * performance or use a specialized library/framework. * * 1. When using a web component inside another web component, call `define({ noRedefinitionError: true })` and * use a constructor instead of `document.createElement()`. * * Suppress a redefinition error because in most cases user will not specify different tag and would not expect * an error if they haven't defined a dependency component. * * Use a constructor because user still might decide to use a custom tag. * * Ultimately, user will get an error, if the definition is invalid. * * 1. If you've encountered a problem that must be resolved immediately by modifying the internals, * everything's here for you. Just use `// @ts-ignore` to suppress the errors. After the hotfix, please file an issue * and explain your case. * * ### Creating web components * * Let's create a simple `BusinessCard` web component: * * ```ts * class BusinessCard extends WebComponent implements CustomElement { * // Define class properties that will be synced with attributes * * static readonly props = ["personName", { name: "orgName", default: "Not affiliated" }]; * * // Declare properties for type-checking to work. You may actually define them, but they will be overridden * // with proper getters and setters, so don't create empty overhead. * * declare personName?: string; * declare orgName: string; * * // Add markup * * connectedCallback() { * super.connectedCallback(); * * if (this.children.length) { * return * } * * const personName = document.createElement("div"); * personName.className = "person-name"; * this.appendChild(personName); * * const orgName = document.createElement("div"); * orgName.className = "org-name"; * this.appendChild(orgName); * } * * // Track changes * * attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) { * switch (name) { * // props are tracked: * case "personName": * case "orgName": * this.getElementsByClassName(name)[0]!.innerText = newValue || "-"; * break; * * // Every other attribute is also tracked: * case "data-some-attr": * console.log(oldValue, newValue); * break; * } * } * * // Provide default tag for definition (optional, but recommended) * * define(options?: WebComponentDefinitionOptions) { * super.define({ ...options, tag: options.tag || "business-card" }); * } * } * * // Define a custom element * * BusinessCard.define(); * ``` * * ### Using web components * * Let's instantiate and mount a `BusinessCard` web component that we've created: * * ```ts * // Create a web component instance: * * const card = new BusinessCard(); * // or: * const card = document.createElement("business-card"); * * // Add component to the DOM: * * document.getElementById("card")!.appendChild(card); * * // Change properties: * * card.firstName = "John"; * // or: * card.setAttribute("first-name", "John"); // Notice that attribute is in snake-case while property is in camelCase * ``` * * ### Extending web components * * Let's create a `CoolBusinessCard` component by extending `BusinessCard` component: * * ```ts * class CoolBusinessCard extends BusinessCard implements CustomElement { * // Define new properties. Inherited properties will be defined and copied to "props" automatically. * // Note: it is impossible to redefine inherited properties because parent relies on their behavior. * * static readonly props = [ * { name: "coolness", default: "120%" }, * ]; * * declare coolness: string; * * // Provide default tag for definition * * define(options: WebComponentDefinitionOptions = {}) { * super.define({ ...options, tag: options.tag || "cool-business-card" }); * } * } * ``` */ export declare class WebComponent extends HTMLElement implements CustomElement { /** * Doesn't do anything. All attributes are already observed. This property will be deleted when custom element * will be defined. * * This property is recognized for compatibility reasons only. */ static observedAttributes?: string[]; /** * Properties that will be synced with attributes. * * Properties must be in `camelCase`. Synchronized attributes will be in `kebab-case`. * * To not confuse cases, prefer single-word names. */ static props: (string | WebComponentProp)[]; /** * Current component state. See {@link WebComponentState} for the details. */ protected _state: WebComponentState; /** * Initial property values. * * If element is created before it is defined, and user will set properties, these properties will be own properties * of the element. When element is upgraded, these properties are kept as own. * Children's constructors may override the base class's properties.` * * The initialization logic must re-apply user's values. To do so, we save them here in the constructor before they're * overridden. * * This property is deleted after initialization is complete. */ private _initialValues?; /** * Called when: * * 1. Component has just finished initialization. Then: * * 1. {@link _state} property will be `attrs-first-change`. * 1. `oldValue` argument will be `null`. * 1. `newValue` will have a default value or `null`, if there's no default. * 1. Thus, following may be true: `oldValue === newValue && newValue === null`. * * 1. **Any** of the element's attributes has **actually** changed (unlike default custom elements' behavior where * this method is called even if the same value has been set). Then: * * 1. {@link _state} property will be `normal`. * 1. `oldValue` and `newValue` will be always different. */ attributeChangedCallback?(...args: Parameters<NonNullable<CustomElement["attributeChangedCallback"]>>): void; constructor(); /** * Initializes component state. * * Custom elements may not add attributes or children in a constructor, so separate initialization is required. * Thus, this method is called whenever an attribute is accessed or in a microtask after component's constructor * has run. * * Initialization is performed only once. */ protected _init(): void; /** * Called when the element is added to a document. * * If overridden, `super.connectedCallback()` must be the first statement. */ connectedCallback(): void; getAttribute(name: string): string | null; setAttribute(name: string, value: string): void; removeAttribute(name: string): void; toggleAttribute(name: string, force?: boolean): boolean; /** * A common attribute setter called by attributes manipulation methods (`setAttribute`, `removeAttribute`, etc) * and property setters * * @param name Attribute name * @param value Attribute value */ private _setAttr; /** * Returns a constructor of this class * * If you need to refer to your own class, type it like so: * * ```ts * class MyClass extends WebComponent { * declare protected _ctor: () => typeof MyClass * } * ``` * * @returns constructor of this class */ protected _ctor(): typeof WebComponent; /** @returns Current web component's metadata */ private _getMeta; /** * Defines a custom element for this web component. * * Won't throw an error if called multiple times with the same tag. * * Will throw an error (unless suppressed via {@link BasicWebComponentDefinitionOptions.noRedefinitionError}) * if defined under a different tag. * * @param options Definition options */ static define(options: BasicWebComponentDefinitionOptions): void; /** Initializes class itself. Called by {@link define} and by the constructor. Initialization happens only once. */ protected static _initClass(): void; }