@genesislcap/foundation-layout 14.354.0 → 14.354.2-FUI-2341-3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/custom-elements.json +245 -106
  2. package/dist/dts/index.d.ts +2 -1
  3. package/dist/dts/index.d.ts.map +1 -1
  4. package/dist/dts/main/layout-item.d.ts +20 -0
  5. package/dist/dts/main/layout-item.d.ts.map +1 -1
  6. package/dist/dts/main/layout-main.d.ts +36 -8
  7. package/dist/dts/main/layout-main.d.ts.map +1 -1
  8. package/dist/dts/utils/factory-registry.d.ts +63 -0
  9. package/dist/dts/utils/factory-registry.d.ts.map +1 -0
  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/types.d.ts +40 -1
  13. package/dist/dts/utils/types.d.ts.map +1 -1
  14. package/dist/esm/index.js +1 -0
  15. package/dist/esm/main/layout-item.js +26 -4
  16. package/dist/esm/main/layout-main.js +140 -45
  17. package/dist/esm/utils/factory-registry.js +88 -0
  18. package/dist/esm/utils/index.js +1 -0
  19. package/dist/foundation-layout.api.json +206 -9
  20. package/dist/foundation-layout.d.ts +152 -7
  21. package/docs/FRAMEWORK_COMPONENTS.md +568 -0
  22. package/docs/api/foundation-layout.componentfactory.md +46 -0
  23. package/docs/api/foundation-layout.foundationlayout.md +2 -2
  24. package/docs/api/foundation-layout.foundationlayout.registeritem.md +31 -7
  25. package/docs/api/foundation-layout.foundationlayoutitem.md +2 -0
  26. package/docs/api/foundation-layout.foundationlayoutitem.registration.md +18 -0
  27. package/docs/api/foundation-layout.getfactory.md +56 -0
  28. package/docs/api/foundation-layout.md +59 -0
  29. package/docs/api/foundation-layout.registerfactory.md +91 -0
  30. package/docs/api/foundation-layout.unregisterfactory.md +63 -0
  31. package/docs/api-report.md.api.md +17 -3
  32. package/package.json +12 -12
@@ -0,0 +1,63 @@
1
+ import type { ComponentFactory } from './types';
2
+ /**
3
+ * Registers a factory function with a unique key.
4
+ * This allows framework components to be used in the declarative layout API
5
+ * without needing to pass function references through HTML attributes.
6
+ *
7
+ * @param key - Unique identifier for the factory. Should be descriptive and unique across the application.
8
+ * @param factory - The factory function that creates the component.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * // React example
13
+ * import { registerFactory } from '@genesislcap/foundation-layout';
14
+ * import { reactFactory } from './utils/react-layout-factory';
15
+ * import { MyComponent } from './components/MyComponent';
16
+ *
17
+ * registerFactory('my-component', reactFactory(MyComponent, { someProp: 'value' }));
18
+ * ```
19
+ *
20
+ * Then in your JSX:
21
+ * ```tsx
22
+ * <rapid-layout-item
23
+ * registration="my-item"
24
+ * title="My Component"
25
+ * factory-key="my-component"
26
+ * />
27
+ * ```
28
+ *
29
+ * @public
30
+ */
31
+ export declare function registerFactory(key: string, factory: ComponentFactory): void;
32
+ /**
33
+ * Retrieves a factory function by its key.
34
+ *
35
+ * @param key - The unique identifier for the factory.
36
+ * @returns The factory function, or undefined if not found.
37
+ *
38
+ * @public
39
+ */
40
+ export declare function getFactory(key: string): ComponentFactory | undefined;
41
+ /**
42
+ * Removes a factory from the registry.
43
+ * This is useful for cleanup when a component is unmounted or no longer needed.
44
+ *
45
+ * @param key - The unique identifier for the factory to remove.
46
+ * @returns True if the factory was found and removed, false otherwise.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * unregisterFactory('my-component');
51
+ * ```
52
+ *
53
+ * @public
54
+ */
55
+ export declare function unregisterFactory(key: string): boolean;
56
+ /**
57
+ * Clears all registered factories from the registry.
58
+ * This is primarily useful for testing purposes.
59
+ *
60
+ * @internal
61
+ */
62
+ export declare function clearFactoryRegistry(): void;
63
+ //# sourceMappingURL=factory-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory-registry.d.ts","sourceRoot":"","sources":["../../../src/utils/factory-registry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAShD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAM5E;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEpE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQtD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C"}
@@ -1,6 +1,7 @@
1
1
  export * from './constants';
