flexlayout-react 0.7.15 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/ChangeLog.txt +28 -0
  2. package/README.md +157 -330
  3. package/Screenshot_light.png +0 -0
  4. package/Screenshot_rounded.png +0 -0
  5. package/declarations/Attribute.d.ts +1 -1
  6. package/declarations/AttributeDefinitions.d.ts +1 -1
  7. package/declarations/DockLocation.d.ts +12 -12
  8. package/declarations/DropInfo.d.ts +12 -12
  9. package/declarations/I18nLabel.d.ts +12 -14
  10. package/declarations/Orientation.d.ts +7 -7
  11. package/declarations/PopupMenu.d.ts +1 -1
  12. package/declarations/Rect.d.ts +41 -28
  13. package/declarations/Types.d.ts +95 -79
  14. package/declarations/examples/demo/Utils.d.ts +4 -0
  15. package/declarations/index.d.ts +21 -22
  16. package/declarations/model/Action.d.ts +5 -5
  17. package/declarations/model/Actions.d.ts +127 -110
  18. package/declarations/model/BorderNode.d.ts +30 -34
  19. package/declarations/model/BorderSet.d.ts +3 -4
  20. package/declarations/model/ICloseType.d.ts +5 -5
  21. package/declarations/model/IDraggable.d.ts +2 -2
  22. package/declarations/model/IDropTarget.d.ts +2 -2
  23. package/declarations/model/IJsonModel.d.ts +811 -149
  24. package/declarations/model/LayoutWindow.d.ts +28 -0
  25. package/declarations/model/Model.d.ts +91 -86
  26. package/declarations/model/Node.d.ts +17 -17
  27. package/declarations/model/RowNode.d.ts +10 -11
  28. package/declarations/model/TabNode.d.ts +44 -37
  29. package/declarations/model/TabSetNode.d.ts +44 -41
  30. package/declarations/model/Utils.d.ts +1 -1
  31. package/declarations/model/WindowLayout.d.ts +24 -0
  32. package/declarations/src/Attribute.d.ts +1 -0
  33. package/declarations/src/AttributeDefinitions.d.ts +1 -0
  34. package/declarations/src/DockLocation.d.ts +12 -0
  35. package/declarations/src/DropInfo.d.ts +12 -0
  36. package/declarations/src/I18nLabel.d.ts +10 -0
  37. package/declarations/src/Orientation.d.ts +7 -0
  38. package/declarations/src/PopupMenu.d.ts +1 -0
  39. package/declarations/src/Rect.d.ts +31 -0
  40. package/declarations/src/Types.d.ts +92 -0
  41. package/declarations/src/index.d.ts +20 -0
  42. package/declarations/src/model/Action.d.ts +5 -0
  43. package/declarations/src/model/Actions.d.ts +110 -0
  44. package/declarations/src/model/BorderNode.d.ts +28 -0
  45. package/declarations/src/model/BorderSet.d.ts +3 -0
  46. package/declarations/src/model/ICloseType.d.ts +5 -0
  47. package/declarations/src/model/IDraggable.d.ts +2 -0
  48. package/declarations/src/model/IDropTarget.d.ts +2 -0
  49. package/declarations/src/model/IJsonModel.d.ts +153 -0
  50. package/declarations/src/model/Model.d.ts +98 -0
  51. package/declarations/src/model/Node.d.ts +16 -0
  52. package/declarations/src/model/RowNode.d.ts +11 -0
  53. package/declarations/src/model/TabNode.d.ts +36 -0
  54. package/declarations/src/model/TabSetNode.d.ts +37 -0
  55. package/declarations/src/model/Utils.d.ts +1 -0
  56. package/declarations/src/view/BorderButton.d.ts +1 -0
  57. package/declarations/src/view/BorderTab.d.ts +2 -0
  58. package/declarations/src/view/BorderTabSet.d.ts +1 -0
  59. package/declarations/src/view/DragContainer.d.ts +1 -0
  60. package/declarations/src/view/ErrorBoundary.d.ts +1 -0
  61. package/declarations/src/view/FloatingWindow.d.ts +1 -0
  62. package/declarations/src/view/Icons.d.ts +7 -0
  63. package/declarations/src/view/Layout.d.ts +113 -0
  64. package/declarations/src/view/Overlay.d.ts +1 -0
  65. package/declarations/src/view/PopupMenu.d.ts +1 -0
  66. package/declarations/src/view/Row.d.ts +1 -0
  67. package/declarations/src/view/Splitter.d.ts +1 -0
  68. package/declarations/src/view/Tab.d.ts +1 -0
  69. package/declarations/src/view/TabButton.d.ts +1 -0
  70. package/declarations/src/view/TabButtonStamp.d.ts +1 -0
  71. package/declarations/src/view/TabOverflowHook.d.ts +1 -0
  72. package/declarations/src/view/TabSet.d.ts +1 -0
  73. package/declarations/src/view/Utils.d.ts +4 -0
  74. package/declarations/view/BorderButton.d.ts +1 -1
  75. package/declarations/view/BorderTab.d.ts +2 -0
  76. package/declarations/view/BorderTabSet.d.ts +1 -1
  77. package/declarations/view/DragContainer.d.ts +1 -0
  78. package/declarations/view/ErrorBoundary.d.ts +1 -1
  79. package/declarations/view/ExtendedResizeObserver.d.ts +23 -0
  80. package/declarations/view/FloatingWindow.d.ts +1 -1
  81. package/declarations/view/Icons.d.ts +8 -7
  82. package/declarations/view/Layout.d.ts +140 -161
  83. package/declarations/view/Overlay.d.ts +1 -0
  84. package/declarations/view/PopoutWindow.d.ts +1 -0
  85. package/declarations/view/PopupMenu.d.ts +1 -0
  86. package/declarations/view/Row.d.ts +1 -0
  87. package/declarations/view/SizeTracker.d.ts +10 -0
  88. package/declarations/view/Splitter.d.ts +1 -1
  89. package/declarations/view/Tab.d.ts +1 -1
  90. package/declarations/view/TabButton.d.ts +1 -1
  91. package/declarations/view/TabButtonStamp.d.ts +1 -1
  92. package/declarations/view/TabOverflowHook.d.ts +1 -1
  93. package/declarations/view/TabSet.d.ts +1 -1
  94. package/declarations/view/Utils.d.ts +11 -1
  95. package/dist/bundles/demo.js +232052 -0
  96. package/dist/bundles/demo.js.map +1 -0
  97. package/dist/flexlayout.js +122 -92
  98. package/dist/flexlayout_min.js +1 -1
  99. package/lib/Attribute.js +42 -31
  100. package/lib/Attribute.js.map +1 -1
  101. package/lib/AttributeDefinitions.js +131 -108
  102. package/lib/AttributeDefinitions.js.map +1 -1
  103. package/lib/DockLocation.js +120 -124
  104. package/lib/DockLocation.js.map +1 -1
  105. package/lib/DropInfo.js +9 -13
  106. package/lib/DropInfo.js.map +1 -1
  107. package/lib/I18nLabel.js +13 -18
  108. package/lib/I18nLabel.js.map +1 -1
  109. package/lib/Orientation.js +22 -26
  110. package/lib/Orientation.js.map +1 -1
  111. package/lib/Rect.js +104 -72
  112. package/lib/Rect.js.map +1 -1
  113. package/lib/Types.js +96 -83
  114. package/lib/Types.js.map +1 -1
  115. package/lib/index.js +21 -38
  116. package/lib/index.js.map +1 -1
  117. package/lib/model/Action.js +6 -10
  118. package/lib/model/Action.js.map +1 -1
  119. package/lib/model/Actions.js +169 -155
  120. package/lib/model/Actions.js.map +1 -1
  121. package/lib/model/BorderNode.js +385 -406
  122. package/lib/model/BorderNode.js.map +1 -1
  123. package/lib/model/BorderSet.js +66 -121
  124. package/lib/model/BorderSet.js.map +1 -1
  125. package/lib/model/ICloseType.js +6 -9
  126. package/lib/model/ICloseType.js.map +1 -1
  127. package/lib/model/IDraggable.js +1 -2
  128. package/lib/model/IDropTarget.js +1 -2
  129. package/lib/model/IJsonModel.js +1 -2
  130. package/lib/model/LayoutWindow.js +83 -0
  131. package/lib/model/LayoutWindow.js.map +1 -0
  132. package/lib/model/Model.js +614 -496
  133. package/lib/model/Model.js.map +1 -1
  134. package/lib/model/Node.js +217 -228
  135. package/lib/model/Node.js.map +1 -1
  136. package/lib/model/RowNode.js +491 -504
  137. package/lib/model/RowNode.js.map +1 -1
  138. package/lib/model/TabNode.js +289 -184
  139. package/lib/model/TabNode.js.map +1 -1
  140. package/lib/model/TabSetNode.js +459 -446
  141. package/lib/model/TabSetNode.js.map +1 -1
  142. package/lib/model/Utils.js +47 -82
  143. package/lib/model/Utils.js.map +1 -1
  144. package/lib/view/BorderButton.js +129 -138
  145. package/lib/view/BorderButton.js.map +1 -1
  146. package/lib/view/BorderTab.js +47 -0
  147. package/lib/view/BorderTab.js.map +1 -0
  148. package/lib/view/BorderTabSet.js +134 -128
  149. package/lib/view/BorderTabSet.js.map +1 -1
  150. package/lib/view/DragContainer.js +16 -0
  151. package/lib/view/DragContainer.js.map +1 -0
  152. package/lib/view/ErrorBoundary.js +23 -27
  153. package/lib/view/ErrorBoundary.js.map +1 -1
  154. package/lib/view/Icons.js +40 -45
  155. package/lib/view/Icons.js.map +1 -1
  156. package/lib/view/Layout.js +919 -907
  157. package/lib/view/Layout.js.map +1 -1
  158. package/lib/view/Overlay.js +9 -0
  159. package/lib/view/Overlay.js.map +1 -0
  160. package/lib/view/PopoutWindow.js +129 -0
  161. package/lib/view/PopoutWindow.js.map +1 -0
  162. package/lib/view/PopupMenu.js +71 -0
  163. package/lib/view/PopupMenu.js.map +1 -0
  164. package/lib/view/Row.js +45 -0
  165. package/lib/view/Row.js.map +1 -0
  166. package/lib/view/SizeTracker.js +11 -0
  167. package/lib/view/SizeTracker.js.map +1 -0
  168. package/lib/view/Splitter.js +191 -147
  169. package/lib/view/Splitter.js.map +1 -1
  170. package/lib/view/Tab.js +86 -60
  171. package/lib/view/Tab.js.map +1 -1
  172. package/lib/view/TabButton.js +127 -135
  173. package/lib/view/TabButton.js.map +1 -1
  174. package/lib/view/TabButtonStamp.js +16 -21
  175. package/lib/view/TabButtonStamp.js.map +1 -1
  176. package/lib/view/TabOverflowHook.js +150 -149
  177. package/lib/view/TabOverflowHook.js.map +1 -1
  178. package/lib/view/TabSet.js +272 -234
  179. package/lib/view/TabSet.js.map +1 -1
  180. package/lib/view/Utils.js +126 -68
  181. package/lib/view/Utils.js.map +1 -1
  182. package/package.json +36 -30
  183. package/src/Attribute.ts +23 -0
  184. package/src/AttributeDefinitions.ts +38 -15
  185. package/src/DockLocation.ts +13 -13
  186. package/src/I18nLabel.ts +7 -9
  187. package/src/Rect.ts +53 -1
  188. package/src/Types.ts +16 -0
  189. package/src/index.ts +1 -2
  190. package/src/model/Actions.ts +49 -29
  191. package/src/model/BorderNode.ts +208 -214
  192. package/src/model/BorderSet.ts +42 -91
  193. package/src/model/IJsonModel.ts +883 -103
  194. package/src/model/LayoutWindow.ts +121 -0
  195. package/src/model/Model.ts +488 -366
  196. package/src/model/Node.ts +98 -111
  197. package/src/model/RowNode.ts +323 -319
  198. package/src/model/TabNode.ts +294 -110
  199. package/src/model/TabSetNode.ts +303 -242
  200. package/src/model/Utils.ts +6 -32
  201. package/src/view/BorderButton.tsx +36 -52
  202. package/src/view/BorderTab.tsx +70 -0
  203. package/src/view/BorderTabSet.tsx +64 -52
  204. package/src/view/DragContainer.tsx +32 -0
  205. package/src/view/Icons.tsx +6 -0
  206. package/src/view/Layout.tsx +1053 -1046
  207. package/src/view/Overlay.tsx +22 -0
  208. package/src/view/PopoutWindow.tsx +152 -0
  209. package/src/{PopupMenu.tsx → view/PopupMenu.tsx} +36 -31
  210. package/src/view/Row.tsx +68 -0
  211. package/src/view/SizeTracker.tsx +20 -0
  212. package/src/view/Splitter.tsx +167 -112
  213. package/src/view/Tab.tsx +76 -42
  214. package/src/view/TabButton.tsx +39 -54
  215. package/src/view/TabButtonStamp.tsx +5 -9
  216. package/src/view/TabOverflowHook.tsx +14 -9
  217. package/src/view/TabSet.tsx +221 -176
  218. package/src/view/Utils.tsx +119 -39
  219. package/style/_base.scss +140 -34
  220. package/style/dark.css +140 -35
  221. package/style/dark.css.map +1 -1
  222. package/style/dark.scss +3 -1
  223. package/style/gray.css +139 -34
  224. package/style/gray.css.map +1 -1
  225. package/style/gray.scss +2 -0
  226. package/style/light.css +141 -36
  227. package/style/light.css.map +1 -1
  228. package/style/light.scss +4 -2
  229. package/style/rounded.css +697 -0
  230. package/style/rounded.css.map +1 -0
  231. package/style/rounded.scss +194 -0
  232. package/style/underline.css +139 -34
  233. package/style/underline.css.map +1 -1
  234. package/style/underline.scss +2 -0
  235. package/cypress.config.ts +0 -16
  236. package/lib/DragDrop.js +0 -316
  237. package/lib/DragDrop.js.map +0 -1
  238. package/lib/PopupMenu.js +0 -68
  239. package/lib/PopupMenu.js.map +0 -1
  240. package/lib/model/SplitterNode.js +0 -72
  241. package/lib/model/SplitterNode.js.map +0 -1
  242. package/lib/view/FloatingWindow.js +0 -123
  243. package/lib/view/FloatingWindow.js.map +0 -1
  244. package/lib/view/FloatingWindowTab.js +0 -19
  245. package/lib/view/FloatingWindowTab.js.map +0 -1
  246. package/lib/view/TabFloating.js +0 -66
  247. package/lib/view/TabFloating.js.map +0 -1
  248. package/src/DragDrop.ts +0 -392
  249. package/src/model/SplitterNode.ts +0 -78
  250. package/src/view/FloatingWindow.tsx +0 -140
  251. package/src/view/FloatingWindowTab.tsx +0 -29
  252. package/src/view/TabFloating.tsx +0 -101
