@stackable-labs/sdk-extension-host 1.29.0 → 1.31.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/ExtensionSlot.d.ts +1 -8
- package/dist/ExtensionSlot.js +3 -57
- package/dist/ExtensionSlot.js.map +1 -1
- package/dist/createNodeRenderer.d.ts +32 -0
- package/dist/createNodeRenderer.js +76 -0
- package/dist/createNodeRenderer.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/ExtensionSlot.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* and renders them using the host component registry.
|
|
7
7
|
*/
|
|
8
8
|
import React from 'react';
|
|
9
|
+
import { type SerializedNode } from './createNodeRenderer';
|
|
9
10
|
/** Filters serialized node attributes against the UI_TAG_ATTRIBUTES allowlist. */
|
|
10
11
|
export declare const filterNodeAttrs: (tag: string, attrs: Record<string, string> | undefined) => {
|
|
11
12
|
filtered: Record<string, unknown>;
|
|
@@ -13,14 +14,6 @@ export declare const filterNodeAttrs: (tag: string, attrs: Record<string, string
|
|
|
13
14
|
};
|
|
14
15
|
/** Validates parent-child nesting rules. Returns invalid child tags (warn-only, never blocks). */
|
|
15
16
|
export declare const validateNesting: (parentTag: string, children: SerializedNode[] | undefined) => string[];
|
|
16
|
-
interface SerializedNode {
|
|
17
|
-
type: 'element' | 'text';
|
|
18
|
-
tag?: string;
|
|
19
|
-
attrs?: Record<string, string>;
|
|
20
|
-
actionId?: string;
|
|
21
|
-
children?: SerializedNode[];
|
|
22
|
-
text?: string;
|
|
23
|
-
}
|
|
24
17
|
interface ExtensionSlotProps {
|
|
25
18
|
/** The extension point target (e.g., "slot.header") */
|
|
26
19
|
target: string;
|
package/dist/ExtensionSlot.js
CHANGED
|
@@ -6,13 +6,12 @@
|
|
|
6
6
|
* and renders them using the host component registry.
|
|
7
7
|
*/
|
|
8
8
|
'use client';
|
|
9
|
-
import { createElement as _createElement } from "react";
|
|
10
9
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
-
import React, { useEffect, useState,
|
|
10
|
+
import React, { useEffect, useState, useMemo, useRef } from 'react';
|
|
12
11
|
import { UI_TAG_ATTRIBUTES, UI_TAG_CHILDREN } from '@stackable-labs/sdk-extension-contracts';
|
|
13
12
|
import { useExtensionProvider } from './ExtensionProvider';
|
|
14
13
|
import { getSandbox } from './SandboxManager';
|
|
15
|
-
import {
|
|
14
|
+
import { createNodeRenderer } from './createNodeRenderer';
|
|
16
15
|
/** Filters serialized node attributes against the UI_TAG_ATTRIBUTES allowlist. */
|
|
17
16
|
export const filterNodeAttrs = (tag, attrs) => {
|
|
18
17
|
const filtered = {};
|
|
@@ -64,60 +63,7 @@ export const ExtensionSlot = ({ target, context, className, separator, fallback
|
|
|
64
63
|
const matchingExtensions = useMemo(() => extensions.filter((ext) => ext.enabled && ext.manifest.targets.includes(target)),
|
|
65
64
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
66
65
|
[extensions.map((e) => e.id).join(','), target]);
|
|
67
|
-
const renderSerializedNode =
|
|
68
|
-
if (node.type === 'text') {
|
|
69
|
-
return node.text || null;
|
|
70
|
-
}
|
|
71
|
-
if (!node.tag)
|
|
72
|
-
return null;
|
|
73
|
-
// Fragment wrapper from Surface serialization
|
|
74
|
-
if (node.tag === '__fragment') {
|
|
75
|
-
return (_jsx(React.Fragment, { children: node.children?.map((child, i) => renderSerializedNode(child, sourceWindow, i)) }, key));
|
|
76
|
-
}
|
|
77
|
-
if (!isValidTag(node.tag)) {
|
|
78
|
-
console.warn(`[ExtensionHost] Unknown UI tag rejected: <${node.tag}>`);
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
const HostComponent = components[node.tag];
|
|
82
|
-
if (!HostComponent) {
|
|
83
|
-
// Valid tag but not registered in this host's component map
|
|
84
|
-
if (node.tag.startsWith('ui-')) {
|
|
85
|
-
console.warn(`[ExtensionSlot] Unregistered UI tag: <${node.tag}>`);
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
// For non-ui tags, just render children
|
|
89
|
-
return (_jsx(React.Fragment, { children: node.children?.map((child, i) => renderSerializedNode(child, sourceWindow, i)) }, key));
|
|
90
|
-
}
|
|
91
|
-
// Filter attributes against the allowlist for this tag (2.1c + 2.1d)
|
|
92
|
-
const { filtered: props, stripped } = filterNodeAttrs(node.tag, node.attrs);
|
|
93
|
-
if (stripped.length > 0) {
|
|
94
|
-
console.warn(`[ExtensionSlot] Attributes not allowed on <${node.tag}>, stripped: ${stripped.join(', ')}`);
|
|
95
|
-
}
|
|
96
|
-
// Warn about invalid nesting (2.2a + 2.2b — warn-only, never blocks rendering)
|
|
97
|
-
const invalidChildren = validateNesting(node.tag, node.children);
|
|
98
|
-
if (invalidChildren.length > 0) {
|
|
99
|
-
console.warn(`[ExtensionSlot] Invalid children in <${node.tag}>: ${invalidChildren.join(', ')}`);
|
|
100
|
-
}
|
|
101
|
-
// Wire up click handler proxy if the node has an actionId
|
|
102
|
-
if (node.actionId) {
|
|
103
|
-
const actionId = node.actionId;
|
|
104
|
-
props.onClick = () => {
|
|
105
|
-
const postTarget = sourceWindow;
|
|
106
|
-
postTarget?.postMessage({ type: 'action-invoke', surfaceId: target, actionId }, '*');
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
// Wire up onChange handler proxy if the node has a data-onchange-id
|
|
110
|
-
if (props['data-onchange-id']) {
|
|
111
|
-
const onchangeId = props['data-onchange-id'];
|
|
112
|
-
delete props['data-onchange-id'];
|
|
113
|
-
props.onChange = (e) => {
|
|
114
|
-
const postTarget = sourceWindow;
|
|
115
|
-
postTarget?.postMessage({ type: 'action-invoke', surfaceId: target, actionId: onchangeId, value: e.target.value }, '*');
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
const children = node.children?.map((child, i) => renderSerializedNode(child, sourceWindow, i));
|
|
119
|
-
return (_createElement(HostComponent, { ...props, key: key }, children && children.length > 0 ? children : undefined));
|
|
120
|
-
}, [target, components]);
|
|
66
|
+
const renderSerializedNode = useMemo(() => createNodeRenderer(components, { surfaceId: target, logPrefix: '[ExtensionSlot]' }), [target, components]);
|
|
121
67
|
useEffect(() => {
|
|
122
68
|
const matchingIds = matchingExtensions.map((ext) => ext.id).join(',');
|
|
123
69
|
if (previousMatchingIdsRef.current !== matchingIds) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExtensionSlot.js","sourceRoot":"","sources":["../src/ExtensionSlot.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,CAAA
|
|
1
|
+
{"version":3,"file":"ExtensionSlot.js","sourceRoot":"","sources":["../src/ExtensionSlot.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createNodeRenderer — shared serialized DOM rendering utility.
|
|
3
|
+
*
|
|
4
|
+
* Extracts the core node rendering logic used by ExtensionSlot into a
|
|
5
|
+
* reusable function. Also consumed by the Extension Studio preview host.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import type { UITag } from '@stackable-labs/sdk-extension-contracts';
|
|
9
|
+
export interface SerializedNode {
|
|
10
|
+
type: 'element' | 'text';
|
|
11
|
+
actionId?: string;
|
|
12
|
+
tag?: string;
|
|
13
|
+
text?: string;
|
|
14
|
+
attrs?: Record<string, string>;
|
|
15
|
+
children?: SerializedNode[];
|
|
16
|
+
}
|
|
17
|
+
interface NodeRendererOptions {
|
|
18
|
+
/** The surface target ID — used for action proxying via postMessage */
|
|
19
|
+
surfaceId?: string;
|
|
20
|
+
/** Log prefix for console warnings */
|
|
21
|
+
logPrefix?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a render function that converts serialized DOM trees into React elements
|
|
25
|
+
* using the provided host component map.
|
|
26
|
+
*
|
|
27
|
+
* @param components - Map of UI tags to React components (from hostComponents())
|
|
28
|
+
* @param options - Optional configuration for action proxying and logging
|
|
29
|
+
* @returns A function that renders a SerializedNode tree into React elements
|
|
30
|
+
*/
|
|
31
|
+
export declare const createNodeRenderer: (components: Partial<Record<UITag, React.ComponentType<Record<string, unknown>>>>, options?: NodeRendererOptions) => (node: SerializedNode, sourceWindow: MessageEventSource | Window | null, key?: number | string) => React.ReactNode;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createElement as _createElement } from "react";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* createNodeRenderer — shared serialized DOM rendering utility.
|
|
5
|
+
*
|
|
6
|
+
* Extracts the core node rendering logic used by ExtensionSlot into a
|
|
7
|
+
* reusable function. Also consumed by the Extension Studio preview host.
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { isValidTag } from './ComponentRegistry';
|
|
11
|
+
import { filterNodeAttrs, validateNesting } from './ExtensionSlot';
|
|
12
|
+
/**
|
|
13
|
+
* Creates a render function that converts serialized DOM trees into React elements
|
|
14
|
+
* using the provided host component map.
|
|
15
|
+
*
|
|
16
|
+
* @param components - Map of UI tags to React components (from hostComponents())
|
|
17
|
+
* @param options - Optional configuration for action proxying and logging
|
|
18
|
+
* @returns A function that renders a SerializedNode tree into React elements
|
|
19
|
+
*/
|
|
20
|
+
export const createNodeRenderer = (components, options = {}) => {
|
|
21
|
+
const { surfaceId, logPrefix = '[NodeRenderer]' } = options;
|
|
22
|
+
const renderNode = (node, sourceWindow, key = 0) => {
|
|
23
|
+
if (node.type === 'text') {
|
|
24
|
+
return node.text || null;
|
|
25
|
+
}
|
|
26
|
+
if (!node.tag)
|
|
27
|
+
return null;
|
|
28
|
+
// Fragment wrapper from Surface serialization
|
|
29
|
+
if (node.tag === '__fragment') {
|
|
30
|
+
return (_jsx(React.Fragment, { children: node.children?.map((child, i) => renderNode(child, sourceWindow, i)) }, key));
|
|
31
|
+
}
|
|
32
|
+
if (!isValidTag(node.tag)) {
|
|
33
|
+
console.warn(`${logPrefix} Unknown UI tag rejected: <${node.tag}>`);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const HostComponent = components[node.tag];
|
|
37
|
+
if (!HostComponent) {
|
|
38
|
+
if (node.tag.startsWith('ui-')) {
|
|
39
|
+
console.warn(`${logPrefix} Unregistered UI tag: <${node.tag}>`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return (_jsx(React.Fragment, { children: node.children?.map((child, i) => renderNode(child, sourceWindow, i)) }, key));
|
|
43
|
+
}
|
|
44
|
+
// Filter attributes against the allowlist
|
|
45
|
+
const { filtered: props, stripped } = filterNodeAttrs(node.tag, node.attrs);
|
|
46
|
+
if (stripped.length > 0) {
|
|
47
|
+
console.warn(`${logPrefix} Attributes not allowed on <${node.tag}>, stripped: ${stripped.join(', ')}`);
|
|
48
|
+
}
|
|
49
|
+
// Warn about invalid nesting (never blocks rendering)
|
|
50
|
+
const invalidChildren = validateNesting(node.tag, node.children);
|
|
51
|
+
if (invalidChildren.length > 0) {
|
|
52
|
+
console.warn(`${logPrefix} Invalid children in <${node.tag}>: ${invalidChildren.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
// Wire up click handler proxy
|
|
55
|
+
if (node.actionId) {
|
|
56
|
+
const actionId = node.actionId;
|
|
57
|
+
props.onClick = () => {
|
|
58
|
+
const postTarget = sourceWindow;
|
|
59
|
+
postTarget?.postMessage({ type: 'action-invoke', surfaceId, actionId }, '*');
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Wire up onChange handler proxy
|
|
63
|
+
if (props['data-onchange-id']) {
|
|
64
|
+
const onchangeId = props['data-onchange-id'];
|
|
65
|
+
delete props['data-onchange-id'];
|
|
66
|
+
props.onChange = (e) => {
|
|
67
|
+
const postTarget = sourceWindow;
|
|
68
|
+
postTarget?.postMessage({ type: 'action-invoke', surfaceId, actionId: onchangeId, value: e.target.value }, '*');
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const children = node.children?.map((child, i) => renderNode(child, sourceWindow, i));
|
|
72
|
+
return (_createElement(HostComponent, { ...props, key: key }, children && children.length > 0 ? children : undefined));
|
|
73
|
+
};
|
|
74
|
+
return renderNode;
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=createNodeRenderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeRenderer.js","sourceRoot":"","sources":["../src/createNodeRenderer.tsx"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { ExtensionSlot } from './ExtensionSlot';
|
|
|
3
3
|
export { createSandbox, getSandbox, destroySandbox, postToSandbox, } from './SandboxManager';
|
|
4
4
|
export { createCapabilityRPCHandler, type CapabilityHandlers, } from './CapabilityRPCHandler';
|
|
5
5
|
export { registerComponent, registerComponents, getComponent, isValidTag, } from './ComponentRegistry';
|
|
6
|
+
export { createNodeRenderer, type SerializedNode } from './createNodeRenderer';
|
package/dist/index.js
CHANGED
|
@@ -3,4 +3,5 @@ export { ExtensionSlot } from './ExtensionSlot';
|
|
|
3
3
|
export { createSandbox, getSandbox, destroySandbox, postToSandbox, } from './SandboxManager';
|
|
4
4
|
export { createCapabilityRPCHandler, } from './CapabilityRPCHandler';
|
|
5
5
|
export { registerComponent, registerComponents, getComponent, isValidTag, } from './ComponentRegistry';
|
|
6
|
+
export { createNodeRenderer } from './createNodeRenderer';
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,0BAA0B,GAE3B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,UAAU,GACX,MAAM,qBAAqB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,0BAA0B,GAE3B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,UAAU,GACX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,kBAAkB,EAAuB,MAAM,sBAAsB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackable-labs/sdk-extension-host",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.31.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@stackable-labs/sdk-extension-contracts": "1.
|
|
15
|
+
"@stackable-labs/sdk-extension-contracts": "1.31.0",
|
|
16
16
|
"@remote-dom/core": "1.x",
|
|
17
17
|
"@remote-dom/react": "1.x"
|
|
18
18
|
},
|