2
2
  export * from './error';
3
3
  export * from './events';
4
+ export * from './factory-registry';
4
5
  export * from './misc';
5
6
  export * from './templates';
6
7
  export * from './types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,oBAAoB,CAAC;AACnC,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
@@ -134,9 +134,48 @@ export type InstanceContainer = {
134
134
  export type LayoutComponentItem<T> = Element & Partial<LayoutComponentWithState<T>> & {
135
135
  [instanceContainer]?: InstanceContainer;
136
136
  };
137
+ /**
138
+ * @public
139
+ * Factory function for creating component instances in the layout.
140
+ * @remarks
141
+ * This is the recommended approach for framework-rendered components (React, Angular, Vue, etc.)
142
+ * because it allows each layout instance to create a fresh component rather than cloning existing
143
+ * DOM elements (which loses event listeners and framework bindings).
144
+ *
145
+ * The factory function receives a container element and should render the component into it.
146
+ * Optionally, it can return a cleanup function that will be called when the component is removed
147
+ * from the layout.
148
+ *
149
+ * @param container - The HTMLElement container where the component should be rendered
150
+ * @returns Optional cleanup function to be called when the component is removed
151
+ *
152
+ * @example
153
+ * React example:
154
+ * ```typescript
155
+ * layout.registerItem('my-component', (container) => {
156
+ * const root = createRoot(container);
157
+ * root.render(<MyComponent />);
158
+ * return () => root.unmount();
159
+ * });
160
+ * ```
161
+ *
162
+ * @example
163
+ * Angular example:
164
+ * ```typescript
165
+ * layout.registerItem('my-component', (container) => {
166
+ * const componentRef = createComponent(MyComponent, {
167
+ * environmentInjector: this.injector,
168
+ * hostElement: container
169
+ * });
170
+ * return () => componentRef.destroy();
171
+ * });
172
+ * ```
173
+ */
174
+ export type ComponentFactory = (container: HTMLElement) => void | (() => void);
137
175
  /** @internal */
138
176
  export interface RegistrationConfig {
139
- elements: Element[];
177
+ elements?: Element[];
178
+ factory?: ComponentFactory;
140
179
  id?: string;
141
180
  }
142
181
  /** @internal */
@@ -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,gBAAgB;AAChB,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,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":"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,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,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"}
package/dist/esm/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './main';
2
2
  export { LAYOUT_ICONS } from './styles';
3
3
  export { DEFAULT_RELOAD_BUFFER, LayoutEmitEvents, LayoutReceiveEvents, LayoutRegistrationError, LayoutUsageError, LAYOUT_POPOUT_CONTAINER_CLASS, } from './utils';
4
+ export { registerFactory, getFactory, unregisterFactory } from './utils/factory-registry';
@@ -3,6 +3,8 @@ import { __decorate } from "tslib";
3
3
  import { attr } from '@microsoft/fast-element';
4
4
  import { FoundationElement } from '@microsoft/fast-foundation';
5
5
  import { componentType, getParentLayoutComponent, wrapperTemplate, } from '../utils';
6
+ import { LayoutUsageError } from '../utils/error';
7
+ import { getFactory } from '../utils/factory-registry';
6
8
  /**
7
9
  * @public
8
10
  * `FoundationLayoutItem` is a custom element that represents an item in the layout.
@@ -10,6 +12,9 @@ import { componentType, getParentLayoutComponent, wrapperTemplate, } from '../ut
10
12
  * This element is used to wrap html elements and configure their layout settings as part of the layout system.
11
13
  *
12
14
  * This is a simple component which is only used to define the layout splits; any JavaScript API interactions or custom styling is used via {@link FoundationLayout}.
15
+ *
16
+ * The item can either use slotted content or a factory function registered via {@link registerFactory}.
17
+ * When a factory is registered with the same name as the registration attribute, it takes precedence over slotted content.
13
18
  * @tagname %%prefix%%-layout-item
14
19
  */
15
20
  export class FoundationLayoutItem extends FoundationElement {
@@ -28,11 +33,28 @@ export class FoundationLayoutItem extends FoundationElement {
28
33
  connectedCallback() {
29
34
  var _b;
30
35
  super.connectedCallback();
36
+ let registeredID;
37
+ // Look up factory from registry using registration name
38
+ const factory = getFactory(this.registration);
39
+ // Get slotted elements to check for conflicts
31
40
  this.slottedElements = this.shadowRoot.querySelector('slot.target').assignedElements();
32
- const registeredID = this.cacheElementsAndRegister({
33
- elements: this.slottedElements,
34
- id: this.registration,
35
- });
41
+ // Validate: cannot have both factory and slotted content
42
+ if (factory && this.slottedElements.length > 0) {
43
+ throw new LayoutUsageError(`Cannot use both factory registration and slotted content for registration "${this.registration}". ` +
44
+ `Either register a factory via registerFactory() OR provide slotted content, but not both.`);
45
+ }
46
+ if (factory) {
47
+ registeredID = this.cacheElementsAndRegister({
48
+ factory: factory,
49
+ id: this.registration,
50
+ });
51
+ }
52
+ else {
53
+ registeredID = this.cacheElementsAndRegister({
54
+ elements: this.slottedElements,
55
+ id: this.registration,
56
+ });
57
+ }
36
58
  const itemConfig = {
37
59
  type: 'component',
38
60
  componentType: registeredID,
@@ -8,6 +8,7 @@ import { FoundationElement } from '@microsoft/fast-foundation';
8
8
  import { globalDraggingStyles, glVisualConfig, LAYOUT_ICONS, layoutStyles } from '../styles';
9
9
  import { AUTOSAVE_KEY, componentType, DEFAULT_RELOAD_BUFFER, getMissingArrayItems, instanceContainer, LAYOUT_POPOUT_CONTAINER_CLASS, LAYOUT_POPOUT_CONTROL_KEY, LayoutEmitEvents, LayoutReceiveEvents, regionConveter, } from '../utils/';
10
10
  import { LayoutRegistrationError, LayoutUsageError } from '../utils/error';
11
+ import { getFactory } from '../utils/factory-registry';
11
12
  import { logger } from '../utils/logger';
12
13
  export { layoutStyles } from '../styles';
13
14
  /*
@@ -248,6 +249,11 @@ export class FoundationLayout extends FoundationElement {
248
249
  return;
249
250
  const orderedStates = [...items].map((item) => { var _b; return (_b = item.getCurrentState) === null || _b === void 0 ? void 0 : _b.call(item); });
250
251
  const componentInstanceContainer = items[0][instanceContainer];
252
+ // Add safety check for factory-based components
253
+ if (!componentInstanceContainer) {
254
+ logger.warn('Component instance container not found for items:', items);
255
+ return;
256
+ }
251
257
  // known use of deprecated API, but there is no other way of implementing it and we control
252
258
  // the underlying library anyway
253
259
  componentInstanceContainer.container.setState({
@@ -528,28 +534,70 @@ export class FoundationLayout extends FoundationElement {
528
534
  }
529
535
  /**
530
536
  * @public
531
- * Register a collection of `Element` and associate them with an `ID` with the layout system for later use.
537
+ * Register a collection of `Element` or a factory function and associate them with an `ID` with the layout system for later use.
532
538
  * @remarks
533
- * You would use this to register elements that you later want to load when using {@link FoundationLayout.loadLayout}.
539
+ * You can register either an array of elements or a factory function.
540
+ *
541
+ * **Element registration**: Use this to register elements that you later want to load when using {@link FoundationLayout.loadLayout}.
534
542
  * Use {@link FoundationLayout.layoutRequiredRegistrations} to see what components need to be registered for a certain config
535
543
  * and then register them using this function before calling {@link FoundationLayout.loadLayout}.
544
+ * When registering an element it is moved by reference into the internals of the layout, so if you pass elements already in the DOM then they will disappear.
545
+ * If you want to avoid this you can pass copies using `element.cloneNode(true)`.
536
546
  *
537
- * When registering an element it is moved by reference into the internals of the layout, so if you pass elements already in the DOM then they will disappear. If you want to avoid this you can pass copies using `element.cloneNode(true)`.
547
+ * **Factory registration**: This is the recommended approach for framework-rendered components (React, Angular, Vue, etc.)
548
+ * because it allows each layout instance to create a fresh component rather than cloning existing
549
+ * DOM elements (which loses event listeners and framework bindings).
550
+ * The factory function will be called each time a new instance of the component is needed. It receives
551
+ * a container element and should render the component into it. Optionally, it can return a cleanup
552
+ * function that will be called when the component is removed from the layout.
538
553
  *
539
554
  * @param registration - string of the registration ID
540
- * @param elements - Elements[] containing the reference to the elements to register for later usage
555
+ * @param elementsOrFactory - Either Elements[] containing the reference to the elements to register, or a ComponentFactory function
541
556
  * @throws {@link LayoutUsageError} if you attempt to add an item before the layout has been initialised.
542
557
  * @throws {@link LayoutRegistrationError} if you attempt to use a `registration` name which is already in use (declarative html API and JavaScript API registrations use the same "pool" of registration names).
543
558
  * @returns - string defining the name of the registered item with the layout system (config.id if set).
559
+ *
560
+ * @example
561
+ * Element registration:
562
+ * ```typescript
563
+ * const div = document.createElement('div');
564
+ * div.innerHTML = '<h1>Hello</h1>';
565
+ * layout.registerItem('my-element', [div]);
566
+ * ```
567
+ *
568
+ * @example
569
+ * Factory registration (React):
570
+ * ```typescript
571
+ * layout.registerItem('text-field', (container) => {
572
+ * const root = createRoot(container);
573
+ * root.render(<TextFieldComponent />);
574
+ * return () => root.unmount();
575
+ * });
576
+ * ```
544
577
  */
545
- registerItem(registration, elements) {
578
+ registerItem(registration, elementsOrFactory) {
546
579
  if (!this.layout.isInitialised) {
547
580
  throw new LayoutUsageError('Cannot add item via JS API until initialised');
548
581
  }
549
- return this.cacheElementsAndRegister({
550
- elements: elements,
551
- id: registration,
552
- });
582
+ // Check if a factory is already registered with this name
583
+ const existingFactory = getFactory(registration);
584
+ if (existingFactory) {
585
+ throw new LayoutRegistrationError(`Registration "${registration}" already has a factory registered via registerFactory(). ` +
586
+ `Cannot register the same name via the JavaScript API. ` +
587
+ `Use a different registration name or unregister the factory first.`);
588
+ }
589
+ if (typeof elementsOrFactory === 'function') {
590
+ return this.cacheElementsAndRegister({
591
+ factory: elementsOrFactory,
592
+ id: registration,
593
+ });
594
+ }
595
+ else {
596
+ return this.cacheElementsAndRegister({
597
+ elements: elementsOrFactory,
598
+ id: registration,
599
+ });
600
+ }
553
601
  }
554
602
  /**
555
603
  * Internal APIs
@@ -610,12 +658,13 @@ export class FoundationLayout extends FoundationElement {
610
658
  /**
611
659
  * Registers a function with golden layout to create a pane
612
660
  * @param elements - Elements[] to add to new new pane
661
+ * @param factory - ComponentFactory function to create component instances
613
662
  * @param id - optional string which is used to register the new function with golden layout. Defaults to sequentially setting the IDs for default items
614
663
  * @returns - string which is the registered ID
615
664
  * @throws - {@link LayoutRegistrationError} if the id is already in use
616
665
  * @internal
617
666
  */
618
- cacheElementsAndRegister({ elements, id }) {
667
+ cacheElementsAndRegister({ elements, factory, id }) {
619
668
  const reg = id || `${(this.registeredComponents += 1)}`;
620
669
  if (this.layout.getRegisteredComponentTypeNames().includes(reg)) {
621
670
  throw new LayoutRegistrationError(`Cannot register item with already registered name: '${reg}'`);
@@ -632,45 +681,91 @@ export class FoundationLayout extends FoundationElement {
632
681
  *
633
682
  * As part of creating each instance we attach a reference to the instance container which is used
634
683
  * to be able to optionally save state, and any state which has been saved we apply to the component.
684
+ *
685
+ * For factory functions:
686
+ * The factory is called for each new instance instead of cloning. This preserves event listeners
687
+ * and framework bindings that would be lost during cloning.
635
688
  */
636
689
  const registrationFunction = (() => {
637
- // Use appendChild to consume the elements and save them in the master copy
638
- const masterCopy = document.createDocumentFragment();
639
- masterCopy[layoutCacheDocument] = true;
640
- elements.forEach((e) => masterCopy.appendChild(e));
641
- const instances = new Map();
642
- return (container, state) => {
643
- var _b;
644
- // If this is a new instance then assign it uuid instance
645
- if (!(state === null || state === void 0 ? void 0 : state['instance'])) {
646
- state['instance'] = this.uuid.createId();
647
- }
648
- container.state['instance'] = state['instance'];
649
- container.state['originalTitle'] = (_b = state['originalTitle']) !== null && _b !== void 0 ? _b : container.title;
650
- // If this is a new instance then copy the master copy into the instances map
651
- // this is then the instance that is recalled for this version each time
652
- // the key point is "cloneNode" which makes a copy at this point
653
- if (!instances.has(state === null || state === void 0 ? void 0 : state['instance'])) {
654
- const instanceCopy = document.createDocumentFragment();
655
- Array.from(masterCopy.children).forEach((e) => {
656
- instanceCopy.appendChild(e.cloneNode(true));
690
+ if (factory) {
691
+ // Factory-based registration for framework components
692
+ const instances = new Map();
693
+ const cleanupFunctions = new Map();
694
+ return (container, state) => {
695
+ var _b;
696
+ // If this is a new instance then assign it uuid instance
697
+ if (!(state === null || state === void 0 ? void 0 : state['instance'])) {
698
+ state['instance'] = this.uuid.createId();
699
+ }
700
+ container.state['instance'] = state['instance'];
701
+ container.state['originalTitle'] = (_b = state['originalTitle']) !== null && _b !== void 0 ? _b : container.title;
702
+ // Store instance container reference for state management FIRST
703
+ const componentInstanceContainer = {
704
+ container,
705
+ instance: state['instance'],
706
+ registration: reg,
707
+ };
708
+ // If this is a new instance, call the factory to create it
709
+ if (!instances.has(state === null || state === void 0 ? void 0 : state['instance'])) {
710
+ const componentContainer = document.createElement('div');
711
+ componentContainer[instanceContainer] = componentInstanceContainer;
712
+ const cleanup = factory(componentContainer);
713
+ instances.set(state['instance'], componentContainer);
714
+ if (cleanup) {
715
+ cleanupFunctions.set(state['instance'], cleanup);
716
+ }
717
+ }
718
+ // Append the instance container to the layout container
719
+ const componentContainer = instances.get(state['instance']);
720
+ if (!componentContainer) {
721
+ logger.error(`Failed to get component container for instance ${state['instance']}`);
722
+ return;
723
+ }
724
+ // Ensure the property is set (in case it was lost)
725
+ componentContainer[instanceContainer] = componentInstanceContainer;
726
+ container.element.appendChild(componentContainer);
727
+ this.setupLayoutReceiveEvents(container, state);
728
+ };
729
+ }
730
+ else {
731
+ // Element-based registration (existing behavior)
732
+ const masterCopy = document.createDocumentFragment();
733
+ masterCopy[layoutCacheDocument] = true;
734
+ elements.forEach((e) => masterCopy.appendChild(e));
735
+ const instances = new Map();
736
+ return (container, state) => {
737
+ var _b;
738
+ // If this is a new instance then assign it uuid instance
739
+ if (!(state === null || state === void 0 ? void 0 : state['instance'])) {
740
+ state['instance'] = this.uuid.createId();
741
+ }
742
+ container.state['instance'] = state['instance'];
743
+ container.state['originalTitle'] = (_b = state['originalTitle']) !== null && _b !== void 0 ? _b : container.title;
744
+ // If this is a new instance then copy the master copy into the instances map
745
+ // this is then the instance that is recalled for this version each time
746
+ // the key point is "cloneNode" which makes a copy at this point
747
+ if (!instances.has(state === null || state === void 0 ? void 0 : state['instance'])) {
748
+ const instanceCopy = document.createDocumentFragment();
749
+ Array.from(masterCopy.children).forEach((e) => {
750
+ instanceCopy.appendChild(e.cloneNode(true));
751
+ });
752
+ instances.set(state['instance'], [...instanceCopy.children]);
753
+ }
754
+ // provide each component with a reference to the instance container
755
+ // so they can optionally save and load their own state
756
+ const componentInstanceContainer = {
757
+ container,
758
+ instance: state['instance'],
759
+ registration: reg,
760
+ };
761
+ // get the instance from the map and append it to the container
762
+ instances.get(state['instance']).forEach((component) => {
763
+ container.element.appendChild(component);
764
+ component[instanceContainer] = componentInstanceContainer;
657
765
  });
658
- instances.set(state['instance'], [...instanceCopy.children]);
659
- }
660
- // provide each component with a reference to the instance container
661
- // so they can optionally save and load their own state
662
- const componentInstanceContainer = {
663
- container,
664
- instance: state['instance'],
665
- registration: reg,
766
+ this.setupLayoutReceiveEvents(container, state);
666
767
  };
667
- // get the instance from the map and append it to the container
668
- instances.get(state['instance']).forEach((component) => {
669
- container.element.appendChild(component);
670
- component[instanceContainer] = componentInstanceContainer;
671
- });
672
- this.setupLayoutReceiveEvents(container, state);
673
- };
768
+ }
674
769
  })();
675
770
  this.layout.registerComponentFactoryFunction(reg, registrationFunction);
676
771
  return reg;
@@ -0,0 +1,88 @@
1
+ import { logger } from './logger';
2
+ /**
3
+ * Global registry for component factories.
4
+ * Maps factory keys (strings) to factory functions.
5
+ * @internal
6
+ */
7
+ const factoryRegistry = new Map();
8
+ /**
9
+ * Registers a factory function with a unique key.
10
+ * This allows framework components to be used in the declarative layout API
11
+ * without needing to pass function references through HTML attributes.
12
+ *
13
+ * @param key - Unique identifier for the factory. Should be descriptive and unique across the application.
14
+ * @param factory - The factory function that creates the component.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * // React example
19
+ * import { registerFactory } from '@genesislcap/foundation-layout';
20
+ * import { reactFactory } from './utils/react-layout-factory';
21
+ * import { MyComponent } from './components/MyComponent';
22
+ *
23
+ * registerFactory('my-component', reactFactory(MyComponent, { someProp: 'value' }));
24
+ * ```
25
+ *
26
+ * Then in your JSX:
27
+ * ```tsx
28
+ * <rapid-layout-item
29
+ * registration="my-item"
30
+ * title="My Component"
31
+ * factory-key="my-component"
32
+ * />
33
+ * ```
34
+ *
35
+ * @public
36
+ */
37
+ export function registerFactory(key, factory) {
38
+ if (factoryRegistry.has(key)) {
39
+ logger.warn(`Factory with key "${key}" is already registered. It will be overwritten.`);
40
+ }
41
+ factoryRegistry.set(key, factory);
42
+ logger.debug(`Factory registered with key: ${key}`);
43
+ }
44
+ /**
45
+ * Retrieves a factory function by its key.
46
+ *
47
+ * @param key - The unique identifier for the factory.
48
+ * @returns The factory function, or undefined if not found.
49
+ *
50
+ * @public
51
+ */
52
+ export function getFactory(key) {
53
+ return factoryRegistry.get(key);
54
+ }
55
+ /**
56
+ * Removes a factory from the registry.
57
+ * This is useful for cleanup when a component is unmounted or no longer needed.
58
+ *
59
+ * @param key - The unique identifier for the factory to remove.
60
+ * @returns True if the factory was found and removed, false otherwise.
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * unregisterFactory('my-component');
65
+ * ```
66
+ *
67
+ * @public
68
+ */
69
+ export function unregisterFactory(key) {
70
+ const deleted = factoryRegistry.delete(key);
71
+ if (deleted) {
72
+ logger.debug(`Factory unregistered with key: ${key}`);
73
+ }
74
+ else {
75
+ logger.warn(`Attempted to unregister factory with key "${key}", but it was not found.`);
76
+ }
77
+ return deleted;
78
+ }
79
+ /**
80
+ * Clears all registered factories from the registry.
81
+ * This is primarily useful for testing purposes.
82
+ *
83
+ * @internal
84
+ */
85
+ export function clearFactoryRegistry() {
86
+ factoryRegistry.clear();
87
+ logger.debug('Factory registry cleared');
88
+ }
@@ -1,6 +1,7 @@
1
1
  export * from './constants';
2
2
  export * from './error';
3
3
  export * from './events';
4
+ export * from './factory-registry';
4
5
  export * from './misc';
5
6
  export * from './templates';
6
7
  export * from './types';