@mintplayer/ng-bootstrap 21.21.0 → 21.23.0

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