@genesis-community/golden-layout 2.6.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 (226) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -0
  3. package/dist/cjs/index.js +40 -0
  4. package/dist/cjs/index.js.map +1 -0
  5. package/dist/cjs/ts/config/config.js +870 -0
  6. package/dist/cjs/ts/config/config.js.map +1 -0
  7. package/dist/cjs/ts/config/resolved-config.js +477 -0
  8. package/dist/cjs/ts/config/resolved-config.js.map +1 -0
  9. package/dist/cjs/ts/container/component-container.js +412 -0
  10. package/dist/cjs/ts/container/component-container.js.map +1 -0
  11. package/dist/cjs/ts/controls/browser-popout.js +298 -0
  12. package/dist/cjs/ts/controls/browser-popout.js.map +1 -0
  13. package/dist/cjs/ts/controls/drag-proxy.js +221 -0
  14. package/dist/cjs/ts/controls/drag-proxy.js.map +1 -0
  15. package/dist/cjs/ts/controls/drag-source.js +149 -0
  16. package/dist/cjs/ts/controls/drag-source.js.map +1 -0
  17. package/dist/cjs/ts/controls/drop-target-indicator.js +31 -0
  18. package/dist/cjs/ts/controls/drop-target-indicator.js.map +1 -0
  19. package/dist/cjs/ts/controls/header-button.js +34 -0
  20. package/dist/cjs/ts/controls/header-button.js.map +1 -0
  21. package/dist/cjs/ts/controls/header.js +366 -0
  22. package/dist/cjs/ts/controls/header.js.map +1 -0
  23. package/dist/cjs/ts/controls/splitter.js +42 -0
  24. package/dist/cjs/ts/controls/splitter.js.map +1 -0
  25. package/dist/cjs/ts/controls/tab.js +262 -0
  26. package/dist/cjs/ts/controls/tab.js.map +1 -0
  27. package/dist/cjs/ts/controls/tabs-container.js +236 -0
  28. package/dist/cjs/ts/controls/tabs-container.js.map +1 -0
  29. package/dist/cjs/ts/controls/transition-indicator.js +64 -0
  30. package/dist/cjs/ts/controls/transition-indicator.js.map +1 -0
  31. package/dist/cjs/ts/errors/external-error.js +46 -0
  32. package/dist/cjs/ts/errors/external-error.js.map +1 -0
  33. package/dist/cjs/ts/errors/internal-error.js +38 -0
  34. package/dist/cjs/ts/errors/internal-error.js.map +1 -0
  35. package/dist/cjs/ts/golden-layout.js +299 -0
  36. package/dist/cjs/ts/golden-layout.js.map +1 -0
  37. package/dist/cjs/ts/items/component-item.js +190 -0
  38. package/dist/cjs/ts/items/component-item.js.map +1 -0
  39. package/dist/cjs/ts/items/component-parentable-item.js +18 -0
  40. package/dist/cjs/ts/items/component-parentable-item.js.map +1 -0
  41. package/dist/cjs/ts/items/content-item.js +414 -0
  42. package/dist/cjs/ts/items/content-item.js.map +1 -0
  43. package/dist/cjs/ts/items/ground-item.js +352 -0
  44. package/dist/cjs/ts/items/ground-item.js.map +1 -0
  45. package/dist/cjs/ts/items/row-or-column.js +609 -0
  46. package/dist/cjs/ts/items/row-or-column.js.map +1 -0
  47. package/dist/cjs/ts/items/stack.js +841 -0
  48. package/dist/cjs/ts/items/stack.js.map +1 -0
  49. package/dist/cjs/ts/layout-manager.js +1618 -0
  50. package/dist/cjs/ts/layout-manager.js.map +1 -0
  51. package/dist/cjs/ts/utils/config-minifier.js +218 -0
  52. package/dist/cjs/ts/utils/config-minifier.js.map +1 -0
  53. package/dist/cjs/ts/utils/dom-constants.js +3 -0
  54. package/dist/cjs/ts/utils/dom-constants.js.map +1 -0
  55. package/dist/cjs/ts/utils/drag-listener.js +132 -0
  56. package/dist/cjs/ts/utils/drag-listener.js.map +1 -0
  57. package/dist/cjs/ts/utils/event-emitter.js +201 -0
  58. package/dist/cjs/ts/utils/event-emitter.js.map +1 -0
  59. package/dist/cjs/ts/utils/event-hub.js +135 -0
  60. package/dist/cjs/ts/utils/event-hub.js.map +1 -0
  61. package/dist/cjs/ts/utils/i18n-strings.js +74 -0
  62. package/dist/cjs/ts/utils/i18n-strings.js.map +1 -0
  63. package/dist/cjs/ts/utils/jquery-legacy.js +15 -0
  64. package/dist/cjs/ts/utils/jquery-legacy.js.map +1 -0
  65. package/dist/cjs/ts/utils/style-constants.js +11 -0
  66. package/dist/cjs/ts/utils/style-constants.js.map +1 -0
  67. package/dist/cjs/ts/utils/types.js +94 -0
  68. package/dist/cjs/ts/utils/types.js.map +1 -0
  69. package/dist/cjs/ts/utils/utils.js +211 -0
  70. package/dist/cjs/ts/utils/utils.js.map +1 -0
  71. package/dist/cjs/ts/virtual-layout.js +247 -0
  72. package/dist/cjs/ts/virtual-layout.js.map +1 -0
  73. package/dist/css/goldenlayout-base.css +319 -0
  74. package/dist/css/themes/goldenlayout-borderless-dark-theme.css +136 -0
  75. package/dist/css/themes/goldenlayout-dark-theme.css +139 -0
  76. package/dist/css/themes/goldenlayout-light-theme.css +129 -0
  77. package/dist/css/themes/goldenlayout-soda-theme.css +126 -0
  78. package/dist/css/themes/goldenlayout-translucent-theme.css +152 -0
  79. package/dist/esm/index.js +21 -0
  80. package/dist/esm/index.js.map +1 -0
  81. package/dist/esm/ts/config/config.js +864 -0
  82. package/dist/esm/ts/config/config.js.map +1 -0
  83. package/dist/esm/ts/config/resolved-config.js +474 -0
  84. package/dist/esm/ts/config/resolved-config.js.map +1 -0
  85. package/dist/esm/ts/container/component-container.js +408 -0
  86. package/dist/esm/ts/container/component-container.js.map +1 -0
  87. package/dist/esm/ts/controls/browser-popout.js +294 -0
  88. package/dist/esm/ts/controls/browser-popout.js.map +1 -0
  89. package/dist/esm/ts/controls/drag-proxy.js +217 -0
  90. package/dist/esm/ts/controls/drag-proxy.js.map +1 -0
  91. package/dist/esm/ts/controls/drag-source.js +145 -0
  92. package/dist/esm/ts/controls/drag-source.js.map +1 -0
  93. package/dist/esm/ts/controls/drop-target-indicator.js +27 -0
  94. package/dist/esm/ts/controls/drop-target-indicator.js.map +1 -0
  95. package/dist/esm/ts/controls/header-button.js +30 -0
  96. package/dist/esm/ts/controls/header-button.js.map +1 -0
  97. package/dist/esm/ts/controls/header.js +362 -0
  98. package/dist/esm/ts/controls/header.js.map +1 -0
  99. package/dist/esm/ts/controls/splitter.js +38 -0
  100. package/dist/esm/ts/controls/splitter.js.map +1 -0
  101. package/dist/esm/ts/controls/tab.js +258 -0
  102. package/dist/esm/ts/controls/tab.js.map +1 -0
  103. package/dist/esm/ts/controls/tabs-container.js +232 -0
  104. package/dist/esm/ts/controls/tabs-container.js.map +1 -0
  105. package/dist/esm/ts/controls/transition-indicator.js +60 -0
  106. package/dist/esm/ts/controls/transition-indicator.js.map +1 -0
  107. package/dist/esm/ts/errors/external-error.js +38 -0
  108. package/dist/esm/ts/errors/external-error.js.map +1 -0
  109. package/dist/esm/ts/errors/internal-error.js +31 -0
  110. package/dist/esm/ts/errors/internal-error.js.map +1 -0
  111. package/dist/esm/ts/golden-layout.js +295 -0
  112. package/dist/esm/ts/golden-layout.js.map +1 -0
  113. package/dist/esm/ts/items/component-item.js +186 -0
  114. package/dist/esm/ts/items/component-item.js.map +1 -0
  115. package/dist/esm/ts/items/component-parentable-item.js +14 -0
  116. package/dist/esm/ts/items/component-parentable-item.js.map +1 -0
  117. package/dist/esm/ts/items/content-item.js +410 -0
  118. package/dist/esm/ts/items/content-item.js.map +1 -0
  119. package/dist/esm/ts/items/ground-item.js +348 -0
  120. package/dist/esm/ts/items/ground-item.js.map +1 -0
  121. package/dist/esm/ts/items/row-or-column.js +605 -0
  122. package/dist/esm/ts/items/row-or-column.js.map +1 -0
  123. package/dist/esm/ts/items/stack.js +837 -0
  124. package/dist/esm/ts/items/stack.js.map +1 -0
  125. package/dist/esm/ts/layout-manager.js +1614 -0
  126. package/dist/esm/ts/layout-manager.js.map +1 -0
  127. package/dist/esm/ts/utils/config-minifier.js +215 -0
  128. package/dist/esm/ts/utils/config-minifier.js.map +1 -0
  129. package/dist/esm/ts/utils/dom-constants.js +2 -0
  130. package/dist/esm/ts/utils/dom-constants.js.map +1 -0
  131. package/dist/esm/ts/utils/drag-listener.js +128 -0
  132. package/dist/esm/ts/utils/drag-listener.js.map +1 -0
  133. package/dist/esm/ts/utils/event-emitter.js +197 -0
  134. package/dist/esm/ts/utils/event-emitter.js.map +1 -0
  135. package/dist/esm/ts/utils/event-hub.js +131 -0
  136. package/dist/esm/ts/utils/event-hub.js.map +1 -0
  137. package/dist/esm/ts/utils/i18n-strings.js +71 -0
  138. package/dist/esm/ts/utils/i18n-strings.js.map +1 -0
  139. package/dist/esm/ts/utils/jquery-legacy.js +11 -0
  140. package/dist/esm/ts/utils/jquery-legacy.js.map +1 -0
  141. package/dist/esm/ts/utils/style-constants.js +8 -0
  142. package/dist/esm/ts/utils/style-constants.js.map +1 -0
  143. package/dist/esm/ts/utils/types.js +91 -0
  144. package/dist/esm/ts/utils/types.js.map +1 -0
  145. package/dist/esm/ts/utils/utils.js +191 -0
  146. package/dist/esm/ts/utils/utils.js.map +1 -0
  147. package/dist/esm/ts/virtual-layout.js +243 -0
  148. package/dist/esm/ts/virtual-layout.js.map +1 -0
  149. package/dist/img/lm_close_black.png +0 -0
  150. package/dist/img/lm_close_tab_white.png +0 -0
  151. package/dist/img/lm_close_white.png +0 -0
  152. package/dist/img/lm_maximise_black.png +0 -0
  153. package/dist/img/lm_maximise_white.png +0 -0
  154. package/dist/img/lm_minimize_black.png +0 -0
  155. package/dist/img/lm_minimize_white.png +0 -0
  156. package/dist/img/lm_popin_black.png +0 -0
  157. package/dist/img/lm_popin_white.png +0 -0
  158. package/dist/img/lm_popout_black.png +0 -0
  159. package/dist/img/lm_popout_white.png +0 -0
  160. package/dist/less/goldenlayout-base.less +422 -0
  161. package/dist/less/themes/goldenlayout-borderless-dark-theme.less +230 -0
  162. package/dist/less/themes/goldenlayout-dark-theme.less +233 -0
  163. package/dist/less/themes/goldenlayout-light-theme.less +223 -0
  164. package/dist/less/themes/goldenlayout-soda-theme.less +211 -0
  165. package/dist/less/themes/goldenlayout-translucent-theme.less +237 -0
  166. package/dist/scss/goldenlayout-base.scss +422 -0
  167. package/dist/scss/themes/_goldenlayout-var-theme.scss +232 -0
  168. package/dist/types/golden-layout-untrimmed.d.ts +3428 -0
  169. package/dist/types/index.d.ts +2246 -0
  170. package/dist/types/tsdoc-metadata.json +11 -0
  171. package/package.json +107 -0
  172. package/src/TOOLCHAIN.md +54 -0
  173. package/src/img/lm_close_black.png +0 -0
  174. package/src/img/lm_close_tab_white.png +0 -0
  175. package/src/img/lm_close_white.png +0 -0
  176. package/src/img/lm_maximise_black.png +0 -0
  177. package/src/img/lm_maximise_white.png +0 -0
  178. package/src/img/lm_minimize_black.png +0 -0
  179. package/src/img/lm_minimize_white.png +0 -0
  180. package/src/img/lm_popin_black.png +0 -0
  181. package/src/img/lm_popin_white.png +0 -0
  182. package/src/img/lm_popout_black.png +0 -0
  183. package/src/img/lm_popout_white.png +0 -0
  184. package/src/index.ts +21 -0
  185. package/src/less/goldenlayout-base.less +422 -0
  186. package/src/less/themes/goldenlayout-borderless-dark-theme.less +230 -0
  187. package/src/less/themes/goldenlayout-dark-theme.less +233 -0
  188. package/src/less/themes/goldenlayout-light-theme.less +223 -0
  189. package/src/less/themes/goldenlayout-soda-theme.less +211 -0
  190. package/src/less/themes/goldenlayout-translucent-theme.less +237 -0
  191. package/src/scss/goldenlayout-base.scss +422 -0
  192. package/src/scss/themes/_goldenlayout-var-theme.scss +232 -0
  193. package/src/ts/config/config.ts +1283 -0
  194. package/src/ts/config/resolved-config.ts +621 -0
  195. package/src/ts/container/component-container.ts +500 -0
  196. package/src/ts/controls/browser-popout.ts +325 -0
  197. package/src/ts/controls/drag-proxy.ts +259 -0
  198. package/src/ts/controls/drag-source.ts +167 -0
  199. package/src/ts/controls/drop-target-indicator.ts +35 -0
  200. package/src/ts/controls/header-button.ts +39 -0
  201. package/src/ts/controls/header.ts +483 -0
  202. package/src/ts/controls/splitter.ts +50 -0
  203. package/src/ts/controls/tab.ts +293 -0
  204. package/src/ts/controls/tabs-container.ts +281 -0
  205. package/src/ts/controls/transition-indicator.ts +78 -0
  206. package/src/ts/errors/external-error.ts +39 -0
  207. package/src/ts/errors/internal-error.ts +34 -0
  208. package/src/ts/golden-layout.ts +365 -0
  209. package/src/ts/items/component-item.ts +252 -0
  210. package/src/ts/items/component-parentable-item.ts +16 -0
  211. package/src/ts/items/content-item.ts +513 -0
  212. package/src/ts/items/ground-item.ts +404 -0
  213. package/src/ts/items/row-or-column.ts +707 -0
  214. package/src/ts/items/stack.ts +975 -0
  215. package/src/ts/layout-manager.ts +1862 -0
  216. package/src/ts/utils/config-minifier.ts +235 -0
  217. package/src/ts/utils/dom-constants.ts +44 -0
  218. package/src/ts/utils/drag-listener.ts +178 -0
  219. package/src/ts/utils/event-emitter.ts +275 -0
  220. package/src/ts/utils/event-hub.ts +163 -0
  221. package/src/ts/utils/i18n-strings.ts +96 -0
  222. package/src/ts/utils/jquery-legacy.ts +12 -0
  223. package/src/ts/utils/style-constants.ts +6 -0
  224. package/src/ts/utils/types.ts +145 -0
  225. package/src/ts/utils/utils.ts +206 -0
  226. package/src/ts/virtual-layout.ts +328 -0
