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