@aquera/nile-elements 1.8.0 → 1.8.2
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.
- package/README.md +6 -0
- package/dist/index-6faafdf4.cjs.js +2 -0
- package/dist/index-6faafdf4.cjs.js.map +1 -0
- package/dist/index-9931b440.esm.js +1 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +375 -226
- package/dist/nile-combobox/index.cjs.js +1 -1
- package/dist/nile-combobox/index.esm.js +1 -1
- package/dist/nile-combobox/nile-combobox.cjs.js +1 -1
- package/dist/nile-combobox/nile-combobox.esm.js +1 -1
- package/dist/nile-context-menu/nile-context-menu.cjs.js +1 -1
- package/dist/nile-context-menu/nile-context-menu.cjs.js.map +1 -1
- package/dist/nile-context-menu/nile-context-menu.esm.js +2 -2
- package/dist/nile-context-menu-item/nile-context-menu-item.cjs.js +1 -1
- package/dist/nile-context-menu-item/nile-context-menu-item.cjs.js.map +1 -1
- package/dist/nile-context-menu-item/nile-context-menu-item.esm.js +3 -3
- package/dist/nile-context-submenu/nile-context-submenu.cjs.js +1 -1
- package/dist/nile-context-submenu/nile-context-submenu.cjs.js.map +1 -1
- package/dist/nile-context-submenu/nile-context-submenu.esm.js +2 -2
- package/dist/nile-detail/index.cjs.js +1 -1
- package/dist/nile-detail/index.esm.js +1 -1
- package/dist/nile-detail/nile-detail.cjs.js +1 -1
- package/dist/nile-detail/nile-detail.esm.js +1 -1
- package/dist/nile-floating-panel/nile-floating-panel.cjs.js +1 -1
- package/dist/nile-floating-panel/nile-floating-panel.cjs.js.map +1 -1
- package/dist/nile-floating-panel/nile-floating-panel.esm.js +1 -1
- package/dist/nile-slider/nile-slider.cjs.js +1 -1
- package/dist/nile-slider/nile-slider.cjs.js.map +1 -1
- package/dist/nile-slider/nile-slider.css.cjs.js +1 -1
- package/dist/nile-slider/nile-slider.css.cjs.js.map +1 -1
- package/dist/nile-slider/nile-slider.css.esm.js +162 -41
- package/dist/nile-slider/nile-slider.esm.js +5 -3
- package/dist/nile-slider/nile-slider.template.cjs.js +1 -1
- package/dist/nile-slider/nile-slider.template.cjs.js.map +1 -1
- package/dist/nile-slider/nile-slider.template.esm.js +49 -23
- package/dist/nile-slider/utils/nile-slider.utils.cjs.js +1 -1
- package/dist/nile-slider/utils/nile-slider.utils.cjs.js.map +1 -1
- package/dist/nile-slider/utils/nile-slider.utils.esm.js +1 -1
- package/dist/src/nile-context-menu/nile-context-menu.d.ts +10 -1
- package/dist/src/nile-context-menu/nile-context-menu.js +85 -5
- package/dist/src/nile-context-menu/nile-context-menu.js.map +1 -1
- package/dist/src/nile-context-menu-item/nile-context-menu-item.d.ts +1 -0
- package/dist/src/nile-context-menu-item/nile-context-menu-item.js +5 -1
- package/dist/src/nile-context-menu-item/nile-context-menu-item.js.map +1 -1
- package/dist/src/nile-context-submenu/nile-context-submenu.d.ts +9 -0
- package/dist/src/nile-context-submenu/nile-context-submenu.js +100 -16
- package/dist/src/nile-context-submenu/nile-context-submenu.js.map +1 -1
- package/dist/src/nile-floating-panel/nile-floating-panel.d.ts +2 -0
- package/dist/src/nile-floating-panel/nile-floating-panel.js +4 -0
- package/dist/src/nile-floating-panel/nile-floating-panel.js.map +1 -1
- package/dist/src/nile-slider/nile-slider.css.js +160 -39
- package/dist/src/nile-slider/nile-slider.css.js.map +1 -1
- package/dist/src/nile-slider/nile-slider.d.ts +5 -1
- package/dist/src/nile-slider/nile-slider.js +23 -1
- package/dist/src/nile-slider/nile-slider.js.map +1 -1
- package/dist/src/nile-slider/nile-slider.template.d.ts +2 -0
- package/dist/src/nile-slider/nile-slider.template.js +38 -1
- package/dist/src/nile-slider/nile-slider.template.js.map +1 -1
- package/dist/src/nile-slider/types/nile-slider.types.d.ts +1 -0
- package/dist/src/nile-slider/types/nile-slider.types.js.map +1 -1
- package/dist/src/nile-slider/utils/nile-slider.utils.js +4 -0
- package/dist/src/nile-slider/utils/nile-slider.utils.js.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/nile-context-menu/nile-context-menu.ts +86 -8
- package/src/nile-context-menu-item/nile-context-menu-item.ts +3 -1
- package/src/nile-context-submenu/nile-context-submenu.ts +97 -12
- package/src/nile-floating-panel/nile-floating-panel.ts +5 -0
- package/src/nile-slider/nile-slider.css.ts +160 -39
- package/src/nile-slider/nile-slider.template.ts +45 -2
- package/src/nile-slider/nile-slider.ts +11 -3
- package/src/nile-slider/types/nile-slider.types.ts +2 -0
- package/src/nile-slider/utils/nile-slider.utils.ts +2 -0
- package/vscode-html-custom-data.json +51 -12
- package/dist/index-644974d4.esm.js +0 -1
- package/dist/index-dd2af8ec.cjs.js +0 -2
- package/dist/index-dd2af8ec.cjs.js.map +0 -1
package/package.json
CHANGED
|
@@ -30,6 +30,7 @@ export interface NileContextMenuItemData {
|
|
|
30
30
|
iconMethod?: 'fill' | 'stroke' | string;
|
|
31
31
|
iconColor?: string;
|
|
32
32
|
submenu?: NileContextMenuData[];
|
|
33
|
+
open?: boolean;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export interface NileContextMenuGroupData {
|
|
@@ -93,7 +94,9 @@ export class NileContextMenu extends NileElement {
|
|
|
93
94
|
@property({ attribute: true, type: String, reflect: true }) skipOn = '';
|
|
94
95
|
|
|
95
96
|
@property({ attribute: true, type: Number, reflect: true }) zIndex = 9999;
|
|
96
|
-
|
|
97
|
+
|
|
98
|
+
@property({ attribute: true, type: Boolean, reflect: true }) open = false;
|
|
99
|
+
|
|
97
100
|
@property({ attribute: false }) public items: NileContextMenuData[] = [];
|
|
98
101
|
|
|
99
102
|
private get _isDataMode(): boolean {
|
|
@@ -102,9 +105,11 @@ export class NileContextMenu extends NileElement {
|
|
|
102
105
|
@query('nile-floating-panel') private _floatingPanel?: HTMLElement;
|
|
103
106
|
|
|
104
107
|
@state() private _items: NileContextMenuItem[] = [];
|
|
105
|
-
|
|
108
|
+
|
|
106
109
|
@state() private _open = false;
|
|
107
|
-
|
|
110
|
+
|
|
111
|
+
private _pinned = false;
|
|
112
|
+
|
|
108
113
|
protected _openContext: NileContextMenuOpenContext | null = null;
|
|
109
114
|
|
|
110
115
|
protected _targetEl: Element | null = null;
|
|
@@ -132,6 +137,15 @@ export class NileContextMenu extends NileElement {
|
|
|
132
137
|
this._relocateLightChildren();
|
|
133
138
|
});
|
|
134
139
|
this._lightObserver.observe(this, { childList: true });
|
|
140
|
+
// Restore a declaratively-opened menu after a disconnect/reconnect.
|
|
141
|
+
if (this.open && !this._open) {
|
|
142
|
+
this.updateComplete.then(() => {
|
|
143
|
+
if (this.isConnected && this.open && !this._open) {
|
|
144
|
+
this._pinned = true;
|
|
145
|
+
this._openAtTarget();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
protected override updated(changed: PropertyValues): void {
|
|
@@ -154,8 +168,33 @@ export class NileContextMenu extends NileElement {
|
|
|
154
168
|
this._wasDataMode = false;
|
|
155
169
|
}
|
|
156
170
|
}
|
|
171
|
+
if (changed.has('open')) {
|
|
172
|
+
if (this.open && !this._open) {
|
|
173
|
+
this._pinned = true;
|
|
174
|
+
this._openAtTarget();
|
|
175
|
+
} else if (!this.open && this._open) {
|
|
176
|
+
this.close('programmatic');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
157
179
|
}
|
|
158
180
|
|
|
181
|
+
/** Open anchored to the `for` target (used when the `open` property is set). */
|
|
182
|
+
private _openAtTarget(): void {
|
|
183
|
+
const rect = this._targetEl?.getBoundingClientRect();
|
|
184
|
+
this.openAt({
|
|
185
|
+
x: rect ? rect.left : 0,
|
|
186
|
+
y: rect ? rect.bottom : 0,
|
|
187
|
+
target: this._targetEl ?? undefined,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Re-anchor a pinned menu to its target after layout changes. */
|
|
192
|
+
private _onPinnedReposition = (): void => {
|
|
193
|
+
if (!this._open || !this._pinned || !this._targetEl) return;
|
|
194
|
+
const rect = this._targetEl.getBoundingClientRect();
|
|
195
|
+
this._positionProxyAt(rect.left, rect.bottom);
|
|
196
|
+
};
|
|
197
|
+
|
|
159
198
|
private _resolveTarget(): void {
|
|
160
199
|
const value = this.for?.trim();
|
|
161
200
|
if (!value) {
|
|
@@ -196,7 +235,7 @@ export class NileContextMenu extends NileElement {
|
|
|
196
235
|
if (isInteractive(me.target as Element | null)) return;
|
|
197
236
|
me.preventDefault();
|
|
198
237
|
if (this._open) return;
|
|
199
|
-
this.
|
|
238
|
+
this.openAt({
|
|
200
239
|
x: me.clientX,
|
|
201
240
|
y: me.clientY,
|
|
202
241
|
target: (me.target as Element | null) ?? undefined,
|
|
@@ -219,7 +258,7 @@ export class NileContextMenu extends NileElement {
|
|
|
219
258
|
const handler = (e: Event) => {
|
|
220
259
|
const me = e as MouseEvent;
|
|
221
260
|
me.preventDefault();
|
|
222
|
-
this.
|
|
261
|
+
this.openAt({
|
|
223
262
|
x: me.clientX,
|
|
224
263
|
y: me.clientY,
|
|
225
264
|
target,
|
|
@@ -233,7 +272,7 @@ export class NileContextMenu extends NileElement {
|
|
|
233
272
|
if (wantsClick) {
|
|
234
273
|
const handler = (e: Event) => {
|
|
235
274
|
const me = e as MouseEvent;
|
|
236
|
-
this.
|
|
275
|
+
this.openAt({
|
|
237
276
|
x: me.clientX,
|
|
238
277
|
y: me.clientY,
|
|
239
278
|
target,
|
|
@@ -267,6 +306,7 @@ export class NileContextMenu extends NileElement {
|
|
|
267
306
|
document.removeEventListener('pointerdown', this._onOutsidePointer, true);
|
|
268
307
|
document.removeEventListener('keydown', this._onKeydown, true);
|
|
269
308
|
window.removeEventListener('scroll', this._onScroll, true);
|
|
309
|
+
window.removeEventListener('resize', this._onPinnedReposition);
|
|
270
310
|
this._proxyEl?.remove();
|
|
271
311
|
this._proxyEl = undefined;
|
|
272
312
|
}
|
|
@@ -312,8 +352,21 @@ export class NileContextMenu extends NileElement {
|
|
|
312
352
|
|
|
313
353
|
private _positionProxyAt(x: number, y: number): void {
|
|
314
354
|
if (!this._proxyEl) return;
|
|
355
|
+
if (this._pinned && this._targetEl) {
|
|
356
|
+
// menu attaches below it pinned menus (or flips cleanly above it)
|
|
357
|
+
const rect = this._targetEl.getBoundingClientRect();
|
|
358
|
+
this._proxyEl.style.position = 'absolute';
|
|
359
|
+
this._proxyEl.style.left = `${rect.left + window.scrollX}px`;
|
|
360
|
+
this._proxyEl.style.top = `${rect.top + window.scrollY}px`;
|
|
361
|
+
this._proxyEl.style.width = `${rect.width}px`;
|
|
362
|
+
this._proxyEl.style.height = `${rect.height}px`;
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
this._proxyEl.style.position = 'fixed';
|
|
315
366
|
this._proxyEl.style.left = `${x}px`;
|
|
316
367
|
this._proxyEl.style.top = `${y}px`;
|
|
368
|
+
this._proxyEl.style.width = '0';
|
|
369
|
+
this._proxyEl.style.height = '0';
|
|
317
370
|
}
|
|
318
371
|
|
|
319
372
|
private _relocateLightChildren(): void {
|
|
@@ -352,6 +405,7 @@ export class NileContextMenu extends NileElement {
|
|
|
352
405
|
if (data.id) item.id = data.id;
|
|
353
406
|
item.value = data.value ?? data.id ?? '';
|
|
354
407
|
if (data.disabled) item.disabled = true;
|
|
408
|
+
if (data.open) item.open = true;
|
|
355
409
|
if (data.icon) {
|
|
356
410
|
const glyph = document.createElement('nile-glyph');
|
|
357
411
|
glyph.setAttribute('slot', 'icon');
|
|
@@ -401,7 +455,7 @@ export class NileContextMenu extends NileElement {
|
|
|
401
455
|
return this._open;
|
|
402
456
|
}
|
|
403
457
|
|
|
404
|
-
public
|
|
458
|
+
public openAt(options: NileContextMenuOpenOptions): void {
|
|
405
459
|
if (this._open) return;
|
|
406
460
|
for (const other of NileContextMenu._openInstances) {
|
|
407
461
|
if (other !== this) other.close('programmatic');
|
|
@@ -416,11 +470,14 @@ export class NileContextMenu extends NileElement {
|
|
|
416
470
|
this._ensureProxy();
|
|
417
471
|
this._positionProxyAt(options.x, options.y);
|
|
418
472
|
this._open = true;
|
|
473
|
+
this.open = true;
|
|
419
474
|
NileContextMenu._openInstances.add(this);
|
|
420
475
|
document.addEventListener('pointerdown', this._onOutsidePointer, true);
|
|
421
476
|
document.addEventListener('keydown', this._onKeydown, true);
|
|
477
|
+
if (this._pinned) window.addEventListener('resize', this._onPinnedReposition);
|
|
422
478
|
requestAnimationFrame(() => {
|
|
423
479
|
if (this._open) window.addEventListener('scroll', this._onScroll, true);
|
|
480
|
+
requestAnimationFrame(() => this._openPinnedSubmenus());
|
|
424
481
|
});
|
|
425
482
|
this.emit('nile-change', { type: 'open', ...this._openContext });
|
|
426
483
|
}
|
|
@@ -428,6 +485,8 @@ export class NileContextMenu extends NileElement {
|
|
|
428
485
|
public close(reason: NileContextMenuCloseReason = 'programmatic'): void {
|
|
429
486
|
if (!this._open) return;
|
|
430
487
|
this._open = false;
|
|
488
|
+
this.open = false;
|
|
489
|
+
this._pinned = false;
|
|
431
490
|
this._openContext = null;
|
|
432
491
|
NileContextMenu._openInstances.delete(this);
|
|
433
492
|
const subs = this._menuContainerRef?.querySelectorAll(SUBMENU_TAG);
|
|
@@ -435,6 +494,7 @@ export class NileContextMenu extends NileElement {
|
|
|
435
494
|
document.removeEventListener('pointerdown', this._onOutsidePointer, true);
|
|
436
495
|
document.removeEventListener('keydown', this._onKeydown, true);
|
|
437
496
|
window.removeEventListener('scroll', this._onScroll, true);
|
|
497
|
+
window.removeEventListener('resize', this._onPinnedReposition);
|
|
438
498
|
const active = document.activeElement;
|
|
439
499
|
const focusInMenu =
|
|
440
500
|
active === this ||
|
|
@@ -533,7 +593,8 @@ export class NileContextMenu extends NileElement {
|
|
|
533
593
|
const sub = this._findSubmenuOwning(container);
|
|
534
594
|
if (!sub) return;
|
|
535
595
|
e.preventDefault();
|
|
536
|
-
|
|
596
|
+
// Pinned submenus (parent item has `open`) stay open; just move focus.
|
|
597
|
+
if (!sub.parentItem?.open) sub.closeSubmenu?.();
|
|
537
598
|
sub.parentItem?.focus();
|
|
538
599
|
return;
|
|
539
600
|
}
|
|
@@ -578,6 +639,10 @@ export class NileContextMenu extends NileElement {
|
|
|
578
639
|
|
|
579
640
|
private _onScroll = (e: Event): void => {
|
|
580
641
|
if (!this._open) return;
|
|
642
|
+
if (this._pinned) {
|
|
643
|
+
this._onPinnedReposition();
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
581
646
|
const path = e.composedPath();
|
|
582
647
|
// Scroll inside the root menu container?
|
|
583
648
|
if (this._menuContainerRef && path.includes(this._menuContainerRef)) return;
|
|
@@ -615,8 +680,21 @@ export class NileContextMenu extends NileElement {
|
|
|
615
680
|
|
|
616
681
|
private _onPanelShown = (e: Event): void => {
|
|
617
682
|
e.stopPropagation();
|
|
683
|
+
this._openPinnedSubmenus();
|
|
618
684
|
};
|
|
619
685
|
|
|
686
|
+
/** Open the submenu of every top-level item carrying the `open` attribute. */
|
|
687
|
+
private _openPinnedSubmenus(): void {
|
|
688
|
+
if (!this._open || !this._menuContainerRef) return;
|
|
689
|
+
for (const item of this._items) {
|
|
690
|
+
if (!item.open || item.disabled) continue;
|
|
691
|
+
const sub = item.querySelector(`:scope > ${SUBMENU_TAG}`) as
|
|
692
|
+
| (HTMLElement & { openSubmenu?: () => void })
|
|
693
|
+
| null;
|
|
694
|
+
sub?.openSubmenu?.();
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
620
698
|
private _onMenuMouseOver = (e: MouseEvent): void => {
|
|
621
699
|
const item = e
|
|
622
700
|
.composedPath()
|
|
@@ -41,6 +41,8 @@ export class NileContextMenuItem extends NileElement {
|
|
|
41
41
|
|
|
42
42
|
@property({ attribute: true, type: Boolean, reflect: true }) disabled = false;
|
|
43
43
|
|
|
44
|
+
@property({ attribute: true, type: Boolean, reflect: true }) open = false;
|
|
45
|
+
|
|
44
46
|
public onSelect?: NileContextMenuItemSelectHandler;
|
|
45
47
|
|
|
46
48
|
@state() private _hasSubmenu = false;
|
|
@@ -95,7 +97,7 @@ export class NileContextMenuItem extends NileElement {
|
|
|
95
97
|
<span part="label" class="label"><slot @slotchange=${this._onSlotChange}></slot></span>
|
|
96
98
|
${this._hasSubmenu
|
|
97
99
|
? html`<span part="chevron" class="chevron" aria-hidden="true">
|
|
98
|
-
<nile-glyph name="ng-chevron-right" size="16"></nile-glyph>
|
|
100
|
+
<nile-glyph name="ng-chevron-right" size="16" method="stroke"></nile-glyph>
|
|
99
101
|
</span>`
|
|
100
102
|
: ''}
|
|
101
103
|
</div>
|
|
@@ -47,6 +47,11 @@ export class NileContextSubmenu extends NileElement {
|
|
|
47
47
|
private _openTimer?: number;
|
|
48
48
|
private _closeTimer?: number;
|
|
49
49
|
private _setupDone = false;
|
|
50
|
+
private _parentObserver?: MutationObserver;
|
|
51
|
+
private _repositionQueued = false;
|
|
52
|
+
private get _pinnedOpen(): boolean {
|
|
53
|
+
return !!(this._parentItem?.open && !this._parentItem.disabled);
|
|
54
|
+
}
|
|
50
55
|
|
|
51
56
|
public override connectedCallback(): void {
|
|
52
57
|
super.connectedCallback();
|
|
@@ -59,10 +64,27 @@ export class NileContextSubmenu extends NileElement {
|
|
|
59
64
|
this._parentItem.addEventListener('mouseenter', this._onParentEnter);
|
|
60
65
|
this._parentItem.addEventListener('mouseleave', this._onParentLeave);
|
|
61
66
|
this._parentItem.addEventListener('click', this._onParentClick, true);
|
|
67
|
+
// Open/close the submenu when the parent item's `open` attribute changes.
|
|
68
|
+
this._parentObserver = new MutationObserver(() => {
|
|
69
|
+
if (this._pinnedOpen) {
|
|
70
|
+
if (this._isParentVisible()) this.openSubmenu();
|
|
71
|
+
} else if (this._open) {
|
|
72
|
+
this.closeSubmenu();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
this._parentObserver.observe(this._parentItem, {
|
|
76
|
+
attributes: true,
|
|
77
|
+
attributeFilter: ['open'],
|
|
78
|
+
});
|
|
62
79
|
}
|
|
63
80
|
this._ensureBodyPanel();
|
|
64
81
|
}
|
|
65
82
|
|
|
83
|
+
private _isParentVisible(): boolean {
|
|
84
|
+
const rect = this._parentItem?.getBoundingClientRect();
|
|
85
|
+
return !!rect && rect.width > 0 && rect.height > 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
66
88
|
private _ensureProxy(): void {
|
|
67
89
|
if (this._proxyEl) return;
|
|
68
90
|
const el = document.createElement('div');
|
|
@@ -77,8 +99,16 @@ export class NileContextSubmenu extends NileElement {
|
|
|
77
99
|
private _syncProxyToParent(): void {
|
|
78
100
|
if (!this._proxyEl || !this._parentItem) return;
|
|
79
101
|
const rect = this._parentItem.getBoundingClientRect();
|
|
80
|
-
this.
|
|
81
|
-
|
|
102
|
+
if (this._pinnedOpen) {
|
|
103
|
+
// Pinned submenus anchor in document coordinates.
|
|
104
|
+
this._proxyEl.style.position = 'absolute';
|
|
105
|
+
this._proxyEl.style.left = `${rect.left + window.scrollX}px`;
|
|
106
|
+
this._proxyEl.style.top = `${rect.top + window.scrollY}px`;
|
|
107
|
+
} else {
|
|
108
|
+
this._proxyEl.style.position = 'fixed';
|
|
109
|
+
this._proxyEl.style.left = `${rect.left}px`;
|
|
110
|
+
this._proxyEl.style.top = `${rect.top}px`;
|
|
111
|
+
}
|
|
82
112
|
this._proxyEl.style.width = `${rect.width}px`;
|
|
83
113
|
this._proxyEl.style.height = `${rect.height}px`;
|
|
84
114
|
}
|
|
@@ -90,6 +120,8 @@ export class NileContextSubmenu extends NileElement {
|
|
|
90
120
|
this._parentItem.removeEventListener('mouseleave', this._onParentLeave);
|
|
91
121
|
this._parentItem.removeEventListener('click', this._onParentClick, true);
|
|
92
122
|
}
|
|
123
|
+
this._parentObserver?.disconnect();
|
|
124
|
+
this._parentObserver = undefined;
|
|
93
125
|
this._clearTimers();
|
|
94
126
|
if (this._open) this.closeSubmenu();
|
|
95
127
|
this._teardownBodyArtifacts();
|
|
@@ -172,6 +204,7 @@ export class NileContextSubmenu extends NileElement {
|
|
|
172
204
|
|
|
173
205
|
private _onParentLeave = (): void => {
|
|
174
206
|
if (this._openTimer != null) { clearTimeout(this._openTimer); this._openTimer = undefined; }
|
|
207
|
+
if (this._pinnedOpen) return;
|
|
175
208
|
if (this._open) {
|
|
176
209
|
this._closeTimer = window.setTimeout(() => this.closeSubmenu(), HOVER_CLOSE_DELAY);
|
|
177
210
|
}
|
|
@@ -181,15 +214,31 @@ export class NileContextSubmenu extends NileElement {
|
|
|
181
214
|
e.stopPropagation();
|
|
182
215
|
if (this._parentItem?.disabled) return;
|
|
183
216
|
this._clearTimers();
|
|
184
|
-
if (this._open)
|
|
185
|
-
|
|
217
|
+
if (this._open) {
|
|
218
|
+
if (!this._pinnedOpen) this.closeSubmenu();
|
|
219
|
+
} else {
|
|
220
|
+
this.openSubmenu();
|
|
221
|
+
}
|
|
186
222
|
};
|
|
187
223
|
|
|
188
224
|
private _onPanelEnter = (): void => {
|
|
189
225
|
this._clearTimers();
|
|
190
226
|
};
|
|
191
227
|
|
|
228
|
+
//Re-anchor to the parent item after scroll/resize
|
|
229
|
+
private _onReposition = (): void => {
|
|
230
|
+
if (this._repositionQueued || !this._open) return;
|
|
231
|
+
this._repositionQueued = true;
|
|
232
|
+
requestAnimationFrame(() => {
|
|
233
|
+
this._repositionQueued = false;
|
|
234
|
+
if (!this._open) return;
|
|
235
|
+
this._syncProxyToParent();
|
|
236
|
+
this._floatingPanelEl?.reposition();
|
|
237
|
+
});
|
|
238
|
+
};
|
|
239
|
+
|
|
192
240
|
private _onPanelLeave = (): void => {
|
|
241
|
+
if (this._pinnedOpen) return;
|
|
193
242
|
if (this._open) {
|
|
194
243
|
this._closeTimer = window.setTimeout(() => {
|
|
195
244
|
const hasOpenDescendant = NileContextSubmenu.openStack.some(
|
|
@@ -209,11 +258,50 @@ export class NileContextSubmenu extends NileElement {
|
|
|
209
258
|
this._ensureBodyPanel();
|
|
210
259
|
this._syncProxyToParent();
|
|
211
260
|
this._open = true;
|
|
212
|
-
if (this._floatingPanelEl)
|
|
261
|
+
if (this._floatingPanelEl) {
|
|
262
|
+
// open pinned descendants only once this panel is fully shown
|
|
263
|
+
this._floatingPanelEl.addEventListener(
|
|
264
|
+
'nile-after-show',
|
|
265
|
+
() => {
|
|
266
|
+
if (this._open) this._openPinnedDescendants();
|
|
267
|
+
},
|
|
268
|
+
{ once: true },
|
|
269
|
+
);
|
|
270
|
+
this._floatingPanelEl.open = true;
|
|
271
|
+
}
|
|
213
272
|
NileContextSubmenu.openStack.push(this);
|
|
273
|
+
window.addEventListener('scroll', this._onReposition, true);
|
|
274
|
+
window.addEventListener('resize', this._onReposition);
|
|
275
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
276
|
+
if (this._open) this._openPinnedDescendants();
|
|
277
|
+
}));
|
|
214
278
|
this._parentItem?.setSubmenuExpanded?.(true);
|
|
215
279
|
}
|
|
216
280
|
|
|
281
|
+
/** Open the submenu of every direct child item carrying the `open` attribute. */
|
|
282
|
+
private _openPinnedDescendants(): void {
|
|
283
|
+
if (!this._menuContainerRef) return;
|
|
284
|
+
const items = Array.from(
|
|
285
|
+
this._menuContainerRef.querySelectorAll(ITEM_TAG)
|
|
286
|
+
) as NileContextMenuItem[];
|
|
287
|
+
for (const item of items) {
|
|
288
|
+
if (!this._isDirectChildItem(item)) continue;
|
|
289
|
+
if (!item.open || item.disabled) continue;
|
|
290
|
+
const sub = item.querySelector(`:scope > ${SUBMENU_TAG}`) as NileContextSubmenu | null;
|
|
291
|
+
sub?.openSubmenu();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** True if `item` sits at this submenu's own level (not inside a deeper submenu). */
|
|
296
|
+
private _isDirectChildItem(item: Element): boolean {
|
|
297
|
+
let cur: Element | null = item.parentElement;
|
|
298
|
+
while (cur && cur !== this._menuContainerRef) {
|
|
299
|
+
if (cur.tagName.toLowerCase() === SUBMENU_TAG) return false;
|
|
300
|
+
cur = cur.parentElement;
|
|
301
|
+
}
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
|
|
217
305
|
public closeSubmenu(): void {
|
|
218
306
|
if (!this._open) return;
|
|
219
307
|
for (const sub of [...NileContextSubmenu.openStack]) {
|
|
@@ -223,6 +311,8 @@ export class NileContextSubmenu extends NileElement {
|
|
|
223
311
|
}
|
|
224
312
|
this._open = false;
|
|
225
313
|
if (this._floatingPanelEl) this._floatingPanelEl.open = false;
|
|
314
|
+
window.removeEventListener('scroll', this._onReposition, true);
|
|
315
|
+
window.removeEventListener('resize', this._onReposition);
|
|
226
316
|
NileContextSubmenu.openStack = NileContextSubmenu.openStack.filter(s => s !== this);
|
|
227
317
|
this._clearTimers();
|
|
228
318
|
this._parentItem?.setSubmenuExpanded?.(false);
|
|
@@ -248,13 +338,7 @@ export class NileContextSubmenu extends NileElement {
|
|
|
248
338
|
this._menuContainerRef.querySelectorAll(ITEM_TAG)
|
|
249
339
|
) as NileContextMenuItem[];
|
|
250
340
|
for (const item of items) {
|
|
251
|
-
|
|
252
|
-
let inDeeperSub = false;
|
|
253
|
-
while (cur && cur !== this._menuContainerRef) {
|
|
254
|
-
if (cur.tagName.toLowerCase() === SUBMENU_TAG) { inDeeperSub = true; break; }
|
|
255
|
-
cur = cur.parentElement;
|
|
256
|
-
}
|
|
257
|
-
if (inDeeperSub) continue;
|
|
341
|
+
if (!this._isDirectChildItem(item)) continue;
|
|
258
342
|
if (!item.disabled) { item.focus(); return; }
|
|
259
343
|
}
|
|
260
344
|
}
|
|
@@ -263,6 +347,7 @@ export class NileContextSubmenu extends NileElement {
|
|
|
263
347
|
const myLevel = this._parentLevel();
|
|
264
348
|
for (const sub of [...NileContextSubmenu.openStack]) {
|
|
265
349
|
if (sub === this) continue;
|
|
350
|
+
if (sub._pinnedOpen) continue;
|
|
266
351
|
if (sub._parentLevel() === myLevel) sub.closeSubmenu();
|
|
267
352
|
}
|
|
268
353
|
}
|
|
@@ -303,6 +303,11 @@ export class NileFloatingPanel extends NileElement {
|
|
|
303
303
|
this._attachTippy();
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
/** Recomputes the panel position against its anchor (e.g. after the anchor moved). */
|
|
307
|
+
public reposition(): void {
|
|
308
|
+
this.tippyInstance?.popperInstance?.update();
|
|
309
|
+
}
|
|
310
|
+
|
|
306
311
|
/** Returns the current resolved placement from Tippy/Popper. */
|
|
307
312
|
public getCurrentPlacement(): string {
|
|
308
313
|
const popper = this.tippyInstance?.popper;
|