diff --git a/src/components/icon/library.ts b/src/components/icon/library.ts index dc8eeaffc..130edcc5a 100644 --- a/src/components/icon/library.ts +++ b/src/components/icon/library.ts @@ -9,14 +9,28 @@ export const CACHEABLE_HTTP_ERRORS = [410]; let parser: DOMParser; +const defaultFallback = function (name: string, family?: string, variant?: string): IconLocator | undefined { + if (this.name !== 'wa') { + return { + name, + family, + variant, + library: 'wa', + }; + } + + return undefined; +}; + export default class IconLibrary { /** The original object used to create this library */ - private spec: UnregisteredIconLibrary; + readonly spec: UnregisteredIconLibrary; readonly name: string; readonly mutator?: IconLibraryMutator; readonly system?: IconMapping; readonly spriteSheet?: boolean; + readonly fallback?: IconMapping; /** Inlined markup, keyed by URL */ inlined: IconLibraryCacheFlat = {}; @@ -33,6 +47,7 @@ export default class IconLibrary { this.mutator = library.mutator; this.system = library.system; this.spriteSheet = library.spriteSheet; + this.fallback = library.fallback ?? defaultFallback; if (library.inlined) { this.inline(library.inlined); @@ -42,7 +57,7 @@ export default class IconLibrary { /** * Convert an icon name, family, and variant into a URL */ - getUrl(name: string, family?: string, variant?: string) { + getUrl(name: string, family?: string, variant?: string): string { // console.warn('getUrl', name, family, variant); if (name.startsWith('system:')) { name = name.slice(7); @@ -54,6 +69,14 @@ export default class IconLibrary { name = resolved.name ?? name; family = resolved.family ?? family; variant = resolved.variant ?? variant; + + if (resolved.library && resolved.library !== this.name) { + let library = IconLibrary.registry.get(resolved.library); + + if (library) { + return library.getUrl(name, family, variant); + } + } } } } @@ -125,7 +148,15 @@ export default class IconLibrary { // Try again with fallback let fallback = this.spec.fallback(name, family, variant); if (fallback) { - return this.getElement(fallback.name, fallback.family, fallback.variant); + let library: IconLibrary | undefined = this; + + if (fallback.library && fallback.library !== this.name) { + library = IconLibrary.registry.get(fallback.library); + } + + if (library) { + return library.getElement(fallback.name, fallback.family, fallback.variant); + } } } @@ -195,14 +226,20 @@ export default class IconLibrary { Object.assign(this.inlined, flatCache); } + + /** + * Create a clone of this library, optionally overriding some of its properties. + */ + extend(library: Partial = {}) { + return new IconLibrary({ ...this.spec, ...library }); + } + + static registry = new Map(); } export type IconLibraryResolver = (name: string, family?: string, variant?: string) => string; -export type IconMapping = ( - name: string, - family?: string, - variant?: string, -) => { name: string; family?: string; variant?: string; library?: string } | undefined; +export type IconLocator = { name: string; family?: string; variant?: string; library?: string }; +export type IconMapping = (name: string, family?: string, variant?: string) => IconLocator | undefined; export type IconLibraryGetKey = (name: string) => string; export type IconLibraryMutator = (svg: SVGElement) => void; export type IconFetchedResult = string | typeof CACHEABLE_ERROR | typeof RETRYABLE_ERROR; diff --git a/src/components/icon/library.default.ts b/src/components/icon/library.wa.ts similarity index 99% rename from src/components/icon/library.default.ts rename to src/components/icon/library.wa.ts index 638ef4c22..6b78de1fa 100644 --- a/src/components/icon/library.default.ts +++ b/src/components/icon/library.wa.ts @@ -108,7 +108,7 @@ export const inlined: IconLibraryCacheDeep = { }; const library: UnregisteredIconLibrary = { - name: 'default', + name: 'wa', getUrl: getIconUrl, // Cache icons using the free URL getCacheKey: url => url.replace(/\?token=[^&]+/, '').replace(/ka-p\./, 'ka-f.'), diff --git a/src/components/icon/registry.ts b/src/components/icon/registry.ts index b2ec1e5d1..a9cd6f7d3 100644 --- a/src/components/icon/registry.ts +++ b/src/components/icon/registry.ts @@ -1,5 +1,4 @@ import type WaIcon from './icon.js'; -import defaultLibrary from './library.default.js'; import IconLibrary, { CACHEABLE_ERROR, RETRYABLE_ERROR, @@ -9,14 +8,16 @@ import IconLibrary, { type IconLibraryCacheFlat, type UnregisteredIconLibrary, } from './library.js'; +import waDefaultLibrary from './library.wa.js'; export { CACHEABLE_ERROR, RETRYABLE_ERROR, fetchIcon }; export type { IconFetchedResult, IconLibrary, IconLibraryCacheDeep, IconLibraryCacheFlat, UnregisteredIconLibrary }; -let registry: IconLibrary[] = []; +let registry = IconLibrary.registry; let watchedIcons: WaIcon[] = []; -registerIconLibrary(defaultLibrary); +registerIconLibrary(waDefaultLibrary); +registerIconLibrary('default', waDefaultLibrary); registerIconLibrary({ name: 'custom' }); /** Adds an icon to the list of watched icons. */ @@ -31,16 +32,37 @@ export function unwatchIcon(icon: WaIcon) { /** Returns a library from the registry. */ export function getIconLibrary(name?: string) { - return registry.find(lib => lib.name === name); + return name ? registry.get(name) : undefined; } -/** Adds an icon library to the registry, or overrides an existing one. */ -export function registerIconLibrary(library: UnregisteredIconLibrary) { - unregisterIconLibrary(library.name); +/** + * Adds an icon library to the registry, or overrides an existing one. + * Optionally accepts a name argument, which will override the library's built-in name, allowing you to register aliases. + */ +export function registerIconLibrary(name: string, library: UnregisteredIconLibrary | IconLibrary): void; +export function registerIconLibrary(library: UnregisteredIconLibrary | IconLibrary): void; +export function registerIconLibrary( + nameOrLibrary: string | UnregisteredIconLibrary | IconLibrary, + library?: UnregisteredIconLibrary | IconLibrary, +) { + let name; + if (typeof nameOrLibrary === 'string') { + name = nameOrLibrary; + } else { + library = nameOrLibrary; + } - let registeredLibrary = new IconLibrary(library); + if (!library) { + throw new Error('No library provided'); + } - registry.push(registeredLibrary); + let instance = library instanceof IconLibrary ? library : new IconLibrary(library); + + if (name) { + instance = instance.extend({ name }); + } + + registry.set(instance.name, instance); // Redraw watched icons watchedIcons.forEach(icon => { @@ -52,5 +74,5 @@ export function registerIconLibrary(library: UnregisteredIconLibrary) { /** Removes an icon library from the registry. */ export function unregisterIconLibrary(name: string) { - registry = registry.filter(lib => lib.name !== name); + registry.delete(name); } diff --git a/src/webawesome.ts b/src/webawesome.ts index 563e7b95e..614cf4043 100644 --- a/src/webawesome.ts +++ b/src/webawesome.ts @@ -1,4 +1,4 @@ -export { getKitCode, setKitCode } from './components/icon/library.default.js'; +export { getKitCode, setKitCode } from './components/icon/library.wa.js'; export { registerIconLibrary, unregisterIconLibrary } from './components/icon/registry.js'; export { discover, preventTurboFouce, startLoader, stopLoader } from './utilities/autoloader.js'; export { getBasePath, setBasePath } from './utilities/base-path.js';