@aquera/nile-elements 1.7.9 → 1.8.1

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 (111) hide show
  1. package/README.md +6 -0
  2. package/dist/index-6faafdf4.cjs.js +2 -0
  3. package/dist/index-6faafdf4.cjs.js.map +1 -0
  4. package/dist/index-9931b440.esm.js +1 -0
  5. package/dist/index.cjs.js +1 -1
  6. package/dist/index.esm.js +1 -1
  7. package/dist/index.js +630 -435
  8. package/dist/nile-combobox/index.cjs.js +1 -1
  9. package/dist/nile-combobox/index.esm.js +1 -1
  10. package/dist/nile-combobox/nile-combobox.cjs.js +1 -1
  11. package/dist/nile-combobox/nile-combobox.esm.js +1 -1
  12. package/dist/nile-context-menu/index.cjs.js +2 -0
  13. package/dist/nile-context-menu/index.cjs.js.map +1 -0
  14. package/dist/nile-context-menu/index.esm.js +1 -0
  15. package/dist/nile-context-menu/nile-context-menu.cjs.js +2 -0
  16. package/dist/nile-context-menu/nile-context-menu.cjs.js.map +1 -0
  17. package/dist/nile-context-menu/nile-context-menu.css.cjs.js +2 -0
  18. package/dist/nile-context-menu/nile-context-menu.css.cjs.js.map +1 -0
  19. package/dist/nile-context-menu/nile-context-menu.css.esm.js +51 -0
  20. package/dist/nile-context-menu/nile-context-menu.esm.js +25 -0
  21. package/dist/nile-context-menu-group/index.cjs.js +2 -0
  22. package/dist/nile-context-menu-group/index.cjs.js.map +1 -0
  23. package/dist/nile-context-menu-group/index.esm.js +1 -0
  24. package/dist/nile-context-menu-group/nile-context-menu-group.cjs.js +2 -0
  25. package/dist/nile-context-menu-group/nile-context-menu-group.cjs.js.map +1 -0
  26. package/dist/nile-context-menu-group/nile-context-menu-group.css.cjs.js +2 -0
  27. package/dist/nile-context-menu-group/nile-context-menu-group.css.cjs.js.map +1 -0
  28. package/dist/nile-context-menu-group/nile-context-menu-group.css.esm.js +20 -0
  29. package/dist/nile-context-menu-group/nile-context-menu-group.esm.js +11 -0
  30. package/dist/nile-context-menu-item/index.cjs.js +2 -0
  31. package/dist/nile-context-menu-item/index.cjs.js.map +1 -0
  32. package/dist/nile-context-menu-item/index.esm.js +1 -0
  33. package/dist/nile-context-menu-item/nile-context-menu-item.cjs.js +2 -0
  34. package/dist/nile-context-menu-item/nile-context-menu-item.cjs.js.map +1 -0
  35. package/dist/nile-context-menu-item/nile-context-menu-item.css.cjs.js +2 -0
  36. package/dist/nile-context-menu-item/nile-context-menu-item.css.cjs.js.map +1 -0
  37. package/dist/nile-context-menu-item/nile-context-menu-item.css.esm.js +72 -0
  38. package/dist/nile-context-menu-item/nile-context-menu-item.esm.js +20 -0
  39. package/dist/nile-context-submenu/index.cjs.js +2 -0
  40. package/dist/nile-context-submenu/index.cjs.js.map +1 -0
  41. package/dist/nile-context-submenu/index.esm.js +1 -0
  42. package/dist/nile-context-submenu/nile-context-submenu.cjs.js +2 -0
  43. package/dist/nile-context-submenu/nile-context-submenu.cjs.js.map +1 -0
  44. package/dist/nile-context-submenu/nile-context-submenu.esm.js +3 -0
  45. package/dist/nile-detail/index.cjs.js +1 -1
  46. package/dist/nile-detail/index.esm.js +1 -1
  47. package/dist/nile-detail/nile-detail.cjs.js +1 -1
  48. package/dist/nile-detail/nile-detail.esm.js +1 -1
  49. package/dist/nile-floating-panel/nile-floating-panel.cjs.js +1 -1
  50. package/dist/nile-floating-panel/nile-floating-panel.cjs.js.map +1 -1
  51. package/dist/nile-floating-panel/nile-floating-panel.esm.js +1 -1
  52. package/dist/src/index.d.ts +3 -0
  53. package/dist/src/index.js +3 -0
  54. package/dist/src/index.js.map +1 -1
  55. package/dist/src/nile-context-menu/index.d.ts +3 -0
  56. package/dist/src/nile-context-menu/index.js +4 -0
  57. package/dist/src/nile-context-menu/index.js.map +1 -0
  58. package/dist/src/nile-context-menu/nile-context-menu.css.d.ts +10 -0
  59. package/dist/src/nile-context-menu/nile-context-menu.css.js +127 -0
  60. package/dist/src/nile-context-menu/nile-context-menu.css.js.map +1 -0
  61. package/dist/src/nile-context-menu/nile-context-menu.d.ts +132 -0
  62. package/dist/src/nile-context-menu/nile-context-menu.js +705 -0
  63. package/dist/src/nile-context-menu/nile-context-menu.js.map +1 -0
  64. package/dist/src/nile-context-menu-group/index.d.ts +1 -0
  65. package/dist/src/nile-context-menu-group/index.js +2 -0
  66. package/dist/src/nile-context-menu-group/index.js.map +1 -0
  67. package/dist/src/nile-context-menu-group/nile-context-menu-group.css.d.ts +9 -0
  68. package/dist/src/nile-context-menu-group/nile-context-menu-group.css.js +29 -0
  69. package/dist/src/nile-context-menu-group/nile-context-menu-group.css.js.map +1 -0
  70. package/dist/src/nile-context-menu-group/nile-context-menu-group.d.ts +28 -0
  71. package/dist/src/nile-context-menu-group/nile-context-menu-group.js +55 -0
  72. package/dist/src/nile-context-menu-group/nile-context-menu-group.js.map +1 -0
  73. package/dist/src/nile-context-menu-item/index.d.ts +1 -0
  74. package/dist/src/nile-context-menu-item/index.js +2 -0
  75. package/dist/src/nile-context-menu-item/index.js.map +1 -0
  76. package/dist/src/nile-context-menu-item/nile-context-menu-item.css.d.ts +9 -0
  77. package/dist/src/nile-context-menu-item/nile-context-menu-item.css.js +81 -0
  78. package/dist/src/nile-context-menu-item/nile-context-menu-item.css.js.map +1 -0
  79. package/dist/src/nile-context-menu-item/nile-context-menu-item.d.ts +46 -0
  80. package/dist/src/nile-context-menu-item/nile-context-menu-item.js +100 -0
  81. package/dist/src/nile-context-menu-item/nile-context-menu-item.js.map +1 -0
  82. package/dist/src/nile-context-submenu/index.d.ts +1 -0
  83. package/dist/src/nile-context-submenu/index.js +2 -0
  84. package/dist/src/nile-context-submenu/index.js.map +1 -0
  85. package/dist/src/nile-context-submenu/nile-context-submenu.d.ts +69 -0
  86. package/dist/src/nile-context-submenu/nile-context-submenu.js +408 -0
  87. package/dist/src/nile-context-submenu/nile-context-submenu.js.map +1 -0
  88. package/dist/src/nile-floating-panel/nile-floating-panel.d.ts +2 -0
  89. package/dist/src/nile-floating-panel/nile-floating-panel.js +4 -0
  90. package/dist/src/nile-floating-panel/nile-floating-panel.js.map +1 -1
  91. package/dist/src/version.js +1 -1
  92. package/dist/src/version.js.map +1 -1
  93. package/dist/tsconfig.tsbuildinfo +1 -1
  94. package/package.json +1 -1
  95. package/src/index.ts +3 -0
  96. package/src/nile-context-menu/index.ts +12 -0
  97. package/src/nile-context-menu/nile-context-menu.css.ts +130 -0
  98. package/src/nile-context-menu/nile-context-menu.ts +776 -0
  99. package/src/nile-context-menu-group/index.ts +1 -0
  100. package/src/nile-context-menu-group/nile-context-menu-group.css.ts +31 -0
  101. package/src/nile-context-menu-group/nile-context-menu-group.ts +55 -0
  102. package/src/nile-context-menu-item/index.ts +5 -0
  103. package/src/nile-context-menu-item/nile-context-menu-item.css.ts +83 -0
  104. package/src/nile-context-menu-item/nile-context-menu-item.ts +114 -0
  105. package/src/nile-context-submenu/index.ts +1 -0
  106. package/src/nile-context-submenu/nile-context-submenu.ts +407 -0
  107. package/src/nile-floating-panel/nile-floating-panel.ts +5 -0
  108. package/vscode-html-custom-data.json +92 -4
  109. package/dist/index-644974d4.esm.js +0 -1
  110. package/dist/index-dd2af8ec.cjs.js +0 -2
  111. package/dist/index-dd2af8ec.cjs.js.map +0 -1
