@genesislcap/foundation-layout 14.393.2 → 14.393.3-FUI-2471.2

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 (32) hide show
  1. package/dist/custom-elements.json +318 -24
  2. package/dist/dts/index.d.ts +1 -1
  3. package/dist/dts/index.d.ts.map +1 -1
  4. package/dist/dts/main/layout-main.d.ts +12 -5
  5. package/dist/dts/main/layout-main.d.ts.map +1 -1
  6. package/dist/dts/main/layout-popout-controller.d.ts +40 -0
  7. package/dist/dts/main/layout-popout-controller.d.ts.map +1 -0
  8. package/dist/dts/utils/constants.d.ts +7 -1
  9. package/dist/dts/utils/constants.d.ts.map +1 -1
  10. package/dist/dts/utils/index.d.ts +1 -0
  11. package/dist/dts/utils/index.d.ts.map +1 -1
  12. package/dist/dts/utils/popout-events.d.ts +37 -0
  13. package/dist/dts/utils/popout-events.d.ts.map +1 -0
  14. package/dist/dts/utils/types.d.ts +28 -0
  15. package/dist/dts/utils/types.d.ts.map +1 -1
  16. package/dist/esm/index.js +1 -1
  17. package/dist/esm/main/layout-main.js +47 -21
  18. package/dist/esm/main/layout-popout-controller.js +243 -0
  19. package/dist/esm/utils/constants.js +7 -1
  20. package/dist/esm/utils/index.js +1 -0
  21. package/dist/esm/utils/popout-events.js +5 -0
  22. package/dist/foundation-layout.api.json +94 -10
  23. package/dist/foundation-layout.d.ts +96 -4
  24. package/docs/api/foundation-layout.foundationlayout.md +3 -3
  25. package/docs/api/foundation-layout.foundationlayout.popoutconfig.md +2 -2
  26. package/docs/api/foundation-layout.foundationlayout.tryactivatepopoutmode.md +38 -1
  27. package/docs/api/foundation-layout.layout_popout_control_key.md +16 -0
  28. package/docs/api/foundation-layout.layoutpopoutconfig.md +22 -0
  29. package/docs/api/foundation-layout.md +22 -0
  30. package/docs/api/foundation-layout.serialisedlayout.md +3 -0
  31. package/docs/api-report.md.api.md +29 -2
  32. package/package.json +15 -13
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/utils/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/utils/types.ts"],"names":[],"mappings":"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"}
package/dist/esm/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from './main';
2
2
  export { LAYOUT_ICONS } from './styles';
3
- export { DEFAULT_RELOAD_BUFFER, LayoutEmitEvents, LayoutReceiveEvents, LayoutRegistrationError, LayoutUsageError, LAYOUT_POPOUT_CONTAINER_CLASS, } from './utils';
3
+ export { DEFAULT_RELOAD_BUFFER, LayoutEmitEvents, LayoutReceiveEvents, LayoutRegistrationError, LayoutUsageError, LAYOUT_POPOUT_CONTAINER_CLASS, LAYOUT_POPOUT_CONTROL_KEY, } from './utils';
4
4
  export { registerFactory, getFactory, unregisterFactory } from './utils/factory-registry';
