@tangible/ui 0.0.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.
- package/README.md +100 -0
- package/components/Accordion/Accordion.d.ts +22 -0
- package/components/Accordion/Accordion.js +192 -0
- package/components/Accordion/AccordionContext.d.ts +5 -0
- package/components/Accordion/AccordionContext.js +23 -0
- package/components/Accordion/index.d.ts +2 -0
- package/components/Accordion/index.js +1 -0
- package/components/Accordion/types.d.ts +61 -0
- package/components/Accordion/types.js +1 -0
- package/components/Avatar/Avatar.d.ts +11 -0
- package/components/Avatar/Avatar.js +67 -0
- package/components/Avatar/AvatarGroup.d.ts +11 -0
- package/components/Avatar/AvatarGroup.js +45 -0
- package/components/Avatar/index.d.ts +9 -0
- package/components/Avatar/index.js +7 -0
- package/components/Avatar/types.d.ts +44 -0
- package/components/Avatar/types.js +12 -0
- package/components/Button/Button.d.ts +4 -0
- package/components/Button/Button.js +33 -0
- package/components/Button/index.d.ts +2 -0
- package/components/Button/index.js +1 -0
- package/components/Button/types.d.ts +127 -0
- package/components/Button/types.js +1 -0
- package/components/Card/Card.d.ts +29 -0
- package/components/Card/Card.js +47 -0
- package/components/Card/index.d.ts +2 -0
- package/components/Card/index.js +1 -0
- package/components/Chip/Chip.d.ts +24 -0
- package/components/Chip/Chip.js +37 -0
- package/components/Chip/index.d.ts +2 -0
- package/components/Chip/index.js +1 -0
- package/components/Chips/Chips.d.ts +31 -0
- package/components/Chips/Chips.js +21 -0
- package/components/Chips/index.d.ts +2 -0
- package/components/Chips/index.js +1 -0
- package/components/ContentIndicator/ContentIndicator.d.ts +2 -0
- package/components/ContentIndicator/ContentIndicator.js +21 -0
- package/components/ContentIndicator/index.d.ts +2 -0
- package/components/ContentIndicator/index.js +1 -0
- package/components/ContentIndicator/types.d.ts +57 -0
- package/components/ContentIndicator/types.js +1 -0
- package/components/Dropdown/Dropdown.d.ts +31 -0
- package/components/Dropdown/Dropdown.js +219 -0
- package/components/Dropdown/DropdownContext.d.ts +3 -0
- package/components/Dropdown/DropdownContext.js +9 -0
- package/components/Dropdown/index.d.ts +2 -0
- package/components/Dropdown/index.js +1 -0
- package/components/Dropdown/types.d.ts +102 -0
- package/components/Dropdown/types.js +8 -0
- package/components/Icon/Icon.d.ts +22 -0
- package/components/Icon/Icon.js +24 -0
- package/components/Icon/index.d.ts +2 -0
- package/components/Icon/index.js +1 -0
- package/components/IconButton/IconButton.d.ts +2 -0
- package/components/IconButton/IconButton.js +50 -0
- package/components/IconButton/index.d.ts +2 -0
- package/components/IconButton/index.js +1 -0
- package/components/IconButton/types.d.ts +79 -0
- package/components/IconButton/types.js +1 -0
- package/components/Modal/Modal.d.ts +52 -0
- package/components/Modal/Modal.js +133 -0
- package/components/Modal/context.d.ts +6 -0
- package/components/Modal/context.js +9 -0
- package/components/Modal/index.d.ts +2 -0
- package/components/Modal/index.js +1 -0
- package/components/Notice/Notice.d.ts +93 -0
- package/components/Notice/Notice.js +144 -0
- package/components/Notice/index.d.ts +2 -0
- package/components/Notice/index.js +1 -0
- package/components/OverlapStack/OverlapStack.d.ts +44 -0
- package/components/OverlapStack/OverlapStack.js +41 -0
- package/components/OverlapStack/index.d.ts +2 -0
- package/components/OverlapStack/index.js +1 -0
- package/components/Pager/Pager.d.ts +26 -0
- package/components/Pager/Pager.js +151 -0
- package/components/Pager/index.d.ts +2 -0
- package/components/Pager/index.js +1 -0
- package/components/Progress/Progress.d.ts +2 -0
- package/components/Progress/Progress.js +100 -0
- package/components/Progress/index.d.ts +4 -0
- package/components/Progress/index.js +2 -0
- package/components/Progress/types.d.ts +251 -0
- package/components/Progress/types.js +1 -0
- package/components/Progress/useProgressSegments.d.ts +40 -0
- package/components/Progress/useProgressSegments.js +42 -0
- package/components/Rating/Rating.d.ts +32 -0
- package/components/Rating/Rating.js +74 -0
- package/components/Rating/index.d.ts +2 -0
- package/components/Rating/index.js +1 -0
- package/components/SegmentedControl/SegmentedControl.d.ts +10 -0
- package/components/SegmentedControl/SegmentedControl.js +183 -0
- package/components/SegmentedControl/SegmentedControlContext.d.ts +3 -0
- package/components/SegmentedControl/SegmentedControlContext.js +9 -0
- package/components/SegmentedControl/index.d.ts +2 -0
- package/components/SegmentedControl/index.js +1 -0
- package/components/SegmentedControl/types.d.ts +63 -0
- package/components/SegmentedControl/types.js +1 -0
- package/components/Sidebar/Sidebar.d.ts +17 -0
- package/components/Sidebar/Sidebar.js +107 -0
- package/components/Sidebar/index.d.ts +2 -0
- package/components/Sidebar/index.js +1 -0
- package/components/Sidebar/types.d.ts +65 -0
- package/components/Sidebar/types.js +4 -0
- package/components/StepIndicator/StepIndicator.d.ts +2 -0
- package/components/StepIndicator/StepIndicator.js +64 -0
- package/components/StepIndicator/index.d.ts +2 -0
- package/components/StepIndicator/index.js +1 -0
- package/components/StepIndicator/types.d.ts +68 -0
- package/components/StepIndicator/types.js +1 -0
- package/components/StepList/StepList.d.ts +12 -0
- package/components/StepList/StepList.js +59 -0
- package/components/StepList/StepListContext.d.ts +3 -0
- package/components/StepList/StepListContext.js +9 -0
- package/components/StepList/index.d.ts +2 -0
- package/components/StepList/index.js +1 -0
- package/components/StepList/types.d.ts +91 -0
- package/components/StepList/types.js +4 -0
- package/components/Table/BulkActionsBar.d.ts +12 -0
- package/components/Table/BulkActionsBar.js +9 -0
- package/components/Table/DataTable.d.ts +35 -0
- package/components/Table/DataTable.js +184 -0
- package/components/Table/Pagination.d.ts +13 -0
- package/components/Table/Pagination.js +13 -0
- package/components/Table/index.d.ts +2 -0
- package/components/Table/index.js +1 -0
- package/components/Tabs/Tabs.d.ts +23 -0
- package/components/Tabs/Tabs.js +309 -0
- package/components/Tabs/TabsContext.d.ts +3 -0
- package/components/Tabs/TabsContext.js +12 -0
- package/components/Tabs/index.d.ts +2 -0
- package/components/Tabs/index.js +1 -0
- package/components/Tabs/types.d.ts +75 -0
- package/components/Tabs/types.js +1 -0
- package/components/Toolbar/Toolbar.d.ts +18 -0
- package/components/Toolbar/Toolbar.js +241 -0
- package/components/Toolbar/index.d.ts +2 -0
- package/components/Toolbar/index.js +1 -0
- package/components/Toolbar/types.d.ts +28 -0
- package/components/Toolbar/types.js +1 -0
- package/components/Tooltip/Tooltip.d.ts +15 -0
- package/components/Tooltip/Tooltip.js +166 -0
- package/components/Tooltip/TooltipContext.d.ts +15 -0
- package/components/Tooltip/TooltipContext.js +25 -0
- package/components/Tooltip/index.d.ts +2 -0
- package/components/Tooltip/index.js +1 -0
- package/components/Tooltip/types.d.ts +85 -0
- package/components/Tooltip/types.js +8 -0
- package/components/index.d.ts +52 -0
- package/components/index.js +26 -0
- package/constants.d.ts +16 -0
- package/constants.js +16 -0
- package/icons/cred/index.d.ts +31 -0
- package/icons/cred/index.js +136 -0
- package/icons/icons.svg +155 -0
- package/icons/lms/index.d.ts +21 -0
- package/icons/lms/index.js +81 -0
- package/icons/manifest.json +1226 -0
- package/icons/player/index.d.ts +55 -0
- package/icons/player/index.js +268 -0
- package/icons/reaction/index.d.ts +79 -0
- package/icons/reaction/index.js +400 -0
- package/icons/registry.d.ts +316 -0
- package/icons/registry.js +163 -0
- package/icons/system/index.d.ts +155 -0
- package/icons/system/index.js +818 -0
- package/package.json +121 -0
- package/styles/all.css +1 -0
- package/styles/all.expanded.css +4137 -0
- package/styles/all.expanded.unlayered.css +4137 -0
- package/styles/all.unlayered.css +1 -0
- package/styles/components/_bundle.scss +51 -0
- package/styles/components/index.scss +1 -0
- package/styles/components/input/index.scss +248 -0
- package/styles/index.scss +71 -0
- package/styles/system/_constants.scss +12 -0
- package/styles/system/_motion.scss +47 -0
- package/styles/system/_palette-fns.scss +10 -0
- package/styles/system/_palettes.scss +80 -0
- package/styles/system/_tokens.scss +249 -0
- package/styles/system/index.scss +4 -0
- package/styles/utilities/_index.scss +373 -0
- package/tui-manifest.json +1858 -0
- package/types/index.d.ts +2 -0
- package/types/index.js +1 -0
- package/types/index.ts +2 -0
- package/types/sizes.d.ts +17 -0
- package/types/sizes.js +10 -0
- package/types/sizes.ts +21 -0
- package/types/svg.d.ts +5 -0
- package/types/themes.d.ts +14 -0
- package/types/themes.js +9 -0
- package/types/themes.ts +17 -0
- package/utils/color/contrast.d.ts +33 -0
- package/utils/color/contrast.js +88 -0
- package/utils/color-scheme.d.ts +25 -0
- package/utils/color-scheme.js +55 -0
- package/utils/compose-refs.d.ts +17 -0
- package/utils/compose-refs.js +38 -0
- package/utils/cx.d.ts +12 -0
- package/utils/cx.js +14 -0
- package/utils/focus-trap.d.ts +40 -0
- package/utils/focus-trap.js +93 -0
- package/utils/index.d.ts +10 -0
- package/utils/index.js +16 -0
- package/utils/math.d.ts +4 -0
- package/utils/math.js +19 -0
- package/utils/merge-props.d.ts +25 -0
- package/utils/merge-props.js +60 -0
- package/utils/polymorphic.d.ts +28 -0
- package/utils/polymorphic.js +44 -0
- package/utils/portal.d.ts +11 -0
- package/utils/portal.js +105 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merges two sets of props, composing event handlers so both fire.
|
|
3
|
+
* Useful for extending component props without clobbering existing values.
|
|
4
|
+
*
|
|
5
|
+
* Merge rules:
|
|
6
|
+
* - Event handlers (on*): Both called in sequence (slotProps first, then overrideProps)
|
|
7
|
+
* - className: Concatenated with a space
|
|
8
|
+
* - style: Shallow merged (overrideProps wins conflicts)
|
|
9
|
+
* - Other props: overrideProps takes precedence
|
|
10
|
+
*
|
|
11
|
+
* Note: For asChild patterns where parent props must not be overridden
|
|
12
|
+
* (e.g., data-* attributes for registration), spread them after the merge:
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // Event handler composition
|
|
16
|
+
* mergeProps({ onClick: logClick }, { onClick: doAction })
|
|
17
|
+
* // Result: { onClick: [calls logClick, then doAction] }
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Ensuring parent attribute wins
|
|
21
|
+
* cloneElement(child, { ...child.props, 'data-toolbar-item': '' })
|
|
22
|
+
*/
|
|
23
|
+
export function mergeProps(slotProps, overrideProps) {
|
|
24
|
+
const result = { ...slotProps };
|
|
25
|
+
for (const key of Object.keys(overrideProps)) {
|
|
26
|
+
const slotValue = slotProps[key];
|
|
27
|
+
const overrideValue = overrideProps[key];
|
|
28
|
+
// Compose event handlers
|
|
29
|
+
if (key.startsWith('on') &&
|
|
30
|
+
typeof slotValue === 'function' &&
|
|
31
|
+
typeof overrideValue === 'function') {
|
|
32
|
+
result[key] = composeEventHandlers(slotValue, overrideValue);
|
|
33
|
+
}
|
|
34
|
+
// Concatenate classNames
|
|
35
|
+
else if (key === 'className') {
|
|
36
|
+
result[key] = [slotValue, overrideValue].filter(Boolean).join(' ');
|
|
37
|
+
}
|
|
38
|
+
// Merge style objects (overrideProps wins conflicts)
|
|
39
|
+
else if (key === 'style' &&
|
|
40
|
+
typeof slotValue === 'object' &&
|
|
41
|
+
typeof overrideValue === 'object') {
|
|
42
|
+
result[key] = { ...slotValue, ...overrideValue };
|
|
43
|
+
}
|
|
44
|
+
// overrideProps takes precedence for everything else
|
|
45
|
+
else if (overrideValue !== undefined) {
|
|
46
|
+
result[key] = overrideValue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Composes two event handlers into one that calls both.
|
|
53
|
+
* The slot handler runs first, then the override handler.
|
|
54
|
+
*/
|
|
55
|
+
function composeEventHandlers(slotHandler, overrideHandler) {
|
|
56
|
+
return (...args) => {
|
|
57
|
+
slotHandler(...args);
|
|
58
|
+
overrideHandler(...args);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if props indicate the component should render as an anchor.
|
|
3
|
+
*/
|
|
4
|
+
export declare function isAnchor<T extends {
|
|
5
|
+
href?: string;
|
|
6
|
+
}>(props: T): props is T & {
|
|
7
|
+
href: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Get safe rel attribute for anchor elements.
|
|
11
|
+
* Adds noopener noreferrer for target="_blank" to prevent security issues.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getSafeRel(target: React.HTMLAttributeAnchorTarget | undefined, rel: string | undefined): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Common anchor props for disabled state.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getDisabledAnchorProps(isDisabled: boolean, href: string | undefined, tabIndex: number | undefined): {
|
|
18
|
+
readonly href: string | undefined;
|
|
19
|
+
readonly tabIndex: number | undefined;
|
|
20
|
+
readonly 'aria-disabled': true | undefined;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Common button props for disabled state.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getDisabledButtonProps(isDisabled: boolean): {
|
|
26
|
+
readonly disabled: boolean;
|
|
27
|
+
readonly 'aria-disabled': true | undefined;
|
|
28
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { cx } from './cx.js';
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Polymorphic Button/Anchor Utilities
|
|
4
|
+
// =============================================================================
|
|
5
|
+
//
|
|
6
|
+
// Shared logic for components that render as <button> or <a> based on href.
|
|
7
|
+
// Used by Button and IconButton.
|
|
8
|
+
//
|
|
9
|
+
// =============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Check if props indicate the component should render as an anchor.
|
|
12
|
+
*/
|
|
13
|
+
export function isAnchor(props) {
|
|
14
|
+
return typeof props.href === 'string';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get safe rel attribute for anchor elements.
|
|
18
|
+
* Adds noopener noreferrer for target="_blank" to prevent security issues.
|
|
19
|
+
*/
|
|
20
|
+
export function getSafeRel(target, rel) {
|
|
21
|
+
if (target === '_blank') {
|
|
22
|
+
return cx(rel, 'noopener', 'noreferrer') || undefined;
|
|
23
|
+
}
|
|
24
|
+
return rel;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Common anchor props for disabled state.
|
|
28
|
+
*/
|
|
29
|
+
export function getDisabledAnchorProps(isDisabled, href, tabIndex) {
|
|
30
|
+
return {
|
|
31
|
+
href: isDisabled ? undefined : href,
|
|
32
|
+
tabIndex: isDisabled ? -1 : tabIndex,
|
|
33
|
+
'aria-disabled': isDisabled || undefined,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Common button props for disabled state.
|
|
38
|
+
*/
|
|
39
|
+
export function getDisabledButtonProps(isDisabled) {
|
|
40
|
+
return {
|
|
41
|
+
disabled: isDisabled,
|
|
42
|
+
'aria-disabled': isDisabled || undefined,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets or creates a portal root inside the first .tui-interface.
|
|
3
|
+
* If no interface wrapper exists, creates a global one at body level.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getPortalRoot(): HTMLElement;
|
|
6
|
+
/**
|
|
7
|
+
* Gets portal root for a specific element (finds its nearest .tui-interface ancestor).
|
|
8
|
+
* Reuses existing portal roots per interface container to prevent DOM accumulation.
|
|
9
|
+
* Always returns a valid portal root inside .tui-interface scope.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getPortalRootFor(element: HTMLElement | null): HTMLElement;
|
package/utils/portal.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Portal Root Utility
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// GUARANTEE: Always returns a node inside `.tui-interface` scope.
|
|
6
|
+
//
|
|
7
|
+
// If no interface wrapper exists, creates one at document.body level.
|
|
8
|
+
// Components using this utility can rely on token inheritance and
|
|
9
|
+
// should NOT add their own `.tui-interface` wrapper.
|
|
10
|
+
//
|
|
11
|
+
// Usage:
|
|
12
|
+
// const root = getPortalRootFor(triggerRef.current);
|
|
13
|
+
// <FloatingPortal root={root}>...</FloatingPortal>
|
|
14
|
+
//
|
|
15
|
+
// =============================================================================
|
|
16
|
+
import { PREFIX, INTERFACE_CLASS } from '../constants.js';
|
|
17
|
+
const PORTAL_ID = `${PREFIX}-portal-root`;
|
|
18
|
+
// Track which interface elements have portal roots (prevents accumulation)
|
|
19
|
+
const portalRoots = new WeakMap();
|
|
20
|
+
/**
|
|
21
|
+
* Creates a portal root element with proper styling.
|
|
22
|
+
*/
|
|
23
|
+
function createPortalRoot(id) {
|
|
24
|
+
const root = document.createElement('div');
|
|
25
|
+
root.id = id;
|
|
26
|
+
// Fixed positioning avoids issues with ancestor transforms/positioning
|
|
27
|
+
// pointer-events: none allows clicks to pass through to content below
|
|
28
|
+
// High z-index ensures portaled content (modals, tooltips) appears above page content
|
|
29
|
+
root.style.cssText =
|
|
30
|
+
'position: fixed; inset: 0; pointer-events: none; z-index: var(--tui-layer-portal, 9999);';
|
|
31
|
+
return root;
|
|
32
|
+
}
|
|
33
|
+
const GLOBAL_INTERFACE_ID = `${PREFIX}-global-interface`;
|
|
34
|
+
/**
|
|
35
|
+
* Gets or creates a global .tui-interface wrapper at body level.
|
|
36
|
+
* Used as fallback when no interface wrapper exists in the DOM.
|
|
37
|
+
*/
|
|
38
|
+
function getOrCreateGlobalInterface() {
|
|
39
|
+
let globalInterface = document.getElementById(GLOBAL_INTERFACE_ID);
|
|
40
|
+
if (!globalInterface) {
|
|
41
|
+
globalInterface = document.createElement('div');
|
|
42
|
+
globalInterface.id = GLOBAL_INTERFACE_ID;
|
|
43
|
+
globalInterface.className = INTERFACE_CLASS;
|
|
44
|
+
document.body.appendChild(globalInterface);
|
|
45
|
+
}
|
|
46
|
+
return globalInterface;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Gets or creates a portal root inside the first .tui-interface.
|
|
50
|
+
* If no interface wrapper exists, creates a global one at body level.
|
|
51
|
+
*/
|
|
52
|
+
export function getPortalRoot() {
|
|
53
|
+
// Check if we already have a global portal root
|
|
54
|
+
let portalRoot = document.getElementById(PORTAL_ID);
|
|
55
|
+
if (portalRoot)
|
|
56
|
+
return portalRoot;
|
|
57
|
+
// Find the interface wrapper, or create global fallback
|
|
58
|
+
let interfaceEl = document.querySelector(`.${INTERFACE_CLASS}`);
|
|
59
|
+
if (!interfaceEl) {
|
|
60
|
+
interfaceEl = getOrCreateGlobalInterface();
|
|
61
|
+
}
|
|
62
|
+
// Create portal root inside the interface
|
|
63
|
+
portalRoot = createPortalRoot(PORTAL_ID);
|
|
64
|
+
interfaceEl.appendChild(portalRoot);
|
|
65
|
+
portalRoots.set(interfaceEl, portalRoot);
|
|
66
|
+
return portalRoot;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Gets portal root for a specific element (finds its nearest .tui-interface ancestor).
|
|
70
|
+
* Reuses existing portal roots per interface container to prevent DOM accumulation.
|
|
71
|
+
* Always returns a valid portal root inside .tui-interface scope.
|
|
72
|
+
*/
|
|
73
|
+
export function getPortalRootFor(element) {
|
|
74
|
+
if (!element)
|
|
75
|
+
return getPortalRoot();
|
|
76
|
+
// Find nearest interface ancestor
|
|
77
|
+
const interfaceEl = element.closest(`.${INTERFACE_CLASS}`);
|
|
78
|
+
if (!interfaceEl)
|
|
79
|
+
return getPortalRoot();
|
|
80
|
+
// Check WeakMap first (most common case after first render)
|
|
81
|
+
const cached = portalRoots.get(interfaceEl);
|
|
82
|
+
if (cached && interfaceEl.contains(cached))
|
|
83
|
+
return cached;
|
|
84
|
+
// Check if global portal root is inside this interface
|
|
85
|
+
const globalRoot = document.getElementById(PORTAL_ID);
|
|
86
|
+
if (globalRoot && interfaceEl.contains(globalRoot)) {
|
|
87
|
+
portalRoots.set(interfaceEl, globalRoot);
|
|
88
|
+
return globalRoot;
|
|
89
|
+
}
|
|
90
|
+
// Check for any existing portal root in this interface (handles SSR/hydration)
|
|
91
|
+
const existingRoot = interfaceEl.querySelector(`[id^="${PORTAL_ID}"]`);
|
|
92
|
+
if (existingRoot) {
|
|
93
|
+
portalRoots.set(interfaceEl, existingRoot);
|
|
94
|
+
return existingRoot;
|
|
95
|
+
}
|
|
96
|
+
// Create a scoped portal root for this interface
|
|
97
|
+
// Use a deterministic ID based on interface position to aid debugging
|
|
98
|
+
const allInterfaces = document.querySelectorAll(`.${INTERFACE_CLASS}`);
|
|
99
|
+
const index = Array.from(allInterfaces).indexOf(interfaceEl);
|
|
100
|
+
const scopedId = index === 0 ? PORTAL_ID : `${PORTAL_ID}-${index}`;
|
|
101
|
+
const portalRoot = createPortalRoot(scopedId);
|
|
102
|
+
interfaceEl.appendChild(portalRoot);
|
|
103
|
+
portalRoots.set(interfaceEl, portalRoot);
|
|
104
|
+
return portalRoot;
|
|
105
|
+
}
|