@mintplayer/ng-bootstrap 20.1.0 → 20.3.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.
@@ -1,587 +1,2633 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Input, ViewChild, Component, HostBinding, ViewChildren, ContentChildren, Injectable, HostListener, NgModule } from '@angular/core';
3
- import * as i7 from '@angular/common';
4
- import { AsyncPipe } from '@angular/common';
5
- import * as i2 from '@angular/cdk/portal';
6
- import { DomPortal, PortalModule } from '@angular/cdk/portal';
7
- import { BsCardModule } from '@mintplayer/ng-bootstrap/card';
8
- import * as i4 from '@mintplayer/ng-bootstrap/splitter';
9
- import { BsSplitterModule } from '@mintplayer/ng-bootstrap/splitter';
10
- import * as i6 from '@mintplayer/ng-bootstrap/resizable';
11
- import { BsResizableModule } from '@mintplayer/ng-bootstrap/resizable';
12
- import * as i5 from '@mintplayer/ng-bootstrap/tab-control';
13
- import { BsTabControlModule } from '@mintplayer/ng-bootstrap/tab-control';
14
- import * as i3 from '@mintplayer/ng-bootstrap/instance-of';
15
- import { BsInstanceOfModule } from '@mintplayer/ng-bootstrap/instance-of';
16
- import { BehaviorSubject, map, take } from 'rxjs';
17
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
18
- import { deepClone } from '@mintplayer/parentify';
19
- import * as i1 from '@angular/cdk/overlay';
20
-
21
- class BsDockPanelComponent {
22
- constructor() {
23
- this.panelId = '';
24
- }
25
- ngAfterViewInit() {
26
- this.headerPortal = new DomPortal(this.headerElement.nativeElement);
27
- this.contentPortal = new DomPortal(this.contentElement.nativeElement);
2
+ import { TemplateRef, ViewChild, Input, Component, EventEmitter, QueryList, ContentChildren, Output, Inject, ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { DOCUMENT, CommonModule } from '@angular/common';
5
+
6
+ class BsDockPaneComponent {
7
+ ngAfterContentInit() {
8
+ if (!this.name) {
9
+ throw new Error('bs-dock-pane requires a unique "name" input.');
10
+ }
28
11
  }
29
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
30
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: BsDockPanelComponent, isStandalone: false, selector: "bs-dock-panel", inputs: { panelId: "panelId" }, viewQueries: [{ propertyName: "headerElement", first: true, predicate: ["headerElement"], descendants: true }, { propertyName: "contentElement", first: true, predicate: ["contentElement"], descendants: true }], ngImport: i0, template: "<!-- <bs-split-panel> -->\n<div #headerElement class=\"dock-header\">\n <ng-content select=\"bs-dock-panel-header\"></ng-content>\n</div>\n<div #contentElement>\n <ng-content></ng-content>\n</div>\n<!-- </bs-split-panel> -->", styles: [".dock-header{padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);margin:calc(-1 * var(--bs-nav-link-padding-y)) calc(-1 * var(--bs-nav-link-padding-x))}\n"] }); }
12
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockPaneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
13
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: BsDockPaneComponent, isStandalone: false, selector: "bs-dock-pane", inputs: { name: "name", title: "title" }, viewQueries: [{ propertyName: "template", first: true, predicate: TemplateRef, descendants: true, static: true }], ngImport: i0, template: `<ng-template><ng-content></ng-content></ng-template>`, isInline: true }); }
31
14
  }