@@ -0,0 +1,705 @@
1
+ /**
2
+ * Copyright Aquera Inc 2026
3
+ *
4
+ * This source code is licensed under the BSD-3-Clause license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ var NileContextMenu_1;
8
+ import { __decorate } from "tslib";
9
+ import { html } from 'lit';
10
+ import { customElement, property, query, state } from 'lit/decorators.js';
11
+ import { styles } from './nile-context-menu.css';
12
+ import NileElement from '../internal/nile-element';
13
+ import '../nile-floating-panel';
14
+ import '../nile-context-menu-group';
15
+ import '../nile-context-menu-item';
16
+ import '../nile-context-submenu';
17
+ const ITEM_TAG = 'nile-context-menu-item';
18
+ const SUBMENU_TAG = 'nile-context-submenu';
19
+ const MENU_CONTAINER_CLASS = 'nile-context-menu__menu';
20
+ const OBSERVED_ITEM_ATTRS = ['value', 'disabled'];
21
+ let proxyIdSeq = 0;
22
+ /**
23
+ * Nile context-menu component.
24
+ *
25
+ * @tag nile-context-menu
26
+ *
27
+ * @slot Default menu content.
28
+ *
29
+ * @event nile-change Fired on menu lifecycle and selection. `detail.type` is one of
30
+ * `'open'`, `'click'`, or `'close'`; remaining fields depend on the type.
31
+ */
32
+ let NileContextMenu = NileContextMenu_1 = class NileContextMenu extends NileElement {
33
+ constructor() {
34
+ super(...arguments);
35
+ this.for = '';
36
+ this.trigger = 'right';
37
+ this.skipOn = '';
38
+ this.zIndex = 9999;
39
+ this.open = false;
40
+ this.items = [];
41
+ this._items = [];
42
+ this._open = false;
43
+ this._pinned = false;
44
+ this._openContext = null;
45
+ this._targetEl = null;
46
+ this._previouslyFocused = null;
47
+ this._proxyId = `nile-context-menu-anchor-${++proxyIdSeq}`;
48
+ this._wasDataMode = false;
49
+ /** Re-anchor a pinned menu to its target after layout changes. */
50
+ this._onPinnedReposition = () => {
51
+ if (!this._open || !this._pinned || !this._targetEl)
52
+ return;
53
+ const rect = this._targetEl.getBoundingClientRect();
54
+ this._positionProxyAt(rect.left, rect.bottom);
55
+ };
56
+ this._menuContainerRef = null;
57
+ this._onKeydown = (e) => {
58
+ if (!this._open)
59
+ return;
60
+ switch (e.key) {
61
+ case 'Escape':
62
+ e.preventDefault();
63
+ this.close('escape');
64
+ return;
65
+ case 'Tab':
66
+ this.close('programmatic');
67
+ return;
68
+ case 'ArrowDown':
69
+ e.preventDefault();
70
+ this._moveFocus(1);
71
+ return;
72
+ case 'ArrowUp':
73
+ e.preventDefault();
74
+ this._moveFocus(-1);
75
+ return;
76
+ case 'ArrowRight': {
77
+ const { item } = this._getFocusedItemAnyLevel();
78
+ const sub = item?.querySelector(`:scope > ${SUBMENU_TAG}`);
79
+ if (!sub)
80
+ return;
81
+ e.preventDefault();
82
+ sub.openSubmenu?.();
83
+ requestAnimationFrame(() => {
84
+ sub.focusFirstItem?.();
85
+ });
86
+ return;
87
+ }
88
+ case 'ArrowLeft': {
89
+ const { item, container } = this._getFocusedItemAnyLevel();
90
+ if (!item || !container)
91
+ return;
92
+ const sub = this._findSubmenuOwning(container);
93
+ if (!sub)
94
+ return;
95
+ e.preventDefault();
96
+ // Pinned submenus (parent item has `open`) stay open; just move focus.
97
+ if (!sub.parentItem?.open)
98
+ sub.closeSubmenu?.();
99
+ sub.parentItem?.focus();
100
+ return;
101
+ }
102
+ case 'Enter':
103
+ case ' ': {
104
+ e.preventDefault();
105
+ const { item } = this._getFocusedItemAnyLevel();
106
+ if (!item || item.disabled)
107
+ return;
108
+ const sub = item.querySelector(`:scope > ${SUBMENU_TAG}`);
109
+ if (sub) {
110
+ sub.openSubmenu?.();
111
+ requestAnimationFrame(() => {
112
+ const panel = sub.shadowRoot?.querySelector(`.${MENU_CONTAINER_CLASS}`);
113
+ const first = panel
114
+ ? this._enabledItemsIn(panel)[0]
115
+ : undefined;
116
+ first?.focus();
117
+ });
118
+ return;
119
+ }
120
+ this._selectItem(item);
121
+ return;
122
+ }
123
+ }
124
+ };
125
+ this._onScroll = (e) => {
126
+ if (!this._open)
127
+ return;
128
+ if (this._pinned) {
129
+ this._onPinnedReposition();
130
+ return;
131
+ }
132
+ const path = e.composedPath();
133
+ // Scroll inside the root menu container?
134
+ if (this._menuContainerRef && path.includes(this._menuContainerRef))
135
+ return;
136
+ // Scroll inside any descendant submenu's portaled panel?
137
+ for (const node of path) {
138
+ if (node instanceof HTMLElement && node.classList?.contains(MENU_CONTAINER_CLASS)) {
139
+ return;
140
+ }
141
+ }
142
+ this.close('programmatic');
143
+ };
144
+ this._onOutsidePointer = (e) => {
145
+ if (!this._open)
146
+ return;
147
+ const path = e.composedPath();
148
+ if (this._menuContainerRef && path.includes(this._menuContainerRef))
149
+ return;
150
+ for (const node of path) {
151
+ if (node instanceof HTMLElement &&
152
+ node.classList?.contains(MENU_CONTAINER_CLASS)) {
153
+ return;
154
+ }
155
+ }
156
+ this.close('outside-click');
157
+ };
158
+ this._onDescendantSelect = (e) => {
159
+ if (e.detail?.type !== 'click')
160
+ return;
161
+ this.close('select');
162
+ };
163
+ this._onPanelShown = (e) => {
164
+ e.stopPropagation();
165
+ this._openPinnedSubmenus();
166
+ };
167
+ this._onMenuMouseOver = (e) => {
168
+ const item = e
169
+ .composedPath()
170
+ .find(node => node instanceof HTMLElement &&
171
+ node.tagName.toLowerCase() === ITEM_TAG);
172
+ if (!item || item.disabled)
173
+ return;
174
+ item.focus();
175
+ };
176
+ this._onMenuClick = (e) => {
177
+ const item = e
178
+ .composedPath()
179
+ .find(node => node instanceof HTMLElement &&
180
+ node.tagName.toLowerCase() === ITEM_TAG);
181
+ if (!item || item.disabled)
182
+ return;
183
+ if (item.querySelector(`:scope > ${SUBMENU_TAG}`))
184
+ return;
185
+ this._selectItem(item);
186
+ };
187
+ }
188
+ static get styles() {
189
+ return [styles];
190
+ }
191
+ get _isDataMode() {
192
+ return Array.isArray(this.items) && this.items.length > 0;
193
+ }
194
+ connectedCallback() {
195
+ super.connectedCallback();
196
+ this._ensureProxy();
197
+ this._resolveTarget();
198
+ this._attachTriggers();
199
+ this._lightObserver = new MutationObserver(() => {
200
+ if (this._isDataMode)
201
+ return;
202
+ this._relocateLightChildren();
203
+ });
204
+ this._lightObserver.observe(this, { childList: true });
205
+ // Restore a declaratively-opened menu after a disconnect/reconnect.
206
+ if (this.open && !this._open) {
207
+ this.updateComplete.then(() => {
208
+ if (this.isConnected && this.open && !this._open) {
209
+ this._pinned = true;
210
+ this._openAtTarget();
211
+ }
212
+ });
213
+ }
214
+ }
215
+ updated(changed) {
216
+ super.updated(changed);
217
+ if ((changed.has('for') || changed.has('trigger') || changed.has('skipOn')) && this._open) {
218
+ this.close('programmatic');
219
+ }
220
+ if (changed.has('for')) {
221
+ this._resolveTarget();
222
+ this._attachTriggers();
223
+ }
224
+ else if (changed.has('trigger') || changed.has('skipOn')) {
225
+ this._attachTriggers();
226
+ }
227
+ if (changed.has('items') && this._menuContainerRef) {
228
+ if (this._isDataMode) {
229
+ this._renderDataItems();
230
+ this._wasDataMode = true;
231
+ }
232
+ else if (this._wasDataMode) {
233
+ this._renderDataItems();
234
+ this._wasDataMode = false;
235
+ }
236
+ }
237
+ if (changed.has('open')) {
238
+ if (this.open && !this._open) {
239
+ this._pinned = true;
240
+ this._openAtTarget();
241
+ }
242
+ else if (!this.open && this._open) {
243
+ this.close('programmatic');
244
+ }
245
+ }
246
+ }
247
+ /** Open anchored to the `for` target (used when the `open` property is set). */
248
+ _openAtTarget() {
249
+ const rect = this._targetEl?.getBoundingClientRect();
250
+ this.openAt({
251
+ x: rect ? rect.left : 0,
252
+ y: rect ? rect.bottom : 0,
253
+ target: this._targetEl ?? undefined,
254
+ });
255
+ }
256
+ _resolveTarget() {
257
+ const value = this.for?.trim();
258
+ if (!value) {
259
+ this._targetEl = null;
260
+ return;
261
+ }
262
+ const root = this.getRootNode() ?? document;
263
+ if (/^[#.\[:]/.test(value)) {
264
+ this._targetEl = root.querySelector(value);
265
+ }
266
+ else if ('getElementById' in root) {
267
+ this._targetEl = root.getElementById(value);
268
+ }
269
+ else {
270
+ this._targetEl = document.getElementById(value);
271
+ }
272
+ }
273
+ get targetElement() {
274
+ return this._targetEl;
275
+ }
276
+ _attachTriggers() {
277
+ this._detachTriggers?.();
278
+ this._detachTriggers = undefined;
279
+ const trigger = this.trigger;
280
+ if (trigger === 'manual')
281
+ return;
282
+ const cleanups = [];
283
+ if (trigger === 'global') {
284
+ const skipSelector = this.skipOn?.trim() ?? '';
285
+ const isInteractive = (el) => {
286
+ if (!el || !skipSelector)
287
+ return false;
288
+ return !!el.closest(skipSelector);
289
+ };
290
+ const handler = (e) => {
291
+ const me = e;
292
+ if (isInteractive(me.target))
293
+ return;
294
+ me.preventDefault();
295
+ if (this._open)
296
+ return;
297
+ this.openAt({
298
+ x: me.clientX,
299
+ y: me.clientY,
300
+ target: me.target ?? undefined,
301
+ originalEvent: me,
302
+ });
303
+ };
304
+ window.addEventListener('contextmenu', handler, true);
305
+ cleanups.push(() => window.removeEventListener('contextmenu', handler, true));
306
+ this._detachTriggers = () => cleanups.forEach(fn => fn());
307
+ return;
308
+ }
309
+ const target = this._targetEl;
310
+ if (!target)
311
+ return;
312
+ const wantsContext = trigger === 'right' || trigger === 'both';
313
+ const wantsClick = trigger === 'left' || trigger === 'both';
314
+ if (wantsContext) {
315
+ const handler = (e) => {
316
+ const me = e;
317
+ me.preventDefault();
318
+ this.openAt({
319
+ x: me.clientX,
320
+ y: me.clientY,
321
+ target,
322
+ originalEvent: me,
323
+ });
324
+ };
325
+ target.addEventListener('contextmenu', handler);
326
+ cleanups.push(() => target.removeEventListener('contextmenu', handler));
327
+ }
328
+ if (wantsClick) {
329
+ const handler = (e) => {
330
+ const me = e;
331
+ this.openAt({
332
+ x: me.clientX,
333
+ y: me.clientY,
334
+ target,
335
+ originalEvent: me,
336
+ });
337
+ };
338
+ target.addEventListener('click', handler);
339
+ cleanups.push(() => target.removeEventListener('click', handler));
340
+ }
341
+ if (cleanups.length === 0)
342
+ return;
343
+ this._detachTriggers = () => cleanups.forEach(fn => fn());
344
+ }
345
+ disconnectedCallback() {
346
+ super.disconnectedCallback();
347
+ NileContextMenu_1._openInstances.delete(this);
348
+ this._open = false;
349
+ this._detachTriggers?.();
350
+ this._detachTriggers = undefined;
351
+ this._lightObserver?.disconnect();
352
+ this._lightObserver = undefined;
353
+ this._menuObserver?.disconnect();
354
+ this._menuObserver = undefined;
355
+ this._menuContainerRef?.removeEventListener('click', this._onMenuClick);
356
+ this._menuContainerRef?.removeEventListener('mouseover', this._onMenuMouseOver);
357
+ this._menuContainerRef?.removeEventListener('nile-change', this._onDescendantSelect);
358
+ document.removeEventListener('pointerdown', this._onOutsidePointer, true);
359
+ document.removeEventListener('keydown', this._onKeydown, true);
360
+ window.removeEventListener('scroll', this._onScroll, true);
361
+ window.removeEventListener('resize', this._onPinnedReposition);
362
+ this._proxyEl?.remove();
363
+ this._proxyEl = undefined;
364
+ }
365
+ firstUpdated() {
366
+ this._menuContainerRef = this.renderRoot.querySelector('.nile-context-menu__menu');
367
+ if (this._isDataMode) {
368
+ this._renderDataItems();
369
+ this._wasDataMode = true;
370
+ }
371
+ else {
372
+ this._relocateLightChildren();
373
+ }
374
+ if (this._menuContainerRef) {
375
+ this._menuObserver = new MutationObserver(() => this._parseChildren());
376
+ this._menuObserver.observe(this._menuContainerRef, {
377
+ childList: true,
378
+ subtree: true,
379
+ attributes: true,
380
+ attributeFilter: OBSERVED_ITEM_ATTRS,
381
+ });
382
+ this._menuContainerRef.addEventListener('click', this._onMenuClick);
383
+ this._menuContainerRef.addEventListener('mouseover', this._onMenuMouseOver);
384
+ this._menuContainerRef.addEventListener('nile-change', this._onDescendantSelect);
385
+ }
386
+ this._parseChildren();
387
+ }
388
+ _ensureProxy() {
389
+ if (this._proxyEl)
390
+ return;
391
+ const el = document.createElement('div');
392
+ el.id = this._proxyId;
393
+ el.setAttribute('aria-hidden', 'true');
394
+ el.style.cssText =
395
+ 'position: fixed; top: 0; left: 0; width: 0; height: 0; pointer-events: none;';
396
+ document.body.appendChild(el);
397
+ this._proxyEl = el;
398
+ }
399
+ _positionProxyAt(x, y) {
400
+ if (!this._proxyEl)
401
+ return;
402
+ if (this._pinned && this._targetEl) {
403
+ // menu attaches below it pinned menus (or flips cleanly above it)
404
+ const rect = this._targetEl.getBoundingClientRect();
405
+ this._proxyEl.style.position = 'absolute';
406
+ this._proxyEl.style.left = `${rect.left + window.scrollX}px`;
407
+ this._proxyEl.style.top = `${rect.top + window.scrollY}px`;
408
+ this._proxyEl.style.width = `${rect.width}px`;
409
+ this._proxyEl.style.height = `${rect.height}px`;
410
+ return;
411
+ }
412
+ this._proxyEl.style.position = 'fixed';
413
+ this._proxyEl.style.left = `${x}px`;
414
+ this._proxyEl.style.top = `${y}px`;
415
+ this._proxyEl.style.width = '0';
416
+ this._proxyEl.style.height = '0';
417
+ }
418
+ _relocateLightChildren() {
419
+ if (!this._menuContainerRef)
420
+ return;
421
+ const kids = Array.from(this.children);
422
+ if (kids.length === 0)
423
+ return;
424
+ for (const kid of kids) {
425
+ this._menuContainerRef.appendChild(kid);
426
+ }
427
+ }
428
+ _renderDataItems() {
429
+ const container = this._menuContainerRef;
430
+ if (!container)
431
+ return;
432
+ while (container.firstChild)
433
+ container.removeChild(container.firstChild);
434
+ for (const entry of this.items) {
435
+ const node = this._createDataNode(entry);
436
+ if (node)
437
+ container.appendChild(node);
438
+ }
439
+ }
440
+ _createDataNode(entry) {
441
+ if (entry.type === 'group') {
442
+ const group = document.createElement('nile-context-menu-group');
443
+ if (entry.name)
444
+ group.label = entry.name;
445
+ for (const item of entry.data ?? []) {
446
+ group.appendChild(this._createItemNode(item));
447
+ }
448
+ return group;
449
+ }
450
+ return null;
451
+ }
452
+ _createItemNode(data) {
453
+ const item = document.createElement('nile-context-menu-item');
454
+ if (data.id)
455
+ item.id = data.id;
456
+ item.value = data.value ?? data.id ?? '';
457
+ if (data.disabled)
458
+ item.disabled = true;
459
+ if (data.open)
460
+ item.open = true;
461
+ if (data.icon) {
462
+ const glyph = document.createElement('nile-glyph');
463
+ glyph.setAttribute('slot', 'icon');
464
+ glyph.setAttribute('name', data.icon);
465
+ if (data.iconSet)
466
+ glyph.setAttribute('set', data.iconSet);
467
+ if (data.iconSize)
468
+ glyph.setAttribute('size', data.iconSize);
469
+ if (data.iconMethod)
470
+ glyph.setAttribute('method', data.iconMethod);
471
+ if (data.iconColor)
472
+ glyph.setAttribute('color', data.iconColor);
473
+ item.appendChild(glyph);
474
+ }
475
+ item.appendChild(document.createTextNode(data.label));
476
+ if (data.submenu && data.submenu.length > 0) {
477
+ const submenu = document.createElement('nile-context-submenu');
478
+ for (const entry of data.submenu) {
479
+ const node = this._createDataNode(entry);
480
+ if (node)
481
+ submenu.appendChild(node);
482
+ }
483
+ item.appendChild(submenu);
484
+ }
485
+ return item;
486
+ }
487
+ _parseChildren() {
488
+ const root = this._menuContainerRef ?? this;
489
+ const all = Array.from(root.querySelectorAll(ITEM_TAG));
490
+ this._items = all.filter(item => !this._isInsideDescendantSubmenu(item, root));
491
+ }
492
+ _isInsideDescendantSubmenu(item, container) {
493
+ let cur = item.parentElement;
494
+ while (cur && cur !== container) {
495
+ if (cur.tagName.toLowerCase() === SUBMENU_TAG)
496
+ return true;
497
+ cur = cur.parentElement;
498
+ }
499
+ return false;
500
+ }
501
+ get menuItems() {
502
+ return this._items;
503
+ }
504
+ getItemByValue(value) {
505
+ return this._items.find(item => item.value === value);
506
+ }
507
+ get isOpen() {
508
+ return this._open;
509
+ }
510
+ openAt(options) {
511
+ if (this._open)
512
+ return;
513
+ for (const other of NileContextMenu_1._openInstances) {
514
+ if (other !== this)
515
+ other.close('programmatic');
516
+ }
517
+ this._previouslyFocused = document.activeElement;
518
+ this._openContext = {
519
+ x: options.x,
520
+ y: options.y,
521
+ target: options.target ?? null,
522
+ originalEvent: options.originalEvent ?? null,
523
+ };
524
+ this._ensureProxy();
525
+ this._positionProxyAt(options.x, options.y);
526
+ this._open = true;
527
+ this.open = true;
528
+ NileContextMenu_1._openInstances.add(this);
529
+ document.addEventListener('pointerdown', this._onOutsidePointer, true);
530
+ document.addEventListener('keydown', this._onKeydown, true);
531
+ if (this._pinned)
532
+ window.addEventListener('resize', this._onPinnedReposition);
533
+ requestAnimationFrame(() => {
534
+ if (this._open)
535
+ window.addEventListener('scroll', this._onScroll, true);
536
+ requestAnimationFrame(() => this._openPinnedSubmenus());
537
+ });
538
+ this.emit('nile-change', { type: 'open', ...this._openContext });
539
+ }
540
+ close(reason = 'programmatic') {
541
+ if (!this._open)
542
+ return;
543
+ this._open = false;
544
+ this.open = false;
545
+ this._pinned = false;
546
+ this._openContext = null;
547
+ NileContextMenu_1._openInstances.delete(this);
548
+ const subs = this._menuContainerRef?.querySelectorAll(SUBMENU_TAG);
549
+ subs?.forEach(s => s.closeSubmenu?.());
550
+ document.removeEventListener('pointerdown', this._onOutsidePointer, true);
551
+ document.removeEventListener('keydown', this._onKeydown, true);
552
+ window.removeEventListener('scroll', this._onScroll, true);
553
+ window.removeEventListener('resize', this._onPinnedReposition);
554
+ const active = document.activeElement;
555
+ const focusInMenu = active === this ||
556
+ this._items.some(item => item === active || item.shadowRoot?.contains(active));
557
+ if (focusInMenu) {
558
+ this._previouslyFocused?.focus?.();
559
+ }
560
+ this._previouslyFocused = null;
561
+ this.emit('nile-change', { type: 'close', reason });
562
+ }
563
+ _enabledItems() {
564
+ return this._items.filter(item => !item.disabled);
565
+ }
566
+ _getFocusedItem() {
567
+ const active = document.activeElement;
568
+ for (const item of this._items) {
569
+ if (item === active || item.shadowRoot?.contains(active))
570
+ return item;
571
+ }
572
+ return null;
573
+ }
574
+ _getFocusedItemAnyLevel() {
575
+ let node = document.activeElement;
576
+ while (node && node.tagName?.toLowerCase() !== ITEM_TAG) {
577
+ const root = node.getRootNode();
578
+ node = root instanceof ShadowRoot ? root.host : node.parentElement;
579
+ }
580
+ if (!node)
581
+ return { item: null, container: null };
582
+ const container = node.closest(`.${MENU_CONTAINER_CLASS}`);
583
+ return { item: node, container };
584
+ }
585
+ _enabledItemsIn(container) {
586
+ const all = Array.from(container.querySelectorAll(ITEM_TAG));
587
+ return all.filter(item => {
588
+ if (item.disabled)
589
+ return false;
590
+ return !this._isInsideDescendantSubmenu(item, container);
591
+ });
592
+ }
593
+ _focusFirstEnabled() {
594
+ const enabled = this._enabledItems();
595
+ enabled[0]?.focus();
596
+ }
597
+ _moveFocus(delta) {
598
+ const { item: focused, container } = this._getFocusedItemAnyLevel();
599
+ const root = container ?? this._menuContainerRef ?? this;
600
+ const enabled = this._enabledItemsIn(root);
601
+ if (enabled.length === 0)
602
+ return;
603
+ const current = focused ? enabled.indexOf(focused) : -1;
604
+ const next = current + delta;
605
+ const wrapped = ((next % enabled.length) + enabled.length) % enabled.length;
606
+ enabled[wrapped].focus();
607
+ }
608
+ _findSubmenuOwning(container) {
609
+ const customElements = window.customElements;
610
+ const ctor = customElements.get(SUBMENU_TAG);
611
+ return ctor?.findByContainer?.(container) ?? null;
612
+ }
613
+ _stop(e) {
614
+ e.stopPropagation();
615
+ }
616
+ /** Open the submenu of every top-level item carrying the `open` attribute. */
617
+ _openPinnedSubmenus() {
618
+ if (!this._open || !this._menuContainerRef)
619
+ return;
620
+ for (const item of this._items) {
621
+ if (!item.open || item.disabled)
622
+ continue;
623
+ const sub = item.querySelector(`:scope > ${SUBMENU_TAG}`);
624
+ sub?.openSubmenu?.();
625
+ }
626
+ }
627
+ _selectItem(item) {
628
+ const detail = {
629
+ id: item.id,
630
+ value: item.value,
631
+ name: (item.textContent ?? '').trim(),
632
+ target: this._openContext?.target ?? null,
633
+ originalEvent: this._openContext?.originalEvent ?? null,
634
+ };
635
+ try {
636
+ item.onSelect?.(detail);
637
+ }
638
+ catch (err) {
639
+ console.error('[nile-context-menu] onSelect callback threw:', err);
640
+ }
641
+ this.emit('nile-change', { type: 'click', ...detail });
642
+ this.close('select');
643
+ }
644
+ render() {
645
+ return html `
646
+ <nile-floating-panel
647
+ for=${this._proxyId}
648
+ trigger="manual"
649
+ placement="bottom-start"
650
+ panelClass="nile-context-menu-panel"
651
+ .zIndex=${this.zIndex}
652
+ ?open=${this._open}
653
+ .interactive=${true}
654
+ .hideOnClick=${false}
655
+ .closeOnEscape=${false}
656
+ .arrow=${'none'}
657
+ .distance=${0}
658
+ @nile-init=${this._stop}
659
+ @nile-destroy=${this._stop}
660
+ @nile-show=${this._stop}
661
+ @nile-hide=${this._stop}
662
+ @nile-after-show=${this._onPanelShown}
663
+ @nile-after-hide=${this._stop}
664
+ @nile-toggle=${this._stop}
665
+ @nile-visibility-change=${this._stop}
666
+ >
667
+ <div class="nile-context-menu__menu" role="menu"></div>
668
+ </nile-floating-panel>
669
+ `;
670
+ }
671
+ };
672
+ NileContextMenu._openInstances = new Set();
673
+ __decorate([
674
+ property({ attribute: true, type: String, reflect: true })
675
+ ], NileContextMenu.prototype, "for", void 0);
676
+ __decorate([
677
+ property({ attribute: true, type: String, reflect: true })
678
+ ], NileContextMenu.prototype, "trigger", void 0);
679
+ __decorate([
680
+ property({ attribute: true, type: String, reflect: true })
681
+ ], NileContextMenu.prototype, "skipOn", void 0);
682
+ __decorate([
683
+ property({ attribute: true, type: Number, reflect: true })
684
+ ], NileContextMenu.prototype, "zIndex", void 0);
685
+ __decorate([
686
+ property({ attribute: true, type: Boolean, reflect: true })
687
+ ], NileContextMenu.prototype, "open", void 0);
688
+ __decorate([
689
+ property({ attribute: false })
690
+ ], NileContextMenu.prototype, "items", void 0);
691
+ __decorate([
692
+ query('nile-floating-panel')
693
+ ], NileContextMenu.prototype, "_floatingPanel", void 0);
694
+ __decorate([
695
+ state()
696
+ ], NileContextMenu.prototype, "_items", void 0);
697
+ __decorate([
698
+ state()
699
+ ], NileContextMenu.prototype, "_open", void 0);
700
+ NileContextMenu = NileContextMenu_1 = __decorate([
701
+ customElement('nile-context-menu')
702
+ ], NileContextMenu);
703
+ export { NileContextMenu };
704
+ export default NileContextMenu;
705
+ //# sourceMappingURL=nile-context-menu.js.map