@ixfx/components 0.3.1 → 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.
Files changed (256) hide show
  1. package/bundle/index.d.ts +1086 -603
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +16527 -14258
  4. package/bundle/index.js.map +1 -1
  5. package/bundle/style.css +14 -4
  6. package/dist/ac-text.d.ts.map +1 -1
  7. package/dist/ac-text.js +11 -6
  8. package/dist/ac-text.js.map +1 -1
  9. package/dist/{button-DMt5XWUK.js → button-C3fLMKUv.js} +4 -4
  10. package/dist/{button-DMt5XWUK.js.map → button-C3fLMKUv.js.map} +1 -1
  11. package/dist/button.d.ts.map +1 -1
  12. package/dist/button.js +1 -1
  13. package/dist/checkbox.d.ts +2 -2
  14. package/dist/checkbox.d.ts.map +1 -1
  15. package/dist/checkbox.js +14 -8
  16. package/dist/checkbox.js.map +1 -1
  17. package/dist/{colour-C3MQIjFJ-BLXg8W3U.js → colour-C3MQIjFJ-vRCdr8W-.js} +146 -552
  18. package/dist/colour-C3MQIjFJ-vRCdr8W-.js.map +1 -0
  19. package/dist/{colour-picker-K6zu82lm.js → colour-picker-0sgAha1T.js} +14 -9
  20. package/dist/colour-picker-0sgAha1T.js.map +1 -0
  21. package/dist/colour-picker.d.ts +1 -1
  22. package/dist/colour-picker.js +1 -1
  23. package/dist/crumbs.d.ts +1 -1
  24. package/dist/crumbs.d.ts.map +1 -1
  25. package/dist/crumbs.js +11 -6
  26. package/dist/crumbs.js.map +1 -1
  27. package/dist/data-display.d.ts +9 -4
  28. package/dist/data-display.d.ts.map +1 -1
  29. package/dist/data-display.js +270 -43
  30. package/dist/data-display.js.map +1 -1
  31. package/dist/data-provider-DgI909FP.js +23 -0
  32. package/dist/data-provider-DgI909FP.js.map +1 -0
  33. package/dist/{decorate-D7rC1gLP.js → decorate-DdjvVHS-.js} +2 -2
  34. package/dist/{decorate-D7rC1gLP.js.map → decorate-DdjvVHS-.js.map} +1 -1
  35. package/dist/{defaults-C7elhuyJ.js → defaults-C3b9OWJD.js} +2 -2
  36. package/dist/{defaults-C7elhuyJ.js.map → defaults-C3b9OWJD.js.map} +1 -1
  37. package/dist/{dist-TAGYwaju.js → dist-CA13FvMn.js} +7 -66
  38. package/dist/dist-CA13FvMn.js.map +1 -0
  39. package/dist/editable-label.d.ts.map +1 -1
  40. package/dist/editable-label.js +3 -2
  41. package/dist/editable-label.js.map +1 -1
  42. package/dist/fallbacks-pNMOMx38.js +168 -0
  43. package/dist/fallbacks-pNMOMx38.js.map +1 -0
  44. package/dist/{hex-editor-DlfoA65y.d.ts → hex-editor-CMIxtNgT.d.ts} +1 -1
  45. package/dist/hex-editor-CMIxtNgT.d.ts.map +1 -0
  46. package/dist/{hex-editor-CsN_ySkH.js → hex-editor-WZrjRtvL.js} +3 -3
  47. package/dist/{hex-editor-CsN_ySkH.js.map → hex-editor-WZrjRtvL.js.map} +1 -1
  48. package/dist/hex.d.ts +1 -1
  49. package/dist/hex.js +1 -1
  50. package/dist/{highlight-TPbSHruI.js → highlight-B-eQrhhb.js} +1 -1
  51. package/dist/{highlight-TPbSHruI.js.map → highlight-B-eQrhhb.js.map} +1 -1
  52. package/dist/{icon-CeTDJhC7.d.ts → icon-Bd5BiU4b.d.ts} +2 -2
  53. package/dist/icon-Bd5BiU4b.d.ts.map +1 -0
  54. package/dist/{icon-9NWTXtE1.js → icon-Ndo40kNO.js} +6 -5
  55. package/dist/icon-Ndo40kNO.js.map +1 -0
  56. package/dist/icons.d.ts +2 -2
  57. package/dist/icons.js +3 -3
  58. package/dist/{incr-search-DSKKsk6w.js → incr-search-CeERfijK.js} +2 -2
  59. package/dist/{incr-search-DSKKsk6w.js.map → incr-search-CeERfijK.js.map} +1 -1
  60. package/dist/incr-search.d.ts +1 -1
  61. package/dist/incr-search.js +2 -2
  62. package/dist/{index-hXFn14tM.d.ts → index-BMjri2-S.d.ts} +2 -2
  63. package/dist/{index-hXFn14tM.d.ts.map → index-BMjri2-S.d.ts.map} +1 -1
  64. package/dist/{index-DSlPeWLK.d.ts → index-CWpNcQKE.d.ts} +5 -5
  65. package/dist/index-CWpNcQKE.d.ts.map +1 -0
  66. package/dist/{index--hrxPBkQ.d.ts → index-CowKi2mo.d.ts} +2 -1
  67. package/dist/index-CowKi2mo.d.ts.map +1 -0
  68. package/dist/{index-B1hNR7Z9.d.ts → index-D87kRcEs.d.ts} +1 -1
  69. package/dist/{index-B1hNR7Z9.d.ts.map → index-D87kRcEs.d.ts.map} +1 -1
  70. package/dist/{index-CnL9krsH.d.ts → index-DgXpCg_G.d.ts} +32 -26
  71. package/dist/index-DgXpCg_G.d.ts.map +1 -0
  72. package/dist/{index-DrQjA8Gs.d.ts → index-s_IhSnwc.d.ts} +2 -2
  73. package/dist/index-s_IhSnwc.d.ts.map +1 -0
  74. package/dist/index.d.ts +432 -121
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +2370 -1707
  77. package/dist/index.js.map +1 -1
  78. package/dist/interaction-D6efrBLo.js +1 -0
  79. package/dist/{keyboard-De_vOHLg.js → keyboard-CimiEskD.js} +1 -1
  80. package/dist/{keyboard-De_vOHLg.js.map → keyboard-CimiEskD.js.map} +1 -1
  81. package/dist/{labelled-input-base-CqPL5rKm.js → labelled-input-base-9K_w3oLj.js} +2 -2
  82. package/dist/{labelled-input-base-CqPL5rKm.js.map → labelled-input-base-9K_w3oLj.js.map} +1 -1
  83. package/dist/{labelled-input-base-D-Y7agky.d.ts → labelled-input-base-ORb1rGq6.d.ts} +1 -1
  84. package/dist/labelled-input-base-ORb1rGq6.d.ts.map +1 -0
  85. package/dist/labelled-radial-input.d.ts +2 -2
  86. package/dist/labelled-radial-input.d.ts.map +1 -1
  87. package/dist/labelled-radial-input.js +7 -6
  88. package/dist/labelled-radial-input.js.map +1 -1
  89. package/dist/labelled-range-input.d.ts +1 -1
  90. package/dist/labelled-range-input.js +2 -2
  91. package/dist/led.d.ts +1 -1
  92. package/dist/led.d.ts.map +1 -1
  93. package/dist/led.js +2 -2
  94. package/dist/led.js.map +1 -1
  95. package/dist/{menu-item-CKRfP0pN.js → menu-item-BrIuORYD.js} +20 -10
  96. package/dist/menu-item-BrIuORYD.js.map +1 -0
  97. package/dist/{menu-item-ClQF2DKN.d.ts → menu-item-COx-Azgj.d.ts} +1 -1
  98. package/dist/menu-item-COx-Azgj.d.ts.map +1 -0
  99. package/dist/{menu-DJJuCm-5.js → menu-vjoiHZ13.js} +30 -17
  100. package/dist/menu-vjoiHZ13.js.map +1 -0
  101. package/dist/menu.d.ts +2 -2
  102. package/dist/menu.js +2 -2
  103. package/dist/miller.d.ts +2 -2
  104. package/dist/miller.d.ts.map +1 -1
  105. package/dist/miller.js +18 -11
  106. package/dist/miller.js.map +1 -1
  107. package/dist/narrowed-text.d.ts.map +1 -1
  108. package/dist/narrowed-text.js +7 -5
  109. package/dist/narrowed-text.js.map +1 -1
  110. package/dist/panel.d.ts +1 -1
  111. package/dist/panel.d.ts.map +1 -1
  112. package/dist/panel.js +5 -4
  113. package/dist/panel.js.map +1 -1
  114. package/dist/plots.d.ts +2 -2
  115. package/dist/plots.js +2 -2
  116. package/dist/polar-pad.d.ts +1 -1
  117. package/dist/polar-pad.d.ts.map +1 -1
  118. package/dist/polar-pad.js +10 -4
  119. package/dist/polar-pad.js.map +1 -1
  120. package/dist/{popup-ImtS9KnS.js → popup-DiWMxsWj.js} +1 -1
  121. package/dist/{popup-ImtS9KnS.js.map → popup-DiWMxsWj.js.map} +1 -1
  122. package/dist/prominence-DlFShic3.js +104 -0
  123. package/dist/prominence-DlFShic3.js.map +1 -0
  124. package/dist/{radial-input-LUSBEycd.d.ts → radial-input-CpQPs1Mp.d.ts} +1 -1
  125. package/dist/radial-input-CpQPs1Mp.d.ts.map +1 -0
  126. package/dist/{radial-input-DZA6WaFu.js → radial-input-DZ7eQCcM.js} +11 -6
  127. package/dist/radial-input-DZ7eQCcM.js.map +1 -0
  128. package/dist/radial-input.d.ts +1 -1
  129. package/dist/radial-input.js +1 -1
  130. package/dist/range-input.d.ts +1 -1
  131. package/dist/range-input.d.ts.map +1 -1
  132. package/dist/range-input.js +9 -4
  133. package/dist/range-input.js.map +1 -1
  134. package/dist/range.d.ts +1 -1
  135. package/dist/range.d.ts.map +1 -1
  136. package/dist/range.js +5 -3
  137. package/dist/range.js.map +1 -1
  138. package/dist/{registry-Q2gYQCHM.js → registry-CkW7q09Q.js} +1 -1
  139. package/dist/{registry-Q2gYQCHM.js.map → registry-CkW7q09Q.js.map} +1 -1
  140. package/dist/selecthorizontal.d.ts +1 -1
  141. package/dist/selecthorizontal.d.ts.map +1 -1
  142. package/dist/selecthorizontal.js +4 -3
  143. package/dist/selecthorizontal.js.map +1 -1
  144. package/dist/snap-container.d.ts.map +1 -1
  145. package/dist/snap-container.js +1 -1
  146. package/dist/split-layout.d.ts +1 -1
  147. package/dist/split-layout.d.ts.map +1 -1
  148. package/dist/split-layout.js +6 -5
  149. package/dist/split-layout.js.map +1 -1
  150. package/dist/style.css +14 -4
  151. package/dist/swipe.d.ts.map +1 -1
  152. package/dist/swipe.js +9 -4
  153. package/dist/swipe.js.map +1 -1
  154. package/dist/{tab-list-BnqISNvO.d.ts → tab-list-DLzb-m-I.d.ts} +2 -2
  155. package/dist/tab-list-DLzb-m-I.d.ts.map +1 -0
  156. package/dist/tabs.d.ts +1 -1
  157. package/dist/tabs.js +10 -4
  158. package/dist/tabs.js.map +1 -1
  159. package/dist/{tickled-controller-Cq1Va0rR.d.ts → tickled-controller-BjYCzGrU.d.ts} +2 -2
  160. package/dist/tickled-controller-BjYCzGrU.d.ts.map +1 -0
  161. package/dist/{tickled-styles-Bg3QbcrD.js → tickled-styles-fNDdqf6l.js} +1 -17
  162. package/dist/{tickled-styles-Bg3QbcrD.js.map → tickled-styles-fNDdqf6l.js.map} +1 -1
  163. package/dist/{timeline-Cv75Th2U.js → timeline-2OFtN4Ta.js} +1437 -36
  164. package/dist/timeline-2OFtN4Ta.js.map +1 -0
  165. package/dist/timeline.d.ts +1 -1
  166. package/dist/timeline.js +1 -1
  167. package/dist/{tooltip-DMxmBNky.js → tooltip-CJt3WLmN.js} +1 -1
  168. package/dist/{tooltip-DMxmBNky.js.map → tooltip-CJt3WLmN.js.map} +1 -1
  169. package/dist/{tooltip-CwGfb4lp.d.ts → tooltip-RGVIwHrZ.d.ts} +1 -1
  170. package/dist/{tooltip-CwGfb4lp.d.ts.map → tooltip-RGVIwHrZ.d.ts.map} +1 -1
  171. package/dist/{tree-CvvKbO-f.js → tree-CJWpMUvD.js} +113 -103
  172. package/dist/tree-CJWpMUvD.js.map +1 -0
  173. package/dist/{tree-component-D8Bg48tt.d.ts → tree-component-BcO26Xvt.d.ts} +65 -3
  174. package/dist/tree-component-BcO26Xvt.d.ts.map +1 -0
  175. package/dist/tree.d.ts +15 -15
  176. package/dist/tree.d.ts.map +1 -1
  177. package/dist/tree.js +1 -1
  178. package/dist/{types-BKaqnpwx.d.ts → types-DSfbtHs3.d.ts} +1 -1
  179. package/dist/types-DSfbtHs3.d.ts.map +1 -0
  180. package/dist/{xy-axis-DL0tfJDr.js → xy-axis-CK64haaK.js} +1831 -359
  181. package/dist/xy-axis-CK64haaK.js.map +1 -0
  182. package/dist/{xy-axis-D9uwpGTT.d.ts → xy-axis-McdUpcYr.d.ts} +102 -3
  183. package/dist/xy-axis-McdUpcYr.d.ts.map +1 -0
  184. package/dist/xy-pad.d.ts +1 -1
  185. package/dist/xy-pad.d.ts.map +1 -1
  186. package/dist/xy-pad.js +11 -6
  187. package/dist/xy-pad.js.map +1 -1
  188. package/docs-user/README.md +45 -0
  189. package/docs-user/ac-text.md +67 -0
  190. package/docs-user/ac-token.md +187 -0
  191. package/docs-user/button.md +355 -0
  192. package/docs-user/checkbox.md +62 -0
  193. package/docs-user/colour-picker.md +237 -0
  194. package/docs-user/crumbs.md +66 -0
  195. package/docs-user/data-display.md +98 -0
  196. package/docs-user/editable-label.md +244 -0
  197. package/docs-user/grouped-item-lister.md +424 -0
  198. package/docs-user/icons.md +191 -0
  199. package/docs-user/incr-search.md +94 -0
  200. package/docs-user/labelled-radial-input.md +204 -0
  201. package/docs-user/labelled-range-input.md +194 -0
  202. package/docs-user/led.md +40 -0
  203. package/docs-user/menu.md +319 -0
  204. package/docs-user/miller.md +453 -0
  205. package/docs-user/narrowed-text.md +72 -0
  206. package/docs-user/panel.md +250 -0
  207. package/docs-user/plots.md +361 -0
  208. package/docs-user/polar-pad.md +228 -0
  209. package/docs-user/radial-input.md +559 -0
  210. package/docs-user/range-input.md +264 -0
  211. package/docs-user/range.md +12 -0
  212. package/docs-user/selecthorizontal.md +32 -0
  213. package/docs-user/snap-container.md +132 -0
  214. package/docs-user/split-layout.md +232 -0
  215. package/docs-user/swipe.md +44 -0
  216. package/docs-user/tabs.md +217 -0
  217. package/docs-user/timeline.md +116 -0
  218. package/docs-user/transitory-label.md +120 -0
  219. package/docs-user/tree.md +592 -0
  220. package/docs-user/user-catalog.md +165 -0
  221. package/docs-user/user-theming.md +379 -0
  222. package/docs-user/util.md +122 -0
  223. package/docs-user/vertical-list.md +351 -0
  224. package/docs-user/xy-pad.md +94 -0
  225. package/package.json +19 -18
  226. package/dist/colour-C3MQIjFJ-BLXg8W3U.js.map +0 -1
  227. package/dist/colour-picker-K6zu82lm.js.map +0 -1
  228. package/dist/dist-BX0OVIm7.js +0 -1290
  229. package/dist/dist-BX0OVIm7.js.map +0 -1
  230. package/dist/dist-TAGYwaju.js.map +0 -1
  231. package/dist/hex-editor-DlfoA65y.d.ts.map +0 -1
  232. package/dist/icon-9NWTXtE1.js.map +0 -1
  233. package/dist/icon-CeTDJhC7.d.ts.map +0 -1
  234. package/dist/index--hrxPBkQ.d.ts.map +0 -1
  235. package/dist/index-CnL9krsH.d.ts.map +0 -1
  236. package/dist/index-DSlPeWLK.d.ts.map +0 -1
  237. package/dist/index-DrQjA8Gs.d.ts.map +0 -1
  238. package/dist/interaction-DG7X1rhI.js +0 -1
  239. package/dist/labelled-input-base-D-Y7agky.d.ts.map +0 -1
  240. package/dist/menu-DJJuCm-5.js.map +0 -1
  241. package/dist/menu-item-CKRfP0pN.js.map +0 -1
  242. package/dist/menu-item-ClQF2DKN.d.ts.map +0 -1
  243. package/dist/prominence--ic9uWBl.js +0 -98
  244. package/dist/prominence--ic9uWBl.js.map +0 -1
  245. package/dist/radial-input-DZA6WaFu.js.map +0 -1
  246. package/dist/radial-input-LUSBEycd.d.ts.map +0 -1
  247. package/dist/tab-list-BnqISNvO.d.ts.map +0 -1
  248. package/dist/tickled-controller-Cq1Va0rR.d.ts.map +0 -1
  249. package/dist/timeline-Cv75Th2U.js.map +0 -1
  250. package/dist/tree-CvvKbO-f.js.map +0 -1
  251. package/dist/tree-component-D8Bg48tt.d.ts.map +0 -1
  252. package/dist/types-BKaqnpwx.d.ts.map +0 -1
  253. package/dist/xy-axis-D9uwpGTT.d.ts.map +0 -1
  254. package/dist/xy-axis-DL0tfJDr.js.map +0 -1
  255. /package/bundle/{chunk-pbuEa-1d.js → chunk-D7D4PA-g.js} +0 -0
  256. /package/dist/{chunk-pbuEa-1d.js → chunk-D7D4PA-g.js} +0 -0
