@ixfx/components 0.6.2 → 0.7.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 (152) hide show
  1. package/bundle/index.d.ts +1490 -615
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +20477 -15855
  4. package/bundle/index.js.map +1 -1
  5. package/bundle/style.css +1 -1
  6. package/dist/ac-text.js +3 -2
  7. package/dist/ac-text.js.map +1 -1
  8. package/dist/ac-token.js +3 -2
  9. package/dist/ac-token.js.map +1 -1
  10. package/dist/{button-Bn0BuLpO.js → button-Dat2yHAU.js} +4 -3
  11. package/dist/button-Dat2yHAU.js.map +1 -0
  12. package/dist/button.js +1 -1
  13. package/dist/checkbox.js +1 -1
  14. package/dist/{colour-picker-0stHB90J.js → colour-picker-BSDBL-QL.js} +4 -3
  15. package/dist/{colour-picker-0stHB90J.js.map → colour-picker-BSDBL-QL.js.map} +1 -1
  16. package/dist/colour-picker.js +1 -1
  17. package/dist/crumbs.js +4 -2
  18. package/dist/crumbs.js.map +1 -1
  19. package/dist/data-display.js +1 -1
  20. package/dist/data-grid.js +4 -2
  21. package/dist/data-grid.js.map +1 -1
  22. package/dist/{defaults-BCiDhZbF.js → defaults-BkWCPJFL.js} +3 -20
  23. package/dist/defaults-BkWCPJFL.js.map +1 -0
  24. package/dist/editable-label-rzXkjf1H.d.ts +75 -0
  25. package/dist/editable-label-rzXkjf1H.d.ts.map +1 -0
  26. package/dist/editable-label.d.ts +2 -75
  27. package/dist/editable-label.js +2 -1
  28. package/dist/editable-label.js.map +1 -1
  29. package/dist/{fallbacks-DEE1nCTt.js → fallbacks-DNI9xt0F.js} +2 -2
  30. package/dist/{fallbacks-DEE1nCTt.js.map → fallbacks-DNI9xt0F.js.map} +1 -1
  31. package/dist/{grouped-item-lister-_pMDwIo1.d.ts → grouped-item-lister-DnWB-eFT.d.ts} +2 -2
  32. package/dist/{grouped-item-lister-_pMDwIo1.d.ts.map → grouped-item-lister-DnWB-eFT.d.ts.map} +1 -1
  33. package/dist/grouped-item-lister.d.ts +1 -1
  34. package/dist/{icons-Bm2ByQ3s.js → icon-CvnPanhA.js} +3 -6
  35. package/dist/icon-CvnPanhA.js.map +1 -0
  36. package/dist/icons.js +8 -2
  37. package/dist/icons.js.map +1 -0
  38. package/dist/index-CG2vIgPv.d.ts +101 -0
  39. package/dist/index-CG2vIgPv.d.ts.map +1 -0
  40. package/dist/index.d.ts +655 -65
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +3682 -924
  43. package/dist/index.js.map +1 -1
  44. package/dist/interaction-D5XdKzbR.js +2 -0
  45. package/dist/labelled-radial-input.js +2 -1
  46. package/dist/labelled-radial-input.js.map +1 -1
  47. package/dist/labelled-slider-input-DzigjMwP.js +808 -0
  48. package/dist/labelled-slider-input-DzigjMwP.js.map +1 -0
  49. package/dist/labelled-slider-input.d.ts +2 -0
  50. package/dist/labelled-slider-input.js +2 -0
  51. package/dist/{types-DlVNJez_.d.ts → list-selection-types-DSuRNWpx.d.ts} +10 -2
  52. package/dist/list-selection-types-DSuRNWpx.d.ts.map +1 -0
  53. package/dist/menu-BjRvlgPy.js +825 -0
  54. package/dist/menu-BjRvlgPy.js.map +1 -0
  55. package/dist/menu-item-B-zYrTTi.js +797 -0
  56. package/dist/menu-item-B-zYrTTi.js.map +1 -0
  57. package/dist/menu.js +2 -1
  58. package/dist/miller.d.ts +1 -1
  59. package/dist/miller.js +3 -2
  60. package/dist/miller.js.map +1 -1
  61. package/dist/narrowed-text.js +4 -2
  62. package/dist/narrowed-text.js.map +1 -1
  63. package/dist/notifier.js +3 -2
  64. package/dist/notifier.js.map +1 -1
  65. package/dist/panel.d.ts +13 -13
  66. package/dist/panel.d.ts.map +1 -1
  67. package/dist/panel.js +37 -253
  68. package/dist/panel.js.map +1 -1
  69. package/dist/plots.js +1 -1
  70. package/dist/polar-pad.js +1 -1
  71. package/dist/{radial-input-Dk8Wydp5.js → radial-input-Dp5qz0ly.js} +2 -2
  72. package/dist/{radial-input-Dk8Wydp5.js.map → radial-input-Dp5qz0ly.js.map} +1 -1
  73. package/dist/radial-input.js +1 -1
  74. package/dist/range-input.js +1 -1
  75. package/dist/range.js +1 -1
  76. package/dist/registry-CkW7q09Q.js +21 -0
  77. package/dist/registry-CkW7q09Q.js.map +1 -0
  78. package/dist/select-horiz.js +3 -2
  79. package/dist/select-horiz.js.map +1 -1
  80. package/dist/{slider-input-XppH3vcy.d.ts → slider-input-BfRUW2ZB.d.ts} +16 -3
  81. package/dist/slider-input-BfRUW2ZB.d.ts.map +1 -0
  82. package/dist/{slider-input-CT4gcF-o.js → slider-input-De4eoT11.js} +382 -67
  83. package/dist/slider-input-De4eoT11.js.map +1 -0
  84. package/dist/slider-input.d.ts +1 -1
  85. package/dist/slider-input.js +1 -1
  86. package/dist/snackbar.js +1 -1
  87. package/dist/split-layout-BFSbUZQv.js +969 -0
  88. package/dist/split-layout-BFSbUZQv.js.map +1 -0
  89. package/dist/split-layout-JK3xWVX2.d.ts +179 -0
  90. package/dist/split-layout-JK3xWVX2.d.ts.map +1 -0
  91. package/dist/split-layout.d.ts +2 -60
  92. package/dist/split-layout.js +1 -617
  93. package/dist/style.css +1 -1
  94. package/dist/swipe.js +1 -1
  95. package/dist/{tab-list-D_1cYhB_.d.ts → tab-list-MPwq_1Jj.d.ts} +2 -1
  96. package/dist/tab-list-MPwq_1Jj.d.ts.map +1 -0
  97. package/dist/tabs.d.ts +1 -1
  98. package/dist/tabs.js +96 -14
  99. package/dist/tabs.js.map +1 -1
  100. package/dist/{tickled-controller-BtevrRLV.d.ts → tickled-controller-CRzdIEjJ.d.ts} +7 -1
  101. package/dist/tickled-controller-CRzdIEjJ.d.ts.map +1 -0
  102. package/dist/{tickled-controller-DkAU-0qL.js → tickled-controller-h9GmJ_bU.js} +8 -2
  103. package/dist/tickled-controller-h9GmJ_bU.js.map +1 -0
  104. package/dist/titlebar-DaCfpliv.d.ts +50 -0
  105. package/dist/titlebar-DaCfpliv.d.ts.map +1 -0
  106. package/dist/titlebar.d.ts +2 -0
  107. package/dist/titlebar.js +324 -0
  108. package/dist/titlebar.js.map +1 -0
  109. package/dist/{tree-BO6BXI2b.js → tree-Ch4W8qXQ.js} +4 -3
  110. package/dist/{tree-BO6BXI2b.js.map → tree-Ch4W8qXQ.js.map} +1 -1
  111. package/dist/tree.d.ts +1 -1
  112. package/dist/tree.js +1 -1
  113. package/dist/vertical-list-ClnqTkF3.js +814 -0
  114. package/dist/vertical-list-ClnqTkF3.js.map +1 -0
  115. package/dist/vertical-list.d.ts +8 -9
  116. package/dist/vertical-list.d.ts.map +1 -1
  117. package/dist/vertical-list.js +1 -736
  118. package/dist/{xy-axis-B6yMjckW.js → xy-axis-CxHC8z5E.js} +109 -3
  119. package/dist/xy-axis-CxHC8z5E.js.map +1 -0
  120. package/dist/xy-pad.js +1 -1
  121. package/docs-user/README.md +5 -0
  122. package/docs-user/detail-list.md +398 -0
  123. package/docs-user/dock.md +310 -0
  124. package/docs-user/index.json +27 -2
  125. package/docs-user/labelled-slider-input.md +198 -0
  126. package/docs-user/llms.txt +6 -1
  127. package/docs-user/panel.md +20 -1
  128. package/docs-user/slider-input.md +55 -15
  129. package/docs-user/split-layout.md +136 -170
  130. package/docs-user/tabs.md +34 -0
  131. package/docs-user/titlebar.md +102 -0
  132. package/docs-user/toolbar.md +95 -22
  133. package/docs-user/twosplit-layout.md +254 -0
  134. package/docs-user/user-catalog.md +8 -1
  135. package/llms.txt +6 -1
  136. package/package.json +1 -2
  137. package/dist/button-Bn0BuLpO.js.map +0 -1
  138. package/dist/defaults-BCiDhZbF.js.map +0 -1
  139. package/dist/editable-label.d.ts.map +0 -1
  140. package/dist/icons-Bm2ByQ3s.js.map +0 -1
  141. package/dist/menu-DIfU70AA.js +0 -1609
  142. package/dist/menu-DIfU70AA.js.map +0 -1
  143. package/dist/slider-input-CT4gcF-o.js.map +0 -1
  144. package/dist/slider-input-XppH3vcy.d.ts.map +0 -1
  145. package/dist/split-layout.d.ts.map +0 -1
  146. package/dist/split-layout.js.map +0 -1
  147. package/dist/tab-list-D_1cYhB_.d.ts.map +0 -1
  148. package/dist/tickled-controller-BtevrRLV.d.ts.map +0 -1
  149. package/dist/tickled-controller-DkAU-0qL.js.map +0 -1
  150. package/dist/types-DlVNJez_.d.ts.map +0 -1
  151. package/dist/vertical-list.js.map +0 -1
  152. package/dist/xy-axis-B6yMjckW.js.map +0 -1
