@ixfx/components 0.7.1 → 0.7.3

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 (109) hide show
  1. package/bundle/index.d.ts +222 -18
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +152 -9
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.d.ts +1 -1
  6. package/dist/{ac-token--_vCHXWw.d.ts → ac-token-TCLAZeVg.d.ts} +2 -2
  7. package/dist/{ac-token--_vCHXWw.d.ts.map → ac-token-TCLAZeVg.d.ts.map} +1 -1
  8. package/dist/ac-token.d.ts +1 -1
  9. package/dist/button.d.ts +1 -1
  10. package/dist/button.d.ts.map +1 -1
  11. package/dist/colour-picker.d.ts +1 -1
  12. package/dist/crumbs.d.ts +1 -1
  13. package/dist/{data-grid-Bh4YrQGK.d.ts → data-grid-Db4QpnII.d.ts} +2 -2
  14. package/dist/{data-grid-Bh4YrQGK.d.ts.map → data-grid-Db4QpnII.d.ts.map} +1 -1
  15. package/dist/data-grid.d.ts +1 -1
  16. package/dist/data-grid.js +1 -1
  17. package/dist/{grouped-item-lister-DnWB-eFT.d.ts → grouped-item-lister-BWlC9S4e.d.ts} +2 -2
  18. package/dist/{grouped-item-lister-DnWB-eFT.d.ts.map → grouped-item-lister-BWlC9S4e.d.ts.map} +1 -1
  19. package/dist/grouped-item-lister.d.ts +1 -1
  20. package/dist/{icon-CDGprSdE.d.ts → icon-DR_3rSyZ.d.ts} +2 -2
  21. package/dist/icon-DR_3rSyZ.d.ts.map +1 -0
  22. package/dist/icons.d.ts +2 -2
  23. package/dist/{index-tq6f8007.d.ts → index-BOUaDIW-.d.ts} +2 -2
  24. package/dist/{index-tq6f8007.d.ts.map → index-BOUaDIW-.d.ts.map} +1 -1
  25. package/dist/{index-CoqtMSd9.d.ts → index-BUB5SICW.d.ts} +2 -2
  26. package/dist/{index-CoqtMSd9.d.ts.map → index-BUB5SICW.d.ts.map} +1 -1
  27. package/dist/{index-9AhaHIT2.d.ts → index-C59-JEPl.d.ts} +3 -3
  28. package/dist/{index-9AhaHIT2.d.ts.map → index-C59-JEPl.d.ts.map} +1 -1
  29. package/dist/{index-CG2vIgPv.d.ts → index-CJ5fshD3.d.ts} +5 -5
  30. package/dist/{index-CG2vIgPv.d.ts.map → index-CJ5fshD3.d.ts.map} +1 -1
  31. package/dist/{index-CS9yM8L-.d.ts → index-DsjpDksO.d.ts} +2 -2
  32. package/dist/{index-CS9yM8L-.d.ts.map → index-DsjpDksO.d.ts.map} +1 -1
  33. package/dist/index.d.ts +219 -16
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +150 -10
  36. package/dist/index.js.map +1 -1
  37. package/dist/{labelled-input-base-DiTme1JO.d.ts → labelled-input-base-Dp_9KP0G.d.ts} +2 -2
  38. package/dist/{labelled-input-base-DiTme1JO.d.ts.map → labelled-input-base-Dp_9KP0G.d.ts.map} +1 -1
  39. package/dist/labelled-radial-input.d.ts +3 -3
  40. package/dist/labelled-radial-input.d.ts.map +1 -1
  41. package/dist/labelled-radial-input.js +1 -1
  42. package/dist/labelled-range-input.d.ts +1 -1
  43. package/dist/{labelled-slider-input-DzigjMwP.js → labelled-slider-input-BInB5GKL.js} +3 -3
  44. package/dist/{labelled-slider-input-DzigjMwP.js.map → labelled-slider-input-BInB5GKL.js.map} +1 -1
  45. package/dist/labelled-slider-input.d.ts +1 -1
  46. package/dist/labelled-slider-input.js +1 -1
  47. package/dist/{menu-CXDFJxNm.js → menu-DMzjkVnK.js} +5 -5
  48. package/dist/menu-DMzjkVnK.js.map +1 -0
  49. package/dist/{menu-item-CZi9vy5O.d.ts → menu-item-ZmkXNjek.d.ts} +2 -2
  50. package/dist/{menu-item-CZi9vy5O.d.ts.map → menu-item-ZmkXNjek.d.ts.map} +1 -1
  51. package/dist/menu.d.ts +2 -2
  52. package/dist/menu.js +1 -1
  53. package/dist/miller.d.ts +1 -1
  54. package/dist/panel.d.ts +1 -1
  55. package/dist/polar-pad.d.ts +2 -2
  56. package/dist/polar-pad.js +1 -1
  57. package/dist/{radial-input-Dp5qz0ly.js → radial-input-BfA3GO0H.js} +2 -2
  58. package/dist/{radial-input-Dp5qz0ly.js.map → radial-input-BfA3GO0H.js.map} +1 -1
  59. package/dist/{radial-input-DXMN55Nz.d.ts → radial-input-DU6t2hYO.d.ts} +3 -3
  60. package/dist/{radial-input-DXMN55Nz.d.ts.map → radial-input-DU6t2hYO.d.ts.map} +1 -1
  61. package/dist/radial-input.d.ts +1 -1
  62. package/dist/radial-input.js +1 -1
  63. package/dist/range-input.d.ts +2 -2
  64. package/dist/range-input.js +1 -1
  65. package/dist/range.d.ts +2 -2
  66. package/dist/range.js +1 -1
  67. package/dist/select-horiz.d.ts +2 -2
  68. package/dist/select-horiz.js +1 -1
  69. package/dist/{slider-input-De4eoT11.js → slider-input-Bl1hFE89.js} +2 -2
  70. package/dist/{slider-input-De4eoT11.js.map → slider-input-Bl1hFE89.js.map} +1 -1
  71. package/dist/{slider-input-BfRUW2ZB.d.ts → slider-input-BxURAAmD.d.ts} +3 -3
  72. package/dist/{slider-input-BfRUW2ZB.d.ts.map → slider-input-BxURAAmD.d.ts.map} +1 -1
  73. package/dist/slider-input.d.ts +1 -1
  74. package/dist/slider-input.js +1 -1
  75. package/dist/{split-layout-JK3xWVX2.d.ts → split-layout-C5SYbDrq.d.ts} +2 -2
  76. package/dist/{split-layout-JK3xWVX2.d.ts.map → split-layout-C5SYbDrq.d.ts.map} +1 -1
  77. package/dist/split-layout.d.ts +1 -1
  78. package/dist/swipe.d.ts +2 -2
  79. package/dist/swipe.js +1 -1
  80. package/dist/{tab-list-CGyPt07Z.d.ts → tab-list-DbMIL520.d.ts} +2 -2
  81. package/dist/{tab-list-CGyPt07Z.d.ts.map → tab-list-DbMIL520.d.ts.map} +1 -1
  82. package/dist/tabs.d.ts +1 -1
  83. package/dist/{tickled-controller-CRzdIEjJ.d.ts → tickled-controller-Ds1DMyG7.d.ts} +2 -2
  84. package/dist/{tickled-controller-CRzdIEjJ.d.ts.map → tickled-controller-Ds1DMyG7.d.ts.map} +1 -1
  85. package/dist/{titlebar-Bj5pfdw_.d.ts → titlebar-B23MrIbI.d.ts} +2 -2
  86. package/dist/{titlebar-Bj5pfdw_.d.ts.map → titlebar-B23MrIbI.d.ts.map} +1 -1
  87. package/dist/titlebar.d.ts +1 -1
  88. package/dist/tree.d.ts +1 -1
  89. package/dist/{vertical-list-ClnqTkF3.js → vertical-list-B4w_ksJk.js} +2 -2
  90. package/dist/{vertical-list-ClnqTkF3.js.map → vertical-list-B4w_ksJk.js.map} +1 -1
  91. package/dist/vertical-list.js +1 -1
  92. package/dist/wheel-nudge-By5s_ioH.d.ts +31 -0
  93. package/dist/wheel-nudge-By5s_ioH.d.ts.map +1 -0
  94. package/dist/{wheel-nudge-BlSCe_hD.js → wheel-nudge-D5rmht3v.js} +6 -1
  95. package/dist/wheel-nudge-D5rmht3v.js.map +1 -0
  96. package/dist/xy-pad.d.ts +2 -2
  97. package/dist/xy-pad.js +1 -1
  98. package/docs-user/README.md +1 -0
  99. package/docs-user/detail-list.md +42 -15
  100. package/docs-user/index.json +6 -2
  101. package/docs-user/infra-tickled.md +163 -0
  102. package/docs-user/llms.txt +1 -0
  103. package/llms.txt +1 -0
  104. package/package.json +1 -1
  105. package/dist/icon-CDGprSdE.d.ts.map +0 -1
  106. package/dist/menu-CXDFJxNm.js.map +0 -1
  107. package/dist/wheel-nudge-BlSCe_hD.js.map +0 -1
  108. package/dist/wheel-nudge-DGrNzWuE.d.ts +0 -29
  109. package/dist/wheel-nudge-DGrNzWuE.d.ts.map +0 -1