@@ -2,7 +2,6 @@ import { Attribute } from "../Attribute";
2
2
  import { AttributeDefinitions } from "../AttributeDefinitions";
3
3
  import { DockLocation } from "../DockLocation";
4
4
  import { DropInfo } from "../DropInfo";
5
- import { Orientation } from "../Orientation";
6
5
  import { Rect } from "../Rect";
7
6
  import { Action } from "./Action";
8
7
  import { Actions } from "./Actions";
@@ -10,281 +9,73 @@ import { BorderNode } from "./BorderNode";
10
9
  import { BorderSet } from "./BorderSet";
11
10
  import { IDraggable } from "./IDraggable";
12
11
  import { IDropTarget } from "./IDropTarget";
13
- import { IJsonModel, ITabSetAttributes } from "./IJsonModel";
12
+ import { IJsonModel, IJsonPopout, ITabSetAttributes } from "./IJsonModel";
14
13
  import { Node } from "./Node";
15
14
  import { RowNode } from "./RowNode";
16
15
  import { TabNode } from "./TabNode";
17
16
  import { TabSetNode } from "./TabSetNode";
18
- import { adjustSelectedIndexAfterDock, adjustSelectedIndexAfterFloat, randomUUID } from "./Utils";
17
+ import { randomUUID } from "./Utils";
18
+ import { LayoutWindow } from "./LayoutWindow";
19
+ import { isOnScreen } from "../view/Utils";
19
20
 
