@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,319 @@
1
+ # Menu Infrastructure
2
+
3
+ A custom menu system built on native HTML popovers with keyboard navigation, tickled focus management, and command routing.
4
+
5
+ ## Components
6
+
7
+ | Component | Description |
8
+ |------------|-------------|
9
+ | `ixfx-menu-bar` | Horizontal container for menu triggers; handles arrow-key navigation between top-level menus |
10
+ | `ixfx-menu-trigger` | Button or slot that opens a menu popover. Modes: `trigger` (button), `dropdown` (slot-only), `context` (right-click) |
11
+ | `ixfx-menu-container` | The popover panel holding menu items; manages keyboard focus and tickled state |
12
+ | `ixfx-menu-item` | Basic menu item that dispatches a `command` event |
13
+ | `ixfx-menu-check-item` | Toggle checkbox item; dispatches `command` with `checked` state |
14
+ | `ixfx-menu-radio-item` | Radio button item for use inside `ixfx-menu-radio-group` |
15
+ | `ixfx-menu-radio-group` | Groups radio items; dispatches `radio-change` when selection changes |
16
+ | `ixfx-menu-sub-trigger` | Menu item that reveals a nested submenu on hover or ArrowRight |
17
+ | `ixfx-menu-separator` | Visual divider between menu sections |
18
+ | `ixfx-menu-header` | Section label header |
19
+
20
+ ## Architecture
21
+
22
+ Menus are built on the native HTML `<popover>` API. `ixfx-menu-trigger` owns a `<popover>` element and uses `showPopover()`/`hidePopover()` to open/close it. `ixfx-menu-container` is slotted into the popover and handles:
23
+
24
+ - **Tickled focus management** — tracks which item should be focused based on keyboard vs mouse input
25
+ - **Keyboard navigation** — Arrow keys, Home/End, Enter/Space to activate
26
+ - **Icon section alignment** — detects whether any item in a section has an icon and pads all items consistently
27
+
28
+ ## Usage
29
+
30
+ ### Declarative Style
31
+
32
+ Static menu markup in HTML:
33
+
34
+ ```html
35
+ <ixfx-menu-bar>
36
+ <ixfx-menu-trigger label="File" @menu-command=${handleCommand}>
37
+ <ixfx-menu-container slot="menu">
38
+ <ixfx-menu-item command="new" label="New"></ixfx-menu-item>
39
+ <ixfx-menu-item command="open" label="Open"></ixfx-menu-item>
40
+ <ixfx-menu-separator></ixfx-menu-separator>
41
+ <ixfx-menu-item command="exit" label="Exit"></ixfx-menu-item>
42
+ </ixfx-menu-container>
43
+ </ixfx-menu-trigger>
44
+
45
+ <ixfx-menu-trigger label="Edit" @menu-command=${handleCommand}>
46
+ <ixfx-menu-container slot="menu">
47
+ <ixfx-menu-check-item command="bold" label="Bold" checked></ixfx-menu-check-item>
48
+ <ixfx-menu-check-item command="italic" label="Italic"></ixfx-menu-check-item>
49
+ </ixfx-menu-container>
50
+ </ixfx-menu-trigger>
51
+ </ixfx-menu-bar>
52
+ ```
53
+
54
+ ### Imperative Style
55
+
56
+ Building menus programmatically via Lit templates:
57
+
58
+ ```ts
59
+ import { html } from 'lit';
60
+ import { MenuItem } from './menu/menu-item.js';
61
+ import { MenuContainer } from './menu/menu-container.js';
62
+ import { MenuTrigger } from './menu/menu-trigger.js';
63
+ import { MenuSeparator } from './menu/menu-separator.js';
64
+ import { MenuCheckItem } from './menu/menu-check-item.js';
65
+
66
+ function buildEditMenu(onCommand: (cmd: string) => void) {
67
+ return html`
68
+ <ixfx-menu-container slot="menu">
69
+ <ixfx-menu-check-item
70
+ command="bold"
71
+ label="Bold"
72
+ @command=${(e: CustomEvent) => onCommand(e.detail.command)}
73
+ ></ixfx-menu-check-item>
74
+ <ixfx-menu-check-item
75
+ command="italic"
76
+ label="Italic"
77
+ @command=${(e: CustomEvent) => onCommand(e.detail.command)}
78
+ ></ixfx-menu-check-item>
79
+ </ixfx-menu-container>
80
+ `;
81
+ }
82
+
83
+ const trigger = document.createElement('ixfx-menu-trigger');
84
+ trigger.label = 'Edit';
85
+ trigger.addEventListener('menu-command', (e) => console.log(e.detail));
86
+ trigger.appendChild(buildEditMenu(myHandler));
87
+ ```
88
+
89
+ ### Building Menus from TreeNode Structure
90
+
91
+ Use `menuFromTree()` or `menuFromTreeToElements()` to construct menus from a `TreeNode[]` array:
92
+
93
+ ```ts
94
+ import { menuFromTree, menuFromTreeToElements } from './menu/menu-from-tree.js';
95
+ import type { TreeNode } from './tree/types.js';
96
+
97
+ const nodes: TreeNode[] = [
98
+ { item: { key: 'new', label: 'New' } },
99
+ { item: { key: 'open', label: 'Open...' } },
100
+ { item: { key: 'sep1', label: '-' } }, // becomes separator
101
+ { item: { key: 'sep2', label: '# File' } }, // becomes header "File"
102
+ {
103
+ item: { key: 'submenu', label: 'Submenu' },
104
+ children: [
105
+ { item: { key: 'action1', label: 'Action 1' } },
106
+ { item: { key: 'action2', label: 'Action 2' } },
107
+ ],
108
+ },
109
+ { item: { key: 'quit', label: 'Quit' } },
110
+ ];
111
+ ```
112
+
113
+ **Rules:**
114
+ - `label === "-"` → separator
115
+ - `label.startsWith("# ")` → header (prefix stripped)
116
+ - Node has `children` → submenu trigger (no command)
117
+ - Leaf node → menu item with `command = item.key`
118
+
119
+ **In Lit templates** (declarative):
120
+ ```ts
121
+ // Inside a Lit component's render()
122
+ return html`
123
+ <ixfx-menu-trigger label="File">
124
+ <ixfx-menu-container slot="menu">
125
+ ${menuFromTree(nodes, { onCommand: this.handleCommand, registry: this.registry })}
126
+ </ixfx-menu-container>
127
+ </ixfx-menu-trigger>
128
+ `;
129
+ ```
130
+
131
+ **Imperatively** (using DOM):
132
+ ```ts
133
+ const elements = menuFromTreeToElements(nodes, { onCommand: handleCommand });
134
+ for (const el of elements) {
135
+ container.appendChild(el);
136
+ }
137
+ ```
138
+
139
+ ### Programmatic Open/Close
140
+
141
+ ```ts
142
+ const trigger = document.querySelector('ixfx-menu-trigger');
143
+
144
+ // Open
145
+ trigger.openMenu();
146
+
147
+ // Close
148
+ trigger.closeMenu();
149
+
150
+ // Check state
151
+ console.log(trigger.isOpen);
152
+ ```
153
+
154
+ ### Submenus
155
+
156
+ Use `ixfx-menu-sub-trigger` with a nested `ixfx-menu-container` in its `submenu` slot:
157
+
158
+ ```html
159
+ <ixfx-menu-item command="view" label="View">
160
+ <ixfx-menu-container slot="submenu">
161
+ <ixfx-menu-item command="zoom-in" label="Zoom In"></ixfx-menu-item>
162
+ <ixfx-menu-item command="zoom-out" label="Zoom Out"></ixfx-menu-item>
163
+ </ixfx-menu-container>
164
+ </ixfx-menu-item>
165
+ ```
166
+
167
+ ### Radio Groups
168
+
169
+ ```html
170
+ <ixfx-menu-radio-group name="alignment" value="left" @radio-change=${handleAlign}>
171
+ <ixfx-menu-radio-item value="left" label="Left"></ixfx-menu-radio-item>
172
+ <ixfx-menu-radio-item value="center" label="Center"></ixfx-menu-radio-item>
173
+ <ixfx-menu-radio-item value="right" label="Right"></ixfx-menu-radio-item>
174
+ </ixfx-menu-radio-group>
175
+ ```
176
+
177
+ ### Context Menu Mode
178
+
179
+ Set `mode="context"` on the trigger. The menu appears at the cursor position on right-click:
180
+
181
+ ```html
182
+ <ixfx-menu-trigger mode="context" @menu-command=${handleContextMenu}>
183
+ <ixfx-menu-container slot="menu">
184
+ <ixfx-menu-item command="cut" label="Cut"></ixfx-menu-item>
185
+ <ixfx-menu-item command="copy" label="Copy"></ixfx-menu-item>
186
+ <ixfx-menu-item command="paste" label="Paste"></ixfx-menu-item>
187
+ </ixfx-menu-container>
188
+ </ixfx-menu-trigger>
189
+ ```
190
+
191
+ ### Command Registry Integration
192
+
193
+ Pass a `CommandRegistry` to the trigger and/or container. Items resolve their label and icon from the registry if not explicitly set:
194
+
195
+ ```ts
196
+ import { CommandRegistry } from '@clinth/ui-commands';
197
+
198
+ const registry = new CommandRegistry();
199
+ registry.register('bold', { label: 'Bold', icon: 'bold' });
200
+ registry.register('italic', { label: 'Italic', icon: 'italic' });
201
+
202
+ trigger.registry = registry;
203
+ container.registry = registry;
204
+ ```
205
+
206
+ ```html
207
+ <!-- label and icon resolved from registry -->
208
+ <ixfx-menu-item command="bold"></ixfx-menu-item>
209
+ <ixfx-menu-item command="italic"></ixfx-item>
210
+ ```
211
+
212
+ ### Keyboard Shortcuts
213
+
214
+ Pass a `KeyboardManager` to display shortcuts next to menu items:
215
+
216
+ ```ts
217
+ import { KeyboardManager } from '@clinth/ui-commands';
218
+
219
+ keyboardManager.bind('bold', { key: 'b', modifiers: ['ctrl'] });
220
+ keyboardManager.bind('italic', { key: 'i', modifiers: ['ctrl'] });
221
+
222
+ trigger.keyboardManager = keyboardManager;
223
+ ```
224
+
225
+ ## Styling
226
+
227
+ ### CSS Custom Properties
228
+
229
+ | Variable | Default | Description |
230
+ |----------|---------|-------------|
231
+ | `--_item-hover-bg` | `var(--surface-6)` | Background on mouse hover |
232
+ | `--_item-focused-bg` | `var(--surface-6)` | Background when keyboard-focused (tickled) |
233
+ | `--_trigger-hover-bg` | `var(--surface-4)` | Trigger button hover background |
234
+ | `--_trigger-hover-opacity` | `1` | Trigger button hover opacity |
235
+
236
+ The popup surface uses these from `popupSurfaceStyles`:
237
+
238
+ | Variable | Description |
239
+ |----------|-------------|
240
+ | `--surface-5` | Menu background |
241
+ | `--surface-6` | Menu border |
242
+ | `--surface-5-text` | Menu text color |
243
+ | `--shadow-m` | Box shadow |
244
+
245
+ ### Custom Item Background Colors
246
+
247
+ ```css
248
+ /* Apply to ixfx-menu-container */
249
+ ixfx-menu-container {
250
+ --_item-hover-bg: #ffdddd;
251
+ --_item-focused-bg: #ffcccc;
252
+ }
253
+ ```
254
+
255
+ ### Custom Trigger Styling
256
+
257
+ ```css
258
+ ixfx-menu-trigger {
259
+ --_trigger-hover-bg: #eeeeee;
260
+ --_trigger-hover-opacity: 0.8;
261
+ }
262
+ ```
263
+
264
+ ### Per-Item Styling
265
+
266
+ Style menu items from the outside by targeting their tag name or by attaching a class attribute and selecting within:
267
+
268
+ ```css
269
+ ixfx-menu-item[command="delete"] .item {
270
+ color: red;
271
+ }
272
+
273
+ /* Target a specific menu via a wrapper */
274
+ .file-menu ixfx-menu-item .item {
275
+ font-weight: bold;
276
+ }
277
+ ```
278
+
279
+ ## Events
280
+
281
+ | Event | Detail | Description |
282
+ |-------|--------|-------------|
283
+ | `command` | `{ command, args, checked? }` | Fired by `ixfx-menu-item`, `ixfx-menu-check-item` |
284
+ | `radio-select` | `{ value }` | Fired by `ixfx-menu-radio-item` |
285
+ | `radio-change` | `{ name, value }` | Fired by `ixfx-menu-radio-group` when selection changes |
286
+ | `menu-command` | `{ command, value?, checked? }` | Bubbles up from `ixfx-menu-trigger` |
287
+
288
+ ## `ixfx-menu-command-controller`
289
+
290
+ A helper class that listens for `command` events and invokes a `CommandRegistry`:
291
+
292
+ ```ts
293
+ import { MenuCommandController } from './menu/menu-command-controller.js';
294
+
295
+ const ctrl = new MenuCommandController({ registry, target: document });
296
+ ctrl.start();
297
+
298
+ // Now any menu item with a matching command will invoke the registry
299
+ ```
300
+
301
+ ## Mode Reference
302
+
303
+ `ixfx-menu-trigger` supports three `mode` values:
304
+
305
+ | Mode | Trigger Behavior |
306
+ |------|-----------------|
307
+ | `trigger` | Shows a button labeled with the `label` property |
308
+ | `dropdown` | No button shown; expects a custom trigger in the `trigger` slot |
309
+ | `context` | Opens at cursor position on right-click |
310
+
311
+ ## Placement
312
+
313
+ The `placement` attribute controls popover anchoring:
314
+
315
+ | Value | Position |
316
+ |-------|----------|
317
+ | `bottom-start` | Below trigger, left edge aligned (default) |
318
+ | `bottom-end` | Below trigger, right edge aligned |
319
+ | `right-start` | To the right of trigger, top edge aligned |