@genesislcap/foundation-layout 14.395.0 → 14.396.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 (32) hide show
  1. package/POPOUT_TODO.md +46 -0
  2. package/dist/custom-elements.json +351 -24
  3. package/dist/dts/index.d.ts +1 -1
  4. package/dist/dts/index.d.ts.map +1 -1
  5. package/dist/dts/main/layout-main.d.ts +9 -4
  6. package/dist/dts/main/layout-main.d.ts.map +1 -1
  7. package/dist/dts/main/layout-popout-controller.d.ts +49 -0
  8. package/dist/dts/main/layout-popout-controller.d.ts.map +1 -0
  9. package/dist/dts/utils/constants.d.ts +7 -1
  10. package/dist/dts/utils/constants.d.ts.map +1 -1
  11. package/dist/dts/utils/index.d.ts +1 -0
  12. package/dist/dts/utils/index.d.ts.map +1 -1
  13. package/dist/dts/utils/popout-events.d.ts +43 -0
  14. package/dist/dts/utils/popout-events.d.ts.map +1 -0
  15. package/dist/dts/utils/types.d.ts +39 -0
  16. package/dist/dts/utils/types.d.ts.map +1 -1
  17. package/dist/esm/index.js +1 -1
  18. package/dist/esm/main/layout-main.js +48 -21
  19. package/dist/esm/main/layout-popout-controller.js +289 -0
  20. package/dist/esm/utils/constants.js +7 -1
  21. package/dist/esm/utils/index.js +1 -0
  22. package/dist/esm/utils/popout-events.js +5 -0
  23. package/dist/foundation-layout.api.json +72 -5
  24. package/dist/foundation-layout.d.ts +103 -3
  25. package/docs/api/foundation-layout.foundationlayout.md +2 -2
  26. package/docs/api/foundation-layout.foundationlayout.popoutconfig.md +2 -2
  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 +28 -1
  32. package/package.json +15 -13
@@ -0,0 +1,43 @@
1
+ import { EventDetailMap } from '@genesislcap/foundation-events';
2
+ import { PopoutGeometry } from './types';
3
+ /**
4
+ * Events for layout popout communication
5
+ * @public
6
+ */
7
+ export interface LayoutPopoutEvents extends EventDetailMap {
8
+ /**
9
+ * Emitted by a popout window when its geometry or component state changes
10
+ */
11
+ 'popout-update': {
12
+ layoutKey: string;
13
+ geometry: PopoutGeometry;
14
+ state?: unknown;
15
+ };
16
+ /**
17
+ * Emitted by a popout window when it is closed
18
+ */
19
+ 'popout-closed': {
20
+ layoutKey: string;
21
+ };
22
+ /**
23
+ * Emitted by a popout window when it opens to request state
24
+ */
25
+ 'popout-handshake': {
26
+ layoutKey: string;
27
+ };
28
+ /**
29
+ * Emitted by the host to sync state to a popout window
30
+ */
31
+ 'popout-sync': {
32
+ layoutKey: string;
33
+ geometry?: PopoutGeometry;
34
+ state?: unknown;
35
+ };
36
+ }
37
+ /**
38
+ * Name of the default broadcast channel used for layout popouts
39
+ * @public
40
+ */
41
+ export declare const LAYOUT_POPOUT_CHANNEL_NAME = "f-layout-popout-channel";
42
+ export { PopoutGeometry };
43
+ //# sourceMappingURL=popout-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popout-events.d.ts","sourceRoot":"","sources":["../../../src/utils/popout-events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC;;;GAGG;AACH,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IACxD;;OAEG;IACH,eAAe,EAAE;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,cAAc,CAAC;QACzB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC;IACF;;OAEG;IACH,eAAe,EAAE;QACf,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF;;OAEG;IACH,kBAAkB,EAAE;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF;;OAEG;IACH,aAAa,EAAE;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,cAAc,CAAC;QAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,CAAC;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,4BAA4B,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -1,5 +1,24 @@
1
1
  import { ComponentContainer, ResolvedLayoutConfig } from '@genesis-community/golden-layout';
