@genesislcap/foundation-layout 14.368.0 → 14.370.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 +177 -9
- 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 +40 -8
- package/dist/dts/main/layout-main.d.ts.map +1 -1
- package/dist/dts/styles/constants.d.ts +1 -1
- package/dist/dts/styles/constants.d.ts.map +1 -1
- package/dist/dts/styles/layout.styles.d.ts.map +1 -1
- package/dist/dts/utils/constants.d.ts +2 -0
- package/dist/dts/utils/constants.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 +187 -47
- package/dist/esm/styles/constants.js +4 -3
- package/dist/esm/styles/layout.styles.js +15 -10
- package/dist/esm/utils/constants.js +11 -0
- 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 +160 -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 +22 -7
- package/package.json +13 -12
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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,
|
|
@@ -2,12 +2,14 @@ var _FoundationLayout__boundDragListener, _a;
|
|
|
2
2
|
import { __classPrivateFieldGet, __classPrivateFieldSet, __decorate } from "tslib";
|
|
3
3
|
import { GoldenLayout, LayoutConfig, ResolvedLayoutConfig, } from '@genesis-community/golden-layout';
|
|
4
4
|
import { Session } from '@genesislcap/foundation-comms';
|
|
5
|
+
import { designTokensMap } from '@genesislcap/foundation-ui';
|
|
5
6
|
import { layoutCacheDocument, UUID } from '@genesislcap/foundation-utils';
|
|
6
7
|
import { attr, html, observable, ref, when } from '@microsoft/fast-element';
|
|
7
8
|
import { FoundationElement } from '@microsoft/fast-foundation';
|
|
8
9
|
import { globalDraggingStyles, glVisualConfig, LAYOUT_ICONS, layoutStyles } from '../styles';
|
|
9
10
|
import { AUTOSAVE_KEY, componentType, DEFAULT_RELOAD_BUFFER, getMissingArrayItems, instanceContainer, LAYOUT_POPOUT_CONTAINER_CLASS, LAYOUT_POPOUT_CONTROL_KEY, LayoutEmitEvents, LayoutReceiveEvents, regionConveter, } from '../utils/';
|
|
10
11
|
import { LayoutRegistrationError, LayoutUsageError } from '../utils/error';
|
|
12
|
+
import { getFactory } from '../utils/factory-registry';
|
|
11
13
|
import { logger } from '../utils/logger';
|
|
12
14
|
export { layoutStyles } from '../styles';
|
|
13
15
|
/*
|
|
@@ -108,6 +110,8 @@ export class FoundationLayout extends FoundationElement {
|
|
|
108
110
|
this.popupMode = false;
|
|
109
111
|
/** @internal */
|
|
110
112
|
_FoundationLayout__boundDragListener.set(this, undefined);
|
|
113
|
+
/** @internal */
|
|
114
|
+
this.customTokensMap = designTokensMap();
|
|
111
115
|
this.onDragStart = this.onDragStart.bind(this);
|
|
112
116
|
this.onDragStop = this.onDragStop.bind(this);
|
|
113
117
|
this.cacheAndSaveLayout = this.cacheAndSaveLayout.bind(this);
|
|
@@ -196,6 +200,16 @@ export class FoundationLayout extends FoundationElement {
|
|
|
196
200
|
onPreItemMinimised() {
|
|
197
201
|
this.updateLifecycleToken();
|
|
198
202
|
}
|
|
203
|
+
/** @internal */
|
|
204
|
+
customTokensMapChanged() {
|
|
205
|
+
// Handles the race condition of the layout being init before the tokens
|
|
206
|
+
// are configured, in this case it'll reload the layout with the tokens
|
|
207
|
+
if (!this.customTokensMap.value)
|
|
208
|
+
return;
|
|
209
|
+
if (!this.hasFirstLoaded)
|
|
210
|
+
return;
|
|
211
|
+
this.loadGLConfigAndSetup(this.layoutConfig);
|
|
212
|
+
}
|
|
199
213
|
/**
|
|
200
214
|
* JS API, public
|
|
201
215
|
*/
|
|
@@ -248,6 +262,13 @@ export class FoundationLayout extends FoundationElement {
|
|
|
248
262
|
return;
|
|
249
263
|
const orderedStates = [...items].map((item) => { var _b; return (_b = item.getCurrentState) === null || _b === void 0 ? void 0 : _b.call(item); });
|
|
250
264
|
const componentInstanceContainer = items[0][instanceContainer];
|
|
265
|
+
// TODO: Factory based components dont handle the LayoutWithState interface, so we need to handle this differently.
|
|
266
|
+
// For now we will just log a warning and return. We need to assess whether to deprecate that interface and handle it in TAM directly
|
|
267
|
+
// or whether adding support is needed
|
|
268
|
+
if (!componentInstanceContainer) {
|
|
269
|
+
logger.warn('Component instance container not found for items:', items);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
251
272
|
// known use of deprecated API, but there is no other way of implementing it and we control
|
|
252
273
|
// the underlying library anyway
|
|
253
274
|
componentInstanceContainer.container.setState({
|
|
@@ -528,28 +549,70 @@ export class FoundationLayout extends FoundationElement {
|
|
|
528
549
|
}
|
|
529
550
|
/**
|
|
530
551
|
* @public
|
|
531
|
-
* Register a collection of `Element` and associate them with an `ID` with the layout system for later use.
|
|
552
|
+
* Register a collection of `Element` or a factory function and associate them with an `ID` with the layout system for later use.
|
|
532
553
|
* @remarks
|
|
533
|
-
* You
|
|
554
|
+
* You can register either an array of elements or a factory function.
|
|
555
|
+
*
|
|
556
|
+
* **Element registration**: Use this to register elements that you later want to load when using {@link FoundationLayout.loadLayout}.
|
|
534
557
|
* Use {@link FoundationLayout.layoutRequiredRegistrations} to see what components need to be registered for a certain config
|
|
535
558
|
* and then register them using this function before calling {@link FoundationLayout.loadLayout}.
|
|
559
|
+
* 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.
|
|
560
|
+
* If you want to avoid this you can pass copies using `element.cloneNode(true)`.
|
|
536
561
|
*
|
|
537
|
-
*
|
|
562
|
+
* **Factory registration**: This is the recommended approach for framework-rendered components (React, Angular, Vue, etc.)
|
|
563
|
+
* because it allows each layout instance to create a fresh component rather than cloning existing
|
|
564
|
+
* DOM elements (which loses event listeners and framework bindings).
|
|
565
|
+
* The factory function will be called each time a new instance of the component is needed. It receives
|
|
566
|
+
* a container element and should render the component into it. Optionally, it can return a cleanup
|
|
567
|
+
* function that will be called when the component is removed from the layout.
|
|
538
568
|
*
|
|
539
569
|
* @param registration - string of the registration ID
|
|
540
|
-
* @param
|
|
570
|
+
* @param elementsOrFactory - Either Elements[] containing the reference to the elements to register, or a ComponentFactory function
|
|
541
571
|
* @throws {@link LayoutUsageError} if you attempt to add an item before the layout has been initialised.
|
|
542
572
|
* @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
573
|
* @returns - string defining the name of the registered item with the layout system (config.id if set).
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* Element registration:
|
|
577
|
+
* ```typescript
|
|
578
|
+
* const div = document.createElement('div');
|
|
579
|
+
* div.innerHTML = '<h1>Hello</h1>';
|
|
580
|
+
* layout.registerItem('my-element', [div]);
|
|
581
|
+
* ```
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* Factory registration (React):
|
|
585
|
+
* ```typescript
|
|
586
|
+
* layout.registerItem('text-field', (container) => {
|
|
587
|
+
* const root = createRoot(container);
|
|
588
|
+
* root.render(<TextFieldComponent />);
|
|
589
|
+
* return () => root.unmount();
|
|
590
|
+
* });
|
|
591
|
+
* ```
|
|
544
592
|
*/
|
|
545
|
-
registerItem(registration,
|
|
593
|
+
registerItem(registration, elementsOrFactory) {
|
|
546
594
|
if (!this.layout.isInitialised) {
|
|
547
595
|
throw new LayoutUsageError('Cannot add item via JS API until initialised');
|
|
548
596
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
597
|
+
// Check if a factory is already registered with this name
|
|
598
|
+
const existingFactory = getFactory(registration);
|
|
599
|
+
if (existingFactory) {
|
|
600
|
+
throw new LayoutRegistrationError(`Registration "${registration}" already has a factory registered via registerFactory(). ` +
|
|
601
|
+
`Cannot register the same name via the JavaScript API. ` +
|
|
602
|
+
`Use a different registration name or unregister the factory first.`);
|
|
603
|
+
}
|
|
604
|
+
if (typeof elementsOrFactory === 'function') {
|
|
605
|
+
return this.cacheElementsAndRegister({
|
|
606
|
+
factory: elementsOrFactory,
|
|
607
|
+
id: registration,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
return this.cacheElementsAndRegister({
|
|
612
|
+
elements: elementsOrFactory,
|
|
613
|
+
id: registration,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
553
616
|
}
|
|
554
617
|
/**
|
|
555
618
|
* Internal APIs
|
|
@@ -610,13 +673,14 @@ export class FoundationLayout extends FoundationElement {
|
|
|
610
673
|
/**
|
|
611
674
|
* Registers a function with golden layout to create a pane
|
|
612
675
|
* @param elements - Elements[] to add to new new pane
|
|
676
|
+
* @param factory - ComponentFactory function to create component instances
|
|
613
677
|
* @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
678
|
* @returns - string which is the registered ID
|
|
615
679
|
* @throws - {@link LayoutRegistrationError} if the id is already in use
|
|
616
680
|
* @internal
|
|
617
681
|
*/
|
|
618
|
-
cacheElementsAndRegister(
|
|
619
|
-
const reg = id || `${(this.registeredComponents += 1)}`;
|
|
682
|
+
cacheElementsAndRegister(config) {
|
|
683
|
+
const reg = config.id || `${(this.registeredComponents += 1)}`;
|
|
620
684
|
if (this.layout.getRegisteredComponentTypeNames().includes(reg)) {
|
|
621
685
|
throw new LayoutRegistrationError(`Cannot register item with already registered name: '${reg}'`);
|
|
622
686
|
}
|
|
@@ -632,45 +696,117 @@ export class FoundationLayout extends FoundationElement {
|
|
|
632
696
|
*
|
|
633
697
|
* As part of creating each instance we attach a reference to the instance container which is used
|
|
634
698
|
* to be able to optionally save state, and any state which has been saved we apply to the component.
|
|
699
|
+
*
|
|
700
|
+
* For factory functions:
|
|
701
|
+
* The factory is called for each new instance instead of cloning. This preserves event listeners
|
|
702
|
+
* and framework bindings that would be lost during cloning.
|
|
635
703
|
*/
|
|
636
704
|
const registrationFunction = (() => {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
705
|
+
if ('factory' in config) {
|
|
706
|
+
// Factory-based registration for framework components
|
|
707
|
+
const instances = new Map();
|
|
708
|
+
const cleanupFunctions = new Map();
|
|
709
|
+
const containerTracking = new Map();
|
|
710
|
+
return (container, state) => {
|
|
711
|
+
var _b;
|
|
712
|
+
// If this is a new instance then assign it uuid instance
|
|
713
|
+
if (!(state === null || state === void 0 ? void 0 : state['instance'])) {
|
|
714
|
+
state['instance'] = this.uuid.createId();
|
|
715
|
+
}
|
|
716
|
+
container.state['instance'] = state['instance'];
|
|
717
|
+
container.state['originalTitle'] = (_b = state['originalTitle']) !== null && _b !== void 0 ? _b : container.title;
|
|
718
|
+
// Store instance container reference for state management FIRST
|
|
719
|
+
const componentInstanceContainer = {
|
|
720
|
+
container,
|
|
721
|
+
instance: state['instance'],
|
|
722
|
+
registration: reg,
|
|
723
|
+
};
|
|
724
|
+
const instanceId = state['instance'];
|
|
725
|
+
// Check if this instance was previously rendered in a different container
|
|
726
|
+
const previousContainer = containerTracking.get(instanceId);
|
|
727
|
+
const hasMoved = previousContainer && previousContainer !== container;
|
|
728
|
+
// If moved to a different container (drag operation), cleanup and recreate to avoid stale state
|
|
729
|
+
if (hasMoved) {
|
|
730
|
+
logger.debug(`Recreating instance ${instanceId} for registration ${reg} due to container change`);
|
|
731
|
+
// Call cleanup function if it exists
|
|
732
|
+
const cleanup = cleanupFunctions.get(instanceId);
|
|
733
|
+
if (cleanup && typeof cleanup === 'function') {
|
|
734
|
+
try {
|
|
735
|
+
cleanup();
|
|
736
|
+
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
logger.error(`Error calling cleanup for instance ${instanceId}:`, error);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// Remove from maps to force recreation
|
|
742
|
+
instances.delete(instanceId);
|
|
743
|
+
cleanupFunctions.delete(instanceId);
|
|
744
|
+
}
|
|
745
|
+
// Create instance if it doesn't exist (new instance or recreated after move)
|
|
746
|
+
if (!instances.has(instanceId)) {
|
|
747
|
+
const componentContainer = document.createElement('div');
|
|
748
|
+
componentContainer.style.width = '100%';
|
|
749
|
+
componentContainer.style.height = '100%';
|
|
750
|
+
componentContainer[instanceContainer] = componentInstanceContainer;
|
|
751
|
+
const cleanup = config.factory(componentContainer);
|
|
752
|
+
instances.set(instanceId, componentContainer);
|
|
753
|
+
if (cleanup) {
|
|
754
|
+
cleanupFunctions.set(instanceId, cleanup);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
// Append the instance container to the layout container
|
|
758
|
+
const componentContainer = instances.get(instanceId);
|
|
759
|
+
if (!componentContainer) {
|
|
760
|
+
logger.error(`Failed to get component container for instance ${instanceId}`);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
// Ensure the property is set (in case it was lost)
|
|
764
|
+
componentContainer[instanceContainer] = componentInstanceContainer;
|
|
765
|
+
container.element.appendChild(componentContainer);
|
|
766
|
+
// Track which container this instance is now in
|
|
767
|
+
containerTracking.set(instanceId, container);
|
|
768
|
+
this.setupLayoutReceiveEvents(container, state);
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
// Element-based registration (existing behavior)
|
|
773
|
+
const masterCopy = document.createDocumentFragment();
|
|
774
|
+
masterCopy[layoutCacheDocument] = true;
|
|
775
|
+
config.elements.forEach((e) => masterCopy.appendChild(e));
|
|
776
|
+
const instances = new Map();
|
|
777
|
+
return (container, state) => {
|
|
778
|
+
var _b;
|
|
779
|
+
// If this is a new instance then assign it uuid instance
|
|
780
|
+
if (!(state === null || state === void 0 ? void 0 : state['instance'])) {
|
|
781
|
+
state['instance'] = this.uuid.createId();
|
|
782
|
+
}
|
|
783
|
+
container.state['instance'] = state['instance'];
|
|
784
|
+
container.state['originalTitle'] = (_b = state['originalTitle']) !== null && _b !== void 0 ? _b : container.title;
|
|
785
|
+
// If this is a new instance then copy the master copy into the instances map
|
|
786
|
+
// this is then the instance that is recalled for this version each time
|
|
787
|
+
// the key point is "cloneNode" which makes a copy at this point
|
|
788
|
+
if (!instances.has(state === null || state === void 0 ? void 0 : state['instance'])) {
|
|
789
|
+
const instanceCopy = document.createDocumentFragment();
|
|
790
|
+
Array.from(masterCopy.children).forEach((e) => {
|
|
791
|
+
instanceCopy.appendChild(e.cloneNode(true));
|
|
792
|
+
});
|
|
793
|
+
instances.set(state['instance'], [...instanceCopy.children]);
|
|
794
|
+
}
|
|
795
|
+
// provide each component with a reference to the instance container
|
|
796
|
+
// so they can optionally save and load their own state
|
|
797
|
+
const componentInstanceContainer = {
|
|
798
|
+
container,
|
|
799
|
+
instance: state['instance'],
|
|
800
|
+
registration: reg,
|
|
801
|
+
};
|
|
802
|
+
// get the instance from the map and append it to the container
|
|
803
|
+
instances.get(state['instance']).forEach((component) => {
|
|
804
|
+
container.element.appendChild(component);
|
|
805
|
+
component[instanceContainer] = componentInstanceContainer;
|
|
657
806
|
});
|
|
658
|
-
|
|
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,
|
|
807
|
+
this.setupLayoutReceiveEvents(container, state);
|
|
666
808
|
};
|
|
667
|
-
|
|
668
|
-
instances.get(state['instance']).forEach((component) => {
|
|
669
|
-
container.element.appendChild(component);
|
|
670
|
-
component[instanceContainer] = componentInstanceContainer;
|
|
671
|
-
});
|
|
672
|
-
this.setupLayoutReceiveEvents(container, state);
|
|
673
|
-
};
|
|
809
|
+
}
|
|
674
810
|
})();
|
|
675
811
|
this.layout.registerComponentFactoryFunction(reg, registrationFunction);
|
|
676
812
|
return reg;
|
|
@@ -747,8 +883,9 @@ export class FoundationLayout extends FoundationElement {
|
|
|
747
883
|
* @internal
|
|
748
884
|
*/
|
|
749
885
|
loadGLConfigAndSetup(config) {
|
|
886
|
+
const _glVisualConfig = glVisualConfig();
|
|
750
887
|
this.hasFirstLoaded = true;
|
|
751
|
-
this.layout.loadLayout(Object.assign(Object.assign(Object.assign({}, config),
|
|
888
|
+
this.layout.loadLayout(Object.assign(Object.assign(Object.assign({}, config), _glVisualConfig), { dimensions: Object.assign(Object.assign({}, _glVisualConfig.dimensions), this.dimensionsConfig) }));
|
|
752
889
|
this.attatchResizeEvents();
|
|
753
890
|
}
|
|
754
891
|
/**
|
|
@@ -869,6 +1006,9 @@ __decorate([
|
|
|
869
1006
|
__decorate([
|
|
870
1007
|
attr({ attribute: 'popout-config' })
|
|
871
1008
|
], FoundationLayout.prototype, "popoutConfig", void 0);
|
|
1009
|
+
__decorate([
|
|
1010
|
+
observable
|
|
1011
|
+
], FoundationLayout.prototype, "customTokensMap", void 0);
|
|
872
1012
|
const loadingTemplate = html `
|
|
873
1013
|
<div class="html-spinner"></div>
|
|
874
1014
|
`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { getLayoutHeaderHeight } from '../utils';
|
|
2
|
+
export const glVisualConfig = () => ({
|
|
2
3
|
dimensions: {
|
|
3
|
-
headerHeight:
|
|
4
|
+
headerHeight: parseInt(getLayoutHeaderHeight().replace('px', '')),
|
|
4
5
|
borderWidth: 12,
|
|
5
6
|
},
|
|
6
7
|
header: {
|
|
@@ -8,7 +9,7 @@ export const glVisualConfig = {
|
|
|
8
9
|
minimise: 'minimise',
|
|
9
10
|
popout: false,
|
|
10
11
|
},
|
|
11
|
-
};
|
|
12
|
+
});
|
|
12
13
|
const renameSVG = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSI0NTAiIGhlaWdodD0iNDUwIiB2aWV3Qm94PSIwIDAgNDUwIDQ1MCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGcgdHJhbnNmb3JtPSJtYXRyaXgoMTguNjEgMCAwIDE4LjYxIDIyMi44MSAyMjIuODEpIiBpZD0iT3V0bGluZSIgID48cGF0aCBzdHlsZT0ic3Ryb2tlOiBub25lOyBzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IG5vbmU7IHN0cm9rZS1saW5lY2FwOiBidXR0OyBzdHJva2UtZGFzaG9mZnNldDogMDsgc3Ryb2tlLWxpbmVqb2luOiBtaXRlcjsgc3Ryb2tlLW1pdGVybGltaXQ6IDQ7IGZpbGw6IHJnYigxMTEsMTI2LDEzNSk7IGZpbGwtcnVsZTogbm9uemVybzsgb3BhY2l0eTogMTsiICB0cmFuc2Zvcm09IiB0cmFuc2xhdGUoLTExLjk2LCAtMTIuMDQpIiBkPSJNIDIyLjg1MyAxLjE0OCBhIDMuNjI2IDMuNjI2IDAgMCAwIC01LjEyNCAwIEwgMS40NjUgMTcuNDEyIEEgNC45NjggNC45NjggMCAwIDAgMCAyMC45NDcgViAyMyBhIDEgMSAwIDAgMCAxIDEgSCAzLjA1MyBhIDQuOTY2IDQuOTY2IDAgMCAwIDMuNTM1IC0xLjQ2NCBMIDIyLjg1MyA2LjI3MSBBIDMuNjI2IDMuNjI2IDAgMCAwIDIyLjg1MyAxLjE0OCBaIE0gNS4xNzQgMjEuMTIyIEEgMy4wMjIgMy4wMjIgMCAwIDEgMy4wNTMgMjIgSCAyIFYgMjAuOTQ3IGEgMi45OCAyLjk4IDAgMCAxIDAuODc5IC0yLjEyMSBMIDE1LjIyMiA2LjQ4MyBsIDIuMyAyLjMgWiBNIDIxLjQzOCA0Ljg1NyBMIDE4LjkzMiA3LjM2NCBsIC0yLjMgLTIuMjk1IGwgMi41MDcgLTIuNTA3IGEgMS42MjMgMS42MjMgMCAxIDEgMi4yOTUgMi4zIFoiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgLz48L2c+PC9zdmc+Cg==';
|
|
13
14
|
const maximiseSVG = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj48IS0tISBGb250IEF3ZXNvbWUgUHJvIDYuMi4wIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlIChDb21tZXJjaWFsIExpY2Vuc2UpIENvcHlyaWdodCAyMDIyIEZvbnRpY29ucywgSW5jLiAtLT48cGF0aCBkPSJNMTQ0IDMyaC0xMjhDNy4xNTYgMzIgMCAzOS4xNiAwIDQ4djEyOEMwIDE4NC44IDcuMTU2IDE5MiAxNiAxOTJTMzIgMTg0LjggMzIgMTc2VjY0aDExMkMxNTIuOCA2NCAxNjAgNTYuODQgMTYwIDQ4UzE1Mi44IDMyIDE0NCAzMnpNMTQ0IDQ0OEgzMnYtMTEyQzMyIDMyNy4yIDI0Ljg0IDMyMCAxNiAzMjBTMCAzMjcuMiAwIDMzNnYxMjhDMCA0NzIuOCA3LjE1NiA0ODAgMTYgNDgwaDEyOEMxNTIuOCA0ODAgMTYwIDQ3Mi44IDE2MCA0NjRTMTUyLjggNDQ4IDE0NCA0NDh6TTQzMiAzMjBjLTguODQ0IDAtMTYgNy4xNTYtMTYgMTZWNDQ4aC0xMTJjLTguODQ0IDAtMTYgNy4xNTYtMTYgMTZzNy4xNTYgMTYgMTYgMTZoMTI4YzguODQ0IDAgMTYtNy4xNTYgMTYtMTZ2LTEyOEM0NDggMzI3LjIgNDQwLjggMzIwIDQzMiAzMjB6TTQzMiAzMmgtMTI4QzI5NS4yIDMyIDI4OCAzOS4xNiAyODggNDhTMjk1LjIgNjQgMzA0IDY0SDQxNnYxMTJDNDE2IDE4NC44IDQyMy4yIDE5MiA0MzIgMTkyUzQ0OCAxODQuOCA0NDggMTc2di0xMjhDNDQ4IDM5LjE2IDQ0MC44IDMyIDQzMiAzMnoiIGZpbGw9IiM4NzliYTYiLz48L3N2Zz4=';
|
|
14
15
|
const minimiseSVG = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48IS0tISBGb250IEF3ZXNvbWUgUHJvIDYuMi4wIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlIChDb21tZXJjaWFsIExpY2Vuc2UpIENvcHlyaWdodCAyMDIyIEZvbnRpY29ucywgSW5jLiAtLT48cGF0aCBkPSJNMCA0NjRDMCA0NTUuMiA3LjE2NCA0NDggMTYgNDQ4SDQ5NkM1MDQuOCA0NDggNTEyIDQ1NS4yIDUxMiA0NjRDNTEyIDQ3Mi44IDUwNC44IDQ4MCA0OTYgNDgwSDE2QzcuMTY0IDQ4MCAwIDQ3Mi44IDAgNDY0eiIgZmlsbD0iIzg3OWJhNiIvPjwvc3ZnPg==';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// NOTE: This file is stylelint ignored as the css class names that golden layout use go against the rules
|
|
2
2
|
import { css } from '@microsoft/fast-element';
|
|
3
|
+
import { DEFAULT_TAB_HEIGHT } from '../utils';
|
|
3
4
|
import { LAYOUT_ICONS } from './constants';
|
|
4
5
|
const containerStyles = `
|
|
5
6
|
:host {
|
|
@@ -94,6 +95,10 @@ export const layoutStyles = css `
|
|
|
94
95
|
${containerStyles}
|
|
95
96
|
${loadingSpinnerStyles}
|
|
96
97
|
|
|
98
|
+
:host {
|
|
99
|
+
--foundation-tab-height: calc((( (var(--base-height-multiplier) + var(--density)) * var(--design-unit)) - 4) * 1px);
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
.lm_goldenlayout {
|
|
98
103
|
border-radius: calc(var(--control-corner-radius) * 1.5px);
|
|
99
104
|
}
|
|
@@ -153,7 +158,7 @@ export const layoutStyles = css `
|
|
|
153
158
|
font-family: inherit;
|
|
154
159
|
font-size: 13px;
|
|
155
160
|
font-weight: 400;
|
|
156
|
-
height:
|
|
161
|
+
height: calc(var(--foundation-tab-height, ${DEFAULT_TAB_HEIGHT}) - 8px);
|
|
157
162
|
margin-right: 2px;
|
|
158
163
|
margin-top: 3px;
|
|
159
164
|
padding: 2px 16px;
|
|
@@ -169,11 +174,11 @@ export const layoutStyles = css `
|
|
|
169
174
|
display: flex;
|
|
170
175
|
}
|
|
171
176
|
.lm_header .lm_controls > * {
|
|
172
|
-
width:
|
|
173
|
-
height:
|
|
177
|
+
width: calc(var(--foundation-tab-height, ${DEFAULT_TAB_HEIGHT}) - 6px);
|
|
178
|
+
height: calc(var(--foundation-tab-height, ${DEFAULT_TAB_HEIGHT}) - 6px);
|
|
174
179
|
background-repeat: no-repeat;
|
|
175
180
|
background-position: center;
|
|
176
|
-
background-size:
|
|
181
|
+
background-size: calc(var(--foundation-tab-height, ${DEFAULT_TAB_HEIGHT}) * 0.533333);
|
|
177
182
|
background-color: color-mix(in srgb, var(--neutral-fill-strong-rest), transparent 90%);
|
|
178
183
|
border-radius: calc(var(--control-corner-radius) * 1.5px);
|
|
179
184
|
margin-right: 4px;
|
|
@@ -196,7 +201,7 @@ export const layoutStyles = css `
|
|
|
196
201
|
}
|
|
197
202
|
.lm_header .lm_tab .lm_close_tab {
|
|
198
203
|
background-image: url('${LAYOUT_ICONS.closeSVG}');
|
|
199
|
-
|
|
204
|
+
background-size: calc(var(--foundation-tab-height, ${DEFAULT_TAB_HEIGHT}) * 0.2777);
|
|
200
205
|
background-repeat: no-repeat;
|
|
201
206
|
margin-left: 12px;
|
|
202
207
|
position: relative;
|
|
@@ -219,7 +224,7 @@ export const layoutStyles = css `
|
|
|
219
224
|
background-image: url('${LAYOUT_ICONS.tabDropdownSVG}');
|
|
220
225
|
}
|
|
221
226
|
.lm_header .lm_tabdropdown_list {
|
|
222
|
-
top:
|
|
227
|
+
top: var(--foundation-tab-height, ${DEFAULT_TAB_HEIGHT});
|
|
223
228
|
right: 108px;
|
|
224
229
|
background-color: var(--neutral-layer-3);
|
|
225
230
|
border: 1px solid;
|
|
@@ -233,7 +238,7 @@ export const layoutStyles = css `
|
|
|
233
238
|
white-space: nowrap;
|
|
234
239
|
background-color: transparent;
|
|
235
240
|
color: var(--neutral-foreground-rest);
|
|
236
|
-
height:
|
|
241
|
+
height: var(--foundation-tab-height, ${DEFAULT_TAB_HEIGHT});
|
|
237
242
|
border-radius: 0;
|
|
238
243
|
overflow: visible;
|
|
239
244
|
text-overflow: normal;
|
|
@@ -351,10 +356,10 @@ export const layoutStyles = css `
|
|
|
351
356
|
padding-left: 10px;
|
|
352
357
|
}
|
|
353
358
|
.lm_header .lm_tab .lm_close_tab {
|
|
354
|
-
width:
|
|
355
|
-
height:
|
|
359
|
+
width: calc(var(--foundation-tab-height, ${DEFAULT_TAB_HEIGHT}) * 0.3888);
|
|
360
|
+
height: calc(var(--foundation-tab-height, ${DEFAULT_TAB_HEIGHT}) * 0.3888);
|
|
361
|
+
top: calc(var(--foundation-tab-height, ${DEFAULT_TAB_HEIGHT}) * 0.3055);
|
|
356
362
|
position: absolute;
|
|
357
|
-
top: 11px;
|
|
358
363
|
right: 0;
|
|
359
364
|
text-align: center;
|
|
360
365
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-magic-numbers */
|
|
2
|
+
import { designTokensMap } from '@genesislcap/foundation-ui';
|
|
1
3
|
/**
|
|
2
4
|
* Used to key what type of LayoutComponent an object is
|
|
3
5
|
* @internal
|
|
@@ -33,3 +35,12 @@ export const LAYOUT_POPOUT_CONTROL_KEY = 'f-layout-key';
|
|
|
33
35
|
* @beta
|
|
34
36
|
*/
|
|
35
37
|
export const LAYOUT_POPOUT_CONTAINER_CLASS = 'f-layout-popout';
|
|
38
|
+
export const DEFAULT_TAB_HEIGHT = '36px';
|
|
39
|
+
// Reads the calc((((var(--base-height-multiplier) + var(--density)) * var(--design-unit)) - 4) * 1px)
|
|
40
|
+
export const getLayoutHeaderHeight = () => {
|
|
41
|
+
const tokens = designTokensMap();
|
|
42
|
+
if (!tokens.value)
|
|
43
|
+
return DEFAULT_TAB_HEIGHT;
|
|
44
|
+
const { style, space } = tokens.value.design_tokens;
|
|
45
|
+
return `${(style.baseHeightMultiplier.$value + style.density.$value) * space.designUnit.$value - 4}px`;
|
|
46
|
+
};
|
|
@@ -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
|
+
* @throws {Error} If a factory with the same key is already registered.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // React example
|
|
20
|
+
* import { registerFactory } from '@genesislcap/foundation-layout';
|
|
21
|
+
* import { reactFactory } from './utils/react-layout-factory';
|
|
22
|
+
* import { MyComponent } from './components/MyComponent';
|
|
23
|
+
*
|
|
24
|
+
* registerFactory('my-component', reactFactory(MyComponent, { someProp: 'value' }));
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Then in your JSX:
|
|
28
|
+
* ```tsx
|
|
29
|
+
* <rapid-layout-item
|
|
30
|
+
* registration="my-item"
|
|
31
|
+
* title="My Component"
|
|
32
|
+
* />
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export function registerFactory(key, factory) {
|
|
38
|
+
if (factoryRegistry.has(key)) {
|
|
39
|
+
throw new Error(`Factory with key "${key}" is already registered. Cannot register duplicate factory.`);
|
|
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
|
+
}
|