@@ -8,20 +8,35 @@ Selection semantics match `ixfx-vertical-list` — same modes, same `list-*` eve
8
8
 
9
9
  ## Contents
10
10
 
11
- 1. [Quick start](#quick-start)
12
- 2. [Item types](#item-types)
13
- 3. [Properties](#properties)
14
- 4. [Selection](#selection)
15
- 5. [Interaction modes](#interaction-modes)
16
- 6. [Custom checkbox](#custom-checkbox)
17
- 7. [Events](#events)
18
- 8. [Keyboard navigation](#keyboard-navigation)
19
- 9. [Incremental search](#incremental-search)
20
- 10. [Filter predicate](#filter-predicate)
21
- 11. [Sorting](#sorting)
22
- 12. [Asynchronous data](#asynchronous-data)
23
- 13. [CSS variables](#css-variables)
24
- 14. [CSS parts](#css-parts)
11
+ - [`ixfx-detail-list`](#ixfx-detail-list)
12
+ - [Contents](#contents)
13
+ - [Quick start](#quick-start)
14
+ - [Declarative HTML items](#declarative-html-items)
15
+ - [Programmatic items](#programmatic-items)
16
+ - [Item types](#item-types)
17
+ - [Plain `<li>` elements](#plain-li-elements)
18
+ - [Object items](#object-items)
19
+ - [Properties](#properties)
20
+ - [Selection](#selection)
21
+ - [Getters](#getters)
22
+ - [Methods](#methods)
23
+ - [Interaction modes](#interaction-modes)
24
+ - [Custom checkbox](#custom-checkbox)
25
+ - [Events](#events)
26
+ - [Keyboard navigation](#keyboard-navigation)
27
+ - [Wrapping](#wrapping)
28
+ - [Incremental search](#incremental-search)
29
+ - [Highlight styling](#highlight-styling)
30
+ - [Filter predicate](#filter-predicate)
31
+ - [Sorting](#sorting)
32
+ - [Simple sort](#simple-sort)
33
+ - [Sort by property](#sort-by-property)
34
+ - [Custom comparator](#custom-comparator)
35
+ - [Asynchronous data](#asynchronous-data)
36
+ - [Promise provider](#promise-provider)
37
+ - [Streaming provider](#streaming-provider)
38
+ - [CSS variables](#css-variables)
39
+ - [CSS parts](#css-parts)
25
40
 
26
41
  ---
27
42
 
@@ -373,13 +388,25 @@ Streaming providers extend columns to the right as batches arrive.
373
388
  | `--detail-list-column-width` | `220px` | Width of each column |
374
389
  | `--detail-list-column-gap` | `8px` | Gap between columns |
375
390
  | `--detail-list-item-height` | `28px` | Height of each item |
376
- | `--detail-list-item-padding` | `0 var(--space-m)` | Item padding |
391
+ | `--detail-list-item-padding` | `0 var(--space-xs)` | Item padding |
377
392
  | `--detail-list-scrollbar-width` | `auto` | Scrollbar width |
378
393
  | `--detail-list-scrollbar-gutter` | `auto` | Scrollbar gutter |
379
394
  | `--detail-list-scrollbar-color` | `auto` | Scrollbar color |
380
395
 
381
396
  Plus all `--item-bg-*`, `--item-text-*` variables from the theme for selection/tickle states.
382
397
 
398
+ To explicitly size columns to the widest visible item, call `fitColumnWidth()`. It
399
+ returns the applied width in pixels and does not run automatically:
400
+
401
+ ```ts
402
+ const width = await list.fitColumnWidth();
403
+ console.log(`Column width: ${width}px`);
404
+ ```
405
+
406
+ The resulting pixel value is set on the element itself and overrides an inherited
407
+ `--detail-list-column-width` value. Lists otherwise continue to use the configured
408
+ CSS variable or the default `220px` width.
409
+
383
410
  ---
384
411
 
385
412
  ## CSS parts
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
- "version": "0.7.1",
4
- "generated": "2026-09-06T10:36:57.248Z",
3
+ "version": "0.7.3",
4
+ "generated": "2026-09-06T18:27:10.873Z",
5
5
  "components": [
6
6
  {
7
7
  "name": "ac-text",
@@ -235,6 +235,10 @@
235
235
  }
236
236
  ],
237
237
  "overviews": [
238
+ {
239
+ "name": "infra-tickled.md",
240
+ "path": "docs-user/infra-tickled.md"
241
+ },
238
242
  {
239
243
  "name": "user-catalog.md",
240
244
  "path": "docs-user/user-catalog.md"
@@ -0,0 +1,163 @@
1
+ # Tickled items
2
+
3
+ An item is **tickled** when it is under active attention — pointer hovering over it, or keyboard cursor on it. Only one item is tickled at a time — a single logical cursor regardless of input mechanism.
4
+
5
+ **Tickled state is controlled via the `.tickled` CSS class** (applied by `TickledController`). The `tickledItemStyles` provides base styling that all tickled components should use.
6
+
7
+ ---
8
+
9
+ ## TickledController
10
+
11
+ `TickledController` is a Lit `ReactiveController` from `src/interaction/index.js` that manages this state for you.
12
+
13
+ ### Direct-render setup (items in your template)
14
+
15
+ ```ts
16
+ import { TickledController, tickledStyles, tickledItemStyles } from '../interaction/index.js';
17
+
18
+ class MyList extends LitElement {
19
+ static override styles = [
20
+ tickledStyles,
21
+ tickledItemStyles,
22
+ css`
23
+ .item.selected { background: var(--item-bg-selected); }
24
+ `
25
+ ];
26
+
27
+ private tickled = new TickledController(this, {
28
+ getItems: () => this.items.map((_, i) => ({ id: i })),
29
+ });
30
+
31
+ protected override render() {
32
+ return html`
33
+ <div
34
+ data-input-mode=${this.tickled.inputMode}
35
+ @pointerleave=${() => this.tickled.clearTickled()}
36
+ @keydown=${this.#handleKeyDown}
37
+ >
38
+ ${this.items.map((item, i) => html`
39
+ <div
40
+ class="item ${this.tickled.isTickled(i) ? 'tickled' : ''}"
41
+ @pointerenter=${() => this.tickled.handlePointerEnter(i)}
42
+ >${item}</div>
43
+ `)}
44
+ </div>
45
+ `;
46
+ }
47
+
48
+ #handleKeyDown(e: KeyboardEvent) {
49
+ if (e.key === 'ArrowDown') { e.preventDefault(); this.tickled.handleKeyboardNavigation('next'); }
50
+ else if (e.key === 'ArrowUp') { e.preventDefault(); this.tickled.handleKeyboardNavigation('previous'); }
51
+ else if (e.key === 'Home') { e.preventDefault(); this.tickled.handleKeyboardNavigation('first'); }
52
+ else if (e.key === 'End') { e.preventDefault(); this.tickled.handleKeyboardNavigation('last'); }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### Slotted-content setup
58
+
59
+ Lit's reactive rendering doesn't touch slotted elements, so you must sync DOM classes manually via `onStateChange`.
60
+
61
+ ```ts
62
+ private tickled = new TickledController<number>(this, {
63
+ getItems: () => this._slottedItems().map((_, i) => ({ id: i })),
64
+ onStateChange: () => this._syncTickled(),
65
+ });
66
+
67
+ // Must run in willUpdate so :host gets data-input-mode before paint
68
+ override willUpdate(changed: Map<PropertyKey, unknown>) {
69
+ super.willUpdate(changed);
70
+ this.setAttribute('data-input-mode', this.tickled.inputMode);
71
+ }
72
+
73
+ private _slottedItems(): HTMLElement[] {
74
+ const slot = this.shadowRoot?.querySelector('slot');
75
+ return Array.from(slot?.assignedElements() ?? []) as HTMLElement[];
76
+ }
77
+
78
+ private _syncTickled() {
79
+ this._slottedItems().forEach((item, i) => {
80
+ const isTickled = this.tickled.isTickled(i);
81
+ if (isTickled && this.tickled.inputMode === 'keyboard') {
82
+ item.classList.add('tickled');
83
+ } else {
84
+ item.classList.remove('tickled');
85
+ }
86
+ item.tabIndex = isTickled ? 0 : -1;
87
+ });
88
+ }
89
+ ```
90
+
91
+ For slotted content, set `data-input-mode` on `:host` (not on an inner div) so `tickledStyles` CSS variables cascade through the shadow boundary to slotted elements.
92
+
93
+ ---
94
+
95
+ ## CSS variable contract
96
+
97
+ All `--item-*` state variables are defined in `src/style.css` (the theme). `tickledItemStyles` applies them to `.item` elements — it no longer sets defaults of its own for the colour/state variables.
98
+
99
+ See [infra-theming.md](./infra-theming.md#item-states) for the full variable listing and their theme values.
100
+
101
+ The layout variables (`--item-padding`, `--item-border-radius`, `--item-icon-spacing`, `--item-bg`) are also defined in `style.css` with defaults derived from the spacing/radius tokens.
102
+
103
+ ## Base item styles
104
+
105
+ `tickledItemStyles` provides these base styles:
106
+
107
+ ```css
108
+ .item {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ gap: var(--item-icon-spacing);
112
+ padding: var(--item-padding);
113
+ background: var(--item-bg);
114
+ border: var(--item-border);
115
+ border-radius: var(--item-border-radius);
116
+ transition: background var(--transition), border-color var(--transition);
117
+ }
118
+
119
+ .item.tickled {
120
+ background: var(--item-bg-tickled);
121
+ border-color: var(--item-border-tickled);
122
+ color: var(--item-text-tickled);
123
+ }
124
+
125
+ .item.selected {
126
+ background: var(--item-bg-selected);
127
+ border-color: var(--item-border-selected);
128
+ color: var(--item-text-selected);
129
+ }
130
+
131
+ .item.selected.tickled {
132
+ background: var(--item-bg-selected-tickled);
133
+ outline: 2px dashed var(--surface-3);
134
+ outline-offset: -2px;
135
+ }
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Controller API
141
+
142
+ **Constructor:** `new TickledController<T>(host, config)`
143
+
144
+ Config:
145
+ - `getItems: () => readonly { id: T; enabled?: boolean }[]` — called on each render
146
+ - `onStateChange?: () => void` — fired after every state mutation (required for slotted content)
147
+ - `onTickle?: (id: T | undefined) => void` — called only when the tickled id *changes* (useful for opening submenus)
148
+ - `initialInputMode?: 'keyboard' | 'mouse'`
149
+
150
+ Properties: `inputMode`, `tickledId`
151
+
152
+ Methods: `isTickled(id)`, `handlePointerEnter(id)`, `handleKeyboardNavigation('next'|'previous'|'first'|'last'|T)`, `clearTickled()`, `setTickled(id)`, `setInputMode(mode)`
153
+
154
+ ---
155
+
156
+ ## Checklist
157
+
158
+ - [ ] `tickledStyles` and `tickledItemStyles` included in `static styles`
159
+ - [ ] Components use `tickledItemStyles` base styles, override only for component-specific needs
160
+ - [ ] `data-input-mode` set on the correct element (container div for direct-render; `:host` via `willUpdate` for slotted)
161
+ - [ ] `@pointerenter` on each item, `@pointerleave` on container
162
+ - [ ] Keyboard navigation calls `handleKeyboardNavigation`
163
+ - [ ] Slotted components: `onStateChange` + `_syncTickled()` implemented; `.tickled` class only added in keyboard mode
@@ -59,5 +59,6 @@ import '@ixfx/components/data-grid';
59
59
  - [xy-pad](docs-user/xy-pad.md): A 2D touch-friendly XY pad for controlling two parameters simultaneously. Each axis supports independent linear/logarithmic scaling.
60
60
 
61
61
  ## Overviews
62
+ - [infra-tickled.md](docs-user/infra-tickled.md)
62
63
  - [user-catalog.md](docs-user/user-catalog.md)
63
64
  - [user-theming.md](docs-user/user-theming.md)
package/llms.txt CHANGED
@@ -59,5 +59,6 @@ import '@ixfx/components/data-grid';
59
59
  - [xy-pad](docs-user/xy-pad.md): A 2D touch-friendly XY pad for controlling two parameters simultaneously. Each axis supports independent linear/logarithmic scaling.
60
60
 
61
61
  ## Overviews
62
+ - [infra-tickled.md](docs-user/infra-tickled.md)
62
63
  - [user-catalog.md](docs-user/user-catalog.md)
63
64
  - [user-theming.md](docs-user/user-theming.md)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
3
  "type": "module",
4
- "version": "0.7.1",
4
+ "version": "0.7.3",
5
5
  "packageManager": "pnpm@10.0.0",
6
6
  "description": "Web components for ixfx — Lit-based UI toolkit",
7
7
  "author": "Clint Heyer",
@@ -1 +0,0 @@
1
- {"version":3,"file":"icon-CDGprSdE.d.ts","names":[],"sources":["../src/icons/icon.ts"],"mappings":";;cAUa,wBAAwB;;EACvB;EACgB;EAChB;EAC0C;EACU;EAevD;EAMA;YAMU,iBAAM,wBAAA;YA4BN,QAAQ,SAAS;SAWpB,sBAAM;;QAiDhB;YACI;IACR,aAAa"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"menu-CXDFJxNm.js","names":["customElement","customElement","customElement","customElement","customElement","customElement","customElement"],"sources":["../src/menu/menu-bar.ts","../src/menu/menu-check-item.ts","../src/menu/menu-command-controller.ts","../src/menu/menu-sub-trigger.ts","../src/menu/menu-separator.ts","../src/menu/menu-header.ts","../src/menu/menu-from-tree.ts","../src/menu/menu-radio-item.ts","../src/menu/menu-radio-group.ts","../src/menu/index.ts"],"sourcesContent":["import type { MenuTrigger } from './menu-trigger.js';\nimport { css, html, LitElement } from 'lit';\nimport { safeCustomElement as customElement } from '../util/custom-elements.js';\nimport './menu-trigger.js';\n\n@customElement(`ixfx-menu-bar`)\nexport class MenuBar extends LitElement {\n private _openTrigger: MenuTrigger | null = null;\n private _inputMode: `keyboard` | `mouse` = `mouse`;\n\n connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener(`menu-trigger-open`, this._handleTriggerOpen as EventListener);\n this.addEventListener(`menu-trigger-close`, this._handleTriggerClose as EventListener);\n this.addEventListener(`focusin`, this._handleFocusIn as EventListener);\n this.addEventListener(`keydown`, this._handleKeydown);\n this.addEventListener(`pointermove`, this._handlePointerMove);\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener(`menu-trigger-open`, this._handleTriggerOpen as EventListener);\n this.removeEventListener(`menu-trigger-close`, this._handleTriggerClose as EventListener);\n this.removeEventListener(`focusin`, this._handleFocusIn as EventListener);\n this.removeEventListener(`keydown`, this._handleKeydown);\n this.removeEventListener(`pointermove`, this._handlePointerMove);\n }\n\n private _setInputMode(mode: `keyboard` | `mouse`): void {\n if (this._inputMode === mode)\n return;\n this._inputMode = mode;\n this.setAttribute(`data-input-mode`, mode);\n }\n\n private _handlePointerMove = (): void => {\n this._setInputMode(`mouse`);\n };\n\n private _getTriggers(): MenuTrigger[] {\n return [...this.children].filter(\n el => el.tagName === `IXFX-MENU-TRIGGER`,\n ) as MenuTrigger[];\n }\n\n private _handleTriggerOpen = (e: Event): void => {\n this._openTrigger = e.target as MenuTrigger;\n };\n\n private _handleTriggerClose = (e: Event): void => {\n if (e.target === this._openTrigger) {\n this._openTrigger = null;\n }\n };\n\n // When focus moves to a trigger button while another menu is open, switch menus.\n // focusin is composed, so e.target is retargeted to the ixfx-menu-trigger host.\n private _handleFocusIn = (e: FocusEvent): void => {\n const triggers = this._getTriggers();\n const trigger = triggers.find(t => t === e.target);\n if (!trigger || !this._openTrigger || trigger === this._openTrigger)\n return;\n this._openTrigger.closeMenu();\n trigger.openMenu();\n };\n\n private _handleKeydown = (e: KeyboardEvent): void => {\n if (e.key !== `ArrowLeft` && e.key !== `ArrowRight`)\n return;\n\n const triggers = this._getTriggers();\n if (triggers.length === 0)\n return;\n\n // When a menu is open, navigate from the open trigger.\n // When no menu is open, navigate from the focused trigger button\n // (e.target is retargeted to ixfx-menu-trigger when focus is on its inner button).\n const current = this._openTrigger ?? triggers.find(t => t === e.target);\n if (!current)\n return;\n\n const idx = triggers.indexOf(current);\n if (idx === -1)\n return;\n\n e.preventDefault();\n e.stopPropagation();\n this._setInputMode(`keyboard`);\n\n const dir = e.key === `ArrowRight` ? 1 : -1;\n const next = triggers[(idx + dir + triggers.length) % triggers.length];\n\n if (this._openTrigger) {\n this._openTrigger.closeMenu();\n next.openMenu();\n } else {\n next.focusTrigger();\n }\n };\n\n static styles = css`\n :host {\n display: flex;\n align-items: center;\n }\n\n /* Keyboard mode: suppress hover highlight on trigger buttons */\n :host([data-input-mode=\"keyboard\"]) {\n --_trigger-hover-bg: transparent;\n --_trigger-hover-opacity: 0.6;\n }\n `;\n\n render() {\n return html`<slot role=\"menubar\"></slot>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ixfx-menu-bar': MenuBar;\n }\n}\n","import type { CommandRegistry, KeyboardManager } from '@clinth/ui-commands';\nimport { css, html, LitElement } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { tickledItemStyles } from '../interaction/index.js';\nimport { themeFallbacks } from '../styles/fallbacks.js';\nimport { safeCustomElement as customElement } from '../util/custom-elements.js';\nimport '../icons/icon.js';\n\nexport type MenuCheckItemCommandDetail = {\n readonly command: string;\n readonly checked: boolean;\n readonly args: string;\n};\n\nexport type MenuCheckItemCommandEvent = CustomEvent<MenuCheckItemCommandDetail>;\n\n@customElement(`ixfx-menu-check-item`)\nexport class MenuCheckItem extends LitElement {\n @property({ type: String }) command = ``;\n @property({ type: String, attribute: `command-args` }) commandArgs = ``;\n @property({ type: String }) label = ``;\n @property({ type: Boolean }) checked = false;\n @property({ type: Boolean }) disabled = false;\n @property({ type: String, attribute: `icon-name` }) iconName = ``;\n @property({ attribute: false }) registry?: CommandRegistry<unknown>;\n @property({ attribute: false }) keyboardManager?: KeyboardManager;\n\n private handleClick(): void {\n if (this.disabled)\n return;\n\n this.checked = !this.checked;\n this.dispatchEvent(new CustomEvent<MenuCheckItemCommandDetail>(`command`, {\n detail: { command: this.command, checked: this.checked, args: this.commandArgs },\n bubbles: true,\n composed: true,\n }));\n }\n\n private _resolvedLabel(): string {\n if (this.label)\n return this.label;\n if (this.registry && this.command) {\n return this.registry.get(this.command)?.label ?? ``;\n }\n return ``;\n }\n\n private _resolvedIcon(): string | null {\n if (this.iconName)\n return this.iconName;\n if (this.registry && this.command) {\n return this.registry.get(this.command)?.icon ?? null;\n }\n return null;\n }\n\n willUpdate(changedProperties: Map<string, unknown>): void {\n super.willUpdate(changedProperties);\n const resolvedIcon = this._resolvedIcon();\n if (resolvedIcon && !this.iconName) {\n this.setAttribute(`data-icon-section`, ``);\n } else if (!resolvedIcon && !this.iconName) {\n this.removeAttribute(`data-icon-section`);\n }\n }\n\n updated(changedProperties: Map<string, unknown>): void {\n super.updated(changedProperties);\n }\n\n private _getShortcut(): string | null {\n if (!this.keyboardManager || !this.command)\n return null;\n const bindings = (this.keyboardManager as any).getBindings?.() ?? [];\n const binding = bindings.find((b: any) => b.commandId === this.command);\n if (!binding)\n return null;\n const mods = Array.from(binding.modifiers ?? [], (m: string) =>\n ({ ctrl: `⌃`, shift: `⇧`, alt: `⌥`, meta: `⌘` })[m] ?? m);\n const key = binding.key?.length === 1 ? binding.key.toUpperCase() : binding.key;\n return [...mods, key].join(``);\n }\n\n static override styles = [themeFallbacks, tickledItemStyles, css`\n :host {\n display: block;\n }\n\n .item {\n display: flex;\n padding: 6px var(--space-m);\n font-size: var(--text-m);\n }\n\n :host(:focus) {\n outline: none;\n }\n\n .item.disabled {\n opacity: var(--disabled-opacity);\n cursor: default;\n }\n\n .item.disabled:hover {\n background: transparent;\n }\n\n .check {\n width: 16px;\n margin-right: var(--space-s);\n font-size: var(--text-m);\n color: var(--accent);\n }\n\n .icon-slot {\n width: 0;\n overflow: hidden;\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n :host([icon-name]) .icon-slot,\n :host([data-icon-section]) .icon-slot {\n width: 1em;\n height: 1em;\n margin-right: var(--space-s);\n }\n\n .label {\n flex: 1;\n }\n\n .shortcut {\n margin-left: var(--space-l);\n color: var(--surface-muted-text);\n font-size: var(--text-s);\n }\n `];\n\n render() {\n const shortcut = this._getShortcut();\n const resolvedIcon = this._resolvedIcon();\n return html`\n <div \n class=\"item ${this.disabled ? `disabled` : ``}\"\n @click=${this.handleClick}\n tabindex=\"${this.disabled ? -1 : 0}\"\n role=\"menuitemcheckbox\"\n aria-checked=\"${this.checked}\"\n >\n <span class=\"check\">${this.checked ? `✓` : ``}</span>\n <span class=\"icon-slot\">${resolvedIcon ? html`<ixfx-icon name=\"${resolvedIcon}\"></ixfx-icon>` : null}</span>\n <span class=\"label\">${this._resolvedLabel()}</span>\n ${shortcut ? html`<span class=\"shortcut\">${shortcut}</span>` : null}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ixfx-menu-check-item': MenuCheckItem;\n }\n}\n","import type { CommandRegistry } from '@clinth/ui-commands';\n\nexport class MenuCommandController {\n private registry: CommandRegistry<unknown>;\n private target: EventTarget;\n private boundHandler: (e: Event) => void;\n\n constructor(options: { registry: CommandRegistry<unknown>; target?: EventTarget }) {\n this.registry = options.registry;\n this.target = options.target ?? document;\n this.boundHandler = this.handleCommand.bind(this);\n }\n\n start(): void {\n this.target.addEventListener(`command`, this.boundHandler);\n }\n\n stop(): void {\n this.target.removeEventListener(`command`, this.boundHandler);\n }\n\n private handleCommand(e: Event): void {\n const target = e.target as any;\n const command = target?.command;\n if (!command)\n return;\n\n const args = target?.commandArgs ?? ``;\n this.registry.invoke(command, { args });\n }\n}\n","import type { CommandRegistry, KeyboardManager } from '@clinth/ui-commands';\nimport { css, html, LitElement, nothing } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\nimport { tickledItemStyles } from '../interaction/index.js';\nimport { themeFallbacks } from '../styles/fallbacks.js';\nimport { safeCustomElement as customElement } from '../util/custom-elements.js';\nimport '../icons/icon.js';\n\n@customElement(`ixfx-menu-sub-trigger`)\nexport class MenuSubTrigger extends LitElement {\n @property({ type: String }) command = ``;\n @property({ type: String, attribute: `command-args` }) commandArgs = ``;\n @property({ type: String }) label = ``;\n @property({ type: Boolean }) disabled = false;\n @property({ type: String, attribute: `icon-name` }) iconName = ``;\n @property({ attribute: false }) registry?: CommandRegistry<unknown>;\n @property({ attribute: false }) keyboardManager?: KeyboardManager;\n\n @state() private _hasSubmenu = false;\n @query(`slot[name=\"submenu\"]`) private _slot!: HTMLSlotElement;\n @query(`.submenu-popover`) private _submenuPopover!: HTMLElement;\n @query(`.item`) private _itemEl!: HTMLElement;\n\n private static _count = 0;\n private _anchorName = `--mst-${MenuSubTrigger._count++}`;\n private _hoverTimeout: number | null = null;\n private _hideTimeout: number | null = null;\n\n connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener(`keydown`, this._handleKeydown);\n }\n\n firstUpdated(): void {\n this.style.setProperty(`--anchor-name`, this._anchorName);\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener(`keydown`, this._handleKeydown);\n if (this._hoverTimeout) {\n clearTimeout(this._hoverTimeout);\n }\n if (this._hideTimeout) {\n clearTimeout(this._hideTimeout);\n }\n }\n\n private _propagateContext(): void {\n const items = this._slot?.assignedElements({ flatten: true }) ?? [];\n for (const el of items) {\n if (`registry` in el)\n (el as any).registry = this.registry;\n if (`keyboardManager` in el)\n (el as any).keyboardManager = this.keyboardManager;\n }\n }\n\n override updated(changedProperties: Map<string, unknown>): void {\n if (changedProperties.has(`registry`) || changedProperties.has(`keyboardManager`)) {\n this._propagateContext();\n }\n }\n\n willUpdate(changedProperties: Map<string, unknown>): void {\n super.willUpdate(changedProperties);\n const resolvedIcon = this._resolvedIcon();\n if (resolvedIcon && !this.iconName) {\n this.setAttribute(`data-icon-section`, ``);\n } else if (!resolvedIcon && !this.iconName) {\n this.removeAttribute(`data-icon-section`);\n }\n }\n\n private _resolvedIcon(): string | null {\n if (this.iconName)\n return this.iconName;\n if (this.registry && this.command) {\n return this.registry.get(this.command)?.icon ?? null;\n }\n return null;\n }\n\n private _handleSlotChange(): void {\n this._hasSubmenu = this._slot?.assignedElements().length > 0;\n this._propagateContext();\n }\n\n private _showSubmenu(): void {\n if (!this._submenuPopover?.popover)\n return;\n try {\n (this._submenuPopover as any).showPopover();\n } catch { /* ignore */ }\n }\n\n private _hideSubmenu(): void {\n if (!this._submenuPopover?.popover)\n return;\n try {\n (this._submenuPopover as any).hidePopover();\n } catch { /* ignore */ }\n }\n\n private _cancelHide(): void {\n if (this._hideTimeout) {\n clearTimeout(this._hideTimeout);\n this._hideTimeout = null;\n }\n }\n\n private _scheduleHide(): void {\n this._cancelHide();\n this._hideTimeout = window.setTimeout(() => {\n this._hideSubmenu();\n this._hideTimeout = null;\n }, 100);\n }\n\n private _handleMouseEnter(): void {\n if (this.disabled || !this._hasSubmenu)\n return;\n this._cancelHide();\n if (this._hoverTimeout) {\n clearTimeout(this._hoverTimeout);\n }\n this._hoverTimeout = window.setTimeout(() => {\n this._showSubmenu();\n }, 150);\n }\n\n private _handleMouseLeave(): void {\n if (this._hoverTimeout) {\n clearTimeout(this._hoverTimeout);\n this._hoverTimeout = null;\n }\n this._scheduleHide();\n }\n\n private _handleSubmenuMouseEnter(): void {\n this._cancelHide();\n }\n\n private _handleSubmenuMouseLeave(): void {\n this._scheduleHide();\n }\n\n private _handleClick(): void {\n if (this.disabled || !this.command)\n return;\n\n this.dispatchEvent(new CustomEvent(`command`, {\n detail: { command: this.command, args: this.commandArgs },\n bubbles: true,\n composed: true,\n }));\n }\n\n private _handleKeydown(e: KeyboardEvent): void {\n if (this.disabled)\n return;\n\n switch (e.key) {\n case `ArrowRight`:\n case `Enter`:\n case ` `:\n e.preventDefault();\n e.stopPropagation();\n if (this._hasSubmenu) {\n this._showSubmenu();\n requestAnimationFrame(() => {\n const container = this._slot?.assignedElements({ flatten: true })\n .find(el => el.tagName === `IXFX-MENU-CONTAINER`) as (HTMLElement & { focusFirst: () => void }) | undefined;\n container?.focusFirst();\n });\n }\n break;\n case `ArrowLeft`:\n e.preventDefault();\n e.stopPropagation();\n this._hideSubmenu();\n this._itemEl?.focus();\n break;\n case `Escape`:\n e.preventDefault();\n e.stopPropagation();\n this._hideSubmenu();\n break;\n }\n }\n\n static override styles = [themeFallbacks, tickledItemStyles, css`\n :host {\n display: block;\n }\n\n .item {\n anchor-name: var(--anchor-name);\n display: flex;\n padding: 6px var(--space-m);\n font-size: var(--text-m);\n }\n\n .submenu-popover {\n position: fixed;\n position-anchor: var(--anchor-name);\n inset: auto;\n top: anchor(top);\n left: anchor(right);\n margin: 0;\n padding: 0;\n border: none;\n background: transparent;\n /* UA stylesheet sets overflow:auto on [popover], which clips the box-shadow\n of the inner ixfx-menu-container child. Override to let the shadow render. */\n overflow: visible;\n position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;\n }\n\n :host(:focus) {\n outline: none;\n }\n\n .item.disabled {\n opacity: var(--disabled-opacity);\n cursor: default;\n }\n\n .item.disabled:hover {\n background: transparent;\n }\n\n .icon-slot {\n width: 0;\n overflow: hidden;\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n :host([icon-name]) .icon-slot,\n :host([data-icon-section]) .icon-slot {\n width: 1em;\n height: 1em;\n margin-right: var(--space-s);\n }\n\n .label {\n flex: 1;\n }\n\n .arrow {\n color: var(--surface-muted-text);\n margin-left: var(--space-m);\n }\n `];\n\n render() {\n const resolvedIcon = this._resolvedIcon();\n return html`\n <div\n class=\"item ${this.disabled ? `disabled` : ``}\"\n @click=${this._handleClick}\n @mouseenter=${this._handleMouseEnter}\n @mouseleave=${this._handleMouseLeave}\n tabindex=\"${this.disabled ? -1 : 0}\"\n role=\"menuitem\"\n aria-haspopup=\"true\"\n aria-expanded=\"false\"\n >\n <span class=\"icon-slot\">${resolvedIcon ? html`<ixfx-icon name=\"${resolvedIcon}\"></ixfx-icon>` : null}</span>\n <span class=\"label\"><slot></slot>${this.label}</span>\n <span class=\"arrow\">${this._hasSubmenu ? html`<ixfx-icon name=\"chevron-right\"></ixfx-icon>` : nothing}</span>\n </div>\n <div class=\"submenu-popover\" popover=\"auto\"\n @mouseenter=${this._handleSubmenuMouseEnter}\n @mouseleave=${this._handleSubmenuMouseLeave}\n >\n <slot name=\"submenu\" @slotchange=${this._handleSlotChange}></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ixfx-menu-sub-trigger': MenuSubTrigger;\n }\n}\n","import { css, html, LitElement } from 'lit';\nimport { themeFallbacks } from '../styles/fallbacks.js';\nimport { safeCustomElement as customElement } from '../util/custom-elements.js';\n\n@customElement(`ixfx-menu-separator`)\nexport class MenuSeparator extends LitElement {\n static styles = [themeFallbacks, css`\n :host {\n display: block;\n margin: var(--space-xs) 0;\n }\n\n .separator {\n height: 1px;\n background: var(--surface-4);\n }\n `];\n\n render() {\n return html`<div class=\"separator\"></div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ixfx-menu-separator': MenuSeparator;\n }\n}\n","import { css, html, LitElement } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { themeFallbacks } from '../styles/fallbacks.js';\nimport { safeCustomElement as customElement } from '../util/custom-elements.js';\n\n@customElement(`ixfx-menu-header`)\nexport class MenuHeader extends LitElement {\n @property({ type: String }) label = ``;\n\n static styles = [themeFallbacks, css`\n :host {\n display: block;\n padding: 6px var(--space-m) var(--space-xs);\n font-size: var(--text-s);\n font-weight: var(--font-weight-bold);\n text-transform: uppercase;\n color: var(--surface-muted-text);\n letter-spacing: 0.5px;\n }\n `];\n\n render() {\n return html`<div>${this.label}</div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ixfx-menu-header': MenuHeader;\n }\n}\n","import type { CommandRegistry, KeyboardManager } from '@clinth/ui-commands';\nimport type { TemplateResult } from 'lit';\nimport type { TreeNode } from '../tree/types.js';\nimport type { MenuTriggerCommandDetail } from './menu-trigger.js';\nimport { html, nothing } from 'lit';\nimport './menu-container.js';\nimport './menu-item.js';\nimport './menu-sub-trigger.js';\nimport './menu-separator.js';\nimport './menu-header.js';\n\nexport type MenuFromTreeOptions = {\n onCommand?: (detail: MenuTriggerCommandDetail) => void;\n registry?: CommandRegistry<unknown>;\n keyboardManager?: KeyboardManager;\n};\n\nfunction isLeaf(node: TreeNode): boolean {\n return node.children === undefined || node.children.length === 0;\n}\n\nexport function menuFromTree(\n nodes: TreeNode[],\n options: MenuFromTreeOptions = {},\n): TemplateResult {\n const { onCommand, registry, keyboardManager } = options;\n\n return html`${nodes.map((node) => {\n const label = node.item.label;\n\n if (label === `-`) {\n return html`<ixfx-menu-separator></ixfx-menu-separator>`;\n }\n\n if (label.startsWith(`# `)) {\n return html`<ixfx-menu-header label=\"${label.slice(2)}\"></ixfx-menu-header>`;\n }\n\n if (!isLeaf(node)) {\n const submenu = menuFromTree(node.children!, options);\n return html`\n <ixfx-menu-sub-trigger label=\"${label}\">\n <ixfx-menu-container\n slot=\"submenu\"\n .registry=${registry ?? nothing}\n .keyboardManager=${keyboardManager ?? nothing}\n >${submenu}</ixfx-menu-container>\n </ixfx-menu-sub-trigger>\n `;\n }\n\n return html`\n <ixfx-menu-item\n command=\"${node.item.key}\"\n label=\"${label}\"\n icon-name=\"${node.item.icon ?? nothing}\"\n @command=${onCommand ? (e: CustomEvent) => onCommand(e.detail) : nothing}\n ></ixfx-menu-item>\n `;\n })}`;\n}\n\nfunction buildMenuItemEl(\n node: TreeNode,\n options: MenuFromTreeOptions,\n): HTMLElement {\n const { onCommand, registry } = options;\n const label = node.item.label;\n\n const el = document.createElement(`ixfx-menu-item`);\n el.setAttribute(`command`, node.item.key);\n el.setAttribute(`label`, label);\n if (node.item.icon) {\n el.setAttribute(`icon-name`, node.item.icon);\n }\n if (onCommand) {\n el.addEventListener(`command`, e => onCommand((e as CustomEvent).detail));\n }\n if (registry) {\n (el as any).registry = registry;\n }\n return el;\n}\n\nfunction buildMenuSeparatorEl(): HTMLElement {\n return document.createElement(`ixfx-menu-separator`);\n}\n\nfunction buildMenuHeaderEl(label: string): HTMLElement {\n const el = document.createElement(`ixfx-menu-header`);\n el.setAttribute(`label`, label);\n return el;\n}\n\nfunction buildMenuSubTriggerEl(\n node: TreeNode,\n options: MenuFromTreeOptions,\n): HTMLElement {\n const { registry, keyboardManager } = options;\n\n const el = document.createElement(`ixfx-menu-sub-trigger`);\n el.setAttribute(`label`, node.item.label);\n if (registry)\n (el as any).registry = registry;\n if (keyboardManager)\n (el as any).keyboardManager = keyboardManager;\n\n const container = document.createElement(`ixfx-menu-container`);\n container.setAttribute(`slot`, `submenu`);\n if (registry)\n (container as any).registry = registry;\n if (keyboardManager)\n (container as any).keyboardManager = keyboardManager;\n\n const childEls = menuFromTreeToElements(node.children!, options);\n for (const child of childEls) {\n container.appendChild(child);\n }\n\n el.appendChild(container);\n return el;\n}\n\nexport function menuFromTreeToElements(\n nodes: TreeNode[],\n options: MenuFromTreeOptions = {},\n): HTMLElement[] {\n return nodes.map((node) => {\n const label = node.item.label;\n\n if (label === `-`) {\n return buildMenuSeparatorEl();\n }\n\n if (label.startsWith(`# `)) {\n return buildMenuHeaderEl(label.slice(2));\n }\n\n if (!isLeaf(node)) {\n return buildMenuSubTriggerEl(node, options);\n }\n\n return buildMenuItemEl(node, options);\n });\n}\n","import { css, html, LitElement } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { tickledItemStyles } from '../interaction/index.js';\nimport { themeFallbacks } from '../styles/fallbacks.js';\nimport { safeCustomElement as customElement } from '../util/custom-elements.js';\nimport '../icons/icon.js';\n\nexport type MenuRadioItemSelectDetail = {\n readonly value: string;\n};\n\nexport type MenuRadioItemSelectEvent = CustomEvent<MenuRadioItemSelectDetail>;\n\n@customElement(`ixfx-menu-radio-item`)\nexport class MenuRadioItem extends LitElement {\n @property({ type: String }) value = ``;\n @property({ type: String }) label = ``;\n @property({ type: Boolean }) disabled = false;\n @property({ type: String }) groupValue = ``;\n @property({ type: String, attribute: `icon-name` }) iconName = ``;\n\n private handleClick(): void {\n if (this.disabled)\n return;\n\n this.dispatchEvent(new CustomEvent<MenuRadioItemSelectDetail>(`radio-select`, {\n detail: { value: this.value },\n bubbles: true,\n composed: true,\n }));\n }\n\n static override styles = [themeFallbacks, tickledItemStyles, css`\n :host {\n display: block;\n }\n\n .item {\n display: flex;\n padding: 6px var(--space-m);\n font-size: var(--text-m);\n }\n\n :host(:focus) {\n outline: none;\n }\n\n .item.disabled {\n opacity: var(--disabled-opacity);\n cursor: default;\n }\n\n .item.disabled:hover {\n background: transparent;\n }\n\n .radio {\n width: 16px;\n margin-right: var(--space-s);\n font-size: var(--text-m);\n color: var(--accent);\n }\n\n .icon-slot {\n width: 0;\n overflow: hidden;\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n :host([icon-name]) .icon-slot,\n :host([data-icon-section]) .icon-slot {\n width: 1em;\n height: 1em;\n margin-right: var(--space-s);\n }\n\n .label {\n flex: 1;\n }\n `];\n\n render() {\n const isSelected = this.groupValue === this.value;\n\n return html`\n <div \n class=\"item ${this.disabled ? `disabled` : ``}\"\n @click=${this.handleClick}\n tabindex=\"${this.disabled ? -1 : 0}\"\n role=\"menuitemradio\"\n aria-checked=\"${isSelected}\"\n >\n <span class=\"radio\">${isSelected ? `●` : ``}</span>\n <span class=\"icon-slot\">${this.iconName ? html`<ixfx-icon name=\"${this.iconName}\"></ixfx-icon>` : null}</span>\n <span class=\"label\">${this.label}</span>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ixfx-menu-radio-item': MenuRadioItem;\n }\n}\n","import { css, html, LitElement } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { safeCustomElement as customElement } from '../util/custom-elements.js';\nimport './menu-radio-item.js';\n\nexport type MenuRadioGroupChangeDetail = {\n readonly name: string;\n readonly value: string;\n};\n\nexport type MenuRadioGroupChangeEvent = CustomEvent<MenuRadioGroupChangeDetail>;\n\n@customElement(`ixfx-menu-radio-group`)\nexport class MenuRadioGroup extends LitElement {\n @property({ type: String }) name = ``;\n @property({ type: String }) value = ``;\n\n @state() private slottedElements: Element[] = [];\n\n private handleSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this.slottedElements = slot.assignedElements();\n this.updateSlottedValues();\n }\n\n private updateSlottedValues(): void {\n for (const el of this.slottedElements) {\n (el as any).groupValue = this.value;\n }\n }\n\n private handleRadioSelect(e: CustomEvent): void {\n this.value = e.detail.value;\n this.dispatchEvent(new CustomEvent<MenuRadioGroupChangeDetail>(`radio-change`, {\n detail: {\n name: this.name,\n value: this.value,\n },\n bubbles: true,\n composed: true,\n }));\n }\n\n updated(changedProperties: Map<string, unknown>): void {\n if (changedProperties.has(`value`)) {\n this.updateSlottedValues();\n }\n }\n\n static styles = css`\n :host {\n display: block;\n }\n\n ::slotted(menu-radio-item) {\n display: block;\n }\n `;\n\n render() {\n return html`\n <slot @slotchange=${this.handleSlotChange} @radio-select=${this.handleRadioSelect}></slot>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ixfx-menu-radio-group': MenuRadioGroup;\n }\n}\n","export { MenuBar } from './menu-bar.js';\nexport { MenuCheckItem } from './menu-check-item.js';\nexport { MenuCommandController } from './menu-command-controller.js';\nexport { MenuContainer } from './menu-container.js';\nexport { menuFromTree, menuFromTreeToElements } from './menu-from-tree.js';\nexport type { MenuFromTreeOptions } from './menu-from-tree.js';\nexport { MenuHeader } from './menu-header.js';\nexport { MenuItem } from './menu-item.js';\nexport { MenuRadioGroup } from './menu-radio-group.js';\nexport { MenuRadioItem } from './menu-radio-item.js';\nexport { MenuSeparator } from './menu-separator.js';\nexport { MenuSubTrigger } from './menu-sub-trigger.js';\nexport { MenuTrigger } from './menu-trigger.js';\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"wheel-nudge-BlSCe_hD.js","names":[],"sources":["../src/mixins/wheel-nudge.ts"],"sourcesContent":["import type { LitElement } from 'lit';\nimport { property } from 'lit/decorators.js';\n\ntype Constructor<T = object> = new (...args: any[]) => T;\n\n/**\n * Direction of a wheel tick. `+1` for `deltaY < 0` (wheel up/away), `-1` for\n * `deltaY > 0` (wheel down/toward).\n */\nexport type WheelDirection = 1 | -1;\n\n/**\n * Abstract surface a consumer of {@link WheelNudgeMixin} must implement.\n *\n * `_applyWheelDelta` is called once per handled wheel tick. The consumer is\n * expected to reuse its existing arrow-key step math (typically a shared\n * `_stepByDelta(direction, effectiveStep)` private method) and call it here.\n *\n * `_shouldHandleWheel` may be overridden to restrict when the wheel acts\n * (e.g. editable-number rejects while the user is typing).\n */\nexport interface WheelNudgeConsumer {\n _applyWheelDelta: (direction: WheelDirection, event: WheelEvent) => void;\n _shouldHandleWheel: () => boolean;\n}\n\n/**\n * Internal: the surface a {@link WheelNudgeMixin}-decorated class exposes to\n * TypeScript — its own members plus the consumer contract.\n */\nexport type WheelNudgeConsumerType<TBase> = TBase & WheelNudgeConsumer;\ntype WheelNudgeMixinBase<T> = T extends Constructor<infer U> ? U : never;\ntype WheelNudgeMixinResult<T extends Constructor<LitElement>>\n = Constructor<WheelNudgeConsumerType<WheelNudgeMixinBase<T>>> & T;\n\n/**\n * Mixin that adds mousewheel support with an optional `lighttouch` hover mode.\n *\n * Activation rule:\n * - If the host's `disabled` property is truthy → ignore.\n * - Otherwise, handle if EITHER:\n * • `lighttouch` is true AND the pointer is currently over the host, OR\n * • the host (or anything in its shadow tree) currently has focus.\n *\n * Direction convention: `deltaY < 0` (wheel up/away) → +1 step; `deltaY > 0` → -1 step.\n *\n * Consumers must implement `_applyWheelDelta(direction, event)` (see\n * {@link WheelNudgeConsumer}). The mixin calls `event.preventDefault()` only when\n * the activation rule accepts the event, so page scroll is suppressed during\n * interaction without blocking wheel events over empty space.\n *\n * Usage:\n *\n * ```ts\n * class MyInput extends WheelNudgeMixin(LitElement) {\n * protected _applyWheelDelta(direction, event) {\n * const step = resolveAxisStep(this.range, this.stops, this.step, event.shiftKey);\n * this._stepByDelta(direction, step);\n * }\n * }\n * ```\n */\nexport function WheelNudgeMixin<T extends Constructor<LitElement>>(superClass: T): WheelNudgeMixinResult<T> {\n class WheelNudgeMixinClass extends superClass {\n /**\n * When true, the wheel adjusts the value simply when the pointer is hovering\n * over the host (no focus required). When false (default), the host must\n * have focus for the wheel to act.\n */\n @property({ type: Boolean, reflect: true })\n lighttouch = false;\n\n /**\n * Imperative hover flag — not Lit-reactive (avoids update churn for what\n * is purely an interaction-time predicate).\n */\n _isHovering = false;\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener(`pointerenter`, this._onPointerEnter);\n this.addEventListener(`pointerleave`, this._onPointerLeave);\n this.addEventListener(`wheel`, this._onWheel, { passive: false });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener(`pointerenter`, this._onPointerEnter);\n this.removeEventListener(`pointerleave`, this._onPointerLeave);\n this.removeEventListener(`wheel`, this._onWheel);\n this._isHovering = false;\n }\n\n _onPointerEnter = (): void => {\n this._isHovering = true;\n };\n\n _onPointerLeave = (): void => {\n this._isHovering = false;\n };\n\n /**\n * Override to restrict when wheel is handled (e.g. editable-number rejects\n * while the user is typing). Default: respect `disabled`, then apply the\n * focus-or-lighttouch rule described on the mixin.\n */\n _shouldHandleWheel(): boolean {\n const anyThis = this as unknown as { disabled?: boolean };\n if (anyThis.disabled)\n return false;\n if (this.lighttouch && this._isHovering)\n return true;\n if (this.matches?.(`:focus, :focus-within`))\n return true;\n const root = this.renderRoot;\n if (root && (root as ShadowRoot).activeElement !== null)\n return true;\n return false;\n }\n\n _onWheel = (event: WheelEvent): void => {\n if (!this._shouldHandleWheel())\n return;\n event.preventDefault();\n // Some browsers (notably when the user holds Shift) send `deltaY=0`\n // and route the scroll into `deltaX` instead. Use whichever axis\n // actually moved so the direction sign matches the user's intent.\n const delta = event.deltaY !== 0 ? event.deltaY : event.deltaX;\n const direction: WheelDirection = delta < 0 ? 1 : -1;\n (this as unknown as WheelNudgeConsumer)._applyWheelDelta(direction, event);\n };\n }\n return WheelNudgeMixinClass as unknown as WheelNudgeMixinResult<T>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DA,SAAgB,gBAAmD,YAAyC;CAC1G,MAAM,6BAA6B,WAAW;;;GAO/B,KAAA,aAAA;GAMC,KAAA,cAAA;GAiBgB,KAAA,wBAAA;IAC5B,KAAK,cAAc;GACrB;GAE8B,KAAA,wBAAA;IAC5B,KAAK,cAAc;GACrB;GAqBY,KAAA,YAAA,UAA4B;IACtC,IAAI,CAAC,KAAK,mBAAmB,GAC3B;IACF,MAAM,eAAe;IAKrB,MAAM,aADQ,MAAM,WAAW,IAAI,MAAM,SAAS,MAAM,UACd,IAAI,IAAI;IAClD,KAAwC,iBAAiB,WAAW,KAAK;GAC3E;;EApDA,oBAAmC;GACjC,MAAM,kBAAkB;GACxB,KAAK,iBAAiB,gBAAgB,KAAK,eAAe;GAC1D,KAAK,iBAAiB,gBAAgB,KAAK,eAAe;GAC1D,KAAK,iBAAiB,SAAS,KAAK,UAAU,EAAE,SAAS,MAAM,CAAC;EAClE;EAEA,uBAAsC;GACpC,MAAM,qBAAqB;GAC3B,KAAK,oBAAoB,gBAAgB,KAAK,eAAe;GAC7D,KAAK,oBAAoB,gBAAgB,KAAK,eAAe;GAC7D,KAAK,oBAAoB,SAAS,KAAK,QAAQ;GAC/C,KAAK,cAAc;EACrB;;;;;;EAeA,qBAA8B;GAE5B,IAAI,KAAQ,UACV,OAAO;GACT,IAAI,KAAK,cAAc,KAAK,aAC1B,OAAO;GACT,IAAI,KAAK,UAAU,uBAAuB,GACxC,OAAO;GACT,MAAM,OAAO,KAAK;GAClB,IAAI,QAAS,KAAoB,kBAAkB,MACjD,OAAO;GACT,OAAO;EACT;CAaF;CA9DG,WAAA,CAAA,SAAS;EAAE,MAAM;EAAS,SAAS;CAAK,CAAC,CAAA,GAAA,qBAAA,WAAA,cAAA,KAAA,CAAA;CA+D5C,OAAO;AACT"}
@@ -1,29 +0,0 @@
1
- import { LitElement } from "lit";
2
- //#region src/mixins/wheel-nudge.d.ts
3
- /**
4
- * Direction of a wheel tick. `+1` for `deltaY < 0` (wheel up/away), `-1` for
5
- * `deltaY > 0` (wheel down/toward).
6
- */
7
- type WheelDirection = 1 | -1;
8
- /**
9
- * Abstract surface a consumer of {@link WheelNudgeMixin} must implement.
10
- *
11
- * `_applyWheelDelta` is called once per handled wheel tick. The consumer is
12
- * expected to reuse its existing arrow-key step math (typically a shared
13
- * `_stepByDelta(direction, effectiveStep)` private method) and call it here.
14
- *
15
- * `_shouldHandleWheel` may be overridden to restrict when the wheel acts
16
- * (e.g. editable-number rejects while the user is typing).
17
- */
18
- interface WheelNudgeConsumer {
19
- _applyWheelDelta: (direction: WheelDirection, event: WheelEvent) => void;
20
- _shouldHandleWheel: () => boolean;
21
- }
22
- /**
23
- * Internal: the surface a {@link WheelNudgeMixin}-decorated class exposes to
24
- * TypeScript — its own members plus the consumer contract.
25
- */
26
- type WheelNudgeConsumerType<TBase> = TBase & WheelNudgeConsumer;
27
- //#endregion
28
- export { WheelNudgeConsumerType as n, WheelDirection as t };
29
- //# sourceMappingURL=wheel-nudge-DGrNzWuE.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wheel-nudge-DGrNzWuE.d.ts","names":[],"sources":["../src/mixins/wheel-nudge.ts"],"mappings":";;;;;;KASY;;;;;;;;;;;UAYK;EACf,mBAAmB,WAAW,gBAAgB,OAAO;EACrD;;;;;;KAOU,uBAAuB,SAAS,QAAQ"}