@stackable-labs/sdk-extension-react 1.0.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/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ LICENSE
2
+
3
+ Copyright (c) 2026-present UNIQUELY PARTICULAR LLC, STACKABLE LABS, LLC, agnoStack, Inc., and Adam Grohs ("Author" herein)
4
+
5
+ Permission is hereby granted to use, copy, and modify this software
6
+ (the "Software") solely for the purpose of developing, testing,
7
+ or maintaining integration with Author's products and services.
8
+
9
+ The Software may not be used:
10
+ - as a standalone product or service,
11
+ - to integrate with any platform or service other than Author's,
12
+ - to build or enhance a competing product or service,
13
+ - or for any purpose unrelated to integrations with Author.
14
+
15
+ Redistribution of the Software, in whole or in part, is permitted
16
+ only as part of an application or service that integrates with Author
17
+ and does not expose the Software as a general-purpose library.
18
+
19
+ This Software is provided "AS IS", without warranty of any kind, express
20
+ or implied, including but not limited to the warranties of merchantability,
21
+ fitness for a particular purpose, and noninfringement.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ PUBLISHER, AUTHORS, ANY CONTRIBUTOR OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
27
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
28
+ OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
29
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @stackable-labs/sdk-extension-react
2
+
3
+ React hooks and components for building Stackable extensions.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @stackable-labs/sdk-extension-react
9
+ ```
10
+
11
+ ## Peer dependencies
12
+
13
+ ```
14
+ react >= 18.0.0 < 19.0.0
15
+ react-dom >= 18.0.0 < 19.0.0
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ Call `createExtension` as the entry point of your extension bundle:
21
+
22
+ ```tsx
23
+ import { createExtension, Surface, ui } from '@stackable-labs/sdk-extension-react';
24
+
25
+ export default createExtension(() => (
26
+ <Surface id="slot.content">
27
+ <ui.Text>Hello from my extension!</ui.Text>
28
+ </Surface>
29
+ ));
30
+ ```
31
+
32
+ ## Key exports
33
+
34
+ - **`createExtension`** — entry point bootstrap; mounts the extension React tree
35
+ - **`useCapabilities`** — call host-mediated APIs (`data.query`, `actions.toast`, `actions.invoke`)
36
+ - **`useSurfaceContext`** — read the context object passed by the host
37
+ - **`useStore` / `createStore`** — lightweight store for extension-local state
38
+ - **`ui`** — pre-approved UI primitive Components (`Button`, `Text`, `Card`, `Badge`, `Input`, etc.)
39
+ - **`Surface`** — Components to render your UI in host target slot
40
+
41
+ ## Changelog
42
+
43
+ See [npm version history](https://www.npmjs.com/package/@stackable-labs/sdk-extension-react?activeTab=versions).
44
+
45
+ ## License
46
+
47
+ SEE LICENSE IN [LICENSE](./LICENSE)
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Surface component — represents a single UI surface within an extension.
3
+ * Each Surface maps to an extension point (target) in the host app.
4
+ *
5
+ * Serializes the React element tree (including event handler references) and
6
+ * sends it to the host via postMessage. The host renders using its component
7
+ * registry and proxies user interactions back via postMessage.
8
+ */
9
+ import React from 'react';
10
+ interface SurfaceProps {
11
+ /** The extension point target ID (e.g., "slot.header") */
12
+ id: string;
13
+ children: React.ReactNode;
14
+ }
15
+ export interface SerializedNode {
16
+ type: 'element' | 'text';
17
+ tag?: string;
18
+ attrs?: Record<string, string>;
19
+ actionId?: string;
20
+ children?: SerializedNode[];
21
+ text?: string;
22
+ }
23
+ export declare const Surface: ({ id, children }: SurfaceProps) => import("react/jsx-runtime").JSX.Element;
24
+ export {};
@@ -0,0 +1,171 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Surface component — represents a single UI surface within an extension.
4
+ * Each Surface maps to an extension point (target) in the host app.
5
+ *
6
+ * Serializes the React element tree (including event handler references) and
7
+ * sends it to the host via postMessage. The host renders using its component
8
+ * registry and proxies user interactions back via postMessage.
9
+ */
10
+ import { useEffect, useRef, useState, useCallback } from 'react';
11
+ import { SurfaceContext } from './context';
12
+ /**
13
+ * Per-surface action handler registry.
14
+ * Each Surface instance gets its own map keyed by surfaceId so they don't collide.
15
+ */
16
+ const surfaceHandlers = new Map();
17
+ const getSurfaceRegistry = (surfaceId) => {
18
+ let reg = surfaceHandlers.get(surfaceId);
19
+ if (!reg) {
20
+ reg = { handlers: new Map(), counter: 0 };
21
+ surfaceHandlers.set(surfaceId, reg);
22
+ }
23
+ return reg;
24
+ };
25
+ /**
26
+ * Serialize the rendered DOM tree from the hidden container.
27
+ * This approach works with hook-based child components because React has
28
+ * already rendered them into actual DOM elements by the time we traverse.
29
+ */
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ const serializeDOMNode = (domNode, registry) => {
32
+ if (domNode.nodeType === Node.TEXT_NODE) {
33
+ const text = domNode.textContent || '';
34
+ if (!text.trim())
35
+ return [];
36
+ return [{ type: 'text', text }];
37
+ }
38
+ if (domNode.nodeType !== Node.ELEMENT_NODE)
39
+ return [];
40
+ const el = domNode;
41
+ const tag = el.tagName.toLowerCase();
42
+ // Skip the container div
43
+ if (tag === 'div' && el.hasAttribute('data-surface-id')) {
44
+ return Array.from(el.childNodes).flatMap((child) => serializeDOMNode(child, registry));
45
+ }
46
+ const attrs = {};
47
+ let actionId;
48
+ // Extract props from React's internal __reactProps$ to preserve original
49
+ // prop names (e.g. className instead of class) and custom element props
50
+ // (e.g. variant, gap, direction) that may not appear as DOM attributes.
51
+ const propsKey = Object.keys(el).find((k) => k.startsWith('__reactProps$'));
52
+ if (propsKey) {
53
+ const reactProps = el[propsKey];
54
+ if (reactProps) {
55
+ for (const [key, value] of Object.entries(reactProps)) {
56
+ if (key === 'children')
57
+ continue;
58
+ if ((key === 'onClick' || key === 'onChange') && typeof value === 'function') {
59
+ const id = `action-${++registry.counter}`;
60
+ registry.handlers.set(id, value);
61
+ if (key === 'onClick')
62
+ actionId = id;
63
+ if (key === 'onChange')
64
+ attrs['data-onchange-id'] = id;
65
+ continue;
66
+ }
67
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
68
+ attrs[key] = String(value);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ else {
74
+ // Fallback: copy DOM attributes if React props are unavailable
75
+ for (const attr of Array.from(el.attributes)) {
76
+ if (attr.name === 'style')
77
+ continue;
78
+ // Map 'class' back to 'className' for host components
79
+ const name = attr.name === 'class' ? 'className' : attr.name;
80
+ attrs[name] = attr.value;
81
+ }
82
+ }
83
+ const children = Array.from(el.childNodes).flatMap((child) => serializeDOMNode(child, registry));
84
+ return [{ type: 'element', tag, attrs, actionId, children }];
85
+ };
86
+ export const Surface = ({ id, children }) => {
87
+ const [context, setContext] = useState({});
88
+ const prevTreeRef = useRef('');
89
+ const containerRef = useRef(null);
90
+ const serializeAndSend = useCallback(() => {
91
+ const container = containerRef.current;
92
+ if (!container)
93
+ return;
94
+ const registry = getSurfaceRegistry(id);
95
+ // Clear previous handlers for THIS surface only
96
+ registry.handlers.clear();
97
+ registry.counter = 0;
98
+ const serialized = serializeDOMNode(container, registry);
99
+ const tree = {
100
+ type: 'element',
101
+ tag: '__fragment',
102
+ children: serialized,
103
+ };
104
+ // Only send if tree structure actually changed (ignore action ID changes)
105
+ const treeForComparison = JSON.stringify(tree, (key, value) => key === 'actionId' || key === 'data-onchange-id' ? undefined : value);
106
+ if (treeForComparison === prevTreeRef.current)
107
+ return;
108
+ prevTreeRef.current = treeForComparison;
109
+ window.parent.postMessage({ type: 'surface-update', surfaceId: id, tree }, '*');
110
+ }, [id]);
111
+ // Serialize after every Surface render
112
+ useEffect(() => {
113
+ serializeAndSend();
114
+ });
115
+ // Watch for DOM mutations from child component state changes
116
+ // (children may re-render without Surface itself re-rendering)
117
+ useEffect(() => {
118
+ const container = containerRef.current;
119
+ if (!container)
120
+ return;
121
+ const observer = new MutationObserver(() => {
122
+ serializeAndSend();
123
+ });
124
+ observer.observe(container, {
125
+ childList: true,
126
+ subtree: true,
127
+ attributes: true,
128
+ characterData: true,
129
+ });
130
+ return () => observer.disconnect();
131
+ }, [serializeAndSend]);
132
+ useEffect(() => {
133
+ // Listen for context updates and action invocations from host
134
+ const handleMessage = (event) => {
135
+ const msg = event.data;
136
+ if (!msg || typeof msg !== 'object')
137
+ return;
138
+ if (msg.type === 'context-update' && msg.surfaceId === id) {
139
+ setContext(msg.context);
140
+ }
141
+ if (msg.type === 'surface-render' && msg.surfaceId === id) {
142
+ // Host slot mounted late — re-send current tree
143
+ if (msg.context) {
144
+ setContext(msg.context);
145
+ }
146
+ prevTreeRef.current = '';
147
+ serializeAndSend();
148
+ }
149
+ if (msg.type === 'action-invoke' && msg.surfaceId === id && msg.actionId) {
150
+ const registry = getSurfaceRegistry(id);
151
+ const handler = registry.handlers.get(msg.actionId);
152
+ if (handler) {
153
+ if (msg.value !== undefined) {
154
+ handler({ target: { value: msg.value } });
155
+ }
156
+ else {
157
+ handler();
158
+ }
159
+ }
160
+ }
161
+ };
162
+ window.addEventListener('message', handleMessage);
163
+ // Notify host that this surface is ready
164
+ window.parent.postMessage({ type: 'surface-ready', surfaceId: id }, '*');
165
+ return () => {
166
+ window.removeEventListener('message', handleMessage);
167
+ };
168
+ }, [id, serializeAndSend]);
169
+ return (_jsx(SurfaceContext.Provider, { value: context, children: _jsx("div", { ref: containerRef, "data-surface-id": id, style: { display: 'none' }, children: children }) }));
170
+ };
171
+ //# sourceMappingURL=Surface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Surface.js","sourceRoot":"","sources":["../src/Surface.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * React contexts for the extension runtime.
3
+ */
4
+ /** Context for surface-level data (provided by host per surface) */
5
+ export declare const SurfaceContext: import("react").Context<Record<string, unknown> | null>;
6
+ /** Context for extension-level data (provided by createExtension) */
7
+ export interface ExtensionContextValue {
8
+ extensionId: string;
9
+ }
10
+ export declare const ExtensionContext: import("react").Context<ExtensionContextValue | null>;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * React contexts for the extension runtime.
3
+ */
4
+ import { createContext } from 'react';
5
+ /** Context for surface-level data (provided by host per surface) */
6
+ export const SurfaceContext = createContext(null);
7
+ export const ExtensionContext = createContext(null);
8
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAErC,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAiC,IAAI,CAAC,CAAA;AAOjF,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAA+B,IAAI,CAAC,CAAA"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * createExtension — bootstraps the extension runtime in the iframe sandbox.
3
+ * Sets up Remote DOM, RPC listener, and renders the extension's React tree.
4
+ */
5
+ import React from 'react';
6
+ interface CreateExtensionOptions {
7
+ extensionId?: string;
8
+ }
9
+ /**
10
+ * Bootstrap an extension. Call this as the entry point of your extension bundle.
11
+ *
12
+ * @example
13
+ * export default createExtension(() => (
14
+ * <>
15
+ * <Surface id="slot.header"><Header /></Surface>
16
+ * <Surface id="slot.content"><Content /></Surface>
17
+ * </>
18
+ * ));
19
+ */
20
+ export declare const createExtension: (factory: () => React.ReactElement, options?: CreateExtensionOptions) => void;
21
+ export {};
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createRoot } from 'react-dom/client';
3
+ import { ExtensionContext } from './context';
4
+ import { initRpcListener } from './rpc-client';
5
+ /**
6
+ * Bootstrap an extension. Call this as the entry point of your extension bundle.
7
+ *
8
+ * @example
9
+ * export default createExtension(() => (
10
+ * <>
11
+ * <Surface id="slot.header"><Header /></Surface>
12
+ * <Surface id="slot.content"><Content /></Surface>
13
+ * </>
14
+ * ));
15
+ */
16
+ export const createExtension = (factory, options) => {
17
+ // Initialize RPC listener for capability responses
18
+ initRpcListener();
19
+ const extensionId = options?.extensionId ?? 'unknown';
20
+ const contextValue = {
21
+ extensionId,
22
+ };
23
+ // Mount the extension React tree
24
+ const container = document.getElementById('extension-root') ?? document.body;
25
+ const root = createRoot(container);
26
+ root.render(_jsx(ExtensionContext.Provider, { value: contextValue, children: factory() }));
27
+ // Notify host that the extension is ready
28
+ window.parent.postMessage({ type: 'extension-ready' }, '*');
29
+ };
30
+ //# sourceMappingURL=createExtension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createExtension.js","sourceRoot":"","sources":["../src/createExtension.tsx"],"names":[],"mappings":";AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAM9C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAiC,EAAE,OAAgC,EAAQ,EAAE;IAC3G,mDAAmD;IACnD,eAAe,EAAE,CAAA;IAEjB,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,SAAS,CAAA;IAErD,MAAM,YAAY,GAA0B;QAC1C,WAAW;KACZ,CAAA;IAED,iCAAiC;IACjC,MAAM,SAAS,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAA;IAC5E,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,CAAA;IAElC,IAAI,CAAC,MAAM,CACT,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,YAAY,YAC3C,OAAO,EAAE,GACgB,CAC7B,CAAA;IAED,0CAA0C;IAC1C,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,GAAG,CAAC,CAAA;AAC7D,CAAC,CAAA"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * React hooks for extension developers.
3
+ */
4
+ import type { ContextData, ApiRequest, ToastPayload } from '@stackable-labs/sdk-extension-contracts';
5
+ import type { Store } from './store';
6
+ /**
7
+ * Read the host-provided context for the current surface.
8
+ */
9
+ export declare const useSurfaceContext: () => Record<string, unknown>;
10
+ /**
11
+ * Access host-mediated capabilities (data, actions, context).
12
+ */
13
+ export declare const useCapabilities: () => {
14
+ data: {
15
+ query: <T = unknown>(payload: ApiRequest) => Promise<T>;
16
+ };
17
+ actions: {
18
+ toast: (payload: ToastPayload) => Promise<void>;
19
+ invoke: <T = unknown>(action: string, payload?: Record<string, unknown>) => Promise<T>;
20
+ };
21
+ context: {
22
+ read: () => Promise<ContextData>;
23
+ };
24
+ };
25
+ /**
26
+ * Subscribe to a shared store with an optional selector.
27
+ */
28
+ export declare const useStore: <T, S = T>(store: Store<T>, selector?: (state: T) => S) => S;
29
+ /**
30
+ * Access the extension-level context (extension ID, manifest, etc.)
31
+ */
32
+ export declare const useExtension: () => import("./context").ExtensionContextValue;
package/dist/hooks.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * React hooks for extension developers.
3
+ */
4
+ import { useContext, useSyncExternalStore, useCallback } from 'react';
5
+ import { ExtensionContext, SurfaceContext } from './context';
6
+ import { callCapability } from './rpc-client';
7
+ /**
8
+ * Read the host-provided context for the current surface.
9
+ */
10
+ export const useSurfaceContext = () => {
11
+ const ctx = useContext(SurfaceContext);
12
+ if (!ctx) {
13
+ throw new Error('useSurfaceContext must be used inside a <Surface> component');
14
+ }
15
+ return ctx;
16
+ };
17
+ /**
18
+ * Access host-mediated capabilities (data, actions, context).
19
+ */
20
+ export const useCapabilities = () => ({
21
+ data: {
22
+ query: (payload) => callCapability('data.query', payload),
23
+ },
24
+ actions: {
25
+ toast: (payload) => callCapability('actions.toast', payload),
26
+ invoke: (action, payload) => callCapability('actions.invoke', { action, payload }),
27
+ },
28
+ context: {
29
+ read: () => callCapability('context.read'),
30
+ },
31
+ });
32
+ /**
33
+ * Subscribe to a shared store with an optional selector.
34
+ */
35
+ export const useStore = (store, selector) => {
36
+ const select = selector ?? ((s) => s);
37
+ const getSnapshot = useCallback(() => select(store.get()), [store, select]);
38
+ const subscribe = useCallback((onStoreChange) => store.subscribe(onStoreChange), [store]);
39
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
40
+ };
41
+ /**
42
+ * Access the extension-level context (extension ID, manifest, etc.)
43
+ */
44
+ export const useExtension = () => {
45
+ const ctx = useContext(ExtensionContext);
46
+ if (!ctx) {
47
+ throw new Error('useExtension must be used inside a createExtension() tree');
48
+ }
49
+ return ctx;
50
+ };
51
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAOrE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAG7C;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAA4B,EAAE;IAC7D,MAAM,GAAG,GAAG,UAAU,CAAC,cAAc,CAAC,CAAA;IACtC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAA;IAChF,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,EAAE,CAAC,CAAC;IAClC,IAAI,EAAE;QACJ,KAAK,EAAE,CAAc,OAAmB,EAAc,EAAE,CACtD,cAAc,CAAI,YAAY,EAAE,OAAO,CAAC;KAC3C;IACD,OAAO,EAAE;QACP,KAAK,EAAE,CAAC,OAAqB,EAAiB,EAAE,CAC9C,cAAc,CAAO,eAAe,EAAE,OAAO,CAAC;QAChD,MAAM,EAAE,CAAc,MAAc,EAAE,OAAiC,EAAc,EAAE,CACrF,cAAc,CAAI,gBAAgB,EAAE,EAAE,MAAM,EAAE,OAAO,EAAgC,CAAC;KACzF;IACD,OAAO,EAAE;QACP,IAAI,EAAE,GAAyB,EAAE,CAC/B,cAAc,CAAc,cAAc,CAAC;KAC9C;CACF,CAAC,CAAA;AAEJ;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAW,KAAe,EAAE,QAA0B,EAAK,EAAE;IACnF,MAAM,MAAM,GAAG,QAAQ,IAAI,CAAC,CAAC,CAAI,EAAE,EAAE,CAAC,CAAiB,CAAC,CAAA;IAExD,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IAE3E,MAAM,SAAS,GAAG,WAAW,CAC3B,CAAC,aAAyB,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,aAAa,CAAC,EAC7D,CAAC,KAAK,CAAC,CACR,CAAA;IAED,OAAO,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,WAAW,CAAC,CAAA;AAClE,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,EAAE;IAC/B,MAAM,GAAG,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAA;IACxC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAA;IAC9E,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA"}
@@ -0,0 +1,7 @@
1
+ export { createExtension } from './createExtension';
2
+ export { createStore } from './store';
3
+ export { useSurfaceContext, useCapabilities, useStore, useExtension, } from './hooks';
4
+ export type { Store } from './store';
5
+ export { Surface } from './Surface';
6
+ export * as ui from './ui';
7
+ export { useContextData } from './useContextData';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { createExtension } from './createExtension';
2
+ export { createStore } from './store';
3
+ export { useSurfaceContext, useCapabilities, useStore, useExtension, } from './hooks';
4
+ export { Surface } from './Surface';
5
+ export * as ui from './ui';
6
+ export { useContextData } from './useContextData';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,QAAQ,EACR,YAAY,GACb,MAAM,SAAS,CAAA;AAEhB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,KAAK,EAAE,MAAM,MAAM,CAAA;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * RPC Client — sends capability requests from sandbox to host via postMessage.
3
+ */
4
+ import type { CapabilityType } from '@stackable-labs/sdk-extension-contracts';
5
+ /**
6
+ * Initialize the RPC listener for responses from the host.
7
+ */
8
+ export declare const initRpcListener: () => void;
9
+ /**
10
+ * Send a capability request to the host and await the response.
11
+ */
12
+ export declare const callCapability: <T = unknown>(capability: CapabilityType, payload?: unknown) => Promise<T>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * RPC Client — sends capability requests from sandbox to host via postMessage.
3
+ */
4
+ let requestCounter = 0;
5
+ const pendingRequests = new Map();
6
+ const generateRequestId = () => `req_${++requestCounter}_${Date.now()}`;
7
+ /**
8
+ * Initialize the RPC listener for responses from the host.
9
+ */
10
+ export const initRpcListener = () => {
11
+ window.addEventListener('message', (event) => {
12
+ const msg = event.data;
13
+ if (msg?.type === 'capability-response') {
14
+ const response = msg;
15
+ const pending = pendingRequests.get(response.id);
16
+ if (pending) {
17
+ pendingRequests.delete(response.id);
18
+ if (response.success) {
19
+ pending.resolve(response.data);
20
+ }
21
+ else {
22
+ pending.reject(new Error(response.error || 'Capability call failed'));
23
+ }
24
+ }
25
+ }
26
+ });
27
+ };
28
+ /**
29
+ * Send a capability request to the host and await the response.
30
+ */
31
+ export const callCapability = (capability, payload) => (new Promise((resolve, reject) => {
32
+ const id = generateRequestId();
33
+ pendingRequests.set(id, {
34
+ resolve: resolve,
35
+ reject,
36
+ });
37
+ const request = {
38
+ type: 'capability-request',
39
+ id,
40
+ capability,
41
+ payload,
42
+ };
43
+ // Post to parent (host) window
44
+ window.parent.postMessage(request, '*');
45
+ // Timeout after 30 seconds
46
+ setTimeout(() => {
47
+ if (pendingRequests.has(id)) {
48
+ pendingRequests.delete(id);
49
+ reject(new Error(`Capability call '${capability}' timed out`));
50
+ }
51
+ }, 30000);
52
+ }));
53
+ //# sourceMappingURL=rpc-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-client.js","sourceRoot":"","sources":["../src/rpc-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,IAAI,cAAc,GAAG,CAAC,CAAA;AACtB,MAAM,eAAe,GAAG,IAAI,GAAG,EAG3B,CAAA;AAEJ,MAAM,iBAAiB,GAAG,GAAW,EAAE,CAAC,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAA;AAE/E;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAS,EAAE;IACxC,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAmB,EAAE,EAAE;QACzD,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAA;QACtB,IAAI,GAAG,EAAE,IAAI,KAAK,qBAAqB,EAAE,CAAC;YACxC,MAAM,QAAQ,GAAG,GAAyB,CAAA;YAC1C,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;YAChD,IAAI,OAAO,EAAE,CAAC;gBACZ,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;gBACnC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;oBACrB,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;gBAChC,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,wBAAwB,CAAC,CAAC,CAAA;gBACvE,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAc,UAA0B,EAAE,OAAiB,EAAc,EAAE,CAAC,CACxG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IAC9B,MAAM,EAAE,GAAG,iBAAiB,EAAE,CAAA;IAC9B,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE;QACtB,OAAO,EAAE,OAAkC;QAC3C,MAAM;KACP,CAAC,CAAA;IAEF,MAAM,OAAO,GAAsB;QACjC,IAAI,EAAE,oBAAoB;QAC1B,EAAE;QACF,UAAU;QACV,OAAO;KACR,CAAA;IAED,+BAA+B;IAC/B,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IAEvC,2BAA2B;IAC3B,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC5B,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAC1B,MAAM,CAAC,IAAI,KAAK,CAAC,oBAAoB,UAAU,aAAa,CAAC,CAAC,CAAA;QAChE,CAAC;IACH,CAAC,EAAE,KAAK,CAAC,CAAA;AACX,CAAC,CAAC,CACH,CAAA"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shared Store — cross-surface state coordination.
3
+ * All surfaces in the same extension runtime share a store instance.
4
+ */
5
+ type Listener<T> = (state: T) => void;
6
+ export interface Store<T> {
7
+ get(): T;
8
+ set(partial: Partial<T>): void;
9
+ subscribe(listener: Listener<T>): () => void;
10
+ }
11
+ /**
12
+ * Create a shared store for cross-surface coordination.
13
+ *
14
+ * @example
15
+ * const store = createStore({ mode: 'account', selectedId: null });
16
+ * store.set({ mode: 'conversations' });
17
+ * store.subscribe((state) => console.log(state));
18
+ */
19
+ export declare const createStore: <T extends object>(initialState: T) => Store<T>;
20
+ export {};
package/dist/store.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared Store — cross-surface state coordination.
3
+ * All surfaces in the same extension runtime share a store instance.
4
+ */
5
+ /**
6
+ * Create a shared store for cross-surface coordination.
7
+ *
8
+ * @example
9
+ * const store = createStore({ mode: 'account', selectedId: null });
10
+ * store.set({ mode: 'conversations' });
11
+ * store.subscribe((state) => console.log(state));
12
+ */
13
+ export const createStore = (initialState) => {
14
+ let state = { ...initialState };
15
+ const listeners = new Set();
16
+ return {
17
+ get: () => state,
18
+ set: (partial) => {
19
+ state = { ...state, ...partial };
20
+ listeners.forEach((listener) => listener(state));
21
+ },
22
+ subscribe: (listener) => {
23
+ listeners.add(listener);
24
+ return () => {
25
+ listeners.delete(listener);
26
+ };
27
+ },
28
+ };
29
+ };
30
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAmB,YAAe,EAAY,EAAE;IACzE,IAAI,KAAK,GAAG,EAAE,GAAG,YAAY,EAAE,CAAA;IAC/B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAe,CAAA;IAExC,OAAO;QACL,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK;QAEhB,GAAG,EAAE,CAAC,OAAmB,EAAE,EAAE;YAC3B,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,OAAO,EAAE,CAAA;YAChC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;QAClD,CAAC;QAED,SAAS,EAAE,CAAC,QAAqB,EAAE,EAAE;YACnC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACvB,OAAO,GAAG,EAAE;gBACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;YAC5B,CAAC,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
package/dist/ui.d.ts ADDED
@@ -0,0 +1,222 @@
1
+ /**
2
+ * UI Component Wrappers for Extensions
3
+ *
4
+ * These are thin React components that render Remote DOM custom elements.
5
+ * The host maps these custom elements to real React components (e.g., shadcn/ui).
6
+ *
7
+ * Extension developers use these as: <ui.Card>, <ui.Button>, etc.
8
+ */
9
+ import React from 'react';
10
+ import type { AllowedIconName } from '@stackable-labs/sdk-extension-contracts';
11
+ type RemoteInputChangeEvent = {
12
+ target: {
13
+ value: string;
14
+ };
15
+ };
16
+ export declare const Card: (props: {
17
+ className?: string;
18
+ onClick?: () => void;
19
+ children?: React.ReactNode;
20
+ }) => import("react/jsx-runtime").JSX.Element;
21
+ export declare const CardContent: (props: {
22
+ className?: string;
23
+ children?: React.ReactNode;
24
+ }) => import("react/jsx-runtime").JSX.Element;
25
+ export declare const CardHeader: (props: {
26
+ className?: string;
27
+ children?: React.ReactNode;
28
+ }) => import("react/jsx-runtime").JSX.Element;
29
+ export declare const Stack: (props: {
30
+ gap?: string;
31
+ direction?: "row" | "column";
32
+ align?: string;
33
+ className?: string;
34
+ children?: React.ReactNode;
35
+ }) => import("react/jsx-runtime").JSX.Element;
36
+ export declare const Inline: (props: {
37
+ gap?: string;
38
+ align?: string;
39
+ className?: string;
40
+ children?: React.ReactNode;
41
+ }) => import("react/jsx-runtime").JSX.Element;
42
+ export declare const Text: (props: {
43
+ className?: string;
44
+ tone?: string;
45
+ children?: React.ReactNode;
46
+ }) => import("react/jsx-runtime").JSX.Element;
47
+ export declare const Heading: (props: {
48
+ level?: "1" | "2" | "3" | "4" | "5" | "6";
49
+ className?: string;
50
+ children?: React.ReactNode;
51
+ }) => import("react/jsx-runtime").JSX.Element;
52
+ export declare const Badge: (props: {
53
+ variant?: string;
54
+ tone?: string;
55
+ className?: string;
56
+ children?: React.ReactNode;
57
+ }) => import("react/jsx-runtime").JSX.Element;
58
+ export declare const Button: (props: {
59
+ variant?: string;
60
+ size?: string;
61
+ disabled?: boolean;
62
+ onClick?: () => void;
63
+ type?: string;
64
+ className?: string;
65
+ title?: string;
66
+ children?: React.ReactNode;
67
+ }) => import("react/jsx-runtime").JSX.Element;
68
+ export declare const Input: (props: {
69
+ type?: string;
70
+ placeholder?: string;
71
+ value?: string;
72
+ onChange?: (e: RemoteInputChangeEvent) => void;
73
+ disabled?: boolean;
74
+ className?: string;
75
+ id?: string;
76
+ }) => import("react/jsx-runtime").JSX.Element;
77
+ export declare const Separator: (props: {
78
+ className?: string;
79
+ }) => import("react/jsx-runtime").JSX.Element;
80
+ export declare const Tabs: (props: {
81
+ defaultValue?: string;
82
+ className?: string;
83
+ children?: React.ReactNode;
84
+ }) => import("react/jsx-runtime").JSX.Element;
85
+ export declare const TabsList: (props: {
86
+ className?: string;
87
+ children?: React.ReactNode;
88
+ }) => import("react/jsx-runtime").JSX.Element;
89
+ export declare const TabsTrigger: (props: {
90
+ value: string;
91
+ className?: string;
92
+ children?: React.ReactNode;
93
+ }) => import("react/jsx-runtime").JSX.Element;
94
+ export declare const TabsContent: (props: {
95
+ value: string;
96
+ className?: string;
97
+ children?: React.ReactNode;
98
+ }) => import("react/jsx-runtime").JSX.Element;
99
+ export declare const ScrollArea: (props: {
100
+ className?: string;
101
+ children?: React.ReactNode;
102
+ }) => import("react/jsx-runtime").JSX.Element;
103
+ export declare const Avatar: (props: {
104
+ className?: string;
105
+ children?: React.ReactNode;
106
+ }) => import("react/jsx-runtime").JSX.Element;
107
+ export declare const AvatarImage: (props: {
108
+ src: string;
109
+ alt?: string;
110
+ className?: string;
111
+ }) => import("react/jsx-runtime").JSX.Element;
112
+ export declare const AvatarFallback: (props: {
113
+ className?: string;
114
+ children?: React.ReactNode;
115
+ }) => import("react/jsx-runtime").JSX.Element;
116
+ export declare const Icon: (props: {
117
+ name: AllowedIconName;
118
+ size?: string;
119
+ className?: string;
120
+ }) => import("react/jsx-runtime").JSX.Element;
121
+ export declare const Link: (props: {
122
+ href?: string;
123
+ target?: string;
124
+ rel?: string;
125
+ className?: string;
126
+ children?: React.ReactNode;
127
+ }) => import("react/jsx-runtime").JSX.Element;
128
+ export declare const FooterLink: (props: {
129
+ href?: string;
130
+ children?: React.ReactNode;
131
+ }) => import("react/jsx-runtime").JSX.Element;
132
+ export declare const Menu: (props: {
133
+ title?: string;
134
+ className?: string;
135
+ children?: React.ReactNode;
136
+ }) => import("react/jsx-runtime").JSX.Element;
137
+ export declare const MenuItem: (props: {
138
+ icon?: AllowedIconName;
139
+ label: string;
140
+ description?: string;
141
+ onClick?: () => void;
142
+ className?: string;
143
+ }) => import("react/jsx-runtime").JSX.Element;
144
+ declare global {
145
+ namespace JSX {
146
+ interface IntrinsicElements {
147
+ 'ui-card': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
148
+ 'ui-card-content': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
149
+ 'ui-card-header': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
150
+ 'ui-button': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
151
+ variant?: string;
152
+ size?: string;
153
+ disabled?: boolean;
154
+ type?: string;
155
+ title?: string;
156
+ }, HTMLElement>;
157
+ 'ui-text': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
158
+ tone?: string;
159
+ }, HTMLElement>;
160
+ 'ui-heading': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
161
+ level?: string;
162
+ }, HTMLElement>;
163
+ 'ui-badge': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
164
+ variant?: string;
165
+ tone?: string;
166
+ }, HTMLElement>;
167
+ 'ui-input': Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> & {
168
+ type?: string;
169
+ placeholder?: string;
170
+ value?: string;
171
+ onChange?: (e: RemoteInputChangeEvent) => void;
172
+ disabled?: boolean;
173
+ id?: string;
174
+ };
175
+ 'ui-stack': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
176
+ gap?: string;
177
+ direction?: string;
178
+ align?: string;
179
+ }, HTMLElement>;
180
+ 'ui-inline': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
181
+ gap?: string;
182
+ align?: string;
183
+ }, HTMLElement>;
184
+ 'ui-separator': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
185
+ 'ui-tabs': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
186
+ defaultValue?: string;
187
+ }, HTMLElement>;
188
+ 'ui-tabs-list': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
189
+ 'ui-tabs-trigger': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
190
+ value?: string;
191
+ }, HTMLElement>;
192
+ 'ui-tabs-content': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
193
+ value?: string;
194
+ }, HTMLElement>;
195
+ 'ui-scroll-area': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
196
+ 'ui-avatar': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
197
+ 'ui-avatar-image': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
198
+ src?: string;
199
+ alt?: string;
200
+ }, HTMLElement>;
201
+ 'ui-avatar-fallback': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
202
+ 'ui-icon': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
203
+ name?: string;
204
+ size?: string;
205
+ }, HTMLElement>;
206
+ 'ui-link': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
207
+ href?: string;
208
+ target?: string;
209
+ rel?: string;
210
+ }, HTMLElement>;
211
+ 'ui-menu-item': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
212
+ icon?: string;
213
+ label?: string;
214
+ description?: string;
215
+ }, HTMLElement>;
216
+ 'ui-menu': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement> & {
217
+ title?: string;
218
+ }, HTMLElement>;
219
+ }
220
+ }
221
+ }
222
+ export {};
package/dist/ui.js ADDED
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // ─── Layout ──────────────────────────────────────────────────────────────────
3
+ export const Card = (props) => _jsx("ui-card", { ...props, children: props.children });
4
+ export const CardContent = (props) => _jsx("ui-card-content", { ...props, children: props.children });
5
+ export const CardHeader = (props) => _jsx("ui-card-header", { ...props, children: props.children });
6
+ export const Stack = (props) => _jsx("ui-stack", { ...props, children: props.children });
7
+ export const Inline = (props) => _jsx("ui-inline", { ...props, children: props.children });
8
+ // ─── Text ────────────────────────────────────────────────────────────────────
9
+ export const Text = (props) => _jsx("ui-text", { ...props, children: props.children });
10
+ export const Heading = (props) => _jsx("ui-heading", { ...props, children: props.children });
11
+ export const Badge = (props) => _jsx("ui-badge", { ...props, children: props.children });
12
+ // ─── Inputs ──────────────────────────────────────────────────────────────────
13
+ export const Button = (props) => _jsx("ui-button", { ...props, children: props.children });
14
+ export const Input = (props) => _jsx("ui-input", { ...props });
15
+ // ─── Feedback / Navigation ───────────────────────────────────────────────────
16
+ export const Separator = (props) => _jsx("ui-separator", { ...props });
17
+ export const Tabs = (props) => _jsx("ui-tabs", { ...props, children: props.children });
18
+ export const TabsList = (props) => _jsx("ui-tabs-list", { ...props, children: props.children });
19
+ export const TabsTrigger = (props) => _jsx("ui-tabs-trigger", { ...props, children: props.children });
20
+ export const TabsContent = (props) => _jsx("ui-tabs-content", { ...props, children: props.children });
21
+ export const ScrollArea = (props) => _jsx("ui-scroll-area", { ...props, children: props.children });
22
+ // ─── Avatar ──────────────────────────────────────────────────────────────────
23
+ export const Avatar = (props) => _jsx("ui-avatar", { ...props, children: props.children });
24
+ export const AvatarImage = (props) => _jsx("ui-avatar-image", { ...props });
25
+ export const AvatarFallback = (props) => _jsx("ui-avatar-fallback", { ...props, children: props.children });
26
+ // ─── Icons ───────────────────────────────────────────────────────────────────
27
+ export const Icon = (props) => _jsx("ui-icon", { ...props });
28
+ export const Link = (props) => _jsx("ui-link", { ...props, children: props.children });
29
+ // NOTE: Keep this implementation in sync with
30
+ // packages/apps/host-app/src/components/FooterLink.tsx.
31
+ // TODO: Abstract FooterLink into a shared React component package.
32
+ export const FooterLink = (props) => (_jsx("ui-link", { ...props, target: "_blank", rel: "noopener noreferrer", className: "text-zinc-500 opacity-80 transition-colors hover:text-zinc-900 hover:opacity-100", children: props.children }));
33
+ // ─── Composite ──────────────────────────────────────────────────────────────
34
+ export const Menu = (props) => _jsx("ui-menu", { ...props, children: props.children });
35
+ export const MenuItem = (props) => _jsx("ui-menu-item", { ...props });
36
+ //# sourceMappingURL=ui.js.map
package/dist/ui.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * useContextData — reads host context on mount and returns all context fields.
3
+ */
4
+ import type { ContextData } from '@stackable-labs/sdk-extension-contracts';
5
+ type UseContextDataResult = ContextData & {
6
+ loading: boolean;
7
+ };
8
+ export declare const useContextData: () => UseContextDataResult;
9
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * useContextData — reads host context on mount and returns all context fields.
3
+ */
4
+ import { useState, useEffect } from 'react';
5
+ import { useCapabilities } from './hooks';
6
+ export const useContextData = () => {
7
+ const capabilities = useCapabilities();
8
+ const [contextData, setContextData] = useState({});
9
+ const [loading, setLoading] = useState(true);
10
+ useEffect(() => {
11
+ const loadContext = async () => {
12
+ try {
13
+ const ctx = await capabilities.context.read();
14
+ setContextData(ctx);
15
+ }
16
+ catch (err) {
17
+ console.error('Failed to read context:', err);
18
+ }
19
+ finally {
20
+ setLoading(false);
21
+ }
22
+ };
23
+ loadContext();
24
+ }, [capabilities]);
25
+ return { ...contextData, loading };
26
+ };
27
+ //# sourceMappingURL=useContextData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useContextData.js","sourceRoot":"","sources":["../src/useContextData.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAIzC,MAAM,CAAC,MAAM,cAAc,GAAG,GAAyB,EAAE;IACvD,MAAM,YAAY,GAAG,eAAe,EAAE,CAAA;IAEtC,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAc,EAAE,CAAC,CAAA;IAC/D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAU,IAAI,CAAC,CAAA;IAErD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,WAAW,GAAG,KAAK,IAAI,EAAE;YAC7B,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;gBAC7C,cAAc,CAAC,GAAG,CAAC,CAAA;YACrB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAA;YAC/C,CAAC;oBAAS,CAAC;gBACT,UAAU,CAAC,KAAK,CAAC,CAAA;YACnB,CAAC;QACH,CAAC,CAAA;QACD,WAAW,EAAE,CAAA;IACf,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAA;IAElB,OAAO,EAAE,GAAG,WAAW,EAAE,OAAO,EAAE,CAAA;AACpC,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@stackable-labs/sdk-extension-react",
3
+ "version": "1.0.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@stackable-labs/sdk-extension-contracts": "1.0.0",
16
+ "@remote-dom/core": "1.x",
17
+ "@remote-dom/react": "1.x"
18
+ },
19
+ "peerDependencies": {
20
+ "react": ">=18.0.0 <19.0.0",
21
+ "react-dom": ">=18.0.0 <19.0.0"
22
+ },
23
+ "description": "React bindings for building Stackable extensions.",
24
+ "license": "SEE LICENSE IN LICENSE",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "homepage": "https://www.npmjs.com/package/@stackable-labs/sdk-extension-react",
29
+ "files": [
30
+ "dist/",
31
+ "README.md",
32
+ "LICENSE"
33
+ ]
34
+ }