@@ -11,7 +11,9 @@ import { AUTOSAVE_KEY, componentType, DEFAULT_RELOAD_BUFFER, getMissingArrayItem
11
11
  import { LayoutRegistrationError, LayoutUsageError } from '../utils/error';
12
12
  import { getFactory } from '../utils/factory-registry';
13
13
  import { logger } from '../utils/logger';
14
+ import { FoundationLayoutPopoutController } from './layout-popout-controller';
14
15
  export { layoutStyles } from '../styles';
16
+ FoundationLayoutPopoutController;
15
17
  /*
16
18
  * We only want to apply the global stylesheet required for golden layout
17
19
  * to work once, and only apply it if we actually use an instance of the
@@ -101,13 +103,14 @@ export class FoundationLayout extends FoundationElement {
101
103
  * has changed and know you potentially need to gate some of your lifecycle functionality.
102
104
  */
103
105
  this.lifecycleUpdateToken = undefined;
106
+ /** @internal */
107
+ this.popupMode = false;
104
108
  /**
105
109
  * Controls whether popout functionality is enabled on the layout. Defaults to disabled.
106
- * Set this attribute to any string to enable popout functionality. If this string is of format `number;number` then this will be interpreted as the width and height of the popout window.
110
+ * Pass an empty object `{}` to use default configurations, or a {@link LayoutPopoutConfig} object to further customise.
107
111
  * @beta
108
112
  */
109
113
  this.popoutConfig = undefined;
110
- this.popupMode = false;
111
114
  /** @internal */
112
115
  _FoundationLayout__boundDragListener.set(this, undefined);
113
116
  /** @internal */
@@ -256,6 +259,7 @@ export class FoundationLayout extends FoundationElement {
256
259
  * @returns - latest version of {@link SerialisedLayout} describing the layout
257
260
  */
258
261
  getLayout() {
262
+ var _b, _c;
259
263
  const componentCollection = this.getLayoutComponents();
260
264
  componentCollection.forEach((items) => {
261
265
  if (!items.length)
@@ -276,7 +280,11 @@ export class FoundationLayout extends FoundationElement {
276
280
  orderedStates,
277
281
  });
278
282
  });
279
- return { v: '1', c: ResolvedLayoutConfig.minifyConfig(this.layout.saveLayout()) };
283
+ return {
284
+ v: '1',
285
+ c: ResolvedLayoutConfig.minifyConfig(this.layout.saveLayout()),
286
+ popouts: (_c = (_b = this.popoutController) === null || _b === void 0 ? void 0 : _b.getRegistry()) !== null && _c !== void 0 ? _c : {},
287
+ };
280
288
  }
281
289
  /**
282
290
  * If in a popout window from the dynamic layout, this function will run the flow to put the component in popout mode.
@@ -287,13 +295,19 @@ export class FoundationLayout extends FoundationElement {
287
295
  * If you set the `LAYOUT_POPOUT_CONTAINER_CLASS` on an element which is a DOM parent of the layout,
288
296
  * then if the layout goes into popout mode then it will place itself as the only child for the popout container you set.
289
297
  * It is likely you'll want to attach this class to your design system provider.
298
+ *
299
+ * @param autoloading - If true, this respects the {@link LayoutPopoutConfig.loadAutomatically} property.
300
+ * You might want to set this to false and call it manually if you're manually registering components to the layout in order to avoid race conditions.
290
301
  * @beta
291
302
  */
292
- tryActivatePopoutMode() {
303
+ tryActivatePopoutMode(autoloading = false) {
304
+ var _b;
293
305
  if (!this.popupMode)
294
306
  return false;
307
+ if (autoloading && ((_b = this.popoutConfig) === null || _b === void 0 ? void 0 : _b.loadAutomatically) === false)
308
+ return;
295
309
  const popoutComponentRegistration = new URLSearchParams(window.location.search).get(LAYOUT_POPOUT_CONTROL_KEY);
296
- const popoutConfig = {
310
+ const glPopoutConfig = {
297
311
  root: {
298
312
  type: 'component',
299
313
  componentType: popoutComponentRegistration,
@@ -303,7 +317,7 @@ export class FoundationLayout extends FoundationElement {
303
317
  hasHeaders: false,
304
318
  },
305
319
  };
306
- this.loadGLConfigAndSetup(popoutConfig);
320
+ this.loadGLConfigAndSetup(glPopoutConfig);
307
321
  const tryFindNewLayoutDOMLocation = (e) => {
308
322
  if (e.classList.contains(LAYOUT_POPOUT_CONTAINER_CLASS))
309
323
  return e;
@@ -397,6 +411,7 @@ export class FoundationLayout extends FoundationElement {
397
411
  * @throws various errors if the layout string is malformed and cannot be parsed
398
412
  */
399
413
  loadLayout(layout, handleMissingItem = 'error', disableCache = false) {
414
+ var _b;
400
415
  const alreadyRegistered = this.registeredItems();
401
416
  const wantedRegistered = FoundationLayout.layoutRequiredRegistrations(layout);
402
417
  const missingRegisteredItems = getMissingArrayItems(wantedRegistered, alreadyRegistered);
@@ -408,6 +423,9 @@ export class FoundationLayout extends FoundationElement {
408
423
  const layoutConfig = LayoutConfig.fromResolved(ResolvedLayoutConfig.unminifyConfig(layout.c));
409
424
  if (disableCache)
410
425
  this.removeConfigCacheInformation(layoutConfig);
426
+ if (!this.popupMode && this.popoutController) {
427
+ this.popoutController.restorePopouts((_b = layout.popouts) !== null && _b !== void 0 ? _b : {}, this.uuid);
428
+ }
411
429
  if (missingRegisteredItems.length !== 0 && handleMissingItem === 'placeholder') {
412
430
  this.registerPlaceholdersAndSetClosable(layoutConfig, missingRegisteredItems);
413
431
  }
@@ -644,7 +662,7 @@ export class FoundationLayout extends FoundationElement {
644
662
  this.hasFirstLoaded = true;
645
663
  // Store the default layout config before attempting to load from storage
646
664
  this.defaultLayoutConfig = JSON.parse(JSON.stringify(this.layoutConfig));
647
- const res = this.tryLoadLayoutFromLocalStorage() || this.tryActivatePopoutMode();
665
+ const res = this.tryLoadLayoutFromLocalStorage() || this.tryActivatePopoutMode(true);
648
666
  if (!res) {
649
667
  this.loadGLConfigAndSetup(this.layoutConfig);
650
668
  }
@@ -973,23 +991,22 @@ export class FoundationLayout extends FoundationElement {
973
991
  if (!this.popoutConfig)
974
992
  return;
975
993
  logger.warn('Layout pop-out mode is enabled, this is an experimental feature and may change in future versions');
976
- const popoutWindowPixelConfig = (popoutConfig) => {
977
- const matcher = /(\d+);(\d+)/;
978
- const mMatches = popoutConfig.match(matcher);
979
- if (!mMatches)
980
- return '';
981
- const [, width, height] = mMatches;
982
- return `,width=${width},height=${height}`;
983
- };
994
+ const configWithDefaults = Object.assign(Object.assign({}, this.popoutConfig), { loadAutomatically: true, popoutDimension: Object.assign(Object.assign({}, this.popoutConfig.popoutDimension), { width: 960, height: 720 }) });
984
995
  this.customButtons.push({
985
996
  svg: LAYOUT_ICONS.popoutSVG,
986
997
  onClick: (_, elem) => {
998
+ var _b;
987
999
  const registration = elem.firstChild[instanceContainer].registration;
988
- const url = new URL(window.location.toString());
989
- const itemParams = new URLSearchParams();
990
- itemParams.append(LAYOUT_POPOUT_CONTROL_KEY, registration);
991
- url.search = itemParams.toString();
992
- window.open(url, undefined, 'popup' + popoutWindowPixelConfig(this.popoutConfig));
1000
+ // Use default dimensions from config
1001
+ const defaultGeometry = {
1002
+ screenX: undefined,
1003
+ screenY: undefined,
1004
+ outerWidth: configWithDefaults.popoutDimension.width,
1005
+ outerHeight: configWithDefaults.popoutDimension.height,
1006
+ };
1007
+ // Add to controller registry immediately so it tracks it
1008
+ (_b = this.popoutController) === null || _b === void 0 ? void 0 : _b.openPopout(registration, defaultGeometry, this.uuid);
1009
+ this.cacheAndSaveLayout();
993
1010
  },
994
1011
  });
995
1012
  this.popupMode = window.location.search.includes(LAYOUT_POPOUT_CONTROL_KEY);
@@ -1021,7 +1038,10 @@ __decorate([
1021
1038
  observable
1022
1039
  ], FoundationLayout.prototype, "dragging", void 0);
1023
1040
  __decorate([
1024
- attr({ attribute: 'popout-config' })
1041
+ observable
1042
+ ], FoundationLayout.prototype, "popupMode", void 0);
1043
+ __decorate([
1044
+ observable
1025
1045
  ], FoundationLayout.prototype, "popoutConfig", void 0);
1026
1046
  __decorate([
1027
1047
  observable
@@ -1037,6 +1057,12 @@ const loadingTemplate = html `
1037
1057
  */
1038
1058
  export const layoutTemplate = html `
1039
1059
  <template>
1060
+ ${when((x) => Boolean(x.popoutConfig), html `
1061
+ <foundation-layout-popout-controller
1062
+ ${ref('popoutController')}
1063
+ channel-name="${(x) => { var _b; return (_b = x.popoutConfig) === null || _b === void 0 ? void 0 : _b.channelName; }}"
1064
+ ></foundation-layout-popout-controller>
1065
+ `)}
1040
1066
  ${when((x) => !x.hasFirstLoaded && x.usingDeclerativeAPI, loadingTemplate)}
1041
1067
  <div class="layout-container" ${ref('layoutElement')}></div>
1042
1068
  </template>
@@ -0,0 +1,243 @@
1
+ import { __awaiter, __decorate } from "tslib";
2
+ import { createTypedBroadcastChannel, } from '@genesislcap/foundation-broadcast-channel';
3
+ import { attr, customElement, FASTElement } from '@microsoft/fast-element';
4
+ import { LAYOUT_POPOUT_CONTROL_KEY, LayoutReceiveEvents, POPOUT_GEOMETRY_BROADCAST_INTERVAL, } from '../utils';
5
+ import { LAYOUT_POPOUT_CHANNEL_NAME } from '../utils/popout-events';
6
+ let FoundationLayoutPopoutController = class FoundationLayoutPopoutController extends FASTElement {
7
+ constructor() {
8
+ super(...arguments);
9
+ // Internal registry to track both geometry and active window references
10
+ this.popoutRegistry = new Map();
11
+ }
12
+ connectedCallback() {
13
+ super.connectedCallback();
14
+ this.layoutKey = new URLSearchParams(window.location.search).get(LAYOUT_POPOUT_CONTROL_KEY);
15
+ this.setupChannel();
16
+ this.setupUnloadListener();
17
+ if (this.layoutKey) {
18
+ this.startPolling();
19
+ }
20
+ else {
21
+ this.setupListener();
22
+ }
23
+ }
24
+ disconnectedCallback() {
25
+ super.disconnectedCallback();
26
+ if (this.channel) {
27
+ this.channel.close();
28
+ }
29
+ this.stopPolling();
30
+ this.teardownUnloadListener();
31
+ if (!this.layoutKey) {
32
+ this.closeAllPopouts();
33
+ }
34
+ }
35
+ setupChannel() {
36
+ const name = this.channelName || LAYOUT_POPOUT_CHANNEL_NAME;
37
+ this.channel = createTypedBroadcastChannel(name);
38
+ }
39
+ setupListener() {
40
+ if (!this.channel)
41
+ return;
42
+ this.channel.onmessage = (ev) => {
43
+ if (this.channel.isMessageType('popout-geometry-change', ev)) {
44
+ const { layoutKey, geometry } = ev.data
45
+ .detail;
46
+ const current = this.popoutRegistry.get(layoutKey);
47
+ this.popoutRegistry.set(layoutKey, Object.assign(Object.assign({}, current), { geometry }));
48
+ this.emitAutosave();
49
+ }
50
+ else if (this.channel.isMessageType('popout-closed', ev)) {
51
+ const { layoutKey } = ev.data.detail;
52
+ this.popoutRegistry.delete(layoutKey);
53
+ this.emitAutosave();
54
+ }
55
+ };
56
+ }
57
+ setupUnloadListener() {
58
+ this.unloadHandler = () => {
59
+ if (this.layoutKey) {
60
+ if (this.channel) {
61
+ this.channel.postMessage('popout-closed', { layoutKey: this.layoutKey });
62
+ }
63
+ }
64
+ else {
65
+ this.closeAllPopouts();
66
+ }
67
+ };
68
+ window.addEventListener('beforeunload', this.unloadHandler);
69
+ }
70
+ teardownUnloadListener() {
71
+ if (this.unloadHandler) {
72
+ window.removeEventListener('beforeunload', this.unloadHandler);
73
+ this.unloadHandler = null;
74
+ }
75
+ }
76
+ emitAutosave() {
77
+ this.$emit(LayoutReceiveEvents.autosave, undefined, {
78
+ bubbles: true,
79
+ composed: true,
80
+ });
81
+ }
82
+ startPolling() {
83
+ this.pollInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
84
+ const currentGeo = yield this.getGeometry();
85
+ const geoString = JSON.stringify(currentGeo);
86
+ if (geoString !== this.lastGeometry) {
87
+ this.broadcastGeometry(currentGeo);
88
+ this.lastGeometry = geoString;
89
+ }
90
+ }), POPOUT_GEOMETRY_BROADCAST_INTERVAL);
91
+ }
92
+ stopPolling() {
93
+ if (this.pollInterval) {
94
+ clearInterval(this.pollInterval);
95
+ }
96
+ }
97
+ getGeometry() {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ const screenId = yield (() => __awaiter(this, void 0, void 0, function* () {
100
+ var _a;
101
+ if ('getScreenDetails' in window) {
102
+ try {
103
+ // This may prompt the user for permission if not already granted
104
+ const details = yield window.getScreenDetails();
105
+ return (_a = details.currentScreen) === null || _a === void 0 ? void 0 : _a.label;
106
+ }
107
+ catch (e) {
108
+ // Silent fail - permission denied or API error
109
+ return undefined;
110
+ }
111
+ }
112
+ return undefined;
113
+ }))();
114
+ return {
115
+ screenX: window.screenX,
116
+ screenY: window.screenY,
117
+ outerWidth: window.outerWidth,
118
+ outerHeight: window.outerHeight,
119
+ screenId,
120
+ };
121
+ });
122
+ }
123
+ broadcastGeometry(geometry) {
124
+ if (!this.channel || !this.layoutKey)
125
+ return;
126
+ this.channel.postMessage('popout-geometry-change', {
127
+ layoutKey: this.layoutKey,
128
+ geometry,
129
+ });
130
+ console.log('[PopoutController] Broadcasting geometry:', geometry);
131
+ }
132
+ /**
133
+ * Opens a component in a popout window.
134
+ * @param registration - The registration ID of the component to pop out.
135
+ * @param geometry - Optional geometry to use for the new window.
136
+ * @param uuid - UUID generator from parent layout
137
+ */
138
+ openPopout(registration, geometry, uuid) {
139
+ return __awaiter(this, void 0, void 0, function* () {
140
+ const url = new URL(window.location.toString());
141
+ const itemParams = new URLSearchParams();
142
+ itemParams.append(LAYOUT_POPOUT_CONTROL_KEY, registration);
143
+ url.search = itemParams.toString();
144
+ let features = 'popup';
145
+ let targetLeft = geometry === null || geometry === void 0 ? void 0 : geometry.screenX;
146
+ let targetTop = geometry === null || geometry === void 0 ? void 0 : geometry.screenY;
147
+ // If we have a screenId, try to find the screen and ensure coordinates are valid
148
+ if ((geometry === null || geometry === void 0 ? void 0 : geometry.screenId) && 'getScreenDetails' in window) {
149
+ try {
150
+ const details = yield window.getScreenDetails();
151
+ const targetScreen = details.screens.find((s) => s.label === geometry.screenId);
152
+ if (targetScreen) {
153
+ const isWithin = targetLeft >= targetScreen.availLeft &&
154
+ targetLeft < targetScreen.availLeft + targetScreen.availWidth &&
155
+ targetTop >= targetScreen.availTop &&
156
+ targetTop < targetScreen.availTop + targetScreen.availHeight;
157
+ if (!isWithin) {
158
+ targetLeft = targetScreen.availLeft;
159
+ targetTop = targetScreen.availTop;
160
+ }
161
+ }
162
+ else {
163
+ targetLeft = undefined;
164
+ targetTop = undefined;
165
+ }
166
+ }
167
+ catch (e) {
168
+ // Fallback
169
+ }
170
+ }
171
+ if (geometry) {
172
+ features += `,width=${geometry.outerWidth},height=${geometry.outerHeight}`;
173
+ if (targetLeft !== undefined)
174
+ features += `,left=${targetLeft}`;
175
+ if (targetTop !== undefined)
176
+ features += `,top=${targetTop}`;
177
+ }
178
+ else {
179
+ features += ',width=800,height=600'; // Default fallback
180
+ }
181
+ const win = window.open(url, `popout-${registration}-${(uuid === null || uuid === void 0 ? void 0 : uuid.createId()) || Date.now()}`, features);
182
+ if (!win) {
183
+ console.warn(`Popout window for ${registration} was blocked.`);
184
+ return null;
185
+ }
186
+ // Update registry with the window reference and geometry (if provided)
187
+ const current = this.popoutRegistry.get(registration);
188
+ this.popoutRegistry.set(registration, {
189
+ geometry: geometry || (current === null || current === void 0 ? void 0 : current.geometry),
190
+ window: win,
191
+ });
192
+ return win;
193
+ });
194
+ }
195
+ getRegistry() {
196
+ const output = {};
197
+ for (const [key, val] of this.popoutRegistry.entries()) {
198
+ if (val.geometry) {
199
+ output[key] = { geometry: val.geometry };
200
+ }
201
+ }
202
+ return output;
203
+ }
204
+ /**
205
+ * Restores popouts from a registry
206
+ */
207
+ restorePopouts(registry, uuid) {
208
+ return __awaiter(this, void 0, void 0, function* () {
209
+ this.closeAllPopouts();
210
+ // Populate the registry with serialized data
211
+ for (const [key, val] of Object.entries(registry)) {
212
+ this.popoutRegistry.set(key, { geometry: val.geometry });
213
+ }
214
+ const keys = Array.from(this.popoutRegistry.keys());
215
+ if (keys.length === 0)
216
+ return;
217
+ console.debug(`Restoring ${keys.length} popout windows`);
218
+ yield Promise.all(keys.map((key) => {
219
+ const state = this.popoutRegistry.get(key);
220
+ return this.openPopout(key, state.geometry, uuid);
221
+ }));
222
+ });
223
+ }
224
+ closeAllPopouts() {
225
+ for (const val of this.popoutRegistry.values()) {
226
+ if (val.window && !val.window.closed) {
227
+ val.window.close();
228
+ }
229
+ }
230
+ this.popoutRegistry.clear();
231
+ }
232
+ };
233
+ __decorate([
234
+ attr({ attribute: 'channel-name' })
235
+ ], FoundationLayoutPopoutController.prototype, "channelName", void 0);
236
+ FoundationLayoutPopoutController = __decorate([
237
+ customElement({
238
+ name: 'foundation-layout-popout-controller',
239
+ template: null,
240
+ styles: null,
241
+ })
242
+ ], FoundationLayoutPopoutController);
243
+ export { FoundationLayoutPopoutController };
@@ -25,9 +25,15 @@ export const DEFAULT_RELOAD_BUFFER = 500;
25
25
  export const AUTOSAVE_KEY = 'foundation-layout-autosave';
26
26
  /**
27
27
  * Key to be used for controlling popout behaviour
28
- * @internal
28
+ * @beta
29
29
  */
30
30
  export const LAYOUT_POPOUT_CONTROL_KEY = 'f-layout-key';
31
+ /**
32
+ * How often a popout window should check to broadcast its
33
+ * geometry
34
+ * @interal
35
+ */
36
+ export const POPOUT_GEOMETRY_BROADCAST_INTERVAL = 1000;
31
37
  /**
32
38
  * Put this classname on an element which is a DOM parent of the layout, and
33
39
  * if the layout goes into popout mode then it will place itself as the only child
@@ -4,5 +4,6 @@ export * from './events';
4
4
  export * from './factory-registry';
5
5
  export * from './misc';
6
6
  export * from './templates';
7
+ export * from './popout-events';
7
8
  export * from './types';
8
9
  export * from './error';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Name of the default broadcast channel used for layout popouts
3
+ * @public
4
+ */
5
+ export const LAYOUT_POPOUT_CHANNEL_NAME = 'f-layout-popout-channel';
@@ -845,15 +845,20 @@
845
845
  {
846
846
  "kind": "Property",
847
847
  "canonicalReference": "@genesislcap/foundation-layout!FoundationLayout#popoutConfig:member",
848
- "docComment": "/**\n * Controls whether popout functionality is enabled on the layout. Defaults to disabled. Set this attribute to any string to enable popout functionality. If this string is of format `number;number` then this will be interpreted as the width and height of the popout window.\n *\n * @beta\n */\n",
848
+ "docComment": "/**\n * Controls whether popout functionality is enabled on the layout. Defaults to disabled. Pass an empty object `{}` to use default configurations, or a {@link LayoutPopoutConfig} object to further customise.\n *\n * @beta\n */\n",
849
849
  "excerptTokens": [
850
850
  {
851
851
  "kind": "Content",
852
852
  "text": "popoutConfig: "
853
853
  },
854
+ {
855
+ "kind": "Reference",
856
+ "text": "LayoutPopoutConfig",
857
+ "canonicalReference": "@genesislcap/foundation-layout!LayoutPopoutConfig:type"
858
+ },
854
859
  {
855
860
  "kind": "Content",
856
- "text": "string | undefined"
861
+ "text": " | undefined"
857
862
  },
858
863
  {
859
864
  "kind": "Content",
@@ -866,7 +871,7 @@
866
871
  "name": "popoutConfig",
867
872
  "propertyTypeTokenRange": {
868
873
  "startIndex": 1,
869
- "endIndex": 2
874
+ "endIndex": 3
870
875
  },
871
876
  "isStatic": false,
872
877
  "isProtected": false,
@@ -1074,11 +1079,19 @@
1074
1079
  {
1075
1080
  "kind": "Method",
1076
1081
  "canonicalReference": "@genesislcap/foundation-layout!FoundationLayout#tryActivatePopoutMode:member(1)",
1077
- "docComment": "/**\n * If in a popout window from the dynamic layout, this function will run the flow to put the component in popout mode. This function is automatically called if using the declarative HTML API, but if only using the JavaScript API then you will need to call this function manually.\n *\n * @remarks\n *\n * If you need to call this manually then you should do it as soon as you register all of the required components with {@link FoundationLayout.registerItem}.\n *\n * If you set the `LAYOUT_POPOUT_CONTAINER_CLASS` on an element which is a DOM parent of the layout, then if the layout goes into popout mode then it will place itself as the only child for the popout container you set. It is likely you'll want to attach this class to your design system provider.\n *\n * @beta\n */\n",
1082
+ "docComment": "/**\n * If in a popout window from the dynamic layout, this function will run the flow to put the component in popout mode. This function is automatically called if using the declarative HTML API, but if only using the JavaScript API then you will need to call this function manually.\n *\n * @remarks\n *\n * If you need to call this manually then you should do it as soon as you register all of the required components with {@link FoundationLayout.registerItem}.\n *\n * If you set the `LAYOUT_POPOUT_CONTAINER_CLASS` on an element which is a DOM parent of the layout, then if the layout goes into popout mode then it will place itself as the only child for the popout container you set. It is likely you'll want to attach this class to your design system provider.\n *\n * @param autoloading - If true, this respects the {@link LayoutPopoutConfig.loadAutomatically} property. You might want to set this to false and call it manually if you're manually registering components to the layout in order to avoid race conditions.\n *\n * @beta\n */\n",
1078
1083
  "excerptTokens": [
1079
1084
  {
1080
1085
  "kind": "Content",
1081
- "text": "tryActivatePopoutMode(): "
1086
+ "text": "tryActivatePopoutMode(autoloading?: "
1087
+ },
1088
+ {
1089
+ "kind": "Content",
1090
+ "text": "boolean"
1091
+ },
1092
+ {
1093
+ "kind": "Content",
1094
+ "text": "): "
1082
1095
  },
1083
1096
  {
1084
1097
  "kind": "Content",
@@ -1091,13 +1104,22 @@
1091
1104
  ],
1092
1105
  "isStatic": false,
1093
1106
  "returnTypeTokenRange": {
1094
- "startIndex": 1,
1095
- "endIndex": 2
1107
+ "startIndex": 3,
1108
+ "endIndex": 4
1096
1109
  },
1097
1110
  "releaseTag": "Beta",
1098
1111
  "isProtected": false,
1099
1112
  "overloadIndex": 1,
1100
- "parameters": [],
1113
+ "parameters": [
1114
+ {
1115
+ "parameterName": "autoloading",
1116
+ "parameterTypeTokenRange": {
1117
+ "startIndex": 1,
1118
+ "endIndex": 2
1119
+ },
1120
+ "isOptional": true
1121
+ }
1122
+ ],
1101
1123
  "isOptional": false,
1102
1124
  "isAbstract": false,
1103
1125
  "name": "tryActivatePopoutMode"
@@ -1721,6 +1743,33 @@
1721
1743
  "endIndex": 0
1722
1744
  }
1723
1745
  },
1746
+ {
1747
+ "kind": "Variable",
1748
+ "canonicalReference": "@genesislcap/foundation-layout!LAYOUT_POPOUT_CONTROL_KEY:var",
1749
+ "docComment": "/**\n * Key to be used for controlling popout behaviour\n *\n * @beta\n */\n",
1750
+ "excerptTokens": [
1751
+ {
1752
+ "kind": "Content",
1753
+ "text": "LAYOUT_POPOUT_CONTROL_KEY = "
1754
+ },
1755
+ {
1756
+ "kind": "Content",
1757
+ "text": "\"f-layout-key\""
1758
+ }
1759
+ ],
1760
+ "fileUrlPath": "src/utils/constants.ts",
1761
+ "initializerTokenRange": {
1762
+ "startIndex": 1,
1763
+ "endIndex": 2
1764
+ },
1765
+ "isReadonly": true,
1766
+ "releaseTag": "Beta",
1767
+ "name": "LAYOUT_POPOUT_CONTROL_KEY",
1768
+ "variableTypeTokenRange": {
1769
+ "startIndex": 0,
1770
+ "endIndex": 0
1771
+ }
1772
+ },
1724
1773
  {
1725
1774
  "kind": "Interface",
1726
1775
  "canonicalReference": "@genesislcap/foundation-layout!LayoutComponentWithState:interface",
@@ -1848,6 +1897,32 @@
1848
1897
  "endIndex": 2
1849
1898
  }
1850
1899
  },
1900
+ {
1901
+ "kind": "TypeAlias",
1902
+ "canonicalReference": "@genesislcap/foundation-layout!LayoutPopoutConfig:type",
1903
+ "docComment": "/**\n * @beta\n */\n",
1904
+ "excerptTokens": [
1905
+ {
1906
+ "kind": "Content",
1907
+ "text": "export type LayoutPopoutConfig = "
1908
+ },
1909
+ {
1910
+ "kind": "Content",
1911
+ "text": "{\n channelName?: string;\n loadAutomatically?: boolean;\n popoutDimension?: {\n width: number;\n height: number;\n };\n}"
1912
+ },
1913
+ {
1914
+ "kind": "Content",
1915
+ "text": ";"
1916
+ }
1917
+ ],
1918
+ "fileUrlPath": "src/utils/types.ts",
1919
+ "releaseTag": "Beta",
1920
+ "name": "LayoutPopoutConfig",
1921
+ "typeTokenRange": {
1922
+ "startIndex": 1,
1923
+ "endIndex": 2
1924
+ }
1925
+ },
1851
1926
  {
1852
1927
  "kind": "Variable",
1853
1928
  "canonicalReference": "@genesislcap/foundation-layout!LayoutReceiveEvents:var",
@@ -2399,7 +2474,16 @@
2399
2474
  },
2400
2475
  {
2401
2476
  "kind": "Content",
2402
- "text": ";\n}"
2477
+ "text": ";\n popouts?: {\n [layoutKey: string]: "
2478
+ },
2479
+ {
2480
+ "kind": "Reference",
2481
+ "text": "PopoutState",
2482
+ "canonicalReference": "@genesislcap/foundation-layout!~PopoutState:type"
2483
+ },
2484
+ {
2485
+ "kind": "Content",
2486
+ "text": ";\n };\n}"
2403
2487
  },
2404
2488
  {
2405
2489
  "kind": "Content",
@@ -2411,7 +2495,7 @@
2411
2495
  "name": "SerialisedLayout",
2412
2496
  "typeTokenRange": {
2413
2497
  "startIndex": 1,
2414
- "endIndex": 4
2498
+ "endIndex": 6
2415
2499
  }
2416
2500
  },
2417
2501
  {