@ixfx/components 0.3.2 → 0.4.0
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/bundle/index.d.ts +1086 -603
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +16153 -15296
- package/bundle/index.js.map +1 -1
- package/bundle/style.css +14 -4
- package/dist/ac-text.d.ts.map +1 -1
- package/dist/ac-text.js +11 -6
- package/dist/ac-text.js.map +1 -1
- package/dist/{button-DMt5XWUK.js → button-C3fLMKUv.js} +4 -4
- package/dist/{button-DMt5XWUK.js.map → button-C3fLMKUv.js.map} +1 -1
- package/dist/button.d.ts +1 -1
- package/dist/button.d.ts.map +1 -1
- package/dist/button.js +1 -1
- package/dist/checkbox.d.ts +2 -2
- package/dist/checkbox.d.ts.map +1 -1
- package/dist/checkbox.js +14 -8
- package/dist/checkbox.js.map +1 -1
- package/dist/{colour-C3MQIjFJ-BLXg8W3U.js → colour-C3MQIjFJ-vRCdr8W-.js} +146 -552
- package/dist/colour-C3MQIjFJ-vRCdr8W-.js.map +1 -0
- package/dist/{colour-picker-K6zu82lm.js → colour-picker-0sgAha1T.js} +14 -9
- package/dist/colour-picker-0sgAha1T.js.map +1 -0
- package/dist/colour-picker.d.ts +1 -1
- package/dist/colour-picker.js +1 -1
- package/dist/crumbs.d.ts +1 -1
- package/dist/crumbs.d.ts.map +1 -1
- package/dist/crumbs.js +11 -6
- package/dist/crumbs.js.map +1 -1
- package/dist/data-display.d.ts +9 -4
- package/dist/data-display.d.ts.map +1 -1
- package/dist/data-display.js +270 -43
- package/dist/data-display.js.map +1 -1
- package/dist/data-provider-DgI909FP.js +23 -0
- package/dist/data-provider-DgI909FP.js.map +1 -0
- package/dist/{decorate-D7rC1gLP.js → decorate-DdjvVHS-.js} +2 -2
- package/dist/{decorate-D7rC1gLP.js.map → decorate-DdjvVHS-.js.map} +1 -1
- package/dist/{defaults-C7elhuyJ.js → defaults-C3b9OWJD.js} +2 -2
- package/dist/{defaults-C7elhuyJ.js.map → defaults-C3b9OWJD.js.map} +1 -1
- package/dist/{dist-TAGYwaju.js → dist-CA13FvMn.js} +7 -66
- package/dist/dist-CA13FvMn.js.map +1 -0
- package/dist/editable-label.d.ts.map +1 -1
- package/dist/editable-label.js +3 -2
- package/dist/editable-label.js.map +1 -1
- package/dist/fallbacks-pNMOMx38.js +168 -0
- package/dist/fallbacks-pNMOMx38.js.map +1 -0
- package/dist/{hex-editor-DlfoA65y.d.ts → hex-editor-CMIxtNgT.d.ts} +1 -1
- package/dist/hex-editor-CMIxtNgT.d.ts.map +1 -0
- package/dist/{hex-editor-CsN_ySkH.js → hex-editor-WZrjRtvL.js} +3 -3
- package/dist/{hex-editor-CsN_ySkH.js.map → hex-editor-WZrjRtvL.js.map} +1 -1
- package/dist/hex.d.ts +1 -1
- package/dist/hex.js +1 -1
- package/dist/{highlight-TPbSHruI.js → highlight-B-eQrhhb.js} +1 -1
- package/dist/{highlight-TPbSHruI.js.map → highlight-B-eQrhhb.js.map} +1 -1
- package/dist/{icon-Dm_DHDz6.d.ts → icon-Bd5BiU4b.d.ts} +3 -3
- package/dist/icon-Bd5BiU4b.d.ts.map +1 -0
- package/dist/{icon-9NWTXtE1.js → icon-Ndo40kNO.js} +6 -5
- package/dist/icon-Ndo40kNO.js.map +1 -0
- package/dist/icons.d.ts +2 -2
- package/dist/icons.js +3 -3
- package/dist/{incr-search-DSKKsk6w.js → incr-search-CeERfijK.js} +2 -2
- package/dist/{incr-search-DSKKsk6w.js.map → incr-search-CeERfijK.js.map} +1 -1
- package/dist/incr-search.d.ts +1 -1
- package/dist/incr-search.js +2 -2
- package/dist/{index-hXFn14tM.d.ts → index-BMjri2-S.d.ts} +2 -2
- package/dist/{index-hXFn14tM.d.ts.map → index-BMjri2-S.d.ts.map} +1 -1
- package/dist/{index-DSlPeWLK.d.ts → index-CWpNcQKE.d.ts} +5 -5
- package/dist/index-CWpNcQKE.d.ts.map +1 -0
- package/dist/{index-DLkSKL4T.d.ts → index-CowKi2mo.d.ts} +1 -1
- package/dist/index-CowKi2mo.d.ts.map +1 -0
- package/dist/{index-B1hNR7Z9.d.ts → index-D87kRcEs.d.ts} +1 -1
- package/dist/{index-B1hNR7Z9.d.ts.map → index-D87kRcEs.d.ts.map} +1 -1
- package/dist/{index-CnL9krsH.d.ts → index-DgXpCg_G.d.ts} +32 -26
- package/dist/index-DgXpCg_G.d.ts.map +1 -0
- package/dist/{index-iZLld8Ca.d.ts → index-s_IhSnwc.d.ts} +2 -2
- package/dist/index-s_IhSnwc.d.ts.map +1 -0
- package/dist/index.d.ts +432 -121
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2369 -1706
- package/dist/index.js.map +1 -1
- package/dist/interaction-D6efrBLo.js +1 -0
- package/dist/{keyboard-De_vOHLg.js → keyboard-CimiEskD.js} +1 -1
- package/dist/{keyboard-De_vOHLg.js.map → keyboard-CimiEskD.js.map} +1 -1
- package/dist/{labelled-input-base-CqPL5rKm.js → labelled-input-base-9K_w3oLj.js} +2 -2
- package/dist/{labelled-input-base-CqPL5rKm.js.map → labelled-input-base-9K_w3oLj.js.map} +1 -1
- package/dist/{labelled-input-base-BVvNeb5R.d.ts → labelled-input-base-ORb1rGq6.d.ts} +2 -2
- package/dist/labelled-input-base-ORb1rGq6.d.ts.map +1 -0
- package/dist/labelled-radial-input.d.ts +3 -3
- package/dist/labelled-radial-input.d.ts.map +1 -1
- package/dist/labelled-radial-input.js +7 -6
- package/dist/labelled-radial-input.js.map +1 -1
- package/dist/labelled-range-input.d.ts +1 -1
- package/dist/labelled-range-input.js +2 -2
- package/dist/led.d.ts +1 -1
- package/dist/led.d.ts.map +1 -1
- package/dist/led.js +2 -2
- package/dist/led.js.map +1 -1
- package/dist/{menu-item-CKRfP0pN.js → menu-item-BrIuORYD.js} +20 -10
- package/dist/menu-item-BrIuORYD.js.map +1 -0
- package/dist/{menu-item-ClQF2DKN.d.ts → menu-item-COx-Azgj.d.ts} +1 -1
- package/dist/menu-item-COx-Azgj.d.ts.map +1 -0
- package/dist/{menu-DJJuCm-5.js → menu-vjoiHZ13.js} +30 -17
- package/dist/menu-vjoiHZ13.js.map +1 -0
- package/dist/menu.d.ts +2 -2
- package/dist/menu.js +2 -2
- package/dist/miller.d.ts +2 -2
- package/dist/miller.d.ts.map +1 -1
- package/dist/miller.js +18 -11
- package/dist/miller.js.map +1 -1
- package/dist/narrowed-text.d.ts.map +1 -1
- package/dist/narrowed-text.js +7 -5
- package/dist/narrowed-text.js.map +1 -1
- package/dist/panel.d.ts +1 -1
- package/dist/panel.d.ts.map +1 -1
- package/dist/panel.js +5 -4
- package/dist/panel.js.map +1 -1
- package/dist/plots.d.ts +2 -2
- package/dist/plots.js +2 -2
- package/dist/polar-pad.d.ts +1 -1
- package/dist/polar-pad.d.ts.map +1 -1
- package/dist/polar-pad.js +10 -4
- package/dist/polar-pad.js.map +1 -1
- package/dist/{popup-ImtS9KnS.js → popup-DiWMxsWj.js} +1 -1
- package/dist/{popup-ImtS9KnS.js.map → popup-DiWMxsWj.js.map} +1 -1
- package/dist/prominence-DlFShic3.js +104 -0
- package/dist/prominence-DlFShic3.js.map +1 -0
- package/dist/{radial-input-LUSBEycd.d.ts → radial-input-CpQPs1Mp.d.ts} +1 -1
- package/dist/radial-input-CpQPs1Mp.d.ts.map +1 -0
- package/dist/{radial-input-DZA6WaFu.js → radial-input-DZ7eQCcM.js} +11 -6
- package/dist/radial-input-DZ7eQCcM.js.map +1 -0
- package/dist/radial-input.d.ts +1 -1
- package/dist/radial-input.js +1 -1
- package/dist/range-input.d.ts +1 -1
- package/dist/range-input.d.ts.map +1 -1
- package/dist/range-input.js +9 -4
- package/dist/range-input.js.map +1 -1
- package/dist/range.d.ts +1 -1
- package/dist/range.d.ts.map +1 -1
- package/dist/range.js +5 -3
- package/dist/range.js.map +1 -1
- package/dist/{registry-Q2gYQCHM.js → registry-CkW7q09Q.js} +1 -1
- package/dist/{registry-Q2gYQCHM.js.map → registry-CkW7q09Q.js.map} +1 -1
- package/dist/selecthorizontal.d.ts +1 -1
- package/dist/selecthorizontal.d.ts.map +1 -1
- package/dist/selecthorizontal.js +4 -3
- package/dist/selecthorizontal.js.map +1 -1
- package/dist/snap-container.d.ts.map +1 -1
- package/dist/snap-container.js +1 -1
- package/dist/split-layout.d.ts +1 -1
- package/dist/split-layout.d.ts.map +1 -1
- package/dist/split-layout.js +6 -5
- package/dist/split-layout.js.map +1 -1
- package/dist/style.css +14 -4
- package/dist/swipe.d.ts.map +1 -1
- package/dist/swipe.js +9 -4
- package/dist/swipe.js.map +1 -1
- package/dist/{tab-list-BnqISNvO.d.ts → tab-list-DLzb-m-I.d.ts} +2 -2
- package/dist/tab-list-DLzb-m-I.d.ts.map +1 -0
- package/dist/tabs.d.ts +1 -1
- package/dist/tabs.js +10 -4
- package/dist/tabs.js.map +1 -1
- package/dist/{tickled-controller-Cq1Va0rR.d.ts → tickled-controller-BjYCzGrU.d.ts} +2 -2
- package/dist/tickled-controller-BjYCzGrU.d.ts.map +1 -0
- package/dist/{tickled-styles-Bg3QbcrD.js → tickled-styles-fNDdqf6l.js} +1 -17
- package/dist/{tickled-styles-Bg3QbcrD.js.map → tickled-styles-fNDdqf6l.js.map} +1 -1
- package/dist/{timeline-Bh0PhlKW.js → timeline-2OFtN4Ta.js} +2 -2
- package/dist/{timeline-Bh0PhlKW.js.map → timeline-2OFtN4Ta.js.map} +1 -1
- package/dist/timeline.d.ts +1 -1
- package/dist/timeline.js +1 -1
- package/dist/{tooltip-DMxmBNky.js → tooltip-CJt3WLmN.js} +1 -1
- package/dist/{tooltip-DMxmBNky.js.map → tooltip-CJt3WLmN.js.map} +1 -1
- package/dist/{tooltip-CwGfb4lp.d.ts → tooltip-RGVIwHrZ.d.ts} +1 -1
- package/dist/{tooltip-CwGfb4lp.d.ts.map → tooltip-RGVIwHrZ.d.ts.map} +1 -1
- package/dist/{tree-CvvKbO-f.js → tree-CJWpMUvD.js} +113 -103
- package/dist/tree-CJWpMUvD.js.map +1 -0
- package/dist/{tree-component-D8Bg48tt.d.ts → tree-component-BcO26Xvt.d.ts} +65 -3
- package/dist/tree-component-BcO26Xvt.d.ts.map +1 -0
- package/dist/tree.d.ts +15 -15
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +1 -1
- package/dist/{types-BKaqnpwx.d.ts → types-DSfbtHs3.d.ts} +1 -1
- package/dist/types-DSfbtHs3.d.ts.map +1 -0
- package/dist/{xy-axis-DL0tfJDr.js → xy-axis-CK64haaK.js} +1831 -359
- package/dist/xy-axis-CK64haaK.js.map +1 -0
- package/dist/{xy-axis-D9uwpGTT.d.ts → xy-axis-McdUpcYr.d.ts} +102 -3
- package/dist/xy-axis-McdUpcYr.d.ts.map +1 -0
- package/dist/xy-pad.d.ts +1 -1
- package/dist/xy-pad.d.ts.map +1 -1
- package/dist/xy-pad.js +11 -6
- package/dist/xy-pad.js.map +1 -1
- package/docs-user/README.md +45 -0
- package/docs-user/ac-text.md +67 -0
- package/docs-user/ac-token.md +187 -0
- package/docs-user/button.md +355 -0
- package/docs-user/checkbox.md +62 -0
- package/docs-user/colour-picker.md +237 -0
- package/docs-user/crumbs.md +66 -0
- package/docs-user/data-display.md +98 -0
- package/docs-user/editable-label.md +244 -0
- package/docs-user/grouped-item-lister.md +424 -0
- package/docs-user/icons.md +191 -0
- package/docs-user/incr-search.md +94 -0
- package/docs-user/labelled-radial-input.md +204 -0
- package/docs-user/labelled-range-input.md +194 -0
- package/docs-user/led.md +40 -0
- package/docs-user/menu.md +319 -0
- package/docs-user/miller.md +453 -0
- package/docs-user/narrowed-text.md +72 -0
- package/docs-user/panel.md +250 -0
- package/docs-user/plots.md +361 -0
- package/docs-user/polar-pad.md +228 -0
- package/docs-user/radial-input.md +559 -0
- package/docs-user/range-input.md +264 -0
- package/docs-user/range.md +12 -0
- package/docs-user/selecthorizontal.md +32 -0
- package/docs-user/snap-container.md +132 -0
- package/docs-user/split-layout.md +232 -0
- package/docs-user/swipe.md +44 -0
- package/docs-user/tabs.md +217 -0
- package/docs-user/timeline.md +116 -0
- package/docs-user/transitory-label.md +120 -0
- package/docs-user/tree.md +592 -0
- package/docs-user/user-catalog.md +165 -0
- package/docs-user/user-theming.md +379 -0
- package/docs-user/util.md +122 -0
- package/docs-user/vertical-list.md +351 -0
- package/docs-user/xy-pad.md +94 -0
- package/package.json +18 -17
- package/dist/colour-C3MQIjFJ-BLXg8W3U.js.map +0 -1
- package/dist/colour-picker-K6zu82lm.js.map +0 -1
- package/dist/dist-BX0OVIm7.js +0 -1290
- package/dist/dist-BX0OVIm7.js.map +0 -1
- package/dist/dist-TAGYwaju.js.map +0 -1
- package/dist/hex-editor-DlfoA65y.d.ts.map +0 -1
- package/dist/icon-9NWTXtE1.js.map +0 -1
- package/dist/icon-Dm_DHDz6.d.ts.map +0 -1
- package/dist/index-CnL9krsH.d.ts.map +0 -1
- package/dist/index-DLkSKL4T.d.ts.map +0 -1
- package/dist/index-DSlPeWLK.d.ts.map +0 -1
- package/dist/index-iZLld8Ca.d.ts.map +0 -1
- package/dist/interaction-DG7X1rhI.js +0 -1
- package/dist/labelled-input-base-BVvNeb5R.d.ts.map +0 -1
- package/dist/menu-DJJuCm-5.js.map +0 -1
- package/dist/menu-item-CKRfP0pN.js.map +0 -1
- package/dist/menu-item-ClQF2DKN.d.ts.map +0 -1
- package/dist/prominence--ic9uWBl.js +0 -98
- package/dist/prominence--ic9uWBl.js.map +0 -1
- package/dist/radial-input-DZA6WaFu.js.map +0 -1
- package/dist/radial-input-LUSBEycd.d.ts.map +0 -1
- package/dist/tab-list-BnqISNvO.d.ts.map +0 -1
- package/dist/tickled-controller-Cq1Va0rR.d.ts.map +0 -1
- package/dist/tree-CvvKbO-f.js.map +0 -1
- package/dist/tree-component-D8Bg48tt.d.ts.map +0 -1
- package/dist/types-BKaqnpwx.d.ts.map +0 -1
- package/dist/xy-axis-D9uwpGTT.d.ts.map +0 -1
- package/dist/xy-axis-DL0tfJDr.js.map +0 -1
- /package/bundle/{chunk-pbuEa-1d.js → chunk-D7D4PA-g.js} +0 -0
- /package/dist/{chunk-pbuEa-1d.js → chunk-D7D4PA-g.js} +0 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# `ixfx-grouped-item-lister`
|
|
2
|
+
|
|
3
|
+
A meta-component that renders a typed item list using any sub-component you supply. Items can be displayed in a single sub-component (_ungrouped_ mode) or split into labelled groups, each with its own sub-component (_grouped_ mode), wrapped in `ixfx-panel` sections.
|
|
4
|
+
|
|
5
|
+
The component uses **Light DOM** so its children are directly accessible to external CSS and can be observed by the page's event listeners without crossing a shadow boundary.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
1. [Quick start](#quick-start)
|
|
12
|
+
2. [Properties](#properties)
|
|
13
|
+
3. [Providing data](#providing-data)
|
|
14
|
+
4. [Built-in adapters](#built-in-adapters)
|
|
15
|
+
- [`createVerticalListAdapter`](#createverticallistadapter)
|
|
16
|
+
- [`createTreeListAdapter`](#createtreelistadapter)
|
|
17
|
+
5. [Selection](#selection)
|
|
18
|
+
6. [Dispatching commands](#dispatching-commands)
|
|
19
|
+
7. [Events](#events)
|
|
20
|
+
8. [Writing a custom adapter](#writing-a-custom-adapter)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import {
|
|
28
|
+
createVerticalListAdapter,
|
|
29
|
+
} from '@ixfx/components';
|
|
30
|
+
import type { GroupedItemListerElement } from '@ixfx/components';
|
|
31
|
+
|
|
32
|
+
type FileItem = { id: string; name: string; ext: string };
|
|
33
|
+
|
|
34
|
+
const lister = document.querySelector<GroupedItemListerElement<FileItem>>('ixfx-grouped-item-lister')!;
|
|
35
|
+
|
|
36
|
+
// 1. Factory — called once per group (or once for the ungrouped view)
|
|
37
|
+
lister.factory = () =>
|
|
38
|
+
createVerticalListAdapter<FileItem>(item => {
|
|
39
|
+
const li = document.createElement('li');
|
|
40
|
+
li.textContent = item.name;
|
|
41
|
+
return li;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 2. Group key function
|
|
45
|
+
lister.groupBy = item => item.ext;
|
|
46
|
+
|
|
47
|
+
// 3. Data source — sync, Promise, or AsyncIterable
|
|
48
|
+
lister.provider = () => myFiles;
|
|
49
|
+
|
|
50
|
+
// 4. Listen for selection changes
|
|
51
|
+
lister.addEventListener('grouped-select', ({ detail }) => {
|
|
52
|
+
const { selected, groupKey } = detail;
|
|
53
|
+
console.log(groupKey, [...selected].map(f => f.name));
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<ixfx-grouped-item-lister grouped></ixfx-grouped-item-lister>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Properties
|
|
64
|
+
|
|
65
|
+
### HTML attributes
|
|
66
|
+
|
|
67
|
+
| Attribute | Type | Default | Description |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `grouped` | `boolean` | `false` | When set, items are split into one sub-component per group key. Without it, all items go into a single sub-component. |
|
|
70
|
+
| `cross-group-selection` | `boolean` | `false` | When `grouped` is on, controls whether selecting an item in one group automatically clears all other groups. Has no effect when `grouped` is off. |
|
|
71
|
+
|
|
72
|
+
### JavaScript-only properties
|
|
73
|
+
|
|
74
|
+
These carry typed or callable values and cannot be set as HTML attributes.
|
|
75
|
+
|
|
76
|
+
| Property | Type | Description |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `factory` | `SubComponentFactory<T>` | Called once per group (or once for the ungrouped view) to create a sub-component. **Must be set before items arrive.** |
|
|
79
|
+
| `groupBy` | `(item: T) => string` | Derives the group key for each item. Required in grouped mode. |
|
|
80
|
+
| `provider` | `IDataProvider<void, T>` | Data source for items. Setting this property triggers an immediate request. |
|
|
81
|
+
|
|
82
|
+
#### `SubComponentFactory<T>`
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
type SubComponentFactory<T> = (groupKey: string | undefined) => HTMLElement & IGroupedSubComponent<T>;
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`groupKey` is the group's key string in grouped mode, or `undefined` in ungrouped mode.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Providing data
|
|
93
|
+
|
|
94
|
+
`provider` accepts any `IDataProvider<void, T>` — a function returning one of:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// Sync — items available immediately
|
|
98
|
+
lister.provider = () => myItemsArray;
|
|
99
|
+
|
|
100
|
+
// Async one-shot
|
|
101
|
+
lister.provider = async () => {
|
|
102
|
+
const res = await fetch('/api/items');
|
|
103
|
+
return res.json() as T[];
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Streaming — items delivered in batches over time
|
|
107
|
+
lister.provider = async function* () {
|
|
108
|
+
for await (const page of paginatedFetch('/api/items')) {
|
|
109
|
+
yield { items: page };
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Replacing `provider` cancels any in-flight request and starts a fresh one.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Built-in adapters
|
|
119
|
+
|
|
120
|
+
Two factory helpers ship with the package, one for each existing list component.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `createVerticalListAdapter`
|
|
125
|
+
|
|
126
|
+
Wraps `ixfx-vertical-list`. Items are typed as `T`; the adapter converts each item to a DOM element via your `renderItem` function, maintains a `WeakMap<Element, T>` for reverse-lookup, and bridges `list-*` events to the interface names (`select`, `activate`, etc.).
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { createVerticalListAdapter } from '@ixfx/components';
|
|
130
|
+
|
|
131
|
+
lister.factory = () =>
|
|
132
|
+
createVerticalListAdapter<MyItem>(
|
|
133
|
+
// Convert your typed item to a DOM element (typically an <li>)
|
|
134
|
+
item => {
|
|
135
|
+
const li = document.createElement('li');
|
|
136
|
+
li.textContent = item.label;
|
|
137
|
+
li.dataset.key = item.id;
|
|
138
|
+
li.dataset.searchLabel = item.label; // enables Ctrl+F search
|
|
139
|
+
return li;
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
selectionMode: 'single', // 'none' | 'single' | 'multiple'
|
|
143
|
+
interactionMode: 'standard', // forwarded to ixfx-vertical-list
|
|
144
|
+
onDispatch: (commandId, args, list) => {
|
|
145
|
+
if (commandId === 'sort-alpha' && list) {
|
|
146
|
+
const rows = [...list.querySelectorAll<HTMLElement>('li')];
|
|
147
|
+
rows.sort((a, b) => (a.textContent ?? '').localeCompare(b.textContent ?? ''));
|
|
148
|
+
for (const row of rows) list.appendChild(row);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Options
|
|
156
|
+
|
|
157
|
+
| Option | Type | Default | Description |
|
|
158
|
+
|---|---|---|---|
|
|
159
|
+
| `selectionMode` | `'none' \| 'single' \| 'multiple'` | `'single'` | Forwarded to the inner `ixfx-vertical-list` |
|
|
160
|
+
| `interactionMode` | `ListInteractionMode` | `'standard'` | Forwarded to the inner `ixfx-vertical-list` |
|
|
161
|
+
| `onDispatch` | `(commandId, args, list?) => void` | — | Called when `lister.dispatch()` is invoked. `list` is the inner `VerticalListElement` |
|
|
162
|
+
|
|
163
|
+
The inner `ixfx-vertical-list` inherits its full feature set: keyboard navigation, incremental search (Ctrl+F), and all selection modes including checkboxes.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### `createTreeListAdapter`
|
|
168
|
+
|
|
169
|
+
Wraps `ixfx-tree-list`. Builds a `TreeNode` hierarchy from your flat or nested items, stores each original `T` in `TreeItem.data` for selection reverse-mapping, and bridges tree events to the interface.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { createTreeListAdapter } from '@ixfx/components';
|
|
173
|
+
|
|
174
|
+
lister.factory = () =>
|
|
175
|
+
createTreeListAdapter<CategoryItem>(
|
|
176
|
+
// Convert your item to TreeItem display metadata
|
|
177
|
+
item => ({
|
|
178
|
+
key: item.id,
|
|
179
|
+
label: item.name,
|
|
180
|
+
isLeaf: item.children.length === 0,
|
|
181
|
+
icon: item.children.length ? 'folder' : 'file',
|
|
182
|
+
}),
|
|
183
|
+
{
|
|
184
|
+
// Return the item's direct children to build the hierarchy
|
|
185
|
+
getChildren: item => item.children,
|
|
186
|
+
selectionFilter: 'all',
|
|
187
|
+
onDispatch: (commandId, _args, tree) => {
|
|
188
|
+
if (commandId === 'sort-alpha' && tree?.root?.children) {
|
|
189
|
+
tree.root = {
|
|
190
|
+
...tree.root,
|
|
191
|
+
children: [...tree.root.children].sort((a, b) =>
|
|
192
|
+
a.item.label.localeCompare(b.item.label),
|
|
193
|
+
),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Flat item lists
|
|
202
|
+
|
|
203
|
+
Omit `getChildren` when items have no hierarchy — all items become root-level leaf nodes:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
lister.factory = () =>
|
|
207
|
+
createTreeListAdapter<FileItem>(
|
|
208
|
+
item => ({ key: item.id, label: item.name, isLeaf: true }),
|
|
209
|
+
);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### Options
|
|
213
|
+
|
|
214
|
+
| Option | Type | Default | Description |
|
|
215
|
+
|---|---|---|---|
|
|
216
|
+
| `getChildren` | `(item: T) => readonly T[]` | — | Returns the item's direct children. Omit for flat lists. |
|
|
217
|
+
| `interactionMode` | `TreeInteractionMode` | `'standard'` | Forwarded to the inner `ixfx-tree-list` |
|
|
218
|
+
| `selectionFilter` | `TreeSelectionFilter` | `'all'` | Which node types are selectable (`'leaf'`, `'branch'`, `'all'`, `'none'`) |
|
|
219
|
+
| `exclusivity` | `TreeExclusivity` | `'none'` | Accordion behaviour when expanding nodes |
|
|
220
|
+
| `onDispatch` | `(commandId, args, tree?) => void` | — | Called when `lister.dispatch()` is invoked. `tree` is the inner `TreeBaseElement` |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Selection
|
|
225
|
+
|
|
226
|
+
### Cross-group selection
|
|
227
|
+
|
|
228
|
+
By default (`cross-group-selection` is off), clicking an item in group B automatically clears selections in all other groups. Set the attribute to allow simultaneous selections across groups:
|
|
229
|
+
|
|
230
|
+
```html
|
|
231
|
+
<ixfx-grouped-item-lister grouped cross-group-selection></ixfx-grouped-item-lister>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
lister.crossGroupSelection = true;
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
This attribute has no effect when `grouped` is off.
|
|
239
|
+
|
|
240
|
+
### Reading the current selection
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// Full union of all selected items across every group
|
|
244
|
+
const all: ReadonlySet<T> = lister.selectedItems;
|
|
245
|
+
|
|
246
|
+
// Or via the grouped-select event (see Events)
|
|
247
|
+
lister.addEventListener('grouped-select', ({ detail }) => {
|
|
248
|
+
const { selected, groupKey } = detail;
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Dispatching commands
|
|
255
|
+
|
|
256
|
+
`lister.dispatch(commandId, args?)` forwards a command to every live sub-component by calling their `dispatch` method. Use this to apply global operations — sort order, view mode, filter string — without knowing which groups exist or how many sub-components are active.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// Sort all groups alphabetically
|
|
260
|
+
lister.dispatch('sort-alpha');
|
|
261
|
+
|
|
262
|
+
// Pass arguments
|
|
263
|
+
lister.dispatch('filter', { query: 'image' });
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Sub-components receive the command via their `dispatch` method. Both built-in adapters expose an `onDispatch` option for handling commands.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Events
|
|
271
|
+
|
|
272
|
+
### `grouped-select`
|
|
273
|
+
|
|
274
|
+
Fired by `ixfx-grouped-item-lister` itself after any sub-component's selection changes (and after cross-group enforcement has been applied). The detail carries the **full cross-group union** — it is the single place to observe the overall selection state regardless of mode.
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
lister.addEventListener('grouped-select', ({ detail }) => {
|
|
278
|
+
const { selected, groupKey } = detail;
|
|
279
|
+
// selected: ReadonlySet<T> — union across all groups
|
|
280
|
+
// groupKey: string | undefined — key of the group that changed;
|
|
281
|
+
// undefined in ungrouped mode
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Bubbled sub-component events
|
|
286
|
+
|
|
287
|
+
Because the component uses Light DOM, events fired by sub-components bubble up through `ixfx-grouped-item-lister` unchanged. Register listeners on the lister element to handle them globally:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
lister.addEventListener('activate', event => {
|
|
291
|
+
const { item } = (event as CustomEvent<{ item: T }>).detail;
|
|
292
|
+
console.log('activated:', item);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
lister.addEventListener('item-click', event => { /* ... */ });
|
|
296
|
+
lister.addEventListener('tickle', event => { /* ... */ });
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`event.target` is the sub-component element that fired the event. In grouped mode, querying `event.target.closest('ixfx-panel')` gives the enclosing panel.
|
|
300
|
+
|
|
301
|
+
### Full event reference
|
|
302
|
+
|
|
303
|
+
| Event | Source | Detail | When fired |
|
|
304
|
+
|---|---|---|---|
|
|
305
|
+
| `grouped-select` | `ixfx-grouped-item-lister` | `{ selected: ReadonlySet<T>, groupKey: string \| undefined }` | After any sub-component fires `select` |
|
|
306
|
+
| `select` | sub-component | `{ selected: ReadonlySet<T>, previous: ReadonlySet<T> }` | Selection changes in a single group |
|
|
307
|
+
| `activate` | sub-component | `{ item: T }` | User double-clicks or presses Enter on an item |
|
|
308
|
+
| `item-click` | sub-component | `{ item: T }` | Raw click on an item |
|
|
309
|
+
| `tickle` | sub-component | `{ item: T }` | Pointer hovers over or keyboard cursor moves to an item |
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Writing a custom adapter
|
|
314
|
+
|
|
315
|
+
To use a different rendering component (your own Lit element, a React portal, a canvas renderer, etc.), implement `IGroupedSubComponent<T>` and return an `HTMLElement` that satisfies it from `factory`.
|
|
316
|
+
|
|
317
|
+
### Interface
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
interface IGroupedSubComponent<T> extends HTMLElement {
|
|
321
|
+
/** Replace the full item list. Called whenever items or grouping changes. */
|
|
322
|
+
setItems(items: readonly T[]): void;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Clear the selection. Called by the lister when enforcing cross-group
|
|
326
|
+
* selection. The implementation MAY fire a 'select' event — the lister
|
|
327
|
+
* guards against re-entrant processing.
|
|
328
|
+
*/
|
|
329
|
+
clearSelection(): void;
|
|
330
|
+
|
|
331
|
+
readonly selectedItems: ReadonlySet<T>;
|
|
332
|
+
selectionMode: 'none' | 'single' | 'multiple';
|
|
333
|
+
|
|
334
|
+
/** Receives commands from lister.dispatch(). */
|
|
335
|
+
dispatch(commandId: string, args?: Record<string, unknown>): void;
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Required events
|
|
340
|
+
|
|
341
|
+
Sub-components must fire these events with `bubbles: true, composed: true`:
|
|
342
|
+
|
|
343
|
+
| Event | Detail | When |
|
|
344
|
+
|---|---|---|
|
|
345
|
+
| `select` | `{ selected: ReadonlySet<T>, previous: ReadonlySet<T> }` | Selection changes |
|
|
346
|
+
| `activate` | `{ item: T }` | Double-click or Enter |
|
|
347
|
+
| `item-click` | `{ item: T }` | Raw click on item |
|
|
348
|
+
| `tickle` | `{ item: T }` | Hover or keyboard cursor |
|
|
349
|
+
|
|
350
|
+
### Minimal example
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
class MyItemList extends HTMLElement implements IGroupedSubComponent<MyItem> {
|
|
354
|
+
#items: readonly MyItem[] = [];
|
|
355
|
+
#selected = new Set<MyItem>();
|
|
356
|
+
|
|
357
|
+
selectionMode: 'none' | 'single' | 'multiple' = 'single';
|
|
358
|
+
|
|
359
|
+
get selectedItems(): ReadonlySet<MyItem> { return this.#selected; }
|
|
360
|
+
|
|
361
|
+
setItems(items: readonly MyItem[]): void {
|
|
362
|
+
this.#items = items;
|
|
363
|
+
this.#render();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
clearSelection(): void {
|
|
367
|
+
if (this.#selected.size === 0) return;
|
|
368
|
+
const previous = new Set(this.#selected);
|
|
369
|
+
this.#selected.clear();
|
|
370
|
+
this.#syncStyles();
|
|
371
|
+
this.dispatchEvent(new CustomEvent('select', {
|
|
372
|
+
detail: { selected: new Set(), previous },
|
|
373
|
+
bubbles: true, composed: true,
|
|
374
|
+
}));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
dispatch(commandId: string, _args?: Record<string, unknown>): void {
|
|
378
|
+
// handle commands
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#render(): void {
|
|
382
|
+
this.innerHTML = '';
|
|
383
|
+
for (const item of this.#items) {
|
|
384
|
+
const row = document.createElement('div');
|
|
385
|
+
row.textContent = item.label;
|
|
386
|
+
row.addEventListener('click', () => this.#handleClick(item));
|
|
387
|
+
row.addEventListener('dblclick', () =>
|
|
388
|
+
this.dispatchEvent(new CustomEvent('activate', {
|
|
389
|
+
detail: { item }, bubbles: true, composed: true,
|
|
390
|
+
})),
|
|
391
|
+
);
|
|
392
|
+
row.addEventListener('pointerover', () =>
|
|
393
|
+
this.dispatchEvent(new CustomEvent('tickle', {
|
|
394
|
+
detail: { item }, bubbles: true, composed: true,
|
|
395
|
+
})),
|
|
396
|
+
);
|
|
397
|
+
this.appendChild(row);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
#handleClick(item: MyItem): void {
|
|
402
|
+
this.dispatchEvent(new CustomEvent('item-click', {
|
|
403
|
+
detail: { item }, bubbles: true, composed: true,
|
|
404
|
+
}));
|
|
405
|
+
const previous = new Set(this.#selected);
|
|
406
|
+
if (this.selectionMode === 'single') this.#selected.clear();
|
|
407
|
+
this.#selected.add(item);
|
|
408
|
+
this.#syncStyles();
|
|
409
|
+
this.dispatchEvent(new CustomEvent('select', {
|
|
410
|
+
detail: { selected: new Set(this.#selected), previous },
|
|
411
|
+
bubbles: true, composed: true,
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
#syncStyles(): void { /* update [aria-selected] etc. */ }
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
customElements.define('my-item-list', MyItemList);
|
|
419
|
+
|
|
420
|
+
// Use it
|
|
421
|
+
lister.factory = () => document.createElement('my-item-list') as MyItemList;
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
> **Constructor constraint**: Custom elements cannot call `this.appendChild()` in their constructor. If your sub-component needs child elements, create them lazily on the first `setItems()` call or in `connectedCallback()`.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Icon Registry
|
|
2
|
+
|
|
3
|
+
A centralized SVG icon system for `@ixfx/components`. Icons are stored by name, can be overridden globally, and all components that use a given icon re-render automatically when it changes.
|
|
4
|
+
|
|
5
|
+
## Built-in icons
|
|
6
|
+
|
|
7
|
+
| Name | Used by | Fill |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `chevron-down` | `<ixfx-split-button>`, `<ixfx-panel>` | `currentColor` |
|
|
10
|
+
| `check` | Available for use | `currentColor` |
|
|
11
|
+
| `close` | `<ixfx-panel>`, `<ixfx-split-button>` | `currentColor` |
|
|
12
|
+
|
|
13
|
+
## Overriding a built-in icon
|
|
14
|
+
|
|
15
|
+
Call `registerIcon` before or after the library loads — components re-render automatically.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { registerIcon } from '@ixfx/components';
|
|
19
|
+
|
|
20
|
+
registerIcon('chevron-down', `<svg viewBox="0 0 24 24" fill="currentColor">
|
|
21
|
+
<path d="M7 10l5 5 5-5H7z"/>
|
|
22
|
+
</svg>`);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The split-button's toggle arrow updates immediately on the next render cycle.
|
|
26
|
+
|
|
27
|
+
## Adding a custom icon
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { registerIcon } from '@ixfx/components';
|
|
31
|
+
|
|
32
|
+
registerIcon('my-logo', `<svg viewBox="0 0 24 24" fill="currentColor">
|
|
33
|
+
<path d="..."/>
|
|
34
|
+
</svg>`);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then use it anywhere in your app:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<ixfx-icon name="my-logo"></ixfx-icon>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## `<ixfx-icon>` element
|
|
44
|
+
|
|
45
|
+
Renders an icon from the registry by name. Re-renders automatically if the icon is replaced via `registerIcon`.
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<!-- Basic usage -->
|
|
49
|
+
<ixfx-icon name="chevron-down"></ixfx-icon>
|
|
50
|
+
|
|
51
|
+
<!-- Fixed pixel size -->
|
|
52
|
+
<ixfx-icon name="close" size="32"></ixfx-icon>
|
|
53
|
+
|
|
54
|
+
<!-- Accessible label (removes aria-hidden, adds aria-label) -->
|
|
55
|
+
<ixfx-icon name="close" label="Close dialog"></ixfx-icon>
|
|
56
|
+
|
|
57
|
+
<!-- Rotate the icon (e.g. for expand/collapse chevrons) -->
|
|
58
|
+
<ixfx-icon name="chevron-down" rotate="-90deg"></ixfx-icon>
|
|
59
|
+
|
|
60
|
+
<!-- Prominence mode for idle/tickled/disabled states -->
|
|
61
|
+
<ixfx-icon name="settings" prominence></ixfx-icon>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Attributes
|
|
65
|
+
|
|
66
|
+
| Attribute | Type | Default | Description |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| `name` | `string` | `''` | Registry key of the icon to render |
|
|
69
|
+
| `size` | `number` | — | Explicit `width`/`height` in px. Omit to size via CSS (`1em × 1em` default) |
|
|
70
|
+
| `label` | `string` | `''` | Accessible label. When set, adds `aria-label` and removes `aria-hidden` |
|
|
71
|
+
| `rotate` | `string` | `''` | CSS angle (e.g. `"90deg"`, `"-90deg"`) to rotate the icon. Animatable with `--transition` |
|
|
72
|
+
| `prominence` | `boolean` | `false` | When set, enables prominence styling (opacity transitions on hover/disabled) |
|
|
73
|
+
|
|
74
|
+
### Sizing via CSS
|
|
75
|
+
|
|
76
|
+
By default `<ixfx-icon>` is `1em × 1em`, inheriting the surrounding font size. Override with CSS:
|
|
77
|
+
|
|
78
|
+
```css
|
|
79
|
+
ixfx-icon {
|
|
80
|
+
width: 24px;
|
|
81
|
+
height: 24px;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Or inline:
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<ixfx-icon name="check" style="width:20px;height:20px"></ixfx-icon>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Rotation
|
|
92
|
+
|
|
93
|
+
Use the `rotate` attribute to rotate an icon by a CSS angle. The rotation is applied as a CSS transform and is animatable:
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<!-- Collapsed state: chevron points right -->
|
|
97
|
+
<ixfx-icon name="chevron-down" rotate="-90deg"></ixfx-icon>
|
|
98
|
+
|
|
99
|
+
<!-- Expanded state: chevron points down (no rotation) -->
|
|
100
|
+
<ixfx-icon name="chevron-down"></ixfx-icon>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The rotation uses a CSS variable internally, so it transitions smoothly when the attribute changes (requires `--transition` to be set).
|
|
104
|
+
|
|
105
|
+
### Prominence mode
|
|
106
|
+
|
|
107
|
+
The `prominence` attribute enables automatic opacity styling for idle, hover/tickled, and disabled states. When present, the icon uses CSS custom properties:
|
|
108
|
+
|
|
109
|
+
```css
|
|
110
|
+
ixfx-icon {
|
|
111
|
+
opacity: var(--prominence-idle-opacity, 1); /* default */
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
ixfx-icon:hover {
|
|
115
|
+
opacity: var(--prominence-tickled-opacity, 1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
ixfx-icon[disabled] {
|
|
119
|
+
opacity: var(--prominence-disabled-opacity, 0.5);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Without `prominence`, the icon uses `--opacity` (defaults to 1).
|
|
124
|
+
|
|
125
|
+
## CSS custom properties
|
|
126
|
+
|
|
127
|
+
| Property | Default | Description |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `--opacity` | `1` | Icon opacity when `prominence` is NOT set |
|
|
130
|
+
| `--prominence-idle-opacity` | `0.6` | Opacity when `prominence` is set and idle |
|
|
131
|
+
| `--prominence-tickled-opacity` | `1` | Opacity on hover (when `prominence` is set) |
|
|
132
|
+
| `--prominence-disabled-opacity` | `0.5` | Opacity when disabled (when `prominence` is set) |
|
|
133
|
+
| `--transition` | `0.15s ease-out` | Transition duration for opacity and rotation |
|
|
134
|
+
|
|
135
|
+
## API
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { registerIcon, getIcon, hasIcon, svgToDataUri, iconBus } from '@ixfx/components';
|
|
139
|
+
|
|
140
|
+
// Add or replace an icon. Notifies all subscribers.
|
|
141
|
+
registerIcon(name: string, svg: string): void
|
|
142
|
+
|
|
143
|
+
// Retrieve an SVG string by name. Returns undefined if not found.
|
|
144
|
+
getIcon(name: string): string | undefined
|
|
145
|
+
|
|
146
|
+
// Check whether an icon is registered.
|
|
147
|
+
hasIcon(name: string): boolean
|
|
148
|
+
|
|
149
|
+
// Convert an SVG string to a CSS data-URI (e.g. for mask-image).
|
|
150
|
+
// svgToDataUri('<svg .../>') → 'url("data:image/svg+xml,...")'
|
|
151
|
+
svgToDataUri(svg: string): string
|
|
152
|
+
|
|
153
|
+
// EventTarget that emits 'ixfx-icon-change' CustomEvents when any icon changes.
|
|
154
|
+
// event.detail.name contains the changed icon name.
|
|
155
|
+
iconBus: EventTarget
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## SVG format conventions for custom icons
|
|
159
|
+
|
|
160
|
+
- `viewBox="0 0 24 24"` — consistent with built-ins
|
|
161
|
+
- No `width` or `height` attributes on `<svg>` — let CSS control sizing
|
|
162
|
+
- `fill="currentColor"` for inline icons — inherits CSS `color`
|
|
163
|
+
- `fill="black"` for CSS mask icons — color comes from `background-color`
|
|
164
|
+
- `aria-hidden="true"` on the `<svg>` itself — the `<ixfx-icon>` wrapper handles accessibility
|
|
165
|
+
|
|
166
|
+
## Subscribing to icon changes
|
|
167
|
+
|
|
168
|
+
Use `iconBus` if you need to react to registry changes outside of a component:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { iconBus } from '@ixfx/components';
|
|
172
|
+
import type { IconChangedEvent } from '@ixfx/components';
|
|
173
|
+
|
|
174
|
+
iconBus.addEventListener('ixfx-icon-change', (e) => {
|
|
175
|
+
const { name } = (e as IconChangedEvent).detail;
|
|
176
|
+
console.log(`Icon "${name}" was updated`);
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Importing raw SVG strings
|
|
181
|
+
|
|
182
|
+
The built-in SVG strings are exported as constants if you need them directly:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { ICON_CHEVRON_DOWN, ICON_CARET_RIGHT, ICON_CHECK, ICON_CLOSE } from '@ixfx/components';
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Notes
|
|
189
|
+
|
|
190
|
+
- The registry is a module singleton. If multiple copies of `@ixfx/components` are loaded in the same page (e.g. version mismatch in a monorepo), each copy has its own registry and bus — overrides in one will not propagate to the other.
|
|
191
|
+
- `registerIcon` uses `unsafeHTML` internally via `<ixfx-icon>`. Never pass untrusted user input as an SVG string.
|