@weasel-js/icons 0.0.0-stage → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +27 -2
- package/dist/index.d.ts +90 -0
- package/dist/index.js +173 -0
- package/dist/index.js.map +1 -0
- package/package.json +22 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 orochi235
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @weasel-js/icons
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Namespaced icon names (`namespace:name`) and the icon registry. No React, no DOM, no glyphs of its
|
|
4
|
+
own: each weasel package that draws glyphs publishes its own table, at `@weasel-js/<package>/icons`.
|
|
5
|
+
The build plugin that ships only the glyphs a build names is `@weasel-js/icons-plugin`.
|
|
6
|
+
|
|
7
|
+
| Export | What it is |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `IconRef` | A qualified name, branded. Each package's generated `icon()` makes one; `toIconRef(value)` reads one from data |
|
|
10
|
+
| `IconName` | Interface whose keys are every qualified name; packages extend it by declaration merging |
|
|
11
|
+
| `scopedIcon(namespace)` | Builds a package's `icon()`, which qualifies a bare name with that namespace |
|
|
12
|
+
| `IconTable` | `{ namespace, owner, icons }`: every glyph one publisher ships under one namespace |
|
|
13
|
+
| `createIconRegistry()` | Holds tables and overrides; refuses a namespace another owner holds |
|
|
14
|
+
|
|
15
|
+
## In an app
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { IconRegistryProvider } from '@weasel-js/core';
|
|
19
|
+
import { icons as coreIcons } from '@weasel-js/core/icons';
|
|
20
|
+
import { icons as uiIcons } from '@weasel-js/ui/icons';
|
|
21
|
+
|
|
22
|
+
const ICONS = [coreIcons, uiIcons];
|
|
23
|
+
|
|
24
|
+
<IconRegistryProvider icons={ICONS}>…</IconRegistryProvider>;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
That registers every glyph core and ui draw. To ship only the glyphs the app names, use
|
|
28
|
+
`@weasel-js/icons-plugin`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
declare const brand: unique symbol;
|
|
2
|
+
/** A qualified icon name (`namespace:name`) that came from `icon()` or `toIconRef`. */
|
|
3
|
+
type IconRef = string & {
|
|
4
|
+
readonly [brand]: 'IconRef';
|
|
5
|
+
};
|
|
6
|
+
/** `name` in `namespace`, unless it already names one. */
|
|
7
|
+
declare function qualify(namespace: string, name: string): string;
|
|
8
|
+
/** The two halves of a qualified ref, or `undefined` for anything else. */
|
|
9
|
+
declare function splitIconRef(ref: string): {
|
|
10
|
+
namespace: string;
|
|
11
|
+
name: string;
|
|
12
|
+
} | undefined;
|
|
13
|
+
/** A string read at run time (a schema loaded as JSON) as a ref, when it is qualified. */
|
|
14
|
+
declare function toIconRef(value: string | undefined): IconRef | undefined;
|
|
15
|
+
/** A package's `icon()`: bare names resolve in `namespace` only. Generated into each package's `icons.gen.ts`. */
|
|
16
|
+
declare function scopedIcon<Own extends string>(namespace: string): (name: Own | QualifiedIconName) => IconRef;
|
|
17
|
+
|
|
18
|
+
/** One glyph: SVG body on a 20x20 viewBox stroked in currentColor, and how it is filed. */
|
|
19
|
+
interface IconGlyph {
|
|
20
|
+
readonly body: string;
|
|
21
|
+
/** The enclosed region `<Icon filled>` shades, when there is one. */
|
|
22
|
+
readonly fill?: string;
|
|
23
|
+
readonly group: string;
|
|
24
|
+
readonly tags: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
/** Every glyph one publisher ships under one namespace. */
|
|
27
|
+
interface IconTable {
|
|
28
|
+
readonly namespace: string;
|
|
29
|
+
/** The publishing package. Tables with one namespace merge only when their owners match. */
|
|
30
|
+
readonly owner: string;
|
|
31
|
+
readonly icons: Readonly<Record<string, IconGlyph>>;
|
|
32
|
+
}
|
|
33
|
+
/** What a variant's dynamic import resolves to. */
|
|
34
|
+
interface IconVariantModule {
|
|
35
|
+
readonly default: readonly IconTable[];
|
|
36
|
+
}
|
|
37
|
+
/** Loads one variant module: the icon tables that variant adds. */
|
|
38
|
+
type IconVariantLoader = () => Promise<IconVariantModule>;
|
|
39
|
+
|
|
40
|
+
/** Where a named icon variant stands: no loader for it (`unknown`), a loader not yet run (`idle`), in flight, in, or failed. */
|
|
41
|
+
type IconVariantState = 'unknown' | 'idle' | 'loading' | 'loaded' | 'failed';
|
|
42
|
+
|
|
43
|
+
/** One icon as the registry reports it. */
|
|
44
|
+
interface IconEntry {
|
|
45
|
+
readonly ref: IconRef;
|
|
46
|
+
readonly glyph: IconGlyph;
|
|
47
|
+
readonly owner: string;
|
|
48
|
+
/** Registered with `override`, over a table's glyph. */
|
|
49
|
+
readonly overridden: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Thrown when a table claims a namespace another owner holds. */
|
|
52
|
+
declare class IconNamespaceClaimedError extends Error {
|
|
53
|
+
readonly namespace: string;
|
|
54
|
+
readonly owner: string;
|
|
55
|
+
readonly claimant: string;
|
|
56
|
+
constructor(namespace: string, owner: string, claimant: string);
|
|
57
|
+
}
|
|
58
|
+
/** Resolves icon refs to glyphs across registered tables and overrides, and reports who owns each namespace. */
|
|
59
|
+
interface IconRegistry {
|
|
60
|
+
get(ref: string): IconGlyph | undefined;
|
|
61
|
+
add(tables: IconTable | readonly IconTable[]): () => void;
|
|
62
|
+
override(ref: IconRef, glyph: IconGlyph, source?: string): () => void;
|
|
63
|
+
owner(namespace: string): string | undefined;
|
|
64
|
+
/** The same array until the registry next changes, so it is safe to read through `useSyncExternalStore`. */
|
|
65
|
+
entries(): readonly IconEntry[];
|
|
66
|
+
provideVariants(loaders: Readonly<Record<string, IconVariantLoader>>): void;
|
|
67
|
+
loadVariant(name: string): Promise<void>;
|
|
68
|
+
variantState(name: string): IconVariantState;
|
|
69
|
+
subscribe(listener: () => void): () => void;
|
|
70
|
+
getVersion(): number;
|
|
71
|
+
}
|
|
72
|
+
/** A registry, optionally seeded with tables; each `add` and `override` returns its own release function. */
|
|
73
|
+
declare function createIconRegistry(initial?: IconTable | readonly IconTable[]): IconRegistry;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* `@weasel-js/icons`: icon names, tables and the registry. No React, no DOM, no glyphs.
|
|
77
|
+
*
|
|
78
|
+
* @packageDocumentation
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Every qualified icon name, as keys. Each package's generated `icons.gen.ts`
|
|
82
|
+
* adds its own by declaration merging. Declared here, in the barrel, because an
|
|
83
|
+
* augmentation aimed at a module that only re-exports this would declare a fresh one.
|
|
84
|
+
*/
|
|
85
|
+
interface IconName {
|
|
86
|
+
}
|
|
87
|
+
/** A name some loaded package declares. */
|
|
88
|
+
type QualifiedIconName = Extract<keyof IconName, string>;
|
|
89
|
+
|
|
90
|
+
export { type IconEntry, type IconGlyph, type IconName, IconNamespaceClaimedError, type IconRef, type IconRegistry, type IconTable, type IconVariantLoader, type IconVariantModule, type IconVariantState, type QualifiedIconName, createIconRegistry, qualify, scopedIcon, splitIconRef, toIconRef };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createEmitter, isDev } from '@weasel-js/registry';
|
|
2
|
+
|
|
3
|
+
// src/names.ts
|
|
4
|
+
var QUALIFIED = /^([^:\s]+):([^:\s]+)$/;
|
|
5
|
+
function qualify(namespace, name) {
|
|
6
|
+
return name.includes(":") ? name : `${namespace}:${name}`;
|
|
7
|
+
}
|
|
8
|
+
function splitIconRef(ref) {
|
|
9
|
+
const m = QUALIFIED.exec(ref);
|
|
10
|
+
return m ? { namespace: m[1], name: m[2] } : void 0;
|
|
11
|
+
}
|
|
12
|
+
function toIconRef(value) {
|
|
13
|
+
return value !== void 0 && QUALIFIED.test(value) ? value : void 0;
|
|
14
|
+
}
|
|
15
|
+
function scopedIcon(namespace) {
|
|
16
|
+
return (name) => qualify(namespace, name);
|
|
17
|
+
}
|
|
18
|
+
function createVariants(addTables, changed) {
|
|
19
|
+
const loaders = /* @__PURE__ */ new Map();
|
|
20
|
+
const states = /* @__PURE__ */ new Map();
|
|
21
|
+
const pending = /* @__PURE__ */ new Map();
|
|
22
|
+
const set = (name, state) => {
|
|
23
|
+
states.set(name, state);
|
|
24
|
+
changed();
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
provide(next) {
|
|
28
|
+
for (const [name, loader] of Object.entries(next)) {
|
|
29
|
+
loaders.set(name, loader);
|
|
30
|
+
if (!states.has(name)) states.set(name, "idle");
|
|
31
|
+
}
|
|
32
|
+
changed();
|
|
33
|
+
},
|
|
34
|
+
state(name) {
|
|
35
|
+
return states.get(name) ?? "unknown";
|
|
36
|
+
},
|
|
37
|
+
load(name) {
|
|
38
|
+
const loader = loaders.get(name);
|
|
39
|
+
if (!loader || states.get(name) === "loaded") return Promise.resolve();
|
|
40
|
+
const inFlight = pending.get(name);
|
|
41
|
+
if (inFlight) return inFlight;
|
|
42
|
+
set(name, "loading");
|
|
43
|
+
const run = Promise.resolve().then(loader).then((mod) => {
|
|
44
|
+
addTables(mod.default);
|
|
45
|
+
set(name, "loaded");
|
|
46
|
+
}).catch((error) => {
|
|
47
|
+
if (isDev()) console.error(`icon variant "${name}" failed to load`, error);
|
|
48
|
+
set(name, "failed");
|
|
49
|
+
}).finally(() => pending.delete(name));
|
|
50
|
+
pending.set(name, run);
|
|
51
|
+
return run;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/registry.ts
|
|
57
|
+
var IconNamespaceClaimedError = class extends Error {
|
|
58
|
+
constructor(namespace, owner, claimant) {
|
|
59
|
+
super(`icon namespace "${namespace}" belongs to ${owner}; ${claimant} cannot register under it`);
|
|
60
|
+
this.namespace = namespace;
|
|
61
|
+
this.owner = owner;
|
|
62
|
+
this.claimant = claimant;
|
|
63
|
+
this.name = "IconNamespaceClaimedError";
|
|
64
|
+
}
|
|
65
|
+
namespace;
|
|
66
|
+
owner;
|
|
67
|
+
claimant;
|
|
68
|
+
};
|
|
69
|
+
var OVERRIDE = "override";
|
|
70
|
+
var lastOf = (it) => {
|
|
71
|
+
let last;
|
|
72
|
+
for (const v of it) last = v;
|
|
73
|
+
return last;
|
|
74
|
+
};
|
|
75
|
+
function createIconRegistry(initial) {
|
|
76
|
+
const slots = /* @__PURE__ */ new Map();
|
|
77
|
+
const claims = /* @__PURE__ */ new Map();
|
|
78
|
+
const notify = createEmitter();
|
|
79
|
+
let version = 0;
|
|
80
|
+
let snapshot = null;
|
|
81
|
+
const changed = () => {
|
|
82
|
+
version++;
|
|
83
|
+
notify.emit();
|
|
84
|
+
};
|
|
85
|
+
const slotFor = (key) => {
|
|
86
|
+
let slot = slots.get(key);
|
|
87
|
+
if (!slot) slots.set(key, slot = { tables: /* @__PURE__ */ new Map(), overrides: [] });
|
|
88
|
+
return slot;
|
|
89
|
+
};
|
|
90
|
+
const prune = (key) => {
|
|
91
|
+
const slot = slots.get(key);
|
|
92
|
+
if (slot && slot.tables.size === 0 && slot.overrides.length === 0) slots.delete(key);
|
|
93
|
+
};
|
|
94
|
+
const live = (slot) => slot && (slot.overrides.at(-1) ?? lastOf(slot.tables.values()));
|
|
95
|
+
const unclaim = (namespace) => {
|
|
96
|
+
const held = claims.get(namespace);
|
|
97
|
+
if (!held) return;
|
|
98
|
+
if (held.count <= 1) claims.delete(namespace);
|
|
99
|
+
else claims.set(namespace, { ...held, count: held.count - 1 });
|
|
100
|
+
};
|
|
101
|
+
const add = (tables) => {
|
|
102
|
+
const list = Array.isArray(tables) ? tables : [tables];
|
|
103
|
+
const pending = /* @__PURE__ */ new Map();
|
|
104
|
+
for (const t of list) {
|
|
105
|
+
const owner = claims.get(t.namespace)?.owner ?? pending.get(t.namespace);
|
|
106
|
+
if (owner !== void 0 && owner !== t.owner) throw new IconNamespaceClaimedError(t.namespace, owner, t.owner);
|
|
107
|
+
pending.set(t.namespace, t.owner);
|
|
108
|
+
}
|
|
109
|
+
const releases = list.map((t) => {
|
|
110
|
+
const held = claims.get(t.namespace);
|
|
111
|
+
claims.set(t.namespace, { owner: t.owner, count: (held?.count ?? 0) + 1 });
|
|
112
|
+
const token = {};
|
|
113
|
+
const keys = Object.keys(t.icons).map((name) => `${t.namespace}:${name}`);
|
|
114
|
+
for (const key of keys) {
|
|
115
|
+
slotFor(key).tables.set(token, { glyph: t.icons[key.slice(t.namespace.length + 1)], owner: t.owner });
|
|
116
|
+
}
|
|
117
|
+
let released = false;
|
|
118
|
+
return () => {
|
|
119
|
+
if (released) return;
|
|
120
|
+
released = true;
|
|
121
|
+
unclaim(t.namespace);
|
|
122
|
+
for (const key of keys) {
|
|
123
|
+
slots.get(key)?.tables.delete(token);
|
|
124
|
+
prune(key);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
changed();
|
|
129
|
+
return () => {
|
|
130
|
+
releases.forEach((r) => r());
|
|
131
|
+
changed();
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
const variants = createVariants((tables) => void add(tables), changed);
|
|
135
|
+
if (initial) add(initial);
|
|
136
|
+
return {
|
|
137
|
+
get: (ref) => live(slots.get(ref))?.glyph,
|
|
138
|
+
add,
|
|
139
|
+
override(ref, glyph, source = OVERRIDE) {
|
|
140
|
+
const layer = { glyph, owner: source };
|
|
141
|
+
slotFor(ref).overrides.push(layer);
|
|
142
|
+
changed();
|
|
143
|
+
return () => {
|
|
144
|
+
const slot = slots.get(ref);
|
|
145
|
+
const i = slot ? slot.overrides.indexOf(layer) : -1;
|
|
146
|
+
if (i === -1) return;
|
|
147
|
+
slot.overrides.splice(i, 1);
|
|
148
|
+
prune(ref);
|
|
149
|
+
changed();
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
owner: (namespace) => claims.get(namespace)?.owner,
|
|
153
|
+
entries() {
|
|
154
|
+
if (snapshot?.version === version) return snapshot.entries;
|
|
155
|
+
const entries = [];
|
|
156
|
+
for (const [key, slot] of slots) {
|
|
157
|
+
const top = live(slot);
|
|
158
|
+
entries.push({ ref: key, glyph: top.glyph, owner: top.owner, overridden: slot.overrides.length > 0 });
|
|
159
|
+
}
|
|
160
|
+
snapshot = { version, entries: Object.freeze(entries) };
|
|
161
|
+
return snapshot.entries;
|
|
162
|
+
},
|
|
163
|
+
provideVariants: variants.provide,
|
|
164
|
+
loadVariant: variants.load,
|
|
165
|
+
variantState: variants.state,
|
|
166
|
+
subscribe: notify.subscribe,
|
|
167
|
+
getVersion: () => version
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export { IconNamespaceClaimedError, createIconRegistry, qualify, scopedIcon, splitIconRef, toIconRef };
|
|
172
|
+
//# sourceMappingURL=index.js.map
|
|
173
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/names.ts","../src/variants.ts","../src/registry.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { QualifiedIconName } from './index';\n\ndeclare const brand: unique symbol;\n\n/** A qualified icon name (`namespace:name`) that came from `icon()` or `toIconRef`. */\nexport type IconRef = string & { readonly [brand]: 'IconRef' };\n\nconst QUALIFIED = /^([^:\\s]+):([^:\\s]+)$/;\n\n/** `name` in `namespace`, unless it already names one. */\nexport function qualify(namespace: string, name: string): string {\n return name.includes(':') ? name : `${namespace}:${name}`;\n}\n\n/** The two halves of a qualified ref, or `undefined` for anything else. */\nexport function splitIconRef(ref: string): { namespace: string; name: string } | undefined {\n const m = QUALIFIED.exec(ref);\n return m ? { namespace: m[1]!, name: m[2]! } : undefined;\n}\n\n/** A string read at run time (a schema loaded as JSON) as a ref, when it is qualified. */\nexport function toIconRef(value: string | undefined): IconRef | undefined {\n return value !== undefined && QUALIFIED.test(value) ? (value as IconRef) : undefined;\n}\n\n/** A package's `icon()`: bare names resolve in `namespace` only. Generated into each package's `icons.gen.ts`. */\nexport function scopedIcon<Own extends string>(namespace: string) {\n return (name: Own | QualifiedIconName): IconRef => qualify(namespace, name) as IconRef;\n}\n","import { isDev } from '@weasel-js/registry';\nimport type { IconTable, IconVariantLoader } from './table';\n\n/** Where a named icon variant stands: no loader for it (`unknown`), a loader not yet run (`idle`), in flight, in, or failed. */\nexport type IconVariantState = 'unknown' | 'idle' | 'loading' | 'loaded' | 'failed';\n\n/** Loads each named variant at most once at a time; a failure leaves it retryable. */\nexport function createVariants(addTables: (tables: readonly IconTable[]) => void, changed: () => void) {\n const loaders = new Map<string, IconVariantLoader>();\n const states = new Map<string, IconVariantState>();\n const pending = new Map<string, Promise<void>>();\n\n const set = (name: string, state: IconVariantState) => {\n states.set(name, state);\n changed();\n };\n\n return {\n provide(next: Readonly<Record<string, IconVariantLoader>>): void {\n for (const [name, loader] of Object.entries(next)) {\n loaders.set(name, loader);\n if (!states.has(name)) states.set(name, 'idle');\n }\n changed();\n },\n state(name: string): IconVariantState {\n return states.get(name) ?? 'unknown';\n },\n load(name: string): Promise<void> {\n const loader = loaders.get(name);\n if (!loader || states.get(name) === 'loaded') return Promise.resolve();\n const inFlight = pending.get(name);\n if (inFlight) return inFlight;\n set(name, 'loading');\n const run = Promise.resolve()\n .then(loader)\n .then((mod) => {\n addTables(mod.default);\n set(name, 'loaded');\n })\n .catch((error: unknown) => {\n if (isDev()) console.error(`icon variant \"${name}\" failed to load`, error);\n set(name, 'failed');\n })\n .finally(() => pending.delete(name));\n pending.set(name, run);\n return run;\n },\n };\n}\n","import { createEmitter } from '@weasel-js/registry';\nimport type { IconRef } from './names';\nimport type { IconGlyph, IconTable, IconVariantLoader } from './table';\nimport { createVariants, type IconVariantState } from './variants';\n\nexport type { IconVariantState } from './variants';\n\n/** One icon as the registry reports it. */\nexport interface IconEntry {\n readonly ref: IconRef;\n readonly glyph: IconGlyph;\n readonly owner: string;\n /** Registered with `override`, over a table's glyph. */\n readonly overridden: boolean;\n}\n\n/** Thrown when a table claims a namespace another owner holds. */\nexport class IconNamespaceClaimedError extends Error {\n constructor(readonly namespace: string, readonly owner: string, readonly claimant: string) {\n super(`icon namespace \"${namespace}\" belongs to ${owner}; ${claimant} cannot register under it`);\n this.name = 'IconNamespaceClaimedError';\n }\n}\n\n/** Resolves icon refs to glyphs across registered tables and overrides, and reports who owns each namespace. */\nexport interface IconRegistry {\n get(ref: string): IconGlyph | undefined;\n add(tables: IconTable | readonly IconTable[]): () => void;\n override(ref: IconRef, glyph: IconGlyph, source?: string): () => void;\n owner(namespace: string): string | undefined;\n /** The same array until the registry next changes, so it is safe to read through `useSyncExternalStore`. */\n entries(): readonly IconEntry[];\n provideVariants(loaders: Readonly<Record<string, IconVariantLoader>>): void;\n loadVariant(name: string): Promise<void>;\n variantState(name: string): IconVariantState;\n subscribe(listener: () => void): () => void;\n getVersion(): number;\n}\n\nconst OVERRIDE = 'override';\n\ninterface Layer {\n readonly glyph: IconGlyph;\n readonly owner: string;\n}\n\ninterface Slot {\n /** One layer per live table, oldest first; the newest is what the table side shows. */\n readonly tables: Map<object, Layer>;\n /** Explicit overrides, oldest first; the newest covers every table layer. */\n readonly overrides: Layer[];\n}\n\nconst lastOf = <T>(it: Iterable<T>): T | undefined => {\n let last: T | undefined;\n for (const v of it) last = v;\n return last;\n};\n\n/** A registry, optionally seeded with tables; each `add` and `override` returns its own release function. */\nexport function createIconRegistry(initial?: IconTable | readonly IconTable[]): IconRegistry {\n const slots = new Map<string, Slot>();\n // How many live tables hold each namespace, and whose it is while any do.\n const claims = new Map<string, { owner: string; count: number }>();\n const notify = createEmitter();\n let version = 0;\n let snapshot: { version: number; entries: readonly IconEntry[] } | null = null;\n const changed = () => {\n version++;\n notify.emit();\n };\n\n const slotFor = (key: string): Slot => {\n let slot = slots.get(key);\n if (!slot) slots.set(key, (slot = { tables: new Map(), overrides: [] }));\n return slot;\n };\n const prune = (key: string) => {\n const slot = slots.get(key);\n if (slot && slot.tables.size === 0 && slot.overrides.length === 0) slots.delete(key);\n };\n const live = (slot: Slot | undefined): Layer | undefined =>\n slot && (slot.overrides.at(-1) ?? lastOf(slot.tables.values()));\n\n const unclaim = (namespace: string) => {\n const held = claims.get(namespace);\n if (!held) return;\n if (held.count <= 1) claims.delete(namespace);\n else claims.set(namespace, { ...held, count: held.count - 1 });\n };\n\n const add = (tables: IconTable | readonly IconTable[]): (() => void) => {\n const list = Array.isArray(tables) ? tables : [tables as IconTable];\n // Check every claim, those within the batch included, before writing any: a refused batch registers nothing.\n const pending = new Map<string, string>();\n for (const t of list) {\n const owner = claims.get(t.namespace)?.owner ?? pending.get(t.namespace);\n if (owner !== undefined && owner !== t.owner) throw new IconNamespaceClaimedError(t.namespace, owner, t.owner);\n pending.set(t.namespace, t.owner);\n }\n const releases = list.map((t) => {\n const held = claims.get(t.namespace);\n claims.set(t.namespace, { owner: t.owner, count: (held?.count ?? 0) + 1 });\n const token = {};\n const keys = Object.keys(t.icons).map((name) => `${t.namespace}:${name}`);\n for (const key of keys) {\n slotFor(key).tables.set(token, { glyph: t.icons[key.slice(t.namespace.length + 1)]!, owner: t.owner });\n }\n let released = false;\n return () => {\n if (released) return;\n released = true;\n unclaim(t.namespace);\n for (const key of keys) {\n slots.get(key)?.tables.delete(token);\n prune(key);\n }\n };\n });\n changed();\n return () => {\n releases.forEach((r) => r());\n changed();\n };\n };\n\n const variants = createVariants((tables) => void add(tables), changed);\n if (initial) add(initial);\n\n return {\n get: (ref) => live(slots.get(ref))?.glyph,\n add,\n override(ref, glyph, source = OVERRIDE) {\n const layer: Layer = { glyph, owner: source };\n slotFor(ref).overrides.push(layer);\n changed();\n return () => {\n const slot = slots.get(ref);\n const i = slot ? slot.overrides.indexOf(layer) : -1;\n if (i === -1) return;\n slot!.overrides.splice(i, 1);\n prune(ref);\n changed();\n };\n },\n owner: (namespace) => claims.get(namespace)?.owner,\n entries() {\n if (snapshot?.version === version) return snapshot.entries;\n const entries: IconEntry[] = [];\n for (const [key, slot] of slots) {\n const top = live(slot)!;\n entries.push({ ref: key as IconRef, glyph: top.glyph, owner: top.owner, overridden: slot.overrides.length > 0 });\n }\n snapshot = { version, entries: Object.freeze(entries) };\n return snapshot.entries;\n },\n provideVariants: variants.provide,\n loadVariant: variants.load,\n variantState: variants.state,\n subscribe: notify.subscribe,\n getVersion: () => version,\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/icons",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.10.1",
|
|
4
|
+
"description": "Namespaced icon names and the icon registry. No React, no DOM, no glyphs of its own.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" },
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"author": "orochi235",
|
|
16
|
+
"homepage": "https://orochi235.github.io/weasel/",
|
|
17
|
+
"repository": { "type": "git", "url": "git+https://github.com/orochi235/weasel.git", "directory": "packages/icons" },
|
|
18
|
+
"bugs": { "url": "https://github.com/orochi235/weasel/issues" },
|
|
19
|
+
"engines": { "node": ">=22" },
|
|
20
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
21
|
+
"scripts": { "build": "tsup" },
|
|
22
|
+
"dependencies": { "@weasel-js/registry": "1.10.1" },
|
|
23
|
+
"publishConfig": { "access": "public", "provenance": true }
|
|
24
|
+
}
|