@alacris/ui 0.2.0 → 0.2.2
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/README.md +8 -6
- package/package.json +2 -2
- package/src/components/ui-autocomplete.js +8 -1
- package/src/components/ui-date-picker.js +6 -3
- package/src/components/ui-icon.js +15 -6
- package/src/components/ui-select.js +15 -2
- package/src/components/ui-slider.js +32 -3
- package/src/util/icons.js +25 -8
- package/src/util/keys.js +31 -0
- package/types/index.d.ts +3 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ A complete, themeable design system built with **[Alacris](https://github.com/bm
|
|
|
6
6
|
|
|
7
7
|
The tags are real custom elements, so they work in plain HTML and inside React, Vue, Svelte, Angular, Rails, Django, and [Alacris-Go](https://github.com/bmartel/alacris-go) — anywhere that renders a tag.
|
|
8
8
|
|
|
9
|
-
**Docs:** [Getting started](https://bmartel.github.io/alacris/ui/getting-started/)
|
|
9
|
+
**Docs:** [Live catalog](https://bmartel.github.io/alacris/ui/) · [Getting started](https://bmartel.github.io/alacris/ui/getting-started/)
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -46,10 +46,10 @@ The published package is plain ESM. Point an import map at a pinned CDN build of
|
|
|
46
46
|
<script type="importmap">
|
|
47
47
|
{
|
|
48
48
|
"imports": {
|
|
49
|
-
"@alacris/core": "https://cdn.jsdelivr.net/npm/@alacris/core@0.11/dist/alacris.js",
|
|
50
|
-
"@alacris/ui": "https://cdn.jsdelivr.net/npm/@alacris/ui/src/index.js",
|
|
51
|
-
"@alacris/ui/theme": "https://cdn.jsdelivr.net/npm/@alacris/ui/src/theme/index.js",
|
|
52
|
-
"@alacris/ui/components/": "https://cdn.jsdelivr.net/npm/@alacris/ui/src/components/"
|
|
49
|
+
"@alacris/core": "https://cdn.jsdelivr.net/npm/@alacris/core@0.11.0/dist/alacris.js",
|
|
50
|
+
"@alacris/ui": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.2.2/src/index.js",
|
|
51
|
+
"@alacris/ui/theme": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.2.2/src/theme/index.js",
|
|
52
|
+
"@alacris/ui/components/": "https://cdn.jsdelivr.net/npm/@alacris/ui@0.2.2/src/components/"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
</script>
|
|
@@ -61,7 +61,7 @@ The published package is plain ESM. Point an import map at a pinned CDN build of
|
|
|
61
61
|
<ui-button>Hello</ui-button>
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
Never mix two versions of `@alacris/core` on one page — two copies means two reactive graphs.
|
|
65
65
|
|
|
66
66
|
## Theming
|
|
67
67
|
|
|
@@ -135,6 +135,8 @@ ui/
|
|
|
135
135
|
CONVENTIONS.md rules every component follows
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
+
The live catalog is [bmartel.github.io/alacris/ui](https://bmartel.github.io/alacris/ui/) — every component plus a theme playground, no clone required.
|
|
139
|
+
|
|
138
140
|
From a clone of the repo:
|
|
139
141
|
|
|
140
142
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alacris/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Themeable design system for Alacris — Material defaults, sixty-eight custom elements, ESM-only, no build step.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"url": "git+https://github.com/bmartel/alacris.git",
|
|
67
67
|
"directory": "ui"
|
|
68
68
|
},
|
|
69
|
-
"homepage": "https://bmartel.github.io/alacris/ui/
|
|
69
|
+
"homepage": "https://bmartel.github.io/alacris/ui/",
|
|
70
70
|
"bugs": {
|
|
71
71
|
"url": "https://github.com/bmartel/alacris/issues"
|
|
72
72
|
},
|
|
@@ -28,6 +28,7 @@ import { define, html, css, vars, computed, signal, effect, onCleanup, each } fr
|
|
|
28
28
|
import { sys } from '../tokens/sys.js';
|
|
29
29
|
import { base } from './base.js';
|
|
30
30
|
import { formBind } from '../util/form.js';
|
|
31
|
+
import { escapeLayer } from '../util/keys.js';
|
|
31
32
|
import { presence } from '../motion/presence.js';
|
|
32
33
|
import { fx } from '../motion/animate.js';
|
|
33
34
|
import { autoUpdate } from '../util/position.js';
|
|
@@ -295,9 +296,15 @@ define('ui-autocomplete', {
|
|
|
295
296
|
if (showPanel() && activeOpt()) { e.preventDefault(); commit(activeOpt().value); }
|
|
296
297
|
else if (freeSolo()) commit(query().trim());
|
|
297
298
|
break;
|
|
298
|
-
case 'Escape': open.set(false); break;
|
|
299
299
|
}
|
|
300
300
|
};
|
|
301
|
+
// The panel owns Escape while it is up, so that closing it inside a
|
|
302
|
+
// dialog does not close the dialog as well.
|
|
303
|
+
effect(() => {
|
|
304
|
+
if (!showPanel()) return;
|
|
305
|
+
return escapeLayer(() => open.set(false));
|
|
306
|
+
});
|
|
307
|
+
|
|
301
308
|
const onFocus = () => { focused.set(true); open.set(true); };
|
|
302
309
|
const onBlur = () => {
|
|
303
310
|
focused.set(false);
|
|
@@ -38,6 +38,7 @@ import { formBind } from '../util/form.js';
|
|
|
38
38
|
import { presence } from '../motion/presence.js';
|
|
39
39
|
import { animate, fx } from '../motion/animate.js';
|
|
40
40
|
import { autoUpdate } from '../util/position.js';
|
|
41
|
+
import { escapeLayer } from '../util/keys.js';
|
|
41
42
|
import { focusTrap, scrollLock } from '../util/focus.js';
|
|
42
43
|
import './ui-icon-button.js';
|
|
43
44
|
import './ui-button.js';
|
|
@@ -607,12 +608,14 @@ define('ui-date-picker', {
|
|
|
607
608
|
if (e.composedPath().includes(host)) return;
|
|
608
609
|
closePanel();
|
|
609
610
|
};
|
|
610
|
-
|
|
611
|
+
// Capture at the document is not early enough: a dialog registers the
|
|
612
|
+
// same way when it opens, so it is already listening by the time this
|
|
613
|
+
// panel does and one Escape closes both.
|
|
614
|
+
const releaseEsc = escapeLayer(closePanel);
|
|
611
615
|
document.addEventListener('pointerdown', onDoc);
|
|
612
|
-
document.addEventListener('keydown', onEsc, true);
|
|
613
616
|
return () => {
|
|
614
617
|
document.removeEventListener('pointerdown', onDoc);
|
|
615
|
-
|
|
618
|
+
releaseEsc();
|
|
616
619
|
};
|
|
617
620
|
});
|
|
618
621
|
effect(() => {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// <ui-icon> — an icon from the registry, or any slotted SVG.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// Names are kebab-case (`arrow-forward`). Underscores are accepted
|
|
4
|
+
// (`arrow_forward`). `iconNames()` lists the built-in set; apps add more
|
|
5
|
+
// with `registerIcons({ name: 'M…' })`. An unknown name logs a warning
|
|
6
|
+
// once and renders a placeholder instead of an empty hole.
|
|
7
|
+
//
|
|
8
|
+
// @prop {string} name='' — registry name; empty renders the slot
|
|
4
9
|
// @prop {string} label='' — accessible name; empty marks the icon decorative
|
|
5
10
|
// @prop {string} size='' — CSS length; overrides --ui-icon-size for this element
|
|
6
11
|
// @slot (default) — a custom <svg> when no name is given
|
|
@@ -8,7 +13,10 @@
|
|
|
8
13
|
|
|
9
14
|
import { define, html, svg, css, vars, effect } from '@alacris/core';
|
|
10
15
|
import { base } from './base.js';
|
|
11
|
-
import { iconPath, iconsVersion } from '../util/icons.js';
|
|
16
|
+
import { iconPath, iconsVersion, warnUnknownIcon } from '../util/icons.js';
|
|
17
|
+
|
|
18
|
+
// Hollow square — obviously not a real glyph, so a typo is visible.
|
|
19
|
+
const MISSING = 'M3 3v18h18V3H3zm2 2h14v14H5V5z';
|
|
12
20
|
|
|
13
21
|
const t = vars('ui-icon', {
|
|
14
22
|
size: '1.5rem',
|
|
@@ -52,10 +60,11 @@ define('ui-icon', {
|
|
|
52
60
|
});
|
|
53
61
|
return html`${() => {
|
|
54
62
|
iconsVersion(); // re-render if icons register late
|
|
55
|
-
const
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
const n = name();
|
|
64
|
+
if (!n) return html`<slot></slot>`;
|
|
65
|
+
const d = iconPath(n);
|
|
66
|
+
if (!d) warnUnknownIcon(n);
|
|
67
|
+
return svg`<svg viewBox="0 0 24 24" aria-hidden="true" data-icon=${d ? n : 'unknown'}><path d=${d || MISSING}></path></svg>`;
|
|
59
68
|
}}`;
|
|
60
69
|
},
|
|
61
70
|
});
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
// </ui-select>
|
|
8
8
|
//
|
|
9
9
|
// Keyboard (APG select-only combobox): Enter/Space/ArrowDown/ArrowUp open;
|
|
10
|
-
// arrows move the active option, Enter/Space selects it, Escape closes
|
|
10
|
+
// arrows move the active option, Enter/Space selects it, Escape closes the
|
|
11
|
+
// panel only — an enclosing dialog keeps its own Escape for a second press,
|
|
11
12
|
// typing jumps to the next option starting with that letter. The panel closes
|
|
12
13
|
// on outside pointerdown and returns focus to the field.
|
|
13
14
|
//
|
|
@@ -37,6 +38,7 @@ import { formBind } from '../util/form.js';
|
|
|
37
38
|
import { presence } from '../motion/presence.js';
|
|
38
39
|
import { fx } from '../motion/animate.js';
|
|
39
40
|
import { autoUpdate } from '../util/position.js';
|
|
41
|
+
import { escapeLayer } from '../util/keys.js';
|
|
40
42
|
import './ui-icon.js';
|
|
41
43
|
import './ui-option.js';
|
|
42
44
|
|
|
@@ -294,6 +296,18 @@ define('ui-select', {
|
|
|
294
296
|
fieldEl?.focus();
|
|
295
297
|
};
|
|
296
298
|
|
|
299
|
+
// Escape belongs to the panel while it is open, not to whatever encloses
|
|
300
|
+
// it. A dialog listens for the key in the capture phase at the document,
|
|
301
|
+
// so without claiming it a step earlier one press closes the panel and the
|
|
302
|
+
// dialog together.
|
|
303
|
+
effect(() => {
|
|
304
|
+
if (!open()) return;
|
|
305
|
+
return escapeLayer(() => {
|
|
306
|
+
closePanel();
|
|
307
|
+
fieldEl?.focus();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
297
311
|
// Outside pointerdown closes (scrim-less popup).
|
|
298
312
|
effect(() => {
|
|
299
313
|
if (!open()) return;
|
|
@@ -362,7 +376,6 @@ define('ui-select', {
|
|
|
362
376
|
}
|
|
363
377
|
case 'Enter':
|
|
364
378
|
case ' ': e.preventDefault(); commit(opts[activeIndex()]); break;
|
|
365
|
-
case 'Escape': e.preventDefault(); closePanel(); break;
|
|
366
379
|
case 'Tab': closePanel(); break;
|
|
367
380
|
default:
|
|
368
381
|
if (e.key.length === 1 && e.key !== ' ') typeahead(e.key);
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
// <ui-slider> — a Material slider on native <input type="range">s for
|
|
2
2
|
// keyboard and screen-reader behavior.
|
|
3
3
|
//
|
|
4
|
+
// <ui-slider label="Volume" value=${volume}
|
|
5
|
+
// @input=${(e) => volume.set(e.detail.value)}></ui-slider>
|
|
6
|
+
//
|
|
7
|
+
// Bind `value` (or `.value`) to a signal like any other control. `input` /
|
|
8
|
+
// `change` report a number in `detail.value` (or `detail.start` / `detail.end`
|
|
9
|
+
// when `range`). The host `.value` is a string, matching a native range
|
|
10
|
+
// input, so composed-path helpers that look for `typeof node.value ===
|
|
11
|
+
// 'string'` work the same as they do for text fields.
|
|
12
|
+
//
|
|
4
13
|
// The active track portion is painted with `--ui-slider-fill` (or start/end
|
|
5
14
|
// when `range`) bound from the template into a gradient; the thumb's
|
|
6
15
|
// hover/focus halo is a box-shadow state layer.
|
|
@@ -157,6 +166,23 @@ define('ui-slider', {
|
|
|
157
166
|
});
|
|
158
167
|
formBind(host, { name, value: submitted, disabled });
|
|
159
168
|
|
|
169
|
+
// Native <input type="range">.value is a string. Present the same on the
|
|
170
|
+
// host so composed-path helpers and attribute-based bindings see it, while
|
|
171
|
+
// the prop signal stays numeric for math and `detail.value`.
|
|
172
|
+
const toNum = (v) => {
|
|
173
|
+
const n = typeof v === 'number' ? v : parseFloat(v);
|
|
174
|
+
return Number.isFinite(n) ? n : min();
|
|
175
|
+
};
|
|
176
|
+
Object.defineProperty(host, 'value', {
|
|
177
|
+
configurable: true,
|
|
178
|
+
enumerable: true,
|
|
179
|
+
get() { return String(value()); },
|
|
180
|
+
set(v) { value.set(toNum(v)); },
|
|
181
|
+
});
|
|
182
|
+
effect(() => {
|
|
183
|
+
host.setAttribute('value', String(value()));
|
|
184
|
+
});
|
|
185
|
+
|
|
160
186
|
const active = signal(false);
|
|
161
187
|
const activeThumb = signal('end');
|
|
162
188
|
|
|
@@ -226,7 +252,8 @@ define('ui-slider', {
|
|
|
226
252
|
${() => (range()
|
|
227
253
|
? html`<div class="dual">
|
|
228
254
|
<input part="input" type="range"
|
|
229
|
-
min=${min} max=${max} step=${step}
|
|
255
|
+
min=${min} max=${max} step=${step}
|
|
256
|
+
value=${valueStart} .value=${valueStart}
|
|
230
257
|
?disabled=${disabled}
|
|
231
258
|
aria-label=${() => (label() ? label() + ' start' : 'Start')}
|
|
232
259
|
@input=${onStartInput} @change=${onStartChange}
|
|
@@ -234,7 +261,8 @@ define('ui-slider', {
|
|
|
234
261
|
@focus=${() => { active.set(true); activeThumb.set('start'); }}
|
|
235
262
|
@blur=${() => active.set(false)}>
|
|
236
263
|
<input part="input" type="range"
|
|
237
|
-
min=${min} max=${max} step=${step}
|
|
264
|
+
min=${min} max=${max} step=${step}
|
|
265
|
+
value=${valueEnd} .value=${valueEnd}
|
|
238
266
|
?disabled=${disabled}
|
|
239
267
|
aria-label=${() => (label() ? label() + ' end' : 'End')}
|
|
240
268
|
@input=${onEndInput} @change=${onEndChange}
|
|
@@ -243,7 +271,8 @@ define('ui-slider', {
|
|
|
243
271
|
@blur=${() => active.set(false)}>
|
|
244
272
|
</div>`
|
|
245
273
|
: html`<input part="input" type="range"
|
|
246
|
-
min=${min} max=${max} step=${step}
|
|
274
|
+
min=${min} max=${max} step=${step}
|
|
275
|
+
value=${value} .value=${value}
|
|
247
276
|
?disabled=${disabled}
|
|
248
277
|
aria-label=${() => label() || 'Slider'}
|
|
249
278
|
@input=${onInput} @change=${onChange}
|
package/src/util/icons.js
CHANGED
|
@@ -1,29 +1,45 @@
|
|
|
1
1
|
// Icon registry.
|
|
2
2
|
//
|
|
3
3
|
// A built-in set of Material Design icon paths (24×24 viewBox, from Google's
|
|
4
|
-
// material-design-icons, Apache-2.0).
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// material-design-icons, Apache-2.0). Names are kebab-case; underscores are
|
|
5
|
+
// treated as hyphens so Material Symbols names (`arrow_forward`) resolve.
|
|
6
|
+
// Components take what they need; the rest is the everyday MD3 filled set so
|
|
7
|
+
// an app can start without a custom registry. Apps add their own with
|
|
8
|
+
// `registerIcons` — any 24×24 path data works — or slot a whole `<svg>` into
|
|
9
|
+
// `<ui-icon>` for one-offs.
|
|
7
10
|
|
|
8
11
|
import { signal } from '@alacris/core';
|
|
9
12
|
|
|
10
13
|
const registry = new Map();
|
|
14
|
+
const warned = new Set();
|
|
11
15
|
|
|
12
16
|
// Bumped on registration so already-rendered <ui-icon>s pick up late icons.
|
|
13
17
|
export const iconsVersion = signal(0);
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
// Material Symbols use underscores; the registry is kebab-case. Treat them
|
|
20
|
+
// as the same name so `arrow_forward` resolves to `arrow-forward`.
|
|
21
|
+
const canon = (name) => String(name).replace(/_/g, '-');
|
|
22
|
+
|
|
23
|
+
/** registerIcons({ name: 'M…' }) — later registrations win. Names are stored kebab-case. */
|
|
16
24
|
export function registerIcons(icons) {
|
|
17
|
-
for (const name in icons) registry.set(name, icons[name]);
|
|
25
|
+
for (const name in icons) registry.set(canon(name), icons[name]);
|
|
18
26
|
iconsVersion.update((n) => n + 1);
|
|
19
27
|
}
|
|
20
28
|
|
|
21
|
-
/** Path data for a name, or undefined. */
|
|
22
|
-
export const iconPath = (name) => registry.get(name);
|
|
29
|
+
/** Path data for a name, or undefined. Underscores and hyphens are equivalent. */
|
|
30
|
+
export const iconPath = (name) => (name ? registry.get(canon(name)) : undefined);
|
|
23
31
|
|
|
24
|
-
/** Registered names (built-ins included) — the demo lists these. */
|
|
32
|
+
/** Registered names (built-ins included, kebab-case) — the demo lists these. */
|
|
25
33
|
export const iconNames = () => [...registry.keys()];
|
|
26
34
|
|
|
35
|
+
/** Warn once per unknown name so a blank icon is grep-able, not a silent hole. */
|
|
36
|
+
export function warnUnknownIcon(name) {
|
|
37
|
+
const n = canon(name);
|
|
38
|
+
if (!n || registry.has(n) || warned.has(n)) return;
|
|
39
|
+
warned.add(n);
|
|
40
|
+
console.warn(`ui-icon: "${name}" is not registered. Names are kebab-case (arrow-forward); underscores are accepted. iconNames() lists the set; registerIcons() adds more.`);
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
registerIcons({
|
|
28
44
|
'add': 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z',
|
|
29
45
|
'arrow-back': 'M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z',
|
|
@@ -31,6 +47,7 @@ registerIcons({
|
|
|
31
47
|
'arrow-drop-down': 'M7 10l5 5 5-5z',
|
|
32
48
|
'arrow-drop-up': 'M7 14l5-5 5 5z',
|
|
33
49
|
'arrow-upward': 'M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z',
|
|
50
|
+
'arrow-downward': 'M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z',
|
|
34
51
|
'calendar': 'M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z',
|
|
35
52
|
'cancel': 'M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z',
|
|
36
53
|
'check': 'M8.95 19.1 3.65 13.8 6.45 11 8.95 13.5 17.55 4.9 20.35 7.7z',
|
package/src/util/keys.js
CHANGED
|
@@ -108,3 +108,34 @@ export function rovingTabindex(container, opts = {}) {
|
|
|
108
108
|
},
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
// escapeLayer — claim Escape for the innermost open layer.
|
|
113
|
+
//
|
|
114
|
+
// `ui-dialog` listens for Escape in the capture phase at the document, so that
|
|
115
|
+
// the key works wherever focus happens to be. That is right for a dialog and
|
|
116
|
+
// wrong for anything transient opened inside one: a select's panel, a menu, a
|
|
117
|
+
// date picker. Those handle Escape too, but the dialog has already seen it by
|
|
118
|
+
// then, so one press closes both — and choosing a format in a dialog looks
|
|
119
|
+
// like the dialog is broken rather than like an ordering problem nobody can
|
|
120
|
+
// see.
|
|
121
|
+
//
|
|
122
|
+
// Capture descends window → document → …, so a layer claims the key one step
|
|
123
|
+
// earlier than the dialog and stops it there. Nothing below ever runs.
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* escapeLayer(onEscape)
|
|
127
|
+
*
|
|
128
|
+
* Call while a transient layer is open; call the returned function when it
|
|
129
|
+
* closes. Only registers a listener while it is held, so a page with nothing
|
|
130
|
+
* open behaves exactly as before.
|
|
131
|
+
*/
|
|
132
|
+
export function escapeLayer(onEscape) {
|
|
133
|
+
const onKeydown = (e) => {
|
|
134
|
+
if (e.key !== 'Escape') return;
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
onEscape(e);
|
|
138
|
+
};
|
|
139
|
+
window.addEventListener('keydown', onKeydown, true);
|
|
140
|
+
return () => window.removeEventListener('keydown', onKeydown, true);
|
|
141
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -147,8 +147,11 @@ export function formBind(
|
|
|
147
147
|
}
|
|
148
148
|
): void;
|
|
149
149
|
|
|
150
|
+
/** Register SVG path data. Names are stored kebab-case; underscores are equivalent. */
|
|
150
151
|
export function registerIcons(icons: Record<string, string>): void;
|
|
152
|
+
/** Path data for a name, or undefined. Underscores and hyphens are equivalent. */
|
|
151
153
|
export function iconPath(name: string): string | undefined;
|
|
154
|
+
/** Registered names (built-ins included, kebab-case). */
|
|
152
155
|
export function iconNames(): string[];
|
|
153
156
|
|
|
154
157
|
export function processTable(opts: Record<string, unknown>): Record<string, unknown>;
|