@mintplayer/ng-bootstrap 21.21.0 → 21.23.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-accordion.mjs +20 -20
- package/fesm2022/mintplayer-ng-bootstrap-accordion.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-alert.mjs +8 -8
- package/fesm2022/mintplayer-ng-bootstrap-alert.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-badge.mjs +5 -5
- package/fesm2022/mintplayer-ng-bootstrap-badge.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-breadcrumb.mjs +6 -6
- package/fesm2022/mintplayer-ng-bootstrap-breadcrumb.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-button-group.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-button-group.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-button-type.mjs +4 -4
- package/fesm2022/mintplayer-ng-bootstrap-button-type.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-calendar-month.mjs +9 -9
- package/fesm2022/mintplayer-ng-bootstrap-calendar-month.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-calendar.mjs +10 -10
- package/fesm2022/mintplayer-ng-bootstrap-calendar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-card.mjs +8 -8
- package/fesm2022/mintplayer-ng-bootstrap-card.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-carousel.mjs +25 -25
- package/fesm2022/mintplayer-ng-bootstrap-carousel.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-close.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-close.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-code-snippet.mjs +7 -7
- package/fesm2022/mintplayer-ng-bootstrap-code-snippet.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-color-picker.mjs +58 -58
- package/fesm2022/mintplayer-ng-bootstrap-color-picker.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-container.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-container.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-context-menu.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-context-menu.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-copy.mjs +4 -4
- package/fesm2022/mintplayer-ng-bootstrap-copy.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-datatable.mjs +20 -20
- package/fesm2022/mintplayer-ng-bootstrap-datatable.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-datepicker.mjs +6 -6
- package/fesm2022/mintplayer-ng-bootstrap-datepicker.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-dock.mjs +1129 -1516
- package/fesm2022/mintplayer-ng-bootstrap-dock.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-dropdown-divider.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-dropdown-divider.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-dropdown-menu.mjs +10 -10
- package/fesm2022/mintplayer-ng-bootstrap-dropdown-menu.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-dropdown.mjs +15 -15
- package/fesm2022/mintplayer-ng-bootstrap-dropdown.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-enhanced-paste.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-enhanced-paste.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-enum.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-enum.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-file-upload.mjs +16 -16
- package/fesm2022/mintplayer-ng-bootstrap-file-upload.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-floating-labels.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-floating-labels.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-font-color.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-font-color.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-for.mjs +4 -4
- package/fesm2022/mintplayer-ng-bootstrap-for.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-form.mjs +11 -11
- package/fesm2022/mintplayer-ng-bootstrap-form.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-grid.mjs +26 -26
- package/fesm2022/mintplayer-ng-bootstrap-grid.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-has-overlay.mjs +4 -4
- package/fesm2022/mintplayer-ng-bootstrap-has-overlay.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-has-property.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-has-property.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-in-list.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-in-list.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-input-group.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-input-group.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-instance-of.mjs +14 -14
- package/fesm2022/mintplayer-ng-bootstrap-instance-of.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-let.mjs +4 -4
- package/fesm2022/mintplayer-ng-bootstrap-let.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-linify.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-linify.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-list-group.mjs +7 -7
- package/fesm2022/mintplayer-ng-bootstrap-list-group.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-markdown.mjs +12 -12
- package/fesm2022/mintplayer-ng-bootstrap-markdown.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-marquee.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-marquee.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-modal.mjs +24 -24
- package/fesm2022/mintplayer-ng-bootstrap-modal.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-multiselect.mjs +24 -24
- package/fesm2022/mintplayer-ng-bootstrap-multiselect.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-navbar-toggler.mjs +5 -5
- package/fesm2022/mintplayer-ng-bootstrap-navbar-toggler.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-navbar.mjs +58 -58
- package/fesm2022/mintplayer-ng-bootstrap-navbar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-navigation-lock.mjs +8 -8
- package/fesm2022/mintplayer-ng-bootstrap-navigation-lock.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-no-noscript.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-no-noscript.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-offcanvas.mjs +40 -40
- package/fesm2022/mintplayer-ng-bootstrap-offcanvas.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-ordinal-number.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-ordinal-number.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-pagination.mjs +12 -12
- package/fesm2022/mintplayer-ng-bootstrap-pagination.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-parallax.mjs +6 -6
- package/fesm2022/mintplayer-ng-bootstrap-parallax.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-placeholder.mjs +7 -7
- package/fesm2022/mintplayer-ng-bootstrap-placeholder.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-playlist-toggler.mjs +5 -5
- package/fesm2022/mintplayer-ng-bootstrap-playlist-toggler.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-popover.mjs +20 -20
- package/fesm2022/mintplayer-ng-bootstrap-popover.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-priority-nav.mjs +30 -30
- package/fesm2022/mintplayer-ng-bootstrap-priority-nav.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-progress-bar.mjs +17 -17
- package/fesm2022/mintplayer-ng-bootstrap-progress-bar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-range.mjs +9 -9
- package/fesm2022/mintplayer-ng-bootstrap-range.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-rating.mjs +7 -7
- package/fesm2022/mintplayer-ng-bootstrap-rating.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-resizable.mjs +25 -25
- package/fesm2022/mintplayer-ng-bootstrap-resizable.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-scheduler.mjs +17 -17
- package/fesm2022/mintplayer-ng-bootstrap-scheduler.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-scrollspy.mjs +14 -14
- package/fesm2022/mintplayer-ng-bootstrap-scrollspy.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-searchbox.mjs +24 -24
- package/fesm2022/mintplayer-ng-bootstrap-searchbox.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-select.mjs +19 -19
- package/fesm2022/mintplayer-ng-bootstrap-select.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-select2.mjs +20 -20
- package/fesm2022/mintplayer-ng-bootstrap-select2.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-shell.mjs +11 -11
- package/fesm2022/mintplayer-ng-bootstrap-shell.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-signature-pad.mjs +7 -7
- package/fesm2022/mintplayer-ng-bootstrap-signature-pad.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-slugify.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-slugify.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-spinner.mjs +7 -7
- package/fesm2022/mintplayer-ng-bootstrap-spinner.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-split-string.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-split-string.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-sticky-footer.mjs +6 -6
- package/fesm2022/mintplayer-ng-bootstrap-sticky-footer.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-tab-control.mjs +57 -67
- package/fesm2022/mintplayer-ng-bootstrap-tab-control.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-table.mjs +10 -10
- package/fesm2022/mintplayer-ng-bootstrap-table.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-timepicker.mjs +8 -8
- package/fesm2022/mintplayer-ng-bootstrap-timepicker.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-toast.mjs +24 -24
- package/fesm2022/mintplayer-ng-bootstrap-toast.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-toggle-button.mjs +22 -22
- package/fesm2022/mintplayer-ng-bootstrap-toggle-button.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-tooltip.mjs +10 -10
- package/fesm2022/mintplayer-ng-bootstrap-tooltip.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-treeview.mjs +14 -14
- package/fesm2022/mintplayer-ng-bootstrap-treeview.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-trust-html.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-trust-html.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-typeahead.mjs +10 -10
- package/fesm2022/mintplayer-ng-bootstrap-typeahead.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-uc-first.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-uc-first.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-user-agent.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-user-agent.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-viewport.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-viewport.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-virtual-datatable.mjs +10 -10
- package/fesm2022/mintplayer-ng-bootstrap-virtual-datatable.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-web-components-scheduler-core.mjs +1356 -0
- package/fesm2022/mintplayer-ng-bootstrap-web-components-scheduler-core.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-bootstrap-web-components-scheduler.mjs +3819 -0
- package/fesm2022/mintplayer-ng-bootstrap-web-components-scheduler.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-bootstrap-web-components-splitter.mjs +731 -0
- package/fesm2022/mintplayer-ng-bootstrap-web-components-splitter.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-bootstrap-web-components-tab-control.mjs +549 -0
- package/fesm2022/mintplayer-ng-bootstrap-web-components-tab-control.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-bootstrap-word-count.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-word-count.mjs.map +1 -1
- package/package.json +21 -6
- package/types/mintplayer-ng-bootstrap-dock.d.ts +70 -29
- package/types/mintplayer-ng-bootstrap-scheduler.d.ts +2 -2
- package/types/mintplayer-ng-bootstrap-tab-control.d.ts +7 -11
- package/types/mintplayer-ng-bootstrap-web-components-scheduler-core.d.ts +890 -0
- package/types/mintplayer-ng-bootstrap-web-components-scheduler.d.ts +354 -0
- package/types/mintplayer-ng-bootstrap-web-components-splitter.d.ts +165 -0
- package/types/mintplayer-ng-bootstrap-web-components-tab-control.d.ts +95 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { input, viewChild, TemplateRef, ChangeDetectionStrategy, Component, output, signal, contentChildren, inject, effect, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
3
3
|
import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
|
|
4
|
+
import { html, unsafeCSS, LitElement } from 'lit';
|
|
5
|
+
import '@mintplayer/ng-bootstrap/web-components/tab-control';
|
|
6
|
+
import '@mintplayer/ng-bootstrap/web-components/splitter';
|
|
4
7
|
|
|
5
8
|
class BsDockPaneComponent {
|
|
6
9
|
constructor() {
|
|
7
|
-
this.name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
8
|
-
this.title = input(undefined, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
10
|
+
this.name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
11
|
+
this.title = input(undefined, ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
9
12
|
this.template = viewChild.required(TemplateRef);
|
|
10
13
|
}
|
|
11
14
|
ngAfterContentInit() {
|
|
@@ -13,10 +16,10 @@ class BsDockPaneComponent {
|
|
|
13
16
|
throw new Error('bs-dock-pane requires a unique "name" input.');
|
|
14
17
|
}
|
|
15
18
|
}
|
|
16
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
17
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.
|
|
19
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BsDockPaneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
20
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.11", type: BsDockPaneComponent, isStandalone: true, selector: "bs-dock-pane", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "template", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], ngImport: i0, template: `<ng-template><ng-content></ng-content></ng-template>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
18
21
|
}
|
|
19
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
22
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BsDockPaneComponent, decorators: [{
|
|
20
23
|
type: Component,
|
|
21
24
|
args: [{
|
|
22
25
|
selector: 'bs-dock-pane',
|
|
@@ -25,502 +28,386 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
|
|
|
25
28
|
}]
|
|
26
29
|
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], template: [{ type: i0.ViewChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.dock-floating__chrome {
|
|
96
|
-
display: flex;
|
|
97
|
-
align-items: center;
|
|
98
|
-
gap: 0.5rem;
|
|
99
|
-
padding: 0.35rem 0.75rem;
|
|
100
|
-
cursor: move;
|
|
101
|
-
background: linear-gradient(
|
|
102
|
-
to bottom,
|
|
103
|
-
rgba(148, 163, 184, 0.6),
|
|
104
|
-
rgba(148, 163, 184, 0.25)
|
|
105
|
-
);
|
|
106
|
-
border-bottom: 1px solid rgba(148, 163, 184, 0.5);
|
|
107
|
-
user-select: none;
|
|
108
|
-
-webkit-user-select: none;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.dock-floating__title {
|
|
112
|
-
flex: 1 1 auto;
|
|
113
|
-
font-size: 0.875rem;
|
|
114
|
-
font-weight: 500;
|
|
115
|
-
color: rgba(30, 41, 59, 0.95);
|
|
116
|
-
overflow: hidden;
|
|
117
|
-
text-overflow: ellipsis;
|
|
118
|
-
white-space: nowrap;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.dock-floating > .dock-stack {
|
|
122
|
-
flex: 1 1 auto;
|
|
123
|
-
min-width: 12rem;
|
|
124
|
-
min-height: 8rem;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.dock-floating__resizer {
|
|
128
|
-
position: absolute;
|
|
129
|
-
pointer-events: auto;
|
|
130
|
-
z-index: 2;
|
|
131
|
-
background: rgba(148, 163, 184, 0.25);
|
|
132
|
-
transition: background 120ms ease;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.dock-floating__resizer:hover,
|
|
136
|
-
.dock-floating__resizer[data-resizing='true'] {
|
|
137
|
-
background: rgba(148, 163, 184, 0.4);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.dock-floating__resizer--top,
|
|
141
|
-
.dock-floating__resizer--bottom {
|
|
142
|
-
left: 0.75rem;
|
|
143
|
-
right: 0.75rem;
|
|
144
|
-
height: 0.5rem;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.dock-floating__resizer--top {
|
|
148
|
-
top: 0;
|
|
149
|
-
cursor: n-resize;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.dock-floating__resizer--bottom {
|
|
153
|
-
bottom: 0;
|
|
154
|
-
cursor: s-resize;
|
|
155
|
-
}
|
|
31
|
+
// AUTO-GENERATED — do not edit by hand.
|
|
32
|
+
// Source: mint-dock-manager.element.html + mint-dock-manager.element.scss
|
|
33
|
+
// Regenerate with the codegen-wc Nx target.
|
|
34
|
+
const template = html `<div class="dock-root">
|
|
35
|
+
<div class="dock-docked"></div>
|
|
36
|
+
<div class="dock-floating-layer"></div>
|
|
37
|
+
<div class="dock-intersections-layer dock-intersection-layer"></div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="dock-drop-indicator"></div>
|
|
40
|
+
<div class="dock-drop-joystick" data-visible="false">
|
|
41
|
+
<div class="dock-drop-joystick__spacer"></div>
|
|
42
|
+
<button
|
|
43
|
+
class="dock-drop-joystick__button"
|
|
44
|
+
type="button"
|
|
45
|
+
data-zone="top"
|
|
46
|
+
aria-label="Dock to top"
|
|
47
|
+
>
|
|
48
|
+
↑
|
|
49
|
+
</button>
|
|
50
|
+
<div class="dock-drop-joystick__spacer"></div>
|
|
51
|
+
<button
|
|
52
|
+
class="dock-drop-joystick__button"
|
|
53
|
+
type="button"
|
|
54
|
+
data-zone="left"
|
|
55
|
+
aria-label="Dock to left"
|
|
56
|
+
>
|
|
57
|
+
←
|
|
58
|
+
</button>
|
|
59
|
+
<button
|
|
60
|
+
class="dock-drop-joystick__button"
|
|
61
|
+
type="button"
|
|
62
|
+
data-zone="center"
|
|
63
|
+
aria-label="Dock to center"
|
|
64
|
+
>
|
|
65
|
+
•
|
|
66
|
+
</button>
|
|
67
|
+
<button
|
|
68
|
+
class="dock-drop-joystick__button"
|
|
69
|
+
type="button"
|
|
70
|
+
data-zone="right"
|
|
71
|
+
aria-label="Dock to right"
|
|
72
|
+
>
|
|
73
|
+
→
|
|
74
|
+
</button>
|
|
75
|
+
<div class="dock-drop-joystick__spacer"></div>
|
|
76
|
+
<button
|
|
77
|
+
class="dock-drop-joystick__button"
|
|
78
|
+
type="button"
|
|
79
|
+
data-zone="bottom"
|
|
80
|
+
aria-label="Dock to bottom"
|
|
81
|
+
>
|
|
82
|
+
↓
|
|
83
|
+
</button>
|
|
84
|
+
<div class="dock-drop-joystick__spacer"></div>
|
|
85
|
+
</div>`;
|
|
86
|
+
const styles = unsafeCSS(`:host {
|
|
87
|
+
display: block;
|
|
88
|
+
position: relative;
|
|
89
|
+
width: 100%;
|
|
90
|
+
height: 100%;
|
|
91
|
+
contain: layout paint size style;
|
|
92
|
+
box-sizing: border-box;
|
|
93
|
+
font-family: inherit;
|
|
94
|
+
color: inherit;
|
|
95
|
+
--dock-split-gap: 0.25rem;
|
|
96
|
+
}
|
|
156
97
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
98
|
+
.dock-root,
|
|
99
|
+
.dock-docked,
|
|
100
|
+
.dock-split,
|
|
101
|
+
.dock-stack,
|
|
102
|
+
.dock-stack__pane {
|
|
103
|
+
box-sizing: border-box;
|
|
104
|
+
min-width: 0;
|
|
105
|
+
min-height: 0;
|
|
106
|
+
}
|
|
163
107
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
108
|
+
.dock-root {
|
|
109
|
+
position: relative;
|
|
110
|
+
width: 100%;
|
|
111
|
+
height: 100%;
|
|
112
|
+
}
|
|
168
113
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
114
|
+
.dock-docked {
|
|
115
|
+
position: absolute;
|
|
116
|
+
inset: 0;
|
|
117
|
+
display: flex;
|
|
118
|
+
z-index: 0;
|
|
119
|
+
}
|
|
173
120
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
121
|
+
.dock-floating-layer {
|
|
122
|
+
position: absolute;
|
|
123
|
+
inset: 0;
|
|
124
|
+
pointer-events: none;
|
|
125
|
+
z-index: 5;
|
|
126
|
+
}
|
|
181
127
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
128
|
+
.dock-intersections-layer {
|
|
129
|
+
position: absolute;
|
|
130
|
+
inset: 0;
|
|
131
|
+
pointer-events: none;
|
|
132
|
+
z-index: 120;
|
|
133
|
+
}
|
|
187
134
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
135
|
+
.dock-floating {
|
|
136
|
+
position: absolute;
|
|
137
|
+
display: flex;
|
|
138
|
+
flex-direction: column;
|
|
139
|
+
pointer-events: auto;
|
|
140
|
+
border: 1px solid var(--bs-border-color);
|
|
141
|
+
border-radius: 0.5rem;
|
|
142
|
+
background: var(--bs-body-bg);
|
|
143
|
+
box-shadow: var(--bs-box-shadow-lg);
|
|
144
|
+
overflow: hidden;
|
|
145
|
+
min-width: 12rem;
|
|
146
|
+
min-height: 8rem;
|
|
147
|
+
}
|
|
193
148
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
cursor: sw-resize;
|
|
198
|
-
}
|
|
149
|
+
.dock-floating[data-dragging=true] {
|
|
150
|
+
pointer-events: none;
|
|
151
|
+
}
|
|
199
152
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
153
|
+
.dock-floating__chrome {
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
gap: 0.5rem;
|
|
157
|
+
padding: 0.35rem 0.75rem;
|
|
158
|
+
cursor: move;
|
|
159
|
+
background: linear-gradient(to bottom, rgba(var(--bs-primary-rgb), 0.15), rgba(var(--bs-primary-rgb), 0.05));
|
|
160
|
+
border-bottom: 1px solid var(--bs-primary-border-subtle);
|
|
161
|
+
user-select: none;
|
|
162
|
+
-webkit-user-select: none;
|
|
163
|
+
}
|
|
205
164
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
165
|
+
.dock-floating__title {
|
|
166
|
+
flex: 1 1 auto;
|
|
167
|
+
font-size: 0.875rem;
|
|
168
|
+
font-weight: 500;
|
|
169
|
+
color: var(--bs-body-color);
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
text-overflow: ellipsis;
|
|
172
|
+
white-space: nowrap;
|
|
173
|
+
}
|
|
212
174
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
175
|
+
.dock-floating > .dock-stack {
|
|
176
|
+
flex: 1 1 auto;
|
|
177
|
+
min-width: 12rem;
|
|
178
|
+
min-height: 8rem;
|
|
179
|
+
}
|
|
216
180
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
181
|
+
.dock-floating__resizer {
|
|
182
|
+
position: absolute;
|
|
183
|
+
pointer-events: auto;
|
|
184
|
+
z-index: 2;
|
|
185
|
+
background: rgba(var(--bs-primary-rgb), 0.1);
|
|
186
|
+
transition: background 120ms ease;
|
|
187
|
+
}
|
|
220
188
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
189
|
+
.dock-floating__resizer:hover,
|
|
190
|
+
.dock-floating__resizer[data-resizing=true] {
|
|
191
|
+
background: rgba(var(--bs-primary-rgb), 0.3);
|
|
192
|
+
}
|
|
226
193
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
194
|
+
.dock-floating__resizer--top,
|
|
195
|
+
.dock-floating__resizer--bottom {
|
|
196
|
+
left: 0.75rem;
|
|
197
|
+
right: 0.75rem;
|
|
198
|
+
height: 0.5rem;
|
|
199
|
+
}
|
|
233
200
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
margin-top: calc(var(--dock-split-gap) * -1);
|
|
239
|
-
margin-bottom: calc(var(--dock-split-gap) * -1);
|
|
240
|
-
}
|
|
201
|
+
.dock-floating__resizer--top {
|
|
202
|
+
top: 0;
|
|
203
|
+
cursor: n-resize;
|
|
204
|
+
}
|
|
241
205
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
margin-left: calc(var(--dock-split-gap) * -1);
|
|
247
|
-
margin-right: calc(var(--dock-split-gap) * -1);
|
|
248
|
-
}
|
|
206
|
+
.dock-floating__resizer--bottom {
|
|
207
|
+
bottom: 0;
|
|
208
|
+
cursor: s-resize;
|
|
209
|
+
}
|
|
249
210
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
border-radius: 999px;
|
|
257
|
-
background: rgba(0, 0, 0, 0.25);
|
|
258
|
-
}
|
|
211
|
+
.dock-floating__resizer--left,
|
|
212
|
+
.dock-floating__resizer--right {
|
|
213
|
+
top: 1.75rem;
|
|
214
|
+
bottom: 0.75rem;
|
|
215
|
+
width: 0.5rem;
|
|
216
|
+
}
|
|
259
217
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
218
|
+
.dock-floating__resizer--left {
|
|
219
|
+
left: 0;
|
|
220
|
+
cursor: w-resize;
|
|
221
|
+
}
|
|
264
222
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
223
|
+
.dock-floating__resizer--right {
|
|
224
|
+
right: 0;
|
|
225
|
+
cursor: e-resize;
|
|
226
|
+
}
|
|
269
227
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
228
|
+
.dock-floating__resizer--top-left,
|
|
229
|
+
.dock-floating__resizer--top-right,
|
|
230
|
+
.dock-floating__resizer--bottom-left,
|
|
231
|
+
.dock-floating__resizer--bottom-right {
|
|
232
|
+
width: 0.75rem;
|
|
233
|
+
height: 0.75rem;
|
|
234
|
+
}
|
|
275
235
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
margin-top: -0.5rem;
|
|
282
|
-
border-radius: 0.375rem;
|
|
283
|
-
background: rgba(59, 130, 246, 0.2);
|
|
284
|
-
border: 1px solid rgba(59, 130, 246, 0.6);
|
|
285
|
-
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.2);
|
|
286
|
-
cursor: all-scroll;
|
|
287
|
-
pointer-events: auto;
|
|
288
|
-
opacity: 0;
|
|
289
|
-
transition: background 120ms ease, border-color 120ms ease, opacity 120ms ease;
|
|
290
|
-
}
|
|
236
|
+
.dock-floating__resizer--top-left {
|
|
237
|
+
top: 0;
|
|
238
|
+
left: 0;
|
|
239
|
+
cursor: nw-resize;
|
|
240
|
+
}
|
|
291
241
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
border-color: rgba(59, 130, 246, 0.9);
|
|
298
|
-
opacity: 1;
|
|
299
|
-
outline: none;
|
|
300
|
-
}
|
|
242
|
+
.dock-floating__resizer--top-right {
|
|
243
|
+
top: 0;
|
|
244
|
+
right: 0;
|
|
245
|
+
cursor: ne-resize;
|
|
246
|
+
}
|
|
301
247
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
margin-top: -3px;
|
|
308
|
-
border-radius: 50%;
|
|
309
|
-
background: rgba(59, 130, 246, 0.7);
|
|
310
|
-
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
|
|
311
|
-
pointer-events: none;
|
|
312
|
-
z-index: 130;
|
|
313
|
-
}
|
|
248
|
+
.dock-floating__resizer--bottom-left {
|
|
249
|
+
bottom: 0;
|
|
250
|
+
left: 0;
|
|
251
|
+
cursor: sw-resize;
|
|
252
|
+
}
|
|
314
253
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
border-radius: 0.25rem;
|
|
321
|
-
background: rgba(255, 255, 255, 0.75);
|
|
322
|
-
backdrop-filter: blur(4px);
|
|
323
|
-
}
|
|
254
|
+
.dock-floating__resizer--bottom-right {
|
|
255
|
+
right: 0;
|
|
256
|
+
bottom: 0;
|
|
257
|
+
cursor: se-resize;
|
|
258
|
+
}
|
|
324
259
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
padding: 0.25rem;
|
|
330
|
-
background: rgba(0, 0, 0, 0.05);
|
|
331
|
-
border-bottom: 1px solid rgba(0, 0, 0, 0.15);
|
|
332
|
-
}
|
|
260
|
+
.dock-split {
|
|
261
|
+
flex: 1 1 0;
|
|
262
|
+
position: relative;
|
|
263
|
+
}
|
|
333
264
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
265
|
+
.dock-intersection-handle {
|
|
266
|
+
position: absolute;
|
|
267
|
+
width: 1rem;
|
|
268
|
+
height: 1rem;
|
|
269
|
+
margin-left: -0.5rem;
|
|
270
|
+
margin-top: -0.5rem;
|
|
271
|
+
border-radius: 0.375rem;
|
|
272
|
+
background: var(--bs-primary-bg-subtle);
|
|
273
|
+
border: 1px solid var(--bs-primary-border-subtle);
|
|
274
|
+
box-shadow: var(--bs-box-shadow-sm);
|
|
275
|
+
cursor: all-scroll;
|
|
276
|
+
pointer-events: auto;
|
|
277
|
+
opacity: 0.45;
|
|
278
|
+
transition: background 120ms ease, border-color 120ms ease, opacity 120ms ease;
|
|
279
|
+
}
|
|
345
280
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
281
|
+
.dock-intersection-handle:hover,
|
|
282
|
+
.dock-intersection-handle:focus-visible,
|
|
283
|
+
.dock-intersection-handle[data-visible=true],
|
|
284
|
+
.dock-intersection-handle[data-resizing=true] {
|
|
285
|
+
background: rgba(var(--bs-primary-rgb), 0.35);
|
|
286
|
+
border-color: var(--bs-primary);
|
|
287
|
+
opacity: 1;
|
|
288
|
+
outline: none;
|
|
289
|
+
}
|
|
349
290
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
291
|
+
.dock-snap-marker {
|
|
292
|
+
position: absolute;
|
|
293
|
+
width: 6px;
|
|
294
|
+
height: 6px;
|
|
295
|
+
margin-left: -3px;
|
|
296
|
+
margin-top: -3px;
|
|
297
|
+
border-radius: 50%;
|
|
298
|
+
background: var(--bs-primary);
|
|
299
|
+
box-shadow: 0 0 0 2px var(--bs-primary-bg-subtle);
|
|
300
|
+
pointer-events: none;
|
|
301
|
+
z-index: 130;
|
|
302
|
+
}
|
|
353
303
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
304
|
+
.dock-stack {
|
|
305
|
+
flex: 1 1 0;
|
|
306
|
+
border: 1px solid var(--bs-border-color);
|
|
307
|
+
border-radius: 0.25rem;
|
|
308
|
+
background: var(--bs-body-bg);
|
|
309
|
+
backdrop-filter: blur(4px);
|
|
310
|
+
}
|
|
358
311
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
312
|
+
.dock-tab {
|
|
313
|
+
cursor: grab;
|
|
314
|
+
display: block;
|
|
315
|
+
padding: 0.5rem 1rem;
|
|
316
|
+
margin: -0.5rem -1rem;
|
|
317
|
+
}
|
|
362
318
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
display: flex;
|
|
367
|
-
overflow: hidden;
|
|
368
|
-
}
|
|
319
|
+
.dock-tab:active {
|
|
320
|
+
cursor: grabbing;
|
|
321
|
+
}
|
|
369
322
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
323
|
+
.dock-stack__pane {
|
|
324
|
+
position: relative;
|
|
325
|
+
display: flex;
|
|
326
|
+
flex-direction: column;
|
|
327
|
+
overflow: hidden;
|
|
328
|
+
height: 100%;
|
|
329
|
+
}
|
|
377
330
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
331
|
+
.dock-drop-indicator {
|
|
332
|
+
position: absolute;
|
|
333
|
+
pointer-events: none;
|
|
334
|
+
border: 2px solid var(--bs-primary);
|
|
335
|
+
background: rgba(var(--bs-primary-rgb), 0.2);
|
|
336
|
+
border-radius: 0.25rem;
|
|
337
|
+
opacity: 0;
|
|
338
|
+
transition: opacity 120ms ease;
|
|
339
|
+
z-index: 100;
|
|
340
|
+
}
|
|
381
341
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
border: 2px solid rgba(59, 130, 246, 0.9);
|
|
386
|
-
background: rgba(59, 130, 246, 0.2);
|
|
387
|
-
border-radius: 0.25rem;
|
|
388
|
-
opacity: 0;
|
|
389
|
-
transition: opacity 120ms ease;
|
|
390
|
-
z-index: 100;
|
|
391
|
-
}
|
|
342
|
+
.dock-drop-indicator[data-visible=true] {
|
|
343
|
+
opacity: 1;
|
|
344
|
+
}
|
|
392
345
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
346
|
+
.dock-drop-joystick {
|
|
347
|
+
position: absolute;
|
|
348
|
+
display: grid;
|
|
349
|
+
grid-template-columns: repeat(3, min-content);
|
|
350
|
+
grid-template-rows: repeat(3, min-content);
|
|
351
|
+
gap: 0.125rem;
|
|
352
|
+
padding: 0.125rem;
|
|
353
|
+
border-radius: 999px;
|
|
354
|
+
background: var(--bs-tertiary-bg);
|
|
355
|
+
box-shadow: var(--bs-box-shadow);
|
|
356
|
+
pointer-events: none;
|
|
357
|
+
transform: translate(-50%, -50%);
|
|
358
|
+
z-index: 110;
|
|
359
|
+
}
|
|
396
360
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
gap: 0.125rem;
|
|
403
|
-
padding: 0.125rem;
|
|
404
|
-
border-radius: 999px;
|
|
405
|
-
background: rgba(15, 23, 42, 0.15);
|
|
406
|
-
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.25);
|
|
407
|
-
pointer-events: none;
|
|
408
|
-
transform: translate(-50%, -50%);
|
|
409
|
-
z-index: 110;
|
|
410
|
-
}
|
|
361
|
+
.dock-drop-joystick__spacer {
|
|
362
|
+
width: 1.75rem;
|
|
363
|
+
height: 1.75rem;
|
|
364
|
+
pointer-events: none;
|
|
365
|
+
}
|
|
411
366
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
367
|
+
.dock-drop-joystick__button {
|
|
368
|
+
width: 1.75rem;
|
|
369
|
+
height: 1.75rem;
|
|
370
|
+
display: inline-flex;
|
|
371
|
+
align-items: center;
|
|
372
|
+
justify-content: center;
|
|
373
|
+
border-radius: 0.375rem;
|
|
374
|
+
border: 1px solid var(--bs-primary-border-subtle);
|
|
375
|
+
background: var(--bs-body-bg);
|
|
376
|
+
color: var(--bs-primary);
|
|
377
|
+
font-size: 0.75rem;
|
|
378
|
+
line-height: 1;
|
|
379
|
+
font-weight: 600;
|
|
380
|
+
pointer-events: auto;
|
|
381
|
+
cursor: pointer;
|
|
382
|
+
transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
|
|
383
|
+
}
|
|
417
384
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
border: 1px solid rgba(59, 130, 246, 0.4);
|
|
426
|
-
background: rgba(255, 255, 255, 0.9);
|
|
427
|
-
color: rgba(30, 64, 175, 0.9);
|
|
428
|
-
font-size: 0.75rem;
|
|
429
|
-
line-height: 1;
|
|
430
|
-
font-weight: 600;
|
|
431
|
-
pointer-events: auto;
|
|
432
|
-
cursor: pointer;
|
|
433
|
-
transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
|
|
434
|
-
}
|
|
385
|
+
.dock-drop-joystick__button[data-active=true],
|
|
386
|
+
.dock-drop-joystick__button:hover,
|
|
387
|
+
.dock-drop-joystick__button:focus-visible {
|
|
388
|
+
background: var(--bs-primary-bg-subtle);
|
|
389
|
+
border-color: var(--bs-primary);
|
|
390
|
+
color: var(--bs-primary);
|
|
391
|
+
}
|
|
435
392
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
border-color: rgba(59, 130, 246, 0.8);
|
|
441
|
-
color: rgba(30, 64, 175, 1);
|
|
442
|
-
}
|
|
393
|
+
.dock-drop-joystick__button:focus-visible {
|
|
394
|
+
outline: 2px solid var(--bs-primary);
|
|
395
|
+
outline-offset: 1px;
|
|
396
|
+
}
|
|
443
397
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
398
|
+
.dock-drop-joystick__button[data-zone=center] {
|
|
399
|
+
border-radius: 0.5rem;
|
|
400
|
+
}
|
|
448
401
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
402
|
+
::slotted(*) {
|
|
403
|
+
flex: 1 1 auto;
|
|
404
|
+
display: block;
|
|
405
|
+
min-width: 0;
|
|
406
|
+
min-height: 0;
|
|
407
|
+
}`);
|
|
452
408
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
display: block;
|
|
456
|
-
min-width: 0;
|
|
457
|
-
min-height: 0;
|
|
458
|
-
}
|
|
459
|
-
</style>
|
|
460
|
-
<div class="dock-root">
|
|
461
|
-
<div class="dock-docked"></div>
|
|
462
|
-
<div class="dock-floating-layer"></div>
|
|
463
|
-
<div class="dock-intersections-layer dock-intersection-layer"></div>
|
|
464
|
-
</div>
|
|
465
|
-
<div class="dock-drop-indicator"></div>
|
|
466
|
-
<div class="dock-drop-joystick" data-visible="false">
|
|
467
|
-
<div class="dock-drop-joystick__spacer"></div>
|
|
468
|
-
<button
|
|
469
|
-
class="dock-drop-joystick__button"
|
|
470
|
-
type="button"
|
|
471
|
-
data-zone="top"
|
|
472
|
-
aria-label="Dock to top"
|
|
473
|
-
>
|
|
474
|
-
↑
|
|
475
|
-
</button>
|
|
476
|
-
<div class="dock-drop-joystick__spacer"></div>
|
|
477
|
-
<button
|
|
478
|
-
class="dock-drop-joystick__button"
|
|
479
|
-
type="button"
|
|
480
|
-
data-zone="left"
|
|
481
|
-
aria-label="Dock to left"
|
|
482
|
-
>
|
|
483
|
-
←
|
|
484
|
-
</button>
|
|
485
|
-
<button
|
|
486
|
-
class="dock-drop-joystick__button"
|
|
487
|
-
type="button"
|
|
488
|
-
data-zone="center"
|
|
489
|
-
aria-label="Dock to center"
|
|
490
|
-
>
|
|
491
|
-
•
|
|
492
|
-
</button>
|
|
493
|
-
<button
|
|
494
|
-
class="dock-drop-joystick__button"
|
|
495
|
-
type="button"
|
|
496
|
-
data-zone="right"
|
|
497
|
-
aria-label="Dock to right"
|
|
498
|
-
>
|
|
499
|
-
→
|
|
500
|
-
</button>
|
|
501
|
-
<div class="dock-drop-joystick__spacer"></div>
|
|
502
|
-
<button
|
|
503
|
-
class="dock-drop-joystick__button"
|
|
504
|
-
type="button"
|
|
505
|
-
data-zone="bottom"
|
|
506
|
-
aria-label="Dock to bottom"
|
|
507
|
-
>
|
|
508
|
-
↓
|
|
509
|
-
</button>
|
|
510
|
-
<div class="dock-drop-joystick__spacer"></div>
|
|
511
|
-
</div>
|
|
512
|
-
`;
|
|
513
|
-
let cachedTemplate = null;
|
|
514
|
-
let cachedTemplateDocument = null;
|
|
515
|
-
function ensureTemplate(documentRef) {
|
|
516
|
-
if (!cachedTemplate || cachedTemplateDocument !== documentRef) {
|
|
517
|
-
cachedTemplate = documentRef.createElement('template');
|
|
518
|
-
cachedTemplate.innerHTML = templateHtml;
|
|
519
|
-
cachedTemplateDocument = documentRef;
|
|
520
|
-
}
|
|
521
|
-
return cachedTemplate;
|
|
522
|
-
}
|
|
523
|
-
class MintDockManagerElement extends HTMLElement {
|
|
409
|
+
class MintDockManagerElement extends LitElement {
|
|
410
|
+
static { this.styles = [styles]; }
|
|
524
411
|
static { this.documentRef = typeof document !== 'undefined' ? document : null; }
|
|
525
412
|
static configureDocument(documentRef) {
|
|
526
413
|
if (documentRef) {
|
|
@@ -528,43 +415,9 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
528
415
|
}
|
|
529
416
|
}
|
|
530
417
|
static get observedAttributes() {
|
|
531
|
-
return ['layout'];
|
|
532
|
-
// return ['layout', 'debug-snap-markers'];
|
|
418
|
+
return [...(super.observedAttributes ?? []), 'layout'];
|
|
533
419
|
}
|
|
534
420
|
static { this.instanceCounter = 0; }
|
|
535
|
-
renderSnapMarkersForDivider() {
|
|
536
|
-
if (!this.showSnapMarkers)
|
|
537
|
-
return;
|
|
538
|
-
const layer = this.shadowRoot?.querySelector('.dock-intersections-layer, .dock-intersection-layer');
|
|
539
|
-
if (!layer)
|
|
540
|
-
return;
|
|
541
|
-
// Clear previous
|
|
542
|
-
Array.from(layer.querySelectorAll('.dock-snap-marker')).forEach((el) => el.remove());
|
|
543
|
-
if (!this.resizeState || !this.activeSnapAxis || this.activeSnapTargets.length === 0)
|
|
544
|
-
return;
|
|
545
|
-
const rootRect = this.rootEl.getBoundingClientRect();
|
|
546
|
-
const dRect = this.resizeState.divider.getBoundingClientRect();
|
|
547
|
-
if (this.activeSnapAxis === 'x') {
|
|
548
|
-
const y = dRect.top + dRect.height / 2 - rootRect.top;
|
|
549
|
-
this.activeSnapTargets.forEach((sx) => {
|
|
550
|
-
const dot = this.documentRef.createElement('div');
|
|
551
|
-
dot.className = 'dock-snap-marker';
|
|
552
|
-
dot.style.left = `${rootRect.left + sx - rootRect.left}px`;
|
|
553
|
-
dot.style.top = `${y}px`;
|
|
554
|
-
layer.appendChild(dot);
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
else if (this.activeSnapAxis === 'y') {
|
|
558
|
-
const x = dRect.left + dRect.width / 2 - rootRect.left;
|
|
559
|
-
this.activeSnapTargets.forEach((sy) => {
|
|
560
|
-
const dot = this.documentRef.createElement('div');
|
|
561
|
-
dot.className = 'dock-snap-marker';
|
|
562
|
-
dot.style.left = `${x}px`;
|
|
563
|
-
dot.style.top = `${rootRect.top + sy - rootRect.top}px`;
|
|
564
|
-
layer.appendChild(dot);
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
421
|
renderSnapMarkersForCorner() {
|
|
569
422
|
if (!this.showSnapMarkers)
|
|
570
423
|
return;
|
|
@@ -575,17 +428,23 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
575
428
|
if (!this.cornerResizeState)
|
|
576
429
|
return;
|
|
577
430
|
const rootRect = this.rootEl.getBoundingClientRect();
|
|
578
|
-
// Compute representative center lines from
|
|
431
|
+
// Compute representative center lines from the dividers being resized.
|
|
432
|
+
// st.{hs,vs}[i].container is the <mp-splitter>; the divider lives in its
|
|
433
|
+
// shadow at getSplitterDividers(splitter)[index].
|
|
579
434
|
let centerX = null;
|
|
580
435
|
let centerY = null;
|
|
581
436
|
const st = this.cornerResizeState;
|
|
582
437
|
if (st.vs.length > 0) {
|
|
583
|
-
const
|
|
438
|
+
const v0 = st.vs[0];
|
|
439
|
+
const vDiv = this.getSplitterDividers(v0.container)[v0.index];
|
|
440
|
+
const vRect = vDiv?.getBoundingClientRect();
|
|
584
441
|
if (vRect)
|
|
585
442
|
centerX = vRect.left + vRect.width / 2 - rootRect.left;
|
|
586
443
|
}
|
|
587
444
|
if (st.hs.length > 0) {
|
|
588
|
-
const
|
|
445
|
+
const h0 = st.hs[0];
|
|
446
|
+
const hDiv = this.getSplitterDividers(h0.container)[h0.index];
|
|
447
|
+
const hRect = hDiv?.getBoundingClientRect();
|
|
589
448
|
if (hRect)
|
|
590
449
|
centerY = hRect.top + hRect.height / 2 - rootRect.top;
|
|
591
450
|
}
|
|
@@ -623,19 +482,16 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
623
482
|
this.floatingLayouts = [];
|
|
624
483
|
this.titles = {};
|
|
625
484
|
this.pendingTabDragMetrics = null;
|
|
626
|
-
this.resizeState = null;
|
|
627
485
|
this.dragState = null;
|
|
628
486
|
this.floatingDragState = null;
|
|
629
487
|
this.floatingResizeState = null;
|
|
630
488
|
this.intersectionRaf = null;
|
|
631
|
-
this.
|
|
489
|
+
this.rootResizeObserver = null;
|
|
490
|
+
this.dockedMutationObserver = null;
|
|
632
491
|
this.cornerResizeState = null;
|
|
633
492
|
this.pointerTrackingActive = false;
|
|
634
493
|
this.dragPointerTrackingActive = false;
|
|
635
494
|
this.lastDragPointerPosition = null;
|
|
636
|
-
// Localized snapping while dragging a divider
|
|
637
|
-
this.activeSnapAxis = null;
|
|
638
|
-
this.activeSnapTargets = [];
|
|
639
495
|
// Localized snapping while dragging an intersection handle
|
|
640
496
|
this.cornerSnapXTargets = [];
|
|
641
497
|
this.cornerSnapYTargets = [];
|
|
@@ -643,12 +499,31 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
643
499
|
this.showSnapMarkers = false;
|
|
644
500
|
this.pendingDragEndTimeout = null;
|
|
645
501
|
this.previousSplitSizes = new Map();
|
|
502
|
+
this.instanceId = `mint-dock-${++MintDockManagerElement.instanceCounter}`;
|
|
503
|
+
// Set windowRef eagerly so connectedCallback's window-level drag listeners
|
|
504
|
+
// (added before firstUpdated runs) can actually attach. Without this,
|
|
505
|
+
// win?.addEventListener was a silent no-op on first connect and HTML5
|
|
506
|
+
// drag-to-detach gestures never reached the dock — the floating wrapper
|
|
507
|
+
// was created but stayed at its conversion-time coordinates because the
|
|
508
|
+
// 'drag' listener that updates its position was never attached.
|
|
509
|
+
this.windowRef = typeof window !== 'undefined' ? window : null;
|
|
510
|
+
this.onPointerMove = this.onPointerMove.bind(this);
|
|
511
|
+
this.onPointerUp = this.onPointerUp.bind(this);
|
|
512
|
+
this.onDragPointerMove = this.onDragPointerMove.bind(this);
|
|
513
|
+
this.onDragPointerUp = this.onDragPointerUp.bind(this);
|
|
514
|
+
this.onDragPointerCancel = this.onDragPointerCancel.bind(this);
|
|
515
|
+
this.onSplitterResize = this.onSplitterResize.bind(this);
|
|
516
|
+
}
|
|
517
|
+
render() {
|
|
518
|
+
return template;
|
|
519
|
+
}
|
|
520
|
+
firstUpdated() {
|
|
521
|
+
// Resolve document and window now that we are connected.
|
|
646
522
|
const documentRef = this.resolveDocument();
|
|
647
523
|
this.documentRef = documentRef;
|
|
648
524
|
this.windowRef = this.resolveWindow(documentRef);
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
shadowRoot.appendChild(template.content.cloneNode(true));
|
|
525
|
+
// Query the rendered shadow DOM for the dock skeleton.
|
|
526
|
+
const shadowRoot = this.shadowRoot;
|
|
652
527
|
const root = shadowRoot.querySelector('.dock-root');
|
|
653
528
|
if (!root) {
|
|
654
529
|
throw new Error('mint-dock-manager template is missing the root element.');
|
|
@@ -679,75 +554,54 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
679
554
|
this.dropIndicator = indicator;
|
|
680
555
|
this.dropJoystick = joystick;
|
|
681
556
|
this.dropJoystickButtons = joystickButtons;
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
this.
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
this.
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
this.
|
|
557
|
+
// Tag the docked surface with a root path so it can act as
|
|
558
|
+
// a drop target when the main layout is empty.
|
|
559
|
+
this.dockedEl.dataset['path'] = this.formatPath({ type: 'docked', segments: [] });
|
|
560
|
+
// Drop targeting (drop indicator + joystick zone selection) runs entirely
|
|
561
|
+
// off pointer-based hit-testing in updatePaneDragDropTargetFromPoint and
|
|
562
|
+
// findDropZoneByPoint — no HTML5 dragover/drop/dragleave listeners needed.
|
|
563
|
+
// Render any layout that was set before the shadow DOM existed.
|
|
564
|
+
this.renderLayout();
|
|
565
|
+
// Reactive triggers for intersection-handle re-rendering. Each observer
|
|
566
|
+
// wakes scheduleRenderIntersectionHandles() (rAF-coalesced), which means
|
|
567
|
+
// multiple notifications in the same frame collapse to one render and
|
|
568
|
+
// the rAF tick gives <mp-splitter> elements time to populate their
|
|
569
|
+
// shadow roots before we query their dividers.
|
|
570
|
+
this.rootResizeObserver = new ResizeObserver(() => this.scheduleRenderIntersectionHandles());
|
|
571
|
+
this.rootResizeObserver.observe(this.rootEl);
|
|
572
|
+
this.dockedMutationObserver = new MutationObserver(() => this.scheduleRenderIntersectionHandles());
|
|
573
|
+
this.dockedMutationObserver.observe(this.dockedEl, { childList: true, subtree: true });
|
|
574
|
+
// mp-splitter dispatches bubbling 'resizing' / 'resize-end' on user drag;
|
|
575
|
+
// delegating on dockedEl catches every nested splitter without per-instance wiring.
|
|
576
|
+
this.dockedEl.addEventListener('resizing', this.onSplitterResize);
|
|
577
|
+
this.dockedEl.addEventListener('resize-end', this.onSplitterResize);
|
|
696
578
|
}
|
|
697
579
|
connectedCallback() {
|
|
580
|
+
super.connectedCallback();
|
|
698
581
|
if (!this.hasAttribute('role')) {
|
|
699
582
|
this.setAttribute('role', 'application');
|
|
700
583
|
}
|
|
701
|
-
// Tag the docked surface with a root path so it can act as
|
|
702
|
-
// a drop target when the main layout is empty.
|
|
703
|
-
this.dockedEl.dataset['path'] = this.formatPath({ type: 'docked', segments: [] });
|
|
704
|
-
this.render();
|
|
705
|
-
this.rootEl.addEventListener('dragover', this.onDragOver);
|
|
706
|
-
this.rootEl.addEventListener('drop', this.onDrop);
|
|
707
|
-
this.rootEl.addEventListener('dragleave', this.onDragLeave);
|
|
708
|
-
this.dropJoystick.addEventListener('dragover', this.onDragOver);
|
|
709
|
-
this.dropJoystick.addEventListener('drop', this.onDrop);
|
|
710
|
-
this.dropJoystick.addEventListener('dragleave', this.onDragLeave);
|
|
711
|
-
// Strengthen zone tracking by reacting to dragenter/dragover directly on the buttons.
|
|
712
|
-
// This avoids relying solely on hit-testing each frame which can be jittery during HTML5 drag.
|
|
713
|
-
this.dropJoystickButtons.forEach((btn) => {
|
|
714
|
-
const handler = (e) => {
|
|
715
|
-
if (!this.dragState)
|
|
716
|
-
return;
|
|
717
|
-
const z = btn.dataset['zone'];
|
|
718
|
-
if (this.isDropZone(z)) {
|
|
719
|
-
this.updateDropJoystickActiveZone(z);
|
|
720
|
-
e.preventDefault();
|
|
721
|
-
}
|
|
722
|
-
};
|
|
723
|
-
btn.addEventListener('dragenter', handler);
|
|
724
|
-
btn.addEventListener('dragover', handler);
|
|
725
|
-
});
|
|
726
|
-
const win = this.windowRef;
|
|
727
|
-
win?.addEventListener('dragover', this.onGlobalDragOver);
|
|
728
|
-
win?.addEventListener('drag', this.onDrag);
|
|
729
|
-
win?.addEventListener('dragend', this.onGlobalDragEnd, true);
|
|
730
|
-
win?.addEventListener('resize', this.onWindowResize);
|
|
731
584
|
}
|
|
732
585
|
disconnectedCallback() {
|
|
733
|
-
this.rootEl.removeEventListener('dragover', this.onDragOver);
|
|
734
|
-
this.rootEl.removeEventListener('drop', this.onDrop);
|
|
735
|
-
this.rootEl.removeEventListener('dragleave', this.onDragLeave);
|
|
736
|
-
this.dropJoystick.removeEventListener('dragover', this.onDragOver);
|
|
737
|
-
this.dropJoystick.removeEventListener('drop', this.onDrop);
|
|
738
|
-
this.dropJoystick.removeEventListener('dragleave', this.onDragLeave);
|
|
739
586
|
const win = this.windowRef;
|
|
740
|
-
win?.removeEventListener('dragover', this.onGlobalDragOver);
|
|
741
|
-
win?.removeEventListener('drag', this.onDrag);
|
|
742
|
-
win?.removeEventListener('dragend', this.onGlobalDragEnd, true);
|
|
743
587
|
this.stopDragPointerTracking();
|
|
744
588
|
win?.removeEventListener('pointermove', this.onPointerMove);
|
|
745
589
|
win?.removeEventListener('pointerup', this.onPointerUp);
|
|
746
590
|
this.pointerTrackingActive = false;
|
|
747
|
-
|
|
748
|
-
|
|
591
|
+
this.rootResizeObserver?.disconnect();
|
|
592
|
+
this.rootResizeObserver = null;
|
|
593
|
+
this.dockedMutationObserver?.disconnect();
|
|
594
|
+
this.dockedMutationObserver = null;
|
|
595
|
+
this.dockedEl?.removeEventListener('resizing', this.onSplitterResize);
|
|
596
|
+
this.dockedEl?.removeEventListener('resize-end', this.onSplitterResize);
|
|
597
|
+
if (this.intersectionRaf !== null) {
|
|
598
|
+
this.windowRef?.clearTimeout(this.intersectionRaf);
|
|
599
|
+
this.intersectionRaf = null;
|
|
600
|
+
}
|
|
601
|
+
super.disconnectedCallback();
|
|
749
602
|
}
|
|
750
603
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
604
|
+
super.attributeChangedCallback(name, _oldValue, newValue);
|
|
751
605
|
if (name === 'layout') {
|
|
752
606
|
this.layout = newValue ? this.parseLayout(newValue) : null;
|
|
753
607
|
}
|
|
@@ -767,10 +621,51 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
767
621
|
}
|
|
768
622
|
set layout(value) {
|
|
769
623
|
const snapshot = this.ensureSnapshot(value);
|
|
624
|
+
// While a drag/resize is in flight, the dock manager is the source of
|
|
625
|
+
// truth for layout state — its mid-drag mutations (e.g. floating bounds
|
|
626
|
+
// updated every mousemove, or a stack split during a pane-drag-to-floating
|
|
627
|
+
// conversion) race the host's two-way binding round-trip. The host re-
|
|
628
|
+
// feeds the layout we *just* dispatched via `dock-layout-changed`, but by
|
|
629
|
+
// the time the round-trip arrives the user has moved the cursor again, so
|
|
630
|
+
// the structural-equality guard below would let it through and clobber the
|
|
631
|
+
// in-progress state (e.g. snap a freshly-detached floating window back to
|
|
632
|
+
// the converted-at coordinates instead of letting it follow the cursor).
|
|
633
|
+
// Reject any external layout write during interaction; the host will sync
|
|
634
|
+
// back to the dock's final state when interaction ends and the dock fires
|
|
635
|
+
// a fresh dock-layout-changed event.
|
|
636
|
+
if (this.isInteracting())
|
|
637
|
+
return;
|
|
638
|
+
// Skip renderLayout when the incoming layout is structurally identical
|
|
639
|
+
// to the current state. After a divider drag the dock dispatches
|
|
640
|
+
// dock-layout-changed; an Angular host doing two-way binding will feed
|
|
641
|
+
// that snapshot right back through `[layout]` (and through the
|
|
642
|
+
// `[attr.layout]` round-trip). Without this guard, every drag-end
|
|
643
|
+
// tears down and rebuilds the whole splitter tree, giving a one-frame
|
|
644
|
+
// flash of `flex: 1 1 0` equal-share before the pin restores sizes.
|
|
645
|
+
const currentJson = JSON.stringify({
|
|
646
|
+
root: this.rootLayout,
|
|
647
|
+
floating: this.floatingLayouts,
|
|
648
|
+
titles: this.titles,
|
|
649
|
+
});
|
|
650
|
+
const newJson = JSON.stringify(snapshot);
|
|
651
|
+
if (currentJson === newJson)
|
|
652
|
+
return;
|
|
770
653
|
this.rootLayout = this.cloneLayoutNode(snapshot.root);
|
|
771
654
|
this.floatingLayouts = this.cloneFloatingArray(snapshot.floating);
|
|
772
655
|
this.titles = snapshot.titles ? { ...snapshot.titles } : {};
|
|
773
|
-
this.
|
|
656
|
+
this.renderLayout();
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* True while the user is actively interacting with the dock — pane drag,
|
|
660
|
+
* floating window drag, floating window resize, or intersection corner
|
|
661
|
+
* resize. The `set layout` setter consults this to refuse external
|
|
662
|
+
* round-trips that would overwrite in-progress drag state.
|
|
663
|
+
*/
|
|
664
|
+
isInteracting() {
|
|
665
|
+
return !!(this.dragState ||
|
|
666
|
+
this.floatingDragState ||
|
|
667
|
+
this.floatingResizeState ||
|
|
668
|
+
this.cornerResizeState);
|
|
774
669
|
}
|
|
775
670
|
get snapshot() {
|
|
776
671
|
return this.layout;
|
|
@@ -823,7 +718,12 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
823
718
|
titles: layout.titles ? { ...layout.titles } : {},
|
|
824
719
|
};
|
|
825
720
|
}
|
|
826
|
-
|
|
721
|
+
renderLayout() {
|
|
722
|
+
// The layout setter may run before firstUpdated() has populated the
|
|
723
|
+
// shadow-DOM fields (e.g. when an attribute is set on the markup).
|
|
724
|
+
// Bail out; firstUpdated() will call renderLayout() once ready.
|
|
725
|
+
if (!this.dockedEl)
|
|
726
|
+
return;
|
|
827
727
|
this.dockedEl.innerHTML = '';
|
|
828
728
|
this.floatingLayerEl.innerHTML = '';
|
|
829
729
|
this.hideDropIndicator();
|
|
@@ -832,7 +732,10 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
832
732
|
this.dockedEl.appendChild(fragment);
|
|
833
733
|
}
|
|
834
734
|
this.renderFloatingPanes();
|
|
835
|
-
|
|
735
|
+
// Note: intersection handles are repositioned reactively via observers
|
|
736
|
+
// wired up in firstUpdated (rootResizeObserver, dockedMutationObserver,
|
|
737
|
+
// and delegated 'resizing' / 'resize-end' events). The MutationObserver
|
|
738
|
+
// on dockedEl fires when the renderNode subtree above is appended.
|
|
836
739
|
}
|
|
837
740
|
renderNode(node, path, floatingIndex) {
|
|
838
741
|
if (node.kind === 'split') {
|
|
@@ -927,104 +830,88 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
927
830
|
this.floatingLayerEl.appendChild(wrapper);
|
|
928
831
|
});
|
|
929
832
|
}
|
|
930
|
-
|
|
931
|
-
//
|
|
833
|
+
onSplitterResize() {
|
|
834
|
+
// mp-splitter dispatches 'resizing' continuously during a divider drag
|
|
835
|
+
// and 'resize-end' when the user releases. Both keep the handle glued
|
|
836
|
+
// to the new intersection coordinate.
|
|
932
837
|
this.scheduleRenderIntersectionHandles();
|
|
933
838
|
}
|
|
934
839
|
scheduleRenderIntersectionHandles() {
|
|
935
|
-
this.intersectionRaf
|
|
936
|
-
|
|
840
|
+
if (this.intersectionRaf !== null)
|
|
841
|
+
return;
|
|
842
|
+
const win = this.windowRef;
|
|
843
|
+
if (!win)
|
|
844
|
+
return;
|
|
845
|
+
// Defer with setTimeout(5) instead of rAF so we run AFTER any
|
|
846
|
+
// flex-redistribution settles. Sequence we have to wait through:
|
|
847
|
+
// (1) DOM mutation (e.g. panel removed by drop)
|
|
848
|
+
// (2) microtasks: <mp-splitter>'s slotchange + size-pinning rAF
|
|
849
|
+
// (3) layout flush
|
|
850
|
+
// A bare rAF can fire before (2) resolves, so getBoundingClientRect on
|
|
851
|
+
// the dividers reads a transient flex-distributed position and the
|
|
852
|
+
// glyph lands ~tens of pixels off. 5ms is past the microtask queue and
|
|
853
|
+
// past splitter's pinning rAF in practice, so the divider rects we
|
|
854
|
+
// read are the settled, post-pin values.
|
|
855
|
+
this.intersectionRaf = win.setTimeout(() => {
|
|
856
|
+
this.intersectionRaf = null;
|
|
857
|
+
this.renderIntersectionHandles();
|
|
858
|
+
}, 5);
|
|
937
859
|
}
|
|
938
860
|
renderIntersectionHandles() {
|
|
939
861
|
const layer = this.shadowRoot?.querySelector('.dock-intersections-layer, .dock-intersection-layer');
|
|
940
862
|
if (!layer)
|
|
941
863
|
return;
|
|
942
|
-
// Keep existing handles; we will diff and update positions
|
|
943
|
-
// 1) Clean up legacy handles (created before keying) that lack a data-key
|
|
944
|
-
Array.from(layer.querySelectorAll('.dock-intersection-handle'))
|
|
945
|
-
.filter((el) => !el.dataset['key'])
|
|
946
|
-
.forEach((el) => el.remove());
|
|
947
|
-
// 2) Rebuild the internal map from DOM to avoid drifting state and dedupe duplicates
|
|
948
|
-
const domByKey = new Map();
|
|
949
|
-
Array.from(layer.querySelectorAll('.dock-intersection-handle[data-key]')).forEach((el) => {
|
|
950
|
-
const key = el.dataset['key'] ?? '';
|
|
951
|
-
if (!key)
|
|
952
|
-
return;
|
|
953
|
-
if (domByKey.has(key)) {
|
|
954
|
-
// Remove duplicates with the same key, keep the first one
|
|
955
|
-
el.remove();
|
|
956
|
-
return;
|
|
957
|
-
}
|
|
958
|
-
domByKey.set(key, el);
|
|
959
|
-
// Ensure listener is attached only once
|
|
960
|
-
if (!el.dataset['listener']) {
|
|
961
|
-
el.dataset['listener'] = '1';
|
|
962
|
-
// Listener will be (re)assigned later when we know the current h/v pair
|
|
963
|
-
}
|
|
964
|
-
});
|
|
965
|
-
// Sync internal map with DOM
|
|
966
|
-
this.intersectionHandles = domByKey;
|
|
967
864
|
const rootRect = this.rootEl.getBoundingClientRect();
|
|
968
|
-
//
|
|
865
|
+
// Active corner-resize: keep st.handle alive (it owns pointer capture and
|
|
866
|
+
// the cornerResizeState references it). Update its position from current
|
|
867
|
+
// divider rects, drop every other handle by reference.
|
|
969
868
|
if (this.cornerResizeState) {
|
|
970
869
|
const st = this.cornerResizeState;
|
|
971
870
|
const h0 = st.hs[0];
|
|
972
871
|
const v0 = st.vs[0];
|
|
973
|
-
const
|
|
974
|
-
const
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
const hDiv = this.shadowRoot?.querySelector(`.dock-split__divider[data-path="${hPathStr}"][data-index="${h0.index}"]`);
|
|
978
|
-
const vDiv = this.shadowRoot?.querySelector(`.dock-split__divider[data-path="${vPathStr}"][data-index="${v0.index}"]`);
|
|
872
|
+
const hSplitter = this.findSplitterByPath(h0.path.segments);
|
|
873
|
+
const vSplitter = this.findSplitterByPath(v0.path.segments);
|
|
874
|
+
const hDiv = hSplitter ? this.getSplitterDividers(hSplitter)[h0.index] : null;
|
|
875
|
+
const vDiv = vSplitter ? this.getSplitterDividers(vSplitter)[v0.index] : null;
|
|
979
876
|
if (hDiv && vDiv) {
|
|
980
877
|
const hr = hDiv.getBoundingClientRect();
|
|
981
878
|
const vr = vDiv.getBoundingClientRect();
|
|
982
879
|
const x = vr.left + vr.width / 2 - rootRect.left;
|
|
983
880
|
const y = hr.top + hr.height / 2 - rootRect.top;
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
handle.dataset['key'] = key;
|
|
987
|
-
}
|
|
988
|
-
this.intersectionHandles.set(key, handle);
|
|
989
|
-
handle.style.left = `${x}px`;
|
|
990
|
-
handle.style.top = `${y}px`;
|
|
991
|
-
// Remove any other handles that don't match the active key
|
|
881
|
+
st.handle.style.left = `${x}px`;
|
|
882
|
+
st.handle.style.top = `${y}px`;
|
|
992
883
|
Array.from(layer.querySelectorAll('.dock-intersection-handle')).forEach((el) => {
|
|
993
|
-
if (
|
|
884
|
+
if (el !== st.handle)
|
|
994
885
|
el.remove();
|
|
995
|
-
}
|
|
996
886
|
});
|
|
997
|
-
// Normalize internal map as well
|
|
998
|
-
this.intersectionHandles = new Map([[key, handle]]);
|
|
999
887
|
}
|
|
1000
888
|
return;
|
|
1001
889
|
}
|
|
1002
|
-
|
|
890
|
+
// Idle path: full clear + rebuild. Cheaper to reason about than incremental
|
|
891
|
+
// diffing, and handles' positions are always derived from current divider
|
|
892
|
+
// rects so a layout change (drop, splitter restructure, flex redistribution)
|
|
893
|
+
// can never leave a stale glyph behind.
|
|
894
|
+
layer.replaceChildren();
|
|
1003
895
|
const hDividers = [];
|
|
1004
896
|
const vDividers = [];
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
const
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
vDividers.push(info);
|
|
1019
|
-
}
|
|
1020
|
-
else if (orientation === 'vertical') {
|
|
1021
|
-
hDividers.push(info);
|
|
1022
|
-
}
|
|
897
|
+
const allSplitters = Array.from(this.shadowRoot?.querySelectorAll('.dock-split') ?? []);
|
|
898
|
+
allSplitters.forEach((splitter) => {
|
|
899
|
+
const direction = splitter.dataset['direction'];
|
|
900
|
+
const pathStr = splitter.dataset['path'] ?? '';
|
|
901
|
+
this.getSplitterDividers(splitter).forEach((el, index) => {
|
|
902
|
+
const info = { rect: el.getBoundingClientRect(), pathStr, index };
|
|
903
|
+
// direction='horizontal' means children flow left-to-right, so the
|
|
904
|
+
// divider bars between them are VERTICAL (and vice-versa).
|
|
905
|
+
if (direction === 'horizontal')
|
|
906
|
+
vDividers.push(info);
|
|
907
|
+
else if (direction === 'vertical')
|
|
908
|
+
hDividers.push(info);
|
|
909
|
+
});
|
|
1023
910
|
});
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const
|
|
1027
|
-
const
|
|
911
|
+
// Group intersections that round to the same on-screen pixel, so two
|
|
912
|
+
// sibling splitters whose dividers happen to overlap share one handle.
|
|
913
|
+
const tol = 24;
|
|
914
|
+
const groups = new Map();
|
|
1028
915
|
hDividers.forEach((h) => {
|
|
1029
916
|
const hCenterY = h.rect.top + h.rect.height / 2;
|
|
1030
917
|
vDividers.forEach((v) => {
|
|
@@ -1035,53 +922,33 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1035
922
|
return;
|
|
1036
923
|
const x = vCenterX - rootRect.left;
|
|
1037
924
|
const y = hCenterY - rootRect.top;
|
|
1038
|
-
const key = `${h.pathStr}:${h.index}|${v.pathStr}:${v.index}`;
|
|
1039
925
|
const gk = `${Math.round(x)}:${Math.round(y)}`;
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
handle = this.intersectionHandles.get(key) ?? null;
|
|
1044
|
-
if (!handle) {
|
|
1045
|
-
handle = this.documentRef.createElement('div');
|
|
1046
|
-
handle.classList.add('dock-intersection-handle', 'glyph');
|
|
1047
|
-
handle.setAttribute('role', 'separator');
|
|
1048
|
-
handle.setAttribute('aria-label', 'Resize split intersection');
|
|
1049
|
-
handle.dataset['key'] = key;
|
|
1050
|
-
handle.dataset['listener'] = '1';
|
|
1051
|
-
handle.addEventListener('pointerdown', (ev) => this.beginCornerResize(ev, h, v, handle));
|
|
1052
|
-
handle.addEventListener('dblclick', (ev) => this.onIntersectionDoubleClick(ev, handle));
|
|
1053
|
-
layer.appendChild(handle);
|
|
1054
|
-
}
|
|
1055
|
-
groupMap.set(gk, handle);
|
|
1056
|
-
}
|
|
1057
|
-
// Track pairs for this group and map all pair keys to the same handle
|
|
1058
|
-
const arr = groupPairs.get(gk) ?? [];
|
|
1059
|
-
arr.push({ h: { pathStr: h.pathStr ?? '', index: h.index }, v: { pathStr: v.pathStr ?? '', index: v.index } });
|
|
1060
|
-
groupPairs.set(gk, arr);
|
|
1061
|
-
this.intersectionHandles.set(key, handle);
|
|
1062
|
-
// Update position for the grouped handle
|
|
1063
|
-
handle.style.left = `${x}px`;
|
|
1064
|
-
handle.style.top = `${y}px`;
|
|
926
|
+
const group = groups.get(gk) ?? { x, y, pairs: [] };
|
|
927
|
+
group.pairs.push({ h: { pathStr: h.pathStr, index: h.index }, v: { pathStr: v.pathStr, index: v.index } });
|
|
928
|
+
groups.set(gk, group);
|
|
1065
929
|
});
|
|
1066
930
|
});
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
handle.
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
931
|
+
groups.forEach((group, gk) => {
|
|
932
|
+
const handle = this.documentRef.createElement('div');
|
|
933
|
+
handle.classList.add('dock-intersection-handle', 'glyph');
|
|
934
|
+
handle.setAttribute('role', 'separator');
|
|
935
|
+
handle.setAttribute('aria-label', 'Resize split intersection');
|
|
936
|
+
const firstPair = group.pairs[0];
|
|
937
|
+
const key = `${firstPair.h.pathStr}:${firstPair.h.index}|${firstPair.v.pathStr}:${firstPair.v.index}`;
|
|
938
|
+
handle.dataset['key'] = key;
|
|
939
|
+
handle.dataset['pairs'] = JSON.stringify(group.pairs);
|
|
940
|
+
handle.style.left = `${group.x}px`;
|
|
941
|
+
handle.style.top = `${group.y}px`;
|
|
942
|
+
// beginCornerResize/onIntersectionDoubleClick read data-pairs to
|
|
943
|
+
// reconstruct the (h, v) pair list, so the (h, v) args we pass here
|
|
944
|
+
// are only used as a fallback when data-pairs is empty — safe to use
|
|
945
|
+
// the first pair's structure as the seed.
|
|
946
|
+
const seedH = { path: this.parsePath(firstPair.h.pathStr), index: firstPair.h.index, container: this.findSplitterByPath(this.parsePath(firstPair.h.pathStr)?.segments ?? []) ?? this.rootEl, rect: new DOMRect() };
|
|
947
|
+
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() };
|
|
948
|
+
handle.addEventListener('pointerdown', (ev) => this.beginCornerResize(ev, seedH, seedV, handle));
|
|
949
|
+
handle.addEventListener('dblclick', (ev) => this.onIntersectionDoubleClick(ev, handle));
|
|
950
|
+
layer.appendChild(handle);
|
|
1083
951
|
});
|
|
1084
|
-
this.intersectionHandles = newMap;
|
|
1085
952
|
}
|
|
1086
953
|
beginCornerResize(event, h, v, handle) {
|
|
1087
954
|
event.preventDefault();
|
|
@@ -1094,20 +961,22 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1094
961
|
const path = this.parsePath(pathStr);
|
|
1095
962
|
if (!path)
|
|
1096
963
|
return;
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
if (!container)
|
|
964
|
+
const splitter = this.findSplitterByPath(path.segments);
|
|
965
|
+
if (!splitter)
|
|
1100
966
|
return;
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
967
|
+
// Initial pixel sizes come from each panel-wrapper inside the splitter's
|
|
968
|
+
// shadow root. We capture them once on pointerdown and feed deltas to
|
|
969
|
+
// setPanelSizes() during the drag.
|
|
970
|
+
const panels = this.getSplitterPanels(splitter);
|
|
971
|
+
if (panels.length === 0)
|
|
972
|
+
return;
|
|
973
|
+
const dim = axis === 'h' ? 'height' : 'width';
|
|
974
|
+
const initial = panels.map((p) => p.getBoundingClientRect()[dim]);
|
|
975
|
+
const entry = { path, index, container: splitter, initialSizes: initial, before: initial[index], after: initial[index + 1] };
|
|
976
|
+
if (axis === 'h')
|
|
977
|
+
hs.push(entry);
|
|
978
|
+
else
|
|
979
|
+
vs.push(entry);
|
|
1111
980
|
};
|
|
1112
981
|
if (parsed.length > 0) {
|
|
1113
982
|
parsed.forEach((p) => { ensureHV(p.h.pathStr, p.h.index, 'h'); ensureHV(p.v.pathStr, p.v.index, 'v'); });
|
|
@@ -1139,44 +1008,47 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1139
1008
|
// Compute localized snap targets for this intersection
|
|
1140
1009
|
try {
|
|
1141
1010
|
const rootRect = this.rootEl.getBoundingClientRect();
|
|
1142
|
-
// Use first pair to define the crossing lines
|
|
1011
|
+
// Use first pair to define the crossing lines. Resolve dividers via
|
|
1012
|
+
// each splitter's shadow root.
|
|
1143
1013
|
let centerX = null;
|
|
1144
1014
|
let centerY = null;
|
|
1145
|
-
// Resolve one vertical bar (from vs) and one horizontal bar (from hs)
|
|
1146
1015
|
if (vs.length > 0) {
|
|
1147
1016
|
const vPair = vs[0];
|
|
1148
|
-
const
|
|
1149
|
-
const vDiv = this.shadowRoot?.querySelector(`.dock-split__divider[data-path="${vPathStr}"][data-index="${vPair.index}"]`) ?? null;
|
|
1017
|
+
const vDiv = this.getSplitterDividers(vPair.container)[vPair.index];
|
|
1150
1018
|
const vr = vDiv?.getBoundingClientRect();
|
|
1151
1019
|
if (vr)
|
|
1152
1020
|
centerX = vr.left + vr.width / 2;
|
|
1153
1021
|
}
|
|
1154
1022
|
if (hs.length > 0) {
|
|
1155
1023
|
const hPair = hs[0];
|
|
1156
|
-
const
|
|
1157
|
-
const hDiv = this.shadowRoot?.querySelector(`.dock-split__divider[data-path="${hPathStr}"][data-index="${hPair.index}"]`) ?? null;
|
|
1024
|
+
const hDiv = this.getSplitterDividers(hPair.container)[hPair.index];
|
|
1158
1025
|
const hr = hDiv?.getBoundingClientRect();
|
|
1159
1026
|
if (hr)
|
|
1160
1027
|
centerY = hr.top + hr.height / 2;
|
|
1161
1028
|
}
|
|
1162
1029
|
const xTargets = [];
|
|
1163
1030
|
const yTargets = [];
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1031
|
+
// Iterate every splitter, then flat-map its shadow dividers — a
|
|
1032
|
+
// splitter's data-direction tells us whether its bars are vertical
|
|
1033
|
+
// (horizontal split) or horizontal (vertical split).
|
|
1034
|
+
const allSplitters = Array.from(this.shadowRoot?.querySelectorAll('.dock-split') ?? []);
|
|
1035
|
+
allSplitters.forEach((splitter) => {
|
|
1036
|
+
const direction = splitter.dataset['direction'] ?? undefined;
|
|
1037
|
+
this.getSplitterDividers(splitter).forEach((el) => {
|
|
1038
|
+
const r = el.getBoundingClientRect();
|
|
1039
|
+
if (direction === 'horizontal' && centerY != null) {
|
|
1040
|
+
// vertical bar → contributes X if it crosses centerY
|
|
1041
|
+
if (centerY >= r.top && centerY <= r.bottom) {
|
|
1042
|
+
xTargets.push(r.left + r.width / 2 - rootRect.left);
|
|
1043
|
+
}
|
|
1172
1044
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1045
|
+
else if (direction === 'vertical' && centerX != null) {
|
|
1046
|
+
// horizontal bar → contributes Y if it crosses centerX
|
|
1047
|
+
if (centerX >= r.left && centerX <= r.right) {
|
|
1048
|
+
yTargets.push(r.top + r.height / 2 - rootRect.top);
|
|
1049
|
+
}
|
|
1178
1050
|
}
|
|
1179
|
-
}
|
|
1051
|
+
});
|
|
1180
1052
|
});
|
|
1181
1053
|
this.cornerSnapXTargets = xTargets;
|
|
1182
1054
|
this.cornerSnapYTargets = yTargets;
|
|
@@ -1237,49 +1109,37 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1237
1109
|
if (bestDist <= tol)
|
|
1238
1110
|
clientY = best;
|
|
1239
1111
|
}
|
|
1240
|
-
//
|
|
1241
|
-
|
|
1242
|
-
|
|
1112
|
+
// Apply the new pair sizes to one splitter's panel-wrappers via
|
|
1113
|
+
// mp-splitter's setPanelSizes(pixels) API. We persist the normalized
|
|
1114
|
+
// ratios on the layout node so renderSplit's initial sizing stays in sync.
|
|
1115
|
+
const applyPairSize = (entry, delta) => {
|
|
1116
|
+
const node = this.resolveSplitNode(entry.path);
|
|
1243
1117
|
if (!node)
|
|
1244
1118
|
return;
|
|
1245
|
-
const deltaY = clientY - h.startY;
|
|
1246
1119
|
const minSize = 48;
|
|
1247
|
-
const pairTotal =
|
|
1248
|
-
let newBefore = Math.min(Math.max(
|
|
1120
|
+
const pairTotal = entry.beforeSize + entry.afterSize;
|
|
1121
|
+
let newBefore = Math.min(Math.max(entry.beforeSize + delta, minSize), pairTotal - minSize);
|
|
1249
1122
|
newBefore = snapValue(newBefore, pairTotal, event.shiftKey);
|
|
1250
1123
|
const newAfter = pairTotal - newBefore;
|
|
1251
|
-
const sizesPx = [...
|
|
1252
|
-
sizesPx[
|
|
1253
|
-
sizesPx[
|
|
1124
|
+
const sizesPx = [...entry.initialSizes];
|
|
1125
|
+
sizesPx[entry.index] = newBefore;
|
|
1126
|
+
sizesPx[entry.index + 1] = newAfter;
|
|
1254
1127
|
const total = sizesPx.reduce((a, s) => a + s, 0);
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
state.vs.forEach((v) =>
|
|
1263
|
-
const node = this.resolveSplitNode(v.path);
|
|
1264
|
-
if (!node)
|
|
1265
|
-
return;
|
|
1266
|
-
const deltaX = clientX - v.startX;
|
|
1267
|
-
const minSize = 48;
|
|
1268
|
-
const pairTotal = v.beforeSize + v.afterSize;
|
|
1269
|
-
let newBefore = Math.min(Math.max(v.beforeSize + deltaX, minSize), pairTotal - minSize);
|
|
1270
|
-
newBefore = snapValue(newBefore, pairTotal, event.shiftKey);
|
|
1271
|
-
const newAfter = pairTotal - newBefore;
|
|
1272
|
-
const sizesPx = [...v.initialSizes];
|
|
1273
|
-
sizesPx[v.index] = newBefore;
|
|
1274
|
-
sizesPx[v.index + 1] = newAfter;
|
|
1275
|
-
const total = sizesPx.reduce((a, s) => a + s, 0);
|
|
1276
|
-
const normalized = total > 0 ? sizesPx.map((s) => s / total) : [];
|
|
1277
|
-
node.sizes = normalized;
|
|
1278
|
-
const children = Array.from(v.container.querySelectorAll(':scope > .dock-split__child'));
|
|
1279
|
-
normalized.forEach((size, idx) => { if (children[idx])
|
|
1280
|
-
children[idx].style.flex = `${Math.max(size, 0)} 1 0`; });
|
|
1281
|
-
});
|
|
1128
|
+
node.sizes = total > 0 ? sizesPx.map((s) => s / total) : [];
|
|
1129
|
+
entry.container
|
|
1130
|
+
.setPanelSizes?.(sizesPx);
|
|
1131
|
+
};
|
|
1132
|
+
// Update all horizontal bars (vertical splits) with Y delta, then all
|
|
1133
|
+
// vertical bars (horizontal splits) with X delta.
|
|
1134
|
+
state.hs.forEach((h) => applyPairSize(h, clientY - h.startY));
|
|
1135
|
+
state.vs.forEach((v) => applyPairSize(v, clientX - v.startX));
|
|
1282
1136
|
this.dispatchLayoutChanged();
|
|
1137
|
+
// setPanelSizes() is programmatic and doesn't fire 'resizing' events, so
|
|
1138
|
+
// the delegated listener on dockedEl doesn't wake during a corner drag.
|
|
1139
|
+
// Schedule the handle repositioning ourselves; renderIntersectionHandles
|
|
1140
|
+
// has a fast-path for the active cornerResizeState that just updates
|
|
1141
|
+
// left/top from the new divider rects.
|
|
1142
|
+
this.scheduleRenderIntersectionHandles();
|
|
1283
1143
|
}
|
|
1284
1144
|
endCornerResize(pointerId) {
|
|
1285
1145
|
const state = this.cornerResizeState;
|
|
@@ -1323,7 +1183,28 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1323
1183
|
let hasStored = false;
|
|
1324
1184
|
splitKeys.forEach((k) => { if (this.previousSplitSizes.has(k))
|
|
1325
1185
|
hasStored = true; });
|
|
1326
|
-
|
|
1186
|
+
// Persist `node.sizes` (normalized) and push pixel sizes into the
|
|
1187
|
+
// matching <mp-splitter> via setPanelSizes(). The splitter's panel
|
|
1188
|
+
// wrappers live in its shadow DOM, so direct flex mutation is no
|
|
1189
|
+
// longer an option.
|
|
1190
|
+
const pushSizesToSplitter = (path, normalized) => {
|
|
1191
|
+
const splitter = this.findSplitterByPath(path.segments);
|
|
1192
|
+
if (!splitter)
|
|
1193
|
+
return;
|
|
1194
|
+
const direction = splitter.dataset['direction'] ?? 'horizontal';
|
|
1195
|
+
const containerSize = direction === 'horizontal'
|
|
1196
|
+
? splitter.getBoundingClientRect().width
|
|
1197
|
+
: splitter.getBoundingClientRect().height;
|
|
1198
|
+
if (!Number.isFinite(containerSize) || containerSize <= 0)
|
|
1199
|
+
return;
|
|
1200
|
+
const totalWeight = normalized.reduce((s, w) => s + Math.max(w, 0), 0);
|
|
1201
|
+
if (totalWeight <= 0)
|
|
1202
|
+
return;
|
|
1203
|
+
const px = normalized.map((w) => (Math.max(w, 0) / totalWeight) * containerSize);
|
|
1204
|
+
splitter
|
|
1205
|
+
.setPanelSizes?.(px);
|
|
1206
|
+
};
|
|
1207
|
+
const applySizes = (pathStr, dividerIndex, mutate) => {
|
|
1327
1208
|
const path = this.parsePath(pathStr);
|
|
1328
1209
|
if (!path)
|
|
1329
1210
|
return;
|
|
@@ -1331,35 +1212,20 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1331
1212
|
if (!node)
|
|
1332
1213
|
return;
|
|
1333
1214
|
const sizes = this.normalizeSizesArray(node.sizes ?? [], node.children.length);
|
|
1334
|
-
|
|
1335
|
-
const divEl = this.shadowRoot?.querySelector(`.dock-split__divider[data-path="${pathStr}"]`);
|
|
1336
|
-
const index = divEl ? Number.parseInt(divEl.dataset['index'] ?? '0', 10) : 0;
|
|
1337
|
-
const newSizes = mutate([...sizes], index);
|
|
1215
|
+
const newSizes = mutate([...sizes], dividerIndex);
|
|
1338
1216
|
node.sizes = newSizes;
|
|
1339
|
-
|
|
1340
|
-
const container = this.shadowRoot?.querySelector(`.dock-split[data-path="${segments}"]`);
|
|
1341
|
-
if (container) {
|
|
1342
|
-
const children = Array.from(container.querySelectorAll(':scope > .dock-split__child'));
|
|
1343
|
-
newSizes.forEach((s, i) => { if (children[i])
|
|
1344
|
-
children[i].style.flex = `${Math.max(s, 0)} 1 0`; });
|
|
1345
|
-
}
|
|
1217
|
+
pushSizesToSplitter(path, newSizes);
|
|
1346
1218
|
};
|
|
1347
1219
|
if (hasStored) {
|
|
1348
1220
|
// Restore stored sizes
|
|
1349
1221
|
this.previousSplitSizes.forEach((sizes, pathStr) => {
|
|
1350
1222
|
const path = this.parsePath(pathStr);
|
|
1351
1223
|
const node = path ? this.resolveSplitNode(path) : null;
|
|
1352
|
-
if (!node)
|
|
1224
|
+
if (!node || !path)
|
|
1353
1225
|
return;
|
|
1354
1226
|
const norm = this.normalizeSizesArray(sizes, node.children.length);
|
|
1355
1227
|
node.sizes = norm;
|
|
1356
|
-
|
|
1357
|
-
const container = this.shadowRoot?.querySelector(`.dock-split[data-path="${segments}"]`);
|
|
1358
|
-
if (container) {
|
|
1359
|
-
const children = Array.from(container.querySelectorAll(':scope > .dock-split__child'));
|
|
1360
|
-
norm.forEach((s, i) => { if (children[i])
|
|
1361
|
-
children[i].style.flex = `${Math.max(s, 0)} 1 0`; });
|
|
1362
|
-
}
|
|
1228
|
+
pushSizesToSplitter(path, norm);
|
|
1363
1229
|
});
|
|
1364
1230
|
this.previousSplitSizes.clear();
|
|
1365
1231
|
}
|
|
@@ -1377,16 +1243,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1377
1243
|
}
|
|
1378
1244
|
touched.add(key);
|
|
1379
1245
|
});
|
|
1380
|
-
|
|
1381
|
-
const total = (sizes[idx] ?? 0) + (sizes[idx + 1] ?? 0);
|
|
1382
|
-
if (total <= 0)
|
|
1383
|
-
return sizes;
|
|
1384
|
-
sizes[idx] = total / 2;
|
|
1385
|
-
sizes[idx + 1] = total / 2;
|
|
1386
|
-
const sum = sizes.reduce((a, s) => a + s, 0);
|
|
1387
|
-
return sum > 0 ? sizes.map((s) => s / sum) : sizes;
|
|
1388
|
-
});
|
|
1389
|
-
applySizes(p.v.pathStr, (sizes, idx) => {
|
|
1246
|
+
const equalize = (sizes, idx) => {
|
|
1390
1247
|
const total = (sizes[idx] ?? 0) + (sizes[idx + 1] ?? 0);
|
|
1391
1248
|
if (total <= 0)
|
|
1392
1249
|
return sizes;
|
|
@@ -1394,7 +1251,9 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1394
1251
|
sizes[idx + 1] = total / 2;
|
|
1395
1252
|
const sum = sizes.reduce((a, s) => a + s, 0);
|
|
1396
1253
|
return sum > 0 ? sizes.map((s) => s / sum) : sizes;
|
|
1397
|
-
}
|
|
1254
|
+
};
|
|
1255
|
+
applySizes(p.h.pathStr, p.h.index, equalize);
|
|
1256
|
+
applySizes(p.v.pathStr, p.v.index, equalize);
|
|
1398
1257
|
});
|
|
1399
1258
|
}
|
|
1400
1259
|
this.dispatchLayoutChanged();
|
|
@@ -1482,11 +1341,13 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1482
1341
|
}
|
|
1483
1342
|
try {
|
|
1484
1343
|
state.handle.releasePointerCapture(state.pointerId);
|
|
1485
|
-
delete state.handle.dataset['resizing'];
|
|
1486
1344
|
}
|
|
1487
1345
|
catch (err) {
|
|
1488
1346
|
/* no-op */
|
|
1489
1347
|
}
|
|
1348
|
+
// Clear outside the try so a thrown releasePointerCapture (capture
|
|
1349
|
+
// already lost) doesn't strand the handle in its visual drag state.
|
|
1350
|
+
delete state.handle.dataset['resizing'];
|
|
1490
1351
|
const dropHandled = state.dropTarget
|
|
1491
1352
|
? this.handleFloatingStackDrop(state.index, state.dropTarget.path, state.dropTarget.zone)
|
|
1492
1353
|
: false;
|
|
@@ -1582,6 +1443,12 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1582
1443
|
catch (err) {
|
|
1583
1444
|
/* no-op */
|
|
1584
1445
|
}
|
|
1446
|
+
// Clear `data-resizing` outside the try — releasePointerCapture can
|
|
1447
|
+
// throw if the capture was already lost (e.g., the pointer left the
|
|
1448
|
+
// window), and we still need to drop the resizing attribute or the
|
|
1449
|
+
// CSS rule `.dock-floating__resizer[data-resizing='true']` keeps the
|
|
1450
|
+
// border dark-blue forever.
|
|
1451
|
+
delete state.handle.dataset['resizing'];
|
|
1585
1452
|
this.floatingResizeState = null;
|
|
1586
1453
|
this.dispatchLayoutChanged();
|
|
1587
1454
|
}
|
|
@@ -1630,7 +1497,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1630
1497
|
}
|
|
1631
1498
|
stopPointerTrackingIfIdle() {
|
|
1632
1499
|
if (this.pointerTrackingActive &&
|
|
1633
|
-
!this.resizeState &&
|
|
1634
1500
|
!this.floatingDragState &&
|
|
1635
1501
|
!this.floatingResizeState &&
|
|
1636
1502
|
!this.cornerResizeState) {
|
|
@@ -1672,63 +1538,102 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1672
1538
|
titleEl.textContent = this.getFloatingWindowTitle(floating);
|
|
1673
1539
|
}
|
|
1674
1540
|
renderSplit(node, path, floatingIndex) {
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
const
|
|
1541
|
+
// Each DockSplitNode renders as <mp-splitter>. The dock keeps its `.dock-split`
|
|
1542
|
+
// class on the host so existing `closest('.dock-split')` queries continue to
|
|
1543
|
+
// resolve, and stamps `data-direction` / `data-path` for the tree-driven
|
|
1544
|
+
// intersection-handle math.
|
|
1545
|
+
const splitter = this.documentRef.createElement('mp-splitter');
|
|
1546
|
+
splitter.classList.add('dock-split');
|
|
1547
|
+
splitter.dataset['direction'] = node.direction;
|
|
1548
|
+
splitter.dataset['path'] = path.join('/');
|
|
1549
|
+
// mp-splitter uses 'horizontal' (left-right) and 'vertical' (top-bottom).
|
|
1550
|
+
// The dock's DockSplitNode.direction matches that vocabulary 1:1.
|
|
1551
|
+
splitter.setAttribute('orientation', node.direction);
|
|
1552
|
+
const splitPath = typeof floatingIndex === 'number'
|
|
1553
|
+
? { type: 'floating', index: floatingIndex, segments: [...path] }
|
|
1554
|
+
: { type: 'docked', segments: [...path] };
|
|
1680
1555
|
node.children.forEach((child, index) => {
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1556
|
+
// mp-splitter accepts direct children — it wraps each in a panel-wrapper
|
|
1557
|
+
// inside its shadow DOM and projects via a named slot per index.
|
|
1558
|
+
splitter.appendChild(this.renderNode(child, [...path, index], floatingIndex));
|
|
1559
|
+
});
|
|
1560
|
+
// Apply persisted sizes from the layout tree once mp-splitter has built
|
|
1561
|
+
// its panel wrappers. mp-splitter's setPanelSizes interprets values as
|
|
1562
|
+
// pixel widths/heights; the dock's saved sizes are flex weights, so
|
|
1563
|
+
// convert using the splitter's measured cross-axis container size.
|
|
1564
|
+
const sizes = Array.isArray(node.sizes) ? node.sizes : [];
|
|
1565
|
+
if (sizes.length > 0) {
|
|
1566
|
+
requestAnimationFrame(() => {
|
|
1567
|
+
const totalWeight = sizes.reduce((s, w) => s + Math.max(w, 0), 0);
|
|
1568
|
+
if (totalWeight <= 0)
|
|
1569
|
+
return;
|
|
1570
|
+
const containerSize = node.direction === 'horizontal'
|
|
1571
|
+
? splitter.getBoundingClientRect().width
|
|
1572
|
+
: splitter.getBoundingClientRect().height;
|
|
1573
|
+
if (!Number.isFinite(containerSize) || containerSize <= 0)
|
|
1574
|
+
return;
|
|
1575
|
+
const px = sizes.map((w) => (Math.max(w, 0) / totalWeight) * containerSize);
|
|
1576
|
+
splitter
|
|
1577
|
+
.setPanelSizes?.(px);
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
// mp-splitter fires resize-end with pixel sizes after a divider drag.
|
|
1581
|
+
// Convert back to flex weights (sum to a stable total — keep current sum
|
|
1582
|
+
// so future renders interpret consistently) and persist to the layout tree.
|
|
1583
|
+
splitter.addEventListener('resize-end', (event) => {
|
|
1584
|
+
// resize-end bubbles, so a nested mp-splitter's drag end would also
|
|
1585
|
+
// reach this listener. Only react to events from THIS splitter, not
|
|
1586
|
+
// from a descendant — otherwise we'd apply the inner's sizes to the
|
|
1587
|
+
// outer's splitNode and mangle the outer's weights.
|
|
1588
|
+
if (event.target !== splitter)
|
|
1589
|
+
return;
|
|
1590
|
+
const detail = event.detail;
|
|
1591
|
+
if (!Array.isArray(detail?.sizes) || detail.sizes.length === 0)
|
|
1592
|
+
return;
|
|
1593
|
+
const splitNode = this.resolveSplitNode(splitPath);
|
|
1594
|
+
if (!splitNode)
|
|
1595
|
+
return;
|
|
1596
|
+
const previousTotal = (splitNode.sizes ?? []).reduce((s, w) => s + Math.max(w, 0), 0);
|
|
1597
|
+
const total = detail.sizes.reduce((s, v) => s + Math.max(v, 0), 0);
|
|
1598
|
+
const targetTotal = previousTotal > 0 ? previousTotal : detail.sizes.length;
|
|
1599
|
+
if (total > 0) {
|
|
1600
|
+
splitNode.sizes = detail.sizes.map((px) => (Math.max(px, 0) / total) * targetTotal);
|
|
1601
|
+
this.dispatchLayoutChanged();
|
|
1709
1602
|
}
|
|
1710
1603
|
});
|
|
1711
|
-
return
|
|
1604
|
+
return splitter;
|
|
1712
1605
|
}
|
|
1713
1606
|
renderStack(node, path, floatingIndex) {
|
|
1714
|
-
|
|
1607
|
+
// Dock stacks are rendered as <mp-tab-control>. The dock keeps `.dock-stack`
|
|
1608
|
+
// as a class on the host so existing `closest('.dock-stack')` queries
|
|
1609
|
+
// continue to resolve. The tab strip + body slot projection are owned by
|
|
1610
|
+
// mp-tab-control; the dock just provides the slotted header/content
|
|
1611
|
+
// elements and listens for tab-activate to drive layout-tree updates.
|
|
1612
|
+
const stack = this.documentRef.createElement('mp-tab-control');
|
|
1715
1613
|
stack.classList.add('dock-stack');
|
|
1614
|
+
// Dock controls activation; tell mp-tab-control not to auto-pick.
|
|
1615
|
+
stack.setAttribute('select-first-tab', 'false');
|
|
1616
|
+
// `border="top"` gives us the strip-cutout line under the tabs (so the
|
|
1617
|
+
// active tab visually punches through into the body) without adding the
|
|
1618
|
+
// full Bootstrap frame, which would double up with the dock's own outer
|
|
1619
|
+
// chrome border on `.dock-stack` (and on `.dock-floating` for floating
|
|
1620
|
+
// panels).
|
|
1621
|
+
stack.setAttribute('border', 'top');
|
|
1716
1622
|
const location = typeof floatingIndex === 'number'
|
|
1717
1623
|
? { type: 'floating', index: floatingIndex, segments: [...path] }
|
|
1718
1624
|
: { type: 'docked', segments: [...path] };
|
|
1719
1625
|
stack.dataset['path'] = this.formatPath(location);
|
|
1720
|
-
const header = this.documentRef.createElement('div');
|
|
1721
|
-
header.classList.add('dock-stack__header');
|
|
1722
|
-
header.setAttribute('role', 'tablist');
|
|
1723
|
-
const content = this.documentRef.createElement('div');
|
|
1724
|
-
content.classList.add('dock-stack__content');
|
|
1725
1626
|
const panes = Array.from(new Set(node.panes));
|
|
1726
1627
|
if (panes.length === 0) {
|
|
1628
|
+
const emptyHeader = this.documentRef.createElement('span');
|
|
1629
|
+
emptyHeader.setAttribute('slot', '__empty__-header');
|
|
1630
|
+
emptyHeader.textContent = '(empty)';
|
|
1727
1631
|
const empty = this.documentRef.createElement('div');
|
|
1632
|
+
empty.setAttribute('slot', '__empty__-content');
|
|
1728
1633
|
empty.classList.add('dock-stack__pane');
|
|
1729
1634
|
empty.textContent = 'No panes configured';
|
|
1730
|
-
|
|
1731
|
-
stack.
|
|
1635
|
+
stack.append(emptyHeader, empty);
|
|
1636
|
+
stack.setAttribute('active-tab', '__empty__');
|
|
1732
1637
|
return stack;
|
|
1733
1638
|
}
|
|
1734
1639
|
const activePane = panes.includes(node.activePane ?? '')
|
|
@@ -1741,228 +1646,114 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1741
1646
|
const paneSlug = paneSlugRaw.length > 0 ? paneSlugRaw : 'pane';
|
|
1742
1647
|
const tabId = `${this.instanceId}-tab-${pathSlug}-${paneSlug}`;
|
|
1743
1648
|
const panelId = `${this.instanceId}-panel-${pathSlug}-${paneSlug}`;
|
|
1744
|
-
|
|
1745
|
-
button.
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
this.captureTabDragMetrics(event,
|
|
1649
|
+
// Header span — projected via mp-tab-control's `${tabId}-header` slot
|
|
1650
|
+
// into the strip's button content. Carries the dock's drag handlers.
|
|
1651
|
+
const headerSpan = this.documentRef.createElement('span');
|
|
1652
|
+
headerSpan.setAttribute('slot', `${tabId}-header`);
|
|
1653
|
+
headerSpan.classList.add('dock-tab');
|
|
1654
|
+
headerSpan.dataset['pane'] = paneName;
|
|
1655
|
+
headerSpan.dataset['tabId'] = tabId;
|
|
1656
|
+
headerSpan.textContent = this.titles[paneName] ?? paneName;
|
|
1657
|
+
// Pointer-only drag (no HTML5 dnd). pointerdown captures metrics + arms
|
|
1658
|
+
// a threshold gesture; once the pointer moves >threshold pixels we
|
|
1659
|
+
// promote it to a real pane drag via beginPaneDrag. Using pointer
|
|
1660
|
+
// events sidesteps the entire class of HTML5 dnd quirks (cancellation
|
|
1661
|
+
// when source DOM is removed mid-drag, suppressed mousemove, bogus 0/0
|
|
1662
|
+
// coordinates in Firefox, browser-specific drag-image behavior).
|
|
1663
|
+
headerSpan.addEventListener('pointerdown', (event) => {
|
|
1664
|
+
this.captureTabDragMetrics(event, stack);
|
|
1665
|
+
this.armPaneDragGesture(event, this.clonePath(location), paneName, stack);
|
|
1760
1666
|
event.stopPropagation();
|
|
1761
1667
|
});
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
const stackEl = button.closest('.dock-stack');
|
|
1766
|
-
this.beginPaneDrag(event, this.clonePath(location), paneName, stackEl ?? null);
|
|
1767
|
-
});
|
|
1768
|
-
button.addEventListener('dragend', () => {
|
|
1769
|
-
this.endPaneDrag();
|
|
1770
|
-
this.clearPendingTabDragMetrics();
|
|
1771
|
-
});
|
|
1772
|
-
button.addEventListener('click', () => {
|
|
1773
|
-
this.activatePane(stack, paneName, this.clonePath(location));
|
|
1774
|
-
this.dispatchEvent(new CustomEvent('dock-pane-activated', {
|
|
1775
|
-
detail: { pane: paneName },
|
|
1776
|
-
bubbles: true,
|
|
1777
|
-
composed: true,
|
|
1778
|
-
}));
|
|
1779
|
-
});
|
|
1780
|
-
header.appendChild(button);
|
|
1668
|
+
// Content wrapper — projected via mp-tab-control's `${tabId}-content`
|
|
1669
|
+
// slot only when this tab is active. Holds the dock manager's per-pane
|
|
1670
|
+
// <slot> for the consumer's content.
|
|
1781
1671
|
const paneHost = this.documentRef.createElement('div');
|
|
1672
|
+
paneHost.setAttribute('slot', `${tabId}-content`);
|
|
1782
1673
|
paneHost.classList.add('dock-stack__pane');
|
|
1783
1674
|
paneHost.dataset['pane'] = paneName;
|
|
1675
|
+
paneHost.dataset['tabId'] = tabId;
|
|
1784
1676
|
paneHost.id = panelId;
|
|
1785
|
-
paneHost.setAttribute('role', 'tabpanel');
|
|
1786
|
-
paneHost.setAttribute('aria-labelledby', tabId);
|
|
1787
|
-
if (paneName !== activePane) {
|
|
1788
|
-
paneHost.setAttribute('hidden', '');
|
|
1789
|
-
}
|
|
1790
1677
|
const slotEl = this.documentRef.createElement('slot');
|
|
1791
1678
|
slotEl.name = paneName;
|
|
1792
1679
|
paneHost.appendChild(slotEl);
|
|
1793
|
-
|
|
1680
|
+
stack.append(headerSpan, paneHost);
|
|
1681
|
+
if (paneName === activePane) {
|
|
1682
|
+
stack.setAttribute('active-tab', tabId);
|
|
1683
|
+
}
|
|
1794
1684
|
});
|
|
1795
1685
|
stack.dataset['activePane'] = activePane;
|
|
1796
|
-
|
|
1686
|
+
// Drive activatePane from mp-tab-control's tab-activate event. We map the
|
|
1687
|
+
// tabId back to the original paneName via the header span's data-pane.
|
|
1688
|
+
stack.addEventListener('tab-activate', (event) => {
|
|
1689
|
+
const detail = event.detail;
|
|
1690
|
+
const headerSpan = stack.querySelector(`:scope > [data-tab-id="${detail.tabId}"]`);
|
|
1691
|
+
const paneName = headerSpan?.dataset['pane'];
|
|
1692
|
+
if (paneName) {
|
|
1693
|
+
this.activatePane(stack, paneName, this.clonePath(location));
|
|
1694
|
+
this.dispatchEvent(new CustomEvent('dock-pane-activated', {
|
|
1695
|
+
detail: { pane: paneName },
|
|
1696
|
+
bubbles: true,
|
|
1697
|
+
composed: true,
|
|
1698
|
+
}));
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1797
1701
|
return stack;
|
|
1798
1702
|
}
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
this.activeSnapTargets = targets;
|
|
1849
|
-
this.renderSnapMarkersForDivider();
|
|
1850
|
-
}
|
|
1851
|
-
else {
|
|
1852
|
-
// Current bar is horizontal → snap Y to centers of other horizontal bars (no crossing check needed)
|
|
1853
|
-
allDividers.forEach((el) => {
|
|
1854
|
-
if (el === divider)
|
|
1855
|
-
return;
|
|
1856
|
-
const o = el.dataset['orientation'] ?? undefined;
|
|
1857
|
-
if (o !== 'vertical')
|
|
1858
|
-
return; // horizontal divider bars (split direction vertical)
|
|
1859
|
-
const r = el.getBoundingClientRect();
|
|
1860
|
-
const yCenter = r.top + r.height / 2 - rootRect.top;
|
|
1861
|
-
targets.push(yCenter);
|
|
1862
|
-
});
|
|
1863
|
-
this.activeSnapAxis = 'y';
|
|
1864
|
-
this.activeSnapTargets = targets;
|
|
1865
|
-
this.renderSnapMarkersForDivider();
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
catch {
|
|
1869
|
-
this.activeSnapAxis = null;
|
|
1870
|
-
this.activeSnapTargets = [];
|
|
1871
|
-
this.clearSnapMarkers();
|
|
1872
|
-
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Returns the strip (`.tsc`) element inside an `<mp-tab-control>`'s shadow
|
|
1705
|
+
* DOM. Used by drag/drop logic that needs the strip's geometry instead of
|
|
1706
|
+
* the host element's bounds.
|
|
1707
|
+
*/
|
|
1708
|
+
getStackStripEl(stack) {
|
|
1709
|
+
if (stack.tagName !== 'MP-TAB-CONTROL')
|
|
1710
|
+
return null;
|
|
1711
|
+
return stack.shadowRoot?.querySelector('.tsc') ?? null;
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Returns the rendered tab buttons inside an `<mp-tab-control>`'s shadow
|
|
1715
|
+
* strip — the light-DOM `.dock-tab` spans the dock owns are projected into
|
|
1716
|
+
* these buttons via `<slot>`. Use these for geometry / position queries
|
|
1717
|
+
* (insert-index computation, drop-indicator placement). Use the light-DOM
|
|
1718
|
+
* `.dock-tab` spans for data queries (paneName, drag listeners).
|
|
1719
|
+
*/
|
|
1720
|
+
getStackTabButtons(stack) {
|
|
1721
|
+
if (stack.tagName !== 'MP-TAB-CONTROL')
|
|
1722
|
+
return [];
|
|
1723
|
+
return Array.from(stack.shadowRoot?.querySelectorAll('button.nav-link') ?? []);
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Returns the dividers inside an `<mp-splitter>`'s shadow DOM, in DOM order.
|
|
1727
|
+
* mp-splitter renders one `.divider` between each pair of adjacent panels,
|
|
1728
|
+
* so for an N-child split, length N-1.
|
|
1729
|
+
*/
|
|
1730
|
+
getSplitterDividers(splitter) {
|
|
1731
|
+
if (splitter.tagName !== 'MP-SPLITTER')
|
|
1732
|
+
return [];
|
|
1733
|
+
return Array.from(splitter.shadowRoot?.querySelectorAll('.divider') ?? []);
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Returns the panel wrappers inside an `<mp-splitter>`'s shadow DOM, in
|
|
1737
|
+
* DOM order. These are the elements mp-splitter sizes (via setPanelSizes)
|
|
1738
|
+
* during a divider drag — the dock reads their geometry for intersection
|
|
1739
|
+
* handle math and snap markers.
|
|
1740
|
+
*/
|
|
1741
|
+
getSplitterPanels(splitter) {
|
|
1742
|
+
if (splitter.tagName !== 'MP-SPLITTER')
|
|
1743
|
+
return [];
|
|
1744
|
+
return Array.from(splitter.shadowRoot?.querySelectorAll('.panel-wrapper') ?? []);
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Locate the rendered `<mp-splitter>` element for a given DockPath
|
|
1748
|
+
* `segments` value (the split-tree path). Searches the dock's shadow.
|
|
1749
|
+
*/
|
|
1750
|
+
findSplitterByPath(segments) {
|
|
1751
|
+
return (this.shadowRoot?.querySelector(`.dock-split[data-path="${segments.join('/')}"]`) ?? null);
|
|
1873
1752
|
}
|
|
1874
1753
|
onPointerMove(event) {
|
|
1875
1754
|
if (this.cornerResizeState && event.pointerId === this.cornerResizeState.pointerId) {
|
|
1876
1755
|
this.handleCornerResizeMove(event);
|
|
1877
1756
|
}
|
|
1878
|
-
if (this.resizeState && event.pointerId === this.resizeState.pointerId) {
|
|
1879
|
-
const state = this.resizeState;
|
|
1880
|
-
const splitNode = this.resolveSplitNode(state.path);
|
|
1881
|
-
if (!splitNode) {
|
|
1882
|
-
return;
|
|
1883
|
-
}
|
|
1884
|
-
let currentPos = state.orientation === 'horizontal' ? event.clientX : event.clientY;
|
|
1885
|
-
// Localized axis snap near neighboring intersections
|
|
1886
|
-
const tol = 10;
|
|
1887
|
-
const rootRect = this.rootEl.getBoundingClientRect();
|
|
1888
|
-
if (this.activeSnapTargets.length) {
|
|
1889
|
-
if (state.orientation === 'horizontal' && this.activeSnapAxis === 'x') {
|
|
1890
|
-
// Vertical divider snapping along X
|
|
1891
|
-
let closest = Number.POSITIVE_INFINITY;
|
|
1892
|
-
let best = currentPos;
|
|
1893
|
-
const pointerX = event.clientX;
|
|
1894
|
-
this.activeSnapTargets.forEach((sx) => {
|
|
1895
|
-
const px = rootRect.left + sx;
|
|
1896
|
-
const d = Math.abs(pointerX - px);
|
|
1897
|
-
if (d < closest) {
|
|
1898
|
-
closest = d;
|
|
1899
|
-
best = px;
|
|
1900
|
-
}
|
|
1901
|
-
});
|
|
1902
|
-
if (closest <= tol)
|
|
1903
|
-
currentPos = best;
|
|
1904
|
-
this.renderSnapMarkersForDivider();
|
|
1905
|
-
}
|
|
1906
|
-
else if (state.orientation === 'vertical' && this.activeSnapAxis === 'y') {
|
|
1907
|
-
// Horizontal divider snapping along Y
|
|
1908
|
-
let closest = Number.POSITIVE_INFINITY;
|
|
1909
|
-
let best = currentPos;
|
|
1910
|
-
const pointerY = event.clientY;
|
|
1911
|
-
this.activeSnapTargets.forEach((sy) => {
|
|
1912
|
-
const py = rootRect.top + sy;
|
|
1913
|
-
const d = Math.abs(pointerY - py);
|
|
1914
|
-
if (d < closest) {
|
|
1915
|
-
closest = d;
|
|
1916
|
-
best = py;
|
|
1917
|
-
}
|
|
1918
|
-
});
|
|
1919
|
-
if (closest <= tol)
|
|
1920
|
-
currentPos = best;
|
|
1921
|
-
this.renderSnapMarkersForDivider();
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
const delta = currentPos - state.startPos;
|
|
1925
|
-
const minSize = 48;
|
|
1926
|
-
const pairTotal = state.beforeSize + state.afterSize;
|
|
1927
|
-
let newBefore = state.beforeSize + delta;
|
|
1928
|
-
// Optional snap with Shift
|
|
1929
|
-
if (event.shiftKey && pairTotal > 0) {
|
|
1930
|
-
const ratios = [1 / 3, 1 / 2, 2 / 3];
|
|
1931
|
-
const target = newBefore / pairTotal;
|
|
1932
|
-
let best = ratios[0];
|
|
1933
|
-
let bestDist = Math.abs(target - best);
|
|
1934
|
-
for (let i = 1; i < ratios.length; i++) {
|
|
1935
|
-
const d = Math.abs(target - ratios[i]);
|
|
1936
|
-
if (d < bestDist) {
|
|
1937
|
-
best = ratios[i];
|
|
1938
|
-
bestDist = d;
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
newBefore = best * pairTotal;
|
|
1942
|
-
}
|
|
1943
|
-
newBefore = Math.min(Math.max(newBefore, minSize), pairTotal - minSize);
|
|
1944
|
-
let newAfter = pairTotal - newBefore;
|
|
1945
|
-
if (!Number.isFinite(newBefore) || !Number.isFinite(newAfter)) {
|
|
1946
|
-
return;
|
|
1947
|
-
}
|
|
1948
|
-
if (newAfter < minSize) {
|
|
1949
|
-
newAfter = minSize;
|
|
1950
|
-
newBefore = pairTotal - minSize;
|
|
1951
|
-
}
|
|
1952
|
-
const newSizesPixels = [...state.initialSizes];
|
|
1953
|
-
newSizesPixels[state.index] = newBefore;
|
|
1954
|
-
newSizesPixels[state.index + 1] = newAfter;
|
|
1955
|
-
const total = newSizesPixels.reduce((acc, size) => acc + size, 0);
|
|
1956
|
-
const normalized = total > 0 ? newSizesPixels.map((size) => size / total) : [];
|
|
1957
|
-
splitNode.sizes = normalized;
|
|
1958
|
-
const children = Array.from(state.container.querySelectorAll(':scope > .dock-split__child'));
|
|
1959
|
-
normalized.forEach((size, idx) => {
|
|
1960
|
-
if (children[idx]) {
|
|
1961
|
-
children[idx].style.flex = `${Math.max(size, 0)} 1 0`;
|
|
1962
|
-
}
|
|
1963
|
-
});
|
|
1964
|
-
this.dispatchLayoutChanged();
|
|
1965
|
-
}
|
|
1966
1757
|
if (this.floatingResizeState && event.pointerId === this.floatingResizeState.pointerId) {
|
|
1967
1758
|
this.handleFloatingResizeMove(event);
|
|
1968
1759
|
}
|
|
@@ -1974,15 +1765,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
1974
1765
|
if (this.cornerResizeState && event.pointerId === this.cornerResizeState.pointerId) {
|
|
1975
1766
|
this.endCornerResize(event.pointerId);
|
|
1976
1767
|
}
|
|
1977
|
-
if (this.resizeState && event.pointerId === this.resizeState.pointerId) {
|
|
1978
|
-
const divider = this.resizeState.divider;
|
|
1979
|
-
divider.dataset['resizing'] = 'false';
|
|
1980
|
-
divider.releasePointerCapture(this.resizeState.pointerId);
|
|
1981
|
-
this.resizeState = null;
|
|
1982
|
-
this.scheduleRenderIntersectionHandles();
|
|
1983
|
-
this.activeSnapAxis = null;
|
|
1984
|
-
this.activeSnapTargets = [];
|
|
1985
|
-
}
|
|
1986
1768
|
if (this.floatingDragState && event.pointerId === this.floatingDragState.pointerId) {
|
|
1987
1769
|
this.endFloatingDrag(event.pointerId);
|
|
1988
1770
|
}
|
|
@@ -2022,30 +1804,55 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2022
1804
|
clearPendingTabDragMetrics() {
|
|
2023
1805
|
this.pendingTabDragMetrics = null;
|
|
2024
1806
|
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Pointerdown handler arms a "may become a drag" gesture. Once the pointer
|
|
1809
|
+
* moves past `threshold` pixels we promote it to an actual pane drag via
|
|
1810
|
+
* {@link beginPaneDrag}; if the user releases first we just clear the
|
|
1811
|
+
* pending tab metrics. All listeners self-clean on resolve so the gesture
|
|
1812
|
+
* stays scoped to a single pointerdown.
|
|
1813
|
+
*/
|
|
1814
|
+
armPaneDragGesture(startEvent, path, pane, stackEl) {
|
|
1815
|
+
if (startEvent.pointerType === 'mouse' && startEvent.button !== 0)
|
|
1816
|
+
return;
|
|
1817
|
+
const win = this.windowRef;
|
|
1818
|
+
if (!win)
|
|
1819
|
+
return;
|
|
1820
|
+
const startX = startEvent.clientX;
|
|
1821
|
+
const startY = startEvent.clientY;
|
|
1822
|
+
const pointerId = startEvent.pointerId;
|
|
1823
|
+
const threshold = 5;
|
|
1824
|
+
let resolved = false;
|
|
1825
|
+
const cleanup = () => {
|
|
1826
|
+
resolved = true;
|
|
1827
|
+
win.removeEventListener('pointermove', onMove, true);
|
|
1828
|
+
win.removeEventListener('pointerup', onRelease, true);
|
|
1829
|
+
win.removeEventListener('pointercancel', onRelease, true);
|
|
1830
|
+
};
|
|
1831
|
+
const onMove = (event) => {
|
|
1832
|
+
if (resolved || event.pointerId !== pointerId)
|
|
1833
|
+
return;
|
|
1834
|
+
const dx = event.clientX - startX;
|
|
1835
|
+
const dy = event.clientY - startY;
|
|
1836
|
+
if (Math.hypot(dx, dy) < threshold)
|
|
1837
|
+
return;
|
|
1838
|
+
cleanup();
|
|
1839
|
+
this.beginPaneDrag(event, path, pane, stackEl);
|
|
1840
|
+
};
|
|
1841
|
+
const onRelease = (event) => {
|
|
1842
|
+
if (resolved || event.pointerId !== pointerId)
|
|
1843
|
+
return;
|
|
1844
|
+
cleanup();
|
|
1845
|
+
this.clearPendingTabDragMetrics();
|
|
1846
|
+
};
|
|
1847
|
+
win.addEventListener('pointermove', onMove, true);
|
|
1848
|
+
win.addEventListener('pointerup', onRelease, true);
|
|
1849
|
+
win.addEventListener('pointercancel', onRelease, true);
|
|
1850
|
+
}
|
|
2025
1851
|
beginPaneDrag(event, path, pane, stackEl) {
|
|
2026
|
-
if (!event.dataTransfer) {
|
|
2027
|
-
return;
|
|
2028
|
-
}
|
|
2029
|
-
// Create a ghost element for the drag image. This prevents the browser from cancelling
|
|
2030
|
-
// the drag operation when the original element is removed from the DOM during re-render.
|
|
2031
|
-
const ghost = event.currentTarget.cloneNode(true);
|
|
2032
|
-
ghost.style.position = 'absolute';
|
|
2033
|
-
ghost.style.left = '-9999px';
|
|
2034
|
-
ghost.style.top = '-9999px';
|
|
2035
|
-
ghost.style.width = `${event.currentTarget.offsetWidth}px`;
|
|
2036
|
-
ghost.style.height = `${event.currentTarget.offsetHeight}px`;
|
|
2037
|
-
this.shadowRoot?.appendChild(ghost);
|
|
2038
|
-
// Use the ghost element as the drag image.
|
|
2039
|
-
// The offset is set to where the user's cursor is on the original element.
|
|
2040
|
-
const dragImgOffsetX = Number.isFinite(event.offsetX) ? event.offsetX : 0;
|
|
2041
|
-
const dragImgOffsetY = Number.isFinite(event.offsetY) ? event.offsetY : 0;
|
|
2042
|
-
event.dataTransfer.setDragImage(ghost, dragImgOffsetX, dragImgOffsetY);
|
|
2043
|
-
// The ghost element is no longer needed after the drag image is set.
|
|
2044
|
-
// We defer its removal to ensure the browser has captured it.
|
|
2045
|
-
setTimeout(() => ghost.remove(), 0);
|
|
2046
1852
|
const { path: sourcePath, floatingIndex, pointerOffsetX, pointerOffsetY, } = this.preparePaneDragSource(path, pane, stackEl, event);
|
|
2047
|
-
// Capture header bounds for detecting when to convert to floating
|
|
2048
|
-
|
|
1853
|
+
// Capture header bounds for detecting when to convert to floating.
|
|
1854
|
+
// The strip lives inside the mp-tab-control's shadow as `.tsc`.
|
|
1855
|
+
const headerEl = stackEl ? this.getStackStripEl(stackEl) : null;
|
|
2049
1856
|
const headerRect = headerEl ? headerEl.getBoundingClientRect() : null;
|
|
2050
1857
|
const headerBounds = headerRect
|
|
2051
1858
|
? { left: headerRect.left, top: headerRect.top, right: headerRect.right, bottom: headerRect.bottom }
|
|
@@ -2062,36 +1869,26 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2062
1869
|
sourceHeaderBounds: headerBounds,
|
|
2063
1870
|
startClientX: metrics && Number.isFinite(metrics.startClientX)
|
|
2064
1871
|
? metrics.startClientX
|
|
2065
|
-
:
|
|
2066
|
-
? event.clientX
|
|
2067
|
-
: undefined,
|
|
1872
|
+
: event.clientX,
|
|
2068
1873
|
startClientY: metrics && Number.isFinite(metrics.startClientY)
|
|
2069
1874
|
? metrics.startClientY
|
|
2070
|
-
:
|
|
2071
|
-
? event.clientY
|
|
2072
|
-
: undefined,
|
|
1875
|
+
: event.clientY,
|
|
2073
1876
|
};
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
this.dragState.startClientY
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
// Prefer the pointer offset relative to the dragged tab to avoid jumps on conversion
|
|
2085
|
-
if (Number.isFinite(event.offsetX)) {
|
|
2086
|
-
this.dragState.pointerOffsetX = event.offsetX;
|
|
2087
|
-
}
|
|
2088
|
-
if (Number.isFinite(event.offsetY)) {
|
|
2089
|
-
this.dragState.pointerOffsetY = event.offsetY;
|
|
2090
|
-
}
|
|
2091
|
-
this.updateDraggedFloatingPosition(event);
|
|
1877
|
+
this.lastDragPointerPosition = {
|
|
1878
|
+
x: this.dragState.startClientX,
|
|
1879
|
+
y: this.dragState.startClientY,
|
|
1880
|
+
};
|
|
1881
|
+
// pointerOffsetX/Y from preparePaneDragSource is the offset within the
|
|
1882
|
+
// source stack rect captured at pointerdown by captureTabDragMetrics.
|
|
1883
|
+
// Don't overwrite with event.offsetX/Y here — the threshold-trigger
|
|
1884
|
+
// pointermove fired on window, so its offset is in window-local coords
|
|
1885
|
+
// (≈ clientX/Y) which would crash the conversion math to ~(0,0).
|
|
1886
|
+
this.updateDraggedFloatingPositionFromPoint(event.clientX, event.clientY);
|
|
2092
1887
|
this.startDragPointerTracking();
|
|
2093
|
-
|
|
2094
|
-
|
|
1888
|
+
// Mark the source floating wrapper (if any) so its CSS rule kicks in and
|
|
1889
|
+
// pointer-events:none lets findStackAtPoint see through to the docked
|
|
1890
|
+
// stack underneath, enabling drop zones over the dock during the drag.
|
|
1891
|
+
this.markDraggedFloatingWrapper();
|
|
2095
1892
|
// Preferred UX: if the dragged tab is the only one in its stack,
|
|
2096
1893
|
// immediately convert to a floating window unless it is already the
|
|
2097
1894
|
// only pane in a floating window (this case is handled by reuse logic).
|
|
@@ -2099,18 +1896,16 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2099
1896
|
const loc = this.resolveStackLocation(this.dragState.sourcePath);
|
|
2100
1897
|
if (loc && Array.isArray(loc.node.panes) && loc.node.panes.length === 1) {
|
|
2101
1898
|
let shouldConvert = false;
|
|
2102
|
-
if (loc.context ===
|
|
1899
|
+
if (loc.context === 'docked') {
|
|
2103
1900
|
shouldConvert = true;
|
|
2104
1901
|
}
|
|
2105
|
-
else if (loc.context ===
|
|
1902
|
+
else if (loc.context === 'floating') {
|
|
2106
1903
|
const floating = this.floatingLayouts[loc.index];
|
|
2107
1904
|
const totalPanes = floating && floating.root ? this.countPanesInTree(floating.root) : 0;
|
|
2108
|
-
shouldConvert = totalPanes > 1;
|
|
1905
|
+
shouldConvert = totalPanes > 1;
|
|
2109
1906
|
}
|
|
2110
1907
|
if (shouldConvert) {
|
|
2111
|
-
|
|
2112
|
-
const startY = Number.isFinite(event.clientY) ? event.clientY : (this.dragState.startClientY ?? 0);
|
|
2113
|
-
this.convertPendingTabDragToFloating(startX, startY);
|
|
1908
|
+
this.convertPendingTabDragToFloating(event.clientX, event.clientY);
|
|
2114
1909
|
}
|
|
2115
1910
|
}
|
|
2116
1911
|
}
|
|
@@ -2192,134 +1987,27 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2192
1987
|
}
|
|
2193
1988
|
endPaneDrag() {
|
|
2194
1989
|
this.clearPendingDragEndTimeout();
|
|
1990
|
+
// Restore the dragged tab's `data-hidden` and remove the placeholder span
|
|
1991
|
+
// BEFORE we null out dragState — clearHeaderDragPlaceholder reads
|
|
1992
|
+
// `dragState.placeholderEl`, `dragState.placeholderHeader`, and
|
|
1993
|
+
// `dragState.pane` to know what to restore. If dragState is nulled first,
|
|
1994
|
+
// this becomes a silent no-op and the dragged pane stays hidden in its
|
|
1995
|
+
// source stack while the placeholder span lingers in the strip — which
|
|
1996
|
+
// is exactly the "Panel disappears, only a small tab-thumb remains"
|
|
1997
|
+
// regression the multi-pane drag-out path can otherwise trigger when
|
|
1998
|
+
// no renderLayout() runs between conversion and end (e.g. user releases
|
|
1999
|
+
// outside any drop zone, or HTML5 dragend fires without a drop).
|
|
2000
|
+
this.clearHeaderDragPlaceholder();
|
|
2001
|
+
this.clearDraggedFloatingWrapperMarkers();
|
|
2195
2002
|
const state = this.dragState;
|
|
2196
2003
|
this.dragState = null;
|
|
2197
2004
|
this.hideDropIndicator();
|
|
2198
|
-
this.clearHeaderDragPlaceholder();
|
|
2199
2005
|
this.stopDragPointerTracking();
|
|
2200
2006
|
this.lastDragPointerPosition = null;
|
|
2201
2007
|
if (state && state.floatingIndex !== null && !state.dropHandled) {
|
|
2202
2008
|
this.dispatchLayoutChanged();
|
|
2203
2009
|
}
|
|
2204
2010
|
}
|
|
2205
|
-
onDragOver(event) {
|
|
2206
|
-
if (!this.dragState) {
|
|
2207
|
-
return;
|
|
2208
|
-
}
|
|
2209
|
-
event.preventDefault();
|
|
2210
|
-
// Keep internal pointer tracking up-to-date.
|
|
2211
|
-
this.updateDraggedFloatingPosition(event);
|
|
2212
|
-
if (event.dataTransfer) {
|
|
2213
|
-
event.dataTransfer.dropEffect = 'move';
|
|
2214
|
-
}
|
|
2215
|
-
// Some browsers intermittently report (0,0) for dragover coordinates.
|
|
2216
|
-
// Mirror the robust logic used in onDrop: prefer actual event coordinates
|
|
2217
|
-
// when valid, otherwise fall back to the last tracked pointer position.
|
|
2218
|
-
const pointFromEvent = Number.isFinite(event.clientX) && Number.isFinite(event.clientY)
|
|
2219
|
-
? { clientX: event.clientX, clientY: event.clientY }
|
|
2220
|
-
: null;
|
|
2221
|
-
const point = pointFromEvent ??
|
|
2222
|
-
(this.lastDragPointerPosition
|
|
2223
|
-
? { clientX: this.lastDragPointerPosition.x, clientY: this.lastDragPointerPosition.y }
|
|
2224
|
-
: null);
|
|
2225
|
-
const stack = this.findStackElement(event) ??
|
|
2226
|
-
(point ? this.findStackAtPoint(point.clientX, point.clientY) : null);
|
|
2227
|
-
if (!stack) {
|
|
2228
|
-
if (this.dropJoystick.dataset['visible'] !== 'true') {
|
|
2229
|
-
this.hideDropIndicator();
|
|
2230
|
-
}
|
|
2231
|
-
return;
|
|
2232
|
-
}
|
|
2233
|
-
const path = this.parsePath(stack.dataset['path']);
|
|
2234
|
-
// While reordering within the same header, suppress the joystick/indicator entirely
|
|
2235
|
-
if (this.dragState &&
|
|
2236
|
-
this.dragState.floatingIndex !== null &&
|
|
2237
|
-
this.dragState.floatingIndex < 0 &&
|
|
2238
|
-
path &&
|
|
2239
|
-
this.pathsEqual(path, this.dragState.sourcePath)) {
|
|
2240
|
-
const px = (point ? point.clientX : event.clientX);
|
|
2241
|
-
const py = (point ? point.clientY : event.clientY);
|
|
2242
|
-
if (Number.isFinite(px) && Number.isFinite(py) && this.isPointerOverSourceHeader(px, py)) {
|
|
2243
|
-
// Drive live reorder using the unified path so we update instantly.
|
|
2244
|
-
this.updatePaneDragDropTargetFromPoint(px, py);
|
|
2245
|
-
this.hideDropIndicator();
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
// If the hovered stack changed, clear any sticky zone from the previous
|
|
2250
|
-
// target before computing the new zone.
|
|
2251
|
-
if (this.dropJoystickTarget && this.dropJoystickTarget !== stack) {
|
|
2252
|
-
delete this.dropJoystick.dataset['zone'];
|
|
2253
|
-
this.updateDropJoystickActiveZone(null);
|
|
2254
|
-
}
|
|
2255
|
-
const eventZoneHint = this.extractDropZoneFromEvent(event);
|
|
2256
|
-
const pointZoneHint = point ? this.findDropZoneByPoint(point.clientX, point.clientY) : null;
|
|
2257
|
-
const zone = this.computeDropZone(stack, point ?? event, pointZoneHint ?? eventZoneHint);
|
|
2258
|
-
this.showDropIndicator(stack, zone);
|
|
2259
|
-
}
|
|
2260
|
-
updateDraggedFloatingPosition(event) {
|
|
2261
|
-
if (!this.dragState) {
|
|
2262
|
-
return;
|
|
2263
|
-
}
|
|
2264
|
-
const { clientX, clientY } = event;
|
|
2265
|
-
const hasValidCoordinates = Number.isFinite(clientX) &&
|
|
2266
|
-
Number.isFinite(clientY) &&
|
|
2267
|
-
!(clientX === 0 && clientY === 0);
|
|
2268
|
-
if (hasValidCoordinates) {
|
|
2269
|
-
this.lastDragPointerPosition = { x: clientX, y: clientY };
|
|
2270
|
-
this.updateDraggedFloatingPositionFromPoint(clientX, clientY);
|
|
2271
|
-
return;
|
|
2272
|
-
}
|
|
2273
|
-
if (this.lastDragPointerPosition) {
|
|
2274
|
-
const { x, y } = this.lastDragPointerPosition;
|
|
2275
|
-
this.updateDraggedFloatingPositionFromPoint(x, y);
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
onGlobalDragOver(event) {
|
|
2279
|
-
if (!this.dragState) {
|
|
2280
|
-
return;
|
|
2281
|
-
}
|
|
2282
|
-
this.updateDraggedFloatingPosition(event);
|
|
2283
|
-
}
|
|
2284
|
-
onDrag(event) {
|
|
2285
|
-
if (!this.dragState) {
|
|
2286
|
-
return;
|
|
2287
|
-
}
|
|
2288
|
-
this.updateDraggedFloatingPosition(event);
|
|
2289
|
-
}
|
|
2290
|
-
onGlobalDragEnd() {
|
|
2291
|
-
// Attempt to finalize a drop even if the drop event doesn't reach us (Firefox/edge cases)
|
|
2292
|
-
const state = this.dragState;
|
|
2293
|
-
const pos = this.lastDragPointerPosition;
|
|
2294
|
-
if (state && pos) {
|
|
2295
|
-
const stack = this.findStackAtPoint(pos.x, pos.y);
|
|
2296
|
-
const joystickVisible = this.dropJoystick.dataset['visible'] === 'true';
|
|
2297
|
-
const joystickPath = this.parsePath(this.dropJoystick.dataset['path']);
|
|
2298
|
-
const joystickTarget = this.dropJoystickTarget;
|
|
2299
|
-
const joystickTargetPath = joystickTarget ? this.parsePath(joystickTarget.dataset['path']) : null;
|
|
2300
|
-
const path = stack ? this.parsePath(stack.dataset['path']) : (joystickPath ?? joystickTargetPath);
|
|
2301
|
-
const joystickZone = this.dropJoystick.dataset['zone'];
|
|
2302
|
-
const zone = this.isDropZone(joystickZone)
|
|
2303
|
-
? joystickZone
|
|
2304
|
-
: (stack ? this.computeDropZone(stack, { clientX: pos.x, clientY: pos.y }, null) : null);
|
|
2305
|
-
if (path && this.isDropZone(zone)) {
|
|
2306
|
-
this.handleDrop(path, zone);
|
|
2307
|
-
this.hideDropIndicator();
|
|
2308
|
-
if (this.dragState) {
|
|
2309
|
-
this.dragState.dropHandled = true;
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
else {
|
|
2314
|
-
this.hideDropIndicator();
|
|
2315
|
-
}
|
|
2316
|
-
if (!this.dragState) {
|
|
2317
|
-
this.clearPendingTabDragMetrics();
|
|
2318
|
-
return;
|
|
2319
|
-
}
|
|
2320
|
-
this.endPaneDrag();
|
|
2321
|
-
this.clearPendingTabDragMetrics();
|
|
2322
|
-
}
|
|
2323
2011
|
updateDraggedFloatingPositionFromPoint(clientX, clientY) {
|
|
2324
2012
|
if (!this.dragState) {
|
|
2325
2013
|
return;
|
|
@@ -2327,10 +2015,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2327
2015
|
if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
|
|
2328
2016
|
return;
|
|
2329
2017
|
}
|
|
2330
|
-
// Ignore obviously bogus coordinates sometimes seen during HTML5 drag
|
|
2331
|
-
if (clientX === 0 && clientY === 0) {
|
|
2332
|
-
return;
|
|
2333
|
-
}
|
|
2334
2018
|
// If still dragging a tab inside its header, only convert to floating once we leave the header.
|
|
2335
2019
|
if (this.dragState.floatingIndex !== null && this.dragState.floatingIndex < 0) {
|
|
2336
2020
|
const b = this.dragState.sourceHeaderBounds;
|
|
@@ -2401,16 +2085,15 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2401
2085
|
const inHeaderByBounds = !!this.dragState.sourceHeaderBounds && this.isPointWithinBounds(this.dragState.sourceHeaderBounds, clientX, clientY);
|
|
2402
2086
|
const inHeaderByHitTest = this.isPointerOverSourceHeader(clientX, clientY);
|
|
2403
2087
|
if (inHeaderByBounds || inHeaderByHitTest) {
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
}
|
|
2088
|
+
// Ensure placeholder exists and move it as the pointer moves.
|
|
2089
|
+
// Placeholder management mutates the slotted children of the
|
|
2090
|
+
// mp-tab-control stack; the WC re-renders the strip on slotchange.
|
|
2091
|
+
this.ensureHeaderDragPlaceholder(stack, this.dragState.pane);
|
|
2092
|
+
const idx = this.computeHeaderInsertIndex(stack, clientX);
|
|
2093
|
+
if (this.dragState.liveReorderIndex !== idx) {
|
|
2094
|
+
this.updateHeaderDragPlaceholderPosition(stack, idx);
|
|
2095
|
+
// Keep model reordering until drop; only move the placeholder now
|
|
2096
|
+
this.dragState.liveReorderIndex = idx;
|
|
2414
2097
|
}
|
|
2415
2098
|
this.hideDropIndicator();
|
|
2416
2099
|
return;
|
|
@@ -2422,81 +2105,125 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2422
2105
|
const zone = this.computeDropZone(stack, { clientX, clientY }, zoneHint);
|
|
2423
2106
|
this.showDropIndicator(stack, zone);
|
|
2424
2107
|
}
|
|
2425
|
-
// Returns true when the pointer is currently over the source stack's header (tab strip)
|
|
2108
|
+
// Returns true when the pointer is currently over the source stack's header (tab strip).
|
|
2109
|
+
// The strip lives inside the mp-tab-control's shadow as `.tsc`, so we test
|
|
2110
|
+
// bounds directly rather than using elementsFromPoint(/contains) which won't
|
|
2111
|
+
// pierce the shadow boundary cleanly.
|
|
2426
2112
|
isPointerOverSourceHeader(clientX, clientY) {
|
|
2427
2113
|
const state = this.dragState;
|
|
2428
2114
|
if (!state) {
|
|
2429
2115
|
return false;
|
|
2430
2116
|
}
|
|
2431
2117
|
const stackEl = state.sourceStackEl ?? null;
|
|
2432
|
-
const
|
|
2433
|
-
if (!
|
|
2434
|
-
// Be conservative: if we cannot resolve the
|
|
2118
|
+
const strip = stackEl ? this.getStackStripEl(stackEl) : null;
|
|
2119
|
+
if (!strip) {
|
|
2120
|
+
// Be conservative: if we cannot resolve the strip, treat as inside
|
|
2435
2121
|
return true;
|
|
2436
2122
|
}
|
|
2437
|
-
const
|
|
2438
|
-
|
|
2439
|
-
for (const el of elements) {
|
|
2440
|
-
if (el instanceof HTMLElement && header.contains(el)) {
|
|
2441
|
-
return true;
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
return false;
|
|
2123
|
+
const r = strip.getBoundingClientRect();
|
|
2124
|
+
return clientX >= r.left && clientX <= r.right && clientY >= r.top && clientY <= r.bottom;
|
|
2445
2125
|
}
|
|
2446
2126
|
isPointWithinBounds(bounds, x, y) {
|
|
2447
2127
|
return x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom;
|
|
2448
2128
|
}
|
|
2449
|
-
// Ensure a placeholder tab exists during in-header drag and hide the real dragged tab visually
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
if (
|
|
2456
|
-
return;
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
//
|
|
2468
|
-
|
|
2469
|
-
//
|
|
2470
|
-
|
|
2129
|
+
// Ensure a placeholder tab exists during in-header drag and hide the real dragged tab visually.
|
|
2130
|
+
// Operates on the mp-tab-control stack: the dragged content div gets `data-hidden`
|
|
2131
|
+
// (mp-tab-control then skips its tab in the strip), and a placeholder header+content
|
|
2132
|
+
// pair is appended as light-DOM children of the stack. mp-tab-control's mutation
|
|
2133
|
+
// observer picks up the change and renders the placeholder as a tab.
|
|
2134
|
+
ensureHeaderDragPlaceholder(stack, pane) {
|
|
2135
|
+
if (stack.tagName !== 'MP-TAB-CONTROL')
|
|
2136
|
+
return;
|
|
2137
|
+
if (this.dragState?.placeholderHeader === stack && this.dragState.placeholderEl) {
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
const draggedHeader = stack.querySelector(`:scope > .dock-tab[data-pane="${CSS.escape(pane)}"]`);
|
|
2141
|
+
const draggedContent = stack.querySelector(`:scope > .dock-stack__pane[data-pane="${CSS.escape(pane)}"]`);
|
|
2142
|
+
if (!draggedHeader || !draggedContent)
|
|
2143
|
+
return;
|
|
2144
|
+
// Measure the dragged tab's text-only width BEFORE hiding it. The
|
|
2145
|
+
// `.dock-tab` rule applies padding (matching the strip button's padding so
|
|
2146
|
+
// the span fills the button as a drag handle), so `offsetWidth` is
|
|
2147
|
+
// text + padding — we subtract the span's own padding to get just the
|
|
2148
|
+
// text width. That's the natural slot content width we want the
|
|
2149
|
+
// placeholder to reserve; the placeholder span will re-apply the same
|
|
2150
|
+
// padding on top, mirroring the original tab's geometry exactly.
|
|
2151
|
+
const draggedCS = this.windowRef
|
|
2152
|
+
? this.windowRef.getComputedStyle(draggedHeader)
|
|
2153
|
+
: globalThis.getComputedStyle(draggedHeader);
|
|
2154
|
+
const draggedHorizontalPadding = parseFloat(draggedCS.paddingLeft) + parseFloat(draggedCS.paddingRight);
|
|
2155
|
+
const slotContentWidth = Math.max(0, draggedHeader.offsetWidth - draggedHorizontalPadding);
|
|
2156
|
+
// Hide the dragged tab from mp-tab-control's strip (frees up the slot).
|
|
2157
|
+
draggedContent.setAttribute('data-hidden', '');
|
|
2158
|
+
// Build placeholder header + content. The placeholder uses a unique tabId
|
|
2159
|
+
// (`__dock-placeholder__`) so its slot names don't collide with real panes.
|
|
2160
|
+
// We mirror the dragged tab's text into the placeholder (dimmed via opacity)
|
|
2161
|
+
// so the strip reads as "this tab is being dragged" rather than "empty slot".
|
|
2162
|
+
const placeholderTabId = '__dock-placeholder__';
|
|
2163
|
+
const phHeader = this.documentRef.createElement('span');
|
|
2164
|
+
phHeader.setAttribute('slot', `${placeholderTabId}-header`);
|
|
2165
|
+
phHeader.classList.add('dock-tab');
|
|
2166
|
+
phHeader.dataset['placeholder'] = 'true';
|
|
2167
|
+
phHeader.dataset['tabId'] = placeholderTabId;
|
|
2168
|
+
phHeader.setAttribute('aria-hidden', 'true');
|
|
2169
|
+
phHeader.textContent = draggedHeader.textContent;
|
|
2170
|
+
// `display: inline-block` is required for `min-width` to take effect on the
|
|
2171
|
+
// span. Without it, an inline element ignores min-width and the placeholder
|
|
2172
|
+
// collapses to its content width (or 0 if textContent is also empty),
|
|
2173
|
+
// leaving a "mini-thumb" in the strip.
|
|
2174
|
+
phHeader.style.display = 'inline-block';
|
|
2175
|
+
phHeader.style.minWidth = `${slotContentWidth}px`;
|
|
2176
|
+
phHeader.style.opacity = '0.5';
|
|
2177
|
+
const phContent = this.documentRef.createElement('div');
|
|
2178
|
+
phContent.setAttribute('slot', `${placeholderTabId}-content`);
|
|
2179
|
+
phContent.classList.add('dock-stack__pane');
|
|
2180
|
+
phContent.dataset['placeholder'] = 'true';
|
|
2181
|
+
// Insert before the dragged header span so the placeholder appears in
|
|
2182
|
+
// the dragged tab's original strip position. The mutation observer in
|
|
2183
|
+
// mp-tab-control will refresh the tab list automatically.
|
|
2184
|
+
stack.insertBefore(phHeader, draggedHeader);
|
|
2185
|
+
stack.insertBefore(phContent, draggedContent);
|
|
2471
2186
|
if (this.dragState) {
|
|
2472
|
-
this.dragState.placeholderHeader =
|
|
2473
|
-
this.dragState.placeholderEl =
|
|
2187
|
+
this.dragState.placeholderHeader = stack;
|
|
2188
|
+
this.dragState.placeholderEl = phHeader;
|
|
2474
2189
|
}
|
|
2475
2190
|
}
|
|
2476
|
-
// Move the placeholder to the computed target index within the
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2191
|
+
// Move the placeholder to the computed target index within the strip.
|
|
2192
|
+
// We reorder light-DOM children (header span + matching content div); the
|
|
2193
|
+
// mp-tab-control then re-renders the strip in the new order on slotchange.
|
|
2194
|
+
updateHeaderDragPlaceholderPosition(stack, targetIndex) {
|
|
2195
|
+
if (stack.tagName !== 'MP-TAB-CONTROL')
|
|
2196
|
+
return;
|
|
2197
|
+
const phHeader = this.dragState?.placeholderEl ?? null;
|
|
2198
|
+
if (!phHeader)
|
|
2480
2199
|
return;
|
|
2481
|
-
}
|
|
2482
2200
|
const draggedPane = this.dragState?.pane ?? null;
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2201
|
+
// Find all real header spans (excluding the placeholder + the hidden dragged one).
|
|
2202
|
+
const realHeaders = Array.from(stack.querySelectorAll(':scope > .dock-tab')).filter((h) => h !== phHeader &&
|
|
2203
|
+
(!draggedPane || h.dataset['pane'] !== draggedPane));
|
|
2204
|
+
const clampedTarget = Math.max(0, Math.min(targetIndex, realHeaders.length));
|
|
2205
|
+
const ref = realHeaders[clampedTarget] ?? null;
|
|
2206
|
+
stack.insertBefore(phHeader, ref);
|
|
2207
|
+
// Keep the placeholder content adjacent to its header so child-order
|
|
2208
|
+
// remains predictable for slotchange-driven re-renders.
|
|
2209
|
+
const phContent = stack.querySelector(`:scope > .dock-stack__pane[data-placeholder="true"]`);
|
|
2210
|
+
if (phContent && phHeader.nextElementSibling !== phContent) {
|
|
2211
|
+
stack.insertBefore(phContent, phHeader.nextElementSibling);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
// Remove placeholder and restore the dragged tab's visibility.
|
|
2490
2215
|
clearHeaderDragPlaceholder() {
|
|
2491
2216
|
const ph = this.dragState?.placeholderEl ?? null;
|
|
2492
|
-
const
|
|
2493
|
-
if (
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
dragged.style.display = '';
|
|
2217
|
+
const stack = this.dragState?.placeholderHeader ?? null;
|
|
2218
|
+
if (stack) {
|
|
2219
|
+
// Restore the dragged content div's visibility so its strip tab returns.
|
|
2220
|
+
if (this.dragState?.pane) {
|
|
2221
|
+
const draggedContent = stack.querySelector(`:scope > .dock-stack__pane[data-pane="${CSS.escape(this.dragState.pane)}"]`);
|
|
2222
|
+
draggedContent?.removeAttribute('data-hidden');
|
|
2499
2223
|
}
|
|
2224
|
+
// Remove the placeholder content div sibling.
|
|
2225
|
+
const phContent = stack.querySelector(`:scope > .dock-stack__pane[data-placeholder="true"]`);
|
|
2226
|
+
phContent?.remove();
|
|
2500
2227
|
}
|
|
2501
2228
|
if (ph && ph.parentElement) {
|
|
2502
2229
|
ph.parentElement.removeChild(ph);
|
|
@@ -2512,11 +2239,9 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2512
2239
|
}
|
|
2513
2240
|
this.lastDragPointerPosition = null;
|
|
2514
2241
|
const win = this.windowRef;
|
|
2515
|
-
win?.addEventListener('
|
|
2516
|
-
win?.addEventListener('
|
|
2517
|
-
win?.addEventListener('
|
|
2518
|
-
win?.addEventListener('touchend', this.onDragTouchEnd, true);
|
|
2519
|
-
win?.addEventListener('touchcancel', this.onDragTouchEnd, true);
|
|
2242
|
+
win?.addEventListener('pointermove', this.onDragPointerMove, true);
|
|
2243
|
+
win?.addEventListener('pointerup', this.onDragPointerUp, true);
|
|
2244
|
+
win?.addEventListener('pointercancel', this.onDragPointerCancel, true);
|
|
2520
2245
|
this.dragPointerTrackingActive = true;
|
|
2521
2246
|
}
|
|
2522
2247
|
stopDragPointerTracking() {
|
|
@@ -2524,52 +2249,38 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2524
2249
|
return;
|
|
2525
2250
|
}
|
|
2526
2251
|
const win = this.windowRef;
|
|
2527
|
-
win?.removeEventListener('
|
|
2528
|
-
win?.removeEventListener('
|
|
2529
|
-
win?.removeEventListener('
|
|
2530
|
-
win?.removeEventListener('touchend', this.onDragTouchEnd, true);
|
|
2531
|
-
win?.removeEventListener('touchcancel', this.onDragTouchEnd, true);
|
|
2252
|
+
win?.removeEventListener('pointermove', this.onDragPointerMove, true);
|
|
2253
|
+
win?.removeEventListener('pointerup', this.onDragPointerUp, true);
|
|
2254
|
+
win?.removeEventListener('pointercancel', this.onDragPointerCancel, true);
|
|
2532
2255
|
this.dragPointerTrackingActive = false;
|
|
2533
2256
|
this.lastDragPointerPosition = null;
|
|
2534
2257
|
this.clearPendingDragEndTimeout();
|
|
2535
2258
|
}
|
|
2536
|
-
|
|
2259
|
+
onDragPointerMove(event) {
|
|
2537
2260
|
if (!this.dragState) {
|
|
2538
2261
|
this.stopDragPointerTracking();
|
|
2539
2262
|
return;
|
|
2540
2263
|
}
|
|
2541
|
-
if (event.buttons === 0) {
|
|
2542
|
-
this.scheduleDeferredDragEnd();
|
|
2543
|
-
return;
|
|
2544
|
-
}
|
|
2545
2264
|
this.lastDragPointerPosition = { x: event.clientX, y: event.clientY };
|
|
2546
2265
|
this.updateDraggedFloatingPositionFromPoint(event.clientX, event.clientY);
|
|
2547
2266
|
}
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
return;
|
|
2552
|
-
}
|
|
2553
|
-
const touch = event.touches[0];
|
|
2554
|
-
if (!touch) {
|
|
2555
|
-
this.scheduleDeferredDragEnd();
|
|
2556
|
-
return;
|
|
2557
|
-
}
|
|
2558
|
-
event.preventDefault();
|
|
2559
|
-
event.stopPropagation();
|
|
2560
|
-
this.lastDragPointerPosition = { x: touch.clientX, y: touch.clientY };
|
|
2561
|
-
this.updateDraggedFloatingPositionFromPoint(touch.clientX, touch.clientY);
|
|
2562
|
-
}
|
|
2563
|
-
onDragMouseUp() {
|
|
2564
|
-
// Prefer committing a drop from pointer-up since some browsers suppress drop events
|
|
2267
|
+
onDragPointerUp(event) {
|
|
2268
|
+
// Commit the drop from pointer release; the pointer-up coordinates are
|
|
2269
|
+
// authoritative for which stack/zone the user dropped into.
|
|
2565
2270
|
if (this.dragState) {
|
|
2566
|
-
const
|
|
2567
|
-
|
|
2568
|
-
|
|
2271
|
+
const x = Number.isFinite(event.clientX) ? event.clientX : this.lastDragPointerPosition?.x;
|
|
2272
|
+
const y = Number.isFinite(event.clientY) ? event.clientY : this.lastDragPointerPosition?.y;
|
|
2273
|
+
if (x !== undefined && y !== undefined) {
|
|
2274
|
+
this.finalizeDropFromPoint(x, y);
|
|
2569
2275
|
}
|
|
2570
2276
|
}
|
|
2571
2277
|
this.handleDragPointerUpCommon();
|
|
2572
2278
|
}
|
|
2279
|
+
onDragPointerCancel() {
|
|
2280
|
+
// OS-level cancel (e.g. pointer capture lost): end the drag without
|
|
2281
|
+
// committing a drop.
|
|
2282
|
+
this.handleDragPointerUpCommon();
|
|
2283
|
+
}
|
|
2573
2284
|
// Convert a currently in-header tab drag into a floating window
|
|
2574
2285
|
convertPendingTabDragToFloating(clientX, clientY) {
|
|
2575
2286
|
if (!this.dragState) {
|
|
@@ -2602,22 +2313,34 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2602
2313
|
: stackRect && Number.isFinite(stackRect.height)
|
|
2603
2314
|
? stackRect.height
|
|
2604
2315
|
: fallbackHeight;
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
const
|
|
2619
|
-
?
|
|
2620
|
-
:
|
|
2316
|
+
// Place the floating wrapper exactly where the docked stack was, so the
|
|
2317
|
+
// pane appears in-place at the moment of detach instead of jumping under
|
|
2318
|
+
// the cursor. metrics.left/top are host-relative (captured at pointerdown
|
|
2319
|
+
// in captureTabDragMetrics). Compensate for the floating wrapper's 1px
|
|
2320
|
+
// border so the visible content edge lines up with the original stack's
|
|
2321
|
+
// visible content edge (the docked .dock-stack also has a 1px border, so
|
|
2322
|
+
// the inner content rectangles match after this offset).
|
|
2323
|
+
const FLOATING_BORDER = 1;
|
|
2324
|
+
const initialLeft = metrics && Number.isFinite(metrics.left)
|
|
2325
|
+
? metrics.left - FLOATING_BORDER
|
|
2326
|
+
: Number.isFinite(clientX)
|
|
2327
|
+
? clientX - hostRect.left - width / 2
|
|
2328
|
+
: 0;
|
|
2329
|
+
const initialTop = metrics && Number.isFinite(metrics.top)
|
|
2330
|
+
? metrics.top - FLOATING_BORDER
|
|
2331
|
+
: Number.isFinite(clientY)
|
|
2332
|
+
? clientY - hostRect.top - height / 2
|
|
2333
|
+
: 0;
|
|
2334
|
+
// Derive pointerOffset from the cursor's actual position relative to the
|
|
2335
|
+
// freshly-placed wrapper (not from pointerdown metrics) so the very next
|
|
2336
|
+
// pointermove translates into a wrapper move of exactly the cursor delta
|
|
2337
|
+
// — no jump, no drift.
|
|
2338
|
+
const pointerOffsetX = Number.isFinite(clientX)
|
|
2339
|
+
? clientX - hostRect.left - initialLeft
|
|
2340
|
+
: width / 2;
|
|
2341
|
+
const pointerOffsetY = Number.isFinite(clientY)
|
|
2342
|
+
? clientY - hostRect.top - initialTop
|
|
2343
|
+
: height / 2;
|
|
2621
2344
|
// Remove pane from its current stack and create a new floating entry
|
|
2622
2345
|
this.removePaneFromLocation(location, pane);
|
|
2623
2346
|
const floatingStack = {
|
|
@@ -2627,8 +2350,8 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2627
2350
|
};
|
|
2628
2351
|
const floatingLayout = {
|
|
2629
2352
|
bounds: {
|
|
2630
|
-
left:
|
|
2631
|
-
top:
|
|
2353
|
+
left: initialLeft,
|
|
2354
|
+
top: initialTop,
|
|
2632
2355
|
width,
|
|
2633
2356
|
height,
|
|
2634
2357
|
},
|
|
@@ -2637,7 +2360,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2637
2360
|
};
|
|
2638
2361
|
this.floatingLayouts.push(floatingLayout);
|
|
2639
2362
|
const newIndex = this.floatingLayouts.length - 1;
|
|
2640
|
-
this.
|
|
2363
|
+
this.renderLayout();
|
|
2641
2364
|
const wrapper = this.getFloatingWrapper(newIndex);
|
|
2642
2365
|
if (wrapper) {
|
|
2643
2366
|
this.promoteFloatingPane(newIndex, wrapper);
|
|
@@ -2647,32 +2370,54 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2647
2370
|
state.floatingIndex = newIndex;
|
|
2648
2371
|
state.pointerOffsetX = pointerOffsetX;
|
|
2649
2372
|
state.pointerOffsetY = pointerOffsetY;
|
|
2373
|
+
// Now that the wrapper exists, mark it so pointer-events:none kicks in
|
|
2374
|
+
// and findStackAtPoint can see through to docked stacks underneath.
|
|
2375
|
+
this.markDraggedFloatingWrapper();
|
|
2650
2376
|
this.dispatchLayoutChanged();
|
|
2651
2377
|
}
|
|
2652
|
-
//
|
|
2653
|
-
//
|
|
2654
|
-
//
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2378
|
+
// Toggle data-dragging on the floating wrapper currently associated with
|
|
2379
|
+
// the active pane drag (dragState.floatingIndex), if any. Used to make the
|
|
2380
|
+
// wrapper transparent to elementsFromPoint so drop zones can be shown on
|
|
2381
|
+
// stacks underneath. clearDraggedFloatingWrapper() is the inverse.
|
|
2382
|
+
markDraggedFloatingWrapper() {
|
|
2383
|
+
const fi = this.dragState?.floatingIndex;
|
|
2384
|
+
if (fi === null || fi === undefined || fi < 0)
|
|
2385
|
+
return;
|
|
2386
|
+
const wrapper = this.getFloatingWrapper(fi);
|
|
2387
|
+
if (wrapper)
|
|
2388
|
+
wrapper.dataset['dragging'] = 'true';
|
|
2389
|
+
}
|
|
2390
|
+
clearDraggedFloatingWrapperMarkers() {
|
|
2391
|
+
const layer = this.floatingLayerEl;
|
|
2392
|
+
if (!layer)
|
|
2393
|
+
return;
|
|
2394
|
+
layer.querySelectorAll('.dock-floating[data-dragging="true"]').forEach((el) => {
|
|
2395
|
+
delete el.dataset['dragging'];
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
// Compute the intended tab insert index within a stack's strip based on pointer X.
|
|
2399
|
+
// Uses the rendered tab buttons inside mp-tab-control's shadow strip for geometry;
|
|
2400
|
+
// the dragged tab is hidden during drag (its content has data-hidden), and the
|
|
2401
|
+
// placeholder button (if present) gives us the dragged-position reference.
|
|
2402
|
+
computeHeaderInsertIndex(stack, clientX) {
|
|
2403
|
+
if (stack.tagName !== 'MP-TAB-CONTROL')
|
|
2404
|
+
return 0;
|
|
2405
|
+
const allTabButtons = this.getStackTabButtons(stack);
|
|
2406
|
+
if (allTabButtons.length === 0) {
|
|
2658
2407
|
return 0;
|
|
2659
2408
|
}
|
|
2660
|
-
const
|
|
2661
|
-
const
|
|
2662
|
-
|
|
2409
|
+
const placeholderHeader = stack.querySelector(':scope > .dock-tab[data-placeholder="true"]');
|
|
2410
|
+
const placeholderTabId = placeholderHeader?.dataset['tabId'];
|
|
2411
|
+
const placeholderButton = placeholderTabId
|
|
2412
|
+
? allTabButtons.find((b) => b.id === `${placeholderTabId}-header-button`) ?? null
|
|
2663
2413
|
: null;
|
|
2664
|
-
const
|
|
2665
|
-
const targets = allTabs.filter((t) => t !== draggedEl && t !== placeholderEl);
|
|
2414
|
+
const targets = allTabButtons.filter((b) => b !== placeholderButton);
|
|
2666
2415
|
if (targets.length === 0) {
|
|
2667
2416
|
return 0;
|
|
2668
2417
|
}
|
|
2669
2418
|
const rightBias = 12;
|
|
2670
2419
|
const leftBias = 0;
|
|
2671
|
-
const baseRect =
|
|
2672
|
-
? placeholderEl.getBoundingClientRect()
|
|
2673
|
-
: draggedEl
|
|
2674
|
-
? draggedEl.getBoundingClientRect()
|
|
2675
|
-
: null;
|
|
2420
|
+
const baseRect = placeholderButton ? placeholderButton.getBoundingClientRect() : null;
|
|
2676
2421
|
const rectValid = !!baseRect && Number.isFinite(baseRect.width) && baseRect.width > 0;
|
|
2677
2422
|
const draggedCenter = rectValid && baseRect ? baseRect.left + baseRect.width / 2 : null;
|
|
2678
2423
|
for (let i = 0; i < targets.length; i += 1) {
|
|
@@ -2706,9 +2451,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2706
2451
|
}
|
|
2707
2452
|
}
|
|
2708
2453
|
}
|
|
2709
|
-
onDragTouchEnd() {
|
|
2710
|
-
this.handleDragPointerUpCommon();
|
|
2711
|
-
}
|
|
2712
2454
|
// Commit a drop using current pointer coordinates and joystick state
|
|
2713
2455
|
finalizeDropFromPoint(clientX, clientY) {
|
|
2714
2456
|
if (!this.dragState) {
|
|
@@ -2734,17 +2476,14 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2734
2476
|
stackPath &&
|
|
2735
2477
|
this.pathsEqual(stackPath, this.dragState.sourcePath) &&
|
|
2736
2478
|
(!zone || zone === 'center')) {
|
|
2737
|
-
const
|
|
2738
|
-
if (
|
|
2739
|
-
const
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
this.dragState.dropHandled = true;
|
|
2746
|
-
return;
|
|
2747
|
-
}
|
|
2479
|
+
const location = this.resolveStackLocation(path);
|
|
2480
|
+
if (location) {
|
|
2481
|
+
const idx = this.computeHeaderInsertIndex(stack, clientX);
|
|
2482
|
+
this.reorderPaneInLocationAtIndex(location, this.dragState.pane, idx);
|
|
2483
|
+
this.renderLayout();
|
|
2484
|
+
this.dispatchLayoutChanged();
|
|
2485
|
+
this.dragState.dropHandled = true;
|
|
2486
|
+
return;
|
|
2748
2487
|
}
|
|
2749
2488
|
}
|
|
2750
2489
|
if (path && this.isDropZone(zone)) {
|
|
@@ -2783,112 +2522,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2783
2522
|
? win.setTimeout(completeDrag, 0)
|
|
2784
2523
|
: setTimeout(completeDrag, 0);
|
|
2785
2524
|
}
|
|
2786
|
-
onDrop(event) {
|
|
2787
|
-
if (!this.dragState) {
|
|
2788
|
-
return;
|
|
2789
|
-
}
|
|
2790
|
-
event.preventDefault();
|
|
2791
|
-
const pointFromEvent = Number.isFinite(event.clientX) && Number.isFinite(event.clientY)
|
|
2792
|
-
? { clientX: event.clientX, clientY: event.clientY }
|
|
2793
|
-
: null;
|
|
2794
|
-
const point = pointFromEvent ??
|
|
2795
|
-
(this.lastDragPointerPosition
|
|
2796
|
-
? {
|
|
2797
|
-
clientX: this.lastDragPointerPosition.x,
|
|
2798
|
-
clientY: this.lastDragPointerPosition.y,
|
|
2799
|
-
}
|
|
2800
|
-
: null);
|
|
2801
|
-
const stack = this.findStackElement(event) ??
|
|
2802
|
-
(point ? this.findStackAtPoint(point.clientX, point.clientY) : null);
|
|
2803
|
-
// Prefer joystick's stored target path when the joystick is visible (drop over buttons)
|
|
2804
|
-
const joystickVisible = this.dropJoystick.dataset['visible'] === 'true';
|
|
2805
|
-
const joystickPath = this.parsePath(this.dropJoystick.dataset['path']);
|
|
2806
|
-
const joystickTarget = this.dropJoystickTarget;
|
|
2807
|
-
const joystickTargetPath = joystickTarget ? this.parsePath(joystickTarget.dataset['path']) : null;
|
|
2808
|
-
let path = stack
|
|
2809
|
-
? this.parsePath(stack.dataset['path'])
|
|
2810
|
-
: (joystickPath ?? joystickTargetPath);
|
|
2811
|
-
if (!path && joystickVisible) {
|
|
2812
|
-
// As a last resort, target the main dock surface only when empty
|
|
2813
|
-
const dockPath = this.parsePath(this.dockedEl.dataset['path']);
|
|
2814
|
-
path = (!this.rootLayout ? dockPath : null);
|
|
2815
|
-
}
|
|
2816
|
-
// Defer same-header reorder decision until after zone resolution below
|
|
2817
|
-
// Prefer joystick's active zone if available, else infer from event/point
|
|
2818
|
-
const joystickZone = this.dropJoystick.dataset['zone'];
|
|
2819
|
-
const eventZoneHint = this.extractDropZoneFromEvent(event);
|
|
2820
|
-
const pointZoneHint = point ? this.findDropZoneByPoint(point.clientX, point.clientY) : null;
|
|
2821
|
-
const zone = this.isDropZone(joystickZone)
|
|
2822
|
-
? joystickZone
|
|
2823
|
-
: stack
|
|
2824
|
-
? this.computeDropZone(stack, point ?? event, pointZoneHint ?? eventZoneHint)
|
|
2825
|
-
: (this.isDropZone(pointZoneHint ?? eventZoneHint) ? (pointZoneHint ?? eventZoneHint) : null);
|
|
2826
|
-
// If still in same header and no side zone chosen, treat as in-header reorder
|
|
2827
|
-
if (this.dragState &&
|
|
2828
|
-
this.dragState.floatingIndex !== null &&
|
|
2829
|
-
this.dragState.floatingIndex < 0 &&
|
|
2830
|
-
stack &&
|
|
2831
|
-
path &&
|
|
2832
|
-
this.pathsEqual(path, this.dragState.sourcePath) &&
|
|
2833
|
-
(!zone || zone === 'center')) {
|
|
2834
|
-
const header = stack.querySelector('.dock-stack__header');
|
|
2835
|
-
if (header) {
|
|
2836
|
-
const x = (point ? point.clientX : event.clientX);
|
|
2837
|
-
if (Number.isFinite(x)) {
|
|
2838
|
-
const location = this.resolveStackLocation(path);
|
|
2839
|
-
if (location) {
|
|
2840
|
-
const idx = this.computeHeaderInsertIndex(header, x);
|
|
2841
|
-
this.reorderPaneInLocationAtIndex(location, this.dragState.pane, idx);
|
|
2842
|
-
this.render();
|
|
2843
|
-
this.dispatchLayoutChanged();
|
|
2844
|
-
this.dragState.dropHandled = true;
|
|
2845
|
-
this.endPaneDrag();
|
|
2846
|
-
return;
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
}
|
|
2850
|
-
}
|
|
2851
|
-
// If joystick is visible and both path and zone are resolved, force using joystick as authoritative
|
|
2852
|
-
if (joystickVisible && path && this.isDropZone(joystickZone)) {
|
|
2853
|
-
this.handleDrop(path, joystickZone);
|
|
2854
|
-
this.endPaneDrag();
|
|
2855
|
-
return;
|
|
2856
|
-
}
|
|
2857
|
-
if (!zone) {
|
|
2858
|
-
this.hideDropIndicator();
|
|
2859
|
-
this.endPaneDrag();
|
|
2860
|
-
return;
|
|
2861
|
-
}
|
|
2862
|
-
this.handleDrop(path, zone);
|
|
2863
|
-
this.endPaneDrag();
|
|
2864
|
-
}
|
|
2865
|
-
onDragLeave(event) {
|
|
2866
|
-
const related = event.relatedTarget;
|
|
2867
|
-
// During active drags, browsers can emit spurious dragleave with null
|
|
2868
|
-
// relatedTarget while the pointer is still over the joystick/buttons.
|
|
2869
|
-
// Be conservative: if we can resolve a stack/joystick at the last known
|
|
2870
|
-
// pointer position, don’t hide (prevents flicker of active state).
|
|
2871
|
-
if (this.dragState) {
|
|
2872
|
-
const pos = (Number.isFinite(event.clientX) && Number.isFinite(event.clientY))
|
|
2873
|
-
? { x: event.clientX, y: event.clientY }
|
|
2874
|
-
: this.lastDragPointerPosition;
|
|
2875
|
-
if (pos) {
|
|
2876
|
-
const stackAtPoint = this.findStackAtPoint(pos.x, pos.y);
|
|
2877
|
-
if (stackAtPoint) {
|
|
2878
|
-
return; // still inside our drop area; ignore this dragleave
|
|
2879
|
-
}
|
|
2880
|
-
}
|
|
2881
|
-
}
|
|
2882
|
-
if (!related) {
|
|
2883
|
-
this.hideDropIndicator();
|
|
2884
|
-
return;
|
|
2885
|
-
}
|
|
2886
|
-
const rootContains = this.rootEl.contains(related);
|
|
2887
|
-
const joystickContains = this.dropJoystick.contains(related);
|
|
2888
|
-
if (!rootContains && !joystickContains) {
|
|
2889
|
-
this.hideDropIndicator();
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
2525
|
handleDrop(targetPath, zone) {
|
|
2893
2526
|
if (!this.dragState || !targetPath) {
|
|
2894
2527
|
return;
|
|
@@ -2911,7 +2544,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2911
2544
|
if (stackEmptied) {
|
|
2912
2545
|
this.cleanupLocation(source);
|
|
2913
2546
|
}
|
|
2914
|
-
this.
|
|
2547
|
+
this.renderLayout();
|
|
2915
2548
|
this.dispatchLayoutChanged();
|
|
2916
2549
|
if (this.dragState) {
|
|
2917
2550
|
this.dragState.dropHandled = true;
|
|
@@ -2926,7 +2559,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2926
2559
|
return;
|
|
2927
2560
|
}
|
|
2928
2561
|
this.reorderPaneInLocation(source, pane);
|
|
2929
|
-
this.
|
|
2562
|
+
this.renderLayout();
|
|
2930
2563
|
this.dispatchLayoutChanged();
|
|
2931
2564
|
if (this.dragState) {
|
|
2932
2565
|
this.dragState.dropHandled = true;
|
|
@@ -2940,7 +2573,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2940
2573
|
if (stackEmptied) {
|
|
2941
2574
|
this.cleanupLocation(source);
|
|
2942
2575
|
}
|
|
2943
|
-
this.
|
|
2576
|
+
this.renderLayout();
|
|
2944
2577
|
this.dispatchLayoutChanged();
|
|
2945
2578
|
if (this.dragState) {
|
|
2946
2579
|
this.dragState.dropHandled = true;
|
|
@@ -2961,7 +2594,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2961
2594
|
if (stackEmptied) {
|
|
2962
2595
|
this.cleanupLocation(source);
|
|
2963
2596
|
}
|
|
2964
|
-
this.
|
|
2597
|
+
this.renderLayout();
|
|
2965
2598
|
this.dispatchLayoutChanged();
|
|
2966
2599
|
return;
|
|
2967
2600
|
}
|
|
@@ -2971,7 +2604,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2971
2604
|
if (stackEmptied) {
|
|
2972
2605
|
this.cleanupLocation(source);
|
|
2973
2606
|
}
|
|
2974
|
-
this.
|
|
2607
|
+
this.renderLayout();
|
|
2975
2608
|
this.dispatchLayoutChanged();
|
|
2976
2609
|
if (this.dragState) {
|
|
2977
2610
|
this.dragState.dropHandled = true;
|
|
@@ -2988,7 +2621,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
2988
2621
|
if (!target && targetPath.type === 'docked' && !this.rootLayout) {
|
|
2989
2622
|
this.rootLayout = this.cloneLayoutNode(source.root);
|
|
2990
2623
|
this.removeFloatingAt(sourceIndex);
|
|
2991
|
-
this.
|
|
2624
|
+
this.renderLayout();
|
|
2992
2625
|
this.dispatchLayoutChanged();
|
|
2993
2626
|
return true;
|
|
2994
2627
|
}
|
|
@@ -3013,7 +2646,7 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
3013
2646
|
this.setActivePaneForLocation(target, activePane);
|
|
3014
2647
|
}
|
|
3015
2648
|
this.removeFloatingAt(sourceIndex);
|
|
3016
|
-
this.
|
|
2649
|
+
this.renderLayout();
|
|
3017
2650
|
this.dispatchLayoutChanged();
|
|
3018
2651
|
return true;
|
|
3019
2652
|
}
|
|
@@ -3025,13 +2658,13 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
3025
2658
|
floating.root = this.dockNodeBeside(floating.root, target.node, source.root, zone);
|
|
3026
2659
|
floating.activePane = source.activePane ?? this.findFirstPaneName(source.root) ?? undefined;
|
|
3027
2660
|
this.removeFloatingAt(sourceIndex);
|
|
3028
|
-
this.
|
|
2661
|
+
this.renderLayout();
|
|
3029
2662
|
this.dispatchLayoutChanged();
|
|
3030
2663
|
return true;
|
|
3031
2664
|
}
|
|
3032
2665
|
this.rootLayout = this.dockNodeBeside(this.rootLayout, target.node, source.root, zone);
|
|
3033
2666
|
this.removeFloatingAt(sourceIndex);
|
|
3034
|
-
this.
|
|
2667
|
+
this.renderLayout();
|
|
3035
2668
|
this.dispatchLayoutChanged();
|
|
3036
2669
|
return true;
|
|
3037
2670
|
}
|
|
@@ -3355,24 +2988,6 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
3355
2988
|
}
|
|
3356
2989
|
return null;
|
|
3357
2990
|
}
|
|
3358
|
-
findStackElement(event) {
|
|
3359
|
-
const path = event.composedPath();
|
|
3360
|
-
const stack = this.findStackInTargets(path);
|
|
3361
|
-
if (stack) {
|
|
3362
|
-
return stack;
|
|
3363
|
-
}
|
|
3364
|
-
// If the root dock area is empty, treat the docked surface as a valid
|
|
3365
|
-
// target when it appears in the composed path.
|
|
3366
|
-
if (!this.rootLayout) {
|
|
3367
|
-
for (const target of path) {
|
|
3368
|
-
if (target instanceof HTMLElement &&
|
|
3369
|
-
(target === this.dockedEl || target.classList.contains('dock-docked'))) {
|
|
3370
|
-
return this.dockedEl;
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
return null;
|
|
3375
|
-
}
|
|
3376
2991
|
findStackInTargets(targets) {
|
|
3377
2992
|
for (const element of targets) {
|
|
3378
2993
|
if (!(element instanceof HTMLElement)) {
|
|
@@ -3392,21 +3007,16 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
3392
3007
|
}
|
|
3393
3008
|
activatePane(stack, paneName, path) {
|
|
3394
3009
|
stack.dataset['activePane'] = paneName;
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
if (pane.dataset['pane'] === paneName) {
|
|
3404
|
-
pane.removeAttribute('hidden');
|
|
3010
|
+
// Reflect to mp-tab-control's `active-tab` attribute. The WC handles
|
|
3011
|
+
// strip button styling (active class, aria-selected) + body-slot
|
|
3012
|
+
// projection automatically via the named-slot pattern.
|
|
3013
|
+
if (stack.tagName === 'MP-TAB-CONTROL') {
|
|
3014
|
+
const headerSpan = stack.querySelector(`:scope > .dock-tab[data-pane="${CSS.escape(paneName)}"]`);
|
|
3015
|
+
const tabId = headerSpan?.dataset['tabId'];
|
|
3016
|
+
if (tabId) {
|
|
3017
|
+
stack.setAttribute('active-tab', tabId);
|
|
3405
3018
|
}
|
|
3406
|
-
|
|
3407
|
-
pane.setAttribute('hidden', '');
|
|
3408
|
-
}
|
|
3409
|
-
});
|
|
3019
|
+
}
|
|
3410
3020
|
const location = this.resolveStackLocation(path);
|
|
3411
3021
|
if (!location) {
|
|
3412
3022
|
return;
|
|
@@ -3612,7 +3222,10 @@ class MintDockManagerElement extends HTMLElement {
|
|
|
3612
3222
|
return { type: 'docked', segments: [...path.segments] };
|
|
3613
3223
|
}
|
|
3614
3224
|
parsePath(path) {
|
|
3615
|
-
|
|
3225
|
+
// The root splitter is tagged with data-path="" (raw segments-join of an
|
|
3226
|
+
// empty array) so empty string is a valid path representing root docked.
|
|
3227
|
+
// Only null/undefined is "no path".
|
|
3228
|
+
if (path == null) {
|
|
3616
3229
|
return null;
|
|
3617
3230
|
}
|
|
3618
3231
|
if (path.startsWith('f:')) {
|
|
@@ -3846,12 +3459,12 @@ class BsDockManagerComponent {
|
|
|
3846
3459
|
return this.cloneLayout(this._layout);
|
|
3847
3460
|
}
|
|
3848
3461
|
constructor() {
|
|
3849
|
-
this.layout = input(null, ...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
3462
|
+
this.layout = input(null, ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
3850
3463
|
this.layoutChange = output();
|
|
3851
3464
|
this.layoutSnapshotChange = output();
|
|
3852
|
-
this.layoutString = signal(null, ...(ngDevMode ? [{ debugName: "layoutString" }] : []));
|
|
3853
|
-
this.panes = contentChildren(BsDockPaneComponent, ...(ngDevMode ? [{ debugName: "panes" }] : []));
|
|
3854
|
-
this.managerRef = viewChild('manager', ...(ngDevMode ? [{ debugName: "managerRef" }] : []));
|
|
3465
|
+
this.layoutString = signal(null, ...(ngDevMode ? [{ debugName: "layoutString" }] : /* istanbul ignore next */ []));
|
|
3466
|
+
this.panes = contentChildren(BsDockPaneComponent, ...(ngDevMode ? [{ debugName: "panes" }] : /* istanbul ignore next */ []));
|
|
3467
|
+
this.managerRef = viewChild('manager', ...(ngDevMode ? [{ debugName: "managerRef" }] : /* istanbul ignore next */ []));
|
|
3855
3468
|
this.trackByPane = (_, pane) => pane.name();
|
|
3856
3469
|
this._layout = { root: null, floating: [] };
|
|
3857
3470
|
const documentRef = inject(DOCUMENT);
|
|
@@ -3914,10 +3527,10 @@ class BsDockManagerComponent {
|
|
|
3914
3527
|
cloneLayout(layout) {
|
|
3915
3528
|
return JSON.parse(JSON.stringify(layout));
|
|
3916
3529
|
}
|
|
3917
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
3918
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
3530
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BsDockManagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3531
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: BsDockManagerComponent, isStandalone: true, selector: "bs-dock-manager", inputs: { layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { layoutChange: "layoutChange", layoutSnapshotChange: "layoutSnapshotChange" }, queries: [{ propertyName: "panes", predicate: BsDockPaneComponent, isSignal: true }], viewQueries: [{ propertyName: "managerRef", first: true, predicate: ["manager"], descendants: true, isSignal: true }], ngImport: i0, template: "<mint-dock-manager\n #manager\n class=\"bs-dock-manager\"\n [attr.layout]=\"layoutString()\"\n (dock-layout-changed)=\"onLayoutChanged($event)\"\n >\n @for (pane of panes(); track trackByPane($index, pane)) {\n <div class=\"bs-dock-pane\" [attr.slot]=\"pane.name()\">\n <ng-container *ngTemplateOutlet=\"pane.template()\"></ng-container>\n </div>\n }\n</mint-dock-manager>\n", styles: [":host{display:block;width:100%;height:100%}.bs-dock-manager{display:block;width:100%;height:100%}.bs-dock-pane{display:contents}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3919
3532
|
}
|
|
3920
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
3533
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BsDockManagerComponent, decorators: [{
|
|
3921
3534
|
type: Component,
|
|
3922
3535
|
args: [{ selector: 'bs-dock-manager', imports: [NgTemplateOutlet], schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush, template: "<mint-dock-manager\n #manager\n class=\"bs-dock-manager\"\n [attr.layout]=\"layoutString()\"\n (dock-layout-changed)=\"onLayoutChanged($event)\"\n >\n @for (pane of panes(); track trackByPane($index, pane)) {\n <div class=\"bs-dock-pane\" [attr.slot]=\"pane.name()\">\n <ng-container *ngTemplateOutlet=\"pane.template()\"></ng-container>\n </div>\n }\n</mint-dock-manager>\n", styles: [":host{display:block;width:100%;height:100%}.bs-dock-manager{display:block;width:100%;height:100%}.bs-dock-pane{display:contents}\n"] }]
|
|
3923
3536
|
}], ctorParameters: () => [], propDecorators: { layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], layoutChange: [{ type: i0.Output, args: ["layoutChange"] }], layoutSnapshotChange: [{ type: i0.Output, args: ["layoutSnapshotChange"] }], panes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => BsDockPaneComponent), { isSignal: true }] }], managerRef: [{ type: i0.ViewChild, args: ['manager', { isSignal: true }] }] } });
|