@@ -0,0 +1,1862 @@
1
+ import { ComponentItemConfig, ItemConfig, LayoutConfig, RowOrColumnItemConfig, StackItemConfig } from './config/config';
2
+ import {
3
+ ResolvedComponentItemConfig,
4
+ ResolvedItemConfig,
5
+ ResolvedLayoutConfig,
6
+ ResolvedPopoutLayoutConfig,
7
+ ResolvedRootItemConfig,
8
+ ResolvedRowOrColumnItemConfig,
9
+ ResolvedStackItemConfig
10
+ } from "./config/resolved-config";
11
+ import { ComponentContainer } from './container/component-container';
12
+ import { BrowserPopout } from './controls/browser-popout';
13
+ import { DragProxy } from './controls/drag-proxy';
14
+ import { DragSource } from './controls/drag-source';
15
+ import { DropTargetIndicator } from './controls/drop-target-indicator';
16
+ import { TransitionIndicator } from './controls/transition-indicator';
17
+ import { ConfigurationError } from './errors/external-error';
18
+ import { AssertError, UnexpectedNullError, UnexpectedUndefinedError, UnreachableCaseError } from './errors/internal-error';
19
+ import { ComponentItem } from './items/component-item';
20
+ import { ComponentParentableItem } from './items/component-parentable-item';
21
+ import { ContentItem } from './items/content-item';
22
+ import { GroundItem } from './items/ground-item';
23
+ import { RowOrColumn } from './items/row-or-column';
24
+ import { Stack } from './items/stack';
25
+ import { ConfigMinifier } from './utils/config-minifier';
26
+ import { DomConstants } from './utils/dom-constants';
27
+ import { DragListener } from './utils/drag-listener';
28
+ import { EventEmitter } from './utils/event-emitter';
29
+ import { EventHub } from './utils/event-hub';
30
+ import { I18nStringId, I18nStrings, i18nStrings } from './utils/i18n-strings';
31
+ import { ItemType, JsonValue, Rect, ResponsiveMode } from './utils/types';
32
+ import {
33
+ getElementWidthAndHeight,
34
+ removeFromArray,
35
+ setElementHeight,
36
+ setElementWidth
37
+ } from './utils/utils';
38
+
39
+ /** @internal */
40
+ declare global {
41
+ interface Window {
42
+ __glInstance: LayoutManager;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * The main class that will be exposed as GoldenLayout.
48
+ */
49
+
50
+ /** @public */
51
+ export abstract class LayoutManager extends EventEmitter {
52
+ /** Whether the layout will be automatically be resized to container whenever the container's size is changed
53
+ * Default is true if <body> is the container otherwise false
54
+ * Default will be changed to true for any container in the future
55
+ */
56
+ resizeWithContainerAutomatically = false;
57
+ /** The debounce interval (in milliseconds) used whenever a layout is automatically resized. 0 means next tick */
58
+ resizeDebounceInterval = 100;
59
+ /** Extend the current debounce delay time period if it is triggered during the delay.
60
+ * If this is true, the layout will only resize when its container has stopped being resized.
61
+ * If it is false, the layout will resize at intervals while its container is being resized.
62
+ */
63
+ resizeDebounceExtendedWhenPossible = true;
64
+
65
+ /** @internal */
66
+ private _containerElement: HTMLElement;
67
+ /** @internal */
68
+ private _isInitialised = false;
69
+ /** @internal */
70
+ private _groundItem: GroundItem | undefined = undefined;
71
+ /** @internal */
72
+ private _openPopouts: BrowserPopout[] = [];
73
+ /** @internal */
74
+ private _dropTargetIndicator: DropTargetIndicator | null = null;
75
+ /** @internal */
76
+ private _transitionIndicator: TransitionIndicator | null = null;
77
+ /** @internal */
78
+ private _resizeTimeoutId: ReturnType<typeof setTimeout> | undefined;
79
+ /** @internal */
80
+ private _itemAreas: ContentItem.Area[] = [];
81
+ /** @internal */
82
+ private _maximisedStack: Stack | undefined;
83
+ /** @internal */
84
+ private _maximisePlaceholder = LayoutManager.createMaximisePlaceElement(document);
85
+ /** @internal */
86
+ private _tabDropPlaceholder = LayoutManager.createTabDropPlaceholderElement(document);
87
+ /** @internal */
88
+ private _dragSources: DragSource[] = [];
89
+ /** @internal */
90
+ private _updatingColumnsResponsive = false;
91
+ /** @internal */
92
+ private _firstLoad = true;
93
+ /** @internal */
94
+ private _eventHub = new EventHub(this);
95
+ /** @internal */
96
+ private _width: number | null = null;
97
+ /** @internal */
98
+ private _height: number | null = null;
99
+ /** @internal */
100
+ private _focusedComponentItem: ComponentItem | undefined;
101
+ /** @internal */
102
+ private _virtualSizedContainers: ComponentContainer[] = [];
103
+ /** @internal */
104
+ private _virtualSizedContainerAddingBeginCount = 0;
105
+ /** @internal */
106
+ private _sizeInvalidationBeginCount = 0;
107
+ /** @internal */
108
+ protected _constructorOrSubWindowLayoutConfig: LayoutConfig | undefined; // protected for backwards compatibility
109
+
110
+ /** @internal */
111
+ private _resizeObserver = new ResizeObserver(() => this.handleContainerResize());
112
+ /** @internal @deprecated to be removed in version 3 */
113
+ private _windowBeforeUnloadListener = () => this.onBeforeUnload();
114
+ /** @internal @deprecated to be removed in version 3 */
115
+ private _windowBeforeUnloadListening = false;
116
+ /** @internal */
117
+ private _maximisedStackBeforeDestroyedListener = (ev: EventEmitter.BubblingEvent) => this.cleanupBeforeMaximisedStackDestroyed(ev);
118
+
119
+ readonly isSubWindow: boolean;
120
+ layoutConfig: ResolvedLayoutConfig;
121
+
122
+ beforeVirtualRectingEvent: LayoutManager.BeforeVirtualRectingEvent | undefined;
123
+ afterVirtualRectingEvent: LayoutManager.AfterVirtualRectingEvent | undefined;
124
+
125
+ get container(): HTMLElement { return this._containerElement; }
126
+ get isInitialised(): boolean { return this._isInitialised; }
127
+ /** @internal */
128
+ get groundItem(): GroundItem | undefined { return this._groundItem; }
129
+ /** @internal @deprecated use {@link (LayoutManager:class).groundItem} instead */
130
+ get root(): GroundItem | undefined { return this._groundItem; }
131
+ get openPopouts(): BrowserPopout[] { return this._openPopouts; }
132
+ /** @internal */
133
+ get dropTargetIndicator(): DropTargetIndicator | null { return this._dropTargetIndicator; }
134
+ /** @internal @deprecated To be removed */
135
+ get transitionIndicator(): TransitionIndicator | null { return this._transitionIndicator; }
136
+ get width(): number | null { return this._width; }
137
+ get height(): number | null { return this._height; }
138
+ /**
139
+ * Retrieves the {@link (EventHub:class)} instance associated with this layout manager.
140
+ * This can be used to propagate events between the windows
141
+ * @public
142
+ */
143
+ get eventHub(): EventHub { return this._eventHub; }
144
+ get rootItem(): ContentItem | undefined {
145
+ if (this._groundItem === undefined) {
146
+ throw new Error('Cannot access rootItem before init');
147
+ } else {
148
+ const groundContentItems = this._groundItem.contentItems;
149
+ if (groundContentItems.length === 0) {
150
+ return undefined;
151
+ } else {
152
+ return this._groundItem.contentItems[0];
153
+ }
154
+ }
155
+ }
156
+ get focusedComponentItem(): ComponentItem | undefined { return this._focusedComponentItem; }
157
+ /** @internal */
158
+ get tabDropPlaceholder(): HTMLElement { return this._tabDropPlaceholder; }
159
+ get maximisedStack(): Stack | undefined { return this._maximisedStack; }
160
+
161
+ /** @deprecated indicates deprecated constructor use */
162
+ get deprecatedConstructor(): boolean { return !this.isSubWindow && this._constructorOrSubWindowLayoutConfig !== undefined; }
163
+
164
+
165
+ /**
166
+ * @param container - A Dom HTML element. Defaults to body
167
+ * @internal
168
+ */
169
+ constructor(parameters: LayoutManager.ConstructorParameters) {
170
+ super();
171
+
172
+ this.isSubWindow = parameters.isSubWindow;
173
+
174
+ this._constructorOrSubWindowLayoutConfig = parameters.constructorOrSubWindowLayoutConfig;
175
+
176
+ I18nStrings.checkInitialise();
177
+ ConfigMinifier.checkInitialise();
178
+
179
+ if (parameters.containerElement !== undefined) {
180
+ this._containerElement = parameters.containerElement;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Destroys the LayoutManager instance itself as well as every ContentItem
186
+ * within it. After this is called nothing should be left of the LayoutManager.
187
+ *
188
+ * This function only needs to be called if an application wishes to destroy the Golden Layout object while
189
+ * a page remains loaded. When a page is unloaded, all resources claimed by Golden Layout will automatically
190
+ * be released.
191
+ */
192
+ destroy(): void {
193
+ if (this._isInitialised) {
194
+ if (this._windowBeforeUnloadListening) {
195
+ globalThis.removeEventListener('beforeunload', this._windowBeforeUnloadListener);
196
+ this._windowBeforeUnloadListening = false;
197
+ }
198
+
199
+ if (this.layoutConfig.settings.closePopoutsOnUnload === true) {
200
+ this.closeAllOpenPopouts();
201
+ }
202
+
203
+ this._resizeObserver.disconnect();
204
+ this.checkClearResizeTimeout();
205
+
206
+ if (this._groundItem !== undefined) {
207
+ this._groundItem.destroy();
208
+ }
209
+ this._tabDropPlaceholder.remove();
210
+ if (this._dropTargetIndicator !== null) {
211
+ this._dropTargetIndicator.destroy();
212
+ }
213
+ if (this._transitionIndicator !== null) {
214
+ this._transitionIndicator.destroy();
215
+ }
216
+ this._eventHub.destroy();
217
+
218
+ for (const dragSource of this._dragSources) {
219
+ dragSource.destroy();
220
+ }
221
+ this._dragSources = [];
222
+
223
+ this._isInitialised = false;
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Takes a GoldenLayout configuration object and
229
+ * replaces its keys and values recursively with
230
+ * one letter codes
231
+ * @deprecated use {@link (ResolvedLayoutConfig:namespace).minifyConfig} instead
232
+ */
233
+ minifyConfig(config: ResolvedLayoutConfig): ResolvedLayoutConfig {
234
+ return ResolvedLayoutConfig.minifyConfig(config);
235
+ }
236
+
237
+ /**
238
+ * Takes a configuration Object that was previously minified
239
+ * using minifyConfig and returns its original version
240
+ * @deprecated use {@link (ResolvedLayoutConfig:namespace).unminifyConfig} instead
241
+ */
242
+ unminifyConfig(config: ResolvedLayoutConfig): ResolvedLayoutConfig {
243
+ return ResolvedLayoutConfig.unminifyConfig(config);
244
+ }
245
+
246
+ /** @internal */
247
+ abstract bindComponent(container: ComponentContainer, itemConfig: ResolvedComponentItemConfig): ComponentContainer.BindableComponent;
248
+ /** @internal */
249
+ abstract unbindComponent(container: ComponentContainer, virtual: boolean, component: ComponentContainer.Component | undefined): void;
250
+
251
+ /**
252
+ * Called from GoldenLayout class. Finishes of init
253
+ * @internal
254
+ */
255
+ init(): void {
256
+ this.setContainer();
257
+ this._dropTargetIndicator = new DropTargetIndicator(/*this.container*/);
258
+ this._transitionIndicator = new TransitionIndicator();
259
+ this.updateSizeFromContainer();
260
+
261
+ let subWindowRootConfig: ComponentItemConfig | undefined;
262
+ if (this.isSubWindow) {
263
+ if (this._constructorOrSubWindowLayoutConfig === undefined) {
264
+ // SubWindow LayoutConfig should have been generated by constructor
265
+ throw new UnexpectedUndefinedError('LMIU07155');
266
+ } else {
267
+ const root = this._constructorOrSubWindowLayoutConfig.root;
268
+ if (root === undefined) {
269
+ // SubWindow LayoutConfig must not be empty
270
+ throw new AssertError('LMIC07156');
271
+ } else {
272
+ if (ItemConfig.isComponent(root)) {
273
+ subWindowRootConfig = root;
274
+ } else {
275
+ // SubWindow LayoutConfig must have Component as Root
276
+ throw new AssertError('LMIC07157');
277
+ }
278
+ }
279
+ const resolvedLayoutConfig = LayoutConfig.resolve(this._constructorOrSubWindowLayoutConfig);
280
+ // remove root from layoutConfig
281
+ this.layoutConfig = {
282
+ ...resolvedLayoutConfig,
283
+ root: undefined,
284
+ }
285
+ }
286
+ } else {
287
+ if (this._constructorOrSubWindowLayoutConfig === undefined) {
288
+ this.layoutConfig = ResolvedLayoutConfig.createDefault(); // will overwritten be loaded via loadLayout
289
+ } else {
290
+ // backwards compatibility
291
+ this.layoutConfig = LayoutConfig.resolve(this._constructorOrSubWindowLayoutConfig);
292
+ }
293
+ }
294
+ const layoutConfig = this.layoutConfig;
295
+ this._groundItem = new GroundItem(this, layoutConfig.root, this._containerElement);
296
+ this._groundItem.init();
297
+
298
+ this.checkLoadedLayoutMaximiseItem();
299
+
300
+ this._resizeObserver.observe(this._containerElement);
301
+ this._isInitialised = true;
302
+ this.adjustColumnsResponsive();
303
+ this.emit('initialised');
304
+
305
+ if (subWindowRootConfig !== undefined) {
306
+ // must be SubWindow
307
+ this.loadComponentAsRoot(subWindowRootConfig);
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Loads a new layout
313
+ * @param layoutConfig - New layout to be loaded
314
+ */
315
+ loadLayout(layoutConfig: LayoutConfig): void {
316
+ if (!this.isInitialised) {
317
+ // In case application not correctly using legacy constructor
318
+ throw new Error('GoldenLayout: Need to call init() if LayoutConfig with defined root passed to constructor')
319
+ } else {
320
+ if (this._groundItem === undefined) {
321
+ throw new UnexpectedUndefinedError('LMLL11119');
322
+ } else {
323
+ this.createSubWindows(); // still needs to be tested
324
+
325
+ this.layoutConfig = LayoutConfig.resolve(layoutConfig);
326
+ this._groundItem.loadRoot(this.layoutConfig.root);
327
+ this.checkLoadedLayoutMaximiseItem();
328
+ this.adjustColumnsResponsive();
329
+ }
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Creates a layout configuration object based on the the current state
335
+ *
336
+ * @public
337
+ * @returns GoldenLayout configuration
338
+ */
339
+ saveLayout(): ResolvedLayoutConfig {
340
+ if (this._isInitialised === false) {
341
+ throw new Error('Can\'t create config, layout not yet initialised');
342
+ } else {
343
+
344
+ // if (root !== undefined && !(root instanceof ContentItem)) {
345
+ // throw new Error('Root must be a ContentItem');
346
+ // }
347
+
348
+ /*
349
+ * Content
350
+ */
351
+ if (this._groundItem === undefined) {
352
+ throw new UnexpectedUndefinedError('LMTC18244');
353
+ } else {
354
+ const groundContent = this._groundItem.calculateConfigContent();
355
+
356
+ let rootItemConfig: ResolvedRootItemConfig | undefined;
357
+ if (groundContent.length !== 1) {
358
+ rootItemConfig = undefined;
359
+ } else {
360
+ rootItemConfig = groundContent[0];
361
+ }
362
+
363
+ /*
364
+ * Retrieve config for subwindows
365
+ */
366
+ this.reconcilePopoutWindows();
367
+ const openPopouts: ResolvedPopoutLayoutConfig[] = [];
368
+ for (let i = 0; i < this._openPopouts.length; i++) {
369
+ openPopouts.push(this._openPopouts[i].toConfig());
370
+ }
371
+
372
+ const config: ResolvedLayoutConfig = {
373
+ root: rootItemConfig,
374
+ openPopouts,
375
+ settings: ResolvedLayoutConfig.Settings.createCopy(this.layoutConfig.settings),
376
+ dimensions: ResolvedLayoutConfig.Dimensions.createCopy(this.layoutConfig.dimensions),
377
+ header: ResolvedLayoutConfig.Header.createCopy(this.layoutConfig.header),
378
+ resolved: true,
379
+ }
380
+
381
+ return config;
382
+ }
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Removes any existing layout. Effectively, an empty layout will be loaded.
388
+ */
389
+
390
+ clear(): void {
391
+ if (this._groundItem === undefined) {
392
+ throw new UnexpectedUndefinedError('LMCL11129');
393
+ } else {
394
+ this._groundItem.clearRoot();
395
+ }
396
+ }
397
+
398
+ /**
399
+ * @deprecated Use {@link (LayoutManager:class).saveLayout}
400
+ */
401
+ toConfig(): ResolvedLayoutConfig {
402
+ return this.saveLayout();
403
+ }
404
+
405
+ /**
406
+ * Adds a new ComponentItem. Will use default location selectors to ensure a location is found and
407
+ * component is successfully added
408
+ * @param componentTypeName - Name of component type to be created.
409
+ * @param state - Optional initial state to be assigned to component
410
+ * @returns New ComponentItem created.
411
+ */
412
+ newComponent(componentType: JsonValue, componentState?: JsonValue, title?: string): ComponentItem {
413
+ const componentItem = this.newComponentAtLocation(componentType, componentState, title);
414
+ if (componentItem === undefined) {
415
+ throw new AssertError('LMNC65588');
416
+ } else {
417
+ return componentItem;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Adds a ComponentItem at the first valid selector location.
423
+ * @param componentTypeName - Name of component type to be created.
424
+ * @param state - Optional initial state to be assigned to component
425
+ * @param locationSelectors - Array of location selectors used to find location in layout where component
426
+ * will be added. First location in array which is valid will be used. If locationSelectors is undefined,
427
+ * {@link (LayoutManager:namespace).defaultLocationSelectors} will be used
428
+ * @returns New ComponentItem created or undefined if no valid location selector was in array.
429
+ */
430
+ newComponentAtLocation(componentType: JsonValue, componentState?: JsonValue, title?: string,
431
+ locationSelectors?: LayoutManager.LocationSelector[]
432
+ ): ComponentItem | undefined{
433
+ if (this._groundItem === undefined) {
434
+ throw new Error('Cannot add component before init');
435
+ } else {
436
+ const location = this.addComponentAtLocation(componentType, componentState, title, locationSelectors);
437
+ if (location === undefined) {
438
+ return undefined;
439
+ } else {
440
+ const createdItem = location.parentItem.contentItems[location.index];
441
+ if (!ContentItem.isComponentItem(createdItem)) {
442
+ throw new AssertError('LMNC992877533');
443
+ } else {
444
+ return createdItem;
445
+ }
446
+ }
447
+ }
448
+ }
449
+
450
+ /**
451
+ * Adds a new ComponentItem. Will use default location selectors to ensure a location is found and
452
+ * component is successfully added
453
+ * @param componentType - Type of component to be created.
454
+ * @param state - Optional initial state to be assigned to component
455
+ * @returns Location of new ComponentItem created.
456
+ */
457
+ addComponent(componentType: JsonValue, componentState?: JsonValue, title?: string): LayoutManager.Location {
458
+ const location = this.addComponentAtLocation(componentType, componentState, title);
459
+ if (location === undefined) {
460
+ throw new AssertError('LMAC99943');
461
+ } else {
462
+ return location;
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Adds a ComponentItem at the first valid selector location.
468
+ * @param componentType - Type of component to be created.
469
+ * @param state - Optional initial state to be assigned to component
470
+ * @param locationSelectors - Array of location selectors used to find determine location in layout where component
471
+ * will be added. First location in array which is valid will be used. If undefined,
472
+ * {@link (LayoutManager:namespace).defaultLocationSelectors} will be used.
473
+ * @returns Location of new ComponentItem created or undefined if no valid location selector was in array.
474
+ */
475
+ addComponentAtLocation(componentType: JsonValue, componentState?: JsonValue, title?: string,
476
+ locationSelectors?: readonly LayoutManager.LocationSelector[]
477
+ ): LayoutManager.Location | undefined {
478
+ const itemConfig: ComponentItemConfig = {
479
+ type: 'component',
480
+ componentType,
481
+ componentState,
482
+ title,
483
+ };
484
+
485
+ return this.addItemAtLocation(itemConfig, locationSelectors);
486
+ }
487
+
488
+ /**
489
+ * Adds a new ContentItem. Will use default location selectors to ensure a location is found and
490
+ * component is successfully added
491
+ * @param itemConfig - ResolvedItemConfig of child to be added.
492
+ * @returns New ContentItem created.
493
+ */
494
+ newItem(itemConfig: RowOrColumnItemConfig | StackItemConfig | ComponentItemConfig): ContentItem {
495
+ const contentItem = this.newItemAtLocation(itemConfig);
496
+ if (contentItem === undefined) {
497
+ throw new AssertError('LMNC65588');
498
+ } else {
499
+ return contentItem;
500
+ }
501
+ }
502
+
503
+ /**
504
+ * Adds a new child ContentItem under the root ContentItem. If a root does not exist, then create root ContentItem instead
505
+ * @param itemConfig - ResolvedItemConfig of child to be added.
506
+ * @param locationSelectors - Array of location selectors used to find determine location in layout where ContentItem
507
+ * will be added. First location in array which is valid will be used. If undefined,
508
+ * {@link (LayoutManager:namespace).defaultLocationSelectors} will be used.
509
+ * @returns New ContentItem created or undefined if no valid location selector was in array. */
510
+ newItemAtLocation(itemConfig: RowOrColumnItemConfig | StackItemConfig | ComponentItemConfig,
511
+ locationSelectors?: readonly LayoutManager.LocationSelector[]
512
+ ): ContentItem | undefined {
513
+ if (this._groundItem === undefined) {
514
+ throw new Error('Cannot add component before init');
515
+ } else {
516
+ const location = this.addItemAtLocation(itemConfig, locationSelectors);
517
+ if (location === undefined) {
518
+ return undefined;
519
+ } else {
520
+ const createdItem = location.parentItem.contentItems[location.index];
521
+ return createdItem;
522
+ }
523
+ }
524
+ }
525
+
526
+ /**
527
+ * Adds a new ContentItem. Will use default location selectors to ensure a location is found and
528
+ * component is successfully added.
529
+ * @param itemConfig - ResolvedItemConfig of child to be added.
530
+ * @returns Location of new ContentItem created. */
531
+ addItem(itemConfig: RowOrColumnItemConfig | StackItemConfig | ComponentItemConfig): LayoutManager.Location {
532
+ const location = this.addItemAtLocation(itemConfig);
533
+ if (location === undefined) {
534
+ throw new AssertError('LMAI99943');
535
+ } else {
536
+ return location;
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Adds a ContentItem at the first valid selector location.
542
+ * @param itemConfig - ResolvedItemConfig of child to be added.
543
+ * @param locationSelectors - Array of location selectors used to find determine location in layout where ContentItem
544
+ * will be added. First location in array which is valid will be used. If undefined,
545
+ * {@link (LayoutManager:namespace).defaultLocationSelectors} will be used.
546
+ * @returns Location of new ContentItem created or undefined if no valid location selector was in array. */
547
+ addItemAtLocation(itemConfig: RowOrColumnItemConfig | StackItemConfig | ComponentItemConfig,
548
+ locationSelectors?: readonly LayoutManager.LocationSelector[]
549
+ ): LayoutManager.Location | undefined {
550
+ if (this._groundItem === undefined) {
551
+ throw new Error('Cannot add component before init');
552
+ } else {
553
+ if (locationSelectors === undefined) {
554
+ // defaultLocationSelectors should always find a location
555
+ locationSelectors = LayoutManager.defaultLocationSelectors;
556
+ }
557
+
558
+ const location = this.findFirstLocation(locationSelectors);
559
+ if (location === undefined) {
560
+ return undefined;
561
+ } else {
562
+ let parentItem = location.parentItem;
563
+ let addIdx: number;
564
+ switch (parentItem.type) {
565
+ case ItemType.ground: {
566
+ const groundItem = parentItem as GroundItem;
567
+ addIdx = groundItem.addItem(itemConfig, location.index);
568
+ if (addIdx >= 0) {
569
+ parentItem = this._groundItem.contentItems[0]; // was added to rootItem
570
+ } else {
571
+ addIdx = 0; // was added as rootItem (which is the first and only ContentItem in GroundItem)
572
+ }
573
+ break;
574
+ }
575
+ case ItemType.row:
576
+ case ItemType.column: {
577
+ const rowOrColumn = parentItem as RowOrColumn;
578
+ addIdx = rowOrColumn.addItem(itemConfig, location.index);
579
+ break;
580
+ }
581
+ case ItemType.stack: {
582
+ if (!ItemConfig.isComponent(itemConfig)) {
583
+ throw Error(i18nStrings[I18nStringId.ItemConfigIsNotTypeComponent]);
584
+ } else {
585
+ const stack = parentItem as Stack;
586
+ addIdx = stack.addItem(itemConfig, location.index);
587
+ break;
588
+ }
589
+ }
590
+ case ItemType.component: {
591
+ throw new AssertError('LMAIALC87444602');
592
+ }
593
+ default:
594
+ throw new UnreachableCaseError('LMAIALU98881733', parentItem.type);
595
+ }
596
+
597
+ if (ItemConfig.isComponent(itemConfig)) {
598
+ // see if stack was inserted
599
+ const item = parentItem.contentItems[addIdx];
600
+ if (ContentItem.isStack(item)) {
601
+ parentItem = item;
602
+ addIdx = 0;
603
+ }
604
+ }
605
+
606
+ location.parentItem = parentItem;
607
+ location.index = addIdx;
608
+
609
+ return location;
610
+ }
611
+ }
612
+ }
613
+
614
+ /** Loads the specified component ResolvedItemConfig as root.
615
+ * This can be used to display a Component all by itself. The layout cannot be changed other than having another new layout loaded.
616
+ * Note that, if this layout is saved and reloaded, it will reload with the Component as a child of a Stack.
617
+ */
618
+ loadComponentAsRoot(itemConfig: ComponentItemConfig): void {
619
+ if (this._groundItem === undefined) {
620
+ throw new Error('Cannot add item before init');
621
+ } else {
622
+ this._groundItem.loadComponentAsRoot(itemConfig);
623
+ }
624
+ }
625
+
626
+ /** @deprecated Use {@link (LayoutManager:class).setSize} */
627
+ updateSize(width: number, height: number): void {
628
+ this.setSize(width, height);
629
+ }
630
+
631
+ /**
632
+ * Updates the layout managers size
633
+ *
634
+ * @param width - Width in pixels
635
+ * @param height - Height in pixels
636
+ */
637
+ setSize(width: number, height: number): void {
638
+ this._width = width;
639
+ this._height = height;
640
+
641
+ if (this._isInitialised === true) {
642
+ if (this._groundItem === undefined) {
643
+ throw new UnexpectedUndefinedError('LMUS18881');
644
+ } else {
645
+ this._groundItem.setSize(this._width, this._height);
646
+
647
+ if (this._maximisedStack) {
648
+ const { width, height } = getElementWidthAndHeight(this._containerElement);
649
+ setElementWidth(this._maximisedStack.element, width);
650
+ setElementHeight(this._maximisedStack.element, height);
651
+ this._maximisedStack.updateSize(false);
652
+ }
653
+
654
+ this.adjustColumnsResponsive();
655
+ }
656
+ }
657
+ }
658
+
659
+ /** @internal */
660
+ beginSizeInvalidation(): void {
661
+ this._sizeInvalidationBeginCount++;
662
+ }
663
+
664
+ /** @internal */
665
+ endSizeInvalidation(): void {
666
+ if (--this._sizeInvalidationBeginCount === 0) {
667
+ this.updateSizeFromContainer();
668
+ }
669
+ }
670
+
671
+ /** @internal */
672
+ updateSizeFromContainer(): void {
673
+ const { width, height } = getElementWidthAndHeight(this._containerElement);
674
+ this.setSize(width, height);
675
+ }
676
+
677
+ /**
678
+ * Update the size of the root ContentItem. This will update the size of all contentItems in the tree
679
+ * @param force - In some cases the size is not updated if it has not changed. In this case, events
680
+ * (such as ComponentContainer.virtualRectingRequiredEvent) are not fired. Setting force to true, ensures the size is updated regardless, and
681
+ * the respective events are fired. This is sometimes necessary when a component's size has not changed but it has become visible, and the
682
+ * relevant events need to be fired.
683
+ */
684
+ updateRootSize(force = false): void {
685
+ if (this._groundItem === undefined) {
686
+ throw new UnexpectedUndefinedError('LMURS28881');
687
+ } else {
688
+ this._groundItem.updateSize(force);
689
+ }
690
+ }
691
+
692
+ /** @public */
693
+ createAndInitContentItem(config: ResolvedItemConfig, parent: ContentItem): ContentItem {
694
+ const newItem = this.createContentItem(config, parent);
695
+ newItem.init();
696
+ return newItem;
697
+ }
698
+
699
+ /**
700
+ * Recursively creates new item tree structures based on a provided
701
+ * ItemConfiguration object
702
+ *
703
+ * @param config - ResolvedItemConfig
704
+ * @param parent - The item the newly created item should be a child of
705
+ * @internal
706
+ */
707
+ createContentItem(config: ResolvedItemConfig, parent: ContentItem): ContentItem {
708
+ if (typeof config.type !== 'string') {
709
+ throw new ConfigurationError('Missing parameter \'type\'', JSON.stringify(config));
710
+ }
711
+
712
+ /**
713
+ * We add an additional stack around every component that's not within a stack anyways.
714
+ */
715
+ if (
716
+ // If this is a component
717
+ ResolvedItemConfig.isComponentItem(config) &&
718
+
719
+ // and it's not already within a stack
720
+ !(parent instanceof Stack) &&
721
+
722
+ // and we have a parent
723
+ !!parent &&
724
+
725
+ // and it's not the topmost item in a new window
726
+ !(this.isSubWindow === true && parent instanceof GroundItem)
727
+ ) {
728
+ const stackConfig: ResolvedStackItemConfig = {
729
+ type: ItemType.stack,
730
+ content: [config],
731
+ size: config.size,
732
+ sizeUnit: config.sizeUnit,
733
+ minSize: config.minSize,
734
+ minSizeUnit: config.minSizeUnit,
735
+ id: config.id,
736
+ maximised: config.maximised,
737
+ isClosable: config.isClosable,
738
+ activeItemIndex: 0,
739
+ header: undefined,
740
+ };
741
+
742
+ config = stackConfig;
743
+ }
744
+
745
+ const contentItem = this.createContentItemFromConfig(config, parent);
746
+ return contentItem;
747
+ }
748
+
749
+ findFirstComponentItemById(id: string): ComponentItem | undefined {
750
+ if (this._groundItem === undefined) {
751
+ throw new UnexpectedUndefinedError('LMFFCIBI82446');
752
+ } else {
753
+ return this.findFirstContentItemTypeByIdRecursive(ItemType.component, id, this._groundItem) as ComponentItem;
754
+ }
755
+ }
756
+
757
+ /**
758
+ * Creates a popout window with the specified content at the specified position
759
+ *
760
+ * @param itemConfigOrContentItem - The content of the popout window's layout manager derived from either
761
+ * a {@link (ContentItem:class)} or {@link (ItemConfig:interface)} or ResolvedItemConfig content (array of {@link (ItemConfig:interface)})
762
+ * @param positionAndSize - The width, height, left and top of Popout window
763
+ * @param parentId -The id of the element this item will be appended to when popIn is called
764
+ * @param indexInParent - The position of this item within its parent element
765
+ */
766
+
767
+ createPopout(itemConfigOrContentItem: ContentItem | ResolvedRootItemConfig,
768
+ positionAndSize: ResolvedPopoutLayoutConfig.Window,
769
+ parentId: string | null,
770
+ indexInParent: number | null
771
+ ): BrowserPopout {
772
+ if (itemConfigOrContentItem instanceof ContentItem) {
773
+ return this.createPopoutFromContentItem(itemConfigOrContentItem, positionAndSize, parentId, indexInParent);
774
+ } else {
775
+ return this.createPopoutFromItemConfig(itemConfigOrContentItem, positionAndSize, parentId, indexInParent);
776
+ }
777
+ }
778
+
779
+ /** @internal */
780
+ createPopoutFromContentItem(item: ContentItem,
781
+ window: ResolvedPopoutLayoutConfig.Window | undefined,
782
+ parentId: string | null,
783
+ indexInParent: number | null | undefined,
784
+ ): BrowserPopout {
785
+ /**
786
+ * If the item is the only component within a stack or for some
787
+ * other reason the only child of its parent the parent will be destroyed
788
+ * when the child is removed.
789
+ *
790
+ * In order to support this we move up the tree until we find something
791
+ * that will remain after the item is being popped out
792
+ */
793
+ let parent = item.parent;
794
+ let child = item;
795
+ while (parent !== null && parent.contentItems.length === 1 && !parent.isGround) {
796
+ child = parent;
797
+ parent = parent.parent;
798
+ }
799
+
800
+ if (parent === null) {
801
+ throw new UnexpectedNullError('LMCPFCI00834');
802
+ } else {
803
+ if (indexInParent === undefined) {
804
+ indexInParent = parent.contentItems.indexOf(child);
805
+ }
806
+
807
+ if (parentId !== null) {
808
+ parent.addPopInParentId(parentId);
809
+ }
810
+
811
+ if (window === undefined) {
812
+ const windowLeft = globalThis.screenX || globalThis.screenLeft;
813
+ const windowTop = globalThis.screenY || globalThis.screenTop;
814
+ const offsetLeft = item.element.offsetLeft;
815
+ const offsetTop = item.element.offsetTop
816
+ // const { left: offsetLeft, top: offsetTop } = getJQueryLeftAndTop(item.element);
817
+ const { width, height } = getElementWidthAndHeight(item.element);
818
+
819
+ window = {
820
+ left: windowLeft + offsetLeft,
821
+ top: windowTop + offsetTop,
822
+ width,
823
+ height,
824
+ };
825
+ }
826
+
827
+ const itemConfig = item.toConfig();
828
+ item.remove();
829
+
830
+ if (!ResolvedRootItemConfig.isRootItemConfig(itemConfig)) {
831
+ throw new Error(`${i18nStrings[I18nStringId.PopoutCannotBeCreatedWithGroundItemConfig]}`);
832
+ } else {
833
+ return this.createPopoutFromItemConfig(itemConfig, window, parentId, indexInParent);
834
+ }
835
+ }
836
+ }
837
+
838
+ /** @internal */
839
+ beginVirtualSizedContainerAdding(): void {
840
+ if (++this._virtualSizedContainerAddingBeginCount === 0) {
841
+ this._virtualSizedContainers.length = 0;
842
+ }
843
+ }
844
+
845
+ /** @internal */
846
+ addVirtualSizedContainer(container: ComponentContainer): void {
847
+ this._virtualSizedContainers.push(container);
848
+ }
849
+
850
+ /** @internal */
851
+ endVirtualSizedContainerAdding(): void {
852
+ if (--this._virtualSizedContainerAddingBeginCount === 0) {
853
+ const count = this._virtualSizedContainers.length;
854
+ if (count > 0) {
855
+ this.fireBeforeVirtualRectingEvent(count);
856
+ for (let i = 0; i < count; i++) {
857
+ const container = this._virtualSizedContainers[i];
858
+ container.notifyVirtualRectingRequired();
859
+ }
860
+ this.fireAfterVirtualRectingEvent();
861
+ this._virtualSizedContainers.length = 0;
862
+ }
863
+ }
864
+ }
865
+
866
+ /** @internal */
867
+ fireBeforeVirtualRectingEvent(count: number): void {
868
+ if (this.beforeVirtualRectingEvent !== undefined) {
869
+ this.beforeVirtualRectingEvent(count);
870
+ }
871
+ }
872
+
873
+ /** @internal */
874
+ fireAfterVirtualRectingEvent(): void {
875
+ if (this.afterVirtualRectingEvent !== undefined) {
876
+ this.afterVirtualRectingEvent();
877
+ }
878
+ }
879
+
880
+ /** @internal */
881
+ private createPopoutFromItemConfig(rootItemConfig: ResolvedRootItemConfig,
882
+ window: ResolvedPopoutLayoutConfig.Window,
883
+ parentId: string | null,
884
+ indexInParent: number | null
885
+ ) {
886
+ const layoutConfig = this.toConfig();
887
+
888
+ const popoutLayoutConfig: ResolvedPopoutLayoutConfig = {
889
+ root: rootItemConfig,
890
+ openPopouts: [],
891
+ settings: layoutConfig.settings,
892
+ dimensions: layoutConfig.dimensions,
893
+ header: layoutConfig.header,
894
+ window,
895
+ parentId,
896
+ indexInParent,
897
+ resolved: true,
898
+ }
899
+
900
+ return this.createPopoutFromPopoutLayoutConfig(popoutLayoutConfig);
901
+ }
902
+
903
+ /** @internal */
904
+ createPopoutFromPopoutLayoutConfig(config: ResolvedPopoutLayoutConfig): BrowserPopout {
905
+ const configWindow = config.window;
906
+ const initialWindow: Rect = {
907
+ left: configWindow.left ?? (globalThis.screenX || globalThis.screenLeft + 20),
908
+ top: configWindow.top ?? (globalThis.screenY || globalThis.screenTop + 20),
909
+ width: configWindow.width ?? 500,
910
+ height: configWindow.height ?? 309,
911
+ };
912
+
913
+ const browserPopout = new BrowserPopout(config, initialWindow, this);
914
+
915
+ browserPopout.on('initialised', () => this.emit('windowOpened', browserPopout));
916
+ browserPopout.on('closed', () => this.reconcilePopoutWindows());
917
+
918
+ this._openPopouts.push(browserPopout);
919
+
920
+ if (this.layoutConfig.settings.closePopoutsOnUnload && !this._windowBeforeUnloadListening) {
921
+ globalThis.addEventListener('beforeunload', this._windowBeforeUnloadListener, { passive: true });
922
+ this._windowBeforeUnloadListening = true;
923
+ }
924
+
925
+ return browserPopout;
926
+ }
927
+
928
+ /**
929
+ * Closes all Open Popouts
930
+ * Applications can call this method when a page is unloaded to remove its open popouts
931
+ */
932
+
933
+ closeAllOpenPopouts() {
934
+ for (let i = 0; i < this._openPopouts.length; i++) {
935
+ this._openPopouts[i].close();
936
+ }
937
+
938
+ this._openPopouts.length = 0;
939
+
940
+ if (this._windowBeforeUnloadListening) {
941
+ globalThis.removeEventListener('beforeunload', this._windowBeforeUnloadListener);
942
+ this._windowBeforeUnloadListening = false;
943
+ }
944
+ }
945
+
946
+ /**
947
+ * Attaches DragListener to any given DOM element
948
+ * and turns it into a way of creating new ComponentItems
949
+ * by 'dragging' the DOM element into the layout
950
+ *
951
+ * @param element - The HTML element which will be listened to for commencement of drag.
952
+ * @param componentTypeOrItemConfigCallback - Type of component to be created, or a callback which will provide the ItemConfig
953
+ * to be used to create the component.
954
+ * @param componentState - Optional initial state of component. This will be ignored if componentTypeOrFtn is a function.
955
+ *
956
+ * @returns an opaque object that identifies the DOM element
957
+ * and the attached itemConfig. This can be used in
958
+ * removeDragSource() later to get rid of the drag listeners.
959
+ */
960
+ newDragSource(element: HTMLElement, itemConfigCallback: () => (DragSource.ComponentItemConfig | ComponentItemConfig)): DragSource;
961
+ /** @deprecated will be replaced in version 3 with newDragSource(element: HTMLElement, itemConfig: ComponentItemConfig) */
962
+ newDragSource(element: HTMLElement, componentType: JsonValue, componentState?: JsonValue, title?: JsonValue, id?: string): DragSource;
963
+ newDragSource(element: HTMLElement,
964
+ componentTypeOrItemConfigCallback: JsonValue | (() => (DragSource.ComponentItemConfig | ComponentItemConfig)),
965
+ componentState?: JsonValue,
966
+ title?: string,
967
+ id?: string,
968
+ ): DragSource {
969
+ const dragSource = new DragSource(this, element, [], componentTypeOrItemConfigCallback, componentState, title, id);
970
+ this._dragSources.push(dragSource);
971
+
972
+ return dragSource;
973
+ }
974
+
975
+ /**
976
+ * Removes a DragListener added by createDragSource() so the corresponding
977
+ * DOM element is not a drag source any more.
978
+ */
979
+ removeDragSource(dragSource: DragSource): void {
980
+ removeFromArray(dragSource, this._dragSources );
981
+ dragSource.destroy();
982
+ }
983
+
984
+ /** @internal */
985
+ startComponentDrag(x: number, y: number, dragListener: DragListener, componentItem: ComponentItem, stack: Stack): void {
986
+ new DragProxy(
987
+ x,
988
+ y,
989
+ dragListener,
990
+ this,
991
+ componentItem,
992
+ stack
993
+ );
994
+ }
995
+
996
+ /**
997
+ * Programmatically focuses an item. This focuses the specified component item
998
+ * and the item emits a focus event
999
+ *
1000
+ * @param item - The component item to be focused
1001
+ * @param suppressEvent - Whether to emit focus event
1002
+ */
1003
+ focusComponent(item: ComponentItem, suppressEvent = false): void {
1004
+ item.focus(suppressEvent);
1005
+ }
1006
+
1007
+ /**
1008
+ * Programmatically blurs (defocuses) the currently focused component.
1009
+ * If a component item is focused, then it is blurred and and the item emits a blur event
1010
+ *
1011
+ * @param item - The component item to be blurred
1012
+ * @param suppressEvent - Whether to emit blur event
1013
+ */
1014
+ clearComponentFocus(suppressEvent = false): void {
1015
+ this.setFocusedComponentItem(undefined, suppressEvent);
1016
+ }
1017
+
1018
+ /**
1019
+ * Programmatically focuses a component item or removes focus (blurs) from an existing focused component item.
1020
+ *
1021
+ * @param item - If defined, specifies the component item to be given focus. If undefined, clear component focus.
1022
+ * @param suppressEvents - Whether to emit focus and blur events
1023
+ * @internal
1024
+ */
1025
+ setFocusedComponentItem(item: ComponentItem | undefined, suppressEvents = false): void {
1026
+ if (item !== this._focusedComponentItem) {
1027
+
1028
+ let newFocusedParentItem: ComponentParentableItem | undefined;
1029
+ if (item === undefined) {
1030
+ newFocusedParentItem === undefined;
1031
+ } else {
1032
+ newFocusedParentItem = item.parentItem;
1033
+ }
1034
+
1035
+ if (this._focusedComponentItem !== undefined) {
1036
+ const oldFocusedItem = this._focusedComponentItem;
1037
+ this._focusedComponentItem = undefined;
1038
+ oldFocusedItem.setBlurred(suppressEvents);
1039
+ const oldFocusedParentItem = oldFocusedItem.parentItem;
1040
+ if (newFocusedParentItem === oldFocusedParentItem) {
1041
+ newFocusedParentItem = undefined;
1042
+ } else {
1043
+ oldFocusedParentItem.setFocusedValue(false);
1044
+ }
1045
+ }
1046
+
1047
+ if (item !== undefined) {
1048
+ this._focusedComponentItem = item;
1049
+ item.setFocused(suppressEvents);
1050
+ if (newFocusedParentItem !== undefined) {
1051
+ newFocusedParentItem.setFocusedValue(true);
1052
+ }
1053
+ }
1054
+ }
1055
+ }
1056
+
1057
+ /** @internal */
1058
+ private createContentItemFromConfig(config: ResolvedItemConfig, parent: ContentItem): ContentItem {
1059
+ switch (config.type) {
1060
+ case ItemType.ground: throw new AssertError('LMCCIFC68871');
1061
+ case ItemType.row: return new RowOrColumn(false, this, config as ResolvedRowOrColumnItemConfig, parent);
1062
+ case ItemType.column: return new RowOrColumn(true, this, config as ResolvedRowOrColumnItemConfig, parent);
1063
+ case ItemType.stack: return new Stack(this, config as ResolvedStackItemConfig, parent);
1064
+ case ItemType.component:
1065
+ return new ComponentItem(this, config as ResolvedComponentItemConfig, parent as Stack);
1066
+ default:
1067
+ throw new UnreachableCaseError('CCC913564', config.type, 'Invalid Config Item type specified');
1068
+ }
1069
+ }
1070
+
1071
+ /**
1072
+ * This should only be called from stack component.
1073
+ * Stack will look after docking processing associated with maximise/minimise
1074
+ * @internal
1075
+ **/
1076
+ setMaximisedStack(stack: Stack | undefined): void {
1077
+ if (stack === undefined) {
1078
+ if (this._maximisedStack !== undefined) {
1079
+ this.processMinimiseMaximisedStack();
1080
+ }
1081
+ } else {
1082
+ if (stack !== this._maximisedStack) {
1083
+ if (this._maximisedStack !== undefined) {
1084
+ this.processMinimiseMaximisedStack();
1085
+ }
1086
+
1087
+ this.processMaximiseStack(stack);
1088
+ }
1089
+ }
1090
+ }
1091
+
1092
+ checkMinimiseMaximisedStack(): void {
1093
+ if (this._maximisedStack !== undefined) {
1094
+ this._maximisedStack.minimise();
1095
+ }
1096
+ }
1097
+
1098
+ // showAllActiveContentItems() was called from ContentItem.show(). Not sure what its purpose was so have commented out
1099
+ // Everything seems to work ok without this. Have left commented code just in case there was a reason for it becomes
1100
+ // apparent
1101
+
1102
+ // /** @internal */
1103
+ // showAllActiveContentItems(): void {
1104
+ // const allStacks = this.getAllStacks();
1105
+
1106
+ // for (let i = 0; i < allStacks.length; i++) {
1107
+ // const stack = allStacks[i];
1108
+ // const activeContentItem = stack.getActiveComponentItem();
1109
+
1110
+ // if (activeContentItem !== undefined) {
1111
+ // if (!(activeContentItem instanceof ComponentItem)) {
1112
+ // throw new AssertError('LMSAACIS22298');
1113
+ // } else {
1114
+ // activeContentItem.container.show();
1115
+ // }
1116
+ // }
1117
+ // }
1118
+ // }
1119
+
1120
+ // hideAllActiveContentItems() was called from ContentItem.hide(). Not sure what its purpose was so have commented out
1121
+ // Everything seems to work ok without this. Have left commented code just in case there was a reason for it becomes
1122
+ // apparent
1123
+
1124
+ // /** @internal */
1125
+ // hideAllActiveContentItems(): void {
1126
+ // const allStacks = this.getAllStacks();
1127
+
1128
+ // for (let i = 0; i < allStacks.length; i++) {
1129
+ // const stack = allStacks[i];
1130
+ // const activeContentItem = stack.getActiveComponentItem();
1131
+
1132
+ // if (activeContentItem !== undefined) {
1133
+ // if (!(activeContentItem instanceof ComponentItem)) {
1134
+ // throw new AssertError('LMSAACIH22298');
1135
+ // } else {
1136
+ // activeContentItem.container.hide();
1137
+ // }
1138
+ // }
1139
+ // }
1140
+ // }
1141
+
1142
+ /** @internal */
1143
+ private cleanupBeforeMaximisedStackDestroyed(event: EventEmitter.BubblingEvent) {
1144
+ if (this._maximisedStack !== null && this._maximisedStack === event.target) {
1145
+ this._maximisedStack.off('beforeItemDestroyed', this._maximisedStackBeforeDestroyedListener);
1146
+ this._maximisedStack = undefined;
1147
+ }
1148
+ }
1149
+
1150
+ /**
1151
+ * This method is used to get around sandboxed iframe restrictions.
1152
+ * If 'allow-top-navigation' is not specified in the iframe's 'sandbox' attribute
1153
+ * (as is the case with codepens) the parent window is forbidden from calling certain
1154
+ * methods on the child, such as window.close() or setting document.location.href.
1155
+ *
1156
+ * This prevented GoldenLayout popouts from popping in in codepens. The fix is to call
1157
+ * _$closeWindow on the child window's gl instance which (after a timeout to disconnect
1158
+ * the invoking method from the close call) closes itself.
1159
+ *
1160
+ * @internal
1161
+ */
1162
+ closeWindow(): void {
1163
+ globalThis.setTimeout(() => globalThis.close(), 1);
1164
+ }
1165
+
1166
+ /** @internal */
1167
+ getArea(x: number, y: number): ContentItem.Area | null {
1168
+ let matchingArea = null;
1169
+ let smallestSurface = Infinity;
1170
+
1171
+ for (let i = 0; i < this._itemAreas.length; i++) {
1172
+ const area = this._itemAreas[i];
1173
+
1174
+ if (
1175
+ x >= area.x1 &&
1176
+ x < area.x2 && // x2 is not included in area
1177
+ y >= area.y1 &&
1178
+ y < area.y2 && // y2 is not included in area
1179
+ smallestSurface > area.surface
1180
+ ) {
1181
+ smallestSurface = area.surface;
1182
+ matchingArea = area;
1183
+ }
1184
+ }
1185
+
1186
+ return matchingArea;
1187
+ }
1188
+
1189
+ /** @internal */
1190
+ calculateItemAreas(): void {
1191
+ const allContentItems = this.getAllContentItems();
1192
+ /**
1193
+ * If the last item is dragged out, highlight the entire container size to
1194
+ * allow to re-drop it. this.ground.contentiItems.length === 0 at this point
1195
+ *
1196
+ * Don't include ground into the possible drop areas though otherwise since it
1197
+ * will used for every gap in the layout, e.g. splitters
1198
+ */
1199
+ const groundItem = this._groundItem;
1200
+ if (groundItem === undefined) {
1201
+ throw new UnexpectedUndefinedError('LMCIAR44365');
1202
+ } else {
1203
+ if (allContentItems.length === 1) {
1204
+ // No root ContentItem (just Ground ContentItem)
1205
+ const groundArea = groundItem.getElementArea();
1206
+ if (groundArea === null) {
1207
+ throw new UnexpectedNullError('LMCIARA44365')
1208
+ } else {
1209
+ this._itemAreas = [groundArea];
1210
+ }
1211
+ return;
1212
+ } else {
1213
+ if (groundItem.contentItems[0].isStack) {
1214
+ // if root is Stack, then split stack and sides of Layout are same, so skip sides
1215
+ this._itemAreas = [];
1216
+ } else {
1217
+ // sides of layout
1218
+ this._itemAreas = groundItem.createSideAreas();
1219
+ }
1220
+
1221
+ for (let i = 0; i < allContentItems.length; i++) {
1222
+ const stack = allContentItems[i];
1223
+ if (ContentItem.isStack(stack)) {
1224
+ const area = stack.getArea();
1225
+
1226
+ if (area === null) {
1227
+ continue;
1228
+ } else {
1229
+ this._itemAreas.push(area);
1230
+ const stackContentAreaDimensions = stack.contentAreaDimensions;
1231
+ if (stackContentAreaDimensions === undefined) {
1232
+ throw new UnexpectedUndefinedError('LMCIASC45599');
1233
+ } else {
1234
+ const highlightArea = stackContentAreaDimensions.header.highlightArea
1235
+ const surface = (highlightArea.x2 - highlightArea.x1) * (highlightArea.y2 - highlightArea.y1);
1236
+
1237
+ const header: ContentItem.Area = {
1238
+ x1: highlightArea.x1,
1239
+ x2: highlightArea.x2,
1240
+ y1: highlightArea.y1,
1241
+ y2: highlightArea.y2,
1242
+ contentItem: stack,
1243
+ surface,
1244
+ };
1245
+ this._itemAreas.push(header);
1246
+ }
1247
+ }
1248
+ }
1249
+ }
1250
+ }
1251
+ }
1252
+ }
1253
+
1254
+ /**
1255
+ * Called as part of loading a new layout (including initial init()).
1256
+ * Checks to see layout has a maximised item. If so, it maximises that item.
1257
+ * @internal
1258
+ */
1259
+ private checkLoadedLayoutMaximiseItem() {
1260
+ if (this._groundItem === undefined) {
1261
+ throw new UnexpectedUndefinedError('LMCLLMI43432');
1262
+ } else {
1263
+ const configMaximisedItems = this._groundItem.getConfigMaximisedItems();
1264
+
1265
+ if (configMaximisedItems.length > 0) {
1266
+ let item = configMaximisedItems[0];
1267
+ if (ContentItem.isComponentItem(item)) {
1268
+ const stack = item.parent;
1269
+ if (stack === null) {
1270
+ throw new UnexpectedNullError('LMXLLMI69999');
1271
+ } else {
1272
+ item = stack;
1273
+ }
1274
+ }
1275
+ if (!ContentItem.isStack(item)) {
1276
+ throw new AssertError('LMCLLMI19993');
1277
+ } else {
1278
+ item.maximise();
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+
1284
+ /** @internal */
1285
+ private processMaximiseStack(stack: Stack): void {
1286
+ this._maximisedStack = stack;
1287
+ stack.on('beforeItemDestroyed', this._maximisedStackBeforeDestroyedListener);
1288
+ stack.element.classList.add(DomConstants.ClassName.Maximised);
1289
+ stack.element.insertAdjacentElement('afterend', this._maximisePlaceholder);
1290
+ if (this._groundItem === undefined) {
1291
+ throw new UnexpectedUndefinedError('LMMXI19993');
1292
+ } else {
1293
+ this._groundItem.element.prepend(stack.element);
1294
+ const { width, height } = getElementWidthAndHeight(this._containerElement);
1295
+ setElementWidth(stack.element, width);
1296
+ setElementHeight(stack.element, height);
1297
+ stack.updateSize(true);
1298
+ stack.focusActiveContentItem();
1299
+ this._maximisedStack.emit('maximised');
1300
+ this.emit('stateChanged');
1301
+ }
1302
+ }
1303
+
1304
+ /** @internal */
1305
+ private processMinimiseMaximisedStack(): void {
1306
+ if (this._maximisedStack === undefined) {
1307
+ throw new AssertError('LMMMS74422');
1308
+ } else {
1309
+ const stack = this._maximisedStack;
1310
+ if (stack.parent === null) {
1311
+ throw new UnexpectedNullError('LMMI13668');
1312
+ } else {
1313
+ stack.element.classList.remove(DomConstants.ClassName.Maximised);
1314
+ this._maximisePlaceholder.insertAdjacentElement('afterend', stack.element);
1315
+ this._maximisePlaceholder.remove();
1316
+ this.updateRootSize(true);
1317
+ this._maximisedStack = undefined;
1318
+ stack.off('beforeItemDestroyed', this._maximisedStackBeforeDestroyedListener);
1319
+ stack.emit('minimised');
1320
+ this.emit('stateChanged');
1321
+ }
1322
+ }
1323
+ }
1324
+
1325
+ /**
1326
+ * Iterates through the array of open popout windows and removes the ones
1327
+ * that are effectively closed. This is necessary due to the lack of reliably
1328
+ * listening for window.close / unload events in a cross browser compatible fashion.
1329
+ * @internal
1330
+ */
1331
+ private reconcilePopoutWindows() {
1332
+ const openPopouts: BrowserPopout[] = [];
1333
+
1334
+ for (let i = 0; i < this._openPopouts.length; i++) {
1335
+ if (this._openPopouts[i].getWindow().closed === false) {
1336
+ openPopouts.push(this._openPopouts[i]);
1337
+ } else {
1338
+ this.emit('windowClosed', this._openPopouts[i]);
1339
+ }
1340
+ }
1341
+
1342
+ if (this._openPopouts.length !== openPopouts.length) {
1343
+ this._openPopouts = openPopouts;
1344
+ this.emit('stateChanged');
1345
+ }
1346
+
1347
+ }
1348
+
1349
+ /**
1350
+ * Returns a flattened array of all content items,
1351
+ * regardles of level or type
1352
+ * @internal
1353
+ */
1354
+ private getAllContentItems() {
1355
+ if (this._groundItem === undefined) {
1356
+ throw new UnexpectedUndefinedError('LMGACI13130');
1357
+ } else {
1358
+ return this._groundItem.getAllContentItems();
1359
+ }
1360
+ }
1361
+
1362
+ /**
1363
+ * Creates Subwindows (if there are any). Throws an error
1364
+ * if popouts are blocked.
1365
+ * @internal
1366
+ */
1367
+ private createSubWindows() {
1368
+ for (let i = 0; i < this.layoutConfig.openPopouts.length; i++) {
1369
+ const popoutConfig = this.layoutConfig.openPopouts[i];
1370
+ this.createPopoutFromPopoutLayoutConfig(popoutConfig);
1371
+ }
1372
+ }
1373
+
1374
+ /**
1375
+ * Debounces resize events
1376
+ * @internal
1377
+ */
1378
+ private handleContainerResize(): void {
1379
+ if (this.resizeWithContainerAutomatically) {
1380
+ this.processResizeWithDebounce();
1381
+ }
1382
+ }
1383
+
1384
+ /**
1385
+ * Debounces resize events
1386
+ * @internal
1387
+ */
1388
+ private processResizeWithDebounce(): void {
1389
+ if (this.resizeDebounceExtendedWhenPossible) {
1390
+ this.checkClearResizeTimeout();
1391
+ }
1392
+
1393
+ if (this._resizeTimeoutId === undefined) {
1394
+ this._resizeTimeoutId = setTimeout(
1395
+ () => {
1396
+ this._resizeTimeoutId = undefined;
1397
+ this.beginSizeInvalidation();
1398
+ this.endSizeInvalidation();
1399
+ },
1400
+ this.resizeDebounceInterval,
1401
+ );
1402
+ }
1403
+ }
1404
+
1405
+ private checkClearResizeTimeout() {
1406
+ if (this._resizeTimeoutId !== undefined) {
1407
+ clearTimeout(this._resizeTimeoutId);
1408
+ this._resizeTimeoutId = undefined;
1409
+ }
1410
+ }
1411
+
1412
+ /**
1413
+ * Determines what element the layout will be created in
1414
+ * @internal
1415
+ */
1416
+ private setContainer() {
1417
+ const bodyElement = document.body;
1418
+ const containerElement = this._containerElement ?? bodyElement;
1419
+
1420
+ if (containerElement === bodyElement) {
1421
+ this.resizeWithContainerAutomatically = true;
1422
+
1423
+ const documentElement = document.documentElement;
1424
+ documentElement.style.height = '100%';
1425
+ documentElement.style.margin = '0';
1426
+ documentElement.style.padding = '0';
1427
+ documentElement.style.overflow = 'clip';
1428
+ bodyElement.style.height = '100%';
1429
+ bodyElement.style.margin = '0';
1430
+ bodyElement.style.padding = '0';
1431
+ bodyElement.style.overflow = 'clip';
1432
+ }
1433
+
1434
+ this._containerElement = containerElement;
1435
+ }
1436
+
1437
+ /**
1438
+ * Called when the window is closed or the user navigates away
1439
+ * from the page
1440
+ * @internal
1441
+ * @deprecated to be removed in version 3
1442
+ */
1443
+ private onBeforeUnload(): void {
1444
+ this.destroy();
1445
+ }
1446
+
1447
+ /**
1448
+ * Adjusts the number of columns to be lower to fit the screen and still maintain minItemWidth.
1449
+ * @internal
1450
+ */
1451
+ private adjustColumnsResponsive() {
1452
+ if (this._groundItem === undefined) {
1453
+ throw new UnexpectedUndefinedError('LMACR20883');
1454
+ } else {
1455
+ this._firstLoad = false;
1456
+ // If there is no min width set, or not content items, do nothing.
1457
+ if (this.useResponsiveLayout() &&
1458
+ !this._updatingColumnsResponsive &&
1459
+ this._groundItem.contentItems.length > 0 &&
1460
+ this._groundItem.contentItems[0].isRow)
1461
+ {
1462
+ if (this._groundItem === undefined || this._width === null) {
1463
+ throw new UnexpectedUndefinedError('LMACR77412');
1464
+ } else {
1465
+ // If there is only one column, do nothing.
1466
+ const columnCount = this._groundItem.contentItems[0].contentItems.length;
1467
+ if (columnCount <= 1) {
1468
+ return;
1469
+ } else {
1470
+ // If they all still fit, do nothing.
1471
+ const minItemWidth = this.layoutConfig.dimensions.defaultMinItemWidth;
1472
+ const totalMinWidth = columnCount * minItemWidth;
1473
+ if (totalMinWidth <= this._width) {
1474
+ return;
1475
+ } else {
1476
+ // Prevent updates while it is already happening.
1477
+ this._updatingColumnsResponsive = true;
1478
+
1479
+ // Figure out how many columns to stack, and put them all in the first stack container.
1480
+ const finalColumnCount = Math.max(Math.floor(this._width / minItemWidth), 1);
1481
+ const stackColumnCount = columnCount - finalColumnCount;
1482
+
1483
+ const rootContentItem = this._groundItem.contentItems[0];
1484
+ const allStacks = this.getAllStacks();
1485
+ if (allStacks.length === 0) {
1486
+ throw new AssertError('LMACRS77413')
1487
+ } else {
1488
+ const firstStackContainer = allStacks[0];
1489
+ for (let i = 0; i < stackColumnCount; i++) {
1490
+ // Stack from right.
1491
+ const column = rootContentItem.contentItems[rootContentItem.contentItems.length - 1];
1492
+ this.addChildContentItemsToContainer(firstStackContainer, column);
1493
+ }
1494
+
1495
+ this._updatingColumnsResponsive = false;
1496
+ }
1497
+ }
1498
+ }
1499
+ }
1500
+ }
1501
+ }
1502
+ }
1503
+
1504
+ /**
1505
+ * Determines if responsive layout should be used.
1506
+ *
1507
+ * @returns True if responsive layout should be used; otherwise false.
1508
+ * @internal
1509
+ */
1510
+ private useResponsiveLayout() {
1511
+ const settings = this.layoutConfig.settings;
1512
+ const alwaysResponsiveMode = settings.responsiveMode === ResponsiveMode.always;
1513
+ const onLoadResponsiveModeAndFirst = settings.responsiveMode === ResponsiveMode.onload && this._firstLoad;
1514
+ return alwaysResponsiveMode || onLoadResponsiveModeAndFirst;
1515
+ }
1516
+
1517
+ /**
1518
+ * Adds all children of a node to another container recursively.
1519
+ * @param container - Container to add child content items to.
1520
+ * @param node - Node to search for content items.
1521
+ * @internal
1522
+ */
1523
+ private addChildContentItemsToContainer(container: ContentItem, node: ContentItem) {
1524
+ const contentItems = node.contentItems;
1525
+ if (node instanceof Stack) {
1526
+ for (let i = 0; i < contentItems.length; i++) {
1527
+ const item = contentItems[i];
1528
+ node.removeChild(item, true);
1529
+ container.addChild(item);
1530
+ }
1531
+ } else {
1532
+ for (let i = 0; i < contentItems.length; i++) {
1533
+ const item = contentItems[i];
1534
+ this.addChildContentItemsToContainer(container, item);
1535
+ }
1536
+ }
1537
+ }
1538
+
1539
+ /**
1540
+ * Finds all the stacks.
1541
+ * @returns The found stack containers.
1542
+ * @internal
1543
+ */
1544
+ private getAllStacks() {
1545
+ if (this._groundItem === undefined) {
1546
+ throw new UnexpectedUndefinedError('LMFASC52778');
1547
+ } else {
1548
+ const stacks: Stack[] = [];
1549
+ this.findAllStacksRecursive(stacks, this._groundItem);
1550
+
1551
+ return stacks;
1552
+ }
1553
+ }
1554
+
1555
+ /** @internal */
1556
+ private findFirstContentItemType(type: ItemType): ContentItem | undefined {
1557
+ if (this._groundItem === undefined) {
1558
+ throw new UnexpectedUndefinedError('LMFFCIT82446');
1559
+ } else {
1560
+ return this.findFirstContentItemTypeRecursive(type, this._groundItem);
1561
+ }
1562
+ }
1563
+
1564
+ /** @internal */
1565
+ private findFirstContentItemTypeRecursive(type: ItemType, node: ContentItem): ContentItem | undefined {
1566
+ const contentItems = node.contentItems;
1567
+ const contentItemCount = contentItems.length;
1568
+ if (contentItemCount === 0) {
1569
+ return undefined;
1570
+ } else {
1571
+ for (let i = 0; i < contentItemCount; i++) {
1572
+ const contentItem = contentItems[i];
1573
+ if (contentItem.type === type) {
1574
+ return contentItem;
1575
+ }
1576
+ }
1577
+
1578
+ for (let i = 0; i < contentItemCount; i++) {
1579
+ const contentItem = contentItems[i];
1580
+ const foundContentItem = this.findFirstContentItemTypeRecursive(type, contentItem);
1581
+ if (foundContentItem !== undefined) {
1582
+ return foundContentItem;
1583
+ }
1584
+ }
1585
+
1586
+ return undefined;
1587
+ }
1588
+ }
1589
+
1590
+ /** @internal */
1591
+ private findFirstContentItemTypeByIdRecursive(type: ItemType, id: string, node: ContentItem): ContentItem | undefined {
1592
+ const contentItems = node.contentItems;
1593
+ const contentItemCount = contentItems.length;
1594
+ if (contentItemCount === 0) {
1595
+ return undefined;
1596
+ } else {
1597
+ for (let i = 0; i < contentItemCount; i++) {
1598
+ const contentItem = contentItems[i];
1599
+ if (contentItem.type === type && contentItem.id === id) {
1600
+ return contentItem;
1601
+ }
1602
+ }
1603
+
1604
+ for (let i = 0; i < contentItemCount; i++) {
1605
+ const contentItem = contentItems[i];
1606
+ const foundContentItem = this.findFirstContentItemTypeByIdRecursive(type, id, contentItem);
1607
+ if (foundContentItem !== undefined) {
1608
+ return foundContentItem;
1609
+ }
1610
+ }
1611
+
1612
+ return undefined;
1613
+ }
1614
+ }
1615
+
1616
+ /**
1617
+ * Finds all the stack containers.
1618
+ *
1619
+ * @param stacks - Set of containers to populate.
1620
+ * @param node - Current node to process.
1621
+ * @internal
1622
+ */
1623
+ private findAllStacksRecursive(stacks: Stack[], node: ContentItem) {
1624
+ const contentItems = node.contentItems;
1625
+ for (let i = 0; i < contentItems.length; i++) {
1626
+ const item = contentItems[i];
1627
+ if (item instanceof Stack) {
1628
+ stacks.push(item);
1629
+ } else {
1630
+ if (!item.isComponent) {
1631
+ this.findAllStacksRecursive(stacks, item);
1632
+ }
1633
+ }
1634
+ }
1635
+ }
1636
+
1637
+ /** @internal */
1638
+ private findFirstLocation(selectors: readonly LayoutManager.LocationSelector[]): LayoutManager.Location | undefined {
1639
+ const count = selectors.length;
1640
+ for (let i = 0; i < count; i++) {
1641
+ const selector = selectors[i];
1642
+ const location = this.findLocation(selector);
1643
+ if (location !== undefined) {
1644
+ return location;
1645
+ }
1646
+ }
1647
+ return undefined;
1648
+ }
1649
+
1650
+ /** @internal */
1651
+ private findLocation(selector: LayoutManager.LocationSelector): LayoutManager.Location | undefined {
1652
+ const selectorIndex = selector.index;
1653
+ switch (selector.typeId) {
1654
+ case LayoutManager.LocationSelector.TypeId.FocusedItem: {
1655
+ if (this._focusedComponentItem === undefined) {
1656
+ return undefined
1657
+ } else {
1658
+ const parentItem = this._focusedComponentItem.parentItem;
1659
+ const parentContentItems = parentItem.contentItems;
1660
+ const parentContentItemCount = parentContentItems.length;
1661
+ if (selectorIndex === undefined) {
1662
+ return { parentItem, index: parentContentItemCount };
1663
+ } else {
1664
+ const focusedIndex = parentContentItems.indexOf(this._focusedComponentItem);
1665
+ const index = focusedIndex + selectorIndex;
1666
+ if (index < 0 || index > parentContentItemCount) {
1667
+ return undefined;
1668
+ } else {
1669
+ return { parentItem, index };
1670
+ }
1671
+ }
1672
+ }
1673
+ }
1674
+ case LayoutManager.LocationSelector.TypeId.FocusedStack: {
1675
+ if (this._focusedComponentItem === undefined) {
1676
+ return undefined
1677
+ } else {
1678
+ const parentItem = this._focusedComponentItem.parentItem;
1679
+ return this.tryCreateLocationFromParentItem(parentItem, selectorIndex);
1680
+ }
1681
+ }
1682
+ case LayoutManager.LocationSelector.TypeId.FirstStack: {
1683
+ const parentItem = this.findFirstContentItemType(ItemType.stack);
1684
+ if (parentItem === undefined) {
1685
+ return undefined;
1686
+ } else {
1687
+ return this.tryCreateLocationFromParentItem(parentItem, selectorIndex);
1688
+ }
1689
+ }
1690
+ case LayoutManager.LocationSelector.TypeId.FirstRowOrColumn: {
1691
+ let parentItem = this.findFirstContentItemType(ItemType.row);
1692
+ if (parentItem !== undefined) {
1693
+ return this.tryCreateLocationFromParentItem(parentItem, selectorIndex);
1694
+ } else {
1695
+ parentItem = this.findFirstContentItemType(ItemType.column);
1696
+ if (parentItem !== undefined) {
1697
+ return this.tryCreateLocationFromParentItem(parentItem, selectorIndex);
1698
+ } else {
1699
+ return undefined;
1700
+ }
1701
+ }
1702
+ }
1703
+ case LayoutManager.LocationSelector.TypeId.FirstRow: {
1704
+ const parentItem = this.findFirstContentItemType(ItemType.row);
1705
+ if (parentItem === undefined) {
1706
+ return undefined;
1707
+ } else {
1708
+ return this.tryCreateLocationFromParentItem(parentItem, selectorIndex);
1709
+ }
1710
+ }
1711
+ case LayoutManager.LocationSelector.TypeId.FirstColumn: {
1712
+ const parentItem = this.findFirstContentItemType(ItemType.column);
1713
+ if (parentItem === undefined) {
1714
+ return undefined;
1715
+ } else {
1716
+ return this.tryCreateLocationFromParentItem(parentItem, selectorIndex);
1717
+ }
1718
+ }
1719
+ case LayoutManager.LocationSelector.TypeId.Empty: {
1720
+ if (this._groundItem === undefined) {
1721
+ throw new UnexpectedUndefinedError('LMFLRIF18244');
1722
+ } else {
1723
+ if (this.rootItem !== undefined) {
1724
+ return undefined;
1725
+ } else {
1726
+ if (selectorIndex === undefined || selectorIndex === 0)
1727
+ return { parentItem: this._groundItem, index: 0 };
1728
+ else {
1729
+ return undefined;
1730
+ }
1731
+ }
1732
+ }
1733
+ }
1734
+ case LayoutManager.LocationSelector.TypeId.Root: {
1735
+ if (this._groundItem === undefined) {
1736
+ throw new UnexpectedUndefinedError('LMFLF18244');
1737
+ } else {
1738
+ const groundContentItems = this._groundItem.contentItems;
1739
+ if (groundContentItems.length === 0) {
1740
+ if (selectorIndex === undefined || selectorIndex === 0)
1741
+ return { parentItem: this._groundItem, index: 0 };
1742
+ else {
1743
+ return undefined;
1744
+ }
1745
+ } else {
1746
+ const parentItem = groundContentItems[0];
1747
+ return this.tryCreateLocationFromParentItem(parentItem, selectorIndex);
1748
+ }
1749
+ }
1750
+ }
1751
+ }
1752
+ }
1753
+
1754
+ /** @internal */
1755
+ private tryCreateLocationFromParentItem(parentItem: ContentItem,
1756
+ selectorIndex: number | undefined
1757
+ ): LayoutManager.Location | undefined {
1758
+ const parentContentItems = parentItem.contentItems;
1759
+ const parentContentItemCount = parentContentItems.length;
1760
+ if (selectorIndex === undefined) {
1761
+ return { parentItem, index: parentContentItemCount };
1762
+ } else {
1763
+ if (selectorIndex < 0 || selectorIndex > parentContentItemCount) {
1764
+ return undefined;
1765
+ } else {
1766
+ return { parentItem, index: selectorIndex };
1767
+ }
1768
+ }
1769
+ }
1770
+ }
1771
+
1772
+ /** @public */
1773
+ export namespace LayoutManager {
1774
+ export type BeforeVirtualRectingEvent = (this: void, count: number) => void;
1775
+ export type AfterVirtualRectingEvent = (this: void) => void;
1776
+
1777
+ /** @internal */
1778
+ export interface ConstructorParameters {
1779
+ constructorOrSubWindowLayoutConfig: LayoutConfig | undefined;
1780
+ isSubWindow: boolean;
1781
+ containerElement: HTMLElement | undefined;
1782
+ }
1783
+
1784
+ /** @internal */
1785
+ export function createMaximisePlaceElement(document: Document): HTMLElement {
1786
+ const element = document.createElement('div');
1787
+ element.classList.add(DomConstants.ClassName.MaximisePlace);
1788
+ return element;
1789
+ }
1790
+
1791
+ /** @internal */
1792
+ export function createTabDropPlaceholderElement(document: Document): HTMLElement {
1793
+ const element = document.createElement('div');
1794
+ element.classList.add(DomConstants.ClassName.DropTabPlaceholder);
1795
+ return element;
1796
+ }
1797
+
1798
+ /**
1799
+ * Specifies a location of a ContentItem without referencing the content item.
1800
+ * Used to specify where a new item is to be added
1801
+ * @public
1802
+ */
1803
+ export interface Location {
1804
+ parentItem: ContentItem;
1805
+ index: number;
1806
+ }
1807
+
1808
+ /**
1809
+ * A selector used to specify a unique location in the layout
1810
+ * @public
1811
+ */
1812
+ export interface LocationSelector {
1813
+ /** Specifies selector algorithm */
1814
+ typeId: LocationSelector.TypeId;
1815
+ /** Used by algorithm to determine index in found ContentItem */
1816
+ index?: number;
1817
+ }
1818
+
1819
+ /** @public */
1820
+ export namespace LocationSelector {
1821
+ export const enum TypeId {
1822
+ /** Stack with focused Item. Index specifies offset from index of focused item (eg 1 is the position after focused item) */
1823
+ FocusedItem,
1824
+ /** Stack with focused Item. Index specfies ContentItems index */
1825
+ FocusedStack,
1826
+ /** First stack found in layout */
1827
+ FirstStack,
1828
+ /** First Row or Column found in layout (rows are searched first) */
1829
+ FirstRowOrColumn,
1830
+ /** First Row in layout */
1831
+ FirstRow,
1832
+ /** First Column in layout */
1833
+ FirstColumn,
1834
+ /** Finds a location if layout is empty. The found location will be the root ContentItem. */
1835
+ Empty,
1836
+ /** Finds root if layout is empty, otherwise a child under root */
1837
+ Root,
1838
+ }
1839
+ }
1840
+
1841
+ /**
1842
+ * Default LocationSelectors array used if none is specified. Will always find a location.
1843
+ * @public
1844
+ */
1845
+ export const defaultLocationSelectors: readonly LocationSelector[] = [
1846
+ { typeId: LocationSelector.TypeId.FocusedStack, index: undefined },
1847
+ { typeId: LocationSelector.TypeId.FirstStack, index: undefined },
1848
+ { typeId: LocationSelector.TypeId.FirstRowOrColumn, index: undefined },
1849
+ { typeId: LocationSelector.TypeId.Root, index: undefined },
1850
+ ];
1851
+
1852
+ /**
1853
+ * LocationSelectors to try to get location next to existing focused item
1854
+ * @public
1855
+ */
1856
+ export const afterFocusedItemIfPossibleLocationSelectors: readonly LocationSelector[] = [
1857
+ { typeId: LocationSelector.TypeId.FocusedItem, index: 1 },
1858
+ { typeId: LocationSelector.TypeId.FirstStack, index: undefined },
1859
+ { typeId: LocationSelector.TypeId.FirstRowOrColumn, index: undefined },
1860
+ { typeId: LocationSelector.TypeId.Root, index: undefined },
1861
+ ];
1862
+ }