20
21
  /** @internal */
21
- export interface ILayoutMetrics {
22
- headerBarSize: number;
23
- tabBarSize: number;
24
- borderBarSize: number;
25
- }
22
+ export const DefaultMin = 0;
23
+ /** @internal */
24
+ export const DefaultMax = 99999;
26
25
 
27
26
  /**
28
27
  * Class containing the Tree of Nodes used by the FlexLayout component
29
28
  */
30
29
  export class Model {
31
- /**
32
- * Loads the model from the given json object
33
- * @param json the json model to load
34
- * @returns {Model} a new Model object
35
- */
36
- static fromJson(json: IJsonModel) {
37
- const model = new Model();
38
- Model._attributeDefinitions.fromJson(json.global, model._attributes);
39
-
40
- if (json.borders) {
41
- model._borders = BorderSet._fromJson(json.borders, model);
42
- }
43
- model._root = RowNode._fromJson(json.layout, model);
44
- model._tidy(); // initial tidy of node tree
45
- return model;
46
- }
47
- /** @internal */
48
- private static _attributeDefinitions: AttributeDefinitions = Model._createAttributeDefinitions();
30
+ static MAIN_WINDOW_ID = "__main_window_id__";
49
31
 
50
32
  /** @internal */
51
- private static _createAttributeDefinitions(): AttributeDefinitions {
52
- const attributeDefinitions = new AttributeDefinitions();
53
-
54
- attributeDefinitions.add("legacyOverflowMenu", false).setType(Attribute.BOOLEAN);
55
- attributeDefinitions.add("enableEdgeDock", true).setType(Attribute.BOOLEAN);
56
- attributeDefinitions.add("rootOrientationVertical", false).setType(Attribute.BOOLEAN);
57
- attributeDefinitions.add("marginInsets", { top: 0, right: 0, bottom: 0, left: 0 })
58
- .setType("IInsets");
59
- attributeDefinitions.add("enableUseVisibility", false).setType(Attribute.BOOLEAN);
60
- attributeDefinitions.add("enableRotateBorderIcons", true).setType(Attribute.BOOLEAN);
61
-
62
- // splitter
63
- attributeDefinitions.add("splitterSize", -1).setType(Attribute.NUMBER);
64
- attributeDefinitions.add("splitterExtra", 0).setType(Attribute.NUMBER);
65
-
66
- // tab
67
- attributeDefinitions.add("tabEnableClose", true).setType(Attribute.BOOLEAN);
68
- attributeDefinitions.add("tabCloseType", 1).setType("ICloseType");
69
- attributeDefinitions.add("tabEnableFloat", false).setType(Attribute.BOOLEAN);
70
- attributeDefinitions.add("tabEnableDrag", true).setType(Attribute.BOOLEAN);
71
- attributeDefinitions.add("tabEnableRename", true).setType(Attribute.BOOLEAN);
72
- attributeDefinitions.add("tabContentClassName", undefined).setType(Attribute.STRING);
73
- attributeDefinitions.add("tabClassName", undefined).setType(Attribute.STRING);
74
- attributeDefinitions.add("tabIcon", undefined).setType(Attribute.STRING);
75
- attributeDefinitions.add("tabEnableRenderOnDemand", true).setType(Attribute.BOOLEAN);
76
- attributeDefinitions.add("tabDragSpeed", 0.3).setType(Attribute.NUMBER);
77
- attributeDefinitions.add("tabBorderWidth", -1).setType(Attribute.NUMBER);
78
- attributeDefinitions.add("tabBorderHeight", -1).setType(Attribute.NUMBER);
79
-
80
- // tabset
81
- attributeDefinitions.add("tabSetEnableDeleteWhenEmpty", true).setType(Attribute.BOOLEAN);
82
- attributeDefinitions.add("tabSetEnableDrop", true).setType(Attribute.BOOLEAN);
83
- attributeDefinitions.add("tabSetEnableDrag", true).setType(Attribute.BOOLEAN);
84
- attributeDefinitions.add("tabSetEnableDivide", true).setType(Attribute.BOOLEAN);
85
- attributeDefinitions.add("tabSetEnableMaximize", true).setType(Attribute.BOOLEAN);
86
- attributeDefinitions.add("tabSetEnableClose", false).setType(Attribute.BOOLEAN);
87
- attributeDefinitions.add("tabSetEnableSingleTabStretch", false).setType(Attribute.BOOLEAN);
88
- attributeDefinitions.add("tabSetAutoSelectTab", true).setType(Attribute.BOOLEAN);
89
- attributeDefinitions.add("tabSetClassNameTabStrip", undefined).setType(Attribute.STRING);
90
- attributeDefinitions.add("tabSetClassNameHeader", undefined).setType(Attribute.STRING);
91
- attributeDefinitions.add("tabSetEnableTabStrip", true).setType(Attribute.BOOLEAN);
92
- attributeDefinitions.add("tabSetHeaderHeight", 0).setType(Attribute.NUMBER);
93
- attributeDefinitions.add("tabSetTabStripHeight", 0).setType(Attribute.NUMBER);
94
- attributeDefinitions.add("tabSetMarginInsets", { top: 0, right: 0, bottom: 0, left: 0 })
95
- .setType("IInsets");
96
- attributeDefinitions.add("tabSetBorderInsets", { top: 0, right: 0, bottom: 0, left: 0 })
97
- .setType("IInsets");
98
- attributeDefinitions.add("tabSetTabLocation", "top").setType("ITabLocation");
99
- attributeDefinitions.add("tabSetMinWidth", 0).setType(Attribute.NUMBER);
100
- attributeDefinitions.add("tabSetMinHeight", 0).setType(Attribute.NUMBER);
101
-
102
- // border
103
- attributeDefinitions.add("borderSize", 200).setType(Attribute.NUMBER);
104
- attributeDefinitions.add("borderMinSize", 0).setType(Attribute.NUMBER);
105
- attributeDefinitions.add("borderBarSize", 0).setType(Attribute.NUMBER);
106
- attributeDefinitions.add("borderEnableDrop", true).setType(Attribute.BOOLEAN);
107
- attributeDefinitions.add("borderAutoSelectTabWhenOpen", true).setType(Attribute.BOOLEAN);
108
- attributeDefinitions.add("borderAutoSelectTabWhenClosed", false).setType(Attribute.BOOLEAN);
109
- attributeDefinitions.add("borderClassName", undefined).setType(Attribute.STRING);
110
- attributeDefinitions.add("borderEnableAutoHide", false).setType(Attribute.BOOLEAN);
111
-
112
- return attributeDefinitions;
113
- }
33
+ private static attributeDefinitions: AttributeDefinitions = Model.createAttributeDefinitions();
114
34
 
115
35
  /** @internal */
116
- private _attributes: Record<string, any>;
117
- /** @internal */
118
- private _idMap: Record<string, Node>;
119
- /** @internal */
120
- private _changeListener?: (action: Action) => void;
121
- /** @internal */
122
- private _root?: RowNode;
123
- /** @internal */
124
- private _borders: BorderSet;
36
+ private attributes: Record<string, any>;
125
37
  /** @internal */
126
- private _onAllowDrop?: (dragNode: Node, dropInfo: DropInfo) => boolean;
38
+ private idMap: Map<string, Node>;
127
39
  /** @internal */
128
- private _maximizedTabSet?: TabSetNode;
40
+ private changeListeners: ((action: Action) => void)[];
129
41
  /** @internal */
130
- private _activeTabSet?: TabSetNode;
42
+ private borders: BorderSet;
131
43
  /** @internal */
132
- private _borderRects: { inner: Rect; outer: Rect } = { inner: Rect.empty(), outer: Rect.empty() };
44
+ private onAllowDrop?: (dragNode: Node, dropInfo: DropInfo) => boolean;
133
45
  /** @internal */
134
- private _pointerFine: boolean;
46
+ private onCreateTabSet?: (tabNode?: TabNode) => ITabSetAttributes;
135
47
  /** @internal */
136
- private _onCreateTabSet?: (tabNode?: TabNode) => ITabSetAttributes;
48
+ private windows: Map<string, LayoutWindow>;
137
49
  /** @internal */
138
- private _showHiddenBorder: DockLocation;
139
-
50
+ private rootWindow: LayoutWindow;
140
51
 
141
52
  /**
142
53
  * 'private' constructor. Use the static method Model.fromJson(json) to create a model
143
54
  * @internal
144
55
  */
145
-
146
- private constructor() {
147
- this._attributes = {};
148
- this._idMap = {};
149
- this._borders = new BorderSet(this);
150
- this._pointerFine = true;
151
- this._showHiddenBorder = DockLocation.CENTER;
152
- }
153
-
154
- /** @internal */
155
- _setChangeListener(listener: ((action: Action) => void) | undefined) {
156
- this._changeListener = listener;
157
- }
158
-
159
- /**
160
- * Get the currently active tabset node
161
- */
162
- getActiveTabset() {
163
- if (this._activeTabSet && this.getNodeById(this._activeTabSet.getId())) {
164
- return this._activeTabSet;
165
- } else {
166
- return undefined;
167
- }
168
- }
169
-
170
- /** @internal */
171
- _getShowHiddenBorder() {
172
- return this._showHiddenBorder;
173
- }
174
-
175
- /** @internal */
176
- _setShowHiddenBorder(location: DockLocation) {
177
- this._showHiddenBorder = location;
178
- }
179
-
180
- /** @internal */
181
- _setActiveTabset(tabsetNode: TabSetNode | undefined) {
182
- this._activeTabSet = tabsetNode;
183
- }
184
-
185
- /**
186
- * Get the currently maximized tabset node
187
- */
188
- getMaximizedTabset() {
189
- return this._maximizedTabSet;
190
- }
191
-
192
- /** @internal */
193
- _setMaximizedTabset(tabsetNode: (TabSetNode | undefined)) {
194
- this._maximizedTabSet = tabsetNode;
195
- }
196
-
197
- /**
198
- * Gets the root RowNode of the model
199
- * @returns {RowNode}
200
- */
201
- getRoot() {
202
- return this._root as RowNode;
203
- }
204
-
205
- isRootOrientationVertical() {
206
- return this._attributes.rootOrientationVertical as boolean;
207
- }
208
-
209
- isUseVisibility() {
210
- return this._attributes.enableUseVisibility as boolean;
211
- }
212
-
213
- isEnableRotateBorderIcons() {
214
- return this._attributes.enableRotateBorderIcons as boolean;
215
- }
216
-
217
- /**
218
- * Gets the
219
- * @returns {BorderSet|*}
220
- */
221
- getBorderSet() {
222
- return this._borders;
223
- }
224
-
225
- /** @internal */
226
- _getOuterInnerRects() {
227
- return this._borderRects;
228
- }
229
-
230
- /** @internal */
231
- _getPointerFine() {
232
- return this._pointerFine;
233
- }
234
-
235
- /** @internal */
236
- _setPointerFine(pointerFine: boolean) {
237
- this._pointerFine = pointerFine;
238
- }
239
-
240
- /**
241
- * Visits all the nodes in the model and calls the given function for each
242
- * @param fn a function that takes visited node and a integer level as parameters
243
- */
244
- visitNodes(fn: (node: Node, level: number) => void) {
245
- this._borders._forEachNode(fn);
246
- (this._root as RowNode)._forEachNode(fn, 0);
247
- }
248
-
249
- /**
250
- * Gets a node by its id
251
- * @param id the id to find
252
- */
253
- getNodeById(id: string): Node | undefined {
254
- return this._idMap[id];
255
- }
256
-
257
- /**
258
- * Finds the first/top left tab set of the given node.
259
- * @param node The top node you want to begin searching from, deafults to the root node
260
- * @returns The first Tab Set
261
- */
262
- getFirstTabSet(node = this._root as Node): Node
263
- {
264
- const child = node.getChildren()[0];
265
- if (child instanceof TabSetNode)
266
- {
267
- return child;
268
- }
269
- else
270
- {
271
- return this.getFirstTabSet(child);
272
- }
56
+ protected constructor() {
57
+ this.attributes = {};
58
+ this.idMap = new Map();
59
+ this.borders = new BorderSet(this);
60
+ this.windows = new Map<string, LayoutWindow>();
61
+ this.rootWindow = new LayoutWindow(Model.MAIN_WINDOW_ID, Rect.empty());
62
+ this.windows.set(Model.MAIN_WINDOW_ID, this.rootWindow);
63
+ this.changeListeners = [];
273
64
  }
274
65
 
275
66
  /**
276
67
  * Update the node tree by performing the given action,
277
68
  * Actions should be generated via static methods on the Actions class
278
69
  * @param action the action to perform
279
- * @returns added Node for Actions.addNode; undefined otherwise
70
+ * @returns added Node for Actions.addNode, windowId for createWindow
280
71
  */
281
- doAction(action: Action): Node | undefined {
72
+ doAction(action: Action): any {
282
73
  let returnVal = undefined;
283
74
  // console.log(action);
284
75
  switch (action.type) {
285
76
  case Actions.ADD_NODE: {
286
77
  const newNode = new TabNode(this, action.data.json, true);
287
- const toNode = this._idMap[action.data.toNode] as Node & IDraggable;
78
+ const toNode = this.idMap.get(action.data.toNode) as Node & IDraggable;
288
79
  if (toNode instanceof TabSetNode || toNode instanceof BorderNode || toNode instanceof RowNode) {
289
80
  toNode.drop(newNode, DockLocation.getByName(action.data.location), action.data.index, action.data.select);
290
81
  returnVal = newNode;
@@ -292,24 +83,31 @@ export class Model {
292
83
  break;
293
84
  }
294
85
  case Actions.MOVE_NODE: {
295
- const fromNode = this._idMap[action.data.fromNode] as Node & IDraggable;
296
- if (fromNode instanceof TabNode || fromNode instanceof TabSetNode) {
297
- const toNode = this._idMap[action.data.toNode] as Node & IDropTarget;
86
+ const fromNode = this.idMap.get(action.data.fromNode) as Node & IDraggable;
87
+
88
+ if (fromNode instanceof TabNode || fromNode instanceof TabSetNode || fromNode instanceof RowNode) {
89
+ if (fromNode === this.getMaximizedTabset(fromNode.getWindowId())) {
90
+ const fromWindow = this.windows.get(fromNode.getWindowId())!;
91
+ fromWindow.maximizedTabSet = undefined;
92
+ }
93
+ const toNode = this.idMap.get(action.data.toNode) as Node & IDropTarget;
298
94
  if (toNode instanceof TabSetNode || toNode instanceof BorderNode || toNode instanceof RowNode) {
299
95
  toNode.drop(fromNode, DockLocation.getByName(action.data.location), action.data.index, action.data.select);
300
96
  }
301
97
  }
98
+ this.removeEmptyWindows();
302
99
  break;
303
100
  }
304
101
  case Actions.DELETE_TAB: {
305
- const node = this._idMap[action.data.node];
102
+ const node = this.idMap.get(action.data.node);
306
103
  if (node instanceof TabNode) {
307
- node._delete();
104
+ node.delete();
308
105
  }
106
+ this.removeEmptyWindows();
309
107
  break;
310
108
  }
311
109
  case Actions.DELETE_TABSET: {
312
- const node = this._idMap[action.data.node];
110
+ const node = this.idMap.get(action.data.node);
313
111
 
314
112
  if (node instanceof TabSetNode) {
315
113
  // first delete all child tabs that are closeable
@@ -317,273 +115,597 @@ export class Model {
317
115
  for (let i = 0; i < children.length; i++) {
318
116
  const child = children[i];
319
117
  if ((child as TabNode).isEnableClose()) {
320
- (child as TabNode)._delete();
118
+ (child as TabNode).delete();
321
119
  }
322
120
  }
323
121
 
324
122
  if (node.getChildren().length === 0) {
325
- node._delete();
123
+ node.delete();
326
124
  }
327
- this._tidy();
125
+ this.tidy();
328
126
  }
127
+ this.removeEmptyWindows();
329
128
  break;
330
129
  }
331
- case Actions.FLOAT_TAB: {
332
- const node = this._idMap[action.data.node];
333
- if (node instanceof TabNode) {
334
- node._setFloating(true);
335
- adjustSelectedIndexAfterFloat(node);
130
+ case Actions.POPOUT_TABSET: {
131
+ const node = this.idMap.get(action.data.node);
132
+ if (node instanceof TabSetNode) {
133
+ const isMaximized = node.isMaximized();
134
+ const oldLayoutWindow = this.windows.get(node.getWindowId())!;
135
+ const windowId = randomUUID()
136
+ const layoutWindow = new LayoutWindow(windowId, oldLayoutWindow.toScreenRectFunction(node.getRect()));
137
+ const json = {
138
+ type: "row",
139
+ children: []
140
+ }
141
+ const row = RowNode.fromJson(json, this, layoutWindow);
142
+ layoutWindow.root = row;
143
+ this.windows.set(windowId, layoutWindow);
144
+ row.drop(node, DockLocation.CENTER, 0);
145
+
146
+ if (isMaximized) {
147
+ this.rootWindow.maximizedTabSet = undefined;
148
+ }
336
149
  }
150
+ this.removeEmptyWindows();
337
151
  break;
338
152
  }
339
- case Actions.UNFLOAT_TAB: {
340
- const node = this._idMap[action.data.node];
153
+ case Actions.POPOUT_TAB: {
154
+ const node = this.idMap.get(action.data.node);
341
155
  if (node instanceof TabNode) {
342
- node._setFloating(false);
343
- adjustSelectedIndexAfterDock(node);
156
+ const windowId = randomUUID()
157
+ let r = Rect.empty();
158
+ if (node.getParent() instanceof TabSetNode) {
159
+ r = node.getParent()!.getRect();
160
+ } else {
161
+ r = (node.getParent() as BorderNode).getContentRect();
162
+ }
163
+ const oldLayoutWindow = this.windows.get(node.getWindowId())!;
164
+ const layoutWindow = new LayoutWindow(windowId, oldLayoutWindow.toScreenRectFunction(r));
165
+ const tabsetId = randomUUID();
166
+ const json = {
167
+ type: "row",
168
+ children: [
169
+ { type: "tabset", id: tabsetId }
170
+ ]
171
+ }
172
+ const row = RowNode.fromJson(json, this, layoutWindow);
173
+ layoutWindow.root = row;
174
+ this.windows.set(windowId, layoutWindow);
175
+
176
+ const tabset = this.idMap.get(tabsetId) as TabSetNode & IDropTarget;
177
+ tabset.drop(node, DockLocation.CENTER, 0, true);
178
+ }
179
+ this.removeEmptyWindows();
180
+ break;
181
+ }
182
+ case Actions.CLOSE_WINDOW: {
183
+ const window = this.windows.get(action.data.windowId);
184
+ if (window) {
185
+ this.rootWindow.root?.drop(window?.root!, DockLocation.CENTER, -1);
186
+ this.rootWindow.visitNodes((node, level) => {
187
+ if (node instanceof RowNode) {
188
+ node.setWindowId(Model.MAIN_WINDOW_ID);
189
+ }
190
+ })
191
+
192
+ // this.getFirstTabSet().drop(window?.root!,DockLocation.CENTER, -1);
193
+
194
+ this.windows.delete(action.data.windowId);
344
195
  }
345
196
  break;
346
197
  }
198
+ case Actions.CREATE_WINDOW: {
199
+ const windowId = randomUUID();
200
+ const layoutWindow = new LayoutWindow(windowId, Rect.fromJson(action.data.rect));
201
+ const row = RowNode.fromJson(action.data.layout, this, layoutWindow);
202
+ layoutWindow.root = row;
203
+ this.windows.set(windowId, layoutWindow);
204
+ returnVal = windowId;
205
+ break;
206
+ }
347
207
  case Actions.RENAME_TAB: {
348
- const node = this._idMap[action.data.node];
208
+ const node = this.idMap.get(action.data.node);
349
209
  if (node instanceof TabNode) {
350
- node._setName(action.data.text);
210
+ node.setName(action.data.text);
351
211
  }
352
212
  break;
353
213
  }
354
214
  case Actions.SELECT_TAB: {
355
- const tabNode = this._idMap[action.data.tabNode];
215
+ const tabNode = this.idMap.get(action.data.tabNode);
216
+ const windowId = action.data.windowId ? action.data.windowId : Model.MAIN_WINDOW_ID;
217
+ const window = this.windows.get(windowId)!;
356
218
  if (tabNode instanceof TabNode) {
357
219
  const parent = tabNode.getParent() as Node;
358
220
  const pos = parent.getChildren().indexOf(tabNode);
359
221
 
360
222
  if (parent instanceof BorderNode) {
361
223
  if (parent.getSelected() === pos) {
362
- parent._setSelected(-1);
224
+ parent.setSelected(-1);
363
225
  } else {
364
- parent._setSelected(pos);
226
+ parent.setSelected(pos);
365
227
  }
366
228
  } else if (parent instanceof TabSetNode) {
367
229
  if (parent.getSelected() !== pos) {
368
- parent._setSelected(pos);
230
+ parent.setSelected(pos);
369
231
  }
370
- this._activeTabSet = parent;
232
+ window.activeTabSet = parent;
371
233
  }
372
234
  }
373
235
  break;
374
236
  }
375
237
  case Actions.SET_ACTIVE_TABSET: {
238
+ const windowId = action.data.windowId ? action.data.windowId : Model.MAIN_WINDOW_ID;
239
+ const window = this.windows.get(windowId)!;
376
240
  if (action.data.tabsetNode === undefined) {
377
- this._activeTabSet = undefined;
241
+ window.activeTabSet = undefined;
378
242
  } else {
379
- const tabsetNode = this._idMap[action.data.tabsetNode];
243
+ const tabsetNode = this.idMap.get(action.data.tabsetNode);
380
244
  if (tabsetNode instanceof TabSetNode) {
381
- this._activeTabSet = tabsetNode;
245
+ window.activeTabSet = tabsetNode;
382
246
  }
383
247
  }
384
248
  break;
385
249
  }
386
- case Actions.ADJUST_SPLIT: {
387
- const node1 = this._idMap[action.data.node1];
388
- const node2 = this._idMap[action.data.node2];
389
-
390
- if ((node1 instanceof TabSetNode || node1 instanceof RowNode) && (node2 instanceof TabSetNode || node2 instanceof RowNode)) {
391
- this._adjustSplitSide(node1, action.data.weight1, action.data.pixelWidth1);
392
- this._adjustSplitSide(node2, action.data.weight2, action.data.pixelWidth2);
250
+ case Actions.ADJUST_WEIGHTS: {
251
+ const row = this.idMap.get(action.data.nodeId) as RowNode;
252
+ const c = row.getChildren();
253
+ for (let i = 0; i < c.length; i++) {
254
+ const n = c[i] as TabSetNode | RowNode;
255
+ n.setWeight(action.data.weights[i]);
393
256
  }
394
257
  break;
395
258
  }
396
259
  case Actions.ADJUST_BORDER_SPLIT: {
397
- const node = this._idMap[action.data.node];
260
+ const node = this.idMap.get(action.data.node);
398
261
  if (node instanceof BorderNode) {
399
- node._setSize(action.data.pos);
262
+ node.setSize(action.data.pos);
400
263
  }
401
264
  break;
402
265
  }
403
266
  case Actions.MAXIMIZE_TOGGLE: {
404
- const node = this._idMap[action.data.node];
267
+ const windowId = action.data.windowId ? action.data.windowId : Model.MAIN_WINDOW_ID;
268
+ const window = this.windows.get(windowId)!;
269
+ const node = this.idMap.get(action.data.node);
405
270
  if (node instanceof TabSetNode) {
406
- if (node === this._maximizedTabSet) {
407
- this._maximizedTabSet = undefined;
271
+ if (node === window.maximizedTabSet) {
272
+ window.maximizedTabSet = undefined;
408
273
  } else {
409
- this._maximizedTabSet = node;
410
- this._activeTabSet = node;
274
+ window.maximizedTabSet = node;
275
+ window.activeTabSet = node;
411
276
  }
412
277
  }
413
278
 
414
279
  break;
415
280
  }
416
281
  case Actions.UPDATE_MODEL_ATTRIBUTES: {
417
- this._updateAttrs(action.data.json);
282
+ this.updateAttrs(action.data.json);
418
283
  break;
419
284
  }
420
285
 
421
286
  case Actions.UPDATE_NODE_ATTRIBUTES: {
422
- const node = this._idMap[action.data.node];
423
- node._updateAttrs(action.data.json);
287
+ const node = this.idMap.get(action.data.node)!;
288
+ node.updateAttrs(action.data.json);
424
289
  break;
425
290
  }
426
291
  default:
427
292
  break;
428
293
  }
429
294
 
430
- this._updateIdMap();
295
+ this.updateIdMap();
431
296
 
432
- if (this._changeListener !== undefined) {
433
- this._changeListener(action);
297
+ for (const listener of this.changeListeners) {
298
+ listener(action);
434
299
  }
435
300
 
436
301
  return returnVal;
437
302
  }
438
303
 
439
- /** @internal */
440
- _updateIdMap() {
441
- // regenerate idMap to stop it building up
442
- this._idMap = {};
443
- this.visitNodes((node) => (this._idMap[node.getId()] = node));
444
- // console.log(JSON.stringify(Object.keys(this._idMap)));
304
+
305
+
306
+ /**
307
+ * Get the currently active tabset node
308
+ */
309
+ getActiveTabset(windowId: string = Model.MAIN_WINDOW_ID) {
310
+ const window = this.windows.get(windowId);
311
+ if (window && window.activeTabSet && this.getNodeById(window.activeTabSet.getId())) {
312
+ return window.activeTabSet;
313
+ } else {
314
+ return undefined;
315
+ }
445
316
  }
446
317
 
447
- /** @internal */
448
- _adjustSplitSide(node: TabSetNode | RowNode, weight: number, pixels: number) {
449
- node._setWeight(weight);
450
- if (node.getWidth() != null && node.getOrientation() === Orientation.VERT) {
451
- node._updateAttrs({ width: pixels });
452
- } else if (node.getHeight() != null && node.getOrientation() === Orientation.HORZ) {
453
- node._updateAttrs({ height: pixels });
318
+ /**
319
+ * Get the currently maximized tabset node
320
+ */
321
+ getMaximizedTabset(windowId: string = Model.MAIN_WINDOW_ID) {
322
+ return this.windows.get(windowId)!.maximizedTabSet;
323
+ }
324
+
325
+ /**
326
+ * Gets the root RowNode of the model
327
+ * @returns {RowNode}
328
+ */
329
+ getRoot(windowId: string = Model.MAIN_WINDOW_ID) {
330
+ return this.windows.get(windowId)!.root!;
331
+ }
332
+
333
+ isRootOrientationVertical() {
334
+ return this.attributes.rootOrientationVertical as boolean;
335
+ }
336
+
337
+ isEnableRotateBorderIcons() {
338
+ return this.attributes.enableRotateBorderIcons as boolean;
339
+ }
340
+
341
+ /**
342
+ * Gets the
343
+ * @returns {BorderSet|*}
344
+ */
345
+ getBorderSet() {
346
+ return this.borders;
347
+ }
348
+
349
+ getwindowsMap() {
350
+ return this.windows;
351
+ }
352
+
353
+ /**
354
+ * Visits all the nodes in the model and calls the given function for each
355
+ * @param fn a function that takes visited node and a integer level as parameters
356
+ */
357
+ visitNodes(fn: (node: Node, level: number) => void) {
358
+ this.borders.forEachNode(fn);
359
+ for (const [_, w] of this.windows) {
360
+ w.root!.forEachNode(fn, 0);
361
+ }
362
+ }
363
+
364
+ visitWindowNodes(windowId: string, fn: (node: Node, level: number) => void) {
365
+ if (this.windows.has(windowId)) {
366
+ if (windowId === Model.MAIN_WINDOW_ID) {
367
+ this.borders.forEachNode(fn);
368
+ }
369
+ this.windows.get(windowId)!.visitNodes(fn);
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Gets a node by its id
375
+ * @param id the id to find
376
+ */
377
+ getNodeById(id: string): Node | undefined {
378
+ return this.idMap.get(id);
379
+ }
380
+
381
+ /**
382
+ * Finds the first/top left tab set of the given node.
383
+ * @param node The top node you want to begin searching from, deafults to the root node
384
+ * @returns The first Tab Set
385
+ */
386
+ getFirstTabSet(node = this.windows.get(Model.MAIN_WINDOW_ID)!.root as Node): TabSetNode {
387
+ const child = node.getChildren()[0];
388
+ if (child instanceof TabSetNode) {
389
+ return child;
390
+ }
391
+ else {
392
+ return this.getFirstTabSet(child);
454
393
  }
455
394
  }
456
395
 
396
+ /**
397
+ * Loads the model from the given json object
398
+ * @param json the json model to load
399
+ * @returns {Model} a new Model object
400
+ */
401
+ static fromJson(json: IJsonModel) {
402
+ const model = new Model();
403
+ Model.attributeDefinitions.fromJson(json.global, model.attributes);
404
+
405
+ if (json.borders) {
406
+ model.borders = BorderSet.fromJson(json.borders, model);
407
+ }
408
+ if (json.popouts) {
409
+ let i= 0;
410
+ let top = 100;
411
+ let left = 100;
412
+ for (const windowId in json.popouts) {
413
+ const windowJson = json.popouts[windowId];
414
+ const layoutWindow = LayoutWindow.fromJson(windowJson, model, windowId);
415
+ model.windows.set(windowId, layoutWindow);
416
+ // offscreen windows will reload cascaded (since cannot reposition)
417
+ if (!isOnScreen(layoutWindow.rect)) {
418
+ layoutWindow.rect = new Rect(top + i*50, left+ i*50, 600, 400);
419
+ i++;
420
+ }
421
+ }
422
+ }
423
+
424
+ model.rootWindow.root = RowNode.fromJson(json.layout, model, model.getwindowsMap().get(Model.MAIN_WINDOW_ID)!);
425
+ model.tidy(); // initial tidy of node tree
426
+ return model;
427
+ }
428
+
457
429
  /**
458
430
  * Converts the model to a json object
459
431
  * @returns {IJsonModel} json object that represents this model
460
432
  */
461
433
  toJson(): IJsonModel {
462
434
  const global: any = {};
463
- Model._attributeDefinitions.toJson(global, this._attributes);
435
+ Model.attributeDefinitions.toJson(global, this.attributes);
464
436
 
465
437
  // save state of nodes
466
438
  this.visitNodes((node) => {
467
- node._fireEvent("save", undefined);
439
+ node.fireEvent("save", {});
468
440
  });
469
441
 
470
- return { global, borders: this._borders._toJson(), layout: (this._root as RowNode).toJson() };
471
- }
472
-
473
- getSplitterSize() {
474
- let splitterSize = this._attributes.splitterSize as number;
475
- if (splitterSize === -1) {
476
- // use defaults
477
- splitterSize = this._pointerFine ? 8 : 12; // larger for mobile
442
+ const windows: Record<string, IJsonPopout> = {};
443
+ for (const [id, window] of this.windows) {
444
+ if (id !== Model.MAIN_WINDOW_ID) {
445
+ windows[id] = window.toJson();
446
+ }
478
447
  }
479
- return splitterSize;
448
+
449
+ return {
450
+ global,
451
+ borders: this.borders.toJson(),
452
+ layout: this.rootWindow.root!.toJson(),
453
+ popouts: windows
454
+ };
480
455
  }
481
456
 
482
- isLegacyOverflowMenu() {
483
- return this._attributes.legacyOverflowMenu as boolean;
457
+ getSplitterSize() {
458
+ return this.attributes.splitterSize as number;
484
459
  }
485
460
 
486
461
  getSplitterExtra() {
487
- return this._attributes.splitterExtra as number;
462
+ return this.attributes.splitterExtra as number;
488
463
  }
489
464
 
490
465
  isEnableEdgeDock() {
491
- return this._attributes.enableEdgeDock as boolean;
466
+ return this.attributes.enableEdgeDock as boolean;
467
+ }
468
+
469
+ isSplitterEnableHandle() {
470
+ return this.attributes.splitterEnableHandle as boolean;
471
+ }
472
+
473
+ /**
474
+ * Sets a function to allow/deny dropping a node
475
+ * @param onAllowDrop function that takes the drag node and DropInfo and returns true if the drop is allowed
476
+ */
477
+ setOnAllowDrop(onAllowDrop: (dragNode: Node, dropInfo: DropInfo) => boolean) {
478
+ this.onAllowDrop = onAllowDrop;
479
+ }
480
+
481
+ /**
482
+ * set callback called when a new TabSet is created.
483
+ * The tabNode can be undefined if it's the auto created first tabset in the root row (when the last
484
+ * tab is deleted, the root tabset can be recreated)
485
+ * @param onCreateTabSet
486
+ */
487
+ setOnCreateTabSet(onCreateTabSet: (tabNode?: TabNode) => ITabSetAttributes) {
488
+ this.onCreateTabSet = onCreateTabSet;
489
+ }
490
+
491
+ addChangeListener(listener: ((action: Action) => void)) {
492
+ this.changeListeners.push(listener);
492
493
  }
493
494
 
495
+ removeChangeListener(listener: ((action: Action) => void)) {
496
+ const pos = this.changeListeners.findIndex(l => l === listener);
497
+ if (pos !== -1) {
498
+ this.changeListeners.splice(pos, 1);
499
+ }
500
+ }
501
+
502
+ toString() {
503
+ return JSON.stringify(this.toJson());
504
+ }
505
+
506
+ /***********************internal ********************************/
507
+
494
508
  /** @internal */
495
- _addNode(node: Node) {
496
- const id = node.getId();
497
- if (this._idMap[id] !== undefined) {
498
- throw new Error(`Error: each node must have a unique id, duplicate id:${node.getId()}`);
509
+ removeEmptyWindows() {
510
+ const emptyWindows = new Set<string>();
511
+ for (const [windowId] of this.windows) {
512
+ if (windowId !== Model.MAIN_WINDOW_ID) {
513
+ let count = 0;
514
+ this.visitWindowNodes(windowId, (node) => {
515
+ if (node instanceof TabNode) {
516
+ count++;
517
+ }
518
+ });
519
+ if (count === 0) {
520
+ emptyWindows.add(windowId);
521
+ }
522
+ }
499
523
  }
500
524
 
501
- if (node.getType() !== "splitter") {
502
- this._idMap[id] = node;
525
+ for (const windowId of emptyWindows) {
526
+ this.windows.delete(windowId);
527
+ }
528
+ }
529
+ /** @internal */
530
+ setActiveTabset(tabsetNode: TabSetNode | undefined, windowId: string) {
531
+ const window = this.windows.get(windowId);
532
+ if (window) {
533
+ if (tabsetNode) {
534
+ window.activeTabSet = tabsetNode;
535
+ } else {
536
+ window.activeTabSet = undefined;
537
+ }
503
538
  }
504
539
  }
505
540
 
506
541
  /** @internal */
507
- _layout(rect: Rect, metrics: ILayoutMetrics) {
508
- // let start = Date.now();
509
- this._borderRects = this._borders._layoutBorder({ outer: rect, inner: rect }, metrics);
510
- rect = this._borderRects.inner.removeInsets(this._getAttribute("marginInsets"));
542
+ setMaximizedTabset(tabsetNode: (TabSetNode | undefined), windowId: string) {
543
+ const window = this.windows.get(windowId);
544
+ if (window) {
545
+ if (tabsetNode) {
546
+ window.maximizedTabSet = tabsetNode;
547
+ } else {
548
+ window.maximizedTabSet = undefined;
549
+ }
550
+ }
551
+ }
511
552
 
512
- this._root?.calcMinSize();
513
- (this._root as RowNode)._layout(rect, metrics);
514
- // console.log("layout time: " + (Date.now() - start));
515
- return rect;
553
+ /** @internal */
554
+ updateIdMap() {
555
+ // regenerate idMap to stop it building up
556
+ this.idMap.clear();
557
+ this.visitNodes((node) => {
558
+ this.idMap.set(node.getId(), node)
559
+ // if (node instanceof RowNode) {
560
+ // node.normalizeWeights();
561
+ // }
562
+ });
563
+ // console.log(JSON.stringify(Object.keys(this._idMap)));
516
564
  }
517
565
 
518
566
  /** @internal */
519
- _findDropTargetNode(dragNode: Node & IDraggable, x: number, y: number) {
520
- let node = (this._root as RowNode)._findDropTargetNode(dragNode, x, y);
521
- if (node === undefined) {
522
- node = this._borders._findDropTargetNode(dragNode, x, y);
567
+ addNode(node: Node) {
568
+ const id = node.getId();
569
+ if (this.idMap.has(id)) {
570
+ throw new Error(`Error: each node must have a unique id, duplicate id:${node.getId()}`);
571
+ }
572
+
573
+ this.idMap.set(id, node);
574
+ }
575
+
576
+ /** @internal */
577
+ findDropTargetNode(windowId: string, dragNode: Node & IDraggable, x: number, y: number) {
578
+ let node = (this.windows.get(windowId)!.root as RowNode).findDropTargetNode(windowId, dragNode, x, y);
579
+ if (node === undefined && windowId === Model.MAIN_WINDOW_ID) {
580
+ node = this.borders.findDropTargetNode(dragNode, x, y);
523
581
  }
524
582
  return node;
525
583
  }
526
584
 
527
585
  /** @internal */
528
- _tidy() {
586
+ tidy() {
529
587
  // console.log("before _tidy", this.toString());
530
- (this._root as RowNode)._tidy();
588
+ for (const [_, window] of this.windows) {
589
+ window.root!.tidy();
590
+ }
531
591
  // console.log("after _tidy", this.toString());
532
592
  }
533
593
 
534
594
  /** @internal */
535
- _updateAttrs(json: any) {
536
- Model._attributeDefinitions.update(json, this._attributes);
595
+ updateAttrs(json: any) {
596
+ Model.attributeDefinitions.update(json, this.attributes);
537
597
  }
538
598
 
539
599
  /** @internal */
540
- _nextUniqueId() {
600
+ nextUniqueId() {
541
601
  return '#' + randomUUID();
542
602
  }
543
603
 
544
604
  /** @internal */
545
- _getAttribute(name: string): any {
546
- return this._attributes[name];
605
+ getAttribute(name: string): any {
606
+ return this.attributes[name];
547
607
  }
548
608
 
549
- /**
550
- * Sets a function to allow/deny dropping a node
551
- * @param onAllowDrop function that takes the drag node and DropInfo and returns true if the drop is allowed
552
- */
553
- setOnAllowDrop(onAllowDrop: (dragNode: Node, dropInfo: DropInfo) => boolean) {
554
- this._onAllowDrop = onAllowDrop;
609
+ /** @internal */
610
+ getOnAllowDrop() {
611
+ return this.onAllowDrop;
555
612
  }
556
613
 
557
614
  /** @internal */
558
- _getOnAllowDrop() {
559
- return this._onAllowDrop;
615
+ getOnCreateTabSet() {
616
+ return this.onCreateTabSet;
560
617
  }
561
618
 
562
- /**
563
- * set callback called when a new TabSet is created.
564
- * The tabNode can be undefined if it's the auto created first tabset in the root row (when the last
565
- * tab is deleted, the root tabset can be recreated)
566
- * @param onCreateTabSet
567
- */
568
- setOnCreateTabSet(onCreateTabSet: (tabNode?: TabNode) => ITabSetAttributes) {
569
- this._onCreateTabSet = onCreateTabSet;
619
+ static toTypescriptInterfaces() {
620
+ Model.attributeDefinitions.pairAttributes("RowNode", RowNode.getAttributeDefinitions());
621
+ Model.attributeDefinitions.pairAttributes("TabSetNode", TabSetNode.getAttributeDefinitions());
622
+ Model.attributeDefinitions.pairAttributes("TabNode", TabNode.getAttributeDefinitions());
623
+ Model.attributeDefinitions.pairAttributes("BorderNode", BorderNode.getAttributeDefinitions());
624
+
625
+ let sb = [];
626
+ sb.push(Model.attributeDefinitions.toTypescriptInterface("Global", undefined));
627
+ sb.push(RowNode.getAttributeDefinitions().toTypescriptInterface("Row", Model.attributeDefinitions));
628
+ sb.push(TabSetNode.getAttributeDefinitions().toTypescriptInterface("TabSet", Model.attributeDefinitions));
629
+ sb.push(TabNode.getAttributeDefinitions().toTypescriptInterface("Tab", Model.attributeDefinitions));
630
+ sb.push(BorderNode.getAttributeDefinitions().toTypescriptInterface("Border", Model.attributeDefinitions));
631
+ console.log(sb.join("\n"));
570
632
  }
571
633
 
572
634
  /** @internal */
573
- _getOnCreateTabSet() {
574
- return this._onCreateTabSet;
575
- }
635
+ private static createAttributeDefinitions(): AttributeDefinitions {
636
+ const attributeDefinitions = new AttributeDefinitions();
576
637
 
577
- static toTypescriptInterfaces() {
578
- console.log(Model._attributeDefinitions.toTypescriptInterface("Global", undefined));
579
- console.log(RowNode.getAttributeDefinitions().toTypescriptInterface("Row", Model._attributeDefinitions));
580
- console.log(TabSetNode.getAttributeDefinitions().toTypescriptInterface("TabSet", Model._attributeDefinitions));
581
- console.log(TabNode.getAttributeDefinitions().toTypescriptInterface("Tab", Model._attributeDefinitions));
582
- console.log(BorderNode.getAttributeDefinitions().toTypescriptInterface("Border", Model._attributeDefinitions));
583
- }
638
+ attributeDefinitions.add("enableEdgeDock", true).setType(Attribute.BOOLEAN).setDescription(
639
+ `enable docking to the edges of the layout, this will show the edge indicators`
640
+ );
641
+ attributeDefinitions.add("rootOrientationVertical", false).setType(Attribute.BOOLEAN).setDescription(
642
+ `the top level 'row' will layout horizontally by default, set this option true to make it layout vertically`
643
+ );
644
+ attributeDefinitions.add("enableRotateBorderIcons", true).setType(Attribute.BOOLEAN).setDescription(
645
+ `boolean indicating if tab icons should rotate with the text in the left and right borders`
646
+ );
584
647
 
585
- toString() {
586
- return JSON.stringify(this.toJson());
648
+ // splitter
649
+ attributeDefinitions.add("splitterSize", 8).setType(Attribute.NUMBER).setDescription(
650
+ `width in pixels of all splitters between tabsets/borders`
651
+ );
652
+ attributeDefinitions.add("splitterExtra", 0).setType(Attribute.NUMBER).setDescription(
653
+ `additional width in pixels of the splitter hit test area`
654
+ );
655
+ attributeDefinitions.add("splitterEnableHandle", false).setType(Attribute.BOOLEAN).setDescription(
656
+ `enable a small centralized handle on all splitters`
657
+ );
658
+
659
+ // tab
660
+ attributeDefinitions.add("tabEnableClose", true).setType(Attribute.BOOLEAN);
661
+ attributeDefinitions.add("tabCloseType", 1).setType("ICloseType");
662
+ attributeDefinitions.add("tabEnablePopout", false).setType(Attribute.BOOLEAN).setAlias("tabEnableFloat");
663
+ attributeDefinitions.add("tabEnablePopoutIcon", true).setType(Attribute.BOOLEAN);
664
+ attributeDefinitions.add("tabEnablePopoutOverlay", false).setType(Attribute.BOOLEAN);
665
+ attributeDefinitions.add("tabEnableDrag", true).setType(Attribute.BOOLEAN);
666
+ attributeDefinitions.add("tabEnableRename", true).setType(Attribute.BOOLEAN);
667
+ attributeDefinitions.add("tabContentClassName", undefined).setType(Attribute.STRING);
668
+ attributeDefinitions.add("tabClassName", undefined).setType(Attribute.STRING);
669
+ attributeDefinitions.add("tabIcon", undefined).setType(Attribute.STRING);
670
+ attributeDefinitions.add("tabEnableRenderOnDemand", true).setType(Attribute.BOOLEAN);
671
+ attributeDefinitions.add("tabDragSpeed", 0.3).setType(Attribute.NUMBER);
672
+ attributeDefinitions.add("tabBorderWidth", -1).setType(Attribute.NUMBER);
673
+ attributeDefinitions.add("tabBorderHeight", -1).setType(Attribute.NUMBER);
674
+
675
+ // tabset
676
+ attributeDefinitions.add("tabSetEnableDeleteWhenEmpty", true).setType(Attribute.BOOLEAN);
677
+ attributeDefinitions.add("tabSetEnableDrop", true).setType(Attribute.BOOLEAN);
678
+ attributeDefinitions.add("tabSetEnableDrag", true).setType(Attribute.BOOLEAN);
679
+ attributeDefinitions.add("tabSetEnableDivide", true).setType(Attribute.BOOLEAN);
680
+ attributeDefinitions.add("tabSetEnableMaximize", true).setType(Attribute.BOOLEAN);
681
+ attributeDefinitions.add("tabSetEnableClose", false).setType(Attribute.BOOLEAN);
682
+ attributeDefinitions.add("tabSetEnableSingleTabStretch", false).setType(Attribute.BOOLEAN);
683
+ attributeDefinitions.add("tabSetAutoSelectTab", true).setType(Attribute.BOOLEAN);
684
+ attributeDefinitions.add("tabSetEnableActiveIcon", false).setType(Attribute.BOOLEAN);
685
+ attributeDefinitions.add("tabSetClassNameTabStrip", undefined).setType(Attribute.STRING);
686
+ attributeDefinitions.add("tabSetEnableTabStrip", true).setType(Attribute.BOOLEAN);
687
+ attributeDefinitions.add("tabSetEnableTabWrap", false).setType(Attribute.BOOLEAN);
688
+ attributeDefinitions.add("tabSetTabLocation", "top").setType("ITabLocation");
689
+ attributeDefinitions.add("tabMinWidth", DefaultMin).setType(Attribute.NUMBER);
690
+ attributeDefinitions.add("tabMinHeight", DefaultMin).setType(Attribute.NUMBER);
691
+ attributeDefinitions.add("tabSetMinWidth", DefaultMin).setType(Attribute.NUMBER);
692
+ attributeDefinitions.add("tabSetMinHeight", DefaultMin).setType(Attribute.NUMBER);
693
+ attributeDefinitions.add("tabMaxWidth", DefaultMax).setType(Attribute.NUMBER);
694
+ attributeDefinitions.add("tabMaxHeight", DefaultMax).setType(Attribute.NUMBER);
695
+ attributeDefinitions.add("tabSetMaxWidth", DefaultMax).setType(Attribute.NUMBER);
696
+ attributeDefinitions.add("tabSetMaxHeight", DefaultMax).setType(Attribute.NUMBER);
697
+
698
+ // border
699
+ attributeDefinitions.add("borderSize", 200).setType(Attribute.NUMBER);
700
+ attributeDefinitions.add("borderMinSize", DefaultMin).setType(Attribute.NUMBER);
701
+ attributeDefinitions.add("borderMaxSize", DefaultMax).setType(Attribute.NUMBER);
702
+ attributeDefinitions.add("borderEnableDrop", true).setType(Attribute.BOOLEAN);
703
+ attributeDefinitions.add("borderAutoSelectTabWhenOpen", true).setType(Attribute.BOOLEAN);
704
+ attributeDefinitions.add("borderAutoSelectTabWhenClosed", false).setType(Attribute.BOOLEAN);
705
+ attributeDefinitions.add("borderClassName", undefined).setType(Attribute.STRING);
706
+ attributeDefinitions.add("borderEnableAutoHide", false).setType(Attribute.BOOLEAN);
707
+
708
+ return attributeDefinitions;
587
709
  }
588
710
  }
589
711