32
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockPanelComponent, decorators: [{
15
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockPaneComponent, decorators: [{
33
16
  type: Component,
34
- args: [{ selector: 'bs-dock-panel', standalone: false, template: "<!-- <bs-split-panel> -->\n<div #headerElement class=\"dock-header\">\n <ng-content select=\"bs-dock-panel-header\"></ng-content>\n</div>\n<div #contentElement>\n <ng-content></ng-content>\n</div>\n<!-- </bs-split-panel> -->", styles: [".dock-header{padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);margin:calc(-1 * var(--bs-nav-link-padding-y)) calc(-1 * var(--bs-nav-link-padding-x))}\n"] }]
35
- }], propDecorators: { headerElement: [{
36
- type: ViewChild,
37
- args: ['headerElement']
38
- }], contentElement: [{
39
- type: ViewChild,
40
- args: ['contentElement']
41
- }], panelId: [{
17
+ args: [{
18
+ selector: 'bs-dock-pane',
19
+ template: `<ng-template><ng-content></ng-content></ng-template>`,
20
+ standalone: false,
21
+ }]
22
+ }], propDecorators: { name: [{
23
+ type: Input
24
+ }], title: [{
42
25
  type: Input
26
+ }], template: [{
27
+ type: ViewChild,
28
+ args: [TemplateRef, { static: true }]
43
29
  }] } });
44
30
 
45
- class BsDockPane {
46
- }
31
+ const templateHtml = `
32
+ <style>
33
+ :host {
34
+ display: block;
35
+ position: relative;
36
+ width: 100%;
37
+ height: 100%;
38
+ contain: layout paint size style;
39
+ box-sizing: border-box;
40
+ font-family: inherit;
41
+ color: inherit;
42
+ }
47
43
 
48
- class BsTabGroupPane extends BsDockPane {
49
- constructor(data) {
50
- super();
51
- this.panes = [];
52
- Object.assign(this, data);
44
+ .dock-root,
45
+ .dock-docked,
46
+ .dock-split,
47
+ .dock-split__child,
48
+ .dock-stack,
49
+ .dock-stack__content,
50
+ .dock-stack__pane {
51
+ box-sizing: border-box;
52
+ min-width: 0;
53
+ min-height: 0;
53
54
  }
54
- get isEmpty() {
55
- return this.panes.length === 0;
55
+
56
+ .dock-root {
57
+ position: relative;
58
+ width: 100%;
59
+ height: 100%;
56
60
  }
57
- }
58
61
 
59
- class BsDocumentHost extends BsDockPane {
60
- constructor(data) {
61
- super();
62
- Object.assign(this, data);
62
+ .dock-docked {
63
+ position: absolute;
64
+ inset: 0;
65
+ display: flex;
66
+ z-index: 0;
63
67
  }
64
- get isEmpty() {
65
- return this.rootPane?.isEmpty ?? true;
68
+
69
+ .dock-floating-layer {
70
+ position: absolute;
71
+ inset: 0;
72
+ pointer-events: none;
73
+ z-index: 5;
66
74
  }
67
- }
68
75
 
69
- class BsContentPane extends BsDockPane {
70
- constructor(data) {
71
- super();
72
- Object.assign(this, data);
76
+ .dock-floating {
77
+ position: absolute;
78
+ display: flex;
79
+ flex-direction: column;
80
+ pointer-events: auto;
81
+ border: 1px solid rgba(0, 0, 0, 0.3);
82
+ border-radius: 0.5rem;
83
+ background: rgba(255, 255, 255, 0.92);
84
+ box-shadow: 0 16px 32px rgba(15, 23, 42, 0.25);
85
+ overflow: hidden;
86
+ min-width: 12rem;
87
+ min-height: 8rem;
73
88
  }
74
- get isEmpty() {
75
- return false;
89
+
90
+ .dock-floating__chrome {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 0.5rem;
94
+ padding: 0.35rem 0.75rem;
95
+ cursor: move;
96
+ background: linear-gradient(
97
+ to bottom,
98
+ rgba(148, 163, 184, 0.6),
99
+ rgba(148, 163, 184, 0.25)
100
+ );
101
+ border-bottom: 1px solid rgba(148, 163, 184, 0.5);
102
+ user-select: none;
103
+ -webkit-user-select: none;
76
104
  }
77
- }
78
105
 
79
- class BsFloatingPane extends BsDockPane {
80
- constructor(data) {
81
- super();
82
- Object.assign(this, data);
106
+ .dock-floating__title {
107
+ flex: 1 1 auto;
108
+ font-size: 0.875rem;
109
+ font-weight: 500;
110
+ color: rgba(30, 41, 59, 0.95);
111
+ overflow: hidden;
112
+ text-overflow: ellipsis;
113
+ white-space: nowrap;
83
114
  }
84
- get isEmpty() {
85
- return this.pane?.isEmpty ?? true;
115
+
116
+ .dock-floating > .dock-stack {
117
+ flex: 1 1 auto;
118
+ min-width: 12rem;
119
+ min-height: 8rem;
86
120
  }
87
- }
88
121
 
89
- class BsSplitPane extends BsDockPane {
90
- constructor(data) {
91
- super();
92
- this.orientation = 'horizontal';
93
- this.panes = [];
94
- Object.assign(this, data);
122
+ .dock-floating__resizer {
123
+ position: absolute;
124
+ pointer-events: auto;
125
+ z-index: 2;
126
+ background: rgba(148, 163, 184, 0.25);
127
+ transition: background 120ms ease;
95
128
  }
96
- get isEmpty() {
97
- return this.panes.length === 0;
129
+
130
+ .dock-floating__resizer:hover {
131
+ background: rgba(148, 163, 184, 0.4);
98
132
  }
99
- }
100
133
 
101
- var EPaneType;
102
- (function (EPaneType) {
103
- EPaneType[EPaneType["documentHost"] = 1] = "documentHost";
104
- EPaneType[EPaneType["splitPane"] = 2] = "splitPane";
105
- EPaneType[EPaneType["contentPane"] = 3] = "contentPane";
106
- EPaneType[EPaneType["tabGroupPane"] = 4] = "tabGroupPane";
107
- })(EPaneType || (EPaneType = {}));
108
-
109
- class BsDockPaneRendererComponent {
110
- constructor(overlay, element) {
111
- this.overlay = overlay;
112
- this.paneTypes = EPaneType;
113
- this.BsDocumentHostType = BsDocumentHost;
114
- this.BsTabGroupType = BsTabGroupPane;
115
- this.BsSplitterType = BsSplitPane;
116
- this.BsContentPaneType = BsContentPane;
117
- this.BsFloatingPaneType = BsFloatingPane;
118
- //#region Layout
119
- this.layout$ = new BehaviorSubject(null);
120
- this.portal = new DomPortal(element);
134
+ .dock-floating__resizer--top,
135
+ .dock-floating__resizer--bottom {
136
+ left: 0.75rem;
137
+ right: 0.75rem;
138
+ height: 0.5rem;
121
139
  }
122
- get layout() {
123
- return this.layout$.value;
140
+
141
+ .dock-floating__resizer--top {
142
+ top: 0;
143
+ cursor: n-resize;
124
144
  }
125
- set layout(value) {
126
- this.layout$.next(value);
145
+
146
+ .dock-floating__resizer--bottom {
147
+ bottom: 0;
148
+ cursor: s-resize;
127
149
  }
128
- moveToOverlay() {
129
- if (!this.overlayRef && !this.portal.isAttached) {
130
- this.overlayRef = this.overlay.create({});
131
- this.portal.attach(this.overlayRef);
132
- }
150
+
151
+ .dock-floating__resizer--left,
152
+ .dock-floating__resizer--right {
153
+ top: 1.75rem;
154
+ bottom: 0.75rem;
155
+ width: 0.5rem;
133
156
  }
134
- disposeOverlay() {
135
- if (this.overlayRef) {
136
- this.portal.detach();
137
- this.overlayRef.dispose();
138
- this.overlayRef = undefined;
139
- }
157
+
158
+ .dock-floating__resizer--left {
159
+ left: 0;
160
+ cursor: w-resize;
161
+ }
162
+
163
+ .dock-floating__resizer--right {
164
+ right: 0;
165
+ cursor: e-resize;
166
+ }
167
+
168
+ .dock-floating__resizer--top-left,
169
+ .dock-floating__resizer--top-right,
170
+ .dock-floating__resizer--bottom-left,
171
+ .dock-floating__resizer--bottom-right {
172
+ width: 0.75rem;
173
+ height: 0.75rem;
174
+ }
175
+
176
+ .dock-floating__resizer--top-left {
177
+ top: 0;
178
+ left: 0;
179
+ cursor: nw-resize;
180
+ }
181
+
182
+ .dock-floating__resizer--top-right {
183
+ top: 0;
184
+ right: 0;
185
+ cursor: ne-resize;
186
+ }
187
+
188
+ .dock-floating__resizer--bottom-left {
189
+ bottom: 0;
190
+ left: 0;
191
+ cursor: sw-resize;
192
+ }
193
+
194
+ .dock-floating__resizer--bottom-right {
195
+ right: 0;
196
+ bottom: 0;
197
+ cursor: se-resize;
198
+ }
199
+
200
+ .dock-split {
201
+ display: flex;
202
+ flex: 1 1 0;
203
+ gap: 0.25rem;
204
+ position: relative;
205
+ }
206
+
207
+ .dock-split[data-direction="vertical"] {
208
+ flex-direction: column;
209
+ }
210
+
211
+ .dock-split[data-direction="horizontal"] {
212
+ flex-direction: row;
213
+ }
214
+
215
+ .dock-split__child {
216
+ display: flex;
217
+ flex: 1 1 0;
218
+ position: relative;
219
+ }
220
+
221
+ .dock-split__divider {
222
+ position: relative;
223
+ flex: 0 0 auto;
224
+ background: rgba(0, 0, 0, 0.08);
225
+ transition: background 120ms ease;
226
+ }
227
+
228
+ .dock-split[data-direction="horizontal"] > .dock-split__divider {
229
+ width: 0.5rem;
230
+ cursor: col-resize;
140
231
  }
141
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockPaneRendererComponent, deps: [{ token: i1.Overlay }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
142
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: BsDockPaneRendererComponent, isStandalone: false, selector: "bs-dock-pane-renderer", inputs: { layout: "layout" }, ngImport: i0, template: "<!-- <ng-content></ng-content> -->\n<ng-container [bsInstanceof]=\"layout$ | async\">\n <div *bsInstanceofCase=\"BsDocumentHostType; let documentHost\" data-hello>\n <bs-dock-pane-renderer [layout]=\"documentHost\"></bs-dock-pane-renderer>\n </div>\n <bs-tab-control *bsInstanceofCase=\"BsTabGroupType; let tabGroup\" [allowDragDrop]=\"false\" class=\"d-flex flex-column h-100 dock-tabs\">\n @for (tab of tabGroup.panes; track tab) {\n <bs-tab-page>\n <ng-container *bsTabPageHeader>\n <ng-template [cdkPortalOutlet]=\"tab.dockPanel.headerPortal\"></ng-template>\n </ng-container>\n <bs-dock-pane-renderer [layout]=\"tab\"></bs-dock-pane-renderer>\n </bs-tab-page>\n }\n </bs-tab-control>\n <bs-splitter *bsInstanceofCase=\"BsSplitterType; let splitter\" [orientation]=\"splitter.orientation\">\n @for (panel of splitter.panes; track panel) {\n <bs-split-panel>\n <bs-dock-pane-renderer [layout]=\"panel\"></bs-dock-pane-renderer>\n </bs-split-panel>\n }\n </bs-splitter>\n <ng-container *bsInstanceofCase=\"BsContentPaneType; let content\">\n <ng-template [cdkPortalOutlet]=\"content.dockPanel.contentPortal\"></ng-template>\n </ng-container>\n <ng-container *bsInstanceofCase=\"BsFloatingPaneType; let floating\">\n @if (floating.pane) {\n <bs-resizable [positioning]=\"'absolute'\" [presetPosition]=\"{ left: floating.location?.x ?? 0, top: floating.location?.y ?? 0, width: floating.size?.width ?? 200, height: floating.size?.height ?? 100 }\">\n <bs-dock-pane-renderer [layout]=\"floating.pane\"></bs-dock-pane-renderer>\n </bs-resizable>\n }\n </ng-container>\n <div *bsInstanceofDefault>\n No match\n </div>\n</ng-container>", styles: [".dock-tabs{background:#f3f3f3}:host ::ng-deep .tab-page-content{background:#fff;min-height:100%}\n"], dependencies: [{ kind: "directive", type: i2.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "directive", type: i3.BsInstanceOfDirective, selector: "[bsInstanceof]", inputs: ["bsInstanceof"] }, { kind: "directive", type: i3.BsInstanceofCaseDirective, selector: "[bsInstanceofCase]", inputs: ["bsInstanceofCase"] }, { kind: "directive", type: i3.BsInstanceOfDefaultDirective, selector: "[bsInstanceofDefault]" }, { kind: "component", type: i4.BsSplitterComponent, selector: "bs-splitter", inputs: ["orientation"] }, { kind: "component", type: i4.BsSplitPanelComponent, selector: "bs-split-panel" }, { kind: "component", type: i5.BsTabControlComponent, selector: "bs-tab-control", inputs: ["border", "restrictDragging", "selectFirstTab", "tabsPosition", "allowDragDrop"] }, { kind: "component", type: i5.BsTabPageComponent, selector: "bs-tab-page", inputs: ["disabled"] }, { kind: "directive", type: i5.BsTabPageHeaderDirective, selector: "[bsTabPageHeader]" }, { kind: "component", type: i6.BsResizableComponent, selector: "bs-resizable", inputs: ["positioning", "presetPosition"] }, { kind: "component", type: BsDockPaneRendererComponent, selector: "bs-dock-pane-renderer", inputs: ["layout"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }] }); }
143
- }
144
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockPaneRendererComponent, decorators: [{
145
- type: Component,
146
- args: [{ selector: 'bs-dock-pane-renderer', standalone: false, template: "<!-- <ng-content></ng-content> -->\n<ng-container [bsInstanceof]=\"layout$ | async\">\n <div *bsInstanceofCase=\"BsDocumentHostType; let documentHost\" data-hello>\n <bs-dock-pane-renderer [layout]=\"documentHost\"></bs-dock-pane-renderer>\n </div>\n <bs-tab-control *bsInstanceofCase=\"BsTabGroupType; let tabGroup\" [allowDragDrop]=\"false\" class=\"d-flex flex-column h-100 dock-tabs\">\n @for (tab of tabGroup.panes; track tab) {\n <bs-tab-page>\n <ng-container *bsTabPageHeader>\n <ng-template [cdkPortalOutlet]=\"tab.dockPanel.headerPortal\"></ng-template>\n </ng-container>\n <bs-dock-pane-renderer [layout]=\"tab\"></bs-dock-pane-renderer>\n </bs-tab-page>\n }\n </bs-tab-control>\n <bs-splitter *bsInstanceofCase=\"BsSplitterType; let splitter\" [orientation]=\"splitter.orientation\">\n @for (panel of splitter.panes; track panel) {\n <bs-split-panel>\n <bs-dock-pane-renderer [layout]=\"panel\"></bs-dock-pane-renderer>\n </bs-split-panel>\n }\n </bs-splitter>\n <ng-container *bsInstanceofCase=\"BsContentPaneType; let content\">\n <ng-template [cdkPortalOutlet]=\"content.dockPanel.contentPortal\"></ng-template>\n </ng-container>\n <ng-container *bsInstanceofCase=\"BsFloatingPaneType; let floating\">\n @if (floating.pane) {\n <bs-resizable [positioning]=\"'absolute'\" [presetPosition]=\"{ left: floating.location?.x ?? 0, top: floating.location?.y ?? 0, width: floating.size?.width ?? 200, height: floating.size?.height ?? 100 }\">\n <bs-dock-pane-renderer [layout]=\"floating.pane\"></bs-dock-pane-renderer>\n </bs-resizable>\n }\n </ng-container>\n <div *bsInstanceofDefault>\n No match\n </div>\n</ng-container>", styles: [".dock-tabs{background:#f3f3f3}:host ::ng-deep .tab-page-content{background:#fff;min-height:100%}\n"] }]
147
- }], ctorParameters: () => [{ type: i1.Overlay }, { type: i0.ElementRef }], propDecorators: { layout: [{
148
- type: Input
149
- }] } });
150
232
 
151
- class BsDockComponent {
233
+ .dock-split[data-direction="vertical"] > .dock-split__divider {
234
+ height: 0.5rem;
235
+ cursor: row-resize;
236
+ }
237
+
238
+ .dock-split__divider::after {
239
+ content: '';
240
+ position: absolute;
241
+ top: 50%;
242
+ left: 50%;
243
+ transform: translate(-50%, -50%);
244
+ border-radius: 999px;
245
+ background: rgba(0, 0, 0, 0.25);
246
+ }
247
+
248
+ .dock-split[data-direction="horizontal"] > .dock-split__divider::after {
249
+ width: 0.125rem;
250
+ height: 60%;
251
+ }
252
+
253
+ .dock-split[data-direction="vertical"] > .dock-split__divider::after {
254
+ width: 60%;
255
+ height: 0.125rem;
256
+ }
257
+
258
+ .dock-split__divider:hover,
259
+ .dock-split__divider:focus-visible,
260
+ .dock-split__divider[data-resizing='true'] {
261
+ background: rgba(59, 130, 246, 0.35);
262
+ }
263
+
264
+ .dock-stack {
265
+ display: flex;
266
+ flex-direction: column;
267
+ flex: 1 1 0;
268
+ border: 1px solid rgba(0, 0, 0, 0.2);
269
+ border-radius: 0.25rem;
270
+ background: rgba(255, 255, 255, 0.75);
271
+ backdrop-filter: blur(4px);
272
+ }
273
+
274
+ .dock-stack__header {
275
+ display: flex;
276
+ flex-wrap: wrap;
277
+ gap: 0.25rem;
278
+ padding: 0.25rem;
279
+ background: rgba(0, 0, 0, 0.05);
280
+ border-bottom: 1px solid rgba(0, 0, 0, 0.15);
281
+ }
282
+
283
+ .dock-tab {
284
+ appearance: none;
285
+ border: none;
286
+ padding: 0.25rem 0.5rem;
287
+ border-radius: 0.25rem;
288
+ background: transparent;
289
+ color: inherit;
290
+ font: inherit;
291
+ cursor: grab;
292
+ transition: background 160ms ease;
293
+ }
294
+
295
+ .dock-tab:active {
296
+ cursor: grabbing;
297
+ }
298
+
299
+ .dock-tab:hover {
300
+ background: rgba(0, 0, 0, 0.05);
301
+ }
302
+
303
+ .dock-tab:focus-visible {
304
+ outline: 2px solid rgba(59, 130, 246, 0.8);
305
+ outline-offset: 1px;
306
+ }
307
+
308
+ .dock-tab--active {
309
+ background: rgba(59, 130, 246, 0.15);
310
+ }
311
+
312
+ .dock-stack__content {
313
+ position: relative;
314
+ flex: 1 1 auto;
315
+ display: flex;
316
+ overflow: hidden;
317
+ }
318
+
319
+ .dock-stack__pane {
320
+ position: relative;
321
+ flex: 1 1 100%;
322
+ display: flex;
323
+ flex-direction: column;
324
+ overflow: hidden;
325
+ }
326
+
327
+ .dock-stack__pane[hidden] {
328
+ display: none !important;
329
+ }
330
+
331
+ .dock-drop-indicator {
332
+ position: absolute;
333
+ pointer-events: none;
334
+ border: 2px solid rgba(59, 130, 246, 0.9);
335
+ background: rgba(59, 130, 246, 0.2);
336
+ border-radius: 0.25rem;
337
+ opacity: 0;
338
+ transition: opacity 120ms ease;
339
+ z-index: 100;
340
+ }
341
+
342
+ .dock-drop-indicator[data-visible='true'] {
343
+ opacity: 1;
344
+ }
345
+
346
+ .dock-drop-joystick {
347
+ position: absolute;
348
+ display: grid;
349
+ grid-template-columns: repeat(3, min-content);
350
+ grid-template-rows: repeat(3, min-content);
351
+ gap: 0.125rem;
352
+ padding: 0.125rem;
353
+ border-radius: 999px;
354
+ background: rgba(15, 23, 42, 0.15);
355
+ box-shadow: 0 4px 12px rgba(15, 23, 42, 0.25);
356
+ pointer-events: none;
357
+ transform: translate(-50%, -50%);
358
+ z-index: 110;
359
+ }
360
+
361
+ .dock-drop-joystick__spacer {
362
+ width: 1.75rem;
363
+ height: 1.75rem;
364
+ pointer-events: none;
365
+ }
366
+
367
+ .dock-drop-joystick__button {
368
+ width: 1.75rem;
369
+ height: 1.75rem;
370
+ display: inline-flex;
371
+ align-items: center;
372
+ justify-content: center;
373
+ border-radius: 0.375rem;
374
+ border: 1px solid rgba(59, 130, 246, 0.4);
375
+ background: rgba(255, 255, 255, 0.9);
376
+ color: rgba(30, 64, 175, 0.9);
377
+ font-size: 0.75rem;
378
+ line-height: 1;
379
+ font-weight: 600;
380
+ pointer-events: auto;
381
+ cursor: pointer;
382
+ transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
383
+ }
384
+
385
+ .dock-drop-joystick__button[data-active='true'],
386
+ .dock-drop-joystick__button:hover,
387
+ .dock-drop-joystick__button:focus-visible {
388
+ background: rgba(59, 130, 246, 0.25);
389
+ border-color: rgba(59, 130, 246, 0.8);
390
+ color: rgba(30, 64, 175, 1);
391
+ }
392
+
393
+ .dock-drop-joystick__button:focus-visible {
394
+ outline: 2px solid rgba(59, 130, 246, 0.9);
395
+ outline-offset: 1px;
396
+ }
397
+
398
+ .dock-drop-joystick__button[data-zone='center'] {
399
+ border-radius: 0.5rem;
400
+ }
401
+
402
+ ::slotted(*) {
403
+ flex: 1 1 auto;
404
+ display: block;
405
+ min-width: 0;
406
+ min-height: 0;
407
+ }
408
+ </style>
409
+ <div class="dock-root">
410
+ <div class="dock-docked"></div>
411
+ <div class="dock-floating-layer"></div>
412
+ </div>
413
+ <div class="dock-drop-indicator"></div>
414
+ <div class="dock-drop-joystick" data-visible="false">
415
+ <div class="dock-drop-joystick__spacer"></div>
416
+ <button
417
+ class="dock-drop-joystick__button"
418
+ type="button"
419
+ data-zone="top"
420
+ aria-label="Dock to top"
421
+ >
422
+
423
+ </button>
424
+ <div class="dock-drop-joystick__spacer"></div>
425
+ <button
426
+ class="dock-drop-joystick__button"
427
+ type="button"
428
+ data-zone="left"
429
+ aria-label="Dock to left"
430
+ >
431
+
432
+ </button>
433
+ <button
434
+ class="dock-drop-joystick__button"
435
+ type="button"
436
+ data-zone="center"
437
+ aria-label="Dock to center"
438
+ >
439
+
440
+ </button>
441
+ <button
442
+ class="dock-drop-joystick__button"
443
+ type="button"
444
+ data-zone="right"
445
+ aria-label="Dock to right"
446
+ >
447
+
448
+ </button>
449
+ <div class="dock-drop-joystick__spacer"></div>
450
+ <button
451
+ class="dock-drop-joystick__button"
452
+ type="button"
453
+ data-zone="bottom"
454
+ aria-label="Dock to bottom"
455
+ >
456
+
457
+ </button>
458
+ <div class="dock-drop-joystick__spacer"></div>
459
+ </div>
460
+ `;
461
+ let cachedTemplate = null;
462
+ let cachedTemplateDocument = null;
463
+ function ensureTemplate(documentRef) {
464
+ if (!cachedTemplate || cachedTemplateDocument !== documentRef) {
465
+ cachedTemplate = documentRef.createElement('template');
466
+ cachedTemplate.innerHTML = templateHtml;
467
+ cachedTemplateDocument = documentRef;
468
+ }
469
+ return cachedTemplate;
470
+ }
471
+ class MintDockManagerElement extends HTMLElement {
472
+ static { this.documentRef = typeof document !== 'undefined' ? document : null; }
473
+ static configureDocument(documentRef) {
474
+ if (documentRef) {
475
+ MintDockManagerElement.documentRef = documentRef;
476
+ }
477
+ }
478
+ static get observedAttributes() {
479
+ return ['layout'];
480
+ }
481
+ static { this.instanceCounter = 0; }
152
482
  constructor() {
153
- //#region Panels
154
- this.panels$ = new BehaviorSubject([]);
155
- //#endregion
156
- this.floating$ = new BehaviorSubject([]);
157
- this.positionAbsolute = true;
158
- this.positionPx = 0;
159
- const tabs = new BsTabGroupPane();
160
- const docHost = new BsDocumentHost();
161
- docHost.rootPane = tabs;
162
- this.layout$ = new BehaviorSubject({
163
- rootPane: docHost,
164
- floatingPanes: []
165
- });
166
- this.floating$.pipe(takeUntilDestroyed())
167
- .subscribe((floating) => {
168
- floating.forEach((panel) => panel.moveToOverlay());
169
- });
170
- this.parentifiedLayout$ = this.layout$.pipe(map(layout => {
171
- const clone = deepClone(layout, true,
172
- // []
173
- [BsContentPane, BsDocumentHost, BsFloatingPane, BsSplitPane, BsTabGroupPane], true, this.paneCache);
174
- // this.paneCache = clone.cache;
175
- console.log('parentify', { layout, result: clone.result });
176
- return clone.result;
177
- }));
178
- this.parentifiedLayout$.pipe(takeUntilDestroyed()).subscribe(console.log);
483
+ super();
484
+ this.dropJoystickTarget = null;
485
+ this.rootLayout = null;
486
+ this.floatingLayouts = [];
487
+ this.pendingTabDragMetrics = null;
488
+ this.resizeState = null;
489
+ this.dragState = null;
490
+ this.floatingDragState = null;
491
+ this.floatingResizeState = null;
492
+ this.pointerTrackingActive = false;
493
+ this.dragPointerTrackingActive = false;
494
+ this.lastDragPointerPosition = null;
495
+ this.pendingDragEndTimeout = null;
496
+ const documentRef = this.resolveDocument();
497
+ this.documentRef = documentRef;
498
+ this.windowRef = this.resolveWindow(documentRef);
499
+ const shadowRoot = this.attachShadow({ mode: 'open' });
500
+ const template = ensureTemplate(documentRef);
501
+ shadowRoot.appendChild(template.content.cloneNode(true));
502
+ const root = shadowRoot.querySelector('.dock-root');
503
+ if (!root) {
504
+ throw new Error('mint-dock-manager template is missing the root element.');
505
+ }
506
+ const docked = shadowRoot.querySelector('.dock-docked');
507
+ if (!docked) {
508
+ throw new Error('mint-dock-manager template is missing the docked surface element.');
509
+ }
510
+ const floatingLayer = shadowRoot.querySelector('.dock-floating-layer');
511
+ if (!floatingLayer) {
512
+ throw new Error('mint-dock-manager template is missing the floating layer element.');
513
+ }
514
+ const indicator = shadowRoot.querySelector('.dock-drop-indicator');
515
+ if (!indicator) {
516
+ throw new Error('mint-dock-manager template is missing the drop indicator element.');
517
+ }
518
+ const joystick = shadowRoot.querySelector('.dock-drop-joystick');
519
+ if (!joystick) {
520
+ throw new Error('mint-dock-manager template is missing the drop joystick element.');
521
+ }
522
+ const joystickButtons = Array.from(joystick.querySelectorAll('.dock-drop-joystick__button[data-zone]'));
523
+ if (joystickButtons.length === 0) {
524
+ throw new Error('mint-dock-manager template is missing drop joystick buttons.');
525
+ }
526
+ this.rootEl = root;
527
+ this.dockedEl = docked;
528
+ this.floatingLayerEl = floatingLayer;
529
+ this.dropIndicator = indicator;
530
+ this.dropJoystick = joystick;
531
+ this.dropJoystickButtons = joystickButtons;
532
+ this.instanceId = `mint-dock-${++MintDockManagerElement.instanceCounter}`;
533
+ this.onPointerMove = this.onPointerMove.bind(this);
534
+ this.onPointerUp = this.onPointerUp.bind(this);
535
+ this.onDragOver = this.onDragOver.bind(this);
536
+ this.onGlobalDragOver = this.onGlobalDragOver.bind(this);
537
+ this.onGlobalDragEnd = this.onGlobalDragEnd.bind(this);
538
+ this.onDrop = this.onDrop.bind(this);
539
+ this.onDragLeave = this.onDragLeave.bind(this);
540
+ this.onDrag = this.onDrag.bind(this);
541
+ this.onDragMouseMove = this.onDragMouseMove.bind(this);
542
+ this.onDragTouchMove = this.onDragTouchMove.bind(this);
543
+ this.onDragMouseUp = this.onDragMouseUp.bind(this);
544
+ this.onDragTouchEnd = this.onDragTouchEnd.bind(this);
545
+ }
546
+ connectedCallback() {
547
+ if (!this.hasAttribute('role')) {
548
+ this.setAttribute('role', 'application');
549
+ }
550
+ this.render();
551
+ this.rootEl.addEventListener('dragover', this.onDragOver);
552
+ this.rootEl.addEventListener('drop', this.onDrop);
553
+ this.rootEl.addEventListener('dragleave', this.onDragLeave);
554
+ this.dropJoystick.addEventListener('dragover', this.onDragOver);
555
+ this.dropJoystick.addEventListener('drop', this.onDrop);
556
+ this.dropJoystick.addEventListener('dragleave', this.onDragLeave);
557
+ const win = this.windowRef;
558
+ win?.addEventListener('dragover', this.onGlobalDragOver);
559
+ win?.addEventListener('drag', this.onDrag);
560
+ win?.addEventListener('dragend', this.onGlobalDragEnd, true);
179
561
  }
180
- set panels(value) {
181
- this.panels$.next(value.toArray());
562
+ disconnectedCallback() {
563
+ this.rootEl.removeEventListener('dragover', this.onDragOver);
564
+ this.rootEl.removeEventListener('drop', this.onDrop);
565
+ this.rootEl.removeEventListener('dragleave', this.onDragLeave);
566
+ this.dropJoystick.removeEventListener('dragover', this.onDragOver);
567
+ this.dropJoystick.removeEventListener('drop', this.onDrop);
568
+ this.dropJoystick.removeEventListener('dragleave', this.onDragLeave);
569
+ const win = this.windowRef;
570
+ win?.removeEventListener('dragover', this.onGlobalDragOver);
571
+ win?.removeEventListener('drag', this.onDrag);
572
+ win?.removeEventListener('dragend', this.onGlobalDragEnd, true);
573
+ this.stopDragPointerTracking();
574
+ win?.removeEventListener('pointermove', this.onPointerMove);
575
+ win?.removeEventListener('pointerup', this.onPointerUp);
576
+ this.pointerTrackingActive = false;
577
+ }
578
+ attributeChangedCallback(name, _oldValue, newValue) {
579
+ if (name === 'layout') {
580
+ this.layout = newValue ? this.parseLayout(newValue) : null;
581
+ }
182
582
  }
183
583
  get layout() {
184
- return this.layout$.value;
584
+ return {
585
+ root: this.cloneLayoutNode(this.rootLayout),
586
+ floating: this.cloneFloatingArray(this.floatingLayouts),
587
+ };
185
588
  }
186
589
  set layout(value) {
187
- this.layout$.next(value);
590
+ const snapshot = this.ensureSnapshot(value);
591
+ this.rootLayout = this.cloneLayoutNode(snapshot.root);
592
+ this.floatingLayouts = this.cloneFloatingArray(snapshot.floating);
593
+ this.render();
188
594
  }
189
- set floatingPanes(value) {
190
- this.floating$.next(value.toArray());
595
+ get snapshot() {
596
+ return this.layout;
191
597
  }
192
- ngOnDestroy() {
193
- this.floating$.value.forEach(panel => panel.disposeOverlay());
194
- }
195
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
196
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: BsDockComponent, isStandalone: false, selector: "bs-dock", inputs: { layout: "layout" }, host: { properties: { "class.position-absolute": "this.positionAbsolute", "style.top": "this.positionPx", "style.left": "this.positionPx", "style.bottom": "this.positionPx", "style.right": "this.positionPx" } }, queries: [{ propertyName: "panels", predicate: BsDockPanelComponent }], viewQueries: [{ propertyName: "floatingPanes", predicate: ["floating"], descendants: true }], ngImport: i0, template: "<ng-content></ng-content>\n@if (layout$ | async; as layout) {\n <bs-dock-pane-renderer [layout]=\"layout.rootPane\"></bs-dock-pane-renderer>\n @for (pane of layout.floatingPanes; track pane) {\n @if (pane.pane) {\n <bs-dock-pane-renderer #floating [layout]=\"pane\"></bs-dock-pane-renderer>\n }\n }\n}", styles: [":host ::ng-deep .tab-content{max-height:calc(100% - 41px);overflow:auto}:host ::ng-deep bs-dock-pane-renderer{min-width:100%;min-height:100%}:host ::ng-deep>bs-dock-panel>*{display:none}\n"], dependencies: [{ kind: "component", type: BsDockPaneRendererComponent, selector: "bs-dock-pane-renderer", inputs: ["layout"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }] }); }
197
- }
198
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockComponent, decorators: [{
199
- type: Component,
200
- args: [{ selector: 'bs-dock', standalone: false, template: "<ng-content></ng-content>\n@if (layout$ | async; as layout) {\n <bs-dock-pane-renderer [layout]=\"layout.rootPane\"></bs-dock-pane-renderer>\n @for (pane of layout.floatingPanes; track pane) {\n @if (pane.pane) {\n <bs-dock-pane-renderer #floating [layout]=\"pane\"></bs-dock-pane-renderer>\n }\n }\n}", styles: [":host ::ng-deep .tab-content{max-height:calc(100% - 41px);overflow:auto}:host ::ng-deep bs-dock-pane-renderer{min-width:100%;min-height:100%}:host ::ng-deep>bs-dock-panel>*{display:none}\n"] }]
201
- }], ctorParameters: () => [], propDecorators: { panels: [{
202
- type: ContentChildren,
203
- args: [BsDockPanelComponent]
204
- }], layout: [{
205
- type: Input
206
- }], floatingPanes: [{
207
- type: ViewChildren,
208
- args: ['floating']
209
- }], positionAbsolute: [{
210
- type: HostBinding,
211
- args: ['class.position-absolute']
212
- }], positionPx: [{
213
- type: HostBinding,
214
- args: ['style.top']
215
- }, {
216
- type: HostBinding,
217
- args: ['style.left']
218
- }, {
219
- type: HostBinding,
220
- args: ['style.bottom']
221
- }, {
222
- type: HostBinding,
223
- args: ['style.right']
224
- }] } });
225
-
226
- class BsDockService {
227
- buildTraces(layout) {
228
- const result = [layout.rootPane, ...layout.floatingPanes].map(pane => this.buildTracesPrivate([pane]));
229
- // const result = this.buildTracesPrivate([layout.rootPane]);
230
- return result.flatMap(traceGroup => traceGroup);
598
+ toJSON() {
599
+ return this.snapshot;
231
600
  }
232
- buildTracesPrivate(currentSequence) {
233
- const children = this.getChildPanes(currentSequence[currentSequence.length - 1]);
234
- if (children.length === 0) {
235
- return [{
236
- finished: true,
237
- trace: currentSequence
238
- }];
601
+ resolveDocument() {
602
+ const staticDocument = MintDockManagerElement.documentRef;
603
+ const ownerDocument = this.ownerDocument ?? null;
604
+ const globalDocument = typeof document !== 'undefined' ? document : null;
605
+ const resolvedDocument = staticDocument ?? ownerDocument ?? globalDocument;
606
+ if (!resolvedDocument) {
607
+ throw new Error('mint-dock-manager requires a Document to initialize.');
239
608
  }
240
- else {
241
- const result = children.map(child => this.buildTracesPrivate([...currentSequence, child]));
242
- return result.flatMap(r => r);
609
+ if (!MintDockManagerElement.documentRef) {
610
+ MintDockManagerElement.documentRef = resolvedDocument;
243
611
  }
612
+ return resolvedDocument;
244
613
  }
245
- getChildPanes(pane) {
246
- if (pane instanceof BsContentPane) {
247
- return [];
614
+ resolveWindow(documentRef) {
615
+ if (typeof window !== 'undefined') {
616
+ return window;
248
617
  }
249
- else if (pane instanceof BsTabGroupPane) {
250
- return pane.panes;
618
+ return documentRef.defaultView ?? null;
619
+ }
620
+ parseLayout(value) {
621
+ try {
622
+ const parsed = JSON.parse(value);
623
+ return this.ensureSnapshot(parsed);
251
624
  }
252
- else if (pane instanceof BsSplitPane) {
253
- return pane.panes;
625
+ catch (err) {
626
+ console.warn('mint-dock-manager: failed to parse layout attribute', err);
627
+ return null;
254
628
  }
255
- else if (pane instanceof BsFloatingPane) {
256
- return pane.pane ? [pane.pane] : [];
629
+ }
630
+ ensureSnapshot(value) {
631
+ if (!value) {
632
+ return { root: null, floating: [] };
257
633
  }
258
- else if (pane instanceof BsDocumentHost) {
259
- return pane.rootPane ? [pane.rootPane] : [];
634
+ if (value.kind) {
635
+ return { root: value, floating: [] };
260
636
  }
261
- else {
262
- return [];
637
+ const layout = value;
638
+ return {
639
+ root: layout.root ?? null,
640
+ floating: Array.isArray(layout.floating)
641
+ ? layout.floating.map((floating) => this.normalizeFloatingLayout(floating))
642
+ : [],
643
+ };
644
+ }
645
+ render() {
646
+ this.dockedEl.innerHTML = '';
647
+ this.floatingLayerEl.innerHTML = '';
648
+ this.hideDropIndicator();
649
+ if (this.rootLayout) {
650
+ const fragment = this.renderNode(this.rootLayout, []);
651
+ this.dockedEl.appendChild(fragment);
263
652
  }
653
+ this.renderFloatingPanes();
264
654
  }
265
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
266
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockService, providedIn: 'root' }); }
267
- }
268
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockService, decorators: [{
269
- type: Injectable,
270
- args: [{
271
- providedIn: 'root'
272
- }]
273
- }] });
274
-
275
- class BsDockPanelHeaderComponent {
276
- constructor(dockPanel, dock, dockService, element, destroy) {
277
- this.dockPanel = dockPanel;
278
- this.dock = dock;
279
- this.dockService = dockService;
280
- this.element = element;
281
- this.destroy = destroy;
282
- this.isMouseDown = false;
283
- this.isDragging = false;
284
- this.dBlock = true;
285
- }
286
- onMouseDown(ev) {
287
- ev.preventDefault();
288
- this.isMouseDown = true;
289
- }
290
- onMouseMove(ev) {
291
- if (this.isMouseDown) {
292
- if (!this.isDragging) {
293
- this.isDragging = true;
294
- this.dock.parentifiedLayout$.pipe(take(1), takeUntilDestroyed(this.destroy)).subscribe((parentifiedLayout) => {
295
- // this.dock.layout$.pipe(take(1)).subscribe((layout) => {
296
- const traces = this.dockService.buildTraces(parentifiedLayout);
297
- console.log('traces', { parentifiedLayout, traces });
298
- const matching = traces.filter(t => {
299
- const lastPane = t.trace[t.trace.length - 1];
300
- // return (lastPane instanceof BsContentPane) && (lastPane.dockPanel === this.dockPanel);
301
- return (lastPane instanceof BsContentPane) && (lastPane.dockPanel === this.dockPanel);
302
- });
303
- console.log('matching', matching);
304
- if (matching.length !== 1) {
305
- return;
306
- }
307
- // const tabs = matching[0].trace.filter(pane => pane instanceof BsTabGroupPane);
308
- // const lastTabGroup = tabs[tabs.length - 1];
309
- // // console.log('last tabgroup', lastTabGroup);
310
- let tabControlEl = this.element.nativeElement;
311
- do {
312
- if (tabControlEl.parentElement) {
313
- tabControlEl = tabControlEl.parentElement;
314
- }
315
- else {
316
- throw 'No more parents';
317
- }
318
- } while (tabControlEl.tagName.toUpperCase() !== 'BS-TAB-CONTROL');
319
- const coords = {
320
- width: tabControlEl.clientWidth,
321
- height: tabControlEl.clientHeight,
322
- left: ev.clientX - ev.offsetX,
323
- top: ev.clientY - ev.offsetY,
324
- };
325
- // this.dockPanel.headerPortal?.isAttached && this.dockPanel.headerPortal?.detach();
326
- // this.dockPanel.contentPortal?.isAttached && this.dockPanel.contentPortal?.detach();
327
- this.removeFromPane(parentifiedLayout.$original.rootPane, this.dockPanel);
328
- const trace = [...matching[0].trace];
329
- this.dockPanel.headerPortal?.isAttached && this.dockPanel.headerPortal?.detach();
330
- this.dockPanel.contentPortal?.isAttached && this.dockPanel.contentPortal?.detach();
331
- for (let index = trace.length - 1; index >= 0; index--) {
332
- if (index > 0) {
333
- this.removeFromPaneBis(trace[index - 1], trace[index]);
334
- if (!trace[index - 1].isEmpty)
335
- break;
336
- }
337
- else {
338
- const floatingIndex = parentifiedLayout.floatingPanes.indexOf(trace[0]);
339
- if (trace[0].isEmpty && (floatingIndex > -1)) {
340
- parentifiedLayout.$original.floatingPanes.splice(floatingIndex, 1);
341
- }
342
- }
343
- }
344
- // this.removeFromPane((<any>matching[0].trace[0])['$original'], this.dockPanel);
345
- const floatingPane = new BsFloatingPane({
346
- pane: new BsTabGroupPane({
347
- panes: [
348
- new BsContentPane({
349
- dockPanel: this.dockPanel
350
- })
351
- ]
352
- }),
353
- size: {
354
- width: coords.width,
355
- height: coords.height,
356
- },
357
- location: {
358
- x: coords.left,
359
- y: coords.top,
360
- }
361
- });
362
- this.dragOperation = {
363
- offsetX: ev.offsetX,
364
- offsetY: ev.offsetY,
365
- floatingPane
366
- };
367
- parentifiedLayout.$original.floatingPanes.push(floatingPane);
368
- // debugger;
369
- setTimeout(() => this.dock.layout$.next(parentifiedLayout.$original), 5);
370
- // console.log('traces', { traces, matching, dockPanel: this.dockPanel, equals: traces[2].trace[3] });
371
- // let element: HTMLElement | null = this.element.nativeElement;
372
- // let tree: HTMLElement[] = [];
373
- // do {
374
- // tree.push(element!);
375
- // element = element!.parentElement;
376
- // } while (element);
377
- // const tabControls = tree.filter(el => el.tagName.toUpperCase() === 'BS-TAB-CONTROL');
378
- // if (tabControls.length > 0) {
379
- // const coords = {
380
- // width: tabControls[0].clientWidth,
381
- // height: tabControls[0].clientHeight,
382
- // left: ev.clientX - ev.offsetX,
383
- // top: ev.clientY - ev.offsetY,
384
- // };
385
- // this.dockPanel.headerPortal?.isAttached && this.dockPanel.headerPortal?.detach();
386
- // this.dockPanel.contentPortal?.isAttached && this.dockPanel.contentPortal?.detach();
387
- // this.removeFromPane(layout.rootPane, this.dockPanel);
388
- // const floatingPane = new BsFloatingPane({
389
- // pane: new BsTabGroupPane({
390
- // panes: [
391
- // new BsContentPane({
392
- // dockPanel: this.dockPanel
393
- // })
394
- // ]
395
- // }),
396
- // size: {
397
- // width: coords.width,
398
- // height: coords.height,
399
- // },
400
- // location: {
401
- // x: coords.left,
402
- // y: coords.top,
403
- // }
404
- // });
405
- // this.dragOperation = {
406
- // offsetX: ev.offsetX,
407
- // offsetY: ev.offsetY,
408
- // floatingPane
409
- // };
410
- // layout.floatingPanes.push(floatingPane);
411
- // this.dock.layout$.next(layout);
412
- // }
413
- });
655
+ renderNode(node, path, floatingIndex) {
656
+ if (node.kind === 'split') {
657
+ return this.renderSplit(node, path, floatingIndex);
658
+ }
659
+ return this.renderStack(node, path, floatingIndex);
660
+ }
661
+ renderFloatingPanes() {
662
+ this.floatingLayerEl.innerHTML = '';
663
+ this.floatingLayouts.forEach((floating, index) => {
664
+ const wrapper = this.documentRef.createElement('div');
665
+ wrapper.classList.add('dock-floating');
666
+ wrapper.dataset['path'] = this.formatPath({
667
+ type: 'floating',
668
+ index,
669
+ segments: [],
670
+ });
671
+ const { left, top, width, height } = floating.bounds;
672
+ wrapper.style.left = `${left}px`;
673
+ wrapper.style.top = `${top}px`;
674
+ wrapper.style.width = `${width}px`;
675
+ wrapper.style.height = `${height}px`;
676
+ const zIndex = this.getFloatingPaneZIndex(index);
677
+ wrapper.style.zIndex = String(zIndex);
678
+ const chrome = this.documentRef.createElement('div');
679
+ chrome.classList.add('dock-floating__chrome');
680
+ chrome.addEventListener('pointerdown', (event) => this.beginFloatingDrag(event, index, wrapper, chrome));
681
+ const title = this.documentRef.createElement('div');
682
+ title.classList.add('dock-floating__title');
683
+ title.textContent = this.getFloatingWindowTitle(floating);
684
+ chrome.appendChild(title);
685
+ wrapper.appendChild(chrome);
686
+ if (floating.root) {
687
+ const content = this.renderNode(floating.root, [], index);
688
+ content.classList.add('dock-floating__stack');
689
+ wrapper.appendChild(content);
414
690
  }
415
691
  else {
416
- if (this.dragOperation) {
417
- if (this.dragOperation.floatingPane.location) {
418
- this.dragOperation.floatingPane.location.x = ev.clientX - this.dragOperation.offsetX;
419
- this.dragOperation.floatingPane.location.y = ev.clientY - this.dragOperation.offsetY;
420
- }
421
- // } else {
422
- // const traces = this.dockService.buildTraces(this.dock.layout);
423
- // const matchingTrace = traces.filter(trace => {
424
- // const pane = trace.trace[trace.trace.length - 1];
425
- // return (pane instanceof BsContentPane) && (pane.dockPanel === this.dockPanel);
426
- // });
427
- // console.log('matching', { traces, matchingTrace });
428
- // // this.dragOperation = {
429
- // // }
430
- }
692
+ const placeholder = this.documentRef.createElement('div');
693
+ placeholder.classList.add('dock-stack');
694
+ placeholder.dataset['path'] = this.formatPath({
695
+ type: 'floating',
696
+ index,
697
+ segments: [],
698
+ });
699
+ const empty = this.documentRef.createElement('div');
700
+ empty.classList.add('dock-stack__pane');
701
+ empty.textContent = 'No panes configured';
702
+ placeholder.appendChild(empty);
703
+ wrapper.appendChild(placeholder);
431
704
  }
705
+ const resizerConfigs = [
706
+ {
707
+ classes: ['dock-floating__resizer', 'dock-floating__resizer--top-left'],
708
+ edges: { horizontal: 'left', vertical: 'top' },
709
+ },
710
+ {
711
+ classes: ['dock-floating__resizer', 'dock-floating__resizer--top'],
712
+ edges: { horizontal: 'none', vertical: 'top' },
713
+ },
714
+ {
715
+ classes: ['dock-floating__resizer', 'dock-floating__resizer--top-right'],
716
+ edges: { horizontal: 'right', vertical: 'top' },
717
+ },
718
+ {
719
+ classes: ['dock-floating__resizer', 'dock-floating__resizer--right'],
720
+ edges: { horizontal: 'right', vertical: 'none' },
721
+ },
722
+ {
723
+ classes: ['dock-floating__resizer', 'dock-floating__resizer--bottom-right'],
724
+ edges: { horizontal: 'right', vertical: 'bottom' },
725
+ },
726
+ {
727
+ classes: ['dock-floating__resizer', 'dock-floating__resizer--bottom'],
728
+ edges: { horizontal: 'none', vertical: 'bottom' },
729
+ },
730
+ {
731
+ classes: ['dock-floating__resizer', 'dock-floating__resizer--bottom-left'],
732
+ edges: { horizontal: 'left', vertical: 'bottom' },
733
+ },
734
+ {
735
+ classes: ['dock-floating__resizer', 'dock-floating__resizer--left'],
736
+ edges: { horizontal: 'left', vertical: 'none' },
737
+ },
738
+ ];
739
+ resizerConfigs.forEach(({ classes, edges }) => {
740
+ const resizer = this.documentRef.createElement('div');
741
+ resizer.classList.add(...classes);
742
+ resizer.addEventListener('pointerdown', (event) => this.beginFloatingResize(event, index, wrapper, resizer, edges));
743
+ wrapper.appendChild(resizer);
744
+ });
745
+ this.floatingLayerEl.appendChild(wrapper);
746
+ });
747
+ }
748
+ beginFloatingDrag(event, index, wrapper, handle) {
749
+ const floating = this.floatingLayouts[index];
750
+ if (!floating) {
751
+ return;
752
+ }
753
+ event.preventDefault();
754
+ event.stopPropagation();
755
+ const { left, top } = floating.bounds;
756
+ try {
757
+ handle.setPointerCapture(event.pointerId);
432
758
  }
759
+ catch (err) {
760
+ /* no-op: pointer capture may not be supported in all environments */
761
+ }
762
+ this.promoteFloatingPane(index, wrapper);
763
+ this.floatingDragState = {
764
+ index,
765
+ pointerId: event.pointerId,
766
+ startX: event.clientX,
767
+ startY: event.clientY,
768
+ startLeft: left,
769
+ startTop: top,
770
+ wrapper,
771
+ handle,
772
+ };
773
+ this.startPointerTracking();
774
+ }
775
+ beginFloatingResize(event, index, wrapper, handle, edges) {
776
+ const floating = this.floatingLayouts[index];
777
+ if (!floating) {
778
+ return;
779
+ }
780
+ event.preventDefault();
781
+ event.stopPropagation();
782
+ try {
783
+ handle.setPointerCapture(event.pointerId);
784
+ }
785
+ catch (err) {
786
+ /* pointer capture may not be supported */
787
+ }
788
+ this.promoteFloatingPane(index, wrapper);
789
+ this.floatingResizeState = {
790
+ index,
791
+ pointerId: event.pointerId,
792
+ startX: event.clientX,
793
+ startY: event.clientY,
794
+ startWidth: floating.bounds.width,
795
+ startHeight: floating.bounds.height,
796
+ startLeft: floating.bounds.left,
797
+ startTop: floating.bounds.top,
798
+ wrapper,
799
+ handle,
800
+ edges,
801
+ };
802
+ this.startPointerTracking();
803
+ }
804
+ handleFloatingDragMove(event) {
805
+ const state = this.floatingDragState;
806
+ if (!state || event.pointerId !== state.pointerId) {
807
+ return;
808
+ }
809
+ const deltaX = event.clientX - state.startX;
810
+ const deltaY = event.clientY - state.startY;
811
+ const newLeft = state.startLeft + deltaX;
812
+ const newTop = state.startTop + deltaY;
813
+ state.wrapper.style.left = `${newLeft}px`;
814
+ state.wrapper.style.top = `${newTop}px`;
815
+ const floating = this.floatingLayouts[state.index];
816
+ if (floating) {
817
+ floating.bounds.left = newLeft;
818
+ floating.bounds.top = newTop;
819
+ }
820
+ this.updateFloatingDragDropTarget(event);
433
821
  }
434
- removeFromPaneBis(host, pane) {
435
- if (host instanceof BsContentPane) {
822
+ endFloatingDrag(pointerId) {
823
+ const state = this.floatingDragState;
824
+ if (!state || pointerId !== state.pointerId) {
825
+ return;
436
826
  }
437
- else if (host instanceof BsDocumentHost) {
438
- // Actually documentHost should never be removed
827
+ try {
828
+ state.handle.releasePointerCapture(state.pointerId);
439
829
  }
440
- else if (host instanceof BsTabGroupPane) {
441
- if (pane instanceof BsContentPane) {
442
- const index = host.panes.indexOf(pane);
443
- host.panes.splice(index, 1);
830
+ catch (err) {
831
+ /* no-op */
832
+ }
833
+ const dropHandled = state.dropTarget
834
+ ? this.handleFloatingStackDrop(state.index, state.dropTarget.path, state.dropTarget.zone)
835
+ : false;
836
+ this.floatingDragState = null;
837
+ this.hideDropIndicator();
838
+ if (!dropHandled) {
839
+ this.dispatchLayoutChanged();
840
+ }
841
+ }
842
+ updateFloatingDragDropTarget(event) {
843
+ const state = this.floatingDragState;
844
+ if (!state) {
845
+ return;
846
+ }
847
+ const stack = this.findStackAtPoint(event.clientX, event.clientY);
848
+ if (!stack) {
849
+ if (state.dropTarget) {
850
+ delete state.dropTarget;
851
+ this.hideDropIndicator();
444
852
  }
853
+ return;
445
854
  }
446
- else if (host instanceof BsSplitPane) {
447
- const index = host.panes.indexOf(pane);
448
- host.panes.splice(index, 1);
855
+ const path = this.parsePath(stack.dataset['path']);
856
+ if (!path || (path.type === 'floating' && path.index === state.index)) {
857
+ if (state.dropTarget) {
858
+ delete state.dropTarget;
859
+ this.hideDropIndicator();
860
+ }
861
+ return;
449
862
  }
450
- else if (host instanceof BsFloatingPane) {
863
+ const zone = this.computeDropZone(stack, event, this.extractDropZoneFromEvent(event));
864
+ if (zone) {
865
+ state.dropTarget = { path, zone };
451
866
  }
452
- else {
453
- throw 'Unknown pane type';
867
+ else if (state.dropTarget) {
868
+ delete state.dropTarget;
869
+ }
870
+ this.showDropIndicator(stack, zone);
871
+ }
872
+ handleFloatingResizeMove(event) {
873
+ const state = this.floatingResizeState;
874
+ if (!state || event.pointerId !== state.pointerId) {
875
+ return;
454
876
  }
877
+ const deltaX = event.clientX - state.startX;
878
+ const deltaY = event.clientY - state.startY;
879
+ const minWidth = 192;
880
+ const minHeight = 128;
881
+ let newWidth = state.startWidth;
882
+ let newHeight = state.startHeight;
883
+ let newLeft = state.startLeft;
884
+ let newTop = state.startTop;
885
+ if (state.edges.horizontal === 'right') {
886
+ newWidth = Math.max(minWidth, state.startWidth + deltaX);
887
+ }
888
+ else if (state.edges.horizontal === 'left') {
889
+ newWidth = Math.max(minWidth, state.startWidth - deltaX);
890
+ newLeft = state.startLeft + (state.startWidth - newWidth);
891
+ }
892
+ if (state.edges.vertical === 'bottom') {
893
+ newHeight = Math.max(minHeight, state.startHeight + deltaY);
894
+ }
895
+ else if (state.edges.vertical === 'top') {
896
+ newHeight = Math.max(minHeight, state.startHeight - deltaY);
897
+ newTop = state.startTop + (state.startHeight - newHeight);
898
+ }
899
+ state.wrapper.style.width = `${newWidth}px`;
900
+ state.wrapper.style.height = `${newHeight}px`;
901
+ state.wrapper.style.left = `${newLeft}px`;
902
+ state.wrapper.style.top = `${newTop}px`;
903
+ const floating = this.floatingLayouts[state.index];
904
+ if (floating) {
905
+ floating.bounds.width = newWidth;
906
+ floating.bounds.height = newHeight;
907
+ floating.bounds.left = newLeft;
908
+ floating.bounds.top = newTop;
909
+ }
910
+ }
911
+ endFloatingResize(pointerId) {
912
+ const state = this.floatingResizeState;
913
+ if (!state || pointerId !== state.pointerId) {
914
+ return;
915
+ }
916
+ try {
917
+ state.handle.releasePointerCapture(state.pointerId);
918
+ }
919
+ catch (err) {
920
+ /* no-op */
921
+ }
922
+ this.floatingResizeState = null;
923
+ this.dispatchLayoutChanged();
924
+ }
925
+ getFloatingPaneZIndex(index) {
926
+ const floating = this.floatingLayouts[index];
927
+ if (!floating) {
928
+ return 10 + index;
929
+ }
930
+ const base = typeof floating.zIndex === 'number' && Number.isFinite(floating.zIndex)
931
+ ? floating.zIndex
932
+ : 10 + index;
933
+ return base;
455
934
  }
456
- removeFromPane(host, panel /*, parents: BsDockPane[] */) {
457
- if (host instanceof BsContentPane) {
458
- return { paneRemoved: false, hostIsEmpty: false };
935
+ promoteFloatingPane(index, wrapper) {
936
+ const floating = this.floatingLayouts[index];
937
+ if (!floating) {
938
+ return;
459
939
  }
460
- else if (host instanceof BsDocumentHost) {
461
- // Actually documentHost should never be removed
462
- if (!host.rootPane) {
463
- return { paneRemoved: false, hostIsEmpty: true };
940
+ const maxExisting = this.floatingLayouts.reduce((max, layout, idx) => {
941
+ if (!layout) {
942
+ return max;
464
943
  }
465
- const result = this.removeFromPane(host.rootPane, panel);
466
- return { paneRemoved: result.paneRemoved, hostIsEmpty: result.hostIsEmpty };
944
+ const z = idx === index ? Number.NEGATIVE_INFINITY : this.getFloatingPaneZIndex(idx);
945
+ return Math.max(max, z);
946
+ }, 0);
947
+ const nextZ = Math.max(maxExisting + 2, 12);
948
+ floating.zIndex = nextZ;
949
+ wrapper.style.zIndex = String(nextZ);
950
+ }
951
+ getFloatingWrapper(index) {
952
+ const selector = `.dock-floating[data-path="${this.formatPath({
953
+ type: 'floating',
954
+ index,
955
+ segments: [],
956
+ })}"]`;
957
+ return this.floatingLayerEl.querySelector(selector);
958
+ }
959
+ startPointerTracking() {
960
+ if (this.pointerTrackingActive) {
961
+ return;
962
+ }
963
+ const win = this.windowRef;
964
+ win?.addEventListener('pointermove', this.onPointerMove);
965
+ win?.addEventListener('pointerup', this.onPointerUp);
966
+ this.pointerTrackingActive = true;
967
+ }
968
+ stopPointerTrackingIfIdle() {
969
+ if (this.pointerTrackingActive &&
970
+ !this.resizeState &&
971
+ !this.floatingDragState &&
972
+ !this.floatingResizeState) {
973
+ const win = this.windowRef;
974
+ win?.removeEventListener('pointermove', this.onPointerMove);
975
+ win?.removeEventListener('pointerup', this.onPointerUp);
976
+ this.pointerTrackingActive = false;
977
+ }
978
+ }
979
+ getFloatingWindowTitle(floating) {
980
+ const fallback = 'Floating Pane';
981
+ if (!floating || !floating.root) {
982
+ return fallback;
983
+ }
984
+ const preferred = floating.activePane ?? this.findFirstPaneName(floating.root);
985
+ if (!preferred) {
986
+ return fallback;
987
+ }
988
+ const owningStack = this.findStackContainingPane(floating.root, preferred);
989
+ if (!owningStack) {
990
+ return preferred ?? fallback;
991
+ }
992
+ return owningStack.titles?.[preferred] ?? preferred ?? fallback;
993
+ }
994
+ updateFloatingWindowTitle(index) {
995
+ const floating = this.floatingLayouts[index];
996
+ if (!floating) {
997
+ return;
998
+ }
999
+ const selector = `.dock-floating[data-path="${this.formatPath({
1000
+ type: 'floating',
1001
+ index,
1002
+ segments: [],
1003
+ })}"]`;
1004
+ const wrapper = this.floatingLayerEl.querySelector(selector);
1005
+ if (!wrapper) {
1006
+ return;
467
1007
  }
468
- else if (host instanceof BsTabGroupPane) {
469
- const matching = host.panes.filter(p => p.dockPanel === panel);
470
- if (matching.length > 0) {
471
- host.panes.splice(host.panes.findIndex(p => p.dockPanel === panel), 1);
472
- return { paneRemoved: true, hostIsEmpty: host.panes.length === 0 };
1008
+ const titleEl = wrapper.querySelector('.dock-floating__title');
1009
+ if (!titleEl) {
1010
+ return;
1011
+ }
1012
+ titleEl.textContent = this.getFloatingWindowTitle(floating);
1013
+ }
1014
+ renderSplit(node, path, floatingIndex) {
1015
+ const container = this.documentRef.createElement('div');
1016
+ container.classList.add('dock-split');
1017
+ container.dataset['direction'] = node.direction;
1018
+ container.dataset['path'] = path.join('/');
1019
+ const sizes = Array.isArray(node.sizes) ? node.sizes : [];
1020
+ node.children.forEach((child, index) => {
1021
+ const childWrapper = this.documentRef.createElement('div');
1022
+ childWrapper.classList.add('dock-split__child');
1023
+ childWrapper.dataset['index'] = String(index);
1024
+ const size = sizes[index];
1025
+ if (typeof size === 'number' && Number.isFinite(size)) {
1026
+ childWrapper.style.flex = `${Math.max(size, 0)} 1 0`;
473
1027
  }
474
1028
  else {
475
- // ATM. all panes are ContentPanes anyway.
476
- // So unless you'd want to have splitters inside the tabs,
477
- // This code will not be hit.
478
- // const result = host.panes
479
- // .map(parentPane => this.removeFromPane(parentPane, panel))
480
- // .filter(r => r.paneRemoved);
481
- //
482
- // if (result.length > 0) {
483
- // return { paneRemoved: true, hostIsEmpty: }
484
- // }
485
- return { paneRemoved: false, hostIsEmpty: host.panes.length === 0 };
486
- }
487
- }
488
- else if (host instanceof BsSplitPane) {
489
- const matching = host.panes
490
- .filter(p => p instanceof BsContentPane)
491
- .map(p => p)
492
- .filter(p => p.dockPanel === panel);
493
- if (matching.length > 0) {
494
- host.panes.splice(host.panes.findIndex(p => (p instanceof BsContentPane) && matching.includes(p)), 1);
495
- // TODO: Remove splitter if only 1 pane left?
496
- return { paneRemoved: true, hostIsEmpty: host.panes.length === 0 };
1029
+ childWrapper.style.flex = '1 1 0';
497
1030
  }
498
- else {
499
- for (let splitPane of host.panes) {
500
- const result = this.removeFromPane(splitPane, panel);
501
- if (result.paneRemoved && result.hostIsEmpty) {
502
- // splitPane is empty now, so we can remove it from this splitter
503
- host.panes.splice(host.panes.indexOf(splitPane), 1);
504
- return { paneRemoved: true, hostIsEmpty: host.panes.length === 0 };
505
- }
1031
+ childWrapper.appendChild(this.renderNode(child, [...path, index], floatingIndex));
1032
+ container.appendChild(childWrapper);
1033
+ if (index < node.children.length - 1) {
1034
+ const divider = this.documentRef.createElement('div');
1035
+ divider.classList.add('dock-split__divider');
1036
+ divider.setAttribute('role', 'separator');
1037
+ divider.tabIndex = 0;
1038
+ divider.addEventListener('pointerdown', (event) => this.beginResize(event, container, floatingIndex !== undefined
1039
+ ? { type: 'floating', index: floatingIndex, segments: [...path] }
1040
+ : { type: 'docked', segments: [...path] }, index));
1041
+ container.appendChild(divider);
1042
+ }
1043
+ });
1044
+ return container;
1045
+ }
1046
+ renderStack(node, path, floatingIndex) {
1047
+ const stack = this.documentRef.createElement('div');
1048
+ stack.classList.add('dock-stack');
1049
+ const location = typeof floatingIndex === 'number'
1050
+ ? { type: 'floating', index: floatingIndex, segments: [...path] }
1051
+ : { type: 'docked', segments: [...path] };
1052
+ stack.dataset['path'] = this.formatPath(location);
1053
+ const header = this.documentRef.createElement('div');
1054
+ header.classList.add('dock-stack__header');
1055
+ header.setAttribute('role', 'tablist');
1056
+ const content = this.documentRef.createElement('div');
1057
+ content.classList.add('dock-stack__content');
1058
+ const panes = Array.from(new Set(node.panes));
1059
+ if (panes.length === 0) {
1060
+ const empty = this.documentRef.createElement('div');
1061
+ empty.classList.add('dock-stack__pane');
1062
+ empty.textContent = 'No panes configured';
1063
+ content.appendChild(empty);
1064
+ stack.append(header, content);
1065
+ return stack;
1066
+ }
1067
+ const activePane = panes.includes(node.activePane ?? '')
1068
+ ? node.activePane
1069
+ : panes[0];
1070
+ const baseSlug = path.length ? path.join('-') : 'root';
1071
+ const pathSlug = typeof floatingIndex === 'number' ? `f${floatingIndex}-${baseSlug}` : baseSlug;
1072
+ panes.forEach((paneName) => {
1073
+ const paneSlugRaw = paneName.replace(/[^a-zA-Z0-9_-]/g, '-');
1074
+ const paneSlug = paneSlugRaw.length > 0 ? paneSlugRaw : 'pane';
1075
+ const tabId = `${this.instanceId}-tab-${pathSlug}-${paneSlug}`;
1076
+ const panelId = `${this.instanceId}-panel-${pathSlug}-${paneSlug}`;
1077
+ const button = this.documentRef.createElement('button');
1078
+ button.type = 'button';
1079
+ button.classList.add('dock-tab');
1080
+ button.dataset['pane'] = paneName;
1081
+ button.id = tabId;
1082
+ button.textContent = node.titles?.[paneName] ?? paneName;
1083
+ button.setAttribute('role', 'tab');
1084
+ button.setAttribute('aria-controls', panelId);
1085
+ if (paneName === activePane) {
1086
+ button.classList.add('dock-tab--active');
1087
+ }
1088
+ button.setAttribute('aria-selected', String(paneName === activePane));
1089
+ button.draggable = true;
1090
+ button.addEventListener('pointerdown', (event) => {
1091
+ const stackEl = button.closest('.dock-stack');
1092
+ this.captureTabDragMetrics(event, stackEl ?? null);
1093
+ event.stopPropagation();
1094
+ });
1095
+ button.addEventListener('pointerup', () => this.clearPendingTabDragMetrics());
1096
+ button.addEventListener('pointercancel', () => this.clearPendingTabDragMetrics());
1097
+ button.addEventListener('dragstart', (event) => {
1098
+ const stackEl = button.closest('.dock-stack');
1099
+ this.beginPaneDrag(event, this.clonePath(location), paneName, stackEl ?? null);
1100
+ });
1101
+ button.addEventListener('dragend', () => {
1102
+ this.endPaneDrag();
1103
+ this.clearPendingTabDragMetrics();
1104
+ });
1105
+ button.addEventListener('click', () => {
1106
+ this.activatePane(stack, paneName, this.clonePath(location));
1107
+ this.dispatchEvent(new CustomEvent('dock-pane-activated', {
1108
+ detail: { pane: paneName },
1109
+ bubbles: true,
1110
+ composed: true,
1111
+ }));
1112
+ });
1113
+ header.appendChild(button);
1114
+ const paneHost = this.documentRef.createElement('div');
1115
+ paneHost.classList.add('dock-stack__pane');
1116
+ paneHost.dataset['pane'] = paneName;
1117
+ paneHost.id = panelId;
1118
+ paneHost.setAttribute('role', 'tabpanel');
1119
+ paneHost.setAttribute('aria-labelledby', tabId);
1120
+ if (paneName !== activePane) {
1121
+ paneHost.setAttribute('hidden', '');
1122
+ }
1123
+ const slotEl = this.documentRef.createElement('slot');
1124
+ slotEl.name = paneName;
1125
+ paneHost.appendChild(slotEl);
1126
+ content.appendChild(paneHost);
1127
+ });
1128
+ stack.dataset['activePane'] = activePane;
1129
+ stack.append(header, content);
1130
+ return stack;
1131
+ }
1132
+ beginResize(event, container, path, index) {
1133
+ event.preventDefault();
1134
+ const divider = event.currentTarget;
1135
+ if (!divider) {
1136
+ return;
1137
+ }
1138
+ const orientation = container.dataset['direction'] ?? 'horizontal';
1139
+ const children = Array.from(container.querySelectorAll(':scope > .dock-split__child'));
1140
+ const initialSizes = children.map((child) => {
1141
+ const rect = child.getBoundingClientRect();
1142
+ return orientation === 'horizontal' ? rect.width : rect.height;
1143
+ });
1144
+ const beforeSize = initialSizes[index];
1145
+ const afterSize = initialSizes[index + 1];
1146
+ const startPos = orientation === 'horizontal' ? event.clientX : event.clientY;
1147
+ divider.setPointerCapture(event.pointerId);
1148
+ divider.dataset['resizing'] = 'true';
1149
+ this.resizeState = {
1150
+ path: this.clonePath(path),
1151
+ index,
1152
+ pointerId: event.pointerId,
1153
+ orientation,
1154
+ container,
1155
+ divider,
1156
+ startPos,
1157
+ initialSizes,
1158
+ beforeSize,
1159
+ afterSize,
1160
+ };
1161
+ this.startPointerTracking();
1162
+ }
1163
+ onPointerMove(event) {
1164
+ if (this.resizeState && event.pointerId === this.resizeState.pointerId) {
1165
+ const state = this.resizeState;
1166
+ const splitNode = this.resolveSplitNode(state.path);
1167
+ if (!splitNode) {
1168
+ return;
1169
+ }
1170
+ const currentPos = state.orientation === 'horizontal' ? event.clientX : event.clientY;
1171
+ const delta = currentPos - state.startPos;
1172
+ const minSize = 48;
1173
+ const pairTotal = state.beforeSize + state.afterSize;
1174
+ let newBefore = Math.min(Math.max(state.beforeSize + delta, minSize), pairTotal - minSize);
1175
+ let newAfter = pairTotal - newBefore;
1176
+ if (!Number.isFinite(newBefore) || !Number.isFinite(newAfter)) {
1177
+ return;
1178
+ }
1179
+ if (newAfter < minSize) {
1180
+ newAfter = minSize;
1181
+ newBefore = pairTotal - minSize;
1182
+ }
1183
+ const newSizesPixels = [...state.initialSizes];
1184
+ newSizesPixels[state.index] = newBefore;
1185
+ newSizesPixels[state.index + 1] = newAfter;
1186
+ const total = newSizesPixels.reduce((acc, size) => acc + size, 0);
1187
+ const normalized = total > 0 ? newSizesPixels.map((size) => size / total) : [];
1188
+ splitNode.sizes = normalized;
1189
+ const children = Array.from(state.container.querySelectorAll(':scope > .dock-split__child'));
1190
+ normalized.forEach((size, idx) => {
1191
+ if (children[idx]) {
1192
+ children[idx].style.flex = `${Math.max(size, 0)} 1 0`;
1193
+ }
1194
+ });
1195
+ this.dispatchLayoutChanged();
1196
+ }
1197
+ if (this.floatingResizeState && event.pointerId === this.floatingResizeState.pointerId) {
1198
+ this.handleFloatingResizeMove(event);
1199
+ }
1200
+ if (this.floatingDragState && event.pointerId === this.floatingDragState.pointerId) {
1201
+ console.warn('state', this.floatingDragState);
1202
+ this.handleFloatingDragMove(event);
1203
+ }
1204
+ }
1205
+ onPointerUp(event) {
1206
+ if (this.resizeState && event.pointerId === this.resizeState.pointerId) {
1207
+ const divider = this.resizeState.divider;
1208
+ divider.dataset['resizing'] = 'false';
1209
+ divider.releasePointerCapture(this.resizeState.pointerId);
1210
+ this.resizeState = null;
1211
+ }
1212
+ if (this.floatingDragState && event.pointerId === this.floatingDragState.pointerId) {
1213
+ this.endFloatingDrag(event.pointerId);
1214
+ }
1215
+ if (this.floatingResizeState && event.pointerId === this.floatingResizeState.pointerId) {
1216
+ this.endFloatingResize(event.pointerId);
1217
+ }
1218
+ this.stopPointerTrackingIfIdle();
1219
+ }
1220
+ captureTabDragMetrics(event, stackEl) {
1221
+ if (!stackEl) {
1222
+ this.pendingTabDragMetrics = null;
1223
+ return;
1224
+ }
1225
+ if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) {
1226
+ this.pendingTabDragMetrics = null;
1227
+ return;
1228
+ }
1229
+ const hostRect = this.getBoundingClientRect();
1230
+ const stackRect = stackEl.getBoundingClientRect();
1231
+ const pointerOffsetX = event.clientX - stackRect.left;
1232
+ const pointerOffsetY = event.clientY - stackRect.top;
1233
+ const left = stackRect.left - hostRect.left;
1234
+ const top = stackRect.top - hostRect.top;
1235
+ const width = Number.isFinite(stackRect.width) ? stackRect.width : 0;
1236
+ const height = Number.isFinite(stackRect.height) ? stackRect.height : 0;
1237
+ this.pendingTabDragMetrics = {
1238
+ pointerOffsetX,
1239
+ pointerOffsetY,
1240
+ left,
1241
+ top,
1242
+ width,
1243
+ height,
1244
+ };
1245
+ }
1246
+ clearPendingTabDragMetrics() {
1247
+ this.pendingTabDragMetrics = null;
1248
+ }
1249
+ beginPaneDrag(event, path, pane, stackEl) {
1250
+ if (!event.dataTransfer) {
1251
+ return;
1252
+ }
1253
+ // Create a ghost element for the drag image. This prevents the browser from cancelling
1254
+ // the drag operation when the original element is removed from the DOM during re-render.
1255
+ const ghost = event.currentTarget.cloneNode(true);
1256
+ ghost.style.position = 'absolute';
1257
+ ghost.style.left = '-9999px';
1258
+ ghost.style.top = '-9999px';
1259
+ ghost.style.width = `${event.currentTarget.offsetWidth}px`;
1260
+ ghost.style.height = `${event.currentTarget.offsetHeight}px`;
1261
+ this.shadowRoot?.appendChild(ghost);
1262
+ // Use the ghost element as the drag image.
1263
+ // The offset is set to where the user's cursor is on the original element.
1264
+ event.dataTransfer.setDragImage(ghost, event.offsetX, event.offsetY);
1265
+ // The ghost element is no longer needed after the drag image is set.
1266
+ // We defer its removal to ensure the browser has captured it.
1267
+ setTimeout(() => ghost.remove(), 0);
1268
+ const { path: sourcePath, floatingIndex, pointerOffsetX, pointerOffsetY, } = this.preparePaneDragSource(path, pane, stackEl, event);
1269
+ this.dragState = {
1270
+ pane,
1271
+ sourcePath: this.clonePath(sourcePath),
1272
+ floatingIndex,
1273
+ pointerOffsetX,
1274
+ pointerOffsetY,
1275
+ dropHandled: false,
1276
+ };
1277
+ this.updateDraggedFloatingPosition(event);
1278
+ this.startDragPointerTracking();
1279
+ event.dataTransfer.effectAllowed = 'move';
1280
+ event.dataTransfer.setData('text/plain', pane);
1281
+ }
1282
+ preparePaneDragSource(path, pane, stackEl, event) {
1283
+ const location = this.resolveStackLocation(path);
1284
+ if (!location || !location.node.panes.includes(pane)) {
1285
+ this.clearPendingTabDragMetrics();
1286
+ return {
1287
+ path,
1288
+ floatingIndex: null,
1289
+ pointerOffsetX: 0,
1290
+ pointerOffsetY: 0,
1291
+ };
1292
+ }
1293
+ const metrics = this.pendingTabDragMetrics;
1294
+ this.pendingTabDragMetrics = null;
1295
+ const domHasSibling = !!stackEl &&
1296
+ Array.from(stackEl.querySelectorAll('.dock-tab')).some((button) => button.dataset['pane'] && button.dataset['pane'] !== pane);
1297
+ const hasSiblingInStack = location.node.panes.some((existing) => existing !== pane);
1298
+ const floating = location.context === 'floating' ? this.floatingLayouts[location.index] : null;
1299
+ const floatingPaneCount = floating && floating.root ? this.countPanesInTree(floating.root) : hasSiblingInStack ? 2 : 1;
1300
+ const shouldReuseExistingWindow = location.context === 'floating' &&
1301
+ !!floating &&
1302
+ !hasSiblingInStack &&
1303
+ !domHasSibling &&
1304
+ floatingPaneCount <= 1;
1305
+ if (shouldReuseExistingWindow) {
1306
+ if (floating) {
1307
+ floating.activePane = pane;
1308
+ this.updateFloatingWindowTitle(location.index);
1309
+ const wrapper = this.getFloatingWrapper(location.index);
1310
+ if (wrapper) {
1311
+ this.promoteFloatingPane(location.index, wrapper);
506
1312
  }
1313
+ const hostRect = this.getBoundingClientRect();
1314
+ const pointerOffsetX = metrics &&
1315
+ Number.isFinite(metrics.pointerOffsetX) &&
1316
+ Number.isFinite(metrics.left) &&
1317
+ Number.isFinite(floating.bounds.left)
1318
+ ? metrics.pointerOffsetX + (metrics.left - floating.bounds.left)
1319
+ : Number.isFinite(event.clientX) && Number.isFinite(floating.bounds.left)
1320
+ ? event.clientX - hostRect.left - floating.bounds.left
1321
+ : floating.bounds.width / 2;
1322
+ const pointerOffsetY = metrics &&
1323
+ Number.isFinite(metrics.pointerOffsetY) &&
1324
+ Number.isFinite(metrics.top) &&
1325
+ Number.isFinite(floating.bounds.top)
1326
+ ? metrics.pointerOffsetY + (metrics.top - floating.bounds.top)
1327
+ : Number.isFinite(event.clientY) && Number.isFinite(floating.bounds.top)
1328
+ ? event.clientY - hostRect.top - floating.bounds.top
1329
+ : floating.bounds.height / 2;
1330
+ return {
1331
+ path,
1332
+ floatingIndex: location.index,
1333
+ pointerOffsetX,
1334
+ pointerOffsetY,
1335
+ };
507
1336
  }
508
- return { paneRemoved: false, hostIsEmpty: host.panes.length === 0 };
1337
+ return {
1338
+ path,
1339
+ floatingIndex: null,
1340
+ pointerOffsetX: 0,
1341
+ pointerOffsetY: 0,
1342
+ };
509
1343
  }
510
- else {
511
- throw 'unknown host type';
1344
+ const hostRect = this.getBoundingClientRect();
1345
+ const stackRect = stackEl?.getBoundingClientRect() ?? null;
1346
+ const fallbackWidth = 320;
1347
+ const fallbackHeight = 240;
1348
+ const width = metrics && Number.isFinite(metrics.width) && metrics.width > 0
1349
+ ? metrics.width
1350
+ : stackRect && Number.isFinite(stackRect.width)
1351
+ ? stackRect.width
1352
+ : fallbackWidth;
1353
+ const height = metrics && Number.isFinite(metrics.height) && metrics.height > 0
1354
+ ? metrics.height
1355
+ : stackRect && Number.isFinite(stackRect.height)
1356
+ ? stackRect.height
1357
+ : fallbackHeight;
1358
+ const pointerOffsetX = metrics && Number.isFinite(metrics.pointerOffsetX)
1359
+ ? metrics.pointerOffsetX
1360
+ : stackRect && Number.isFinite(event.clientX)
1361
+ ? event.clientX - stackRect.left
1362
+ : width / 2;
1363
+ const pointerOffsetY = metrics && Number.isFinite(metrics.pointerOffsetY)
1364
+ ? metrics.pointerOffsetY
1365
+ : stackRect && Number.isFinite(event.clientY)
1366
+ ? event.clientY - stackRect.top
1367
+ : height / 2;
1368
+ const pointerLeft = metrics && Number.isFinite(metrics.left)
1369
+ ? metrics.left
1370
+ : Number.isFinite(event.clientX)
1371
+ ? event.clientX - hostRect.left - pointerOffsetX
1372
+ : null;
1373
+ const pointerTop = metrics && Number.isFinite(metrics.top)
1374
+ ? metrics.top
1375
+ : Number.isFinite(event.clientY)
1376
+ ? event.clientY - hostRect.top - pointerOffsetY
1377
+ : null;
1378
+ const left = pointerLeft !== null
1379
+ ? pointerLeft
1380
+ : stackRect
1381
+ ? stackRect.left - hostRect.left
1382
+ : (hostRect.width - width) / 2;
1383
+ const top = pointerTop !== null
1384
+ ? pointerTop
1385
+ : stackRect
1386
+ ? stackRect.top - hostRect.top
1387
+ : (hostRect.height - height) / 2;
1388
+ // Defer the DOM modification to prevent the browser from cancelling the drag operation.
1389
+ setTimeout(() => {
1390
+ const paneTitle = location.node.titles?.[pane];
1391
+ this.removePaneFromLocation(location, pane);
1392
+ const floatingStack = {
1393
+ kind: 'stack',
1394
+ panes: [pane],
1395
+ activePane: pane,
1396
+ };
1397
+ if (paneTitle) {
1398
+ floatingStack.titles = { [pane]: paneTitle };
1399
+ }
1400
+ const floatingLayout = {
1401
+ bounds: {
1402
+ left,
1403
+ top,
1404
+ width,
1405
+ height,
1406
+ },
1407
+ root: floatingStack,
1408
+ activePane: pane,
1409
+ };
1410
+ if (paneTitle) {
1411
+ floatingLayout.titles = { [pane]: paneTitle };
1412
+ }
1413
+ this.floatingLayouts.push(floatingLayout);
1414
+ const floatingIndex = this.floatingLayouts.length - 1;
1415
+ // Defer rendering to avoid interrupting the drag-and-drop initialization in the browser.
1416
+ // Synchronously re-rendering can cause the browser to lose track of the drag operation.
1417
+ this.render();
1418
+ const wrapper = this.getFloatingWrapper(floatingIndex);
1419
+ if (wrapper) {
1420
+ this.promoteFloatingPane(floatingIndex, wrapper);
1421
+ }
1422
+ this.dispatchLayoutChanged();
1423
+ if (this.dragState) {
1424
+ this.dragState.sourcePath = { type: 'floating', index: floatingIndex, segments: [] };
1425
+ this.dragState.floatingIndex = floatingIndex;
1426
+ }
1427
+ }, 0);
1428
+ return {
1429
+ path: { type: 'floating', index: -1, segments: [] }, // Placeholder path
1430
+ floatingIndex: -1,
1431
+ pointerOffsetX,
1432
+ pointerOffsetY,
1433
+ };
1434
+ }
1435
+ endPaneDrag() {
1436
+ this.clearPendingDragEndTimeout();
1437
+ const state = this.dragState;
1438
+ this.dragState = null;
1439
+ this.hideDropIndicator();
1440
+ this.stopDragPointerTracking();
1441
+ this.lastDragPointerPosition = null;
1442
+ if (state && state.floatingIndex !== null && !state.dropHandled) {
1443
+ this.dispatchLayoutChanged();
1444
+ }
1445
+ }
1446
+ onDragOver(event) {
1447
+ if (!this.dragState) {
1448
+ return;
512
1449
  }
1450
+ event.preventDefault();
1451
+ this.updateDraggedFloatingPosition(event);
1452
+ if (event.dataTransfer) {
1453
+ event.dataTransfer.dropEffect = 'move';
1454
+ }
1455
+ const stack = this.findStackElement(event);
1456
+ if (!stack) {
1457
+ this.hideDropIndicator();
1458
+ return;
1459
+ }
1460
+ const path = this.parsePath(stack.dataset['path']);
1461
+ const zone = this.computeDropZone(stack, event, this.extractDropZoneFromEvent(event));
1462
+ this.showDropIndicator(stack, zone);
513
1463
  }
514
- onMouseUp(ev) {
515
- this.isMouseDown = false;
516
- this.isDragging = false;
517
- this.dragOperation = undefined;
1464
+ updateDraggedFloatingPosition(event) {
1465
+ if (!this.dragState) {
1466
+ return;
1467
+ }
1468
+ const { clientX, clientY, screenX, screenY } = event;
1469
+ const hasValidCoordinates = Number.isFinite(clientX) &&
1470
+ Number.isFinite(clientY) &&
1471
+ !(clientX === 0 && clientY === 0 && screenX === 0 && screenY === 0);
1472
+ if (hasValidCoordinates) {
1473
+ this.lastDragPointerPosition = { x: clientX, y: clientY };
1474
+ this.updateDraggedFloatingPositionFromPoint(clientX, clientY);
1475
+ return;
1476
+ }
1477
+ if (this.lastDragPointerPosition) {
1478
+ const { x, y } = this.lastDragPointerPosition;
1479
+ this.updateDraggedFloatingPositionFromPoint(x, y);
1480
+ }
518
1481
  }
519
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockPanelHeaderComponent, deps: [{ token: BsDockPanelComponent }, { token: BsDockComponent }, { token: BsDockService }, { token: i0.ElementRef }, { token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component }); }
520
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: BsDockPanelHeaderComponent, isStandalone: false, selector: "bs-dock-panel-header", host: { listeners: { "mousedown": "onMouseDown($event)", "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp($event)" }, properties: { "class.d-block": "this.dBlock" } }, ngImport: i0, template: "<ng-content></ng-content>", styles: [":host{padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);margin:calc(-1 * var(--bs-nav-link-padding-y)) calc(-1 * var(--bs-nav-link-padding-x))}\n"] }); }
521
- }
522
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockPanelHeaderComponent, decorators: [{
523
- type: Component,
524
- args: [{ selector: 'bs-dock-panel-header', standalone: false, template: "<ng-content></ng-content>", styles: [":host{padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);margin:calc(-1 * var(--bs-nav-link-padding-y)) calc(-1 * var(--bs-nav-link-padding-x))}\n"] }]
525
- }], ctorParameters: () => [{ type: BsDockPanelComponent }, { type: BsDockComponent }, { type: BsDockService }, { type: i0.ElementRef }, { type: i0.DestroyRef }], propDecorators: { onMouseDown: [{
526
- type: HostListener,
527
- args: ['mousedown', ['$event']]
528
- }], onMouseMove: [{
529
- type: HostListener,
530
- args: ['document:mousemove', ['$event']]
531
- }], onMouseUp: [{
532
- type: HostListener,
533
- args: ['document:mouseup', ['$event']]
534
- }], dBlock: [{
535
- type: HostBinding,
536
- args: ['class.d-block']
1482
+ onGlobalDragOver(event) {
1483
+ if (!this.dragState) {
1484
+ return;
1485
+ }
1486
+ this.updateDraggedFloatingPosition(event);
1487
+ }
1488
+ onDrag(event) {
1489
+ if (!this.dragState) {
1490
+ return;
1491
+ }
1492
+ this.updateDraggedFloatingPosition(event);
1493
+ }
1494
+ onGlobalDragEnd() {
1495
+ this.hideDropIndicator();
1496
+ if (!this.dragState) {
1497
+ this.clearPendingTabDragMetrics();
1498
+ return;
1499
+ }
1500
+ this.endPaneDrag();
1501
+ this.clearPendingTabDragMetrics();
1502
+ }
1503
+ updateDraggedFloatingPositionFromPoint(clientX, clientY) {
1504
+ if (!this.dragState) {
1505
+ return;
1506
+ }
1507
+ if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
1508
+ return;
1509
+ }
1510
+ this.updatePaneDragDropTargetFromPoint(clientX, clientY);
1511
+ const { floatingIndex, pointerOffsetX, pointerOffsetY } = this.dragState;
1512
+ if (floatingIndex === null || floatingIndex < 0) {
1513
+ return;
1514
+ }
1515
+ const floating = this.floatingLayouts[floatingIndex];
1516
+ if (!floating) {
1517
+ return;
1518
+ }
1519
+ const hostRect = this.getBoundingClientRect();
1520
+ const newLeft = clientX - hostRect.left - pointerOffsetX;
1521
+ const newTop = clientY - hostRect.top - pointerOffsetY;
1522
+ floating.bounds.left = newLeft;
1523
+ floating.bounds.top = newTop;
1524
+ const wrapper = this.getFloatingWrapper(floatingIndex);
1525
+ if (wrapper) {
1526
+ wrapper.style.left = `${newLeft}px`;
1527
+ wrapper.style.top = `${newTop}px`;
1528
+ }
1529
+ }
1530
+ updatePaneDragDropTargetFromPoint(clientX, clientY) {
1531
+ if (!this.dragState) {
1532
+ return;
1533
+ }
1534
+ const stack = this.findStackAtPoint(clientX, clientY);
1535
+ if (!stack) {
1536
+ this.hideDropIndicator();
1537
+ return;
1538
+ }
1539
+ const path = this.parsePath(stack.dataset['path']);
1540
+ if (!path) {
1541
+ this.hideDropIndicator();
1542
+ return;
1543
+ }
1544
+ const zoneHint = this.findDropZoneByPoint(clientX, clientY);
1545
+ const zone = this.computeDropZone(stack, { clientX, clientY }, zoneHint);
1546
+ this.showDropIndicator(stack, zone);
1547
+ }
1548
+ startDragPointerTracking() {
1549
+ if (this.dragPointerTrackingActive) {
1550
+ return;
1551
+ }
1552
+ this.lastDragPointerPosition = null;
1553
+ const win = this.windowRef;
1554
+ win?.addEventListener('mousemove', this.onDragMouseMove, true);
1555
+ win?.addEventListener('touchmove', this.onDragTouchMove, { passive: false });
1556
+ win?.addEventListener('mouseup', this.onDragMouseUp, true);
1557
+ win?.addEventListener('touchend', this.onDragTouchEnd, true);
1558
+ win?.addEventListener('touchcancel', this.onDragTouchEnd, true);
1559
+ this.dragPointerTrackingActive = true;
1560
+ }
1561
+ stopDragPointerTracking() {
1562
+ if (!this.dragPointerTrackingActive) {
1563
+ return;
1564
+ }
1565
+ const win = this.windowRef;
1566
+ win?.removeEventListener('mousemove', this.onDragMouseMove, true);
1567
+ win?.removeEventListener('touchmove', this.onDragTouchMove);
1568
+ win?.removeEventListener('mouseup', this.onDragMouseUp, true);
1569
+ win?.removeEventListener('touchend', this.onDragTouchEnd, true);
1570
+ win?.removeEventListener('touchcancel', this.onDragTouchEnd, true);
1571
+ this.dragPointerTrackingActive = false;
1572
+ this.lastDragPointerPosition = null;
1573
+ this.clearPendingDragEndTimeout();
1574
+ }
1575
+ onDragMouseMove(event) {
1576
+ if (!this.dragState) {
1577
+ this.stopDragPointerTracking();
1578
+ return;
1579
+ }
1580
+ if (event.buttons === 0) {
1581
+ this.scheduleDeferredDragEnd();
1582
+ return;
1583
+ }
1584
+ this.lastDragPointerPosition = { x: event.clientX, y: event.clientY };
1585
+ this.updateDraggedFloatingPositionFromPoint(event.clientX, event.clientY);
1586
+ }
1587
+ onDragTouchMove(event) {
1588
+ if (!this.dragState) {
1589
+ this.stopDragPointerTracking();
1590
+ return;
1591
+ }
1592
+ const touch = event.touches[0];
1593
+ if (!touch) {
1594
+ this.scheduleDeferredDragEnd();
1595
+ return;
1596
+ }
1597
+ event.preventDefault();
1598
+ this.lastDragPointerPosition = { x: touch.clientX, y: touch.clientY };
1599
+ this.updateDraggedFloatingPositionFromPoint(touch.clientX, touch.clientY);
1600
+ }
1601
+ onDragMouseUp() {
1602
+ if (!this.dragState) {
1603
+ this.stopDragPointerTracking();
1604
+ return;
1605
+ }
1606
+ this.scheduleDeferredDragEnd();
1607
+ }
1608
+ onDragTouchEnd() {
1609
+ if (!this.dragState) {
1610
+ this.stopDragPointerTracking();
1611
+ return;
1612
+ }
1613
+ this.scheduleDeferredDragEnd();
1614
+ }
1615
+ clearPendingDragEndTimeout() {
1616
+ if (this.pendingDragEndTimeout !== null) {
1617
+ const win = this.windowRef;
1618
+ if (win && typeof this.pendingDragEndTimeout === 'number') {
1619
+ win.clearTimeout(this.pendingDragEndTimeout);
1620
+ }
1621
+ else {
1622
+ globalThis.clearTimeout(this.pendingDragEndTimeout);
1623
+ }
1624
+ this.pendingDragEndTimeout = null;
1625
+ }
1626
+ }
1627
+ scheduleDeferredDragEnd() {
1628
+ if (this.pendingDragEndTimeout !== null) {
1629
+ return;
1630
+ }
1631
+ const completeDrag = () => {
1632
+ this.pendingDragEndTimeout = null;
1633
+ this.hideDropIndicator();
1634
+ if (!this.dragState) {
1635
+ this.clearPendingTabDragMetrics();
1636
+ return;
1637
+ }
1638
+ this.endPaneDrag();
1639
+ this.clearPendingTabDragMetrics();
1640
+ };
1641
+ const win = this.windowRef;
1642
+ this.pendingDragEndTimeout = win
1643
+ ? win.setTimeout(completeDrag, 0)
1644
+ : setTimeout(completeDrag, 0);
1645
+ }
1646
+ onDrop(event) {
1647
+ if (!this.dragState) {
1648
+ return;
1649
+ }
1650
+ event.preventDefault();
1651
+ const pointFromEvent = Number.isFinite(event.clientX) && Number.isFinite(event.clientY)
1652
+ ? { clientX: event.clientX, clientY: event.clientY }
1653
+ : null;
1654
+ const point = pointFromEvent ??
1655
+ (this.lastDragPointerPosition
1656
+ ? {
1657
+ clientX: this.lastDragPointerPosition.x,
1658
+ clientY: this.lastDragPointerPosition.y,
1659
+ }
1660
+ : null);
1661
+ const stack = this.findStackElement(event) ??
1662
+ (point ? this.findStackAtPoint(point.clientX, point.clientY) : null);
1663
+ if (!stack) {
1664
+ this.hideDropIndicator();
1665
+ this.endPaneDrag();
1666
+ return;
1667
+ }
1668
+ const path = this.parsePath(stack.dataset['path']);
1669
+ const eventZoneHint = this.extractDropZoneFromEvent(event);
1670
+ const pointZoneHint = point ? this.findDropZoneByPoint(point.clientX, point.clientY) : null;
1671
+ const zone = this.computeDropZone(stack, point ?? event, pointZoneHint ?? eventZoneHint);
1672
+ if (!zone) {
1673
+ this.hideDropIndicator();
1674
+ this.endPaneDrag();
1675
+ return;
1676
+ }
1677
+ this.handleDrop(path, zone);
1678
+ this.endPaneDrag();
1679
+ }
1680
+ onDragLeave(event) {
1681
+ const related = event.relatedTarget;
1682
+ if (!related) {
1683
+ this.hideDropIndicator();
1684
+ return;
1685
+ }
1686
+ const rootContains = this.rootEl.contains(related);
1687
+ const joystickContains = this.dropJoystick.contains(related);
1688
+ if (!rootContains && !joystickContains) {
1689
+ this.hideDropIndicator();
1690
+ }
1691
+ }
1692
+ handleDrop(targetPath, zone) {
1693
+ if (!this.dragState || !targetPath) {
1694
+ return;
1695
+ }
1696
+ const { pane, sourcePath } = this.dragState;
1697
+ const source = this.resolveStackLocation(sourcePath);
1698
+ const target = this.resolveStackLocation(targetPath);
1699
+ if (!source || !target) {
1700
+ return;
1701
+ }
1702
+ if (zone === 'center' && this.pathsEqual(sourcePath, targetPath)) {
1703
+ if (!source.node.panes.includes(pane)) {
1704
+ return;
1705
+ }
1706
+ this.reorderPaneInLocation(source, pane);
1707
+ this.render();
1708
+ this.dispatchLayoutChanged();
1709
+ if (this.dragState) {
1710
+ this.dragState.dropHandled = true;
1711
+ }
1712
+ return;
1713
+ }
1714
+ const paneTitle = source.node.titles?.[pane];
1715
+ const stackEmptied = this.removePaneFromLocation(source, pane, true);
1716
+ if (zone === 'center') {
1717
+ this.addPaneToLocation(target, pane);
1718
+ this.setActivePaneForLocation(target, pane);
1719
+ if (paneTitle) {
1720
+ target.node.titles = target.node.titles ? { ...target.node.titles } : {};
1721
+ target.node.titles[pane] = paneTitle;
1722
+ }
1723
+ if (stackEmptied) {
1724
+ this.cleanupLocation(source);
1725
+ }
1726
+ this.render();
1727
+ this.dispatchLayoutChanged();
1728
+ if (this.dragState) {
1729
+ this.dragState.dropHandled = true;
1730
+ }
1731
+ return;
1732
+ }
1733
+ const newStack = {
1734
+ kind: 'stack',
1735
+ panes: [pane],
1736
+ activePane: pane,
1737
+ };
1738
+ if (paneTitle) {
1739
+ newStack.titles = { [pane]: paneTitle };
1740
+ }
1741
+ if (target.context === 'docked') {
1742
+ this.rootLayout = this.dockNodeBeside(this.rootLayout, target.node, newStack, zone);
1743
+ }
1744
+ else {
1745
+ const floating = this.floatingLayouts[target.index];
1746
+ if (!floating) {
1747
+ if (stackEmptied) {
1748
+ this.cleanupLocation(source);
1749
+ }
1750
+ this.render();
1751
+ this.dispatchLayoutChanged();
1752
+ return;
1753
+ }
1754
+ floating.root = this.dockNodeBeside(floating.root, target.node, newStack, zone);
1755
+ floating.activePane = pane;
1756
+ }
1757
+ if (stackEmptied) {
1758
+ this.cleanupLocation(source);
1759
+ }
1760
+ this.render();
1761
+ this.dispatchLayoutChanged();
1762
+ if (this.dragState) {
1763
+ this.dragState.dropHandled = true;
1764
+ }
1765
+ }
1766
+ handleFloatingStackDrop(sourceIndex, targetPath, zone) {
1767
+ const source = this.floatingLayouts[sourceIndex];
1768
+ if (!source || !source.root) {
1769
+ return false;
1770
+ }
1771
+ const target = this.resolveStackLocation(targetPath);
1772
+ if (!target) {
1773
+ return false;
1774
+ }
1775
+ if (target.context === 'floating' && target.index === sourceIndex) {
1776
+ return false;
1777
+ }
1778
+ if (zone === 'center') {
1779
+ const { panes, titles } = this.collectFloatingPaneMetadata(source.root);
1780
+ if (panes.length === 0) {
1781
+ return false;
1782
+ }
1783
+ panes.forEach((pane) => {
1784
+ this.addPaneToLocation(target, pane);
1785
+ const title = titles[pane];
1786
+ if (title) {
1787
+ target.node.titles = target.node.titles ? { ...target.node.titles } : {};
1788
+ target.node.titles[pane] = title;
1789
+ }
1790
+ });
1791
+ const activePane = source.activePane && panes.includes(source.activePane)
1792
+ ? source.activePane
1793
+ : this.findFirstPaneName(source.root) ?? panes[0];
1794
+ if (activePane) {
1795
+ this.setActivePaneForLocation(target, activePane);
1796
+ }
1797
+ this.removeFloatingAt(sourceIndex);
1798
+ this.render();
1799
+ this.dispatchLayoutChanged();
1800
+ return true;
1801
+ }
1802
+ if (target.context === 'floating') {
1803
+ const floating = this.floatingLayouts[target.index];
1804
+ if (!floating) {
1805
+ return false;
1806
+ }
1807
+ floating.root = this.dockNodeBeside(floating.root, target.node, source.root, zone);
1808
+ floating.activePane = source.activePane ?? this.findFirstPaneName(source.root) ?? undefined;
1809
+ this.removeFloatingAt(sourceIndex);
1810
+ this.render();
1811
+ this.dispatchLayoutChanged();
1812
+ return true;
1813
+ }
1814
+ this.rootLayout = this.dockNodeBeside(this.rootLayout, target.node, source.root, zone);
1815
+ this.removeFloatingAt(sourceIndex);
1816
+ this.render();
1817
+ this.dispatchLayoutChanged();
1818
+ return true;
1819
+ }
1820
+ insertWeight(sizes, index, totalChildren) {
1821
+ const existingCount = totalChildren - 1;
1822
+ const normalized = this.normalizeSizesArray(sizes, existingCount);
1823
+ const newWeight = 1 / totalChildren;
1824
+ const remaining = 1 - newWeight;
1825
+ const result = [];
1826
+ for (let i = 0; i < totalChildren; i += 1) {
1827
+ if (i === index) {
1828
+ result.push(newWeight);
1829
+ }
1830
+ else {
1831
+ const sourceIndex = i < index ? i : i - 1;
1832
+ result.push(normalized[sourceIndex] * remaining);
1833
+ }
1834
+ }
1835
+ return result;
1836
+ }
1837
+ removePaneFromStack(stack, pane, skipCleanup = false) {
1838
+ stack.panes = stack.panes.filter((p) => p !== pane);
1839
+ if (!stack.panes.includes(stack.activePane ?? '')) {
1840
+ if (stack.panes.length > 0) {
1841
+ stack.activePane = stack.panes[0];
1842
+ }
1843
+ else {
1844
+ delete stack.activePane;
1845
+ }
1846
+ }
1847
+ if (stack.panes.length > 0) {
1848
+ return false;
1849
+ }
1850
+ if (skipCleanup) {
1851
+ return true;
1852
+ }
1853
+ this.rootLayout = this.cleanupEmptyStackInTree(this.rootLayout, stack);
1854
+ return true;
1855
+ }
1856
+ findParentSplit(node, child) {
1857
+ if (!node || node === child) {
1858
+ return null;
1859
+ }
1860
+ if (node.kind !== 'split') {
1861
+ return null;
1862
+ }
1863
+ const index = node.children.indexOf(child);
1864
+ if (index !== -1) {
1865
+ return { parent: node, index };
1866
+ }
1867
+ for (let i = 0; i < node.children.length; i += 1) {
1868
+ const result = this.findParentSplit(node.children[i], child);
1869
+ if (result) {
1870
+ return result;
1871
+ }
1872
+ }
1873
+ return null;
1874
+ }
1875
+ computeDropZone(stack, point, zoneHint) {
1876
+ const hintedZone = this.isDropZone(zoneHint) ? zoneHint : null;
1877
+ if (hintedZone) {
1878
+ this.updateDropJoystickActiveZone(hintedZone);
1879
+ return hintedZone;
1880
+ }
1881
+ const pointZone = point &&
1882
+ Number.isFinite(point.clientX) &&
1883
+ Number.isFinite(point.clientY)
1884
+ ? this.findDropZoneByPoint(point.clientX, point.clientY)
1885
+ : null;
1886
+ if (pointZone) {
1887
+ this.updateDropJoystickActiveZone(pointZone);
1888
+ return pointZone;
1889
+ }
1890
+ if (this.dropJoystickTarget === stack) {
1891
+ this.updateDropJoystickActiveZone(null);
1892
+ }
1893
+ return null;
1894
+ }
1895
+ extractDropZoneFromEvent(event) {
1896
+ if (!event) {
1897
+ return null;
1898
+ }
1899
+ if (typeof event.composedPath === 'function') {
1900
+ const path = event.composedPath();
1901
+ for (const target of path) {
1902
+ if (target instanceof HTMLElement &&
1903
+ target.classList.contains('dock-drop-joystick__button')) {
1904
+ const zone = target.dataset?.['zone'];
1905
+ if (this.isDropZone(zone)) {
1906
+ return zone;
1907
+ }
1908
+ }
1909
+ }
1910
+ }
1911
+ if ('clientX' in event && 'clientY' in event) {
1912
+ const pointEvent = event;
1913
+ const zoneFromPoint = this.findDropZoneByPoint(pointEvent.clientX, pointEvent.clientY);
1914
+ if (zoneFromPoint) {
1915
+ return zoneFromPoint;
1916
+ }
1917
+ }
1918
+ return null;
1919
+ }
1920
+ findDropZoneByPoint(clientX, clientY) {
1921
+ if (!this.dropJoystickButtons ||
1922
+ this.dropJoystick.dataset['visible'] !== 'true' ||
1923
+ !this.dropJoystickTarget) {
1924
+ return null;
1925
+ }
1926
+ for (const button of this.dropJoystickButtons) {
1927
+ const rect = button.getBoundingClientRect();
1928
+ if (clientX >= rect.left &&
1929
+ clientX <= rect.right &&
1930
+ clientY >= rect.top &&
1931
+ clientY <= rect.bottom) {
1932
+ const zone = button.dataset['zone'];
1933
+ if (this.isDropZone(zone)) {
1934
+ return zone;
1935
+ }
1936
+ }
1937
+ }
1938
+ return null;
1939
+ }
1940
+ updateDropJoystickActiveZone(zone) {
1941
+ this.dropJoystickButtons.forEach((button) => {
1942
+ const isActive = zone !== null && button.dataset['zone'] === zone;
1943
+ if (isActive) {
1944
+ button.dataset['active'] = 'true';
1945
+ }
1946
+ else {
1947
+ delete button.dataset['active'];
1948
+ }
1949
+ });
1950
+ if (zone) {
1951
+ this.dropJoystick.dataset['zone'] = zone;
1952
+ }
1953
+ else {
1954
+ delete this.dropJoystick.dataset['zone'];
1955
+ }
1956
+ }
1957
+ isDropZone(value) {
1958
+ return value === 'left' || value === 'right' || value === 'top' || value === 'bottom' || value === 'center';
1959
+ }
1960
+ showDropIndicator(stack, zone) {
1961
+ const targetPath = this.parsePath(stack.dataset['path']);
1962
+ const sourcePath = this.dragState?.sourcePath ?? null;
1963
+ if (targetPath && sourcePath && this.isOrIsAncestorOf(targetPath, sourcePath)) {
1964
+ // Don't show any drop indicators on the pane being dragged.
1965
+ return;
1966
+ }
1967
+ const rect = stack.getBoundingClientRect();
1968
+ const hostRect = this.getBoundingClientRect();
1969
+ const indicator = this.dropIndicator;
1970
+ const joystick = this.dropJoystick;
1971
+ joystick.hidden = false;
1972
+ const path = this.parsePath(stack.dataset['path']);
1973
+ let overlayZ = 100;
1974
+ if (path && path.type === 'floating') {
1975
+ overlayZ = this.getFloatingPaneZIndex(path.index) + 100;
1976
+ }
1977
+ indicator.style.zIndex = String(overlayZ);
1978
+ joystick.style.zIndex = String(overlayZ + 1);
1979
+ let left = rect.left - hostRect.left;
1980
+ let top = rect.top - hostRect.top;
1981
+ let width = rect.width;
1982
+ let height = rect.height;
1983
+ const portion = 0.5;
1984
+ if (zone) {
1985
+ switch (zone) {
1986
+ case 'left':
1987
+ width = rect.width * portion;
1988
+ break;
1989
+ case 'right':
1990
+ width = rect.width * portion;
1991
+ left += rect.width * (1 - portion);
1992
+ break;
1993
+ case 'top':
1994
+ height = rect.height * portion;
1995
+ break;
1996
+ case 'bottom':
1997
+ height = rect.height * portion;
1998
+ top += rect.height * (1 - portion);
1999
+ break;
2000
+ default:
2001
+ break;
2002
+ }
2003
+ indicator.style.left = `${left}px`;
2004
+ indicator.style.top = `${top}px`;
2005
+ indicator.style.width = `${width}px`;
2006
+ indicator.style.height = `${height}px`;
2007
+ indicator.dataset['visible'] = 'true';
2008
+ }
2009
+ else {
2010
+ indicator.dataset['visible'] = 'false';
2011
+ }
2012
+ joystick.style.left = `${rect.left - hostRect.left + rect.width / 2}px`;
2013
+ joystick.style.top = `${rect.top - hostRect.top + rect.height / 2}px`;
2014
+ joystick.dataset['visible'] = 'true';
2015
+ this.dropJoystick.style.display = 'grid';
2016
+ joystick.dataset['path'] = stack.dataset['path'] ?? '';
2017
+ this.dropJoystickTarget = stack;
2018
+ this.updateDropJoystickActiveZone(zone);
2019
+ }
2020
+ hideDropIndicator() {
2021
+ this.dropIndicator.dataset['visible'] = 'false';
2022
+ this.dropJoystick.dataset['visible'] = 'false';
2023
+ this.dropJoystick.style.display = 'none';
2024
+ delete this.dropJoystick.dataset['path'];
2025
+ this.dropJoystickTarget = null;
2026
+ this.updateDropJoystickActiveZone(null);
2027
+ }
2028
+ findStackAtPoint(clientX, clientY) {
2029
+ const shadow = this.shadowRoot;
2030
+ if (!shadow) {
2031
+ return null;
2032
+ }
2033
+ const elements = shadow.elementsFromPoint(clientX, clientY);
2034
+ for (const element of elements) {
2035
+ if (!(element instanceof HTMLElement)) {
2036
+ continue;
2037
+ }
2038
+ if (element.classList.contains('dock-stack')) {
2039
+ return element;
2040
+ }
2041
+ if (this.dropJoystickTarget &&
2042
+ (element.classList.contains('dock-drop-joystick') ||
2043
+ element.classList.contains('dock-drop-joystick__button') ||
2044
+ element.classList.contains('dock-drop-joystick__spacer'))) {
2045
+ return this.dropJoystickTarget;
2046
+ }
2047
+ }
2048
+ return null;
2049
+ }
2050
+ findStackElement(event) {
2051
+ const path = event.composedPath();
2052
+ for (const target of path) {
2053
+ if (!(target instanceof HTMLElement)) {
2054
+ continue;
2055
+ }
2056
+ if (target.classList.contains('dock-stack')) {
2057
+ return target;
2058
+ }
2059
+ if (this.dropJoystickTarget &&
2060
+ (target.classList.contains('dock-drop-joystick') ||
2061
+ target.classList.contains('dock-drop-joystick__button') ||
2062
+ target.classList.contains('dock-drop-joystick__spacer'))) {
2063
+ return this.dropJoystickTarget;
2064
+ }
2065
+ }
2066
+ return null;
2067
+ }
2068
+ activatePane(stack, paneName, path) {
2069
+ stack.dataset['activePane'] = paneName;
2070
+ const headerButtons = stack.querySelectorAll('.dock-tab');
2071
+ headerButtons.forEach((button) => {
2072
+ const isSelected = button.dataset['pane'] === paneName;
2073
+ button.classList.toggle('dock-tab--active', isSelected);
2074
+ button.setAttribute('aria-selected', String(isSelected));
2075
+ });
2076
+ const panes = stack.querySelectorAll('.dock-stack__pane');
2077
+ panes.forEach((pane) => {
2078
+ if (pane.dataset['pane'] === paneName) {
2079
+ pane.removeAttribute('hidden');
2080
+ }
2081
+ else {
2082
+ pane.setAttribute('hidden', '');
2083
+ }
2084
+ });
2085
+ const location = this.resolveStackLocation(path);
2086
+ if (!location) {
2087
+ return;
2088
+ }
2089
+ location.node.activePane = paneName;
2090
+ if (path.type === 'floating') {
2091
+ const floating = this.floatingLayouts[path.index];
2092
+ if (floating) {
2093
+ floating.activePane = paneName;
2094
+ }
2095
+ this.updateFloatingWindowTitle(path.index);
2096
+ }
2097
+ this.dispatchLayoutChanged();
2098
+ }
2099
+ getNodeAtPath(root, path) {
2100
+ if (!root) {
2101
+ return null;
2102
+ }
2103
+ let current = root;
2104
+ if (path.length === 0) {
2105
+ return current;
2106
+ }
2107
+ for (const segment of path) {
2108
+ if (!current || current.kind !== 'split') {
2109
+ return null;
2110
+ }
2111
+ current = current.children[segment] ?? null;
2112
+ }
2113
+ return current;
2114
+ }
2115
+ resolveSplitNode(path) {
2116
+ if (path.type === 'docked') {
2117
+ const node = this.getNodeAtPath(this.rootLayout, path.segments);
2118
+ return node && node.kind === 'split' ? node : null;
2119
+ }
2120
+ const floating = this.floatingLayouts[path.index];
2121
+ if (!floating || !floating.root) {
2122
+ return null;
2123
+ }
2124
+ const node = this.getNodeAtPath(floating.root, path.segments);
2125
+ return node && node.kind === 'split' ? node : null;
2126
+ }
2127
+ replaceNodeInTree(root, target, replacement) {
2128
+ if (!root) {
2129
+ return replacement;
2130
+ }
2131
+ if (root === target) {
2132
+ return replacement;
2133
+ }
2134
+ const parentInfo = this.findParentSplit(root, target);
2135
+ if (!parentInfo) {
2136
+ return root;
2137
+ }
2138
+ parentInfo.parent.children[parentInfo.index] = replacement;
2139
+ this.normalizeSplitNode(parentInfo.parent);
2140
+ return root;
2141
+ }
2142
+ cleanupEmptyStackInTree(root, stack) {
2143
+ if (!root || stack.panes.length > 0) {
2144
+ return root;
2145
+ }
2146
+ const parentInfo = this.findParentSplit(root, stack);
2147
+ if (!parentInfo) {
2148
+ return root === stack ? null : root;
2149
+ }
2150
+ const parent = parentInfo.parent;
2151
+ const index = parent.children.indexOf(stack);
2152
+ if (index === -1) {
2153
+ return root;
2154
+ }
2155
+ parent.children.splice(index, 1);
2156
+ if (Array.isArray(parent.sizes)) {
2157
+ parent.sizes.splice(index, 1);
2158
+ }
2159
+ this.normalizeSplitNode(parent);
2160
+ return this.cleanupSplitIfNecessary(root, parent);
2161
+ }
2162
+ cleanupSplitIfNecessary(root, split) {
2163
+ if (split.children.length === 1) {
2164
+ return this.replaceNodeInTree(root, split, split.children[0]);
2165
+ }
2166
+ if (split.children.length === 0) {
2167
+ const parentInfo = this.findParentSplit(root, split);
2168
+ if (!parentInfo) {
2169
+ return null;
2170
+ }
2171
+ const parent = parentInfo.parent;
2172
+ const index = parent.children.indexOf(split);
2173
+ if (index !== -1) {
2174
+ parent.children.splice(index, 1);
2175
+ if (Array.isArray(parent.sizes)) {
2176
+ parent.sizes.splice(index, 1);
2177
+ }
2178
+ this.normalizeSplitNode(parent);
2179
+ return this.cleanupSplitIfNecessary(root, parent);
2180
+ }
2181
+ }
2182
+ return root;
2183
+ }
2184
+ dockNodeBeside(root, targetNode, newNode, zone) {
2185
+ const orientation = zone === 'left' || zone === 'right' ? 'horizontal' : 'vertical';
2186
+ const placeBefore = zone === 'left' || zone === 'top';
2187
+ const parentInfo = this.findParentSplit(root, targetNode);
2188
+ if (parentInfo && parentInfo.parent.direction === orientation) {
2189
+ const insertIndex = placeBefore ? parentInfo.index : parentInfo.index + 1;
2190
+ parentInfo.parent.children.splice(insertIndex, 0, newNode);
2191
+ parentInfo.parent.sizes = this.insertWeight(parentInfo.parent.sizes, insertIndex, parentInfo.parent.children.length);
2192
+ return root ?? newNode;
2193
+ }
2194
+ const split = {
2195
+ kind: 'split',
2196
+ direction: orientation,
2197
+ children: placeBefore ? [newNode, targetNode] : [targetNode, newNode],
2198
+ sizes: [0.5, 0.5],
2199
+ };
2200
+ return this.replaceNodeInTree(root, targetNode, split);
2201
+ }
2202
+ forEachStack(node, visitor, path = []) {
2203
+ if (!node) {
2204
+ return;
2205
+ }
2206
+ if (node.kind === 'stack') {
2207
+ visitor(node, path);
2208
+ return;
2209
+ }
2210
+ node.children.forEach((child, index) => this.forEachStack(child, visitor, [...path, index]));
2211
+ }
2212
+ findStackContainingPane(node, pane) {
2213
+ let result = null;
2214
+ this.forEachStack(node, (stack) => {
2215
+ if (!result && stack.panes.includes(pane)) {
2216
+ result = stack;
2217
+ }
2218
+ });
2219
+ return result;
2220
+ }
2221
+ findFirstPaneName(node) {
2222
+ let found = null;
2223
+ this.forEachStack(node, (stack) => {
2224
+ if (found || stack.panes.length === 0) {
2225
+ return;
2226
+ }
2227
+ if (stack.activePane && stack.panes.includes(stack.activePane)) {
2228
+ found = stack.activePane;
2229
+ }
2230
+ else {
2231
+ found = stack.panes[0];
2232
+ }
2233
+ });
2234
+ return found;
2235
+ }
2236
+ collectFloatingPaneMetadata(node) {
2237
+ const panes = [];
2238
+ const titles = {};
2239
+ this.forEachStack(node, (stack) => {
2240
+ stack.panes.forEach((pane) => {
2241
+ panes.push(pane);
2242
+ const title = stack.titles?.[pane];
2243
+ if (title) {
2244
+ titles[pane] = title;
2245
+ }
2246
+ });
2247
+ });
2248
+ return { panes, titles };
2249
+ }
2250
+ normalizeFloatingLayout(layout) {
2251
+ const bounds = layout.bounds ?? { left: 0, top: 0, width: 320, height: 200 };
2252
+ const normalizedBounds = {
2253
+ left: Number.isFinite(bounds.left) ? bounds.left : 0,
2254
+ top: Number.isFinite(bounds.top) ? bounds.top : 0,
2255
+ width: Number.isFinite(bounds.width) ? Math.max(bounds.width, 160) : 320,
2256
+ height: Number.isFinite(bounds.height) ? Math.max(bounds.height, 120) : 200,
2257
+ };
2258
+ let root = layout.root ? this.cloneLayoutNode(layout.root) : null;
2259
+ if (!root) {
2260
+ const panes = Array.isArray(layout.panes) ? [...layout.panes] : [];
2261
+ if (panes.length > 0) {
2262
+ const active = layout.activePane && panes.includes(layout.activePane)
2263
+ ? layout.activePane
2264
+ : panes[0];
2265
+ root = {
2266
+ kind: 'stack',
2267
+ panes,
2268
+ titles: layout.titles ? { ...layout.titles } : undefined,
2269
+ activePane: active,
2270
+ };
2271
+ }
2272
+ }
2273
+ return {
2274
+ id: layout.id,
2275
+ bounds: normalizedBounds,
2276
+ zIndex: layout.zIndex,
2277
+ root,
2278
+ activePane: layout.activePane,
2279
+ };
2280
+ }
2281
+ formatPath(path) {
2282
+ if (path.type === 'floating') {
2283
+ const suffix = path.segments.length > 0
2284
+ ? `/${path.segments.map((segment) => segment.toString()).join('/')}`
2285
+ : '';
2286
+ return `f:${path.index}${suffix}`;
2287
+ }
2288
+ const suffix = path.segments.join('/');
2289
+ return suffix.length > 0 ? `d:${suffix}` : 'd:';
2290
+ }
2291
+ clonePath(path) {
2292
+ if (path.type === 'floating') {
2293
+ return { type: 'floating', index: path.index, segments: [...path.segments] };
2294
+ }
2295
+ return { type: 'docked', segments: [...path.segments] };
2296
+ }
2297
+ parsePath(path) {
2298
+ if (!path) {
2299
+ return null;
2300
+ }
2301
+ if (path.startsWith('f:')) {
2302
+ const remainder = path.slice(2);
2303
+ const [indexPart, ...segmentParts] = remainder.split('/');
2304
+ const index = Number.parseInt(indexPart, 10);
2305
+ if (!Number.isFinite(index)) {
2306
+ return null;
2307
+ }
2308
+ const segments = segmentParts
2309
+ .filter((segment) => segment.length > 0)
2310
+ .map((segment) => Number.parseInt(segment, 10))
2311
+ .filter((value) => Number.isFinite(value));
2312
+ return { type: 'floating', index, segments };
2313
+ }
2314
+ const normalized = path.startsWith('d:') ? path.slice(2) : path;
2315
+ if (normalized.length === 0) {
2316
+ return { type: 'docked', segments: [] };
2317
+ }
2318
+ const segments = normalized
2319
+ .split('/')
2320
+ .filter((segment) => segment.length > 0)
2321
+ .map((segment) => Number.parseInt(segment, 10))
2322
+ .filter((value) => Number.isFinite(value));
2323
+ return { type: 'docked', segments };
2324
+ }
2325
+ pathsEqual(a, b) {
2326
+ if (a.type !== b.type) {
2327
+ return false;
2328
+ }
2329
+ if (a.type === 'floating') {
2330
+ const other = b;
2331
+ if (a.index !== other.index) {
2332
+ return false;
2333
+ }
2334
+ if (a.segments.length !== other.segments.length) {
2335
+ return false;
2336
+ }
2337
+ return a.segments.every((value, index) => value === other.segments[index]);
2338
+ }
2339
+ const other = b;
2340
+ if (a.segments.length !== other.segments.length) {
2341
+ return false;
2342
+ }
2343
+ return a.segments.every((value, index) => value === other.segments[index]);
2344
+ }
2345
+ isOrIsAncestorOf(ancestor, descendant) {
2346
+ if (ancestor.type !== descendant.type) {
2347
+ return false;
2348
+ }
2349
+ if (ancestor.type === 'floating') {
2350
+ if (descendant.index !== ancestor.index) {
2351
+ return false;
2352
+ }
2353
+ }
2354
+ if (ancestor.segments.length > descendant.segments.length) {
2355
+ return false;
2356
+ }
2357
+ return ancestor.segments.every((segment, i) => segment === descendant.segments[i]);
2358
+ }
2359
+ countPanesInTree(node) {
2360
+ if (!node) {
2361
+ return 0;
2362
+ }
2363
+ if (node.kind === 'stack') {
2364
+ return node.panes.length;
2365
+ }
2366
+ return node.children.reduce((total, child) => total + this.countPanesInTree(child), 0);
2367
+ }
2368
+ resolveStackLocation(path) {
2369
+ if (path.type === 'floating') {
2370
+ const layout = this.floatingLayouts[path.index];
2371
+ if (!layout || !layout.root) {
2372
+ return null;
2373
+ }
2374
+ const node = this.getNodeAtPath(layout.root, path.segments);
2375
+ if (!node || node.kind !== 'stack') {
2376
+ return null;
2377
+ }
2378
+ return { context: 'floating', index: path.index, path: [...path.segments], node };
2379
+ }
2380
+ const node = this.getNodeAtPath(this.rootLayout, path.segments);
2381
+ if (!node || node.kind !== 'stack') {
2382
+ return null;
2383
+ }
2384
+ return { context: 'docked', path: [...path.segments], node };
2385
+ }
2386
+ removePaneFromLocation(location, pane, skipCleanup = false) {
2387
+ if (location.context === 'docked') {
2388
+ return this.removePaneFromStack(location.node, pane, skipCleanup);
2389
+ }
2390
+ return this.removePaneFromFloating(location.index, location.path, pane, skipCleanup);
2391
+ }
2392
+ addPaneToLocation(location, pane) {
2393
+ const panes = location.node.panes;
2394
+ const existingIndex = panes.indexOf(pane);
2395
+ if (existingIndex !== -1) {
2396
+ panes.splice(existingIndex, 1);
2397
+ }
2398
+ panes.push(pane);
2399
+ }
2400
+ setActivePaneForLocation(location, pane) {
2401
+ location.node.activePane = pane;
2402
+ if (location.context === 'floating') {
2403
+ const floating = this.floatingLayouts[location.index];
2404
+ if (floating) {
2405
+ floating.activePane = pane;
2406
+ }
2407
+ }
2408
+ }
2409
+ cleanupLocation(location) {
2410
+ if (location.context === 'docked') {
2411
+ this.rootLayout = this.cleanupEmptyStackInTree(this.rootLayout, location.node);
2412
+ }
2413
+ else {
2414
+ const floating = this.floatingLayouts[location.index];
2415
+ if (!floating) {
2416
+ return;
2417
+ }
2418
+ floating.root = this.cleanupEmptyStackInTree(floating.root, location.node);
2419
+ if (!floating.root) {
2420
+ this.removeFloatingAt(location.index);
2421
+ }
2422
+ }
2423
+ }
2424
+ reorderPaneInLocation(location, pane) {
2425
+ const panes = location.node.panes;
2426
+ const index = panes.indexOf(pane);
2427
+ if (index === -1) {
2428
+ return;
2429
+ }
2430
+ panes.splice(index, 1);
2431
+ panes.push(pane);
2432
+ location.node.activePane = pane;
2433
+ if (location.context === 'floating') {
2434
+ const floating = this.floatingLayouts[location.index];
2435
+ if (floating) {
2436
+ floating.activePane = pane;
2437
+ }
2438
+ }
2439
+ }
2440
+ removeFloatingAt(index) {
2441
+ if (index < 0 || index >= this.floatingLayouts.length) {
2442
+ return;
2443
+ }
2444
+ this.floatingLayouts.splice(index, 1);
2445
+ }
2446
+ removePaneFromFloating(index, path, pane, skipCleanup = false) {
2447
+ const floating = this.floatingLayouts[index];
2448
+ if (!floating || !floating.root) {
2449
+ return false;
2450
+ }
2451
+ const node = this.getNodeAtPath(floating.root, path);
2452
+ if (!node || node.kind !== 'stack') {
2453
+ return false;
2454
+ }
2455
+ node.panes = node.panes.filter((p) => p !== pane);
2456
+ if (!node.panes.includes(node.activePane ?? '')) {
2457
+ if (node.panes.length > 0) {
2458
+ node.activePane = node.panes[0];
2459
+ }
2460
+ else {
2461
+ delete node.activePane;
2462
+ }
2463
+ }
2464
+ if (floating.activePane === pane) {
2465
+ const fallback = this.findFirstPaneName(floating.root);
2466
+ if (fallback) {
2467
+ floating.activePane = fallback;
2468
+ }
2469
+ else {
2470
+ delete floating.activePane;
2471
+ }
2472
+ }
2473
+ if (node.panes.length > 0) {
2474
+ return false;
2475
+ }
2476
+ if (skipCleanup) {
2477
+ return true;
2478
+ }
2479
+ floating.root = this.cleanupEmptyStackInTree(floating.root, node);
2480
+ if (!floating.root) {
2481
+ this.removeFloatingAt(index);
2482
+ }
2483
+ return true;
2484
+ }
2485
+ normalizeSizesArray(sizes, count) {
2486
+ if (count <= 0) {
2487
+ return [];
2488
+ }
2489
+ if (!Array.isArray(sizes) || sizes.length !== count) {
2490
+ return Array.from({ length: count }, () => 1 / count);
2491
+ }
2492
+ const normalized = sizes.map((value) => (Number.isFinite(value) ? Math.max(value, 0) : 0));
2493
+ const total = normalized.reduce((acc, value) => acc + value, 0);
2494
+ if (total <= 0) {
2495
+ return Array.from({ length: count }, () => 1 / count);
2496
+ }
2497
+ return normalized.map((value) => value / total);
2498
+ }
2499
+ normalizeSplitNode(split) {
2500
+ split.sizes = this.normalizeSizesArray(split.sizes, split.children.length);
2501
+ }
2502
+ dispatchLayoutChanged() {
2503
+ this.dispatchEvent(new CustomEvent('dock-layout-changed', {
2504
+ detail: this.snapshot,
2505
+ bubbles: true,
2506
+ composed: true,
2507
+ }));
2508
+ }
2509
+ cloneLayoutNode(layout) {
2510
+ if (!layout) {
2511
+ return null;
2512
+ }
2513
+ return JSON.parse(JSON.stringify(layout));
2514
+ }
2515
+ cloneFloatingArray(layouts) {
2516
+ return JSON.parse(JSON.stringify(layouts));
2517
+ }
2518
+ }
2519
+ const tagName = 'mint-dock-manager';
2520
+ if (typeof customElements !== 'undefined' && !customElements.get(tagName)) {
2521
+ customElements.define(tagName, MintDockManagerElement);
2522
+ }
2523
+
2524
+ class BsDockManagerComponent {
2525
+ set layout(value) {
2526
+ const snapshot = this.cloneLayout(this.ensureSnapshot(value));
2527
+ this._layout = snapshot;
2528
+ this.layoutString = this.stringifyLayout(snapshot);
2529
+ this.applyLayout();
2530
+ }
2531
+ get layout() {
2532
+ if (!this._layout.root && this._layout.floating.length === 0) {
2533
+ return null;
2534
+ }
2535
+ return this.cloneLayout(this._layout);
2536
+ }
2537
+ constructor(changeDetector, documentRef) {
2538
+ this.changeDetector = changeDetector;
2539
+ this.layoutChange = new EventEmitter();
2540
+ this.layoutSnapshotChange = new EventEmitter();
2541
+ this.layoutString = null;
2542
+ this.panes = new QueryList();
2543
+ this.trackByPane = (_, pane) => pane.name;
2544
+ this._layout = { root: null, floating: [] };
2545
+ if (documentRef) {
2546
+ MintDockManagerElement.configureDocument(documentRef);
2547
+ }
2548
+ }
2549
+ ngAfterViewInit() {
2550
+ this.applyLayout();
2551
+ }
2552
+ captureLayout() {
2553
+ const element = this.managerRef?.nativeElement;
2554
+ return element?.snapshot ?? { root: null, floating: [] };
2555
+ }
2556
+ onLayoutChanged(event) {
2557
+ const snapshot = event.detail ?? {
2558
+ root: null,
2559
+ floating: [],
2560
+ };
2561
+ this._layout = this.cloneLayout(snapshot);
2562
+ this.layoutString = this.stringifyLayout(this._layout);
2563
+ this.layoutChange.emit(this.layout);
2564
+ this.layoutSnapshotChange.emit(this.cloneLayout(snapshot));
2565
+ this.changeDetector.markForCheck();
2566
+ }
2567
+ applyLayout() {
2568
+ if (!this.managerRef) {
2569
+ return;
2570
+ }
2571
+ const element = this.managerRef.nativeElement;
2572
+ const layout = this.layout;
2573
+ element.layout = layout ?? null;
2574
+ }
2575
+ ensureSnapshot(value) {
2576
+ if (!value) {
2577
+ return { root: null, floating: [] };
2578
+ }
2579
+ if ('kind' in value) {
2580
+ return { root: value, floating: [] };
2581
+ }
2582
+ return {
2583
+ root: value.root ?? null,
2584
+ floating: Array.isArray(value.floating) ? [...value.floating] : [],
2585
+ };
2586
+ }
2587
+ stringifyLayout(layout) {
2588
+ if (!layout.root && layout.floating.length === 0) {
2589
+ return null;
2590
+ }
2591
+ return JSON.stringify(layout);
2592
+ }
2593
+ cloneLayout(layout) {
2594
+ return JSON.parse(JSON.stringify(layout));
2595
+ }
2596
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockManagerComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); }
2597
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.7", type: BsDockManagerComponent, isStandalone: false, selector: "bs-dock-manager", inputs: { layout: "layout" }, outputs: { layoutChange: "layoutChange", layoutSnapshotChange: "layoutSnapshotChange" }, queries: [{ propertyName: "panes", predicate: BsDockPaneComponent }], viewQueries: [{ propertyName: "managerRef", first: true, predicate: ["manager"], descendants: true, static: 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 <ng-container *ngFor=\"let pane of panes; trackBy: trackByPane\">\n <div class=\"bs-dock-pane\" [attr.slot]=\"pane.name\">\n <ng-container *ngTemplateOutlet=\"pane.template\"></ng-container>\n </div>\n </ng-container>\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: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2598
+ }
2599
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockManagerComponent, decorators: [{
2600
+ type: Component,
2601
+ args: [{ selector: 'bs-dock-manager', standalone: false, 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 <ng-container *ngFor=\"let pane of panes; trackBy: trackByPane\">\n <div class=\"bs-dock-pane\" [attr.slot]=\"pane.name\">\n <ng-container *ngTemplateOutlet=\"pane.template\"></ng-container>\n </div>\n </ng-container>\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"] }]
2602
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: Document, decorators: [{
2603
+ type: Inject,
2604
+ args: [DOCUMENT]
2605
+ }] }], propDecorators: { layout: [{
2606
+ type: Input
2607
+ }], layoutChange: [{
2608
+ type: Output
2609
+ }], layoutSnapshotChange: [{
2610
+ type: Output
2611
+ }], panes: [{
2612
+ type: ContentChildren,
2613
+ args: [BsDockPaneComponent]
2614
+ }], managerRef: [{
2615
+ type: ViewChild,
2616
+ args: ['manager', { static: true }]
537
2617
  }] } });
538
2618
 
539
2619
  class BsDockModule {
540
2620
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
541
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.1.7", ngImport: i0, type: BsDockModule, declarations: [BsDockComponent,
542
- BsDockPanelComponent,
543
- BsDockPanelHeaderComponent,
544
- BsDockPaneRendererComponent], imports: [AsyncPipe,
545
- PortalModule,
546
- BsCardModule,
547
- BsInstanceOfModule,
548
- BsSplitterModule,
549
- BsTabControlModule,
550
- BsResizableModule], exports: [BsDockComponent,
551
- BsDockPanelComponent,
552
- BsDockPanelHeaderComponent,
553
- BsDockPaneRendererComponent] }); }
554
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockModule, imports: [PortalModule,
555
- BsCardModule,
556
- BsInstanceOfModule,
557
- BsSplitterModule,
558
- BsTabControlModule,
559
- BsResizableModule] }); }
2621
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.1.7", ngImport: i0, type: BsDockModule, declarations: [BsDockManagerComponent, BsDockPaneComponent], imports: [CommonModule], exports: [BsDockManagerComponent, BsDockPaneComponent] }); }
2622
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockModule, imports: [CommonModule] }); }
560
2623
  }
561
2624
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: BsDockModule, decorators: [{
562
2625
  type: NgModule,
563
2626
  args: [{
564
- declarations: [
565
- BsDockComponent,
566
- BsDockPanelComponent,
567
- BsDockPanelHeaderComponent,
568
- BsDockPaneRendererComponent
569
- ],
570
- imports: [
571
- AsyncPipe,
572
- PortalModule,
573
- BsCardModule,
574
- BsInstanceOfModule,
575
- BsSplitterModule,
576
- BsTabControlModule,
577
- BsResizableModule
578
- ],
579
- exports: [
580
- BsDockComponent,
581
- BsDockPanelComponent,
582
- BsDockPanelHeaderComponent,
583
- BsDockPaneRendererComponent
584
- ]
2627
+ declarations: [BsDockManagerComponent, BsDockPaneComponent],
2628
+ imports: [CommonModule],
2629
+ exports: [BsDockManagerComponent, BsDockPaneComponent],
2630
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
585
2631
  }]
586
2632
  }] });
587
2633
 
@@ -589,5 +2635,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
589
2635
  * Generated bundle index. Do not edit.
590
2636
  */
591
2637
 
592
- export { BsContentPane, BsDockComponent, BsDockModule, BsDockPane, BsDockPaneRendererComponent, BsDockPanelComponent, BsDockPanelHeaderComponent, BsDockService, BsDocumentHost, BsFloatingPane, BsSplitPane, BsTabGroupPane, EPaneType };
2638
+ export { BsDockManagerComponent, BsDockModule, BsDockPaneComponent, MintDockManagerElement };
593
2639
  //# sourceMappingURL=mintplayer-ng-bootstrap-dock.mjs.map