@mintplayer/ng-bootstrap 21.29.0 → 21.31.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.
- package/fesm2022/mintplayer-ng-bootstrap-a11y.mjs +455 -0
- package/fesm2022/mintplayer-ng-bootstrap-a11y.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-bootstrap-accordion.mjs +8 -5
- package/fesm2022/mintplayer-ng-bootstrap-accordion.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-breadcrumb.mjs +10 -4
- package/fesm2022/mintplayer-ng-bootstrap-breadcrumb.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-button-group.mjs +7 -4
- package/fesm2022/mintplayer-ng-bootstrap-button-group.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-calendar.mjs +131 -3
- package/fesm2022/mintplayer-ng-bootstrap-calendar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-carousel.mjs +80 -48
- package/fesm2022/mintplayer-ng-bootstrap-carousel.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-code-snippet.mjs +4 -1
- package/fesm2022/mintplayer-ng-bootstrap-code-snippet.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-color-picker.mjs +218 -14
- package/fesm2022/mintplayer-ng-bootstrap-color-picker.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-datatable.mjs +4 -3
- package/fesm2022/mintplayer-ng-bootstrap-datatable.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-datepicker.mjs +2 -2
- package/fesm2022/mintplayer-ng-bootstrap-datepicker.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-dock.mjs +294 -3
- package/fesm2022/mintplayer-ng-bootstrap-dock.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-dropdown-menu.mjs +163 -18
- package/fesm2022/mintplayer-ng-bootstrap-dropdown-menu.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-dropdown.mjs +179 -7
- package/fesm2022/mintplayer-ng-bootstrap-dropdown.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-file-upload.mjs +14 -4
- package/fesm2022/mintplayer-ng-bootstrap-file-upload.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-has-overlay.mjs +14 -0
- package/fesm2022/mintplayer-ng-bootstrap-has-overlay.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-list-group.mjs +2 -1
- package/fesm2022/mintplayer-ng-bootstrap-list-group.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-marquee.mjs +7 -4
- package/fesm2022/mintplayer-ng-bootstrap-marquee.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-modal.mjs +70 -6
- package/fesm2022/mintplayer-ng-bootstrap-modal.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-multi-range.mjs +693 -0
- package/fesm2022/mintplayer-ng-bootstrap-multi-range.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-bootstrap-multiselect.mjs +5 -4
- package/fesm2022/mintplayer-ng-bootstrap-multiselect.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-navbar-toggler.mjs +6 -6
- package/fesm2022/mintplayer-ng-bootstrap-navbar-toggler.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-navbar.mjs +45 -13
- package/fesm2022/mintplayer-ng-bootstrap-navbar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-offcanvas.mjs +51 -5
- package/fesm2022/mintplayer-ng-bootstrap-offcanvas.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-pagination.mjs +5 -3
- package/fesm2022/mintplayer-ng-bootstrap-pagination.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-placeholder.mjs +18 -4
- package/fesm2022/mintplayer-ng-bootstrap-placeholder.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-playlist-toggler.mjs +6 -6
- package/fesm2022/mintplayer-ng-bootstrap-playlist-toggler.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-popover.mjs +61 -6
- package/fesm2022/mintplayer-ng-bootstrap-popover.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-priority-nav.mjs +19 -4
- package/fesm2022/mintplayer-ng-bootstrap-priority-nav.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-progress-bar.mjs +8 -5
- package/fesm2022/mintplayer-ng-bootstrap-progress-bar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-range.mjs +4 -3
- package/fesm2022/mintplayer-ng-bootstrap-range.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-rating.mjs +34 -4
- package/fesm2022/mintplayer-ng-bootstrap-rating.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-reduced-motion.mjs +59 -0
- package/fesm2022/mintplayer-ng-bootstrap-reduced-motion.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-bootstrap-resizable.mjs +91 -2
- package/fesm2022/mintplayer-ng-bootstrap-resizable.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-scheduler.mjs +16 -5
- package/fesm2022/mintplayer-ng-bootstrap-scheduler.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-scrollspy.mjs +2 -2
- package/fesm2022/mintplayer-ng-bootstrap-scrollspy.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-searchbox.mjs +28 -5
- package/fesm2022/mintplayer-ng-bootstrap-searchbox.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-select.mjs +4 -3
- package/fesm2022/mintplayer-ng-bootstrap-select.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-select2.mjs +18 -4
- package/fesm2022/mintplayer-ng-bootstrap-select2.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-signature-pad.mjs +4 -3
- package/fesm2022/mintplayer-ng-bootstrap-signature-pad.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-tab-control.mjs +2 -2
- package/fesm2022/mintplayer-ng-bootstrap-tab-control.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-table.mjs +10 -3
- package/fesm2022/mintplayer-ng-bootstrap-table.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-tile-manager.mjs +143 -29
- package/fesm2022/mintplayer-ng-bootstrap-tile-manager.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-timepicker.mjs +2 -2
- package/fesm2022/mintplayer-ng-bootstrap-timepicker.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-toast.mjs +7 -4
- package/fesm2022/mintplayer-ng-bootstrap-toast.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-toggle-button.mjs +42 -21
- package/fesm2022/mintplayer-ng-bootstrap-toggle-button.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-tooltip.mjs +33 -4
- package/fesm2022/mintplayer-ng-bootstrap-tooltip.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-treeview.mjs +17 -7
- package/fesm2022/mintplayer-ng-bootstrap-treeview.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-typeahead.mjs +50 -8
- package/fesm2022/mintplayer-ng-bootstrap-typeahead.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-virtual-datatable.mjs +34 -12
- package/fesm2022/mintplayer-ng-bootstrap-virtual-datatable.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-web-components-a11y.mjs +74 -0
- package/fesm2022/mintplayer-ng-bootstrap-web-components-a11y.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-bootstrap-web-components-scheduler.mjs +1476 -71
- package/fesm2022/mintplayer-ng-bootstrap-web-components-scheduler.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-web-components-splitter.mjs +194 -2
- package/fesm2022/mintplayer-ng-bootstrap-web-components-splitter.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-web-components-tab-control.mjs +4 -0
- package/fesm2022/mintplayer-ng-bootstrap-web-components-tab-control.mjs.map +1 -1
- package/package.json +18 -2
- package/types/mintplayer-ng-bootstrap-a11y.d.ts +196 -0
- package/types/mintplayer-ng-bootstrap-accordion.d.ts +4 -2
- package/types/mintplayer-ng-bootstrap-breadcrumb.d.ts +2 -1
- package/types/mintplayer-ng-bootstrap-button-group.d.ts +2 -1
- package/types/mintplayer-ng-bootstrap-calendar.d.ts +32 -0
- package/types/mintplayer-ng-bootstrap-carousel.d.ts +56 -3
- package/types/mintplayer-ng-bootstrap-code-snippet.d.ts +1 -0
- package/types/mintplayer-ng-bootstrap-color-picker.d.ts +75 -4
- package/types/mintplayer-ng-bootstrap-datatable.d.ts +1 -1
- package/types/mintplayer-ng-bootstrap-dock.d.ts +51 -0
- package/types/mintplayer-ng-bootstrap-dropdown-menu.d.ts +54 -9
- package/types/mintplayer-ng-bootstrap-dropdown.d.ts +57 -2
- package/types/mintplayer-ng-bootstrap-file-upload.d.ts +4 -1
- package/types/mintplayer-ng-bootstrap-has-overlay.d.ts +14 -0
- package/types/mintplayer-ng-bootstrap-marquee.d.ts +2 -1
- package/types/mintplayer-ng-bootstrap-modal.d.ts +25 -1
- package/types/mintplayer-ng-bootstrap-multi-range.d.ts +170 -0
- package/types/mintplayer-ng-bootstrap-multiselect.d.ts +2 -1
- package/types/mintplayer-ng-bootstrap-navbar-toggler.d.ts +4 -2
- package/types/mintplayer-ng-bootstrap-navbar.d.ts +25 -1
- package/types/mintplayer-ng-bootstrap-offcanvas.d.ts +23 -1
- package/types/mintplayer-ng-bootstrap-pagination.d.ts +3 -1
- package/types/mintplayer-ng-bootstrap-placeholder.d.ts +5 -1
- package/types/mintplayer-ng-bootstrap-playlist-toggler.d.ts +4 -2
- package/types/mintplayer-ng-bootstrap-popover.d.ts +21 -1
- package/types/mintplayer-ng-bootstrap-priority-nav.d.ts +4 -1
- package/types/mintplayer-ng-bootstrap-progress-bar.d.ts +4 -2
- package/types/mintplayer-ng-bootstrap-range.d.ts +2 -1
- package/types/mintplayer-ng-bootstrap-rating.d.ts +3 -0
- package/types/mintplayer-ng-bootstrap-reduced-motion.d.ts +36 -0
- package/types/mintplayer-ng-bootstrap-resizable.d.ts +4 -0
- package/types/mintplayer-ng-bootstrap-scheduler.d.ts +42 -9
- package/types/mintplayer-ng-bootstrap-scrollspy.d.ts +1 -1
- package/types/mintplayer-ng-bootstrap-searchbox.d.ts +8 -1
- package/types/mintplayer-ng-bootstrap-select.d.ts +2 -1
- package/types/mintplayer-ng-bootstrap-select2.d.ts +3 -0
- package/types/mintplayer-ng-bootstrap-signature-pad.d.ts +2 -1
- package/types/mintplayer-ng-bootstrap-table.d.ts +8 -1
- package/types/mintplayer-ng-bootstrap-tile-manager.d.ts +21 -2
- package/types/mintplayer-ng-bootstrap-toast.d.ts +6 -1
- package/types/mintplayer-ng-bootstrap-toggle-button.d.ts +11 -0
- package/types/mintplayer-ng-bootstrap-tooltip.d.ts +5 -0
- package/types/mintplayer-ng-bootstrap-treeview.d.ts +12 -1
- package/types/mintplayer-ng-bootstrap-typeahead.d.ts +11 -3
- package/types/mintplayer-ng-bootstrap-virtual-datatable.d.ts +14 -1
- package/types/mintplayer-ng-bootstrap-web-components-a11y.d.ts +34 -0
- package/types/mintplayer-ng-bootstrap-web-components-scheduler-core.d.ts +35 -11
- package/types/mintplayer-ng-bootstrap-web-components-scheduler.d.ts +246 -0
- package/types/mintplayer-ng-bootstrap-web-components-splitter.d.ts +95 -37
|
@@ -4,6 +4,7 @@ import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
|
|
|
4
4
|
import { html, unsafeCSS, LitElement } from 'lit';
|
|
5
5
|
import '@mintplayer/ng-bootstrap/web-components/tab-control';
|
|
6
6
|
import '@mintplayer/ng-bootstrap/web-components/splitter';
|
|
7
|
+
import { LiveAnnouncerController } from '@mintplayer/ng-bootstrap/web-components/a11y';
|
|
7
8
|
|
|
8
9
|
class BsDockPaneComponent {
|
|
9
10
|
constructor() {
|
|
@@ -173,6 +174,27 @@ const styles = unsafeCSS(`:host {
|
|
|
173
174
|
white-space: nowrap;
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
.dock-floating__close {
|
|
178
|
+
flex: 0 0 auto;
|
|
179
|
+
appearance: none;
|
|
180
|
+
background: transparent;
|
|
181
|
+
border: 0;
|
|
182
|
+
color: var(--bs-body-color);
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
font-size: 1.25rem;
|
|
185
|
+
line-height: 1;
|
|
186
|
+
padding: 0 0.4rem;
|
|
187
|
+
border-radius: 3px;
|
|
188
|
+
margin-top: -1px;
|
|
189
|
+
}
|
|
190
|
+
.dock-floating__close:hover {
|
|
191
|
+
background: rgba(var(--bs-primary-rgb), 0.15);
|
|
192
|
+
}
|
|
193
|
+
.dock-floating__close:focus-visible {
|
|
194
|
+
outline: 2px solid var(--bs-primary);
|
|
195
|
+
outline-offset: 1px;
|
|
196
|
+
}
|
|
197
|
+
|
|
176
198
|
.dock-floating > .dock-stack {
|
|
177
199
|
flex: 1 1 auto;
|
|
178
200
|
min-width: 12rem;
|
|
@@ -288,7 +310,11 @@ const styles = unsafeCSS(`:host {
|
|
|
288
310
|
background: rgba(var(--bs-primary-rgb), 0.35);
|
|
289
311
|
border-color: var(--bs-primary);
|
|
290
312
|
opacity: 1;
|
|
291
|
-
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.dock-intersection-handle:focus-visible {
|
|
316
|
+
outline: 2px solid var(--bs-primary);
|
|
317
|
+
outline-offset: 1px;
|
|
292
318
|
}
|
|
293
319
|
|
|
294
320
|
.dock-snap-marker {
|
|
@@ -417,6 +443,18 @@ const styles = unsafeCSS(`:host {
|
|
|
417
443
|
display: block;
|
|
418
444
|
min-width: 0;
|
|
419
445
|
min-height: 0;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
@media (prefers-reduced-motion: reduce) {
|
|
449
|
+
.dock-floating,
|
|
450
|
+
.dock-floating__resizer,
|
|
451
|
+
.dock-floating__close,
|
|
452
|
+
.dock-intersection-handle,
|
|
453
|
+
.dock-drop-indicator,
|
|
454
|
+
.dock-drop-joystick__button {
|
|
455
|
+
transition: none !important;
|
|
456
|
+
animation: none !important;
|
|
457
|
+
}
|
|
420
458
|
}`);
|
|
421
459
|
|
|
422
460
|
class MintDockManagerElement extends LitElement {
|
|
@@ -497,6 +535,14 @@ class MintDockManagerElement extends LitElement {
|
|
|
497
535
|
}
|
|
498
536
|
constructor() {
|
|
499
537
|
super();
|
|
538
|
+
this.liveAnnouncer = new LiveAnnouncerController(this);
|
|
539
|
+
/**
|
|
540
|
+
* Active keyboard-driven pane move (Phase 7 of WC ARIA PRD). The user
|
|
541
|
+
* pressed M on a focused tab; until they pick a destination key (T/R/B/L
|
|
542
|
+
* to dock to a side, F to float) or Escape, we intercept letter keys and
|
|
543
|
+
* route them to the synthetic-drop pipeline.
|
|
544
|
+
*/
|
|
545
|
+
this.paneMoveMode = null;
|
|
500
546
|
this.dropJoystickTarget = null;
|
|
501
547
|
this.rootLayout = null;
|
|
502
548
|
this.floatingLayouts = [];
|
|
@@ -540,7 +586,7 @@ class MintDockManagerElement extends LitElement {
|
|
|
540
586
|
this.onSplitterResize = this.onSplitterResize.bind(this);
|
|
541
587
|
}
|
|
542
588
|
render() {
|
|
543
|
-
return template
|
|
589
|
+
return html `${template}${this.liveAnnouncer.template()}`;
|
|
544
590
|
}
|
|
545
591
|
firstUpdated() {
|
|
546
592
|
// Resolve document and window now that we are connected.
|
|
@@ -585,6 +631,9 @@ class MintDockManagerElement extends LitElement {
|
|
|
585
631
|
// Drop targeting (drop indicator + joystick zone selection) runs entirely
|
|
586
632
|
// off pointer-based hit-testing in updatePaneDragDropTargetFromPoint and
|
|
587
633
|
// findDropZoneByPoint — no HTML5 dragover/drop/dragleave listeners needed.
|
|
634
|
+
// Keyboard pane-move (Phase 7). Capture phase so we run before mp-tab-control's
|
|
635
|
+
// shadow-internal handlers preventDefault on Arrow / Home / End / Enter.
|
|
636
|
+
root.addEventListener('keydown', (ev) => this.onRootKeyDown(ev), { capture: true });
|
|
588
637
|
// Render any layout that was set before the shadow DOM existed.
|
|
589
638
|
this.renderLayout();
|
|
590
639
|
// Reactive triggers for intersection-handle re-rendering. Each observer
|
|
@@ -785,6 +834,14 @@ class MintDockManagerElement extends LitElement {
|
|
|
785
834
|
index,
|
|
786
835
|
segments: [],
|
|
787
836
|
});
|
|
837
|
+
const titleText = this.getFloatingWindowTitle(floating);
|
|
838
|
+
const titleId = `${this.instanceId}-floating-${index}-title`;
|
|
839
|
+
// role=dialog announces the window-like region; aria-modal=false because
|
|
840
|
+
// floating panes don't trap focus — users can still interact with docked
|
|
841
|
+
// panes behind them.
|
|
842
|
+
wrapper.setAttribute('role', 'dialog');
|
|
843
|
+
wrapper.setAttribute('aria-labelledby', titleId);
|
|
844
|
+
wrapper.setAttribute('aria-modal', 'false');
|
|
788
845
|
const { left, top, width, height } = floating.bounds;
|
|
789
846
|
wrapper.style.left = `${left}px`;
|
|
790
847
|
wrapper.style.top = `${top}px`;
|
|
@@ -797,8 +854,21 @@ class MintDockManagerElement extends LitElement {
|
|
|
797
854
|
chrome.addEventListener('pointerdown', (event) => this.beginFloatingDrag(event, index, wrapper, chrome));
|
|
798
855
|
const title = this.documentRef.createElement('div');
|
|
799
856
|
title.classList.add('dock-floating__title');
|
|
800
|
-
title.
|
|
857
|
+
title.id = titleId;
|
|
858
|
+
title.textContent = titleText;
|
|
801
859
|
chrome.appendChild(title);
|
|
860
|
+
const closeBtn = this.documentRef.createElement('button');
|
|
861
|
+
closeBtn.type = 'button';
|
|
862
|
+
closeBtn.classList.add('dock-floating__close');
|
|
863
|
+
closeBtn.setAttribute('aria-label', `Close pane: ${titleText}`);
|
|
864
|
+
closeBtn.textContent = '×';
|
|
865
|
+
closeBtn.addEventListener('click', (ev) => {
|
|
866
|
+
ev.stopPropagation();
|
|
867
|
+
this.closeFloatingPane(index);
|
|
868
|
+
});
|
|
869
|
+
// The chrome's pointerdown otherwise begins a drag on the close button.
|
|
870
|
+
closeBtn.addEventListener('pointerdown', (ev) => ev.stopPropagation());
|
|
871
|
+
chrome.appendChild(closeBtn);
|
|
802
872
|
wrapper.appendChild(chrome);
|
|
803
873
|
if (floating.root) {
|
|
804
874
|
const content = this.renderNode(floating.root, [], index);
|
|
@@ -856,6 +926,26 @@ class MintDockManagerElement extends LitElement {
|
|
|
856
926
|
resizerConfigs.forEach(({ classes, edges }) => {
|
|
857
927
|
const resizer = this.documentRef.createElement('div');
|
|
858
928
|
resizer.classList.add(...classes);
|
|
929
|
+
resizer.setAttribute('role', 'separator');
|
|
930
|
+
// For corner resizers (both axes), aria-orientation is omitted —
|
|
931
|
+
// separator's default is "horizontal" but corner == both, so neither
|
|
932
|
+
// value is correct. SR users still hear "separator" with the label.
|
|
933
|
+
const isPureHorizontal = edges.horizontal === 'none' && edges.vertical !== 'none';
|
|
934
|
+
const isPureVertical = edges.vertical === 'none' && edges.horizontal !== 'none';
|
|
935
|
+
if (isPureHorizontal)
|
|
936
|
+
resizer.setAttribute('aria-orientation', 'horizontal');
|
|
937
|
+
else if (isPureVertical)
|
|
938
|
+
resizer.setAttribute('aria-orientation', 'vertical');
|
|
939
|
+
const labelParts = [];
|
|
940
|
+
if (edges.vertical === 'top')
|
|
941
|
+
labelParts.push('top');
|
|
942
|
+
else if (edges.vertical === 'bottom')
|
|
943
|
+
labelParts.push('bottom');
|
|
944
|
+
if (edges.horizontal === 'left')
|
|
945
|
+
labelParts.push('left');
|
|
946
|
+
else if (edges.horizontal === 'right')
|
|
947
|
+
labelParts.push('right');
|
|
948
|
+
resizer.setAttribute('aria-label', `Resize pane ${labelParts.length === 2 ? 'corner ' : 'edge '}${labelParts.join('-')}`);
|
|
859
949
|
resizer.addEventListener('pointerdown', (event) => this.beginFloatingResize(event, index, wrapper, resizer, edges));
|
|
860
950
|
wrapper.appendChild(resizer);
|
|
861
951
|
});
|
|
@@ -965,6 +1055,9 @@ class MintDockManagerElement extends LitElement {
|
|
|
965
1055
|
handle.classList.add('dock-intersection-handle', 'glyph');
|
|
966
1056
|
handle.setAttribute('role', 'separator');
|
|
967
1057
|
handle.setAttribute('aria-label', 'Resize split intersection');
|
|
1058
|
+
// tabindex=0 — make the handle keyboard-reachable. Arrow keys then
|
|
1059
|
+
// resize the underlying h/v dividers (handled in onIntersectionKeyDown).
|
|
1060
|
+
handle.setAttribute('tabindex', '0');
|
|
968
1061
|
const firstPair = group.pairs[0];
|
|
969
1062
|
const key = `${firstPair.h.pathStr}:${firstPair.h.index}|${firstPair.v.pathStr}:${firstPair.v.index}`;
|
|
970
1063
|
handle.dataset['key'] = key;
|
|
@@ -979,9 +1072,51 @@ class MintDockManagerElement extends LitElement {
|
|
|
979
1072
|
const seedV = { path: this.parsePath(firstPair.v.pathStr), index: firstPair.v.index, container: this.findSplitterByPath(this.parsePath(firstPair.v.pathStr)?.segments ?? []) ?? this.rootEl, rect: new DOMRect() };
|
|
980
1073
|
handle.addEventListener('pointerdown', (ev) => this.beginCornerResize(ev, seedH, seedV, handle));
|
|
981
1074
|
handle.addEventListener('dblclick', (ev) => this.onIntersectionDoubleClick(ev, handle));
|
|
1075
|
+
handle.addEventListener('keydown', (ev) => this.onIntersectionKeyDown(ev, handle));
|
|
982
1076
|
layer.appendChild(handle);
|
|
983
1077
|
});
|
|
984
1078
|
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Keyboard resize on a focused intersection handle. ArrowLeft/Right resize
|
|
1081
|
+
* the vertical divider the handle sits on; ArrowUp/Down resize the
|
|
1082
|
+
* horizontal divider. Home/End shrink/grow the leading panel of whichever
|
|
1083
|
+
* axis is implied by the key. Shift = fine 1% step.
|
|
1084
|
+
*
|
|
1085
|
+
* Internally delegates to mp-splitter.resizeDividerBy() so the percent-step
|
|
1086
|
+
* + clamp math lives in one place. If a 2D handle straddles multiple split
|
|
1087
|
+
* pairs (sibling splitters whose dividers happen to overlap), the first
|
|
1088
|
+
* pair drives — same precedence as the pointer drag path.
|
|
1089
|
+
*/
|
|
1090
|
+
onIntersectionKeyDown(event, handle) {
|
|
1091
|
+
const isVerticalAxis = event.key === 'ArrowLeft' || event.key === 'ArrowRight';
|
|
1092
|
+
const isHorizontalAxis = event.key === 'ArrowUp' || event.key === 'ArrowDown';
|
|
1093
|
+
const isHomeEnd = event.key === 'Home' || event.key === 'End';
|
|
1094
|
+
if (!isVerticalAxis && !isHorizontalAxis && !isHomeEnd)
|
|
1095
|
+
return;
|
|
1096
|
+
const pairsJson = handle.dataset['pairs'];
|
|
1097
|
+
if (!pairsJson)
|
|
1098
|
+
return;
|
|
1099
|
+
let pairs;
|
|
1100
|
+
try {
|
|
1101
|
+
pairs = JSON.parse(pairsJson);
|
|
1102
|
+
}
|
|
1103
|
+
catch {
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
if (pairs.length === 0)
|
|
1107
|
+
return;
|
|
1108
|
+
event.preventDefault();
|
|
1109
|
+
const fine = event.shiftKey;
|
|
1110
|
+
// Which divider does this key drive? Arrow{Left,Right} → vertical
|
|
1111
|
+
// (h-flow splitter, v-bar divider); Arrow{Up,Down} → horizontal. For
|
|
1112
|
+
// Home/End, default to the vertical-axis divider (matches APG's "drive
|
|
1113
|
+
// the splitter to its limit" convention on a horizontal layout).
|
|
1114
|
+
const drivesVerticalDivider = isVerticalAxis || isHomeEnd;
|
|
1115
|
+
const target = drivesVerticalDivider ? pairs[0].v : pairs[0].h;
|
|
1116
|
+
const path = this.parsePath(target.pathStr);
|
|
1117
|
+
const splitter = this.findSplitterByPath(path?.segments ?? []);
|
|
1118
|
+
splitter?.resizeDividerBy?.(target.index, event.key, fine);
|
|
1119
|
+
}
|
|
985
1120
|
beginCornerResize(event, h, v, handle) {
|
|
986
1121
|
event.preventDefault();
|
|
987
1122
|
// Build pairs from dataset if available (grouped intersections), otherwise from the provided pair
|
|
@@ -2553,6 +2688,7 @@ class MintDockManagerElement extends LitElement {
|
|
|
2553
2688
|
if (wrapper) {
|
|
2554
2689
|
this.promoteFloatingPane(newIndex, wrapper);
|
|
2555
2690
|
}
|
|
2691
|
+
this.liveAnnouncer.announce(`Pane ${this.titles[pane] ?? pane} torn off into a floating window.`);
|
|
2556
2692
|
// Update drag state so subsequent moves reposition the floating window
|
|
2557
2693
|
state.sourcePath = { type: 'floating', index: newIndex, segments: [] };
|
|
2558
2694
|
state.floatingIndex = newIndex;
|
|
@@ -3526,6 +3662,161 @@ class MintDockManagerElement extends LitElement {
|
|
|
3526
3662
|
}
|
|
3527
3663
|
this.floatingLayouts.splice(index, 1);
|
|
3528
3664
|
}
|
|
3665
|
+
/**
|
|
3666
|
+
* Keyboard handler on the dock root, registered in capture phase so it
|
|
3667
|
+
* runs before mp-tab-control's shadow-internal handlers swallow Arrow /
|
|
3668
|
+
* Home / End / Enter. M opens move mode on a focused tab; subsequent
|
|
3669
|
+
* letter keys (T/R/B/L/F) commit the move; Escape cancels.
|
|
3670
|
+
*/
|
|
3671
|
+
onRootKeyDown(event) {
|
|
3672
|
+
if (this.paneMoveMode) {
|
|
3673
|
+
this.handlePaneMoveModeKey(event);
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
3676
|
+
if (event.key !== 'm' && event.key !== 'M')
|
|
3677
|
+
return;
|
|
3678
|
+
const focused = this.findFocusedPaneOrigin();
|
|
3679
|
+
if (!focused)
|
|
3680
|
+
return;
|
|
3681
|
+
event.preventDefault();
|
|
3682
|
+
event.stopPropagation();
|
|
3683
|
+
this.paneMoveMode = focused;
|
|
3684
|
+
const title = this.titles[focused.paneName] ?? focused.paneName;
|
|
3685
|
+
this.liveAnnouncer.announce(`Move mode for pane ${title}. Press T, R, B, or L to dock to top, right, bottom, or left of the current stack. Press F to float. Escape to cancel.`);
|
|
3686
|
+
}
|
|
3687
|
+
handlePaneMoveModeKey(event) {
|
|
3688
|
+
if (event.key === 'Escape') {
|
|
3689
|
+
event.preventDefault();
|
|
3690
|
+
event.stopPropagation();
|
|
3691
|
+
this.paneMoveMode = null;
|
|
3692
|
+
this.liveAnnouncer.announce('Move cancelled.');
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3695
|
+
const key = event.key.toLowerCase();
|
|
3696
|
+
const zoneByKey = {
|
|
3697
|
+
t: 'top', r: 'right', b: 'bottom', l: 'left', f: 'float',
|
|
3698
|
+
};
|
|
3699
|
+
const choice = zoneByKey[key];
|
|
3700
|
+
if (!choice)
|
|
3701
|
+
return;
|
|
3702
|
+
event.preventDefault();
|
|
3703
|
+
event.stopPropagation();
|
|
3704
|
+
if (choice === 'float')
|
|
3705
|
+
this.commitPaneMoveAsFloat();
|
|
3706
|
+
else
|
|
3707
|
+
this.commitPaneMoveToZone(choice);
|
|
3708
|
+
}
|
|
3709
|
+
/**
|
|
3710
|
+
* Walk the focused element through nested shadow roots to find a focused
|
|
3711
|
+
* tab button, then map it back to the dock's pane name + source path via
|
|
3712
|
+
* the data attributes on the slotted .dock-tab span.
|
|
3713
|
+
*/
|
|
3714
|
+
findFocusedPaneOrigin() {
|
|
3715
|
+
if (!this.shadowRoot)
|
|
3716
|
+
return null;
|
|
3717
|
+
let active = this.shadowRoot.activeElement;
|
|
3718
|
+
while (active && active.shadowRoot && active.shadowRoot.activeElement) {
|
|
3719
|
+
active = active.shadowRoot.activeElement;
|
|
3720
|
+
}
|
|
3721
|
+
if (!active || active.getAttribute('role') !== 'tab')
|
|
3722
|
+
return null;
|
|
3723
|
+
// mp-tab-control assigns the role=tab button id `${tabId}-header-button`,
|
|
3724
|
+
// while the dock's `.dock-tab` slot wrapper carries `data-tab-id=${tabId}`.
|
|
3725
|
+
// Strip the suffix so the lookup matches the slot wrapper.
|
|
3726
|
+
const tabId = active.id.replace(/-header-button$/, '');
|
|
3727
|
+
const headerSpan = this.shadowRoot.querySelector(`.dock-tab[data-tab-id="${tabId}"]`);
|
|
3728
|
+
if (!headerSpan)
|
|
3729
|
+
return null;
|
|
3730
|
+
const stack = headerSpan.closest('.dock-stack');
|
|
3731
|
+
if (!stack)
|
|
3732
|
+
return null;
|
|
3733
|
+
const path = this.parsePath(stack.dataset['path']);
|
|
3734
|
+
if (!path)
|
|
3735
|
+
return null;
|
|
3736
|
+
return { paneName: headerSpan.dataset['pane'], sourcePath: path };
|
|
3737
|
+
}
|
|
3738
|
+
/**
|
|
3739
|
+
* Synthesise a minimal dragState so handleDrop's existing pipeline can
|
|
3740
|
+
* commit the keyboard-driven move. handleDrop reads {pane, sourcePath,
|
|
3741
|
+
* dropHandled} — the rest of dragState is pointer-drag bookkeeping that
|
|
3742
|
+
* doesn't apply here.
|
|
3743
|
+
*/
|
|
3744
|
+
synthesiseDragStateForKeyboardMove() {
|
|
3745
|
+
if (!this.paneMoveMode)
|
|
3746
|
+
return;
|
|
3747
|
+
this.dragState = {
|
|
3748
|
+
pane: this.paneMoveMode.paneName,
|
|
3749
|
+
sourcePath: this.clonePath(this.paneMoveMode.sourcePath),
|
|
3750
|
+
floatingIndex: this.paneMoveMode.sourcePath.type === 'floating'
|
|
3751
|
+
? this.paneMoveMode.sourcePath.index
|
|
3752
|
+
: null,
|
|
3753
|
+
pointerOffsetX: 0,
|
|
3754
|
+
pointerOffsetY: 0,
|
|
3755
|
+
dropHandled: false,
|
|
3756
|
+
sourceStackEl: null,
|
|
3757
|
+
sourceHeaderBounds: null,
|
|
3758
|
+
startClientX: 0,
|
|
3759
|
+
startClientY: 0,
|
|
3760
|
+
};
|
|
3761
|
+
}
|
|
3762
|
+
commitPaneMoveToZone(zone) {
|
|
3763
|
+
if (!this.paneMoveMode)
|
|
3764
|
+
return;
|
|
3765
|
+
const move = this.paneMoveMode;
|
|
3766
|
+
this.synthesiseDragStateForKeyboardMove();
|
|
3767
|
+
// Source stack is also the target stack — handleDrop creates a sibling
|
|
3768
|
+
// split with the moved pane on the chosen side.
|
|
3769
|
+
this.handleDrop(move.sourcePath, zone);
|
|
3770
|
+
this.dragState = null;
|
|
3771
|
+
this.paneMoveMode = null;
|
|
3772
|
+
const title = this.titles[move.paneName] ?? move.paneName;
|
|
3773
|
+
this.liveAnnouncer.announce(`Pane ${title} docked to ${zone}.`);
|
|
3774
|
+
}
|
|
3775
|
+
commitPaneMoveAsFloat() {
|
|
3776
|
+
if (!this.paneMoveMode)
|
|
3777
|
+
return;
|
|
3778
|
+
const move = this.paneMoveMode;
|
|
3779
|
+
const source = this.resolveStackLocation(move.sourcePath);
|
|
3780
|
+
if (!source) {
|
|
3781
|
+
this.paneMoveMode = null;
|
|
3782
|
+
return;
|
|
3783
|
+
}
|
|
3784
|
+
this.removePaneFromLocation(source, move.paneName, true);
|
|
3785
|
+
const floatingLayout = {
|
|
3786
|
+
bounds: {
|
|
3787
|
+
left: 100 + this.floatingLayouts.length * 24,
|
|
3788
|
+
top: 100 + this.floatingLayouts.length * 24,
|
|
3789
|
+
width: 320,
|
|
3790
|
+
height: 200,
|
|
3791
|
+
},
|
|
3792
|
+
root: { kind: 'stack', panes: [move.paneName], activePane: move.paneName },
|
|
3793
|
+
activePane: move.paneName,
|
|
3794
|
+
};
|
|
3795
|
+
this.floatingLayouts.push(floatingLayout);
|
|
3796
|
+
this.normalizeAllLayouts();
|
|
3797
|
+
this.renderLayout();
|
|
3798
|
+
this.dispatchLayoutChanged();
|
|
3799
|
+
this.paneMoveMode = null;
|
|
3800
|
+
const title = this.titles[move.paneName] ?? move.paneName;
|
|
3801
|
+
this.liveAnnouncer.announce(`Pane ${title} torn off into a floating window.`);
|
|
3802
|
+
}
|
|
3803
|
+
/**
|
|
3804
|
+
* Close a floating pane via the chrome's close button (or any external
|
|
3805
|
+
* caller). Announces the close to SR users, redraws the dock, and emits a
|
|
3806
|
+
* layout-changed event. Phase 4 of the WC ARIA PRD — the floating chrome
|
|
3807
|
+
* had no close affordance before this; users had to programmatically pop
|
|
3808
|
+
* the pane out of `floatingLayouts`.
|
|
3809
|
+
*/
|
|
3810
|
+
closeFloatingPane(index) {
|
|
3811
|
+
const floating = this.floatingLayouts[index];
|
|
3812
|
+
if (!floating)
|
|
3813
|
+
return;
|
|
3814
|
+
const title = this.getFloatingWindowTitle(floating);
|
|
3815
|
+
this.removeFloatingAt(index);
|
|
3816
|
+
this.renderLayout();
|
|
3817
|
+
this.dispatchLayoutChanged();
|
|
3818
|
+
this.liveAnnouncer.announce(`Closed floating pane ${title}.`);
|
|
3819
|
+
}
|
|
3529
3820
|
removePaneFromFloating(index, path, pane, skipCleanup = false) {
|
|
3530
3821
|
const floating = this.floatingLayouts[index];
|
|
3531
3822
|
if (!floating || !floating.root) {
|