@@ -0,0 +1,453 @@
1
+ # Miller Columns
2
+
3
+ A Miller column component displays hierarchical data as multiple vertical columns, like macOS Finder or email clients. Each column shows the children of the selected item in the previous column.
4
+
5
+ ---
6
+
7
+ ## MillerBaseElement / MillerListElement
8
+
9
+ ### Attributes
10
+
11
+ | Attribute | Type | Default | Description |
12
+ |-----------|------|---------|-------------|
13
+ | `headers` | boolean | `false` | Show column headers labeling each column with its parent item |
14
+
15
+ ### Properties
16
+
17
+ | Property | Type | Default | Description |
18
+ |----------|------|---------|-------------|
19
+ | `root` | `MillerNode \| undefined` | — | The root node of the tree. Assigning a new root resets all columns and selection state. |
20
+ | `selectionMode` | `MillerSelectionMode` | `'implicit'` | Controls how items are selected. See [Selection Modes](#selection-modes) below. |
21
+ | `selectedKeys` | `ReadonlySet<string>` (getter) / `Iterable<string>` (setter) | — | The set of currently selected item keys. Assigning replaces the entire selection and triggers a re-render. |
22
+ | `loadChildren` | `MillerLoadChildrenCallback \| undefined` | — | Callback to load children for lazy nodes. If undefined, clicking a lazy node opens an empty column. |
23
+
24
+ ### CSS Variables
25
+
26
+ These can be overridden in your CSS:
27
+
28
+ | Variable | Default | Description |
29
+ |----------|---------|-------------|
30
+ | `--column-bg` | `var(--surface-l)` | Background color of columns |
31
+ | `--column-header-bg` | `var(--surface)` | Background color of column headers |
32
+ | `--column-header-text` | `var(--surface-muted-text)` | Text color of column headers |
33
+ | `--column-separator` | `var(--surface-h)` | Color of column borders/dividers |
34
+ | `--column-resize-handle` | `var(--surface-h)` | Color of resize handles |
35
+ | `--column-resize-handle-hover` | `var(--accent)` | Color of resize handle on hover |
36
+ | `--column-resize-width` | `4px` | Width of resize handles |
37
+ | `--item-hover-bg` | `var(--item-bg-tickled)` | Background on item hover |
38
+ | `--item-selected-bg` | `var(--item-bg-selected)` | Background of selected item |
39
+ | `--item-selected-text` | `var(--item-text-selected)` | Text color of selected item |
40
+
41
+ Inherited from theme:
42
+ - `--font-family` - Font family
43
+ - `--font-weight-bold` - Bold font weight
44
+ - `--text-s`, `--text-m`, `--text-l` - Font sizes
45
+ - `--space-xs`, `--space-s`, `--space-m` - Spacing
46
+ - `--radius-s` - Border radius
47
+ - `--transition` - Transition timing
48
+
49
+ ### Methods
50
+
51
+ ```typescript
52
+ // Navigate to a node by key path (root-to-target keys, excluding root itself).
53
+ // Only works with already-loaded children — returns false if any node along
54
+ // the path has unloaded children or if a key is not found.
55
+ navigateTo(keys: readonly string[]): boolean
56
+
57
+ // Navigate to the first loaded node (breadth-first) matching a predicate.
58
+ // Returns false if no match is found in the currently loaded tree.
59
+ navigateToNode(predicate: (node: MillerNode) => boolean): boolean
60
+
61
+ // Get current configuration (column widths, etc.)
62
+ getConfig(): MillerConfig
63
+
64
+ // Set configuration. Requires a full MillerConfig object.
65
+ setConfig(config: MillerConfig): void
66
+ ```
67
+
68
+ ### Events
69
+
70
+ All events bubble and are composed.
71
+
72
+ | Event | Detail | Description |
73
+ |-------|--------|-------------|
74
+ | `expand` | `{ node: MillerNode, depth: number }` | FYI - fired after a column opens. |
75
+ | `collapse` | `{ node: MillerNode, depth: number }` | FYI - fired when a column closes. |
76
+ | `item-click` | `{ node: MillerNode, depth: number }` | Fired when any item is clicked. |
77
+ | `select` | `{ node: MillerNode, depth: number }` | Fired when Enter is pressed on an item. |
78
+ | `header-click` | `{ depth: number, label: string, path: MillerNode[] }` | Fired when a column header is clicked (requires `headers` attribute). `path` is the ancestry chain from root down to the node whose label is shown in the header. |
79
+ | `change` | — | Fired when selection changes in `checked` mode (checkbox toggled). |
80
+
81
+ ---
82
+
83
+ ## Selection Modes
84
+
85
+ Set via the `selectionMode` property (not an HTML attribute). Default: `'implicit'`.
86
+
87
+ ```typescript
88
+ type MillerSelectionMode = 'implicit' | 'standard' | 'manual' | 'checked' | 'vscode';
89
+ ```
90
+
91
+ ### `implicit` (default)
92
+
93
+ Selection follows the cursor path — whatever is open in the column pane is considered selected. This is the classic Miller column behaviour and is unchanged from prior versions.
94
+
95
+ ### `standard`
96
+
97
+ Explicit multi-select similar to a file manager:
98
+
99
+ | Input | Action |
100
+ |-------|--------|
101
+ | Click | Single-select item and expand (sets anchor, clears others) |
102
+ | Shift+Click | Contiguous range from anchor to item (within same depth) |
103
+ | Cmd/Ctrl+Click | Toggle item without expanding (no anchor change) |
104
+ | Arrow Up/Down | Move focus; re-seats anchor |
105
+ | Shift+Arrow | Extend range from anchor (bootstraps anchor from current position if unset) |
106
+ | Enter | Select focused item (sets anchor) |
107
+ | Shift+Enter | Extend range to focused item |
108
+
109
+ ### `vscode`
110
+
111
+ Extends `standard` with persistent multi-selection across cursor movement:
112
+
113
+ | Input | Action |
114
+ |-------|--------|
115
+ | Click | Single-select item and expand (clears others) — same as `standard` |
116
+ | Shift+Click | Contiguous range from anchor — same as `standard` |
117
+ | Cmd/Ctrl+Click | Toggle item without expanding — same as `standard` |
118
+ | Arrow Up/Down | Move cursor **only** — selection is preserved, anchor is not re-seated |
119
+ | Shift+Arrow | Extend range from anchor — same as `standard` |
120
+ | Arrow Right | Expand/navigate into branch — selection is preserved (does not clear) |
121
+ | Enter | Add focused item to selection (sets anchor) |
122
+ | Shift+Enter | Extend range to focused item |
123
+
124
+ The key difference from `standard`: in `vscode` mode the cursor is free to move without disturbing the accumulated multi-selection. Only a plain unmodified click clears the selection.
125
+
126
+ ### `manual`
127
+
128
+ The component never modifies `selectedKeys`. All clicks and keyboard actions fire events as normal but selection state is entirely driven by the caller via the public API. Use this when you want to implement custom selection logic.
129
+
130
+ ### `checked`
131
+
132
+ A checkbox appears beside each item. Clicking the row navigates/expands as usual; clicking the checkbox toggles selection. Selecting a branch recursively selects all loaded descendants. Checkbox state follows a three-state cycle: **indeterminate → checked → unchecked**.
133
+
134
+ - **Checked**: item key is in `selectedKeys`
135
+ - **Unchecked**: item key is not in `selectedKeys` and no loaded descendants are selected
136
+ - **Indeterminate**: some (but not all) loaded descendants are selected
137
+
138
+ The `change` event is fired on the component after every checkbox toggle.
139
+
140
+ ---
141
+
142
+ ## Selection API
143
+
144
+ ```typescript
145
+ // Read current selection
146
+ const keys: ReadonlySet<string> = el.selectedKeys;
147
+
148
+ // Replace entire selection (triggers re-render)
149
+ el.selectedKeys = ['key-a', 'key-b'];
150
+
151
+ // Select or deselect a single node
152
+ // In 'checked' mode, selecting a branch recursively selects all loaded descendants
153
+ el.setSelected(node: MillerNode, selected: boolean): void
154
+
155
+ // Return all currently-selected MillerNodes (searches loaded tree only)
156
+ el.getSelected(): MillerNode[]
157
+
158
+ // Clear all selection state
159
+ el.clearSelection(): void
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Types
165
+
166
+ ```typescript
167
+ type MillerItem = {
168
+ key: string; // Unique identifier
169
+ label: string; // Display text
170
+ isLeaf?: boolean; // If true, clicking never fires 'expand'
171
+ data?: unknown; // Arbitrary user data
172
+ icon?: string; // Optional icon name
173
+ tooltip?: string; // Optional tooltip text
174
+ value?: string; // Optional string value
175
+ };
176
+
177
+ type MillerNode = {
178
+ item: MillerItem;
179
+ children?: MillerNode[]; // undefined = unloaded, [] = confirmed empty
180
+ };
181
+
182
+ type MillerConfig = {
183
+ columnWidths: Record<number, number>; // depth -> px
184
+ };
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Basic Usage
190
+
191
+ ### Static Columns
192
+
193
+ ```html
194
+ <ixfx-miller-list id="miller" headers></ixfx-miller-list>
195
+
196
+ <script type="module">
197
+ const el = document.getElementById('miller');
198
+
199
+ el.root = {
200
+ item: { key: 'root', label: 'My Computer' },
201
+ children: [
202
+ {
203
+ item: { key: 'c', label: 'C:' },
204
+ children: [
205
+ {
206
+ item: { key: 'users', label: 'Users' },
207
+ children: [
208
+ { item: { key: 'jane', label: 'Jane', isLeaf: true } },
209
+ { item: { key: 'barry', label: 'Barry', isLeaf: true } }
210
+ ]
211
+ },
212
+ { item: { key: 'windows', label: 'Windows', isLeaf: true } }
213
+ ]
214
+ },
215
+ {
216
+ item: { key: 'd', label: 'D:' },
217
+ children: [
218
+ { item: { key: 'games', label: 'Games', isLeaf: true } }
219
+ ]
220
+ }
221
+ ]
222
+ };
223
+
224
+ // Respond to clicks
225
+ el.addEventListener('item-click', ({ detail }) => {
226
+ console.log('Clicked:', detail.node.item.label, 'at column', detail.depth);
227
+ });
228
+
229
+ // Respond to header clicks (if headers enabled)
230
+ el.addEventListener('header-click', ({ detail }) => {
231
+ console.log('Header clicked:', detail.label, 'at depth', detail.depth);
232
+ console.log('Path:', detail.path.map(n => n.item.label));
233
+ });
234
+ </script>
235
+ ```
236
+
237
+ ### Lazy Loading
238
+
239
+ Set the `loadChildren` callback to handle loading children on demand. The callback
240
+ receives the node, depth, and an AbortSignal. It should return the children array
241
+ (or a Promise that resolves to it). The callback is debounced by 150ms.
242
+
243
+ ```html
244
+ <ixfx-miller-list id="miller"></ixfx-miller-list>
245
+
246
+ <script type="module">
247
+ const el = document.getElementById('miller');
248
+
249
+ // Children at root level are pre-loaded; deeper nodes will be loaded on demand
250
+ el.root = {
251
+ item: { key: 'root', label: 'Root' },
252
+ children: [
253
+ { item: { key: 'a', label: 'Alpha' } }, // children: undefined → lazy
254
+ { item: { key: 'b', label: 'Beta' } },
255
+ ]
256
+ };
257
+
258
+ el.loadChildren = async (node, depth, signal) => {
259
+ // Return children array (sync or async)
260
+ return fetchChildren(node.item.key, signal);
261
+ };
262
+
263
+ async function fetchChildren(key, signal) {
264
+ const response = await fetch(`/api/children/${key}`, { signal });
265
+ return response.json();
266
+ }
267
+ </script>
268
+ ```
269
+
270
+ If no `loadChildren` callback is set, clicking a lazy node opens an empty column.
271
+
272
+ **FYI Events:**
273
+ - `expand`: fired after a column opens. Detail: `{ node, depth }`
274
+ - `collapse`: fired when a column closes (user navigates away or load is aborted). Detail: `{ node, depth }`
275
+
276
+ ### Programmatic Navigation
277
+
278
+ ```javascript
279
+ // Navigate by key path — keys are item.key values from root's children downwards.
280
+ // Returns false (silently) if any node along the path has not yet been loaded.
281
+ el.navigateTo(['c', 'users', 'jane']);
282
+
283
+ // Or find by predicate across already-loaded nodes
284
+ el.navigateToNode(node => node.item.label === 'System32');
285
+ ```
286
+
287
+ ### Column Resizing
288
+
289
+ Columns can be resized by dragging the handle on the right edge of each column. The widths are persisted in the config:
290
+
291
+ ```javascript
292
+ // Serialize current state (e.g. to localStorage)
293
+ const config = el.getConfig();
294
+ localStorage.setItem('millerConfig', JSON.stringify(config));
295
+
296
+ // Restore saved state
297
+ el.setConfig(JSON.parse(localStorage.getItem('millerConfig')));
298
+ ```
299
+
300
+ ### Configuration
301
+
302
+ ```javascript
303
+ // Set custom column widths (depth index → width in px)
304
+ el.setConfig({ columnWidths: { 0: 150, 1: 200, 2: 180 } });
305
+ ```
306
+
307
+ ---
308
+
309
+ ## MillerTreeController
310
+
311
+ Bridge an ixfx `TraversableTree` to a Miller component. Children are populated
312
+ lazily — the controller handles `expand` events by synchronously reading from
313
+ the underlying tree.
314
+
315
+ ```typescript
316
+ import { Trees } from '@ixfx/collections';
317
+ import { MillerTreeController } from './miller-tree-controller.js';
318
+
319
+ const root = Trees.Mutable.createNode({ key: 'root', label: 'Root' });
320
+ Trees.Mutable.addValue({ key: 'a', label: 'Alpha' }, root);
321
+
322
+ const ctrl = new MillerTreeController(
323
+ Trees.Mutable.asDynamicTraversable(root),
324
+ document.querySelector('ixfx-miller-list'),
325
+ {
326
+ // Both the value and the raw tree node are provided so you can inspect
327
+ // child count, identity, etc. when building the MillerItem.
328
+ valueToItem: (value, treeNode) => ({
329
+ key: value.key,
330
+ label: value.label,
331
+ isLeaf: value.isLeaf
332
+ })
333
+ }
334
+ );
335
+
336
+ ctrl.connect();
337
+
338
+ // Navigate to a value — ancestors with unloaded children are populated first
339
+ ctrl.navigateToValue(v => v.label === 'Target');
340
+
341
+ // Clean up event listeners when done
342
+ ctrl.disconnect();
343
+ ```
344
+
345
+ ### MillerTreeControllerOptions
346
+
347
+ ```typescript
348
+ type MillerTreeControllerOptions<T> = {
349
+ // Convert a tree node's value to a MillerItem.
350
+ // key and label are required. If isLeaf is omitted, it is inferred
351
+ // from whether the TraversableTree node has any children.
352
+ valueToItem: (value: T, node: TraversableTree<T>) => MillerItem;
353
+ };
354
+ ```
355
+
356
+ ### Methods
357
+
358
+ ```typescript
359
+ // Wire up the controller — sets element.root and provides loadChildren callback.
360
+ // Safe to call again after disconnect().
361
+ connect(): void
362
+
363
+ // Remove all event listeners and release internal state.
364
+ disconnect(): void
365
+
366
+ // Navigate the component to the first node (depth-first) whose value matches predicate.
367
+ // Any ancestor MillerNodes with unloaded children are populated synchronously first.
368
+ // Returns false if no matching node is found.
369
+ navigateToValue(predicate: (value: T) => boolean): boolean
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Keyboard Navigation
375
+
376
+ Miller columns support full keyboard navigation when the component has focus:
377
+
378
+ | Key | Action |
379
+ |-----|--------|
380
+ | `Arrow Up` | Move focus to previous item in column |
381
+ | `Arrow Down` | Move focus to next item in column |
382
+ | `Arrow Right` | Select focused item and open its children (if any) |
383
+ | `Arrow Left` | Go back to previous column |
384
+ | `Enter` | Fire `select` event on focused item; in `standard` mode also selects and sets anchor |
385
+ | `Shift+Arrow` | In `standard` mode: extend selection range from anchor |
386
+ | `Shift+Enter` | In `standard` mode: extend selection range to focused item |
387
+ | `Escape` | Cancel pending lazy load / go back one column |
388
+
389
+ ---
390
+
391
+ ## Subclassing MillerBaseElement
392
+
393
+ Create custom column renderers by subclassing `MillerBaseElement`. Override
394
+ `renderColumnContent` to control how items are rendered. The following protected
395
+ members are available for subclasses to use:
396
+
397
+ ```typescript
398
+ // True if node is currently selected at the given depth
399
+ _isSelected(node: MillerNode, depth: number): boolean
400
+
401
+ // True if node has keyboard focus at the given depth
402
+ _isFocused(node: MillerNode, depth: number): boolean
403
+
404
+ // Returns the index of the focused item at the given depth
405
+ _getFocusedIndex(depth: number): number
406
+
407
+ // Call this to handle an item click (selection, expand, events)
408
+ _handleItemClick(node: MillerNode, depth: number): void
409
+
410
+ // Handle a checkbox click for the given node (used in 'checked' mode).
411
+ // Returns the new checked state (true = selected) after the toggle.
412
+ // Also fires the 'change' event on the element.
413
+ _handleCheckboxClick(event: MouseEvent, node: MillerNode, depth: number): boolean
414
+
415
+ // The current selection set — read/write in subclasses
416
+ _selectedKeys: Set<string>
417
+ ```
418
+
419
+ Example:
420
+
421
+ ```typescript
422
+ import { MillerBaseElement } from './miller-base.js';
423
+ import { html, css } from 'lit';
424
+ import { customElement } from 'lit/decorators.js';
425
+ import type { MillerNode } from './types.js';
426
+
427
+ @customElement('ixfx-miller-custom')
428
+ export class MillerCustomElement extends MillerBaseElement {
429
+ protected override renderColumnContent(nodes: MillerNode[], depth: number) {
430
+ return html`
431
+ <div class="custom-column">
432
+ ${nodes.map(node => html`
433
+ <div
434
+ class="custom-item ${this._isSelected(node, depth) ? 'selected' : ''}
435
+ ${this._isFocused(node, depth) ? 'focused' : ''}"
436
+ @click=${() => this._handleItemClick(node, depth)}
437
+ >
438
+ ${node.item.label}
439
+ </div>
440
+ `)}
441
+ </div>
442
+ `;
443
+ }
444
+
445
+ static override styles = [
446
+ ...MillerBaseElement.styles,
447
+ css`
448
+ .custom-item.selected { background: var(--item-selected-bg); }
449
+ .custom-item.focused { outline: 2px solid var(--accent); }
450
+ `,
451
+ ];
452
+ }
453
+ ```
@@ -0,0 +1,72 @@
1
+ # `ixfx-narrowed-text`
2
+
3
+ A filtered text selection component with inline search. Shows slotted items filtered by a query string as the user types.
4
+
5
+ ```html
6
+ <ixfx-narrowed-text selection-mode="single" value="beta">
7
+ <span data-value="alpha">Alpha</span>
8
+ <span data-value="beta">Beta</span>
9
+ <span data-value="gamma">Gamma</span>
10
+ </ixfx-narrowed-text>
11
+
12
+ <ixfx-narrowed-text selection-mode="multiple">
13
+ <span data-value="opt1" data-label="Option One">Option One</span>
14
+ <span data-value="opt2" data-label="Option Two">Option Two</span>
15
+ </ixfx-narrowed-text>
16
+ ```
17
+
18
+ ### Properties / attributes
19
+
20
+ | Attribute / Property | Type | Default | Description |
21
+ |-------------------------|-----------------|-------------|------------------------------------------------|
22
+ | `selectionMode` | `'single' \| 'multiple'` | `'single'` | Number of items that can be selected |
23
+ | `requiredSelection` | `boolean` | `false` | Prevent deselecting last item in single mode |
24
+ | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction |
25
+ | `nowrap` | `boolean` | `false` | Prevent items from wrapping |
26
+ | `separator` | `string` | `' '` | Separator for multi-value string |
27
+ | `width` | `string \| undefined` | `undefined` | Container width (e.g. `'200px'`) |
28
+ | `height` | `string \| undefined` | `undefined` | Container height |
29
+ | `preserveFilter` | `boolean` | `false` | Keep filter query after selection |
30
+ | `value` | `string` | `''` | Selected value(s) as string |
31
+
32
+ ### CSS parts
33
+
34
+ | Part | Description |
35
+ |------------|---------------------|
36
+ | `container` | Main container |
37
+ | `items` | Items container |
38
+ | `item` | Individual item |
39
+ | `label` | Item label text |
40
+
41
+ ### Events
42
+
43
+ | Event | Detail | Description |
44
+ |----------|-------------------------------------------------|----------------------------|
45
+ | `change` | `{ item, query, selected }` | Fired when selection changes |
46
+
47
+ ```js
48
+ const nt = document.querySelector('ixfx-narrowed-text');
49
+ nt.addEventListener('change', e => {
50
+ console.log(e.detail.selected); // array of selected items
51
+ });
52
+ ```
53
+
54
+ ### Properties of slotted items
55
+
56
+ Items should have `data-value` attribute. Optional attributes:
57
+ - `data-label`: Display label (defaults to textContent)
58
+ - `data-description`: Optional description text
59
+ - `data-icon-name`: Optional icon name
60
+
61
+ ### CSS custom properties
62
+
63
+ | Property | Default | Description |
64
+ |----------------------------------------------|---------|--------------------------------------|
65
+ | `--item-padding` | `var(--space-xs) var(--space-m)` | Item padding |
66
+ | `--item-border-radius` | `var(--radius-s)` | Item border radius |
67
+ | `--item-icon-spacing` | `var(--space-xs)` | Gap between icon and label |
68
+ | `--item-height` | `28px` | Minimum item container height |
69
+ | `--item-label-font-style-default` | `normal` | Default font style |
70
+ | `--item-label-text-decoration-tickled` | `underline dotted` | Tickled item decoration |
71
+ | `--highlight-background` | `var(--accent)` | Query match highlight bg |
72
+ | `--highlight-color` | `var(--accent-text)` | Query match highlight color |