@genesislcap/foundation-layout 14.368.0 → 14.369.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.
- package/dist/custom-elements.json +142 -3
- package/dist/dts/index.d.ts +2 -1
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/layout-item.d.ts +20 -0
- package/dist/dts/main/layout-item.d.ts.map +1 -1
- package/dist/dts/main/layout-main.d.ts +36 -8
- package/dist/dts/main/layout-main.d.ts.map +1 -1
- package/dist/dts/utils/factory-registry.d.ts +63 -0
- package/dist/dts/utils/factory-registry.d.ts.map +1 -0
- package/dist/dts/utils/index.d.ts +1 -0
- package/dist/dts/utils/index.d.ts.map +1 -1
- package/dist/dts/utils/types.d.ts +44 -3
- package/dist/dts/utils/types.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/main/layout-item.js +26 -4
- package/dist/esm/main/layout-main.js +169 -46
- package/dist/esm/utils/factory-registry.js +88 -0
- package/dist/esm/utils/index.js +1 -0
- package/dist/foundation-layout.api.json +206 -9
- package/dist/foundation-layout.d.ts +156 -9
- package/docs/FRAMEWORK_COMPONENTS.md +568 -0
- package/docs/api/foundation-layout.componentfactory.md +46 -0
- package/docs/api/foundation-layout.foundationlayout.md +2 -2
- package/docs/api/foundation-layout.foundationlayout.registeritem.md +31 -7
- package/docs/api/foundation-layout.foundationlayoutitem.md +2 -0
- package/docs/api/foundation-layout.foundationlayoutitem.registration.md +18 -0
- package/docs/api/foundation-layout.getfactory.md +56 -0
- package/docs/api/foundation-layout.md +59 -0
- package/docs/api/foundation-layout.registerfactory.md +94 -0
- package/docs/api/foundation-layout.unregisterfactory.md +63 -0
- package/docs/api-report.md.api.md +20 -7
- package/package.json +12 -12
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
# Framework Components in Foundation Layout
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document discusses patterns and future enhancements for using framework-rendered components (React, Angular, Vue, etc.) within the foundation-layout system.
|
|
6
|
+
|
|
7
|
+
## Current State (JavaScript API)
|
|
8
|
+
|
|
9
|
+
The layout system now supports **factory functions** for registering components. This is the recommended approach for framework-rendered components because it allows each layout instance to create a fresh component rather than cloning existing DOM elements (which loses event listeners).
|
|
10
|
+
|
|
11
|
+
### Factory Function Pattern
|
|
12
|
+
|
|
13
|
+
The factory function pattern is **framework-agnostic** and works with any framework:
|
|
14
|
+
|
|
15
|
+
#### React
|
|
16
|
+
```typescript
|
|
17
|
+
layout.registerItem('my-component', (container) => {
|
|
18
|
+
const root = createRoot(container);
|
|
19
|
+
root.render(
|
|
20
|
+
<Provider store={store}>
|
|
21
|
+
<MyComponent />
|
|
22
|
+
</Provider>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Optional: return cleanup function
|
|
26
|
+
return () => root.unmount();
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Angular
|
|
31
|
+
```typescript
|
|
32
|
+
layout.registerItem('my-component', (container) => {
|
|
33
|
+
const componentRef = createComponent(MyComponent, {
|
|
34
|
+
environmentInjector: this.injector,
|
|
35
|
+
hostElement: container
|
|
36
|
+
});
|
|
37
|
+
componentRef.changeDetectorRef.detectChanges();
|
|
38
|
+
|
|
39
|
+
return () => componentRef.destroy();
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### Vue
|
|
44
|
+
```typescript
|
|
45
|
+
layout.registerItem('my-component', (container) => {
|
|
46
|
+
const app = createApp(MyComponent);
|
|
47
|
+
app.mount(container);
|
|
48
|
+
|
|
49
|
+
return () => app.unmount();
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Why Factory Functions?
|
|
54
|
+
|
|
55
|
+
The layout system needs to create multiple instances of registered components (e.g., when loading saved layouts, adding items multiple times, or handling popout windows). The original implementation used `cloneNode(true)`, which:
|
|
56
|
+
|
|
57
|
+
1. ✅ Works great for native HTML elements
|
|
58
|
+
2. ❌ **Loses JavaScript event listeners** (limitation of `cloneNode` API)
|
|
59
|
+
3. ❌ **Loses framework bindings** (React event handlers, Angular directives, Vue reactivity)
|
|
60
|
+
|
|
61
|
+
Factory functions solve this by letting frameworks create fresh, fully-functional component instances.
|
|
62
|
+
|
|
63
|
+
## Known Limitations
|
|
64
|
+
|
|
65
|
+
### Component State Serialization
|
|
66
|
+
|
|
67
|
+
**Current Limitation:** Factory-based components do not support the `LayoutComponentWithState<T>` interface for saving and restoring component-specific state.
|
|
68
|
+
|
|
69
|
+
#### Why This Limitation Exists
|
|
70
|
+
|
|
71
|
+
The layout system's state management works by calling `getCurrentState()` and `applyState()` methods on DOM elements. For factory-based components:
|
|
72
|
+
|
|
73
|
+
1. The factory creates a wrapper `div` that gets added to the layout
|
|
74
|
+
2. Framework components (React/Angular/Vue) render **inside** this wrapper
|
|
75
|
+
3. The wrapper is a plain `HTMLDivElement` without `getCurrentState()` or `applyState()` methods
|
|
76
|
+
4. The framework components inside have no mechanism to expose these methods to the layout system
|
|
77
|
+
|
|
78
|
+
#### What Works vs What Doesn't
|
|
79
|
+
|
|
80
|
+
✅ **Works:**
|
|
81
|
+
- Event listeners (click, input, change, etc.)
|
|
82
|
+
- Multiple instances of the same component
|
|
83
|
+
- Basic layout save/load (positions, sizes, tab order)
|
|
84
|
+
- Component cleanup via factory return value
|
|
85
|
+
|
|
86
|
+
❌ **Doesn't Work:**
|
|
87
|
+
- Saving component-specific state (form values, scroll position, selected items, etc.)
|
|
88
|
+
- Restoring component state when loading a saved layout
|
|
89
|
+
- Using `LayoutComponentWithState<T>` interface with factory components
|
|
90
|
+
|
|
91
|
+
#### Workarounds
|
|
92
|
+
|
|
93
|
+
Until state management is implemented for factory components, use these approaches:
|
|
94
|
+
|
|
95
|
+
**1. External State Management (Recommended)**
|
|
96
|
+
Use your framework's state management solution:
|
|
97
|
+
- React: Redux, Context API, Zustand, Jotai
|
|
98
|
+
- Angular: Services with RxJS
|
|
99
|
+
- Vue: Vuex, Pinia
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// React example with Redux
|
|
103
|
+
layout.registerItem('my-form', (container) => {
|
|
104
|
+
const root = createRoot(container);
|
|
105
|
+
root.render(
|
|
106
|
+
<Provider store={reduxStore}>
|
|
107
|
+
<MyFormComponent /> {/* State persisted in Redux */}
|
|
108
|
+
</Provider>
|
|
109
|
+
);
|
|
110
|
+
return () => root.unmount();
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**2. URL Parameters or Query Strings**
|
|
115
|
+
Store state in the URL for shareable/bookmarkable states:
|
|
116
|
+
```typescript
|
|
117
|
+
// React example
|
|
118
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
119
|
+
const initialState = searchParams.get('formData');
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**3. LocalStorage/SessionStorage**
|
|
123
|
+
Manually persist state outside the layout system:
|
|
124
|
+
```typescript
|
|
125
|
+
// React example with useEffect
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
localStorage.setItem('myComponentState', JSON.stringify(state));
|
|
128
|
+
}, [state]);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**4. Use Element-Based Registration**
|
|
132
|
+
If state management is critical and external solutions don't work, use element-based registration for those specific components (though you'll lose event listeners).
|
|
133
|
+
|
|
134
|
+
#### Proposed Solutions for Future Implementation
|
|
135
|
+
|
|
136
|
+
**Option 1: Extended Factory Return Type** (Recommended)
|
|
137
|
+
```typescript
|
|
138
|
+
type ComponentFactory = (container: HTMLElement) => {
|
|
139
|
+
cleanup?: () => void;
|
|
140
|
+
getCurrentState?: () => unknown;
|
|
141
|
+
applyState?: (state: unknown) => void;
|
|
142
|
+
} | void | (() => void);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Factory functions could return an object with state handlers:
|
|
146
|
+
```typescript
|
|
147
|
+
layout.registerItem('my-component', (container) => {
|
|
148
|
+
const root = createRoot(container);
|
|
149
|
+
let componentState = null;
|
|
150
|
+
|
|
151
|
+
root.render(<MyComponent onStateChange={(s) => componentState = s} />);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
cleanup: () => root.unmount(),
|
|
155
|
+
getCurrentState: () => componentState,
|
|
156
|
+
applyState: (state) => componentState = state
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Option 2: State Manager Callback**
|
|
162
|
+
```typescript
|
|
163
|
+
type ComponentFactory = (
|
|
164
|
+
container: HTMLElement,
|
|
165
|
+
stateManager?: {
|
|
166
|
+
registerStateHandlers: (handlers: {
|
|
167
|
+
getCurrentState: () => unknown;
|
|
168
|
+
applyState: (state: unknown) => void;
|
|
169
|
+
}) => void;
|
|
170
|
+
}
|
|
171
|
+
) => void | (() => void);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Factory receives a callback to register state handlers:
|
|
175
|
+
```typescript
|
|
176
|
+
layout.registerItem('my-component', (container, stateManager) => {
|
|
177
|
+
const root = createRoot(container);
|
|
178
|
+
let componentState = null;
|
|
179
|
+
|
|
180
|
+
root.render(<MyComponent onStateChange={(s) => componentState = s} />);
|
|
181
|
+
|
|
182
|
+
stateManager?.registerStateHandlers({
|
|
183
|
+
getCurrentState: () => componentState,
|
|
184
|
+
applyState: (state) => componentState = state
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return () => root.unmount();
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Option 3: Framework-Specific Wrappers**
|
|
192
|
+
Create helper functions that handle state automatically:
|
|
193
|
+
```typescript
|
|
194
|
+
// React helper
|
|
195
|
+
function createReactLayoutComponent(Component, getStateFromProps, applyStateToProps) {
|
|
196
|
+
return (container) => {
|
|
197
|
+
const root = createRoot(container);
|
|
198
|
+
// ... state management logic
|
|
199
|
+
return { cleanup, getCurrentState, applyState };
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Declarative HTML API with Factory Functions
|
|
205
|
+
|
|
206
|
+
The `foundation-layout-item` component supports factory functions for the declarative HTML API, enabling framework components to work properly while preserving event listeners and lifecycle management.
|
|
207
|
+
|
|
208
|
+
### Recommended Approach: Factory Registry Pattern
|
|
209
|
+
|
|
210
|
+
The factory registry uses the `registration` attribute as the lookup key for pre-registered factory functions. This provides a clean, unified API where the same name identifies the component in both the registry and the layout.
|
|
211
|
+
|
|
212
|
+
#### How It Works
|
|
213
|
+
|
|
214
|
+
1. Register factory functions using `registerFactory(key, factory)`
|
|
215
|
+
2. Use the same key as the `registration` attribute in your layout item
|
|
216
|
+
3. The layout automatically looks up the factory when the item connects
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// Simple and unified API:
|
|
220
|
+
registerFactory('my-component', factoryFunction);
|
|
221
|
+
|
|
222
|
+
// Then in HTML/JSX:
|
|
223
|
+
<rapid-layout-item registration="my-component" title="My Component" />
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Priority order when layout item connects:**
|
|
227
|
+
1. Factory from registry (using registration name) ✅
|
|
228
|
+
2. Slotted content (backward compatible) ✅
|
|
229
|
+
|
|
230
|
+
### React Example (Recommended)
|
|
231
|
+
|
|
232
|
+
Register factories **before** rendering using the same names as your layout item registrations:
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
import { registerFactory } from '@genesislcap/foundation-layout';
|
|
236
|
+
import { reactFactory, reactFactoryWithProvider } from './utils/react-layout-factory';
|
|
237
|
+
import { Provider } from 'react-redux';
|
|
238
|
+
import { TableComponent, TextFieldComponent } from './components';
|
|
239
|
+
|
|
240
|
+
// Register factories at module level using the same names as registrations
|
|
241
|
+
registerFactory('table', reactFactory(TableComponent));
|
|
242
|
+
registerFactory('table-with-redux', reactFactoryWithProvider(
|
|
243
|
+
TableComponent,
|
|
244
|
+
Provider,
|
|
245
|
+
{ store: reduxStore }
|
|
246
|
+
));
|
|
247
|
+
registerFactory('textfield', reactFactoryWithProvider(
|
|
248
|
+
TextFieldComponent,
|
|
249
|
+
Provider,
|
|
250
|
+
{ store: reduxStore }
|
|
251
|
+
));
|
|
252
|
+
|
|
253
|
+
function MyLayout() {
|
|
254
|
+
return (
|
|
255
|
+
<rapid-layout>
|
|
256
|
+
<rapid-layout-region>
|
|
257
|
+
{/* Simple component - registration name matches factory key */}
|
|
258
|
+
<rapid-layout-item
|
|
259
|
+
registration="table"
|
|
260
|
+
title="Table"
|
|
261
|
+
closable
|
|
262
|
+
/>
|
|
263
|
+
|
|
264
|
+
{/* Component with Redux provider */}
|
|
265
|
+
<rapid-layout-item
|
|
266
|
+
registration="table-with-redux"
|
|
267
|
+
title="Table (with Redux)"
|
|
268
|
+
closable
|
|
269
|
+
/>
|
|
270
|
+
|
|
271
|
+
{/* Component with custom size */}
|
|
272
|
+
<rapid-layout-item
|
|
273
|
+
registration="textfield"
|
|
274
|
+
title="Text Field"
|
|
275
|
+
size="33%"
|
|
276
|
+
/>
|
|
277
|
+
</rapid-layout-region>
|
|
278
|
+
</rapid-layout>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Helper utility (`react-layout-factory.tsx`):**
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import { ComponentFactory } from '@genesislcap/foundation-layout';
|
|
287
|
+
import { createRoot, Root } from 'react-dom/client';
|
|
288
|
+
import type { ComponentType, ReactElement } from 'react';
|
|
289
|
+
|
|
290
|
+
export function reactFactory<P = {}>(
|
|
291
|
+
Component: ComponentType<P>,
|
|
292
|
+
props?: P
|
|
293
|
+
): ComponentFactory {
|
|
294
|
+
return (container: HTMLElement) => {
|
|
295
|
+
const root: Root = createRoot(container);
|
|
296
|
+
root.render(<Component {...(props || ({} as P))} />);
|
|
297
|
+
return () => root.unmount(); // Cleanup
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function reactFactoryWithProvider<CP = {}, WP = {}>(
|
|
302
|
+
Component: ComponentType<CP>,
|
|
303
|
+
Wrapper: ComponentType<WP & { children: ReactElement }>,
|
|
304
|
+
wrapperProps: WP,
|
|
305
|
+
componentProps?: CP
|
|
306
|
+
): ComponentFactory {
|
|
307
|
+
return (container: HTMLElement) => {
|
|
308
|
+
const root: Root = createRoot(container);
|
|
309
|
+
root.render(
|
|
310
|
+
<Wrapper {...wrapperProps}>
|
|
311
|
+
<Component {...(componentProps || ({} as CP))} />
|
|
312
|
+
</Wrapper>
|
|
313
|
+
);
|
|
314
|
+
return () => root.unmount();
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Angular Example
|
|
320
|
+
|
|
321
|
+
Register factories in your component setup using the same names as registrations:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { Component, OnInit, Injector } from '@angular/core';
|
|
325
|
+
import { registerFactory, ComponentFactory } from '@genesislcap/foundation-layout';
|
|
326
|
+
import { createComponent } from '@angular/core';
|
|
327
|
+
import { MyAngularComponent } from './my-angular.component';
|
|
328
|
+
|
|
329
|
+
@Component({
|
|
330
|
+
selector: 'app-layout',
|
|
331
|
+
template: `
|
|
332
|
+
<rapid-layout>
|
|
333
|
+
<rapid-layout-region>
|
|
334
|
+
<rapid-layout-item
|
|
335
|
+
registration="angular-comp"
|
|
336
|
+
title="Angular Component">
|
|
337
|
+
</rapid-layout-item>
|
|
338
|
+
</rapid-layout-region>
|
|
339
|
+
</rapid-layout>
|
|
340
|
+
`
|
|
341
|
+
})
|
|
342
|
+
export class LayoutComponent implements OnInit {
|
|
343
|
+
constructor(private injector: Injector) {}
|
|
344
|
+
|
|
345
|
+
ngOnInit() {
|
|
346
|
+
// Register factory using the same name as the registration
|
|
347
|
+
registerFactory('angular-comp', (container: HTMLElement) => {
|
|
348
|
+
const componentRef = createComponent(MyAngularComponent, {
|
|
349
|
+
environmentInjector: this.injector,
|
|
350
|
+
hostElement: container
|
|
351
|
+
});
|
|
352
|
+
componentRef.changeDetectorRef.detectChanges();
|
|
353
|
+
|
|
354
|
+
return () => componentRef.destroy();
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Vue 3 Example
|
|
361
|
+
|
|
362
|
+
Register factories before mounting using the same names as registrations:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
import { createApp } from 'vue';
|
|
366
|
+
import { registerFactory } from '@genesislcap/foundation-layout';
|
|
367
|
+
import MyVueComponent from './MyVueComponent.vue';
|
|
368
|
+
|
|
369
|
+
// Register factory using the same name as the registration
|
|
370
|
+
registerFactory('vue-comp', (container: HTMLElement) => {
|
|
371
|
+
const app = createApp(MyVueComponent);
|
|
372
|
+
app.mount(container);
|
|
373
|
+
|
|
374
|
+
return () => app.unmount();
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Then in your template:
|
|
379
|
+
|
|
380
|
+
```html
|
|
381
|
+
<rapid-layout>
|
|
382
|
+
<rapid-layout-region>
|
|
383
|
+
<rapid-layout-item
|
|
384
|
+
registration="vue-comp"
|
|
385
|
+
title="Vue Component">
|
|
386
|
+
</rapid-layout-item>
|
|
387
|
+
</rapid-layout-region>
|
|
388
|
+
</rapid-layout>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### FAST Bindings Example
|
|
392
|
+
|
|
393
|
+
Register factories in your setup code using the same names as registrations:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
import { registerFactory } from '@genesislcap/foundation-layout';
|
|
397
|
+
|
|
398
|
+
registerFactory('grid', (container: HTMLElement) => {
|
|
399
|
+
const grid = document.createElement('rapid-grid');
|
|
400
|
+
// Configure grid...
|
|
401
|
+
container.appendChild(grid);
|
|
402
|
+
|
|
403
|
+
return () => grid.remove();
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Then use in your FAST template:
|
|
408
|
+
|
|
409
|
+
```html
|
|
410
|
+
<rapid-layout>
|
|
411
|
+
<rapid-layout-region>
|
|
412
|
+
<rapid-layout-item
|
|
413
|
+
registration="grid"
|
|
414
|
+
title="Data Grid">
|
|
415
|
+
</rapid-layout-item>
|
|
416
|
+
</rapid-layout-region>
|
|
417
|
+
</rapid-layout>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Factory Registry API
|
|
421
|
+
|
|
422
|
+
**`registerFactory(key: string, factory: ComponentFactory): void`**
|
|
423
|
+
- Registers a factory function with a unique key
|
|
424
|
+
- Key should be descriptive and unique across your application
|
|
425
|
+
- Logs a warning if key is already registered (will overwrite)
|
|
426
|
+
|
|
427
|
+
**`getFactory(key: string): ComponentFactory | undefined`**
|
|
428
|
+
- Retrieves a factory by its key
|
|
429
|
+
- Returns `undefined` if key not found
|
|
430
|
+
- Primarily for internal use by the layout system
|
|
431
|
+
|
|
432
|
+
**`unregisterFactory(key: string): boolean`**
|
|
433
|
+
- Removes a factory from the registry
|
|
434
|
+
- Returns `true` if factory was found and removed
|
|
435
|
+
- Useful for cleanup when components are unmounted
|
|
436
|
+
|
|
437
|
+
**Example:**
|
|
438
|
+
```typescript
|
|
439
|
+
import { registerFactory, unregisterFactory } from '@genesislcap/foundation-layout';
|
|
440
|
+
|
|
441
|
+
// Register
|
|
442
|
+
registerFactory('my-component', myFactoryFunction);
|
|
443
|
+
|
|
444
|
+
// Later, cleanup
|
|
445
|
+
unregisterFactory('my-component');
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Validation Rules
|
|
449
|
+
|
|
450
|
+
The layout system enforces two important validation rules to prevent conflicts:
|
|
451
|
+
|
|
452
|
+
**Rule 1: Cannot register via JavaScript API if factory exists**
|
|
453
|
+
```typescript
|
|
454
|
+
registerFactory('my-component', myFactory);
|
|
455
|
+
layout.registerItem('my-component', elements); // ❌ ERROR!
|
|
456
|
+
// LayoutRegistrationError: Registration "my-component" already has a factory registered
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**Rule 2: Cannot mix factory and slotted content**
|
|
460
|
+
```typescript
|
|
461
|
+
registerFactory('my-component', myFactory);
|
|
462
|
+
// ❌ ERROR - Has both factory AND slotted content
|
|
463
|
+
<rapid-layout-item registration="my-component">
|
|
464
|
+
<div>Content</div>
|
|
465
|
+
</rapid-layout-item>
|
|
466
|
+
// LayoutUsageError: Cannot use both factory registration and slotted content
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**Valid Usage Patterns:**
|
|
470
|
+
```typescript
|
|
471
|
+
// ✅ Declarative with factory
|
|
472
|
+
registerFactory('comp-1', myFactory);
|
|
473
|
+
<rapid-layout-item registration="comp-1" />
|
|
474
|
+
|
|
475
|
+
// ✅ Declarative with slotted content (no factory)
|
|
476
|
+
<rapid-layout-item registration="comp-2">
|
|
477
|
+
<div>Content</div>
|
|
478
|
+
</rapid-layout-item>
|
|
479
|
+
|
|
480
|
+
// ✅ JavaScript API (no factory in registry)
|
|
481
|
+
layout.registerItem('comp-3', elements);
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Key Points
|
|
485
|
+
|
|
486
|
+
✅ **Unified API:**
|
|
487
|
+
- Use the `registration` attribute as both the component identifier and factory key
|
|
488
|
+
- Register factories with `registerFactory(name, factory)` before rendering
|
|
489
|
+
- Simpler API with no duplication between registration and factory-key
|
|
490
|
+
- Guarantees factory availability when custom element connects
|
|
491
|
+
|
|
492
|
+
✅ **What Works:**
|
|
493
|
+
- Event listeners are preserved
|
|
494
|
+
- Framework lifecycle methods work correctly
|
|
495
|
+
- Multiple instances can be created
|
|
496
|
+
- Cleanup is handled automatically
|
|
497
|
+
- Works with React, Angular, Vue, FAST, and any other framework
|
|
498
|
+
|
|
499
|
+
✅ **Backward Compatibility:**
|
|
500
|
+
- Slotted content still works when no factory is registered
|
|
501
|
+
- No breaking changes to existing code using slotted elements
|
|
502
|
+
- Priority order: factory (using registration name) → slotted content
|
|
503
|
+
|
|
504
|
+
✅ **Validation:**
|
|
505
|
+
- Cannot register via JavaScript API if a factory exists with that name
|
|
506
|
+
- Cannot mix factory registration with slotted content
|
|
507
|
+
- Clear error messages guide proper usage
|
|
508
|
+
|
|
509
|
+
### Comparison: JavaScript API vs Declarative API
|
|
510
|
+
|
|
511
|
+
| Aspect | JavaScript API | Declarative API (with factory registry) |
|
|
512
|
+
|--------|----------------|-------------------------------|
|
|
513
|
+
| **Usage** | Programmatic registration | HTML/JSX markup |
|
|
514
|
+
| **Setup** | Requires `useEffect` or lifecycle hooks | Register factories, then use in template |
|
|
515
|
+
| **Dynamic Items** | Easy to add/remove items | Static structure |
|
|
516
|
+
| **Type Safety** | Full TypeScript support | Depends on framework bindings |
|
|
517
|
+
| **Factory Approach** | Direct function passing | Registry with registration name as key |
|
|
518
|
+
| **When to Use** | Complex layouts with dynamic items | Simple, static layouts |
|
|
519
|
+
|
|
520
|
+
**Recommendation:** Use the JavaScript API for dynamic layouts where items are added/removed programmatically. Use the declarative API with factory registration for simpler layouts with a fixed set of items.
|
|
521
|
+
|
|
522
|
+
## Best Practices
|
|
523
|
+
|
|
524
|
+
### When to Use Factory Functions
|
|
525
|
+
|
|
526
|
+
Use factory functions when:
|
|
527
|
+
- Components have JavaScript event listeners
|
|
528
|
+
- Components use framework-specific features (React hooks, Angular services, Vue composition API)
|
|
529
|
+
- Components need to be instantiated multiple times
|
|
530
|
+
- Components need cleanup logic
|
|
531
|
+
|
|
532
|
+
### When Element Arrays Are OK
|
|
533
|
+
|
|
534
|
+
Element arrays still work great for:
|
|
535
|
+
- Static HTML content
|
|
536
|
+
- Native web components without event listeners
|
|
537
|
+
- FAST components using FAST's binding system (these handle cloning correctly)
|
|
538
|
+
|
|
539
|
+
### Example: Mixed Approach
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
// Factory for React component with events
|
|
543
|
+
layout.registerItem('interactive-form', (container) => {
|
|
544
|
+
const root = createRoot(container);
|
|
545
|
+
root.render(<FormComponent />);
|
|
546
|
+
return () => root.unmount();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Element array for static content
|
|
550
|
+
const staticDiv = document.createElement('div');
|
|
551
|
+
staticDiv.innerHTML = '<h1>Static Content</h1>';
|
|
552
|
+
layout.registerItem('static-content', [staticDiv]);
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
## Questions or Issues?
|
|
556
|
+
|
|
557
|
+
If you encounter problems with framework components in the layout:
|
|
558
|
+
1. Check if you're using the factory function pattern
|
|
559
|
+
2. Verify cleanup functions are properly implemented
|
|
560
|
+
3. Check browser console for React/Angular/Vue warnings
|
|
561
|
+
4. File an issue with reproduction steps
|
|
562
|
+
|
|
563
|
+
## Related Documentation
|
|
564
|
+
|
|
565
|
+
- [Layout README](../README.md)
|
|
566
|
+
- [API Documentation](./api/foundation-layout.foundationlayout.md)
|
|
567
|
+
- [Element Cloning Limitations](../README.md#binding-events-inline-in-the-declarative-api)
|
|
568
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
|
2
|
+
|
|
3
|
+
[Home](./index.md) > [@genesislcap/foundation-layout](./foundation-layout.md) > [ComponentFactory](./foundation-layout.componentfactory.md)
|
|
4
|
+
|
|
5
|
+
## ComponentFactory type
|
|
6
|
+
|
|
7
|
+
Factory function for creating component instances in the layout.
|
|
8
|
+
|
|
9
|
+
**Signature:**
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
export type ComponentFactory = (container: HTMLElement) => void | (() => void);
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Remarks
|
|
16
|
+
|
|
17
|
+
This is the recommended approach for framework-rendered components (React, Angular, Vue, etc.) because it allows each layout instance to create a fresh component rather than cloning existing DOM elements (which loses event listeners and framework bindings).
|
|
18
|
+
|
|
19
|
+
The factory function receives a container element and should render the component into it. Optionally, it can return a cleanup function that will be called when the component is removed from the layout.
|
|
20
|
+
|
|
21
|
+
## Example 1
|
|
22
|
+
|
|
23
|
+
React example:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
layout.registerItem('my-component', (container) => {
|
|
27
|
+
const root = createRoot(container);
|
|
28
|
+
root.render(<MyComponent />);
|
|
29
|
+
return () => root.unmount();
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Example 2
|
|
34
|
+
|
|
35
|
+
Angular example:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
layout.registerItem('my-component', (container) => {
|
|
39
|
+
const componentRef = createComponent(MyComponent, {
|
|
40
|
+
environmentInjector: this.injector,
|
|
41
|
+
hostElement: container
|
|
42
|
+
});
|
|
43
|
+
return () => componentRef.destroy();
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
@@ -342,7 +342,7 @@ Gets all of the currently registered names
|
|
|
342
342
|
</td></tr>
|
|
343
343
|
<tr><td>
|
|
344
344
|
|
|
345
|
-
[registerItem(registration,
|
|
345
|
+
[registerItem(registration, elementsOrFactory)](./foundation-layout.foundationlayout.registeritem.md)
|
|
346
346
|
|
|
347
347
|
|
|
348
348
|
</td><td>
|
|
@@ -350,7 +350,7 @@ Gets all of the currently registered names
|
|
|
350
350
|
|
|
351
351
|
</td><td>
|
|
352
352
|
|
|
353
|
-
Register a collection of `Element` and associate them with an `ID` with the layout system for later use.
|
|
353
|
+
Register a collection of `Element` or a factory function and associate them with an `ID` with the layout system for later use.
|
|
354
354
|
|
|
355
355
|
|
|
356
356
|
</td></tr>
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## FoundationLayout.registerItem() method
|
|
6
6
|
|
|
7
|
-
Register a collection of `Element` and associate them with an `ID` with the layout system for later use.
|
|
7
|
+
Register a collection of `Element` or a factory function and associate them with an `ID` with the layout system for later use.
|
|
8
8
|
|
|
9
9
|
**Signature:**
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
|
-
registerItem(registration: string,
|
|
12
|
+
registerItem(registration: string, elementsOrFactory: Element[] | ComponentFactory): string;
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Parameters
|
|
@@ -48,17 +48,17 @@ string of the registration ID
|
|
|
48
48
|
</td></tr>
|
|
49
49
|
<tr><td>
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
elementsOrFactory
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
</td><td>
|
|
55
55
|
|
|
56
|
-
Element\[\]
|
|
56
|
+
Element\[\] \| [ComponentFactory](./foundation-layout.componentfactory.md)
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
</td><td>
|
|
60
60
|
|
|
61
|
-
Elements\[\] containing the reference to the elements to register
|
|
61
|
+
Either Elements\[\] containing the reference to the elements to register, or a ComponentFactory function
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
</td></tr>
|
|
@@ -78,7 +78,31 @@ string
|
|
|
78
78
|
|
|
79
79
|
## Remarks
|
|
80
80
|
|
|
81
|
-
You
|
|
81
|
+
You can register either an array of elements or a factory function.
|
|
82
82
|
|
|
83
|
-
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)`<!-- -->.
|
|
83
|
+
\*\*Element registration\*\*: Use this to register elements that you later want to load when using [FoundationLayout.loadLayout()](./foundation-layout.foundationlayout.loadlayout.md)<!-- -->. Use [FoundationLayout.layoutRequiredRegistrations()](./foundation-layout.foundationlayout.layoutrequiredregistrations.md) to see what components need to be registered for a certain config and then register them using this function before calling [FoundationLayout.loadLayout()](./foundation-layout.foundationlayout.loadlayout.md)<!-- -->. 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)`<!-- -->.
|
|
84
|
+
|
|
85
|
+
\*\*Factory registration\*\*: This is the recommended approach for framework-rendered components (React, Angular, Vue, etc.) because it allows each layout instance to create a fresh component rather than cloning existing DOM elements (which loses event listeners and framework bindings). The factory function will be called each time a new instance of the component is needed. It receives a container element and should render the component into it. Optionally, it can return a cleanup function that will be called when the component is removed from the layout.
|
|
86
|
+
|
|
87
|
+
## Example 1
|
|
88
|
+
|
|
89
|
+
Element registration:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const div = document.createElement('div');
|
|
93
|
+
div.innerHTML = '<h1>Hello</h1>';
|
|
94
|
+
layout.registerItem('my-element', [div]);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Example 2
|
|
98
|
+
|
|
99
|
+
Factory registration (React):
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
layout.registerItem('text-field', (container) => {
|
|
103
|
+
const root = createRoot(container);
|
|
104
|
+
root.render(<TextFieldComponent />);
|
|
105
|
+
return () => root.unmount();
|
|
106
|
+
});
|
|
107
|
+
```
|
|
84
108
|
|