@mintplayer/ng-bootstrap 21.22.0 → 21.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/mintplayer-ng-bootstrap-accordion.mjs +20 -20
- package/fesm2022/mintplayer-ng-bootstrap-accordion.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-alert.mjs +8 -8
- package/fesm2022/mintplayer-ng-bootstrap-alert.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-badge.mjs +5 -5
- package/fesm2022/mintplayer-ng-bootstrap-badge.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-breadcrumb.mjs +6 -6
- package/fesm2022/mintplayer-ng-bootstrap-breadcrumb.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-button-group.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-button-group.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-button-type.mjs +4 -4
- package/fesm2022/mintplayer-ng-bootstrap-button-type.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-calendar-month.mjs +9 -9
- package/fesm2022/mintplayer-ng-bootstrap-calendar-month.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-calendar.mjs +10 -10
- package/fesm2022/mintplayer-ng-bootstrap-calendar.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-card.mjs +8 -8
- package/fesm2022/mintplayer-ng-bootstrap-card.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-carousel.mjs +25 -25
- package/fesm2022/mintplayer-ng-bootstrap-carousel.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-close.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-close.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-code-snippet.mjs +7 -7
- package/fesm2022/mintplayer-ng-bootstrap-code-snippet.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-color-picker.mjs +58 -58
- package/fesm2022/mintplayer-ng-bootstrap-color-picker.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-container.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-container.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-context-menu.mjs +3 -3
- package/fesm2022/mintplayer-ng-bootstrap-context-menu.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-copy.mjs +4 -4
- package/fesm2022/mintplayer-ng-bootstrap-copy.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-datatable.mjs +20 -20
- package/fesm2022/mintplayer-ng-bootstrap-datatable.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-datepicker.mjs +6 -6
- package/fesm2022/mintplayer-ng-bootstrap-datepicker.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-bootstrap-dock.mjs +789 -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,27 @@ 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
|
-
background: linear-gradient(to bottom, rgba(
|
|
156
|
-
border-bottom: 1px solid
|
|
159
|
+
background: linear-gradient(to bottom, rgba(var(--bs-primary-rgb), 0.15), rgba(var(--bs-primary-rgb), 0.05));
|
|
160
|
+
border-bottom: 1px solid var(--bs-primary-border-subtle);
|
|
157
161
|
user-select: none;
|
|
158
162
|
-webkit-user-select: none;
|
|
159
163
|
}
|
|
@@ -162,7 +166,7 @@ const styles = unsafeCSS(`:host {
|
|
|
162
166
|
flex: 1 1 auto;
|
|
163
167
|
font-size: 0.875rem;
|
|
164
168
|
font-weight: 500;
|
|
165
|
-
color:
|
|
169
|
+
color: var(--bs-body-color);
|
|
166
170
|
overflow: hidden;
|
|
167
171
|
text-overflow: ellipsis;
|
|
168
172
|
white-space: nowrap;
|
|
@@ -178,13 +182,13 @@ const styles = unsafeCSS(`:host {
|
|
|
178
182
|
position: absolute;
|
|
179
183
|
pointer-events: auto;
|
|
180
184
|
z-index: 2;
|
|
181
|
-
background: rgba(
|
|
185
|
+
background: rgba(var(--bs-primary-rgb), 0.1);
|
|
182
186
|
transition: background 120ms ease;
|
|
183
187
|
}
|
|
184
188
|
|
|
185
189
|
.dock-floating__resizer:hover,
|
|
186
190
|
.dock-floating__resizer[data-resizing=true] {
|
|
187
|
-
background: rgba(
|
|
191
|
+
background: rgba(var(--bs-primary-rgb), 0.3);
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
.dock-floating__resizer--top,
|
|
@@ -254,75 +258,10 @@ const styles = unsafeCSS(`:host {
|
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
.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
261
|
flex: 1 1 0;
|
|
274
262
|
position: relative;
|
|
275
263
|
}
|
|
276
264
|
|
|
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
265
|
.dock-intersection-handle {
|
|
327
266
|
position: absolute;
|
|
328
267
|
width: 1rem;
|
|
@@ -330,12 +269,12 @@ const styles = unsafeCSS(`:host {
|
|
|
330
269
|
margin-left: -0.5rem;
|
|
331
270
|
margin-top: -0.5rem;
|
|
332
271
|
border-radius: 0.375rem;
|
|
333
|
-
background:
|
|
334
|
-
border: 1px solid
|
|
335
|
-
box-shadow:
|
|
272
|
+
background: var(--bs-primary-bg-subtle);
|
|
273
|
+
border: 1px solid var(--bs-primary-border-subtle);
|
|
274
|
+
box-shadow: var(--bs-box-shadow-sm);
|
|
336
275
|
cursor: all-scroll;
|
|
337
276
|
pointer-events: auto;
|
|
338
|
-
opacity: 0;
|
|
277
|
+
opacity: 0.45;
|
|
339
278
|
transition: background 120ms ease, border-color 120ms ease, opacity 120ms ease;
|
|
340
279
|
}
|
|
341
280
|
|
|
@@ -343,8 +282,8 @@ const styles = unsafeCSS(`:host {
|
|
|
343
282
|
.dock-intersection-handle:focus-visible,
|
|
344
283
|
.dock-intersection-handle[data-visible=true],
|
|
345
284
|
.dock-intersection-handle[data-resizing=true] {
|
|
346
|
-
background: rgba(
|
|
347
|
-
border-color:
|
|
285
|
+
background: rgba(var(--bs-primary-rgb), 0.35);
|
|
286
|
+
border-color: var(--bs-primary);
|
|
348
287
|
opacity: 1;
|
|
349
288
|
outline: none;
|
|
350
289
|
}
|
|
@@ -356,84 +295,44 @@ const styles = unsafeCSS(`:host {
|
|
|
356
295
|
margin-left: -3px;
|
|
357
296
|
margin-top: -3px;
|
|
358
297
|
border-radius: 50%;
|
|
359
|
-
background:
|
|
360
|
-
box-shadow: 0 0 0 2px
|
|
298
|
+
background: var(--bs-primary);
|
|
299
|
+
box-shadow: 0 0 0 2px var(--bs-primary-bg-subtle);
|
|
361
300
|
pointer-events: none;
|
|
362
301
|
z-index: 130;
|
|
363
302
|
}
|
|
364
303
|
|
|
365
304
|
.dock-stack {
|
|
366
|
-
display: flex;
|
|
367
|
-
flex-direction: column;
|
|
368
305
|
flex: 1 1 0;
|
|
369
|
-
border: 1px solid
|
|
306
|
+
border: 1px solid var(--bs-border-color);
|
|
370
307
|
border-radius: 0.25rem;
|
|
371
|
-
background:
|
|
308
|
+
background: var(--bs-body-bg);
|
|
372
309
|
backdrop-filter: blur(4px);
|
|
373
310
|
}
|
|
374
311
|
|
|
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
312
|
.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
313
|
cursor: grab;
|
|
393
|
-
|
|
314
|
+
display: block;
|
|
315
|
+
padding: 0.5rem 1rem;
|
|
316
|
+
margin: -0.5rem -1rem;
|
|
394
317
|
}
|
|
395
318
|
|
|
396
319
|
.dock-tab:active {
|
|
397
320
|
cursor: grabbing;
|
|
398
321
|
}
|
|
399
322
|
|
|
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
323
|
.dock-stack__pane {
|
|
421
324
|
position: relative;
|
|
422
|
-
flex: 1 1 100%;
|
|
423
325
|
display: flex;
|
|
424
326
|
flex-direction: column;
|
|
425
327
|
overflow: hidden;
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
.dock-stack__pane[hidden] {
|
|
429
|
-
display: none !important;
|
|
328
|
+
height: 100%;
|
|
430
329
|
}
|
|
431
330
|
|
|
432
331
|
.dock-drop-indicator {
|
|
433
332
|
position: absolute;
|
|
434
333
|
pointer-events: none;
|
|
435
|
-
border: 2px solid
|
|
436
|
-
background: rgba(
|
|
334
|
+
border: 2px solid var(--bs-primary);
|
|
335
|
+
background: rgba(var(--bs-primary-rgb), 0.2);
|
|
437
336
|
border-radius: 0.25rem;
|
|
438
337
|
opacity: 0;
|
|
439
338
|
transition: opacity 120ms ease;
|
|
@@ -452,8 +351,8 @@ const styles = unsafeCSS(`:host {
|
|
|
452
351
|
gap: 0.125rem;
|
|
453
352
|
padding: 0.125rem;
|
|
454
353
|
border-radius: 999px;
|
|
455
|
-
background:
|
|
456
|
-
box-shadow:
|
|
354
|
+
background: var(--bs-tertiary-bg);
|
|
355
|
+
box-shadow: var(--bs-box-shadow);
|
|
457
356
|
pointer-events: none;
|
|
458
357
|
transform: translate(-50%, -50%);
|
|
459
358
|
z-index: 110;
|
|
@@ -472,9 +371,9 @@ const styles = unsafeCSS(`:host {
|
|
|
472
371
|
align-items: center;
|
|
473
372
|
justify-content: center;
|
|
474
373
|
border-radius: 0.375rem;
|
|
475
|
-
border: 1px solid
|
|
476
|
-
background:
|
|
477
|
-
color:
|
|
374
|
+
border: 1px solid var(--bs-primary-border-subtle);
|
|
375
|
+
background: var(--bs-body-bg);
|
|
376
|
+
color: var(--bs-primary);
|
|
478
377
|
font-size: 0.75rem;
|
|
479
378
|
line-height: 1;
|
|
480
379
|
font-weight: 600;
|
|
@@ -486,13 +385,13 @@ const styles = unsafeCSS(`:host {
|
|
|
486
385
|
.dock-drop-joystick__button[data-active=true],
|
|
487
386
|
.dock-drop-joystick__button:hover,
|
|
488
387
|
.dock-drop-joystick__button:focus-visible {
|
|
489
|
-
background:
|
|
490
|
-
border-color:
|
|
491
|
-
color:
|
|
388
|
+
background: var(--bs-primary-bg-subtle);
|
|
389
|
+
border-color: var(--bs-primary);
|
|
390
|
+
color: var(--bs-primary);
|
|
492
391
|
}
|
|
493
392
|
|
|
494
393
|
.dock-drop-joystick__button:focus-visible {
|
|
495
|
-
outline: 2px solid
|
|
394
|
+
outline: 2px solid var(--bs-primary);
|
|
496
395
|
outline-offset: 1px;
|
|
497
396
|
}
|
|
498
397
|
|
|
@@ -519,39 +418,6 @@ class MintDockManagerElement extends LitElement {
|
|
|
519
418
|
return [...(super.observedAttributes ?? []), 'layout'];
|
|
520
419
|
}
|
|
521
420
|
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
421
|
renderSnapMarkersForCorner() {
|
|
556
422
|
if (!this.showSnapMarkers)
|
|
557
423
|
return;
|
|
@@ -562,17 +428,23 @@ class MintDockManagerElement extends LitElement {
|
|
|
562
428
|
if (!this.cornerResizeState)
|
|
563
429
|
return;
|
|
564
430
|
const rootRect = this.rootEl.getBoundingClientRect();
|
|
565
|
-
// Compute representative center lines from
|
|
431
|
+
// Compute representative center lines from the dividers being resized.
|
|
432
|
+
// st.{hs,vs}[i].container is the <mp-splitter>; the divider lives in its
|
|
433
|
+
// shadow at getSplitterDividers(splitter)[index].
|
|
566
434
|
let centerX = null;
|
|
567
435
|
let centerY = null;
|
|
568
436
|
const st = this.cornerResizeState;
|
|
569
437
|
if (st.vs.length > 0) {
|
|
570
|
-
const
|
|
438
|
+
const v0 = st.vs[0];
|
|
439
|
+
const vDiv = this.getSplitterDividers(v0.container)[v0.index];
|
|
440
|
+
const vRect = vDiv?.getBoundingClientRect();
|
|
571
441
|
if (vRect)
|
|
572
442
|
centerX = vRect.left + vRect.width / 2 - rootRect.left;
|
|
573
443
|
}
|
|
574
444
|
if (st.hs.length > 0) {
|
|
575
|
-
const
|
|
445
|
+
const h0 = st.hs[0];
|
|
446
|
+
const hDiv = this.getSplitterDividers(h0.container)[h0.index];
|
|
447
|
+
const hRect = hDiv?.getBoundingClientRect();
|
|
576
448
|
if (hRect)
|
|
577
449
|
centerY = hRect.top + hRect.height / 2 - rootRect.top;
|
|
578
450
|
}
|
|
@@ -610,19 +482,16 @@ class MintDockManagerElement extends LitElement {
|
|
|
610
482
|
this.floatingLayouts = [];
|
|
611
483
|
this.titles = {};
|
|
612
484
|
this.pendingTabDragMetrics = null;
|
|
613
|
-
this.resizeState = null;
|
|
614
485
|
this.dragState = null;
|
|
615
486
|
this.floatingDragState = null;
|
|
616
487
|
this.floatingResizeState = null;
|
|
617
488
|
this.intersectionRaf = null;
|
|
618
|
-
this.
|
|
489
|
+
this.rootResizeObserver = null;
|
|
490
|
+
this.dockedMutationObserver = null;
|
|
619
491
|
this.cornerResizeState = null;
|
|
620
492
|
this.pointerTrackingActive = false;
|
|
621
493
|
this.dragPointerTrackingActive = false;
|
|
622
494
|
this.lastDragPointerPosition = null;
|
|
623
|
-
// Localized snapping while dragging a divider
|
|
624
|
-
this.activeSnapAxis = null;
|
|
625
|
-
this.activeSnapTargets = [];
|
|
626
495
|
// Localized snapping while dragging an intersection handle
|
|
627
496
|
this.cornerSnapXTargets = [];
|
|
628
497
|
this.cornerSnapYTargets = [];
|
|
@@ -631,19 +500,19 @@ class MintDockManagerElement extends LitElement {
|
|
|
631
500
|
this.pendingDragEndTimeout = null;
|
|
632
501
|
this.previousSplitSizes = new Map();
|
|
633
502
|
this.instanceId = `mint-dock-${++MintDockManagerElement.instanceCounter}`;
|
|
503
|
+
// Set windowRef eagerly so connectedCallback's window-level drag listeners
|
|
504
|
+
// (added before firstUpdated runs) can actually attach. Without this,
|
|
505
|
+
// win?.addEventListener was a silent no-op on first connect and HTML5
|
|
506
|
+
// drag-to-detach gestures never reached the dock — the floating wrapper
|
|
507
|
+
// was created but stayed at its conversion-time coordinates because the
|
|
508
|
+
// 'drag' listener that updates its position was never attached.
|
|
509
|
+
this.windowRef = typeof window !== 'undefined' ? window : null;
|
|
634
510
|
this.onPointerMove = this.onPointerMove.bind(this);
|
|
635
511
|
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);
|
|
512
|
+
this.onDragPointerMove = this.onDragPointerMove.bind(this);
|
|
513
|
+
this.onDragPointerUp = this.onDragPointerUp.bind(this);
|
|
514
|
+
this.onDragPointerCancel = this.onDragPointerCancel.bind(this);
|
|
515
|
+
this.onSplitterResize = this.onSplitterResize.bind(this);
|
|
647
516
|
}
|
|
648
517
|
render() {
|
|
649
518
|
return template;
|
|
@@ -688,56 +557,47 @@ class MintDockManagerElement extends LitElement {
|
|
|
688
557
|
// Tag the docked surface with a root path so it can act as
|
|
689
558
|
// a drop target when the main layout is empty.
|
|
690
559
|
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
|
-
});
|
|
560
|
+
// Drop targeting (drop indicator + joystick zone selection) runs entirely
|
|
561
|
+
// off pointer-based hit-testing in updatePaneDragDropTargetFromPoint and
|
|
562
|
+
// findDropZoneByPoint — no HTML5 dragover/drop/dragleave listeners needed.
|
|
711
563
|
// Render any layout that was set before the shadow DOM existed.
|
|
712
564
|
this.renderLayout();
|
|
565
|
+
// Reactive triggers for intersection-handle re-rendering. Each observer
|
|
566
|
+
// wakes scheduleRenderIntersectionHandles() (rAF-coalesced), which means
|
|
567
|
+
// multiple notifications in the same frame collapse to one render and
|
|
568
|
+
// the rAF tick gives <mp-splitter> elements time to populate their
|
|
569
|
+
// shadow roots before we query their dividers.
|
|
570
|
+
this.rootResizeObserver = new ResizeObserver(() => this.scheduleRenderIntersectionHandles());
|
|
571
|
+
this.rootResizeObserver.observe(this.rootEl);
|
|
572
|
+
this.dockedMutationObserver = new MutationObserver(() => this.scheduleRenderIntersectionHandles());
|
|
573
|
+
this.dockedMutationObserver.observe(this.dockedEl, { childList: true, subtree: true });
|
|
574
|
+
// mp-splitter dispatches bubbling 'resizing' / 'resize-end' on user drag;
|
|
575
|
+
// delegating on dockedEl catches every nested splitter without per-instance wiring.
|
|
576
|
+
this.dockedEl.addEventListener('resizing', this.onSplitterResize);
|
|
577
|
+
this.dockedEl.addEventListener('resize-end', this.onSplitterResize);
|
|
713
578
|
}
|
|
714
579
|
connectedCallback() {
|
|
715
580
|
super.connectedCallback();
|
|
716
581
|
if (!this.hasAttribute('role')) {
|
|
717
582
|
this.setAttribute('role', 'application');
|
|
718
583
|
}
|
|
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
584
|
}
|
|
725
585
|
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
586
|
const win = this.windowRef;
|
|
733
|
-
win?.removeEventListener('dragover', this.onGlobalDragOver);
|
|
734
|
-
win?.removeEventListener('drag', this.onDrag);
|
|
735
|
-
win?.removeEventListener('dragend', this.onGlobalDragEnd, true);
|
|
736
587
|
this.stopDragPointerTracking();
|
|
737
588
|
win?.removeEventListener('pointermove', this.onPointerMove);
|
|
738
589
|
win?.removeEventListener('pointerup', this.onPointerUp);
|
|
739
590
|
this.pointerTrackingActive = false;
|
|
740
|
-
|
|
591
|
+
this.rootResizeObserver?.disconnect();
|
|
592
|
+
this.rootResizeObserver = null;
|
|
593
|
+
this.dockedMutationObserver?.disconnect();
|
|
594
|
+
this.dockedMutationObserver = null;
|
|
595
|
+
this.dockedEl?.removeEventListener('resizing', this.onSplitterResize);
|
|
596
|
+
this.dockedEl?.removeEventListener('resize-end', this.onSplitterResize);
|
|
597
|
+
if (this.intersectionRaf !== null) {
|
|
598
|
+
this.windowRef?.clearTimeout(this.intersectionRaf);
|
|
599
|
+
this.intersectionRaf = null;
|
|
600
|
+
}
|
|
741
601
|
super.disconnectedCallback();
|
|
742
602
|
}
|
|
743
603
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
@@ -761,11 +621,52 @@ class MintDockManagerElement extends LitElement {
|
|
|
761
621
|
}
|
|
762
622
|
set layout(value) {
|
|
763
623
|
const snapshot = this.ensureSnapshot(value);
|
|
624
|
+
// While a drag/resize is in flight, the dock manager is the source of
|
|
625
|
+
// truth for layout state — its mid-drag mutations (e.g. floating bounds
|
|
626
|
+
// updated every mousemove, or a stack split during a pane-drag-to-floating
|
|
627
|
+
// conversion) race the host's two-way binding round-trip. The host re-
|
|
628
|
+
// feeds the layout we *just* dispatched via `dock-layout-changed`, but by
|
|
629
|
+
// the time the round-trip arrives the user has moved the cursor again, so
|
|
630
|
+
// the structural-equality guard below would let it through and clobber the
|
|
631
|
+
// in-progress state (e.g. snap a freshly-detached floating window back to
|
|
632
|
+
// the converted-at coordinates instead of letting it follow the cursor).
|
|
633
|
+
// Reject any external layout write during interaction; the host will sync
|
|
634
|
+
// back to the dock's final state when interaction ends and the dock fires
|
|
635
|
+
// a fresh dock-layout-changed event.
|
|
636
|
+
if (this.isInteracting())
|
|
637
|
+
return;
|
|
638
|
+
// Skip renderLayout when the incoming layout is structurally identical
|
|
639
|
+
// to the current state. After a divider drag the dock dispatches
|
|
640
|
+
// dock-layout-changed; an Angular host doing two-way binding will feed
|
|
641
|
+
// that snapshot right back through `[layout]` (and through the
|
|
642
|
+
// `[attr.layout]` round-trip). Without this guard, every drag-end
|
|
643
|
+
// tears down and rebuilds the whole splitter tree, giving a one-frame
|
|
644
|
+
// flash of `flex: 1 1 0` equal-share before the pin restores sizes.
|
|
645
|
+
const currentJson = JSON.stringify({
|
|
646
|
+
root: this.rootLayout,
|
|
647
|
+
floating: this.floatingLayouts,
|
|
648
|
+
titles: this.titles,
|
|
649
|
+
});
|
|
650
|
+
const newJson = JSON.stringify(snapshot);
|
|
651
|
+
if (currentJson === newJson)
|
|
652
|
+
return;
|
|
764
653
|
this.rootLayout = this.cloneLayoutNode(snapshot.root);
|
|
765
654
|
this.floatingLayouts = this.cloneFloatingArray(snapshot.floating);
|
|
766
655
|
this.titles = snapshot.titles ? { ...snapshot.titles } : {};
|
|
767
656
|
this.renderLayout();
|
|
768
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* True while the user is actively interacting with the dock — pane drag,
|
|
660
|
+
* floating window drag, floating window resize, or intersection corner
|
|
661
|
+
* resize. The `set layout` setter consults this to refuse external
|
|
662
|
+
* round-trips that would overwrite in-progress drag state.
|
|
663
|
+
*/
|
|
664
|
+
isInteracting() {
|
|
665
|
+
return !!(this.dragState ||
|
|
666
|
+
this.floatingDragState ||
|
|
667
|
+
this.floatingResizeState ||
|
|
668
|
+
this.cornerResizeState);
|
|
669
|
+
}
|
|
769
670
|
get snapshot() {
|
|
770
671
|
return this.layout;
|
|
771
672
|
}
|
|
@@ -831,7 +732,10 @@ class MintDockManagerElement extends LitElement {
|
|
|
831
732
|
this.dockedEl.appendChild(fragment);
|
|
832
733
|
}
|
|
833
734
|
this.renderFloatingPanes();
|
|
834
|
-
|
|
735
|
+
// Note: intersection handles are repositioned reactively via observers
|
|
736
|
+
// wired up in firstUpdated (rootResizeObserver, dockedMutationObserver,
|
|
737
|
+
// and delegated 'resizing' / 'resize-end' events). The MutationObserver
|
|
738
|
+
// on dockedEl fires when the renderNode subtree above is appended.
|
|
835
739
|
}
|
|
836
740
|
renderNode(node, path, floatingIndex) {
|
|
837
741
|
if (node.kind === 'split') {
|
|
@@ -926,104 +830,88 @@ class MintDockManagerElement extends LitElement {
|
|
|
926
830
|
this.floatingLayerEl.appendChild(wrapper);
|
|
927
831
|
});
|
|
928
832
|
}
|
|
929
|
-
|
|
930
|
-
//
|
|
833
|
+
onSplitterResize() {
|
|
834
|
+
// mp-splitter dispatches 'resizing' continuously during a divider drag
|
|
835
|
+
// and 'resize-end' when the user releases. Both keep the handle glued
|
|
836
|
+
// to the new intersection coordinate.
|
|
931
837
|
this.scheduleRenderIntersectionHandles();
|
|
932
838
|
}
|
|
933
839
|
scheduleRenderIntersectionHandles() {
|
|
934
|
-
this.intersectionRaf
|
|
935
|
-
|
|
840
|
+
if (this.intersectionRaf !== null)
|
|
841
|
+
return;
|
|
842
|
+
const win = this.windowRef;
|
|
843
|
+
if (!win)
|
|
844
|
+
return;
|
|
845
|
+
// Defer with setTimeout(5) instead of rAF so we run AFTER any
|
|
846
|
+
// flex-redistribution settles. Sequence we have to wait through:
|
|
847
|
+
// (1) DOM mutation (e.g. panel removed by drop)
|
|
848
|
+
// (2) microtasks: <mp-splitter>'s slotchange + size-pinning rAF
|
|
849
|
+
// (3) layout flush
|
|
850
|
+
// A bare rAF can fire before (2) resolves, so getBoundingClientRect on
|
|
851
|
+
// the dividers reads a transient flex-distributed position and the
|
|
852
|
+
// glyph lands ~tens of pixels off. 5ms is past the microtask queue and
|
|
853
|
+
// past splitter's pinning rAF in practice, so the divider rects we
|
|
854
|
+
// read are the settled, post-pin values.
|
|
855
|
+
this.intersectionRaf = win.setTimeout(() => {
|
|
856
|
+
this.intersectionRaf = null;
|
|
857
|
+
this.renderIntersectionHandles();
|
|
858
|
+
}, 5);
|
|
936
859
|
}
|
|
937
860
|
renderIntersectionHandles() {
|
|
938
861
|
const layer = this.shadowRoot?.querySelector('.dock-intersections-layer, .dock-intersection-layer');
|
|
939
862
|
if (!layer)
|
|
940
863
|
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
864
|
const rootRect = this.rootEl.getBoundingClientRect();
|
|
967
|
-
//
|
|
865
|
+
// Active corner-resize: keep st.handle alive (it owns pointer capture and
|
|
866
|
+
// the cornerResizeState references it). Update its position from current
|
|
867
|
+
// divider rects, drop every other handle by reference.
|
|
968
868
|
if (this.cornerResizeState) {
|
|
969
869
|
const st = this.cornerResizeState;
|
|
970
870
|
const h0 = st.hs[0];
|
|
971
871
|
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}"]`);
|
|
872
|
+
const hSplitter = this.findSplitterByPath(h0.path.segments);
|
|
873
|
+
const vSplitter = this.findSplitterByPath(v0.path.segments);
|
|
874
|
+
const hDiv = hSplitter ? this.getSplitterDividers(hSplitter)[h0.index] : null;
|
|
875
|
+
const vDiv = vSplitter ? this.getSplitterDividers(vSplitter)[v0.index] : null;
|
|
978
876
|
if (hDiv && vDiv) {
|
|
979
877
|
const hr = hDiv.getBoundingClientRect();
|
|
980
878
|
const vr = vDiv.getBoundingClientRect();
|
|
981
879
|
const x = vr.left + vr.width / 2 - rootRect.left;
|
|
982
880
|
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
|
|
881
|
+
st.handle.style.left = `${x}px`;
|
|
882
|
+
st.handle.style.top = `${y}px`;
|
|
991
883
|
Array.from(layer.querySelectorAll('.dock-intersection-handle')).forEach((el) => {
|
|
992
|
-
if (
|
|
884
|
+
if (el !== st.handle)
|
|
993
885
|
el.remove();
|
|
994
|
-
}
|
|
995
886
|
});
|
|
996
|
-
// Normalize internal map as well
|
|
997
|
-
this.intersectionHandles = new Map([[key, handle]]);
|
|
998
887
|
}
|
|
999
888
|
return;
|
|
1000
889
|
}
|
|
1001
|
-
|
|
890
|
+
// Idle path: full clear + rebuild. Cheaper to reason about than incremental
|
|
891
|
+
// diffing, and handles' positions are always derived from current divider
|
|
892
|
+
// rects so a layout change (drop, splitter restructure, flex redistribution)
|
|
893
|
+
// can never leave a stale glyph behind.
|
|
894
|
+
layer.replaceChildren();
|
|
1002
895
|
const hDividers = [];
|
|
1003
896
|
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
|
-
}
|
|
897
|
+
const allSplitters = Array.from(this.shadowRoot?.querySelectorAll('.dock-split') ?? []);
|
|
898
|
+
allSplitters.forEach((splitter) => {
|
|
899
|
+
const direction = splitter.dataset['direction'];
|
|
900
|
+
const pathStr = splitter.dataset['path'] ?? '';
|
|
901
|
+
this.getSplitterDividers(splitter).forEach((el, index) => {
|
|
902
|
+
const info = { rect: el.getBoundingClientRect(), pathStr, index };
|
|
903
|
+
// direction='horizontal' means children flow left-to-right, so the
|
|
904
|
+
// divider bars between them are VERTICAL (and vice-versa).
|
|
905
|
+
if (direction === 'horizontal')
|
|
906
|
+
vDividers.push(info);
|
|
907
|
+
else if (direction === 'vertical')
|
|
908
|
+
hDividers.push(info);
|
|
909
|
+
});
|
|
1022
910
|
});
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
const
|
|
1026
|
-
const
|
|
911
|
+
// Group intersections that round to the same on-screen pixel, so two
|
|
912
|
+
// sibling splitters whose dividers happen to overlap share one handle.
|
|
913
|
+
const tol = 24;
|
|
914
|
+
const groups = new Map();
|
|
1027
915
|
hDividers.forEach((h) => {
|
|
1028
916
|
const hCenterY = h.rect.top + h.rect.height / 2;
|
|
1029
917
|
vDividers.forEach((v) => {
|
|
@@ -1034,53 +922,33 @@ class MintDockManagerElement extends LitElement {
|
|
|
1034
922
|
return;
|
|
1035
923
|
const x = vCenterX - rootRect.left;
|
|
1036
924
|
const y = hCenterY - rootRect.top;
|
|
1037
|
-
const key = `${h.pathStr}:${h.index}|${v.pathStr}:${v.index}`;
|
|
1038
925
|
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`;
|
|
926
|
+
const group = groups.get(gk) ?? { x, y, pairs: [] };
|
|
927
|
+
group.pairs.push({ h: { pathStr: h.pathStr, index: h.index }, v: { pathStr: v.pathStr, index: v.index } });
|
|
928
|
+
groups.set(gk, group);
|
|
1064
929
|
});
|
|
1065
930
|
});
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
handle.
|
|
931
|
+
groups.forEach((group, gk) => {
|
|
932
|
+
const handle = this.documentRef.createElement('div');
|
|
933
|
+
handle.classList.add('dock-intersection-handle', 'glyph');
|
|
934
|
+
handle.setAttribute('role', 'separator');
|
|
935
|
+
handle.setAttribute('aria-label', 'Resize split intersection');
|
|
936
|
+
const firstPair = group.pairs[0];
|
|
937
|
+
const key = `${firstPair.h.pathStr}:${firstPair.h.index}|${firstPair.v.pathStr}:${firstPair.v.index}`;
|
|
938
|
+
handle.dataset['key'] = key;
|
|
939
|
+
handle.dataset['pairs'] = JSON.stringify(group.pairs);
|
|
940
|
+
handle.style.left = `${group.x}px`;
|
|
941
|
+
handle.style.top = `${group.y}px`;
|
|
942
|
+
// beginCornerResize/onIntersectionDoubleClick read data-pairs to
|
|
943
|
+
// reconstruct the (h, v) pair list, so the (h, v) args we pass here
|
|
944
|
+
// are only used as a fallback when data-pairs is empty — safe to use
|
|
945
|
+
// the first pair's structure as the seed.
|
|
946
|
+
const seedH = { path: this.parsePath(firstPair.h.pathStr), index: firstPair.h.index, container: this.findSplitterByPath(this.parsePath(firstPair.h.pathStr)?.segments ?? []) ?? this.rootEl, rect: new DOMRect() };
|
|
947
|
+
const seedV = { path: this.parsePath(firstPair.v.pathStr), index: firstPair.v.index, container: this.findSplitterByPath(this.parsePath(firstPair.v.pathStr)?.segments ?? []) ?? this.rootEl, rect: new DOMRect() };
|
|
948
|
+
handle.addEventListener('pointerdown', (ev) => this.beginCornerResize(ev, seedH, seedV, handle));
|
|
949
|
+
handle.addEventListener('dblclick', (ev) => this.onIntersectionDoubleClick(ev, handle));
|
|
950
|
+
layer.appendChild(handle);
|
|
1071
951
|
});
|
|
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
952
|
}
|
|
1085
953
|
beginCornerResize(event, h, v, handle) {
|
|
1086
954
|
event.preventDefault();
|
|
@@ -1093,20 +961,22 @@ class MintDockManagerElement extends LitElement {
|
|
|
1093
961
|
const path = this.parsePath(pathStr);
|
|
1094
962
|
if (!path)
|
|
1095
963
|
return;
|
|
1096
|
-
const
|
|
1097
|
-
|
|
1098
|
-
if (!container)
|
|
964
|
+
const splitter = this.findSplitterByPath(path.segments);
|
|
965
|
+
if (!splitter)
|
|
1099
966
|
return;
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
967
|
+
// Initial pixel sizes come from each panel-wrapper inside the splitter's
|
|
968
|
+
// shadow root. We capture them once on pointerdown and feed deltas to
|
|
969
|
+
// setPanelSizes() during the drag.
|
|
970
|
+
const panels = this.getSplitterPanels(splitter);
|
|
971
|
+
if (panels.length === 0)
|
|
972
|
+
return;
|
|
973
|
+
const dim = axis === 'h' ? 'height' : 'width';
|
|
974
|
+
const initial = panels.map((p) => p.getBoundingClientRect()[dim]);
|
|
975
|
+
const entry = { path, index, container: splitter, initialSizes: initial, before: initial[index], after: initial[index + 1] };
|
|
976
|
+
if (axis === 'h')
|
|
977
|
+
hs.push(entry);
|
|
978
|
+
else
|
|
979
|
+
vs.push(entry);
|
|
1110
980
|
};
|
|
1111
981
|
if (parsed.length > 0) {
|
|
1112
982
|
parsed.forEach((p) => { ensureHV(p.h.pathStr, p.h.index, 'h'); ensureHV(p.v.pathStr, p.v.index, 'v'); });
|
|
@@ -1138,44 +1008,47 @@ class MintDockManagerElement extends LitElement {
|
|
|
1138
1008
|
// Compute localized snap targets for this intersection
|
|
1139
1009
|
try {
|
|
1140
1010
|
const rootRect = this.rootEl.getBoundingClientRect();
|
|
1141
|
-
// Use first pair to define the crossing lines
|
|
1011
|
+
// Use first pair to define the crossing lines. Resolve dividers via
|
|
1012
|
+
// each splitter's shadow root.
|
|
1142
1013
|
let centerX = null;
|
|
1143
1014
|
let centerY = null;
|
|
1144
|
-
// Resolve one vertical bar (from vs) and one horizontal bar (from hs)
|
|
1145
1015
|
if (vs.length > 0) {
|
|
1146
1016
|
const vPair = vs[0];
|
|
1147
|
-
const
|
|
1148
|
-
const vDiv = this.shadowRoot?.querySelector(`.dock-split__divider[data-path="${vPathStr}"][data-index="${vPair.index}"]`) ?? null;
|
|
1017
|
+
const vDiv = this.getSplitterDividers(vPair.container)[vPair.index];
|
|
1149
1018
|
const vr = vDiv?.getBoundingClientRect();
|
|
1150
1019
|
if (vr)
|
|
1151
1020
|
centerX = vr.left + vr.width / 2;
|
|
1152
1021
|
}
|
|
1153
1022
|
if (hs.length > 0) {
|
|
1154
1023
|
const hPair = hs[0];
|
|
1155
|
-
const
|
|
1156
|
-
const hDiv = this.shadowRoot?.querySelector(`.dock-split__divider[data-path="${hPathStr}"][data-index="${hPair.index}"]`) ?? null;
|
|
1024
|
+
const hDiv = this.getSplitterDividers(hPair.container)[hPair.index];
|
|
1157
1025
|
const hr = hDiv?.getBoundingClientRect();
|
|
1158
1026
|
if (hr)
|
|
1159
1027
|
centerY = hr.top + hr.height / 2;
|
|
1160
1028
|
}
|
|
1161
1029
|
const xTargets = [];
|
|
1162
1030
|
const yTargets = [];
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1031
|
+
// Iterate every splitter, then flat-map its shadow dividers — a
|
|
1032
|
+
// splitter's data-direction tells us whether its bars are vertical
|
|
1033
|
+
// (horizontal split) or horizontal (vertical split).
|
|
1034
|
+
const allSplitters = Array.from(this.shadowRoot?.querySelectorAll('.dock-split') ?? []);
|
|
1035
|
+
allSplitters.forEach((splitter) => {
|
|
1036
|
+
const direction = splitter.dataset['direction'] ?? undefined;
|
|
1037
|
+
this.getSplitterDividers(splitter).forEach((el) => {
|
|
1038
|
+
const r = el.getBoundingClientRect();
|
|
1039
|
+
if (direction === 'horizontal' && centerY != null) {
|
|
1040
|
+
// vertical bar → contributes X if it crosses centerY
|
|
1041
|
+
if (centerY >= r.top && centerY <= r.bottom) {
|
|
1042
|
+
xTargets.push(r.left + r.width / 2 - rootRect.left);
|
|
1043
|
+
}
|
|
1171
1044
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1045
|
+
else if (direction === 'vertical' && centerX != null) {
|
|
1046
|
+
// horizontal bar → contributes Y if it crosses centerX
|
|
1047
|
+
if (centerX >= r.left && centerX <= r.right) {
|
|
1048
|
+
yTargets.push(r.top + r.height / 2 - rootRect.top);
|
|
1049
|
+
}
|
|
1177
1050
|
}
|
|
1178
|
-
}
|
|
1051
|
+
});
|
|
1179
1052
|
});
|
|
1180
1053
|
this.cornerSnapXTargets = xTargets;
|
|
1181
1054
|
this.cornerSnapYTargets = yTargets;
|
|
@@ -1236,49 +1109,37 @@ class MintDockManagerElement extends LitElement {
|
|
|
1236
1109
|
if (bestDist <= tol)
|
|
1237
1110
|
clientY = best;
|
|
1238
1111
|
}
|
|
1239
|
-
//
|
|
1240
|
-
|
|
1241
|
-
|
|
1112
|
+
// Apply the new pair sizes to one splitter's panel-wrappers via
|
|
1113
|
+
// mp-splitter's setPanelSizes(pixels) API. We persist the normalized
|
|
1114
|
+
// ratios on the layout node so renderSplit's initial sizing stays in sync.
|
|
1115
|
+
const applyPairSize = (entry, delta) => {
|
|
1116
|
+
const node = this.resolveSplitNode(entry.path);
|
|
1242
1117
|
if (!node)
|
|
1243
1118
|
return;
|
|
1244
|
-
const deltaY = clientY - h.startY;
|
|
1245
1119
|
const minSize = 48;
|
|
1246
|
-
const pairTotal =
|
|
1247
|
-
let newBefore = Math.min(Math.max(
|
|
1120
|
+
const pairTotal = entry.beforeSize + entry.afterSize;
|
|
1121
|
+
let newBefore = Math.min(Math.max(entry.beforeSize + delta, minSize), pairTotal - minSize);
|
|
1248
1122
|
newBefore = snapValue(newBefore, pairTotal, event.shiftKey);
|
|
1249
1123
|
const newAfter = pairTotal - newBefore;
|
|
1250
|
-
const sizesPx = [...
|
|
1251
|
-
sizesPx[
|
|
1252
|
-
sizesPx[
|
|
1124
|
+
const sizesPx = [...entry.initialSizes];
|
|
1125
|
+
sizesPx[entry.index] = newBefore;
|
|
1126
|
+
sizesPx[entry.index + 1] = newAfter;
|
|
1253
1127
|
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
|
-
});
|
|
1128
|
+
node.sizes = total > 0 ? sizesPx.map((s) => s / total) : [];
|
|
1129
|
+
entry.container
|
|
1130
|
+
.setPanelSizes?.(sizesPx);
|
|
1131
|
+
};
|
|
1132
|
+
// Update all horizontal bars (vertical splits) with Y delta, then all
|
|
1133
|
+
// vertical bars (horizontal splits) with X delta.
|
|
1134
|
+
state.hs.forEach((h) => applyPairSize(h, clientY - h.startY));
|
|
1135
|
+
state.vs.forEach((v) => applyPairSize(v, clientX - v.startX));
|
|
1281
1136
|
this.dispatchLayoutChanged();
|
|
1137
|
+
// setPanelSizes() is programmatic and doesn't fire 'resizing' events, so
|
|
1138
|
+
// the delegated listener on dockedEl doesn't wake during a corner drag.
|
|
1139
|
+
// Schedule the handle repositioning ourselves; renderIntersectionHandles
|
|
1140
|
+
// has a fast-path for the active cornerResizeState that just updates
|
|
1141
|
+
// left/top from the new divider rects.
|
|
1142
|
+
this.scheduleRenderIntersectionHandles();
|
|
1282
1143
|
}
|
|
1283
1144
|
endCornerResize(pointerId) {
|
|
1284
1145
|
const state = this.cornerResizeState;
|
|
@@ -1322,7 +1183,28 @@ class MintDockManagerElement extends LitElement {
|
|
|
1322
1183
|
let hasStored = false;
|
|
1323
1184
|
splitKeys.forEach((k) => { if (this.previousSplitSizes.has(k))
|
|
1324
1185
|
hasStored = true; });
|
|
1325
|
-
|
|
1186
|
+
// Persist `node.sizes` (normalized) and push pixel sizes into the
|
|
1187
|
+
// matching <mp-splitter> via setPanelSizes(). The splitter's panel
|
|
1188
|
+
// wrappers live in its shadow DOM, so direct flex mutation is no
|
|
1189
|
+
// longer an option.
|
|
1190
|
+
const pushSizesToSplitter = (path, normalized) => {
|
|
1191
|
+
const splitter = this.findSplitterByPath(path.segments);
|
|
1192
|
+
if (!splitter)
|
|
1193
|
+
return;
|
|
1194
|
+
const direction = splitter.dataset['direction'] ?? 'horizontal';
|
|
1195
|
+
const containerSize = direction === 'horizontal'
|
|
1196
|
+
? splitter.getBoundingClientRect().width
|
|
1197
|
+
: splitter.getBoundingClientRect().height;
|
|
1198
|
+
if (!Number.isFinite(containerSize) || containerSize <= 0)
|
|
1199
|
+
return;
|
|
1200
|
+
const totalWeight = normalized.reduce((s, w) => s + Math.max(w, 0), 0);
|
|
1201
|
+
if (totalWeight <= 0)
|
|
1202
|
+
return;
|
|
1203
|
+
const px = normalized.map((w) => (Math.max(w, 0) / totalWeight) * containerSize);
|
|
1204
|
+
splitter
|
|
1205
|
+
.setPanelSizes?.(px);
|
|
1206
|
+
};
|
|
1207
|
+
const applySizes = (pathStr, dividerIndex, mutate) => {
|
|
1326
1208
|
const path = this.parsePath(pathStr);
|
|
1327
1209
|
if (!path)
|
|
1328
1210
|
return;
|
|
@@ -1330,35 +1212,20 @@ class MintDockManagerElement extends LitElement {
|
|
|
1330
1212
|
if (!node)
|
|
1331
1213
|
return;
|
|
1332
1214
|
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);
|
|
1215
|
+
const newSizes = mutate([...sizes], dividerIndex);
|
|
1337
1216
|
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
|
-
}
|
|
1217
|
+
pushSizesToSplitter(path, newSizes);
|
|
1345
1218
|
};
|
|
1346
1219
|
if (hasStored) {
|
|
1347
1220
|
// Restore stored sizes
|
|
1348
1221
|
this.previousSplitSizes.forEach((sizes, pathStr) => {
|
|
1349
1222
|
const path = this.parsePath(pathStr);
|
|
1350
1223
|
const node = path ? this.resolveSplitNode(path) : null;
|
|
1351
|
-
if (!node)
|
|
1224
|
+
if (!node || !path)
|
|
1352
1225
|
return;
|
|
1353
1226
|
const norm = this.normalizeSizesArray(sizes, node.children.length);
|
|
1354
1227
|
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
|
-
}
|
|
1228
|
+
pushSizesToSplitter(path, norm);
|
|
1362
1229
|
});
|
|
1363
1230
|
this.previousSplitSizes.clear();
|
|
1364
1231
|
}
|
|
@@ -1376,7 +1243,7 @@ class MintDockManagerElement extends LitElement {
|
|
|
1376
1243
|
}
|
|
1377
1244
|
touched.add(key);
|
|
1378
1245
|
});
|
|
1379
|
-
|
|
1246
|
+
const equalize = (sizes, idx) => {
|
|
1380
1247
|
const total = (sizes[idx] ?? 0) + (sizes[idx + 1] ?? 0);
|
|
1381
1248
|
if (total <= 0)
|
|
1382
1249
|
return sizes;
|
|
@@ -1384,16 +1251,9 @@ class MintDockManagerElement extends LitElement {
|
|
|
1384
1251
|
sizes[idx + 1] = total / 2;
|
|
1385
1252
|
const sum = sizes.reduce((a, s) => a + s, 0);
|
|
1386
1253
|
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
|
-
});
|
|
1254
|
+
};
|
|
1255
|
+
applySizes(p.h.pathStr, p.h.index, equalize);
|
|
1256
|
+
applySizes(p.v.pathStr, p.v.index, equalize);
|
|
1397
1257
|
});
|
|
1398
1258
|
}
|
|
1399
1259
|
this.dispatchLayoutChanged();
|
|
@@ -1481,11 +1341,13 @@ class MintDockManagerElement extends LitElement {
|
|
|
1481
1341
|
}
|
|
1482
1342
|
try {
|
|
1483
1343
|
state.handle.releasePointerCapture(state.pointerId);
|
|
1484
|
-
delete state.handle.dataset['resizing'];
|
|
1485
1344
|
}
|
|
1486
1345
|
catch (err) {
|
|
1487
1346
|
/* no-op */
|
|
1488
1347
|
}
|
|
1348
|
+
// Clear outside the try so a thrown releasePointerCapture (capture
|
|
1349
|
+
// already lost) doesn't strand the handle in its visual drag state.
|
|
1350
|
+
delete state.handle.dataset['resizing'];
|
|
1489
1351
|
const dropHandled = state.dropTarget
|
|
1490
1352
|
? this.handleFloatingStackDrop(state.index, state.dropTarget.path, state.dropTarget.zone)
|
|
1491
1353
|
: false;
|
|
@@ -1581,6 +1443,12 @@ class MintDockManagerElement extends LitElement {
|
|
|
1581
1443
|
catch (err) {
|
|
1582
1444
|
/* no-op */
|
|
1583
1445
|
}
|
|
1446
|
+
// Clear `data-resizing` outside the try — releasePointerCapture can
|
|
1447
|
+
// throw if the capture was already lost (e.g., the pointer left the
|
|
1448
|
+
// window), and we still need to drop the resizing attribute or the
|
|
1449
|
+
// CSS rule `.dock-floating__resizer[data-resizing='true']` keeps the
|
|
1450
|
+
// border dark-blue forever.
|
|
1451
|
+
delete state.handle.dataset['resizing'];
|
|
1584
1452
|
this.floatingResizeState = null;
|
|
1585
1453
|
this.dispatchLayoutChanged();
|
|
1586
1454
|
}
|
|
@@ -1629,7 +1497,6 @@ class MintDockManagerElement extends LitElement {
|
|
|
1629
1497
|
}
|
|
1630
1498
|
stopPointerTrackingIfIdle() {
|
|
1631
1499
|
if (this.pointerTrackingActive &&
|
|
1632
|
-
!this.resizeState &&
|
|
1633
1500
|
!this.floatingDragState &&
|
|
1634
1501
|
!this.floatingResizeState &&
|
|
1635
1502
|
!this.cornerResizeState) {
|
|
@@ -1671,63 +1538,102 @@ class MintDockManagerElement extends LitElement {
|
|
|
1671
1538
|
titleEl.textContent = this.getFloatingWindowTitle(floating);
|
|
1672
1539
|
}
|
|
1673
1540
|
renderSplit(node, path, floatingIndex) {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
const
|
|
1541
|
+
// Each DockSplitNode renders as <mp-splitter>. The dock keeps its `.dock-split`
|
|
1542
|
+
// class on the host so existing `closest('.dock-split')` queries continue to
|
|
1543
|
+
// resolve, and stamps `data-direction` / `data-path` for the tree-driven
|
|
1544
|
+
// intersection-handle math.
|
|
1545
|
+
const splitter = this.documentRef.createElement('mp-splitter');
|
|
1546
|
+
splitter.classList.add('dock-split');
|
|
1547
|
+
splitter.dataset['direction'] = node.direction;
|
|
1548
|
+
splitter.dataset['path'] = path.join('/');
|
|
1549
|
+
// mp-splitter uses 'horizontal' (left-right) and 'vertical' (top-bottom).
|
|
1550
|
+
// The dock's DockSplitNode.direction matches that vocabulary 1:1.
|
|
1551
|
+
splitter.setAttribute('orientation', node.direction);
|
|
1552
|
+
const splitPath = typeof floatingIndex === 'number'
|
|
1553
|
+
? { type: 'floating', index: floatingIndex, segments: [...path] }
|
|
1554
|
+
: { type: 'docked', segments: [...path] };
|
|
1679
1555
|
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
|
-
|
|
1556
|
+
// mp-splitter accepts direct children — it wraps each in a panel-wrapper
|
|
1557
|
+
// inside its shadow DOM and projects via a named slot per index.
|
|
1558
|
+
splitter.appendChild(this.renderNode(child, [...path, index], floatingIndex));
|
|
1559
|
+
});
|
|
1560
|
+
// Apply persisted sizes from the layout tree once mp-splitter has built
|
|
1561
|
+
// its panel wrappers. mp-splitter's setPanelSizes interprets values as
|
|
1562
|
+
// pixel widths/heights; the dock's saved sizes are flex weights, so
|
|
1563
|
+
// convert using the splitter's measured cross-axis container size.
|
|
1564
|
+
const sizes = Array.isArray(node.sizes) ? node.sizes : [];
|
|
1565
|
+
if (sizes.length > 0) {
|
|
1566
|
+
requestAnimationFrame(() => {
|
|
1567
|
+
const totalWeight = sizes.reduce((s, w) => s + Math.max(w, 0), 0);
|
|
1568
|
+
if (totalWeight <= 0)
|
|
1569
|
+
return;
|
|
1570
|
+
const containerSize = node.direction === 'horizontal'
|
|
1571
|
+
? splitter.getBoundingClientRect().width
|
|
1572
|
+
: splitter.getBoundingClientRect().height;
|
|
1573
|
+
if (!Number.isFinite(containerSize) || containerSize <= 0)
|
|
1574
|
+
return;
|
|
1575
|
+
const px = sizes.map((w) => (Math.max(w, 0) / totalWeight) * containerSize);
|
|
1576
|
+
splitter
|
|
1577
|
+
.setPanelSizes?.(px);
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
// mp-splitter fires resize-end with pixel sizes after a divider drag.
|
|
1581
|
+
// Convert back to flex weights (sum to a stable total — keep current sum
|
|
1582
|
+
// so future renders interpret consistently) and persist to the layout tree.
|
|
1583
|
+
splitter.addEventListener('resize-end', (event) => {
|
|
1584
|
+
// resize-end bubbles, so a nested mp-splitter's drag end would also
|
|
1585
|
+
// reach this listener. Only react to events from THIS splitter, not
|
|
1586
|
+
// from a descendant — otherwise we'd apply the inner's sizes to the
|
|
1587
|
+
// outer's splitNode and mangle the outer's weights.
|
|
1588
|
+
if (event.target !== splitter)
|
|
1589
|
+
return;
|
|
1590
|
+
const detail = event.detail;
|
|
1591
|
+
if (!Array.isArray(detail?.sizes) || detail.sizes.length === 0)
|
|
1592
|
+
return;
|
|
1593
|
+
const splitNode = this.resolveSplitNode(splitPath);
|
|
1594
|
+
if (!splitNode)
|
|
1595
|
+
return;
|
|
1596
|
+
const previousTotal = (splitNode.sizes ?? []).reduce((s, w) => s + Math.max(w, 0), 0);
|
|
1597
|
+
const total = detail.sizes.reduce((s, v) => s + Math.max(v, 0), 0);
|
|
1598
|
+
const targetTotal = previousTotal > 0 ? previousTotal : detail.sizes.length;
|
|
1599
|
+
if (total > 0) {
|
|
1600
|
+
splitNode.sizes = detail.sizes.map((px) => (Math.max(px, 0) / total) * targetTotal);
|
|
1601
|
+
this.dispatchLayoutChanged();
|
|
1708
1602
|
}
|
|
1709
1603
|
});
|
|
1710
|
-
return
|
|
1604
|
+
return splitter;
|
|
1711
1605
|
}
|
|
1712
1606
|
renderStack(node, path, floatingIndex) {
|
|
1713
|
-
|
|
1607
|
+
// Dock stacks are rendered as <mp-tab-control>. The dock keeps `.dock-stack`
|
|
1608
|
+
// as a class on the host so existing `closest('.dock-stack')` queries
|
|
1609
|
+
// continue to resolve. The tab strip + body slot projection are owned by
|
|
1610
|
+
// mp-tab-control; the dock just provides the slotted header/content
|
|
1611
|
+
// elements and listens for tab-activate to drive layout-tree updates.
|
|
1612
|
+
const stack = this.documentRef.createElement('mp-tab-control');
|
|
1714
1613
|
stack.classList.add('dock-stack');
|
|
1614
|
+
// Dock controls activation; tell mp-tab-control not to auto-pick.
|
|
1615
|
+
stack.setAttribute('select-first-tab', 'false');
|
|
1616
|
+
// `border="top"` gives us the strip-cutout line under the tabs (so the
|
|
1617
|
+
// active tab visually punches through into the body) without adding the
|
|
1618
|
+
// full Bootstrap frame, which would double up with the dock's own outer
|
|
1619
|
+
// chrome border on `.dock-stack` (and on `.dock-floating` for floating
|
|
1620
|
+
// panels).
|
|
1621
|
+
stack.setAttribute('border', 'top');
|
|
1715
1622
|
const location = typeof floatingIndex === 'number'
|
|
1716
1623
|
? { type: 'floating', index: floatingIndex, segments: [...path] }
|
|
1717
1624
|
: { type: 'docked', segments: [...path] };
|
|
1718
1625
|
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
1626
|
const panes = Array.from(new Set(node.panes));
|
|
1725
1627
|
if (panes.length === 0) {
|
|
1628
|
+
const emptyHeader = this.documentRef.createElement('span');
|
|
1629
|
+
emptyHeader.setAttribute('slot', '__empty__-header');
|
|
1630
|
+
emptyHeader.textContent = '(empty)';
|
|
1726
1631
|
const empty = this.documentRef.createElement('div');
|
|
1632
|
+
empty.setAttribute('slot', '__empty__-content');
|
|
1727
1633
|
empty.classList.add('dock-stack__pane');
|
|
1728
1634
|
empty.textContent = 'No panes configured';
|
|
1729
|
-
|
|
1730
|
-
stack.
|
|
1635
|
+
stack.append(emptyHeader, empty);
|
|
1636
|
+
stack.setAttribute('active-tab', '__empty__');
|
|
1731
1637
|
return stack;
|
|
1732
1638
|
}
|
|
1733
1639
|
const activePane = panes.includes(node.activePane ?? '')
|
|
@@ -1740,228 +1646,114 @@ class MintDockManagerElement extends LitElement {
|
|
|
1740
1646
|
const paneSlug = paneSlugRaw.length > 0 ? paneSlugRaw : 'pane';
|
|
1741
1647
|
const tabId = `${this.instanceId}-tab-${pathSlug}-${paneSlug}`;
|
|
1742
1648
|
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,
|
|
1649
|
+
// Header span — projected via mp-tab-control's `${tabId}-header` slot
|
|
1650
|
+
// into the strip's button content. Carries the dock's drag handlers.
|
|
1651
|
+
const headerSpan = this.documentRef.createElement('span');
|
|
1652
|
+
headerSpan.setAttribute('slot', `${tabId}-header`);
|
|
1653
|
+
headerSpan.classList.add('dock-tab');
|
|
1654
|
+
headerSpan.dataset['pane'] = paneName;
|
|
1655
|
+
headerSpan.dataset['tabId'] = tabId;
|
|
1656
|
+
headerSpan.textContent = this.titles[paneName] ?? paneName;
|
|
1657
|
+
// Pointer-only drag (no HTML5 dnd). pointerdown captures metrics + arms
|
|
1658
|
+
// a threshold gesture; once the pointer moves >threshold pixels we
|
|
1659
|
+
// promote it to a real pane drag via beginPaneDrag. Using pointer
|
|
1660
|
+
// events sidesteps the entire class of HTML5 dnd quirks (cancellation
|
|
1661
|
+
// when source DOM is removed mid-drag, suppressed mousemove, bogus 0/0
|
|
1662
|
+
// coordinates in Firefox, browser-specific drag-image behavior).
|
|
1663
|
+
headerSpan.addEventListener('pointerdown', (event) => {
|
|
1664
|
+
this.captureTabDragMetrics(event, stack);
|
|
1665
|
+
this.armPaneDragGesture(event, this.clonePath(location), paneName, stack);
|
|
1759
1666
|
event.stopPropagation();
|
|
1760
1667
|
});
|
|
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);
|
|
1668
|
+
// Content wrapper — projected via mp-tab-control's `${tabId}-content`
|
|
1669
|
+
// slot only when this tab is active. Holds the dock manager's per-pane
|
|
1670
|
+
// <slot> for the consumer's content.
|
|
1780
1671
|
const paneHost = this.documentRef.createElement('div');
|
|
1672
|
+
paneHost.setAttribute('slot', `${tabId}-content`);
|
|
1781
1673
|
paneHost.classList.add('dock-stack__pane');
|
|
1782
1674
|
paneHost.dataset['pane'] = paneName;
|
|
1675
|
+
paneHost.dataset['tabId'] = tabId;
|
|
1783
1676
|
paneHost.id = panelId;
|
|
1784
|
-
paneHost.setAttribute('role', 'tabpanel');
|
|
1785
|
-
paneHost.setAttribute('aria-labelledby', tabId);
|
|
1786
|
-
if (paneName !== activePane) {
|
|
1787
|
-
paneHost.setAttribute('hidden', '');
|
|
1788
|
-
}
|
|
1789
1677
|
const slotEl = this.documentRef.createElement('slot');
|
|
1790
1678
|
slotEl.name = paneName;
|
|
1791
1679
|
paneHost.appendChild(slotEl);
|
|
1792
|
-
|
|
1680
|
+
stack.append(headerSpan, paneHost);
|
|
1681
|
+
if (paneName === activePane) {
|
|
1682
|
+
stack.setAttribute('active-tab', tabId);
|
|
1683
|
+
}
|
|
1793
1684
|
});
|
|
1794
1685
|
stack.dataset['activePane'] = activePane;
|
|
1795
|
-
|
|
1686
|
+
// Drive activatePane from mp-tab-control's tab-activate event. We map the
|
|
1687
|
+
// tabId back to the original paneName via the header span's data-pane.
|
|
1688
|
+
stack.addEventListener('tab-activate', (event) => {
|
|
1689
|
+
const detail = event.detail;
|
|
1690
|
+
const headerSpan = stack.querySelector(`:scope > [data-tab-id="${detail.tabId}"]`);
|
|
1691
|
+
const paneName = headerSpan?.dataset['pane'];
|
|
1692
|
+
if (paneName) {
|
|
1693
|
+
this.activatePane(stack, paneName, this.clonePath(location));
|
|
1694
|
+
this.dispatchEvent(new CustomEvent('dock-pane-activated', {
|
|
1695
|
+
detail: { pane: paneName },
|
|
1696
|
+
bubbles: true,
|
|
1697
|
+
composed: true,
|
|
1698
|
+
}));
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1796
1701
|
return stack;
|
|
1797
1702
|
}
|
|
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
|
-
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Returns the strip (`.tsc`) element inside an `<mp-tab-control>`'s shadow
|
|
1705
|
+
* DOM. Used by drag/drop logic that needs the strip's geometry instead of
|
|
1706
|
+
* the host element's bounds.
|
|
1707
|
+
*/
|
|
1708
|
+
getStackStripEl(stack) {
|
|
1709
|
+
if (stack.tagName !== 'MP-TAB-CONTROL')
|
|
1710
|
+
return null;
|
|
1711
|
+
return stack.shadowRoot?.querySelector('.tsc') ?? null;
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Returns the rendered tab buttons inside an `<mp-tab-control>`'s shadow
|
|
1715
|
+
* strip — the light-DOM `.dock-tab` spans the dock owns are projected into
|
|
1716
|
+
* these buttons via `<slot>`. Use these for geometry / position queries
|
|
1717
|
+
* (insert-index computation, drop-indicator placement). Use the light-DOM
|
|
1718
|
+
* `.dock-tab` spans for data queries (paneName, drag listeners).
|
|
1719
|
+
*/
|
|
1720
|
+
getStackTabButtons(stack) {
|
|
1721
|
+
if (stack.tagName !== 'MP-TAB-CONTROL')
|
|
1722
|
+
return [];
|
|
1723
|
+
return Array.from(stack.shadowRoot?.querySelectorAll('button.nav-link') ?? []);
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Returns the dividers inside an `<mp-splitter>`'s shadow DOM, in DOM order.
|
|
1727
|
+
* mp-splitter renders one `.divider` between each pair of adjacent panels,
|
|
1728
|
+
* so for an N-child split, length N-1.
|
|
1729
|
+
*/
|
|
1730
|
+
getSplitterDividers(splitter) {
|
|
1731
|
+
if (splitter.tagName !== 'MP-SPLITTER')
|
|
1732
|
+
return [];
|
|
1733
|
+
return Array.from(splitter.shadowRoot?.querySelectorAll('.divider') ?? []);
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Returns the panel wrappers inside an `<mp-splitter>`'s shadow DOM, in
|
|
1737
|
+
* DOM order. These are the elements mp-splitter sizes (via setPanelSizes)
|
|
1738
|
+
* during a divider drag — the dock reads their geometry for intersection
|
|
1739
|
+
* handle math and snap markers.
|
|
1740
|
+
*/
|
|
1741
|
+
getSplitterPanels(splitter) {
|
|
1742
|
+
if (splitter.tagName !== 'MP-SPLITTER')
|
|
1743
|
+
return [];
|
|
1744
|
+
return Array.from(splitter.shadowRoot?.querySelectorAll('.panel-wrapper') ?? []);
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Locate the rendered `<mp-splitter>` element for a given DockPath
|
|
1748
|
+
* `segments` value (the split-tree path). Searches the dock's shadow.
|
|
1749
|
+
*/
|
|
1750
|
+
findSplitterByPath(segments) {
|
|
1751
|
+
return (this.shadowRoot?.querySelector(`.dock-split[data-path="${segments.join('/')}"]`) ?? null);
|
|
1872
1752
|
}
|
|
1873
1753
|
onPointerMove(event) {
|
|
1874
1754
|
if (this.cornerResizeState && event.pointerId === this.cornerResizeState.pointerId) {
|
|
1875
1755
|
this.handleCornerResizeMove(event);
|
|
1876
1756
|
}
|
|
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
1757
|
if (this.floatingResizeState && event.pointerId === this.floatingResizeState.pointerId) {
|
|
1966
1758
|
this.handleFloatingResizeMove(event);
|
|
1967
1759
|
}
|
|
@@ -1973,15 +1765,6 @@ class MintDockManagerElement extends LitElement {
|
|
|
1973
1765
|
if (this.cornerResizeState && event.pointerId === this.cornerResizeState.pointerId) {
|
|
1974
1766
|
this.endCornerResize(event.pointerId);
|
|
1975
1767
|
}
|
|
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
1768
|
if (this.floatingDragState && event.pointerId === this.floatingDragState.pointerId) {
|
|
1986
1769
|
this.endFloatingDrag(event.pointerId);
|
|
1987
1770
|
}
|
|
@@ -2021,30 +1804,55 @@ class MintDockManagerElement extends LitElement {
|
|
|
2021
1804
|
clearPendingTabDragMetrics() {
|
|
2022
1805
|
this.pendingTabDragMetrics = null;
|
|
2023
1806
|
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Pointerdown handler arms a "may become a drag" gesture. Once the pointer
|
|
1809
|
+
* moves past `threshold` pixels we promote it to an actual pane drag via
|
|
1810
|
+
* {@link beginPaneDrag}; if the user releases first we just clear the
|
|
1811
|
+
* pending tab metrics. All listeners self-clean on resolve so the gesture
|
|
1812
|
+
* stays scoped to a single pointerdown.
|
|
1813
|
+
*/
|
|
1814
|
+
armPaneDragGesture(startEvent, path, pane, stackEl) {
|
|
1815
|
+
if (startEvent.pointerType === 'mouse' && startEvent.button !== 0)
|
|
1816
|
+
return;
|
|
1817
|
+
const win = this.windowRef;
|
|
1818
|
+
if (!win)
|
|
1819
|
+
return;
|
|
1820
|
+
const startX = startEvent.clientX;
|
|
1821
|
+
const startY = startEvent.clientY;
|
|
1822
|
+
const pointerId = startEvent.pointerId;
|
|
1823
|
+
const threshold = 5;
|
|
1824
|
+
let resolved = false;
|
|
1825
|
+
const cleanup = () => {
|
|
1826
|
+
resolved = true;
|
|
1827
|
+
win.removeEventListener('pointermove', onMove, true);
|
|
1828
|
+
win.removeEventListener('pointerup', onRelease, true);
|
|
1829
|
+
win.removeEventListener('pointercancel', onRelease, true);
|
|
1830
|
+
};
|
|
1831
|
+
const onMove = (event) => {
|
|
1832
|
+
if (resolved || event.pointerId !== pointerId)
|
|
1833
|
+
return;
|
|
1834
|
+
const dx = event.clientX - startX;
|
|
1835
|
+
const dy = event.clientY - startY;
|
|
1836
|
+
if (Math.hypot(dx, dy) < threshold)
|
|
1837
|
+
return;
|
|
1838
|
+
cleanup();
|
|
1839
|
+
this.beginPaneDrag(event, path, pane, stackEl);
|
|
1840
|
+
};
|
|
1841
|
+
const onRelease = (event) => {
|
|
1842
|
+
if (resolved || event.pointerId !== pointerId)
|
|
1843
|
+
return;
|
|
1844
|
+
cleanup();
|
|
1845
|
+
this.clearPendingTabDragMetrics();
|
|
1846
|
+
};
|
|
1847
|
+
win.addEventListener('pointermove', onMove, true);
|
|
1848
|
+
win.addEventListener('pointerup', onRelease, true);
|
|
1849
|
+
win.addEventListener('pointercancel', onRelease, true);
|
|
1850
|
+
}
|
|
2024
1851
|
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
1852
|
const { path: sourcePath, floatingIndex, pointerOffsetX, pointerOffsetY, } = this.preparePaneDragSource(path, pane, stackEl, event);
|
|
2046
|
-
// Capture header bounds for detecting when to convert to floating
|
|
2047
|
-
|
|
1853
|
+
// Capture header bounds for detecting when to convert to floating.
|
|
1854
|
+
// The strip lives inside the mp-tab-control's shadow as `.tsc`.
|
|
1855
|
+
const headerEl = stackEl ? this.getStackStripEl(stackEl) : null;
|
|
2048
1856
|
const headerRect = headerEl ? headerEl.getBoundingClientRect() : null;
|
|
2049
1857
|
const headerBounds = headerRect
|
|
2050
1858
|
? { left: headerRect.left, top: headerRect.top, right: headerRect.right, bottom: headerRect.bottom }
|
|
@@ -2061,36 +1869,26 @@ class MintDockManagerElement extends LitElement {
|
|
|
2061
1869
|
sourceHeaderBounds: headerBounds,
|
|
2062
1870
|
startClientX: metrics && Number.isFinite(metrics.startClientX)
|
|
2063
1871
|
? metrics.startClientX
|
|
2064
|
-
:
|
|
2065
|
-
? event.clientX
|
|
2066
|
-
: undefined,
|
|
1872
|
+
: event.clientX,
|
|
2067
1873
|
startClientY: metrics && Number.isFinite(metrics.startClientY)
|
|
2068
1874
|
? metrics.startClientY
|
|
2069
|
-
:
|
|
2070
|
-
? event.clientY
|
|
2071
|
-
: undefined,
|
|
1875
|
+
: event.clientY,
|
|
2072
1876
|
};
|
|
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);
|
|
1877
|
+
this.lastDragPointerPosition = {
|
|
1878
|
+
x: this.dragState.startClientX,
|
|
1879
|
+
y: this.dragState.startClientY,
|
|
1880
|
+
};
|
|
1881
|
+
// pointerOffsetX/Y from preparePaneDragSource is the offset within the
|
|
1882
|
+
// source stack rect captured at pointerdown by captureTabDragMetrics.
|
|
1883
|
+
// Don't overwrite with event.offsetX/Y here — the threshold-trigger
|
|
1884
|
+
// pointermove fired on window, so its offset is in window-local coords
|
|
1885
|
+
// (≈ clientX/Y) which would crash the conversion math to ~(0,0).
|
|
1886
|
+
this.updateDraggedFloatingPositionFromPoint(event.clientX, event.clientY);
|
|
2091
1887
|
this.startDragPointerTracking();
|
|
2092
|
-
|
|
2093
|
-
|
|
1888
|
+
// Mark the source floating wrapper (if any) so its CSS rule kicks in and
|
|
1889
|
+
// pointer-events:none lets findStackAtPoint see through to the docked
|
|
1890
|
+
// stack underneath, enabling drop zones over the dock during the drag.
|
|
1891
|
+
this.markDraggedFloatingWrapper();
|
|
2094
1892
|
// Preferred UX: if the dragged tab is the only one in its stack,
|
|
2095
1893
|
// immediately convert to a floating window unless it is already the
|
|
2096
1894
|
// only pane in a floating window (this case is handled by reuse logic).
|
|
@@ -2098,18 +1896,16 @@ class MintDockManagerElement extends LitElement {
|
|
|
2098
1896
|
const loc = this.resolveStackLocation(this.dragState.sourcePath);
|
|
2099
1897
|
if (loc && Array.isArray(loc.node.panes) && loc.node.panes.length === 1) {
|
|
2100
1898
|
let shouldConvert = false;
|
|
2101
|
-
if (loc.context ===
|
|
1899
|
+
if (loc.context === 'docked') {
|
|
2102
1900
|
shouldConvert = true;
|
|
2103
1901
|
}
|
|
2104
|
-
else if (loc.context ===
|
|
1902
|
+
else if (loc.context === 'floating') {
|
|
2105
1903
|
const floating = this.floatingLayouts[loc.index];
|
|
2106
1904
|
const totalPanes = floating && floating.root ? this.countPanesInTree(floating.root) : 0;
|
|
2107
|
-
shouldConvert = totalPanes > 1;
|
|
1905
|
+
shouldConvert = totalPanes > 1;
|
|
2108
1906
|
}
|
|
2109
1907
|
if (shouldConvert) {
|
|
2110
|
-
|
|
2111
|
-
const startY = Number.isFinite(event.clientY) ? event.clientY : (this.dragState.startClientY ?? 0);
|
|
2112
|
-
this.convertPendingTabDragToFloating(startX, startY);
|
|
1908
|
+
this.convertPendingTabDragToFloating(event.clientX, event.clientY);
|
|
2113
1909
|
}
|
|
2114
1910
|
}
|
|
2115
1911
|
}
|
|
@@ -2191,134 +1987,27 @@ class MintDockManagerElement extends LitElement {
|
|
|
2191
1987
|
}
|
|
2192
1988
|
endPaneDrag() {
|
|
2193
1989
|
this.clearPendingDragEndTimeout();
|
|
1990
|
+
// Restore the dragged tab's `data-hidden` and remove the placeholder span
|
|
1991
|
+
// BEFORE we null out dragState — clearHeaderDragPlaceholder reads
|
|
1992
|
+
// `dragState.placeholderEl`, `dragState.placeholderHeader`, and
|
|
1993
|
+
// `dragState.pane` to know what to restore. If dragState is nulled first,
|
|
1994
|
+
// this becomes a silent no-op and the dragged pane stays hidden in its
|
|
1995
|
+
// source stack while the placeholder span lingers in the strip — which
|
|
1996
|
+
// is exactly the "Panel disappears, only a small tab-thumb remains"
|
|
1997
|
+
// regression the multi-pane drag-out path can otherwise trigger when
|
|
1998
|
+
// no renderLayout() runs between conversion and end (e.g. user releases
|
|
1999
|
+
// outside any drop zone, or HTML5 dragend fires without a drop).
|
|
2000
|
+
this.clearHeaderDragPlaceholder();
|
|
2001
|
+
this.clearDraggedFloatingWrapperMarkers();
|
|
2194
2002
|
const state = this.dragState;
|
|
2195
2003
|
this.dragState = null;
|
|
2196
2004
|
this.hideDropIndicator();
|
|
2197
|
-
this.clearHeaderDragPlaceholder();
|
|
2198
2005
|
this.stopDragPointerTracking();
|
|
2199
2006
|
this.lastDragPointerPosition = null;
|
|
2200
2007
|
if (state && state.floatingIndex !== null && !state.dropHandled) {
|
|
2201
2008
|
this.dispatchLayoutChanged();
|
|
2202
2009
|
}
|
|
2203
2010
|
}
|
|
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
2011
|
updateDraggedFloatingPositionFromPoint(clientX, clientY) {
|
|
2323
2012
|
if (!this.dragState) {
|
|
2324
2013
|
return;
|
|
@@ -2326,10 +2015,6 @@ class MintDockManagerElement extends LitElement {
|
|
|
2326
2015
|
if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
|
|
2327
2016
|
return;
|
|
2328
2017
|
}
|
|
2329
|
-
// Ignore obviously bogus coordinates sometimes seen during HTML5 drag
|
|
2330
|
-
if (clientX === 0 && clientY === 0) {
|
|
2331
|
-
return;
|
|
2332
|
-
}
|
|
2333
2018
|
// If still dragging a tab inside its header, only convert to floating once we leave the header.
|
|
2334
2019
|
if (this.dragState.floatingIndex !== null && this.dragState.floatingIndex < 0) {
|
|
2335
2020
|
const b = this.dragState.sourceHeaderBounds;
|
|
@@ -2400,16 +2085,15 @@ class MintDockManagerElement extends LitElement {
|
|
|
2400
2085
|
const inHeaderByBounds = !!this.dragState.sourceHeaderBounds && this.isPointWithinBounds(this.dragState.sourceHeaderBounds, clientX, clientY);
|
|
2401
2086
|
const inHeaderByHitTest = this.isPointerOverSourceHeader(clientX, clientY);
|
|
2402
2087
|
if (inHeaderByBounds || inHeaderByHitTest) {
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
}
|
|
2088
|
+
// Ensure placeholder exists and move it as the pointer moves.
|
|
2089
|
+
// Placeholder management mutates the slotted children of the
|
|
2090
|
+
// mp-tab-control stack; the WC re-renders the strip on slotchange.
|
|
2091
|
+
this.ensureHeaderDragPlaceholder(stack, this.dragState.pane);
|
|
2092
|
+
const idx = this.computeHeaderInsertIndex(stack, clientX);
|
|
2093
|
+
if (this.dragState.liveReorderIndex !== idx) {
|
|
2094
|
+
this.updateHeaderDragPlaceholderPosition(stack, idx);
|
|
2095
|
+
// Keep model reordering until drop; only move the placeholder now
|
|
2096
|
+
this.dragState.liveReorderIndex = idx;
|
|
2413
2097
|
}
|
|
2414
2098
|
this.hideDropIndicator();
|
|
2415
2099
|
return;
|
|
@@ -2421,81 +2105,125 @@ class MintDockManagerElement extends LitElement {
|
|
|
2421
2105
|
const zone = this.computeDropZone(stack, { clientX, clientY }, zoneHint);
|
|
2422
2106
|
this.showDropIndicator(stack, zone);
|
|
2423
2107
|
}
|
|
2424
|
-
// Returns true when the pointer is currently over the source stack's header (tab strip)
|
|
2108
|
+
// Returns true when the pointer is currently over the source stack's header (tab strip).
|
|
2109
|
+
// The strip lives inside the mp-tab-control's shadow as `.tsc`, so we test
|
|
2110
|
+
// bounds directly rather than using elementsFromPoint(/contains) which won't
|
|
2111
|
+
// pierce the shadow boundary cleanly.
|
|
2425
2112
|
isPointerOverSourceHeader(clientX, clientY) {
|
|
2426
2113
|
const state = this.dragState;
|
|
2427
2114
|
if (!state) {
|
|
2428
2115
|
return false;
|
|
2429
2116
|
}
|
|
2430
2117
|
const stackEl = state.sourceStackEl ?? null;
|
|
2431
|
-
const
|
|
2432
|
-
if (!
|
|
2433
|
-
// Be conservative: if we cannot resolve the
|
|
2118
|
+
const strip = stackEl ? this.getStackStripEl(stackEl) : null;
|
|
2119
|
+
if (!strip) {
|
|
2120
|
+
// Be conservative: if we cannot resolve the strip, treat as inside
|
|
2434
2121
|
return true;
|
|
2435
2122
|
}
|
|
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;
|
|
2123
|
+
const r = strip.getBoundingClientRect();
|
|
2124
|
+
return clientX >= r.left && clientX <= r.right && clientY >= r.top && clientY <= r.bottom;
|
|
2444
2125
|
}
|
|
2445
2126
|
isPointWithinBounds(bounds, x, y) {
|
|
2446
2127
|
return x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom;
|
|
2447
2128
|
}
|
|
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
|
-
|
|
2129
|
+
// Ensure a placeholder tab exists during in-header drag and hide the real dragged tab visually.
|
|
2130
|
+
// Operates on the mp-tab-control stack: the dragged content div gets `data-hidden`
|
|
2131
|
+
// (mp-tab-control then skips its tab in the strip), and a placeholder header+content
|
|
2132
|
+
// pair is appended as light-DOM children of the stack. mp-tab-control's mutation
|
|
2133
|
+
// observer picks up the change and renders the placeholder as a tab.
|
|
2134
|
+
ensureHeaderDragPlaceholder(stack, pane) {
|
|
2135
|
+
if (stack.tagName !== 'MP-TAB-CONTROL')
|
|
2136
|
+
return;
|
|
2137
|
+
if (this.dragState?.placeholderHeader === stack && this.dragState.placeholderEl) {
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
const draggedHeader = stack.querySelector(`:scope > .dock-tab[data-pane="${CSS.escape(pane)}"]`);
|
|
2141
|
+
const draggedContent = stack.querySelector(`:scope > .dock-stack__pane[data-pane="${CSS.escape(pane)}"]`);
|
|
2142
|
+
if (!draggedHeader || !draggedContent)
|
|
2143
|
+
return;
|
|
2144
|
+
// Measure the dragged tab's text-only width BEFORE hiding it. The
|
|
2145
|
+
// `.dock-tab` rule applies padding (matching the strip button's padding so
|
|
2146
|
+
// the span fills the button as a drag handle), so `offsetWidth` is
|
|
2147
|
+
// text + padding — we subtract the span's own padding to get just the
|
|
2148
|
+
// text width. That's the natural slot content width we want the
|
|
2149
|
+
// placeholder to reserve; the placeholder span will re-apply the same
|
|
2150
|
+
// padding on top, mirroring the original tab's geometry exactly.
|
|
2151
|
+
const draggedCS = this.windowRef
|
|
2152
|
+
? this.windowRef.getComputedStyle(draggedHeader)
|
|
2153
|
+
: globalThis.getComputedStyle(draggedHeader);
|
|
2154
|
+
const draggedHorizontalPadding = parseFloat(draggedCS.paddingLeft) + parseFloat(draggedCS.paddingRight);
|
|
2155
|
+
const slotContentWidth = Math.max(0, draggedHeader.offsetWidth - draggedHorizontalPadding);
|
|
2156
|
+
// Hide the dragged tab from mp-tab-control's strip (frees up the slot).
|
|
2157
|
+
draggedContent.setAttribute('data-hidden', '');
|
|
2158
|
+
// Build placeholder header + content. The placeholder uses a unique tabId
|
|
2159
|
+
// (`__dock-placeholder__`) so its slot names don't collide with real panes.
|
|
2160
|
+
// We mirror the dragged tab's text into the placeholder (dimmed via opacity)
|
|
2161
|
+
// so the strip reads as "this tab is being dragged" rather than "empty slot".
|
|
2162
|
+
const placeholderTabId = '__dock-placeholder__';
|
|
2163
|
+
const phHeader = this.documentRef.createElement('span');
|
|
2164
|
+
phHeader.setAttribute('slot', `${placeholderTabId}-header`);
|
|
2165
|
+
phHeader.classList.add('dock-tab');
|
|
2166
|
+
phHeader.dataset['placeholder'] = 'true';
|
|
2167
|
+
phHeader.dataset['tabId'] = placeholderTabId;
|
|
2168
|
+
phHeader.setAttribute('aria-hidden', 'true');
|
|
2169
|
+
phHeader.textContent = draggedHeader.textContent;
|
|
2170
|
+
// `display: inline-block` is required for `min-width` to take effect on the
|
|
2171
|
+
// span. Without it, an inline element ignores min-width and the placeholder
|
|
2172
|
+
// collapses to its content width (or 0 if textContent is also empty),
|
|
2173
|
+
// leaving a "mini-thumb" in the strip.
|
|
2174
|
+
phHeader.style.display = 'inline-block';
|
|
2175
|
+
phHeader.style.minWidth = `${slotContentWidth}px`;
|
|
2176
|
+
phHeader.style.opacity = '0.5';
|
|
2177
|
+
const phContent = this.documentRef.createElement('div');
|
|
2178
|
+
phContent.setAttribute('slot', `${placeholderTabId}-content`);
|
|
2179
|
+
phContent.classList.add('dock-stack__pane');
|
|
2180
|
+
phContent.dataset['placeholder'] = 'true';
|
|
2181
|
+
// Insert before the dragged header span so the placeholder appears in
|
|
2182
|
+
// the dragged tab's original strip position. The mutation observer in
|
|
2183
|
+
// mp-tab-control will refresh the tab list automatically.
|
|
2184
|
+
stack.insertBefore(phHeader, draggedHeader);
|
|
2185
|
+
stack.insertBefore(phContent, draggedContent);
|
|
2470
2186
|
if (this.dragState) {
|
|
2471
|
-
this.dragState.placeholderHeader =
|
|
2472
|
-
this.dragState.placeholderEl =
|
|
2187
|
+
this.dragState.placeholderHeader = stack;
|
|
2188
|
+
this.dragState.placeholderEl = phHeader;
|
|
2473
2189
|
}
|
|
2474
2190
|
}
|
|
2475
|
-
// Move the placeholder to the computed target index within the
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2191
|
+
// Move the placeholder to the computed target index within the strip.
|
|
2192
|
+
// We reorder light-DOM children (header span + matching content div); the
|
|
2193
|
+
// mp-tab-control then re-renders the strip in the new order on slotchange.
|
|
2194
|
+
updateHeaderDragPlaceholderPosition(stack, targetIndex) {
|
|
2195
|
+
if (stack.tagName !== 'MP-TAB-CONTROL')
|
|
2196
|
+
return;
|
|
2197
|
+
const phHeader = this.dragState?.placeholderEl ?? null;
|
|
2198
|
+
if (!phHeader)
|
|
2479
2199
|
return;
|
|
2480
|
-
}
|
|
2481
2200
|
const draggedPane = this.dragState?.pane ?? null;
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
const
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2201
|
+
// Find all real header spans (excluding the placeholder + the hidden dragged one).
|
|
2202
|
+
const realHeaders = Array.from(stack.querySelectorAll(':scope > .dock-tab')).filter((h) => h !== phHeader &&
|
|
2203
|
+
(!draggedPane || h.dataset['pane'] !== draggedPane));
|
|
2204
|
+
const clampedTarget = Math.max(0, Math.min(targetIndex, realHeaders.length));
|
|
2205
|
+
const ref = realHeaders[clampedTarget] ?? null;
|
|
2206
|
+
stack.insertBefore(phHeader, ref);
|
|
2207
|
+
// Keep the placeholder content adjacent to its header so child-order
|
|
2208
|
+
// remains predictable for slotchange-driven re-renders.
|
|
2209
|
+
const phContent = stack.querySelector(`:scope > .dock-stack__pane[data-placeholder="true"]`);
|
|
2210
|
+
if (phContent && phHeader.nextElementSibling !== phContent) {
|
|
2211
|
+
stack.insertBefore(phContent, phHeader.nextElementSibling);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
// Remove placeholder and restore the dragged tab's visibility.
|
|
2489
2215
|
clearHeaderDragPlaceholder() {
|
|
2490
2216
|
const ph = this.dragState?.placeholderEl ?? null;
|
|
2491
|
-
const
|
|
2492
|
-
if (
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
dragged.style.display = '';
|
|
2217
|
+
const stack = this.dragState?.placeholderHeader ?? null;
|
|
2218
|
+
if (stack) {
|
|
2219
|
+
// Restore the dragged content div's visibility so its strip tab returns.
|
|
2220
|
+
if (this.dragState?.pane) {
|
|
2221
|
+
const draggedContent = stack.querySelector(`:scope > .dock-stack__pane[data-pane="${CSS.escape(this.dragState.pane)}"]`);
|
|
2222
|
+
draggedContent?.removeAttribute('data-hidden');
|
|
2498
2223
|
}
|
|
2224
|
+
// Remove the placeholder content div sibling.
|
|
2225
|
+
const phContent = stack.querySelector(`:scope > .dock-stack__pane[data-placeholder="true"]`);
|
|
2226
|
+
phContent?.remove();
|
|
2499
2227
|
}
|
|
2500
2228
|
if (ph && ph.parentElement) {
|
|
2501
2229
|
ph.parentElement.removeChild(ph);
|
|
@@ -2511,11 +2239,9 @@ class MintDockManagerElement extends LitElement {
|
|
|
2511
2239
|
}
|
|
2512
2240
|
this.lastDragPointerPosition = null;
|
|
2513
2241
|
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);
|
|
2242
|
+
win?.addEventListener('pointermove', this.onDragPointerMove, true);
|
|
2243
|
+
win?.addEventListener('pointerup', this.onDragPointerUp, true);
|
|
2244
|
+
win?.addEventListener('pointercancel', this.onDragPointerCancel, true);
|
|
2519
2245
|
this.dragPointerTrackingActive = true;
|
|
2520
2246
|
}
|
|
2521
2247
|
stopDragPointerTracking() {
|
|
@@ -2523,52 +2249,38 @@ class MintDockManagerElement extends LitElement {
|
|
|
2523
2249
|
return;
|
|
2524
2250
|
}
|
|
2525
2251
|
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);
|
|
2252
|
+
win?.removeEventListener('pointermove', this.onDragPointerMove, true);
|
|
2253
|
+
win?.removeEventListener('pointerup', this.onDragPointerUp, true);
|
|
2254
|
+
win?.removeEventListener('pointercancel', this.onDragPointerCancel, true);
|
|
2531
2255
|
this.dragPointerTrackingActive = false;
|
|
2532
2256
|
this.lastDragPointerPosition = null;
|
|
2533
2257
|
this.clearPendingDragEndTimeout();
|
|
2534
2258
|
}
|
|
2535
|
-
|
|
2259
|
+
onDragPointerMove(event) {
|
|
2536
2260
|
if (!this.dragState) {
|
|
2537
2261
|
this.stopDragPointerTracking();
|
|
2538
2262
|
return;
|
|
2539
2263
|
}
|
|
2540
|
-
if (event.buttons === 0) {
|
|
2541
|
-
this.scheduleDeferredDragEnd();
|
|
2542
|
-
return;
|
|
2543
|
-
}
|
|
2544
2264
|
this.lastDragPointerPosition = { x: event.clientX, y: event.clientY };
|
|
2545
2265
|
this.updateDraggedFloatingPositionFromPoint(event.clientX, event.clientY);
|
|
2546
2266
|
}
|
|
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
|
|
2267
|
+
onDragPointerUp(event) {
|
|
2268
|
+
// Commit the drop from pointer release; the pointer-up coordinates are
|
|
2269
|
+
// authoritative for which stack/zone the user dropped into.
|
|
2564
2270
|
if (this.dragState) {
|
|
2565
|
-
const
|
|
2566
|
-
|
|
2567
|
-
|
|
2271
|
+
const x = Number.isFinite(event.clientX) ? event.clientX : this.lastDragPointerPosition?.x;
|
|
2272
|
+
const y = Number.isFinite(event.clientY) ? event.clientY : this.lastDragPointerPosition?.y;
|
|
2273
|
+
if (x !== undefined && y !== undefined) {
|
|
2274
|
+
this.finalizeDropFromPoint(x, y);
|
|
2568
2275
|
}
|
|
2569
2276
|
}
|
|
2570
2277
|
this.handleDragPointerUpCommon();
|
|
2571
2278
|
}
|
|
2279
|
+
onDragPointerCancel() {
|
|
2280
|
+
// OS-level cancel (e.g. pointer capture lost): end the drag without
|
|
2281
|
+
// committing a drop.
|
|
2282
|
+
this.handleDragPointerUpCommon();
|
|
2283
|
+
}
|
|
2572
2284
|
// Convert a currently in-header tab drag into a floating window
|
|
2573
2285
|
convertPendingTabDragToFloating(clientX, clientY) {
|
|
2574
2286
|
if (!this.dragState) {
|
|
@@ -2601,22 +2313,34 @@ class MintDockManagerElement extends LitElement {
|
|
|
2601
2313
|
: stackRect && Number.isFinite(stackRect.height)
|
|
2602
2314
|
? stackRect.height
|
|
2603
2315
|
: fallbackHeight;
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
const
|
|
2618
|
-
?
|
|
2619
|
-
:
|
|
2316
|
+
// Place the floating wrapper exactly where the docked stack was, so the
|
|
2317
|
+
// pane appears in-place at the moment of detach instead of jumping under
|
|
2318
|
+
// the cursor. metrics.left/top are host-relative (captured at pointerdown
|
|
2319
|
+
// in captureTabDragMetrics). Compensate for the floating wrapper's 1px
|
|
2320
|
+
// border so the visible content edge lines up with the original stack's
|
|
2321
|
+
// visible content edge (the docked .dock-stack also has a 1px border, so
|
|
2322
|
+
// the inner content rectangles match after this offset).
|
|
2323
|
+
const FLOATING_BORDER = 1;
|
|
2324
|
+
const initialLeft = metrics && Number.isFinite(metrics.left)
|
|
2325
|
+
? metrics.left - FLOATING_BORDER
|
|
2326
|
+
: Number.isFinite(clientX)
|
|
2327
|
+
? clientX - hostRect.left - width / 2
|
|
2328
|
+
: 0;
|
|
2329
|
+
const initialTop = metrics && Number.isFinite(metrics.top)
|
|
2330
|
+
? metrics.top - FLOATING_BORDER
|
|
2331
|
+
: Number.isFinite(clientY)
|
|
2332
|
+
? clientY - hostRect.top - height / 2
|
|
2333
|
+
: 0;
|
|
2334
|
+
// Derive pointerOffset from the cursor's actual position relative to the
|
|
2335
|
+
// freshly-placed wrapper (not from pointerdown metrics) so the very next
|
|
2336
|
+
// pointermove translates into a wrapper move of exactly the cursor delta
|
|
2337
|
+
// — no jump, no drift.
|
|
2338
|
+
const pointerOffsetX = Number.isFinite(clientX)
|
|
2339
|
+
? clientX - hostRect.left - initialLeft
|
|
2340
|
+
: width / 2;
|
|
2341
|
+
const pointerOffsetY = Number.isFinite(clientY)
|
|
2342
|
+
? clientY - hostRect.top - initialTop
|
|
2343
|
+
: height / 2;
|
|
2620
2344
|
// Remove pane from its current stack and create a new floating entry
|
|
2621
2345
|
this.removePaneFromLocation(location, pane);
|
|
2622
2346
|
const floatingStack = {
|
|
@@ -2626,8 +2350,8 @@ class MintDockManagerElement extends LitElement {
|
|
|
2626
2350
|
};
|
|
2627
2351
|
const floatingLayout = {
|
|
2628
2352
|
bounds: {
|
|
2629
|
-
left:
|
|
2630
|
-
top:
|
|
2353
|
+
left: initialLeft,
|
|
2354
|
+
top: initialTop,
|
|
2631
2355
|
width,
|
|
2632
2356
|
height,
|
|
2633
2357
|
},
|
|
@@ -2646,32 +2370,54 @@ class MintDockManagerElement extends LitElement {
|
|
|
2646
2370
|
state.floatingIndex = newIndex;
|
|
2647
2371
|
state.pointerOffsetX = pointerOffsetX;
|
|
2648
2372
|
state.pointerOffsetY = pointerOffsetY;
|
|
2373
|
+
// Now that the wrapper exists, mark it so pointer-events:none kicks in
|
|
2374
|
+
// and findStackAtPoint can see through to docked stacks underneath.
|
|
2375
|
+
this.markDraggedFloatingWrapper();
|
|
2649
2376
|
this.dispatchLayoutChanged();
|
|
2650
2377
|
}
|
|
2651
|
-
//
|
|
2652
|
-
//
|
|
2653
|
-
//
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2378
|
+
// Toggle data-dragging on the floating wrapper currently associated with
|
|
2379
|
+
// the active pane drag (dragState.floatingIndex), if any. Used to make the
|
|
2380
|
+
// wrapper transparent to elementsFromPoint so drop zones can be shown on
|
|
2381
|
+
// stacks underneath. clearDraggedFloatingWrapper() is the inverse.
|
|
2382
|
+
markDraggedFloatingWrapper() {
|
|
2383
|
+
const fi = this.dragState?.floatingIndex;
|
|
2384
|
+
if (fi === null || fi === undefined || fi < 0)
|
|
2385
|
+
return;
|
|
2386
|
+
const wrapper = this.getFloatingWrapper(fi);
|
|
2387
|
+
if (wrapper)
|
|
2388
|
+
wrapper.dataset['dragging'] = 'true';
|
|
2389
|
+
}
|
|
2390
|
+
clearDraggedFloatingWrapperMarkers() {
|
|
2391
|
+
const layer = this.floatingLayerEl;
|
|
2392
|
+
if (!layer)
|
|
2393
|
+
return;
|
|
2394
|
+
layer.querySelectorAll('.dock-floating[data-dragging="true"]').forEach((el) => {
|
|
2395
|
+
delete el.dataset['dragging'];
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
// Compute the intended tab insert index within a stack's strip based on pointer X.
|
|
2399
|
+
// Uses the rendered tab buttons inside mp-tab-control's shadow strip for geometry;
|
|
2400
|
+
// the dragged tab is hidden during drag (its content has data-hidden), and the
|
|
2401
|
+
// placeholder button (if present) gives us the dragged-position reference.
|
|
2402
|
+
computeHeaderInsertIndex(stack, clientX) {
|
|
2403
|
+
if (stack.tagName !== 'MP-TAB-CONTROL')
|
|
2404
|
+
return 0;
|
|
2405
|
+
const allTabButtons = this.getStackTabButtons(stack);
|
|
2406
|
+
if (allTabButtons.length === 0) {
|
|
2657
2407
|
return 0;
|
|
2658
2408
|
}
|
|
2659
|
-
const
|
|
2660
|
-
const
|
|
2661
|
-
|
|
2409
|
+
const placeholderHeader = stack.querySelector(':scope > .dock-tab[data-placeholder="true"]');
|
|
2410
|
+
const placeholderTabId = placeholderHeader?.dataset['tabId'];
|
|
2411
|
+
const placeholderButton = placeholderTabId
|
|
2412
|
+
? allTabButtons.find((b) => b.id === `${placeholderTabId}-header-button`) ?? null
|
|
2662
2413
|
: null;
|
|
2663
|
-
const
|
|
2664
|
-
const targets = allTabs.filter((t) => t !== draggedEl && t !== placeholderEl);
|
|
2414
|
+
const targets = allTabButtons.filter((b) => b !== placeholderButton);
|
|
2665
2415
|
if (targets.length === 0) {
|
|
2666
2416
|
return 0;
|
|
2667
2417
|
}
|
|
2668
2418
|
const rightBias = 12;
|
|
2669
2419
|
const leftBias = 0;
|
|
2670
|
-
const baseRect =
|
|
2671
|
-
? placeholderEl.getBoundingClientRect()
|
|
2672
|
-
: draggedEl
|
|
2673
|
-
? draggedEl.getBoundingClientRect()
|
|
2674
|
-
: null;
|
|
2420
|
+
const baseRect = placeholderButton ? placeholderButton.getBoundingClientRect() : null;
|
|
2675
2421
|
const rectValid = !!baseRect && Number.isFinite(baseRect.width) && baseRect.width > 0;
|
|
2676
2422
|
const draggedCenter = rectValid && baseRect ? baseRect.left + baseRect.width / 2 : null;
|
|
2677
2423
|
for (let i = 0; i < targets.length; i += 1) {
|
|
@@ -2705,9 +2451,6 @@ class MintDockManagerElement extends LitElement {
|
|
|
2705
2451
|
}
|
|
2706
2452
|
}
|
|
2707
2453
|
}
|
|
2708
|
-
onDragTouchEnd() {
|
|
2709
|
-
this.handleDragPointerUpCommon();
|
|
2710
|
-
}
|
|
2711
2454
|
// Commit a drop using current pointer coordinates and joystick state
|
|
2712
2455
|
finalizeDropFromPoint(clientX, clientY) {
|
|
2713
2456
|
if (!this.dragState) {
|
|
@@ -2733,17 +2476,14 @@ class MintDockManagerElement extends LitElement {
|
|
|
2733
2476
|
stackPath &&
|
|
2734
2477
|
this.pathsEqual(stackPath, this.dragState.sourcePath) &&
|
|
2735
2478
|
(!zone || zone === 'center')) {
|
|
2736
|
-
const
|
|
2737
|
-
if (
|
|
2738
|
-
const
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
this.dragState.dropHandled = true;
|
|
2745
|
-
return;
|
|
2746
|
-
}
|
|
2479
|
+
const location = this.resolveStackLocation(path);
|
|
2480
|
+
if (location) {
|
|
2481
|
+
const idx = this.computeHeaderInsertIndex(stack, clientX);
|
|
2482
|
+
this.reorderPaneInLocationAtIndex(location, this.dragState.pane, idx);
|
|
2483
|
+
this.renderLayout();
|
|
2484
|
+
this.dispatchLayoutChanged();
|
|
2485
|
+
this.dragState.dropHandled = true;
|
|
2486
|
+
return;
|
|
2747
2487
|
}
|
|
2748
2488
|
}
|
|
2749
2489
|
if (path && this.isDropZone(zone)) {
|
|
@@ -2782,112 +2522,6 @@ class MintDockManagerElement extends LitElement {
|
|
|
2782
2522
|
? win.setTimeout(completeDrag, 0)
|
|
2783
2523
|
: setTimeout(completeDrag, 0);
|
|
2784
2524
|
}
|
|
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
2525
|
handleDrop(targetPath, zone) {
|
|
2892
2526
|
if (!this.dragState || !targetPath) {
|
|
2893
2527
|
return;
|
|
@@ -3354,24 +2988,6 @@ class MintDockManagerElement extends LitElement {
|
|
|
3354
2988
|
}
|
|
3355
2989
|
return null;
|
|
3356
2990
|
}
|
|
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
2991
|
findStackInTargets(targets) {
|
|
3376
2992
|
for (const element of targets) {
|
|
3377
2993
|
if (!(element instanceof HTMLElement)) {
|
|
@@ -3391,21 +3007,16 @@ class MintDockManagerElement extends LitElement {
|
|
|
3391
3007
|
}
|
|
3392
3008
|
activatePane(stack, paneName, path) {
|
|
3393
3009
|
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', '');
|
|
3010
|
+
// Reflect to mp-tab-control's `active-tab` attribute. The WC handles
|
|
3011
|
+
// strip button styling (active class, aria-selected) + body-slot
|
|
3012
|
+
// projection automatically via the named-slot pattern.
|
|
3013
|
+
if (stack.tagName === 'MP-TAB-CONTROL') {
|
|
3014
|
+
const headerSpan = stack.querySelector(`:scope > .dock-tab[data-pane="${CSS.escape(paneName)}"]`);
|
|
3015
|
+
const tabId = headerSpan?.dataset['tabId'];
|
|
3016
|
+
if (tabId) {
|
|
3017
|
+
stack.setAttribute('active-tab', tabId);
|
|
3407
3018
|
}
|
|
3408
|
-
}
|
|
3019
|
+
}
|
|
3409
3020
|
const location = this.resolveStackLocation(path);
|
|
3410
3021
|
if (!location) {
|
|
3411
3022
|
return;
|
|
@@ -3611,7 +3222,10 @@ class MintDockManagerElement extends LitElement {
|
|
|
3611
3222
|
return { type: 'docked', segments: [...path.segments] };
|
|
3612
3223
|
}
|
|
3613
3224
|
parsePath(path) {
|
|
3614
|
-
|
|
3225
|
+
// The root splitter is tagged with data-path="" (raw segments-join of an
|
|
3226
|
+
// empty array) so empty string is a valid path representing root docked.
|
|
3227
|
+
// Only null/undefined is "no path".
|
|
3228
|
+
if (path == null) {
|
|
3615
3229
|
return null;
|
|
3616
3230
|
}
|
|
3617
3231
|
if (path.startsWith('f:')) {
|
|
@@ -3845,12 +3459,12 @@ class BsDockManagerComponent {
|
|
|
3845
3459
|
return this.cloneLayout(this._layout);
|
|
3846
3460
|
}
|
|
3847
3461
|
constructor() {
|
|
3848
|
-
this.layout = input(null, ...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
3462
|
+
this.layout = input(null, ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
3849
3463
|
this.layoutChange = output();
|
|
3850
3464
|
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" }] : []));
|
|
3465
|
+
this.layoutString = signal(null, ...(ngDevMode ? [{ debugName: "layoutString" }] : /* istanbul ignore next */ []));
|
|
3466
|
+
this.panes = contentChildren(BsDockPaneComponent, ...(ngDevMode ? [{ debugName: "panes" }] : /* istanbul ignore next */ []));
|
|
3467
|
+
this.managerRef = viewChild('manager', ...(ngDevMode ? [{ debugName: "managerRef" }] : /* istanbul ignore next */ []));
|
|
3854
3468
|
this.trackByPane = (_, pane) => pane.name();
|
|
3855
3469
|
this._layout = { root: null, floating: [] };
|
|
3856
3470
|
const documentRef = inject(DOCUMENT);
|
|
@@ -3913,10 +3527,10 @@ class BsDockManagerComponent {
|
|
|
3913
3527
|
cloneLayout(layout) {
|
|
3914
3528
|
return JSON.parse(JSON.stringify(layout));
|
|
3915
3529
|
}
|
|
3916
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
3917
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.
|
|
3530
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BsDockManagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3531
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: BsDockManagerComponent, isStandalone: true, selector: "bs-dock-manager", inputs: { layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { layoutChange: "layoutChange", layoutSnapshotChange: "layoutSnapshotChange" }, queries: [{ propertyName: "panes", predicate: BsDockPaneComponent, isSignal: true }], viewQueries: [{ propertyName: "managerRef", first: true, predicate: ["manager"], descendants: true, isSignal: true }], ngImport: i0, template: "<mint-dock-manager\n #manager\n class=\"bs-dock-manager\"\n [attr.layout]=\"layoutString()\"\n (dock-layout-changed)=\"onLayoutChanged($event)\"\n >\n @for (pane of panes(); track trackByPane($index, pane)) {\n <div class=\"bs-dock-pane\" [attr.slot]=\"pane.name()\">\n <ng-container *ngTemplateOutlet=\"pane.template()\"></ng-container>\n </div>\n }\n</mint-dock-manager>\n", styles: [":host{display:block;width:100%;height:100%}.bs-dock-manager{display:block;width:100%;height:100%}.bs-dock-pane{display:contents}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3918
3532
|
}
|
|
3919
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
3533
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BsDockManagerComponent, decorators: [{
|
|
3920
3534
|
type: Component,
|
|
3921
3535
|
args: [{ selector: 'bs-dock-manager', imports: [NgTemplateOutlet], schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush, template: "<mint-dock-manager\n #manager\n class=\"bs-dock-manager\"\n [attr.layout]=\"layoutString()\"\n (dock-layout-changed)=\"onLayoutChanged($event)\"\n >\n @for (pane of panes(); track trackByPane($index, pane)) {\n <div class=\"bs-dock-pane\" [attr.slot]=\"pane.name()\">\n <ng-container *ngTemplateOutlet=\"pane.template()\"></ng-container>\n </div>\n }\n</mint-dock-manager>\n", styles: [":host{display:block;width:100%;height:100%}.bs-dock-manager{display:block;width:100%;height:100%}.bs-dock-pane{display:contents}\n"] }]
|
|
3922
3536
|
}], ctorParameters: () => [], propDecorators: { layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], layoutChange: [{ type: i0.Output, args: ["layoutChange"] }], layoutSnapshotChange: [{ type: i0.Output, args: ["layoutSnapshotChange"] }], panes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => BsDockPaneComponent), { isSignal: true }] }], managerRef: [{ type: i0.ViewChild, args: ['manager', { isSignal: true }] }] } });
|