2
2
  import { componentType, instanceContainer } from './constants';
3
+ /**
4
+ * Geometry details for a popout window
5
+ * @public
6
+ */
7
+ export type PopoutGeometry = {
8
+ screenX: number;
9
+ screenY: number;
10
+ outerWidth: number;
11
+ outerHeight: number;
12
+ screenId?: string;
13
+ };
14
+ /**
15
+ * @beta
16
+ * State of a popped out window
17
+ */
18
+ export type PopoutState = {
19
+ geometry: PopoutGeometry;
20
+ state?: unknown;
21
+ };
3
22
  /**
4
23
  * Definition of a custom button which will be added to all layout items.
5
24
  * @remarks
@@ -25,6 +44,13 @@ export type CustomButton = {
25
44
  export type SerialisedLayout = {
26
45
  v: '1';
27
46
  c: ResolvedLayoutConfig;
47
+ /**
48
+ * Optional registry of popped out components and their window geometry.
49
+ * @beta
50
+ */
51
+ popouts?: {
52
+ [layoutKey: string]: PopoutState;
53
+ };
28
54
  };
29
55
  /**
30
56
  * Interface to implement on an item which is a component of the layout and you wish to serialise state with. This is saved separately for each instance of the component, which allows you to restore multiple instances of the same component with different state.
@@ -200,4 +226,17 @@ export interface LayoutComponent {
200
226
  export type AutosavedLayouts = {
201
227
  [x: string]: string;
202
228
  };
229
+ /** @beta */
230
+ export type LayoutPopoutConfig = {
231
+ /**
232
+ * The name of the broadcast channel to use for communication between the main window and popout windows.
233
+ * Defaults to 'f-layout-popout-channel'.
234
+ */
235
+ channelName?: string;
236
+ loadAutomatically?: boolean;
237
+ popoutDimension?: {
238
+ width: number;
239
+ height: number;
240
+ };
241
+ };
203
242
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/utils/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC9D,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,CAAC,EAAE,GAAG,CAAC;IACP,CAAC,EAAE,oBAAoB,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAoDK;AACL,MAAM,WAAW,wBAAwB,CAAC,CAAC;IACzC;;OAEG;IACH,eAAe,IAAI,CAAC,CAAC;IACrB;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;CACnC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC1C,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,gBAAgB,CAAC;QACvB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH,CAAC;AAEF,gBAAgB;AAChB,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,kBAAkB,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,gBAAgB;AAChB,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI,OAAO,GAC1C,OAAO,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,GAAG;IACrC,CAAC,iBAAiB,CAAC,CAAC,EAAE,iBAAiB,CAAC;CACzC,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,WAAW,KAAK,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;AAE/E,gBAAgB;AAChB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,GAAG,CACA;IACE,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,GACD;IACE,OAAO,EAAE,gBAAgB,CAAC;CAC3B,CACJ,CAAC;AAEF,gBAAgB;AAChB,eAAO,MAAM,iBAAiB,6CAA8C,CAAC;AAC7E;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAClE,gBAAgB;AAChB,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhE,gBAAgB;AAChB,MAAM,WAAW,eAAe;IAC9B,CAAC,aAAa,CAAC,EAAE,cAAc,CAAC;IAChC,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAC7D,mBAAmB,IAAI,IAAI,CAAC;CAC7B;AAED,gBAAgB;AAChB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACrB,CAAC"}
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,14 @@ 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
+ *
290
299
  * @beta
291
300
  */