@@ -1,1609 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { t as TickledController } from "./tickled-controller-DkAU-0qL.js";
3
- import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
4
- import { t as themeFallbacks } from "./fallbacks-DEE1nCTt.js";
5
- import { t as popupSurfaceStyles } from "./popup-D80sj2QC.js";
6
- import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
7
- import { LitElement, css, html, nothing } from "lit";
8
- import { property, query, state } from "lit/decorators.js";
9
- //#region src/menu/menu-container.ts
10
- let MenuContainer = class MenuContainer extends LitElement {
11
- constructor(..._args) {
12
- super(..._args);
13
- this.tickled = new TickledController(this, {
14
- getItems: () => {
15
- return this._getMenuItems().map((item, i) => ({
16
- id: i,
17
- enabled: !item.hasAttribute(`disabled`)
18
- }));
19
- },
20
- onTickle: (index) => {
21
- const items = this._getMenuItems();
22
- if (index !== void 0) {
23
- const item = items[index];
24
- item.tabIndex = 0;
25
- item.focus({ preventScroll: true });
26
- }
27
- },
28
- onStateChange: () => {
29
- this._syncTickledClasses();
30
- }
31
- });
32
- this._handleMouseOver = (e) => {
33
- const items = this._getMenuItems();
34
- const path = e.composedPath();
35
- const hoveredIndex = items.findIndex((item) => path.includes(item));
36
- if (hoveredIndex !== -1) {
37
- const item = items[hoveredIndex];
38
- const itemPathIndex = path.indexOf(item);
39
- if (!path.slice(0, itemPathIndex).some((node) => node instanceof Element && node.tagName === `IXFX-MENU-CONTAINER`)) this.tickled.handlePointerEnter(hoveredIndex);
40
- e.stopPropagation();
41
- }
42
- };
43
- this._handleKeydown = (e) => {
44
- const items = this._getMenuItems();
45
- if (items.length === 0) return;
46
- switch (e.key) {
47
- case `ArrowDown`:
48
- e.preventDefault();
49
- e.stopPropagation();
50
- this._focusNext(items, 1);
51
- break;
52
- case `ArrowUp`:
53
- e.preventDefault();
54
- e.stopPropagation();
55
- this._focusNext(items, -1);
56
- break;
57
- case `Home`:
58
- e.preventDefault();
59
- e.stopPropagation();
60
- this._focusItem(items, 0);
61
- break;
62
- case `End`:
63
- e.preventDefault();
64
- e.stopPropagation();
65
- this._focusItem(items, items.length - 1);
66
- break;
67
- case `Enter`:
68
- case ` `:
69
- e.preventDefault();
70
- e.stopPropagation();
71
- this._activateFocusedItem(items);
72
- }
73
- };
74
- }
75
- static {
76
- this.styles = [
77
- popupSurfaceStyles,
78
- themeFallbacks,
79
- tickledStyles,
80
- css`
81
- :host {
82
- display: block;
83
- }
84
-
85
- .popover {
86
- padding: var(--space-xs) 0;
87
- min-width: 160px;
88
- }
89
-
90
- :host {
91
- opacity: 1;
92
- transform: translateY(0) scale(1);
93
- transition: opacity 0.12s ease-out, transform 0.12s ease-out,
94
- display 0.12s allow-discrete, overlay 0.12s allow-discrete;
95
- }
96
-
97
- @starting-style :host {
98
- opacity: 0;
99
- transform: translateY(-4px) scale(0.97);
100
- }
101
-
102
- ::slotted(menu-separator) {
103
- display: block;
104
- margin: var(--space-xs) 0;
105
- }
106
- `
107
- ];
108
- }
109
- _updateIconSections() {
110
- const slot = this.shadowRoot?.querySelector(`slot`);
111
- if (!slot) return;
112
- const assigned = slot.assignedElements({ flatten: true });
113
- const sections = [];
114
- let section = [];
115
- for (const el of assigned) if (el.tagName === `IXFX-MENU-SEPARATOR` || el.tagName === `IXFX-MENU-HEADER`) {
116
- if (section.length > 0) {
117
- sections.push(section);
118
- section = [];
119
- }
120
- } else section.push(el);
121
- if (section.length > 0) sections.push(section);
122
- this._propagateContext();
123
- for (const sec of sections) {
124
- const hasIcon = sec.some((el) => {
125
- if (el.tagName === `IXFX-MENU-RADIO-GROUP`) return [...el.children].some((ri) => ri.hasAttribute(`icon-name`));
126
- return el.hasAttribute(`icon-name`);
127
- });
128
- for (const el of sec) if (el.tagName === `IXFX-MENU-RADIO-GROUP`) for (const ri of el.children) if (hasIcon) ri.setAttribute(`data-icon-section`, ``);
129
- else ri.removeAttribute(`data-icon-section`);
130
- else if (hasIcon) el.setAttribute(`data-icon-section`, ``);
131
- else el.removeAttribute(`data-icon-section`);
132
- }
133
- this.performUpdate();
134
- }
135
- _propagateContext() {
136
- const slot = this.shadowRoot?.querySelector(`slot`);
137
- if (!slot) return;
138
- for (const el of slot.assignedElements({ flatten: true })) {
139
- if (`registry` in el) el.registry = this.registry;
140
- if (`keyboardManager` in el) el.keyboardManager = this.keyboardManager;
141
- }
142
- }
143
- updated(changedProperties) {
144
- if (changedProperties.has(`registry`) || changedProperties.has(`keyboardManager`)) this._propagateContext();
145
- }
146
- /**
147
- * Sync data-input-mode to :host so CSS variables cascade to slotted items
148
- */
149
- willUpdate(changed) {
150
- super.willUpdate(changed);
151
- this.setAttribute(`data-input-mode`, this.tickled.inputMode);
152
- }
153
- render() {
154
- return html`
155
- <div
156
- class="popover popup-surface"
157
- role="menu"
158
- @keydown=${this._handleKeydown}
159
- @mouseover=${this._handleMouseOver}
160
- >
161
- <slot @slotchange=${this._updateIconSections}></slot>
162
- </div>
163
- `;
164
- }
165
- _getMenuItems() {
166
- const slot = this.shadowRoot?.querySelector(`slot`);
167
- if (!slot) return [];
168
- const skipTags = [`IXFX-MENU-SEPARATOR`, `IXFX-MENU-HEADER`];
169
- const result = [];
170
- for (const el of slot.assignedElements({ flatten: true })) {
171
- if (skipTags.includes(el.tagName)) continue;
172
- if (el.hasAttribute(`disabled`)) continue;
173
- const role = el.getAttribute(`role`);
174
- if (role === `none` || role === `presentation`) continue;
175
- if (el.tagName === `IXFX-MENU-RADIO-GROUP`) {
176
- const groupSlot = el.shadowRoot?.querySelector(`slot`);
177
- for (const radioItem of groupSlot?.assignedElements({ flatten: true }) ?? []) if (!radioItem.hasAttribute(`disabled`)) result.push(radioItem);
178
- } else result.push(el);
179
- }
180
- return result;
181
- }
182
- /**
183
- * Sync .tickled class on slotted items to match controller state
184
- * This is called via onStateChange whenever the tickled state changes
185
- */
186
- _syncTickledClasses() {
187
- const items = this._getMenuItems();
188
- items.forEach((item, i) => {
189
- if (this.tickled.isTickled(i)) {
190
- item.classList.add(`tickled`);
191
- item.tabIndex = 0;
192
- const innerItem = item.shadowRoot?.querySelector(`.item`);
193
- if (innerItem) {
194
- innerItem.style.background = `rgba(0, 0, 0, 0.06)`;
195
- innerItem.style.borderColor = `var(--accent)`;
196
- innerItem.style.color = `inherit`;
197
- }
198
- } else {
199
- item.classList.remove(`tickled`);
200
- item.tabIndex = -1;
201
- const innerItem = item.shadowRoot?.querySelector(`.item`);
202
- if (innerItem) {
203
- innerItem.style.background = ``;
204
- innerItem.style.borderColor = ``;
205
- innerItem.style.color = ``;
206
- }
207
- }
208
- });
209
- if (this.tickled.inputMode === `keyboard` && this.tickled.tickledId !== void 0) items[this.tickled.tickledId]?.focus();
210
- }
211
- focusFirst() {
212
- this.tickled.handleKeyboardNavigation(`first`);
213
- }
214
- _focusNext(items, direction) {
215
- if (items.length === 0) return;
216
- this.tickled.handleKeyboardNavigation(direction > 0 ? `next` : `previous`);
217
- }
218
- _focusItem(items, index) {
219
- if (items.length === 0) return;
220
- const clampedIndex = Math.max(0, Math.min(index, items.length - 1));
221
- this.tickled.handleKeyboardNavigation(clampedIndex);
222
- }
223
- _activateFocusedItem(items) {
224
- const tickledIndex = this.tickled.tickledId;
225
- if (tickledIndex === void 0) return;
226
- const item = items[tickledIndex];
227
- if (!item) return;
228
- const tagName = item.tagName;
229
- if (tagName === `IXFX-MENU-SUB-TRIGGER`) return;
230
- if (tagName === `IXFX-MENU-CHECK-ITEM`) {
231
- const checkItem = item;
232
- checkItem.checked = !checkItem.checked;
233
- checkItem.dispatchEvent(new CustomEvent(`command`, {
234
- detail: {
235
- command: checkItem.command,
236
- checked: checkItem.checked
237
- },
238
- bubbles: true,
239
- composed: true
240
- }));
241
- return;
242
- }
243
- if (tagName === `IXFX-MENU-RADIO-ITEM`) {
244
- item.dispatchEvent(new CustomEvent(`radio-select`, {
245
- detail: { value: item.value },
246
- bubbles: true,
247
- composed: true
248
- }));
249
- return;
250
- }
251
- if (tagName === `IXFX-MENU-ITEM`) {
252
- const command = item.command;
253
- if (command) item.dispatchEvent(new CustomEvent(`command`, {
254
- detail: { command },
255
- bubbles: true,
256
- composed: true
257
- }));
258
- return;
259
- }
260
- item.click();
261
- }
262
- };
263
- __decorate([property({
264
- type: String,
265
- attribute: `anchor`
266
- })], MenuContainer.prototype, "anchor", void 0);
267
- __decorate([property({ attribute: false })], MenuContainer.prototype, "registry", void 0);
268
- __decorate([property({ attribute: false })], MenuContainer.prototype, "keyboardManager", void 0);
269
- __decorate([query(`.popover`)], MenuContainer.prototype, "_popover", void 0);
270
- MenuContainer = __decorate([safeCustomElement(`ixfx-menu-container`)], MenuContainer);
271
- //#endregion
272
- //#region src/menu/menu-trigger.ts
273
- var _MenuTrigger;
274
- let MenuTrigger = class MenuTrigger extends LitElement {
275
- static {
276
- _MenuTrigger = this;
277
- }
278
- constructor(..._args) {
279
- super(..._args);
280
- this.mode = `trigger`;
281
- this.label = `⋮`;
282
- this.placement = `bottom-start`;
283
- this.tabStop = true;
284
- this._isOpen = false;
285
- this._hasMenu = false;
286
- this._hasTriggerSlot = false;
287
- this._anchorName = `--mt-${_MenuTrigger._count++}`;
288
- this._clickListener = this._handleClickOutside.bind(this);
289
- this._handlePopoverToggle = (e) => {
290
- if (e.newState === `closed`) {
291
- this._isOpen = false;
292
- document.removeEventListener(`click`, this._clickListener);
293
- this.dispatchEvent(new CustomEvent(`menu-trigger-close`, {
294
- bubbles: true,
295
- composed: true
296
- }));
297
- }
298
- };
299
- this._handleKeydown = (e) => {
300
- if (!this._isOpen) return;
301
- if (e.key === `Escape`) {
302
- e.preventDefault();
303
- e.stopPropagation();
304
- this._closeMenu();
305
- this._triggerBtn?.focus();
306
- } else if (e.key === `Tab`) {
307
- this._closeMenu();
308
- this._triggerBtn?.focus();
309
- }
310
- };
311
- }
312
- static {
313
- this._count = 0;
314
- }
315
- connectedCallback() {
316
- super.connectedCallback();
317
- document.addEventListener(`keydown`, this._handleKeydown);
318
- this.addEventListener(`command`, this._handleCommand);
319
- this.addEventListener(`radio-change`, this._handleRadioChange);
320
- }
321
- disconnectedCallback() {
322
- super.disconnectedCallback();
323
- document.removeEventListener(`keydown`, this._handleKeydown);
324
- this.removeEventListener(`command`, this._handleCommand);
325
- this.removeEventListener(`radio-change`, this._handleRadioChange);
326
- document.removeEventListener(`click`, this._clickListener);
327
- }
328
- firstUpdated() {
329
- this.style.setProperty(`--anchor-name`, this._anchorName);
330
- this._popup.addEventListener(`toggle`, this._handlePopoverToggle);
331
- this.updateComplete.then(() => {
332
- const slot = this.shadowRoot?.querySelector(`slot[name="menu"]`);
333
- if (slot) {
334
- if (slot.assignedElements({ flatten: true }).length > 0) {
335
- this._hasMenu = true;
336
- this._propagateContext();
337
- }
338
- }
339
- this._updatePosition();
340
- });
341
- }
342
- updated(changedProperties) {
343
- if (changedProperties.has(`registry`) || changedProperties.has(`keyboardManager`)) this._propagateContext();
344
- }
345
- _updatePosition() {
346
- const pos = {
347
- "bottom-start": { insetArea: `block-end span-inline-start` },
348
- "bottom-end": { insetArea: `block-end span-inline-end` },
349
- "right-start": { insetArea: `inline-end span-block-start` }
350
- }[this.placement];
351
- this._popup.style.insetArea = pos.insetArea;
352
- this._popup.style.positionAnchor = this._anchorName;
353
- const fb = {
354
- "bottom-start": {
355
- top: `anchor(bottom)`,
356
- left: `anchor(left)`,
357
- right: `auto`
358
- },
359
- "bottom-end": {
360
- top: `anchor(bottom)`,
361
- left: `auto`,
362
- right: `anchor(left)`
363
- },
364
- "right-start": {
365
- top: `anchor(top)`,
366
- left: `anchor(right)`,
367
- right: `auto`
368
- }
369
- }[this.placement];
370
- this._popup.style.top = fb.top;
371
- this._popup.style.left = fb.left;
372
- this._popup.style.right = fb.right;
373
- }
374
- _propagateContext() {
375
- const items = (this.shadowRoot?.querySelector(`slot[name="menu"]`))?.assignedElements({ flatten: true }) ?? [];
376
- for (const el of items) {
377
- if (`registry` in el) el.registry = this.registry;
378
- if (`keyboardManager` in el) el.keyboardManager = this.keyboardManager;
379
- }
380
- }
381
- _handleSlotChange(e) {
382
- const assigned = e.target.assignedElements({ flatten: true });
383
- this._hasMenu = assigned.length > 0;
384
- this._propagateContext();
385
- }
386
- _handleTriggerSlotChange(e) {
387
- const slot = e.target;
388
- this._hasTriggerSlot = slot.assignedElements({ flatten: true }).length > 0;
389
- }
390
- get isOpen() {
391
- return this._isOpen;
392
- }
393
- openMenu() {
394
- this._openMenu();
395
- }
396
- closeMenu() {
397
- this._closeMenu();
398
- }
399
- focusTrigger() {
400
- this._triggerBtn?.focus();
401
- }
402
- _openMenu() {
403
- if (this._isOpen || !this._popup?.popover) return;
404
- try {
405
- this._popup.showPopover({ source: this });
406
- this._isOpen = true;
407
- document.addEventListener(`click`, this._clickListener);
408
- this.dispatchEvent(new CustomEvent(`menu-trigger-open`, {
409
- bubbles: true,
410
- composed: true
411
- }));
412
- requestAnimationFrame(() => {
413
- this._getMenuContainer()?.focusFirst();
414
- });
415
- } catch (err) {
416
- console.warn(`showPopover failed:`, err);
417
- }
418
- }
419
- _closeMenu() {
420
- if (!this._isOpen) return;
421
- if (this._popup?.popover) try {
422
- this._popup.hidePopover();
423
- } catch {}
424
- this._isOpen = false;
425
- document.removeEventListener(`click`, this._clickListener);
426
- }
427
- _handleTriggerClick(e) {
428
- e.stopPropagation();
429
- if (this._isOpen) {
430
- this._closeMenu();
431
- this._triggerBtn?.focus();
432
- } else this._openMenu();
433
- }
434
- _handleClickOutside(e) {
435
- if (!this.contains(e.target)) {
436
- if (this._popup?.popover && this._popup.hasAttribute(`open`)) try {
437
- this._popup.hidePopover();
438
- } catch {}
439
- }
440
- }
441
- _getMenuContainer() {
442
- return ((this.shadowRoot?.querySelector(`slot[name="menu"]`))?.assignedElements({ flatten: true }) ?? []).find((el) => el.tagName === `IXFX-MENU-CONTAINER`) ?? null;
443
- }
444
- _handleCommand(e) {
445
- if (this._popup?.popover) try {
446
- this._popup.hidePopover();
447
- } catch {}
448
- this.dispatchEvent(new CustomEvent(`menu-command`, {
449
- detail: e.detail,
450
- bubbles: true,
451
- composed: true
452
- }));
453
- }
454
- _handleRadioChange(e) {
455
- if (this._popup?.popover) try {
456
- this._popup.hidePopover();
457
- } catch {}
458
- this.dispatchEvent(new CustomEvent(`menu-command`, {
459
- detail: {
460
- command: e.detail.name,
461
- value: e.detail.value
462
- },
463
- bubbles: true,
464
- composed: true
465
- }));
466
- }
467
- _handleContextMenu(e) {
468
- if (this.mode !== `context`) return;
469
- e.preventDefault();
470
- this._popup.style.top = `${e.clientY}px`;
471
- this._popup.style.left = `${e.clientX}px`;
472
- this._popup.style.position = `fixed`;
473
- this._popup.style.positionAnchor = `none`;
474
- if (this._popup?.popover) try {
475
- this._popup.showPopover();
476
- } catch {}
477
- }
478
- static {
479
- this.styles = [
480
- themeFallbacks,
481
- popupSurfaceStyles,
482
- css`
483
- :host {
484
- display: inline-block;
485
- position: relative;
486
- --ixfx-button-padding: var(--ixfx-button-padding, var(--space-s) var(--space-m));
487
- }
488
-
489
- .trigger-area {
490
- anchor-name: var(--anchor-name);
491
- display: inline-flex;
492
- align-items: stretch;
493
- }
494
-
495
- .trigger-btn {
496
- background: none;
497
- border: none;
498
- color: var(--surface-3-text);
499
- cursor: pointer;
500
- padding: var(--ixfx-button-padding);
501
- font-family: var(--font-family, inherit);
502
- font-size: var(--text-m);
503
- font-weight: 600;
504
- border-radius: var(--radius-s);
505
- opacity: 0.6;
506
- transition: opacity var(--transition), background var(--transition);
507
- }
508
-
509
- .trigger-btn:hover {
510
- background: var(--_trigger-hover-bg, var(--surface-4));
511
- color: var(--surface-3-text);
512
- opacity: var(--_trigger-hover-opacity, 1);
513
- }
514
-
515
- .trigger-btn:focus-visible {
516
- background: var(--surface-4);
517
- color: var(--surface-3-text);
518
- outline: none;
519
- opacity: 1;
520
- }
521
-
522
- .popup {
523
- position: fixed;
524
- position-anchor: var(--anchor-name);
525
- inset: auto;
526
- margin: 8px;
527
- padding: 0;
528
- border: none;
529
- background: transparent;
530
- z-index: 100;
531
- max-height: min(60vh, 400px);
532
- max-width: min(90vw, 320px);
533
- overflow: auto;
534
- overscroll-behavior: contain;
535
- position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
536
- }
537
-
538
- .popup[popover] {
539
- opacity: 1;
540
- transform: translateY(0) scale(1);
541
- transition: opacity 0.12s ease-out, transform 0.12s ease-out,
542
- display 0.12s allow-discrete, overlay 0.12s allow-discrete;
543
- }
544
-
545
- @starting-style .popup {
546
- opacity: 0;
547
- transform: translateY(-4px) scale(0.97);
548
- }
549
- `
550
- ];
551
- }
552
- render() {
553
- const showButton = this.mode !== `dropdown` && this._hasMenu && !this._hasTriggerSlot;
554
- return html`
555
- <div
556
- class="trigger-area"
557
- @click=${this._handleTriggerClick}
558
- @contextmenu=${this._handleContextMenu}
559
- >
560
- ${showButton ? html`<button class="trigger-btn" part="base" tabindex=${this.tabStop ? `0` : `-1`}>${this.label}</button>` : null}
561
- <slot name="trigger" @slotchange=${this._handleTriggerSlotChange}></slot>
562
- </div>
563
- <div
564
- class="popup popup-surface"
565
- popover="auto"
566
- @click=${(e) => e.stopPropagation()}
567
- >
568
- <slot name="menu" @slotchange=${this._handleSlotChange}></slot>
569
- </div>
570
- `;
571
- }
572
- };
573
- __decorate([property({ type: String })], MenuTrigger.prototype, "mode", void 0);
574
- __decorate([property({ type: String })], MenuTrigger.prototype, "label", void 0);
575
- __decorate([property({ attribute: false })], MenuTrigger.prototype, "registry", void 0);
576
- __decorate([property({ attribute: false })], MenuTrigger.prototype, "keyboardManager", void 0);
577
- __decorate([property({ type: String })], MenuTrigger.prototype, "placement", void 0);
578
- __decorate([property({
579
- type: Boolean,
580
- attribute: `tab-stop`
581
- })], MenuTrigger.prototype, "tabStop", void 0);
582
- __decorate([state()], MenuTrigger.prototype, "_isOpen", void 0);
583
- __decorate([state()], MenuTrigger.prototype, "_hasMenu", void 0);
584
- __decorate([state()], MenuTrigger.prototype, "_hasTriggerSlot", void 0);
585
- __decorate([query(`.trigger-btn`)], MenuTrigger.prototype, "_triggerBtn", void 0);
586
- __decorate([query(`.popup`)], MenuTrigger.prototype, "_popup", void 0);
587
- MenuTrigger = _MenuTrigger = __decorate([safeCustomElement(`ixfx-menu-trigger`)], MenuTrigger);
588
- //#endregion
589
- //#region src/menu/menu-item.ts
590
- let MenuItem = class MenuItem extends LitElement {
591
- constructor(..._args) {
592
- super(..._args);
593
- this.command = ``;
594
- this.commandArgs = ``;
595
- this.label = ``;
596
- this.disabled = false;
597
- this.iconName = ``;
598
- this._hasChildren = false;
599
- this._hoverTimeout = null;
600
- }
601
- disconnectedCallback() {
602
- super.disconnectedCallback();
603
- if (this._hoverTimeout) clearTimeout(this._hoverTimeout);
604
- }
605
- _handleSlotChange() {
606
- const assigned = this._slot?.assignedElements() ?? [];
607
- this._hasChildren = assigned.length > 0;
608
- }
609
- _handleMouseEnter() {
610
- if (this.disabled || !this._hasChildren || !this.submenuPopover) return;
611
- if (this._hoverTimeout) clearTimeout(this._hoverTimeout);
612
- this._hoverTimeout = window.setTimeout(() => {
613
- this._showSubmenu();
614
- }, 150);
615
- }
616
- _handleMouseLeave() {
617
- if (this._hoverTimeout) {
618
- clearTimeout(this._hoverTimeout);
619
- this._hoverTimeout = null;
620
- }
621
- this._hideSubmenu();
622
- }
623
- _showSubmenu() {
624
- if (!this.submenuPopover?.popover) return;
625
- try {
626
- this.submenuPopover.showPopover();
627
- } catch (err) {
628
- console.warn(`showPopover failed:`, err);
629
- }
630
- }
631
- _hideSubmenu() {
632
- if (!this.submenuPopover?.popover) return;
633
- try {
634
- this.submenuPopover.hidePopover();
635
- } catch (err) {
636
- console.warn(`hidePopover failed:`, err);
637
- }
638
- }
639
- handleClick() {
640
- if (this.disabled) return;
641
- this.dispatchEvent(new CustomEvent(`command`, {
642
- detail: {
643
- command: this.command,
644
- args: this.commandArgs
645
- },
646
- bubbles: true,
647
- composed: true
648
- }));
649
- }
650
- _resolvedLabel() {
651
- if (this.label) return this.label;
652
- if (this.registry && this.command) return this.registry.get(this.command)?.label ?? ``;
653
- return ``;
654
- }
655
- _resolvedIcon() {
656
- if (this.iconName) return this.iconName;
657
- if (this.registry && this.command) return this.registry.get(this.command)?.icon ?? null;
658
- return null;
659
- }
660
- willUpdate(changedProperties) {
661
- super.willUpdate(changedProperties);
662
- const resolvedIcon = this._resolvedIcon();
663
- if (resolvedIcon && !this.iconName) this.setAttribute(`data-icon-section`, ``);
664
- else if (!resolvedIcon && !this.iconName) this.removeAttribute(`data-icon-section`);
665
- }
666
- updated(changedProperties) {
667
- super.updated(changedProperties);
668
- }
669
- _getShortcut() {
670
- if (!this.keyboardManager || !this.command) return null;
671
- const binding = (this.keyboardManager.getBindings?.() ?? []).find((b) => b.commandId === this.command);
672
- if (!binding) return null;
673
- const mods = Array.from(binding.modifiers ?? [], (m) => ({
674
- ctrl: `⌃`,
675
- shift: `⇧`,
676
- alt: `⌥`,
677
- meta: `⌘`
678
- })[m] ?? m);
679
- const key = binding.key?.length === 1 ? binding.key.toUpperCase() : binding.key;
680
- return [...mods, key].join(``);
681
- }
682
- static {
683
- this.styles = [
684
- themeFallbacks,
685
- tickledItemStyles,
686
- css`
687
- :host {
688
- display: block;
689
- }
690
-
691
- .item {
692
- display: flex !important;
693
- align-items: center;
694
- padding: 6px var(--space-m);
695
- font-size: var(--text-m);
696
- background: var(--item-bg, transparent);
697
- border: var(--item-border, 1px solid transparent);
698
- color: var(--item-text, inherit);
699
- }
700
-
701
- :host(.tickled) .item {
702
- background: var(--item-bg-tickled);
703
- border: var(--item-border-tickled);
704
- color: var(--item-text-tickled);
705
- }
706
-
707
- :host(:focus) {
708
- outline: none;
709
- }
710
-
711
- .item.disabled {
712
- opacity: var(--disabled-opacity);
713
- cursor: default;
714
- }
715
-
716
- .item.disabled:hover {
717
- background: transparent;
718
- }
719
-
720
- .icon-slot {
721
- width: 0;
722
- overflow: hidden;
723
- flex-shrink: 0;
724
- display: inline-flex;
725
- align-items: center;
726
- justify-content: center;
727
- }
728
-
729
- :host([icon-name]) .icon-slot,
730
- :host([data-icon-section]) .icon-slot {
731
- width: 1em;
732
- height: 1em;
733
- margin-right: var(--space-s);
734
- }
735
-
736
- .label {
737
- flex: 1;
738
- }
739
-
740
- .shortcut {
741
- margin-left: auto;
742
- color: var(--surface-muted-text);
743
- font-size: var(--text-s);
744
- }
745
-
746
- .arrow {
747
- color: var(--surface-muted-text);
748
- margin-left: var(--space-m);
749
- }
750
- `
751
- ];
752
- }
753
- render() {
754
- const shortcut = this._getShortcut();
755
- const resolvedIcon = this._resolvedIcon();
756
- return html`
757
- <div
758
- class="item ${this.disabled ? `disabled` : ``}"
759
- @click=${this.handleClick}
760
- @mouseenter=${this._handleMouseEnter}
761
- @mouseleave=${this._handleMouseLeave}
762
- tabindex="${this.disabled ? -1 : 0}"
763
- role="menuitem"
764
- aria-haspopup="${this._hasChildren ? `true` : `false`}"
765
- aria-expanded="${this.submenuPopover?.matches(`:popover-open`) ?? false}"
766
- >
767
- <span class="icon-slot">${resolvedIcon ? html`<ixfx-icon name="${resolvedIcon}"></ixfx-icon>` : null}</span>
768
- <span class="label"><slot></slot>${this._resolvedLabel()}</span>
769
- ${shortcut ? html`<span class="shortcut">${shortcut}</span>` : null}
770
- ${this._hasChildren ? html`<span class="arrow"><ixfx-icon name="chevron-right"></ixfx-icon></span>` : null}
771
- </div>
772
- <slot @slotchange=${this._handleSlotChange}></slot>
773
- `;
774
- }
775
- };
776
- __decorate([property({ type: String })], MenuItem.prototype, "command", void 0);
777
- __decorate([property({
778
- type: String,
779
- attribute: `command-args`
780
- })], MenuItem.prototype, "commandArgs", void 0);
781
- __decorate([property({ type: String })], MenuItem.prototype, "label", void 0);
782
- __decorate([property({ type: Boolean })], MenuItem.prototype, "disabled", void 0);
783
- __decorate([property({
784
- type: String,
785
- attribute: `icon-name`
786
- })], MenuItem.prototype, "iconName", void 0);
787
- __decorate([property({ attribute: false })], MenuItem.prototype, "registry", void 0);
788
- __decorate([property({ attribute: false })], MenuItem.prototype, "keyboardManager", void 0);
789
- __decorate([property({ attribute: false })], MenuItem.prototype, "submenuPopover", void 0);
790
- __decorate([state()], MenuItem.prototype, "_hasChildren", void 0);
791
- __decorate([query(`slot`)], MenuItem.prototype, "_slot", void 0);
792
- MenuItem = __decorate([safeCustomElement(`ixfx-menu-item`)], MenuItem);
793
- //#endregion
794
- //#region src/menu/menu-bar.ts
795
- let MenuBar = class MenuBar extends LitElement {
796
- constructor(..._args) {
797
- super(..._args);
798
- this._openTrigger = null;
799
- this._inputMode = `mouse`;
800
- this._handlePointerMove = () => {
801
- this._setInputMode(`mouse`);
802
- };
803
- this._handleTriggerOpen = (e) => {
804
- this._openTrigger = e.target;
805
- };
806
- this._handleTriggerClose = (e) => {
807
- if (e.target === this._openTrigger) this._openTrigger = null;
808
- };
809
- this._handleFocusIn = (e) => {
810
- const trigger = this._getTriggers().find((t) => t === e.target);
811
- if (!trigger || !this._openTrigger || trigger === this._openTrigger) return;
812
- this._openTrigger.closeMenu();
813
- trigger.openMenu();
814
- };
815
- this._handleKeydown = (e) => {
816
- if (e.key !== `ArrowLeft` && e.key !== `ArrowRight`) return;
817
- const triggers = this._getTriggers();
818
- if (triggers.length === 0) return;
819
- const current = this._openTrigger ?? triggers.find((t) => t === e.target);
820
- if (!current) return;
821
- const idx = triggers.indexOf(current);
822
- if (idx === -1) return;
823
- e.preventDefault();
824
- e.stopPropagation();
825
- this._setInputMode(`keyboard`);
826
- const next = triggers[(idx + (e.key === `ArrowRight` ? 1 : -1) + triggers.length) % triggers.length];
827
- if (this._openTrigger) {
828
- this._openTrigger.closeMenu();
829
- next.openMenu();
830
- } else next.focusTrigger();
831
- };
832
- }
833
- connectedCallback() {
834
- super.connectedCallback();
835
- this.addEventListener(`menu-trigger-open`, this._handleTriggerOpen);
836
- this.addEventListener(`menu-trigger-close`, this._handleTriggerClose);
837
- this.addEventListener(`focusin`, this._handleFocusIn);
838
- this.addEventListener(`keydown`, this._handleKeydown);
839
- this.addEventListener(`pointermove`, this._handlePointerMove);
840
- }
841
- disconnectedCallback() {
842
- super.disconnectedCallback();
843
- this.removeEventListener(`menu-trigger-open`, this._handleTriggerOpen);
844
- this.removeEventListener(`menu-trigger-close`, this._handleTriggerClose);
845
- this.removeEventListener(`focusin`, this._handleFocusIn);
846
- this.removeEventListener(`keydown`, this._handleKeydown);
847
- this.removeEventListener(`pointermove`, this._handlePointerMove);
848
- }
849
- _setInputMode(mode) {
850
- if (this._inputMode === mode) return;
851
- this._inputMode = mode;
852
- this.setAttribute(`data-input-mode`, mode);
853
- }
854
- _getTriggers() {
855
- return [...this.children].filter((el) => el.tagName === `IXFX-MENU-TRIGGER`);
856
- }
857
- static {
858
- this.styles = css`
859
- :host {
860
- display: flex;
861
- align-items: center;
862
- }
863
-
864
- /* Keyboard mode: suppress hover highlight on trigger buttons */
865
- :host([data-input-mode="keyboard"]) {
866
- --_trigger-hover-bg: transparent;
867
- --_trigger-hover-opacity: 0.6;
868
- }
869
- `;
870
- }
871
- render() {
872
- return html`<slot role="menubar"></slot>`;
873
- }
874
- };
875
- MenuBar = __decorate([safeCustomElement(`ixfx-menu-bar`)], MenuBar);
876
- //#endregion
877
- //#region src/menu/menu-check-item.ts
878
- let MenuCheckItem = class MenuCheckItem extends LitElement {
879
- constructor(..._args) {
880
- super(..._args);
881
- this.command = ``;
882
- this.commandArgs = ``;
883
- this.label = ``;
884
- this.checked = false;
885
- this.disabled = false;
886
- this.iconName = ``;
887
- }
888
- handleClick() {
889
- if (this.disabled) return;
890
- this.checked = !this.checked;
891
- this.dispatchEvent(new CustomEvent(`command`, {
892
- detail: {
893
- command: this.command,
894
- checked: this.checked,
895
- args: this.commandArgs
896
- },
897
- bubbles: true,
898
- composed: true
899
- }));
900
- }
901
- _resolvedLabel() {
902
- if (this.label) return this.label;
903
- if (this.registry && this.command) return this.registry.get(this.command)?.label ?? ``;
904
- return ``;
905
- }
906
- _resolvedIcon() {
907
- if (this.iconName) return this.iconName;
908
- if (this.registry && this.command) return this.registry.get(this.command)?.icon ?? null;
909
- return null;
910
- }
911
- willUpdate(changedProperties) {
912
- super.willUpdate(changedProperties);
913
- const resolvedIcon = this._resolvedIcon();
914
- if (resolvedIcon && !this.iconName) this.setAttribute(`data-icon-section`, ``);
915
- else if (!resolvedIcon && !this.iconName) this.removeAttribute(`data-icon-section`);
916
- }
917
- updated(changedProperties) {
918
- super.updated(changedProperties);
919
- }
920
- _getShortcut() {
921
- if (!this.keyboardManager || !this.command) return null;
922
- const binding = (this.keyboardManager.getBindings?.() ?? []).find((b) => b.commandId === this.command);
923
- if (!binding) return null;
924
- const mods = Array.from(binding.modifiers ?? [], (m) => ({
925
- ctrl: `⌃`,
926
- shift: `⇧`,
927
- alt: `⌥`,
928
- meta: `⌘`
929
- })[m] ?? m);
930
- const key = binding.key?.length === 1 ? binding.key.toUpperCase() : binding.key;
931
- return [...mods, key].join(``);
932
- }
933
- static {
934
- this.styles = [
935
- themeFallbacks,
936
- tickledItemStyles,
937
- css`
938
- :host {
939
- display: block;
940
- }
941
-
942
- .item {
943
- display: flex;
944
- padding: 6px var(--space-m);
945
- font-size: var(--text-m);
946
- }
947
-
948
- :host(:focus) {
949
- outline: none;
950
- }
951
-
952
- .item.disabled {
953
- opacity: var(--disabled-opacity);
954
- cursor: default;
955
- }
956
-
957
- .item.disabled:hover {
958
- background: transparent;
959
- }
960
-
961
- .check {
962
- width: 16px;
963
- margin-right: var(--space-s);
964
- font-size: var(--text-m);
965
- color: var(--accent);
966
- }
967
-
968
- .icon-slot {
969
- width: 0;
970
- overflow: hidden;
971
- flex-shrink: 0;
972
- display: inline-flex;
973
- align-items: center;
974
- justify-content: center;
975
- }
976
-
977
- :host([icon-name]) .icon-slot,
978
- :host([data-icon-section]) .icon-slot {
979
- width: 1em;
980
- height: 1em;
981
- margin-right: var(--space-s);
982
- }
983
-
984
- .label {
985
- flex: 1;
986
- }
987
-
988
- .shortcut {
989
- margin-left: var(--space-l);
990
- color: var(--surface-muted-text);
991
- font-size: var(--text-s);
992
- }
993
- `
994
- ];
995
- }
996
- render() {
997
- const shortcut = this._getShortcut();
998
- const resolvedIcon = this._resolvedIcon();
999
- return html`
1000
- <div
1001
- class="item ${this.disabled ? `disabled` : ``}"
1002
- @click=${this.handleClick}
1003
- tabindex="${this.disabled ? -1 : 0}"
1004
- role="menuitemcheckbox"
1005
- aria-checked="${this.checked}"
1006
- >
1007
- <span class="check">${this.checked ? `✓` : ``}</span>
1008
- <span class="icon-slot">${resolvedIcon ? html`<ixfx-icon name="${resolvedIcon}"></ixfx-icon>` : null}</span>
1009
- <span class="label">${this._resolvedLabel()}</span>
1010
- ${shortcut ? html`<span class="shortcut">${shortcut}</span>` : null}
1011
- </div>
1012
- `;
1013
- }
1014
- };
1015
- __decorate([property({ type: String })], MenuCheckItem.prototype, "command", void 0);
1016
- __decorate([property({
1017
- type: String,
1018
- attribute: `command-args`
1019
- })], MenuCheckItem.prototype, "commandArgs", void 0);
1020
- __decorate([property({ type: String })], MenuCheckItem.prototype, "label", void 0);
1021
- __decorate([property({ type: Boolean })], MenuCheckItem.prototype, "checked", void 0);
1022
- __decorate([property({ type: Boolean })], MenuCheckItem.prototype, "disabled", void 0);
1023
- __decorate([property({
1024
- type: String,
1025
- attribute: `icon-name`
1026
- })], MenuCheckItem.prototype, "iconName", void 0);
1027
- __decorate([property({ attribute: false })], MenuCheckItem.prototype, "registry", void 0);
1028
- __decorate([property({ attribute: false })], MenuCheckItem.prototype, "keyboardManager", void 0);
1029
- MenuCheckItem = __decorate([safeCustomElement(`ixfx-menu-check-item`)], MenuCheckItem);
1030
- //#endregion
1031
- //#region src/menu/menu-command-controller.ts
1032
- var MenuCommandController = class {
1033
- constructor(options) {
1034
- this.registry = options.registry;
1035
- this.target = options.target ?? document;
1036
- this.boundHandler = this.handleCommand.bind(this);
1037
- }
1038
- start() {
1039
- this.target.addEventListener(`command`, this.boundHandler);
1040
- }
1041
- stop() {
1042
- this.target.removeEventListener(`command`, this.boundHandler);
1043
- }
1044
- handleCommand(e) {
1045
- const target = e.target;
1046
- const command = target?.command;
1047
- if (!command) return;
1048
- const args = target?.commandArgs ?? ``;
1049
- this.registry.invoke(command, { args });
1050
- }
1051
- };
1052
- //#endregion
1053
- //#region src/menu/menu-sub-trigger.ts
1054
- var _MenuSubTrigger;
1055
- let MenuSubTrigger = class MenuSubTrigger extends LitElement {
1056
- static {
1057
- _MenuSubTrigger = this;
1058
- }
1059
- constructor(..._args) {
1060
- super(..._args);
1061
- this.command = ``;
1062
- this.commandArgs = ``;
1063
- this.label = ``;
1064
- this.disabled = false;
1065
- this.iconName = ``;
1066
- this._hasSubmenu = false;
1067
- this._anchorName = `--mst-${_MenuSubTrigger._count++}`;
1068
- this._hoverTimeout = null;
1069
- this._hideTimeout = null;
1070
- }
1071
- static {
1072
- this._count = 0;
1073
- }
1074
- connectedCallback() {
1075
- super.connectedCallback();
1076
- this.addEventListener(`keydown`, this._handleKeydown);
1077
- }
1078
- firstUpdated() {
1079
- this.style.setProperty(`--anchor-name`, this._anchorName);
1080
- }
1081
- disconnectedCallback() {
1082
- super.disconnectedCallback();
1083
- this.removeEventListener(`keydown`, this._handleKeydown);
1084
- if (this._hoverTimeout) clearTimeout(this._hoverTimeout);
1085
- if (this._hideTimeout) clearTimeout(this._hideTimeout);
1086
- }
1087
- _propagateContext() {
1088
- const items = this._slot?.assignedElements({ flatten: true }) ?? [];
1089
- for (const el of items) {
1090
- if (`registry` in el) el.registry = this.registry;
1091
- if (`keyboardManager` in el) el.keyboardManager = this.keyboardManager;
1092
- }
1093
- }
1094
- updated(changedProperties) {
1095
- if (changedProperties.has(`registry`) || changedProperties.has(`keyboardManager`)) this._propagateContext();
1096
- }
1097
- willUpdate(changedProperties) {
1098
- super.willUpdate(changedProperties);
1099
- const resolvedIcon = this._resolvedIcon();
1100
- if (resolvedIcon && !this.iconName) this.setAttribute(`data-icon-section`, ``);
1101
- else if (!resolvedIcon && !this.iconName) this.removeAttribute(`data-icon-section`);
1102
- }
1103
- _resolvedIcon() {
1104
- if (this.iconName) return this.iconName;
1105
- if (this.registry && this.command) return this.registry.get(this.command)?.icon ?? null;
1106
- return null;
1107
- }
1108
- _handleSlotChange() {
1109
- this._hasSubmenu = this._slot?.assignedElements().length > 0;
1110
- this._propagateContext();
1111
- }
1112
- _showSubmenu() {
1113
- if (!this._submenuPopover?.popover) return;
1114
- try {
1115
- this._submenuPopover.showPopover();
1116
- } catch {}
1117
- }
1118
- _hideSubmenu() {
1119
- if (!this._submenuPopover?.popover) return;
1120
- try {
1121
- this._submenuPopover.hidePopover();
1122
- } catch {}
1123
- }
1124
- _cancelHide() {
1125
- if (this._hideTimeout) {
1126
- clearTimeout(this._hideTimeout);
1127
- this._hideTimeout = null;
1128
- }
1129
- }
1130
- _scheduleHide() {
1131
- this._cancelHide();
1132
- this._hideTimeout = window.setTimeout(() => {
1133
- this._hideSubmenu();
1134
- this._hideTimeout = null;
1135
- }, 100);
1136
- }
1137
- _handleMouseEnter() {
1138
- if (this.disabled || !this._hasSubmenu) return;
1139
- this._cancelHide();
1140
- if (this._hoverTimeout) clearTimeout(this._hoverTimeout);
1141
- this._hoverTimeout = window.setTimeout(() => {
1142
- this._showSubmenu();
1143
- }, 150);
1144
- }
1145
- _handleMouseLeave() {
1146
- if (this._hoverTimeout) {
1147
- clearTimeout(this._hoverTimeout);
1148
- this._hoverTimeout = null;
1149
- }
1150
- this._scheduleHide();
1151
- }
1152
- _handleSubmenuMouseEnter() {
1153
- this._cancelHide();
1154
- }
1155
- _handleSubmenuMouseLeave() {
1156
- this._scheduleHide();
1157
- }
1158
- _handleClick() {
1159
- if (this.disabled || !this.command) return;
1160
- this.dispatchEvent(new CustomEvent(`command`, {
1161
- detail: {
1162
- command: this.command,
1163
- args: this.commandArgs
1164
- },
1165
- bubbles: true,
1166
- composed: true
1167
- }));
1168
- }
1169
- _handleKeydown(e) {
1170
- if (this.disabled) return;
1171
- switch (e.key) {
1172
- case `ArrowRight`:
1173
- case `Enter`:
1174
- case ` `:
1175
- e.preventDefault();
1176
- e.stopPropagation();
1177
- if (this._hasSubmenu) {
1178
- this._showSubmenu();
1179
- requestAnimationFrame(() => {
1180
- (this._slot?.assignedElements({ flatten: true }).find((el) => el.tagName === `IXFX-MENU-CONTAINER`))?.focusFirst();
1181
- });
1182
- }
1183
- break;
1184
- case `ArrowLeft`:
1185
- e.preventDefault();
1186
- e.stopPropagation();
1187
- this._hideSubmenu();
1188
- this._itemEl?.focus();
1189
- break;
1190
- case `Escape`:
1191
- e.preventDefault();
1192
- e.stopPropagation();
1193
- this._hideSubmenu();
1194
- }
1195
- }
1196
- static {
1197
- this.styles = [
1198
- themeFallbacks,
1199
- tickledItemStyles,
1200
- css`
1201
- :host {
1202
- display: block;
1203
- }
1204
-
1205
- .item {
1206
- anchor-name: var(--anchor-name);
1207
- display: flex;
1208
- padding: 6px var(--space-m);
1209
- font-size: var(--text-m);
1210
- }
1211
-
1212
- .submenu-popover {
1213
- position: fixed;
1214
- position-anchor: var(--anchor-name);
1215
- inset: auto;
1216
- top: anchor(top);
1217
- left: anchor(right);
1218
- margin: 0;
1219
- padding: 0;
1220
- border: none;
1221
- background: transparent;
1222
- /* UA stylesheet sets overflow:auto on [popover], which clips the box-shadow
1223
- of the inner ixfx-menu-container child. Override to let the shadow render. */
1224
- overflow: visible;
1225
- position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
1226
- }
1227
-
1228
- :host(:focus) {
1229
- outline: none;
1230
- }
1231
-
1232
- .item.disabled {
1233
- opacity: var(--disabled-opacity);
1234
- cursor: default;
1235
- }
1236
-
1237
- .item.disabled:hover {
1238
- background: transparent;
1239
- }
1240
-
1241
- .icon-slot {
1242
- width: 0;
1243
- overflow: hidden;
1244
- flex-shrink: 0;
1245
- display: inline-flex;
1246
- align-items: center;
1247
- justify-content: center;
1248
- }
1249
-
1250
- :host([icon-name]) .icon-slot,
1251
- :host([data-icon-section]) .icon-slot {
1252
- width: 1em;
1253
- height: 1em;
1254
- margin-right: var(--space-s);
1255
- }
1256
-
1257
- .label {
1258
- flex: 1;
1259
- }
1260
-
1261
- .arrow {
1262
- color: var(--surface-muted-text);
1263
- margin-left: var(--space-m);
1264
- }
1265
- `
1266
- ];
1267
- }
1268
- render() {
1269
- const resolvedIcon = this._resolvedIcon();
1270
- return html`
1271
- <div
1272
- class="item ${this.disabled ? `disabled` : ``}"
1273
- @click=${this._handleClick}
1274
- @mouseenter=${this._handleMouseEnter}
1275
- @mouseleave=${this._handleMouseLeave}
1276
- tabindex="${this.disabled ? -1 : 0}"
1277
- role="menuitem"
1278
- aria-haspopup="true"
1279
- aria-expanded="false"
1280
- >
1281
- <span class="icon-slot">${resolvedIcon ? html`<ixfx-icon name="${resolvedIcon}"></ixfx-icon>` : null}</span>
1282
- <span class="label"><slot></slot>${this.label}</span>
1283
- <span class="arrow">${this._hasSubmenu ? html`<ixfx-icon name="chevron-right"></ixfx-icon>` : nothing}</span>
1284
- </div>
1285
- <div class="submenu-popover" popover="auto"
1286
- @mouseenter=${this._handleSubmenuMouseEnter}
1287
- @mouseleave=${this._handleSubmenuMouseLeave}
1288
- >
1289
- <slot name="submenu" @slotchange=${this._handleSlotChange}></slot>
1290
- </div>
1291
- `;
1292
- }
1293
- };
1294
- __decorate([property({ type: String })], MenuSubTrigger.prototype, "command", void 0);
1295
- __decorate([property({
1296
- type: String,
1297
- attribute: `command-args`
1298
- })], MenuSubTrigger.prototype, "commandArgs", void 0);
1299
- __decorate([property({ type: String })], MenuSubTrigger.prototype, "label", void 0);
1300
- __decorate([property({ type: Boolean })], MenuSubTrigger.prototype, "disabled", void 0);
1301
- __decorate([property({
1302
- type: String,
1303
- attribute: `icon-name`
1304
- })], MenuSubTrigger.prototype, "iconName", void 0);
1305
- __decorate([property({ attribute: false })], MenuSubTrigger.prototype, "registry", void 0);
1306
- __decorate([property({ attribute: false })], MenuSubTrigger.prototype, "keyboardManager", void 0);
1307
- __decorate([state()], MenuSubTrigger.prototype, "_hasSubmenu", void 0);
1308
- __decorate([query(`slot[name="submenu"]`)], MenuSubTrigger.prototype, "_slot", void 0);
1309
- __decorate([query(`.submenu-popover`)], MenuSubTrigger.prototype, "_submenuPopover", void 0);
1310
- __decorate([query(`.item`)], MenuSubTrigger.prototype, "_itemEl", void 0);
1311
- MenuSubTrigger = _MenuSubTrigger = __decorate([safeCustomElement(`ixfx-menu-sub-trigger`)], MenuSubTrigger);
1312
- //#endregion
1313
- //#region src/menu/menu-separator.ts
1314
- let MenuSeparator = class MenuSeparator extends LitElement {
1315
- static {
1316
- this.styles = [themeFallbacks, css`
1317
- :host {
1318
- display: block;
1319
- margin: var(--space-xs) 0;
1320
- }
1321
-
1322
- .separator {
1323
- height: 1px;
1324
- background: var(--surface-4);
1325
- }
1326
- `];
1327
- }
1328
- render() {
1329
- return html`<div class="separator"></div>`;
1330
- }
1331
- };
1332
- MenuSeparator = __decorate([safeCustomElement(`ixfx-menu-separator`)], MenuSeparator);
1333
- //#endregion
1334
- //#region src/menu/menu-header.ts
1335
- let MenuHeader = class MenuHeader extends LitElement {
1336
- constructor(..._args) {
1337
- super(..._args);
1338
- this.label = ``;
1339
- }
1340
- static {
1341
- this.styles = [themeFallbacks, css`
1342
- :host {
1343
- display: block;
1344
- padding: 6px var(--space-m) var(--space-xs);
1345
- font-size: var(--text-s);
1346
- font-weight: var(--font-weight-bold);
1347
- text-transform: uppercase;
1348
- color: var(--surface-muted-text);
1349
- letter-spacing: 0.5px;
1350
- }
1351
- `];
1352
- }
1353
- render() {
1354
- return html`<div>${this.label}</div>`;
1355
- }
1356
- };
1357
- __decorate([property({ type: String })], MenuHeader.prototype, "label", void 0);
1358
- MenuHeader = __decorate([safeCustomElement(`ixfx-menu-header`)], MenuHeader);
1359
- //#endregion
1360
- //#region src/menu/menu-from-tree.ts
1361
- function isLeaf(node) {
1362
- return node.children === void 0 || node.children.length === 0;
1363
- }
1364
- function menuFromTree(nodes, options = {}) {
1365
- const { onCommand, registry, keyboardManager } = options;
1366
- return html`${nodes.map((node) => {
1367
- const label = node.item.label;
1368
- if (label === `-`) return html`<ixfx-menu-separator></ixfx-menu-separator>`;
1369
- if (label.startsWith(`# `)) return html`<ixfx-menu-header label="${label.slice(2)}"></ixfx-menu-header>`;
1370
- if (!isLeaf(node)) {
1371
- const submenu = menuFromTree(node.children, options);
1372
- return html`
1373
- <ixfx-menu-sub-trigger label="${label}">
1374
- <ixfx-menu-container
1375
- slot="submenu"
1376
- .registry=${registry ?? nothing}
1377
- .keyboardManager=${keyboardManager ?? nothing}
1378
- >${submenu}</ixfx-menu-container>
1379
- </ixfx-menu-sub-trigger>
1380
- `;
1381
- }
1382
- return html`
1383
- <ixfx-menu-item
1384
- command="${node.item.key}"
1385
- label="${label}"
1386
- icon-name="${node.item.icon ?? nothing}"
1387
- @command=${onCommand ? (e) => onCommand(e.detail) : nothing}
1388
- ></ixfx-menu-item>
1389
- `;
1390
- })}`;
1391
- }
1392
- function buildMenuItemEl(node, options) {
1393
- const { onCommand, registry } = options;
1394
- const label = node.item.label;
1395
- const el = document.createElement(`ixfx-menu-item`);
1396
- el.setAttribute(`command`, node.item.key);
1397
- el.setAttribute(`label`, label);
1398
- if (node.item.icon) el.setAttribute(`icon-name`, node.item.icon);
1399
- if (onCommand) el.addEventListener(`command`, (e) => onCommand(e.detail));
1400
- if (registry) el.registry = registry;
1401
- return el;
1402
- }
1403
- function buildMenuSeparatorEl() {
1404
- return document.createElement(`ixfx-menu-separator`);
1405
- }
1406
- function buildMenuHeaderEl(label) {
1407
- const el = document.createElement(`ixfx-menu-header`);
1408
- el.setAttribute(`label`, label);
1409
- return el;
1410
- }
1411
- function buildMenuSubTriggerEl(node, options) {
1412
- const { registry, keyboardManager } = options;
1413
- const el = document.createElement(`ixfx-menu-sub-trigger`);
1414
- el.setAttribute(`label`, node.item.label);
1415
- if (registry) el.registry = registry;
1416
- if (keyboardManager) el.keyboardManager = keyboardManager;
1417
- const container = document.createElement(`ixfx-menu-container`);
1418
- container.setAttribute(`slot`, `submenu`);
1419
- if (registry) container.registry = registry;
1420
- if (keyboardManager) container.keyboardManager = keyboardManager;
1421
- const childEls = menuFromTreeToElements(node.children, options);
1422
- for (const child of childEls) container.appendChild(child);
1423
- el.appendChild(container);
1424
- return el;
1425
- }
1426
- function menuFromTreeToElements(nodes, options = {}) {
1427
- return nodes.map((node) => {
1428
- const label = node.item.label;
1429
- if (label === `-`) return buildMenuSeparatorEl();
1430
- if (label.startsWith(`# `)) return buildMenuHeaderEl(label.slice(2));
1431
- if (!isLeaf(node)) return buildMenuSubTriggerEl(node, options);
1432
- return buildMenuItemEl(node, options);
1433
- });
1434
- }
1435
- //#endregion
1436
- //#region src/menu/menu-radio-item.ts
1437
- let MenuRadioItem = class MenuRadioItem extends LitElement {
1438
- constructor(..._args) {
1439
- super(..._args);
1440
- this.value = ``;
1441
- this.label = ``;
1442
- this.disabled = false;
1443
- this.groupValue = ``;
1444
- this.iconName = ``;
1445
- }
1446
- handleClick() {
1447
- if (this.disabled) return;
1448
- this.dispatchEvent(new CustomEvent(`radio-select`, {
1449
- detail: { value: this.value },
1450
- bubbles: true,
1451
- composed: true
1452
- }));
1453
- }
1454
- static {
1455
- this.styles = [
1456
- themeFallbacks,
1457
- tickledItemStyles,
1458
- css`
1459
- :host {
1460
- display: block;
1461
- }
1462
-
1463
- .item {
1464
- display: flex;
1465
- padding: 6px var(--space-m);
1466
- font-size: var(--text-m);
1467
- }
1468
-
1469
- :host(:focus) {
1470
- outline: none;
1471
- }
1472
-
1473
- .item.disabled {
1474
- opacity: var(--disabled-opacity);
1475
- cursor: default;
1476
- }
1477
-
1478
- .item.disabled:hover {
1479
- background: transparent;
1480
- }
1481
-
1482
- .radio {
1483
- width: 16px;
1484
- margin-right: var(--space-s);
1485
- font-size: var(--text-m);
1486
- color: var(--accent);
1487
- }
1488
-
1489
- .icon-slot {
1490
- width: 0;
1491
- overflow: hidden;
1492
- flex-shrink: 0;
1493
- display: inline-flex;
1494
- align-items: center;
1495
- justify-content: center;
1496
- }
1497
-
1498
- :host([icon-name]) .icon-slot,
1499
- :host([data-icon-section]) .icon-slot {
1500
- width: 1em;
1501
- height: 1em;
1502
- margin-right: var(--space-s);
1503
- }
1504
-
1505
- .label {
1506
- flex: 1;
1507
- }
1508
- `
1509
- ];
1510
- }
1511
- render() {
1512
- const isSelected = this.groupValue === this.value;
1513
- return html`
1514
- <div
1515
- class="item ${this.disabled ? `disabled` : ``}"
1516
- @click=${this.handleClick}
1517
- tabindex="${this.disabled ? -1 : 0}"
1518
- role="menuitemradio"
1519
- aria-checked="${isSelected}"
1520
- >
1521
- <span class="radio">${isSelected ? `●` : ``}</span>
1522
- <span class="icon-slot">${this.iconName ? html`<ixfx-icon name="${this.iconName}"></ixfx-icon>` : null}</span>
1523
- <span class="label">${this.label}</span>
1524
- </div>
1525
- `;
1526
- }
1527
- };
1528
- __decorate([property({ type: String })], MenuRadioItem.prototype, "value", void 0);
1529
- __decorate([property({ type: String })], MenuRadioItem.prototype, "label", void 0);
1530
- __decorate([property({ type: Boolean })], MenuRadioItem.prototype, "disabled", void 0);
1531
- __decorate([property({ type: String })], MenuRadioItem.prototype, "groupValue", void 0);
1532
- __decorate([property({
1533
- type: String,
1534
- attribute: `icon-name`
1535
- })], MenuRadioItem.prototype, "iconName", void 0);
1536
- MenuRadioItem = __decorate([safeCustomElement(`ixfx-menu-radio-item`)], MenuRadioItem);
1537
- //#endregion
1538
- //#region src/menu/menu-radio-group.ts
1539
- let MenuRadioGroup = class MenuRadioGroup extends LitElement {
1540
- constructor(..._args) {
1541
- super(..._args);
1542
- this.name = ``;
1543
- this.value = ``;
1544
- this.slottedElements = [];
1545
- }
1546
- handleSlotChange(e) {
1547
- const slot = e.target;
1548
- this.slottedElements = slot.assignedElements();
1549
- this.updateSlottedValues();
1550
- }
1551
- updateSlottedValues() {
1552
- for (const el of this.slottedElements) el.groupValue = this.value;
1553
- }
1554
- handleRadioSelect(e) {
1555
- this.value = e.detail.value;
1556
- this.dispatchEvent(new CustomEvent(`radio-change`, {
1557
- detail: {
1558
- name: this.name,
1559
- value: this.value
1560
- },
1561
- bubbles: true,
1562
- composed: true
1563
- }));
1564
- }
1565
- updated(changedProperties) {
1566
- if (changedProperties.has(`value`)) this.updateSlottedValues();
1567
- }
1568
- static {
1569
- this.styles = css`
1570
- :host {
1571
- display: block;
1572
- }
1573
-
1574
- ::slotted(menu-radio-item) {
1575
- display: block;
1576
- }
1577
- `;
1578
- }
1579
- render() {
1580
- return html`
1581
- <slot @slotchange=${this.handleSlotChange} @radio-select=${this.handleRadioSelect}></slot>
1582
- `;
1583
- }
1584
- };
1585
- __decorate([property({ type: String })], MenuRadioGroup.prototype, "name", void 0);
1586
- __decorate([property({ type: String })], MenuRadioGroup.prototype, "value", void 0);
1587
- __decorate([state()], MenuRadioGroup.prototype, "slottedElements", void 0);
1588
- MenuRadioGroup = __decorate([safeCustomElement(`ixfx-menu-radio-group`)], MenuRadioGroup);
1589
- //#endregion
1590
- //#region src/menu/index.ts
1591
- var menu_exports = /* @__PURE__ */ __exportAll({
1592
- MenuBar: () => MenuBar,
1593
- MenuCheckItem: () => MenuCheckItem,
1594
- MenuCommandController: () => MenuCommandController,
1595
- MenuContainer: () => MenuContainer,
1596
- MenuHeader: () => MenuHeader,
1597
- MenuItem: () => MenuItem,
1598
- MenuRadioGroup: () => MenuRadioGroup,
1599
- MenuRadioItem: () => MenuRadioItem,
1600
- MenuSeparator: () => MenuSeparator,
1601
- MenuSubTrigger: () => MenuSubTrigger,
1602
- MenuTrigger: () => MenuTrigger,
1603
- menuFromTree: () => menuFromTree,
1604
- menuFromTreeToElements: () => menuFromTreeToElements
1605
- });
1606
- //#endregion
1607
- export { menuFromTreeToElements as a, MenuSubTrigger as c, MenuBar as d, MenuItem as f, menuFromTree as i, MenuCommandController as l, MenuContainer as m, MenuRadioGroup as n, MenuHeader as o, MenuTrigger as p, MenuRadioItem as r, MenuSeparator as s, menu_exports as t, MenuCheckItem as u };
1608
-
1609
- //# sourceMappingURL=menu-DIfU70AA.js.map