292
301
  tryActivatePopoutMode() {
293
302
  if (!this.popupMode)
294
303
  return false;
295
304
  const popoutComponentRegistration = new URLSearchParams(window.location.search).get(LAYOUT_POPOUT_CONTROL_KEY);
296
- const popoutConfig = {
305
+ const glPopoutConfig = {
297
306
  root: {
298
307
  type: 'component',
299
308
  componentType: popoutComponentRegistration,
@@ -303,7 +312,7 @@ export class FoundationLayout extends FoundationElement {
303
312
  hasHeaders: false,
304
313
  },
305
314
  };
306
- this.loadGLConfigAndSetup(popoutConfig);
315
+ this.loadGLConfigAndSetup(glPopoutConfig);
307
316
  const tryFindNewLayoutDOMLocation = (e) => {
308
317
  if (e.classList.contains(LAYOUT_POPOUT_CONTAINER_CLASS))
309
318
  return e;
@@ -397,6 +406,7 @@ export class FoundationLayout extends FoundationElement {
397
406
  * @throws various errors if the layout string is malformed and cannot be parsed
398
407
  */
399
408
  loadLayout(layout, handleMissingItem = 'error', disableCache = false) {
409
+ var _b;
400
410
  const alreadyRegistered = this.registeredItems();
401
411
  const wantedRegistered = FoundationLayout.layoutRequiredRegistrations(layout);
402
412
  const missingRegisteredItems = getMissingArrayItems(wantedRegistered, alreadyRegistered);
@@ -408,6 +418,9 @@ export class FoundationLayout extends FoundationElement {
408
418
  const layoutConfig = LayoutConfig.fromResolved(ResolvedLayoutConfig.unminifyConfig(layout.c));
409
419
  if (disableCache)
410
420
  this.removeConfigCacheInformation(layoutConfig);
421
+ if (!this.popupMode && this.popoutController) {
422
+ this.popoutController.restorePopouts((_b = layout.popouts) !== null && _b !== void 0 ? _b : {}, this.uuid);
423
+ }
411
424
  if (missingRegisteredItems.length !== 0 && handleMissingItem === 'placeholder') {
412
425
  this.registerPlaceholdersAndSetClosable(layoutConfig, missingRegisteredItems);
413
426
  }
@@ -639,12 +652,14 @@ export class FoundationLayout extends FoundationElement {
639
652
  return;
640
653
  this.reloadPending = true;
641
654
  setTimeout(() => {
655
+ var _b;
642
656
  this.reloadPending = false;
643
657
  if (!this.hasFirstLoaded) {
644
658
  this.hasFirstLoaded = true;
645
659
  // Store the default layout config before attempting to load from storage
646
660
  this.defaultLayoutConfig = JSON.parse(JSON.stringify(this.layoutConfig));
647
- const res = this.tryLoadLayoutFromLocalStorage() || this.tryActivatePopoutMode();
661
+ const res = this.tryLoadLayoutFromLocalStorage() ||
662
+ (((_b = this.popoutConfig) === null || _b === void 0 ? void 0 : _b.loadAutomatically) !== false && this.tryActivatePopoutMode());
648
663
  if (!res) {
649
664
  this.loadGLConfigAndSetup(this.layoutConfig);
650
665
  }
@@ -973,23 +988,25 @@ export class FoundationLayout extends FoundationElement {
973
988
  if (!this.popoutConfig)
974
989
  return;
975
990
  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
- };
991
+ const configWithDefaults = Object.assign(Object.assign({}, this.popoutConfig), { loadAutomatically: true, popoutDimension: Object.assign(Object.assign({}, this.popoutConfig.popoutDimension), { width: 960, height: 720 }) });
984
992
  this.customButtons.push({
985
993
  svg: LAYOUT_ICONS.popoutSVG,
986
994
  onClick: (_, elem) => {
987
- 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));
995
+ var _b, _c;
996
+ const instance = elem.firstChild[instanceContainer];
997
+ const registration = instance.registration;
998
+ const component = elem.firstChild;
999
+ const state = (_b = component === null || component === void 0 ? void 0 : component.getCurrentState) === null || _b === void 0 ? void 0 : _b.call(component);
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
+ (_c = this.popoutController) === null || _c === void 0 ? void 0 : _c.openPopout(registration, { geometry: defaultGeometry, state }, 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,13 @@ 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
+ :parentLayout="${(x) => x}"
1064
+ channel-name="${(x) => { var _b; return (_b = x.popoutConfig) === null || _b === void 0 ? void 0 : _b.channelName; }}"
1065
+ ></foundation-layout-popout-controller>
1066
+ `)}
1040
1067
  ${when((x) => !x.hasFirstLoaded && x.usingDeclerativeAPI, loadingTemplate)}
1041
1068
  <div class="layout-container" ${ref('layoutElement')}></div>
1042
1069
  </template>
@@ -0,0 +1,289 @@
1
+ import { __awaiter, __decorate } from "tslib";
2
+ import { createTypedBroadcastChannel, } from '@genesislcap/foundation-broadcast-channel';
3
+ import { attr, customElement, FASTElement, observable } 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
+ this.channel.postMessage('popout-handshake', { layoutKey: this.layoutKey });
20
+ }
21
+ else {
22
+ this.setupListener();
23
+ }
24
+ }
25
+ disconnectedCallback() {
26
+ super.disconnectedCallback();
27
+ if (this.channel) {
28
+ this.channel.close();
29
+ }
30
+ this.stopPolling();
31
+ this.teardownUnloadListener();
32
+ if (!this.layoutKey) {
33
+ this.closeAllPopouts();
34
+ }
35
+ }
36
+ setupChannel() {
37
+ const name = this.channelName || LAYOUT_POPOUT_CHANNEL_NAME;
38
+ this.channel = createTypedBroadcastChannel(name);
39
+ if (this.layoutKey) {
40
+ this.channel.onmessage = (ev) => {
41
+ if (this.channel.isMessageType('popout-sync', ev)) {
42
+ const { layoutKey, state } = ev.data.detail;
43
+ if (layoutKey === this.layoutKey && state !== undefined) {
44
+ this.applyStateToParent(state);
45
+ }
46
+ }
47
+ };
48
+ }
49
+ }
50
+ setupListener() {
51
+ if (!this.channel)
52
+ return;
53
+ this.channel.onmessage = (ev) => {
54
+ if (this.channel.isMessageType('popout-update', ev)) {
55
+ const { layoutKey, geometry, state } = ev.data
56
+ .detail;
57
+ const current = this.popoutRegistry.get(layoutKey);
58
+ this.popoutRegistry.set(layoutKey, Object.assign(Object.assign({}, current), { geometry, state }));
59
+ this.emitAutosave();
60
+ }
61
+ else if (this.channel.isMessageType('popout-closed', ev)) {
62
+ const { layoutKey } = ev.data.detail;
63
+ this.popoutRegistry.delete(layoutKey);
64
+ this.emitAutosave();
65
+ }
66
+ else if (this.channel.isMessageType('popout-handshake', ev)) {
67
+ const { layoutKey } = ev.data.detail;
68
+ const current = this.popoutRegistry.get(layoutKey);
69
+ if (current) {
70
+ this.channel.postMessage('popout-sync', {
71
+ layoutKey,
72
+ geometry: current.geometry,
73
+ state: current.state,
74
+ });
75
+ }
76
+ }
77
+ };
78
+ }
79
+ applyStateToParent(state) {
80
+ if (!this.parentLayout)
81
+ return;
82
+ const components = this.parentLayout.getLayoutComponents();
83
+ for (const group of components) {
84
+ for (const item of group) {
85
+ if (item && typeof item.applyState === 'function') {
86
+ item.applyState(state);
87
+ }
88
+ }
89
+ }
90
+ }
91
+ setupUnloadListener() {
92
+ this.unloadHandler = () => {
93
+ if (this.layoutKey) {
94
+ if (this.channel) {
95
+ this.channel.postMessage('popout-closed', { layoutKey: this.layoutKey });
96
+ }
97
+ }
98
+ else {
99
+ this.closeAllPopouts();
100
+ }
101
+ };
102
+ window.addEventListener('beforeunload', this.unloadHandler);
103
+ }
104
+ teardownUnloadListener() {
105
+ if (this.unloadHandler) {
106
+ window.removeEventListener('beforeunload', this.unloadHandler);
107
+ this.unloadHandler = null;
108
+ }
109
+ }
110
+ emitAutosave() {
111
+ this.$emit(LayoutReceiveEvents.autosave, undefined, {
112
+ bubbles: true,
113
+ composed: true,
114
+ });
115
+ }
116
+ startPolling() {
117
+ this.pollInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
118
+ const geometry = yield this.getGeometry();
119
+ const state = this.getComponentState();
120
+ const update = { geometry, state };
121
+ const updateString = JSON.stringify(update);
122
+ if (updateString !== this.lastUpdate) {
123
+ this.broadcastUpdate(geometry, state);
124
+ this.lastUpdate = updateString;
125
+ }
126
+ }), POPOUT_GEOMETRY_BROADCAST_INTERVAL);
127
+ }
128
+ stopPolling() {
129
+ if (this.pollInterval) {
130
+ clearInterval(this.pollInterval);
131
+ }
132
+ }
133
+ getGeometry() {
134
+ return __awaiter(this, void 0, void 0, function* () {
135
+ const screenId = yield (() => __awaiter(this, void 0, void 0, function* () {
136
+ var _a;
137
+ if ('getScreenDetails' in window) {
138
+ try {
139
+ const details = yield window.getScreenDetails();
140
+ return (_a = details.currentScreen) === null || _a === void 0 ? void 0 : _a.label;
141
+ }
142
+ catch (e) {
143
+ return undefined;
144
+ }
145
+ }
146
+ return undefined;
147
+ }))();
148
+ return {
149
+ screenX: window.screenX,
150
+ screenY: window.screenY,
151
+ outerWidth: window.outerWidth,
152
+ outerHeight: window.outerHeight,
153
+ screenId,
154
+ };
155
+ });
156
+ }
157
+ getComponentState() {
158
+ if (!this.parentLayout)
159
+ return undefined;
160
+ const components = this.parentLayout.getLayoutComponents();
161
+ // In a popout window, we expect exactly one component
162
+ for (const group of components) {
163
+ for (const item of group) {
164
+ if (item && typeof item.getCurrentState === 'function') {
165
+ return item.getCurrentState();
166
+ }
167
+ }
168
+ }
169
+ return undefined;
170
+ }
171
+ broadcastUpdate(geometry, state) {
172
+ if (!this.channel || !this.layoutKey)
173
+ return;
174
+ this.channel.postMessage('popout-update', {
175
+ layoutKey: this.layoutKey,
176
+ geometry,
177
+ state,
178
+ });
179
+ }
180
+ /**
181
+ * Opens a component in a popout window.
182
+ * @param registration - The registration ID of the component to pop out.
183
+ * @param popoutState - Optional state (geometry and component state) to use for the new window.
184
+ * @param uuid - UUID generator from parent layout
185
+ */
186
+ openPopout(registration, popoutState, uuid) {
187
+ return __awaiter(this, void 0, void 0, function* () {
188
+ const url = new URL(window.location.toString());
189
+ const itemParams = new URLSearchParams();
190
+ itemParams.append(LAYOUT_POPOUT_CONTROL_KEY, registration);
191
+ url.search = itemParams.toString();
192
+ let features = 'popup';
193
+ const geometry = popoutState === null || popoutState === void 0 ? void 0 : popoutState.geometry;
194
+ let targetLeft = geometry === null || geometry === void 0 ? void 0 : geometry.screenX;
195
+ let targetTop = geometry === null || geometry === void 0 ? void 0 : geometry.screenY;
196
+ if ((geometry === null || geometry === void 0 ? void 0 : geometry.screenId) && 'getScreenDetails' in window) {
197
+ try {
198
+ const details = yield window.getScreenDetails();
199
+ const targetScreen = details.screens.find((s) => s.label === geometry.screenId);
200
+ if (targetScreen) {
201
+ const isWithin = targetLeft >= targetScreen.availLeft &&
202
+ targetLeft < targetScreen.availLeft + targetScreen.availWidth &&
203
+ targetTop >= targetScreen.availTop &&
204
+ targetTop < targetScreen.availTop + targetScreen.availHeight;
205
+ if (!isWithin) {
206
+ targetLeft = targetScreen.availLeft;
207
+ targetTop = targetScreen.availTop;
208
+ }
209
+ }
210
+ else {
211
+ targetLeft = undefined;
212
+ targetTop = undefined;
213
+ }
214
+ }
215
+ catch (e) {
216
+ // Fallback
217
+ }
218
+ }
219
+ if (geometry) {
220
+ features += `,width=${geometry.outerWidth},height=${geometry.outerHeight}`;
221
+ if (targetLeft !== undefined)
222
+ features += `,left=${targetLeft}`;
223
+ if (targetTop !== undefined)
224
+ features += `,top=${targetTop}`;
225
+ }
226
+ else {
227
+ features += ',width=800,height=600';
228
+ }
229
+ const win = window.open(url, `popout-${registration}-${(uuid === null || uuid === void 0 ? void 0 : uuid.createId()) || Date.now()}`, features);
230
+ if (!win) {
231
+ console.warn(`Popout window for ${registration} was blocked.`);
232
+ return null;
233
+ }
234
+ this.popoutRegistry.set(registration, {
235
+ geometry: geometry || {},
236
+ state: popoutState === null || popoutState === void 0 ? void 0 : popoutState.state,
237
+ window: win,
238
+ });
239
+ return win;
240
+ });
241
+ }
242
+ getRegistry() {
243
+ const output = {};
244
+ for (const [key, val] of this.popoutRegistry.entries()) {
245
+ output[key] = { geometry: val.geometry, state: val.state };
246
+ }
247
+ return output;
248
+ }
249
+ /**
250
+ * Restores popouts from a registry
251
+ */
252
+ restorePopouts(registry, uuid) {
253
+ return __awaiter(this, void 0, void 0, function* () {
254
+ this.closeAllPopouts();
255
+ for (const [key, val] of Object.entries(registry)) {
256
+ this.popoutRegistry.set(key, { geometry: val.geometry, state: val.state });
257
+ }
258
+ const keys = Array.from(this.popoutRegistry.keys());
259
+ if (keys.length === 0)
260
+ return;
261
+ yield Promise.all(keys.map((key) => {
262
+ const state = this.popoutRegistry.get(key);
263
+ return this.openPopout(key, state, uuid);
264
+ }));
265
+ });
266
+ }
267
+ closeAllPopouts() {
268
+ for (const val of this.popoutRegistry.values()) {
269
+ if (val.window && !val.window.closed) {
270
+ val.window.close();
271
+ }
272
+ }
273
+ this.popoutRegistry.clear();
274
+ }
275
+ };
276
+ __decorate([
277
+ attr({ attribute: 'channel-name' })
278
+ ], FoundationLayoutPopoutController.prototype, "channelName", void 0);
279
+ __decorate([
280
+ observable
281
+ ], FoundationLayoutPopoutController.prototype, "parentLayout", void 0);
282
+ FoundationLayoutPopoutController = __decorate([
283
+ customElement({
284
+ name: 'foundation-layout-popout-controller',
285
+ template: null,
286
+ styles: null,
287
+ })
288
+ ], FoundationLayoutPopoutController);
289
+ 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';