@gigabuddy/gadgets 0.1.3 → 0.1.4
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/index.js +71 -3
- package/index.js.map +2 -2
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -358,8 +358,46 @@ function setupGadgetBreakout(iframe) {
|
|
|
358
358
|
"maxHeight",
|
|
359
359
|
"margin",
|
|
360
360
|
"transform",
|
|
361
|
-
"pointerEvents"
|
|
361
|
+
"pointerEvents",
|
|
362
|
+
"overflow",
|
|
363
|
+
"visibility"
|
|
362
364
|
];
|
|
365
|
+
const ancestorOverrides = [];
|
|
366
|
+
let placeholderDiv = null;
|
|
367
|
+
function clearAncestorClipping() {
|
|
368
|
+
let el = iframe.parentElement;
|
|
369
|
+
while (el && el !== document.body && el !== document.documentElement) {
|
|
370
|
+
const computed = getComputedStyle(el);
|
|
371
|
+
const overflow = computed.overflow + computed.overflowX + computed.overflowY;
|
|
372
|
+
const hasClipping = /hidden|auto|scroll|clip/.test(overflow);
|
|
373
|
+
const hasContainingBlock = computed.transform !== "none" || computed.willChange === "transform" || computed.contain !== "none" || computed.filter !== "none";
|
|
374
|
+
if (hasClipping || hasContainingBlock) {
|
|
375
|
+
if (hasClipping) {
|
|
376
|
+
ancestorOverrides.push({ el, key: "overflow", original: el.style.overflow });
|
|
377
|
+
ancestorOverrides.push({ el, key: "overflowX", original: el.style.overflowX });
|
|
378
|
+
ancestorOverrides.push({ el, key: "overflowY", original: el.style.overflowY });
|
|
379
|
+
el.style.overflow = "visible";
|
|
380
|
+
el.style.overflowX = "visible";
|
|
381
|
+
el.style.overflowY = "visible";
|
|
382
|
+
}
|
|
383
|
+
if (hasContainingBlock && computed.transform !== "none") {
|
|
384
|
+
ancestorOverrides.push({ el, key: "transform", original: el.style.transform });
|
|
385
|
+
el.style.transform = "none";
|
|
386
|
+
}
|
|
387
|
+
if (hasContainingBlock && computed.contain !== "none") {
|
|
388
|
+
ancestorOverrides.push({ el, key: "contain", original: el.style.contain });
|
|
389
|
+
el.style.contain = "none";
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
el = el.parentElement;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function restoreAncestorClipping() {
|
|
396
|
+
for (const { el, key, original } of ancestorOverrides) {
|
|
397
|
+
el.style[key] = original;
|
|
398
|
+
}
|
|
399
|
+
ancestorOverrides.length = 0;
|
|
400
|
+
}
|
|
363
401
|
function handler(event) {
|
|
364
402
|
if (event.source !== iframe.contentWindow)
|
|
365
403
|
return;
|
|
@@ -371,6 +409,14 @@ function setupGadgetBreakout(iframe) {
|
|
|
371
409
|
for (const key of STYLE_KEYS) {
|
|
372
410
|
savedStyles[key] = iframe.style[key];
|
|
373
411
|
}
|
|
412
|
+
savedStyles["_scrolling"] = iframe.getAttribute("scrolling") ?? "";
|
|
413
|
+
placeholderDiv = document.createElement("div");
|
|
414
|
+
placeholderDiv.style.width = `${rect.width}px`;
|
|
415
|
+
placeholderDiv.style.height = `${rect.height}px`;
|
|
416
|
+
placeholderDiv.style.flexShrink = "0";
|
|
417
|
+
iframe.parentElement?.insertBefore(placeholderDiv, iframe);
|
|
418
|
+
iframe.removeAttribute("scrolling");
|
|
419
|
+
clearAncestorClipping();
|
|
374
420
|
Object.assign(iframe.style, {
|
|
375
421
|
position: "fixed",
|
|
376
422
|
top: "0",
|
|
@@ -385,7 +431,9 @@ function setupGadgetBreakout(iframe) {
|
|
|
385
431
|
maxHeight: "none",
|
|
386
432
|
margin: "0",
|
|
387
433
|
transform: "none",
|
|
388
|
-
pointerEvents: "
|
|
434
|
+
pointerEvents: "none",
|
|
435
|
+
overflow: "visible",
|
|
436
|
+
visibility: "hidden"
|
|
389
437
|
});
|
|
390
438
|
iframe.contentWindow?.postMessage(
|
|
391
439
|
{
|
|
@@ -394,15 +442,35 @@ function setupGadgetBreakout(iframe) {
|
|
|
394
442
|
},
|
|
395
443
|
"*"
|
|
396
444
|
);
|
|
445
|
+
requestAnimationFrame(
|
|
446
|
+
() => requestAnimationFrame(() => {
|
|
447
|
+
iframe.style.visibility = "visible";
|
|
448
|
+
})
|
|
449
|
+
);
|
|
397
450
|
}
|
|
398
451
|
if (d.type === "gadget-exit-breakout") {
|
|
452
|
+
if (placeholderDiv) {
|
|
453
|
+
placeholderDiv.remove();
|
|
454
|
+
placeholderDiv = null;
|
|
455
|
+
}
|
|
399
456
|
for (const key of STYLE_KEYS) {
|
|
400
457
|
iframe.style[key] = savedStyles[key] ?? "";
|
|
401
458
|
}
|
|
459
|
+
if (savedStyles["_scrolling"]) {
|
|
460
|
+
iframe.setAttribute("scrolling", savedStyles["_scrolling"]);
|
|
461
|
+
}
|
|
462
|
+
restoreAncestorClipping();
|
|
402
463
|
}
|
|
403
464
|
}
|
|
404
465
|
window.addEventListener("message", handler);
|
|
405
|
-
return () =>
|
|
466
|
+
return () => {
|
|
467
|
+
if (placeholderDiv) {
|
|
468
|
+
placeholderDiv.remove();
|
|
469
|
+
placeholderDiv = null;
|
|
470
|
+
}
|
|
471
|
+
restoreAncestorClipping();
|
|
472
|
+
window.removeEventListener("message", handler);
|
|
473
|
+
};
|
|
406
474
|
}
|
|
407
475
|
export {
|
|
408
476
|
createGadgetRenderer,
|
package/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../libs/gadgets/src/lib/createGadgetRenderer.ts", "../../../libs/gadgets/src/lib/setupGadgetBreakout.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Creates a self-contained HTML string for rendering a gadget component in an iframe.\n *\n * Loads React 18, ReactDOM, Babel (for JSX transpilation), and Tailwind CSS from CDNs.\n * Sets up the full gadget bridge: state, breakout, actions, chat, context, and hot-swap.\n *\n * Usage:\n * import { createGadgetRenderer, setupGadgetBreakout } from '@gigabuddy/chat';\n *\n * const html = createGadgetRenderer(componentCode, data, { viewport: 'compact', stateEnabled: true });\n * iframe.srcdoc = html;\n * setupGadgetBreakout(iframe);\n *\n * The gadget component receives these props:\n * function Gadget({ data, viewport, state, userId, breakout, chat, context })\n *\n * Bridge protocol (postMessage):\n * - Host \u2192 iframe: `gadget-set-data` \u2014 push new props\n * - Host \u2192 iframe: `gadget-state-init` / `gadget-state-update` \u2014 state changes\n * - Host \u2192 iframe: `gadget-update-component` \u2014 hot-swap component code\n * - Host \u2192 iframe: `gadget-breakout-started` \u2014 breakout activated with originalRect\n * - iframe \u2192 Host: `gadget-interaction` \u2014 user dispatched an action\n * - iframe \u2192 Host: `gadget-resize` \u2014 auto-height\n * - iframe \u2192 Host: `gadget-request-breakout` / `gadget-exit-breakout` \u2014 breakout lifecycle\n * - iframe \u2192 Host: `gadget-action-request` / `gadget-chat-request` / `gadget-context-request` \u2014 API calls\n */\nexport function createGadgetRenderer(\n componentCode: string,\n data: unknown = {},\n options?: {\n viewport?: 'compact' | 'full' | 'mobile';\n stateEnabled?: boolean;\n chatEnabled?: boolean;\n contextEnabled?: boolean;\n assets?: Record<string, { url: string; name: string }>;\n },\n): string {\n const serializedData = JSON.stringify(data);\n const viewport = options?.viewport ?? 'full';\n const stateEnabled = options?.stateEnabled ?? false;\n const chatEnabled = options?.chatEnabled ?? false;\n const contextEnabled = options?.contextEnabled ?? false;\n const escapedCode = componentCode.replace(/<\\/script>/g, '<\\\\/script>');\n const serializedAssets = JSON.stringify(options?.assets ?? {});\n\n const stateBridgeScript = stateEnabled\n ? `\n window.gadget.state = {\n shared: {},\n user: {},\n userId: null,\n _sharedCallbacks: [],\n _userCallbacks: [],\n\n dispatch: function(action, data) {\n window.parent.postMessage({ type: 'gadget-interaction', action: action, data: data || {} }, '*');\n },\n\n onSharedChange: function(callback) {\n window.gadget.state._sharedCallbacks.push(callback);\n callback(window.gadget.state.shared);\n return function() {\n var i = window.gadget.state._sharedCallbacks.indexOf(callback);\n if (i !== -1) window.gadget.state._sharedCallbacks.splice(i, 1);\n };\n },\n\n onUserChange: function(callback) {\n window.gadget.state._userCallbacks.push(callback);\n callback(window.gadget.state.user);\n return function() {\n var i = window.gadget.state._userCallbacks.indexOf(callback);\n if (i !== -1) window.gadget.state._userCallbacks.splice(i, 1);\n };\n },\n };\n\n window.addEventListener('message', function(event) {\n var d = event.data;\n if (d && (d.type === 'gadget-state-init' || d.type === 'gadget-state-update')) {\n if (d.userId !== undefined) {\n window.gadget.state.userId = d.userId;\n }\n if (d.shared !== undefined) {\n window.gadget.state.shared = d.shared;\n window.gadget.state._sharedCallbacks.forEach(function(cb) { try { cb(d.shared); } catch(e) {} });\n }\n if (d.user !== undefined) {\n window.gadget.state.user = d.user;\n window.gadget.state._userCallbacks.forEach(function(cb) { try { cb(d.user); } catch(e) {} });\n }\n window.__rerenderGadget();\n }\n });`\n : '';\n\n const chatBridgeScript = chatEnabled\n ? `\n window.gadget.chat = {\n _request: function(method, params) {\n var requestId = Math.random().toString(36).slice(2) + Date.now().toString(36);\n return new Promise(function(resolve, reject) {\n var handler = function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-chat-response' && d.requestId === requestId) {\n window.removeEventListener('message', handler);\n if (d.error) reject(new Error(d.error));\n else resolve(d.result);\n }\n };\n window.addEventListener('message', handler);\n setTimeout(function() { window.removeEventListener('message', handler); reject(new Error('Timed out')); }, 30000);\n window.parent.postMessage({ type: 'gadget-chat-request', requestId: requestId, method: method, params: params || {} }, '*');\n });\n },\n\n _eventCallbacks: [],\n\n createChannel: function(opts) { return window.gadget.chat._request('createChannel', opts); },\n findOrCreateChannel: function(name, opts) { return window.gadget.chat._request('findOrCreateChannel', { name: name, description: opts && opts.description }); },\n listChannels: function() { return window.gadget.chat._request('listChannels'); },\n joinChannel: function(conversationId) { return window.gadget.chat._request('joinChannel', { conversationId: conversationId }); },\n sendMessage: function(conversationId, text) { return window.gadget.chat._request('sendMessage', { conversationId: conversationId, text: text }); },\n getMessages: function(conversationId, opts) { return window.gadget.chat._request('getMessages', { conversationId: conversationId, limit: opts && opts.limit, before: opts && opts.before }); },\n updateChannel: function(conversationId, updates) { return window.gadget.chat._request('updateChannel', { conversationId: conversationId, description: updates && updates.description }); },\n createInvite: function(conversationId) { return window.gadget.chat._request('createInvite', { conversationId: conversationId }); },\n redeemInvite: function(token) { return window.gadget.chat._request('redeemInvite', { token: token }); },\n\n onEvent: function(callback) {\n window.gadget.chat._eventCallbacks.push(callback);\n return function() {\n var i = window.gadget.chat._eventCallbacks.indexOf(callback);\n if (i !== -1) window.gadget.chat._eventCallbacks.splice(i, 1);\n };\n },\n };\n\n window.addEventListener('message', function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-chat-event') {\n window.gadget.chat._eventCallbacks.forEach(function(cb) { try { cb(d.event); } catch(e) {} });\n window.__rerenderGadget();\n }\n });`\n : '';\n\n const contextBridgeScript = contextEnabled\n ? `\n window.gadget.context = {\n _request: function(method, params) {\n var requestId = Math.random().toString(36).slice(2) + Date.now().toString(36);\n return new Promise(function(resolve, reject) {\n var handler = function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-context-response' && d.id === requestId) {\n window.removeEventListener('message', handler);\n if (d.error) reject(new Error(d.error));\n else resolve(d.result);\n }\n };\n window.addEventListener('message', handler);\n setTimeout(function() { window.removeEventListener('message', handler); reject(new Error('Timed out')); }, 30000);\n window.parent.postMessage({ type: 'gadget-context-request', id: requestId, method: method, params: params || {} }, '*');\n });\n },\n\n _eventCallbacks: [],\n\n listTypes: function() { return window.gadget.context._request('listTypes', {}); },\n getType: function(contextType) { return window.gadget.context._request('getType', { contextType: contextType }); },\n listInstances: function(params) { return window.gadget.context._request('listInstances', params || {}); },\n getInstance: function(id) { return window.gadget.context._request('getInstance', { id: id }); },\n createInstance: function(params) { return window.gadget.context._request('createInstance', params); },\n updateInstance: function(id, data) { return window.gadget.context._request('updateInstance', { id: id, data: data }); },\n deleteInstance: function(id) { return window.gadget.context._request('deleteInstance', { id: id }); },\n searchKnowledge: function(params) { return window.gadget.context._request('searchKnowledge', params); },\n getEnriched: function(id) { return window.gadget.context._request('getEnriched', { id: id }); },\n getRelated: function(id) { return window.gadget.context._request('getRelated', { id: id }); },\n\n onEvent: function(callback) {\n window.gadget.context._eventCallbacks.push(callback);\n return function() {\n var i = window.gadget.context._eventCallbacks.indexOf(callback);\n if (i !== -1) window.gadget.context._eventCallbacks.splice(i, 1);\n };\n },\n };\n\n window.addEventListener('message', function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-context-event') {\n window.gadget.context._eventCallbacks.forEach(function(cb) { try { cb(d.event); } catch(e) {} });\n window.__rerenderGadget();\n }\n });`\n : '';\n\n // When state is enabled, pass state prop alongside data and viewport\n const chatPropFragment = chatEnabled ? ', chat: window.gadget.chat' : '';\n const contextPropFragment = contextEnabled ? ', context: window.gadget.context' : '';\n const breakoutPropFragment =\n ', breakout: { active: window.gadget.breakout._active, originalRect: window.gadget.breakout._originalRect, request: window.gadget.breakout.request, exit: window.gadget.breakout.exit }';\n const componentProps = stateEnabled\n ? `{ data: window.__GADGET_DATA__, viewport: window.__GADGET_VIEWPORT__, state: window.gadget.state ? { shared: window.gadget.state.shared, user: window.gadget.state.user } : undefined, userId: window.gadget.state ? window.gadget.state.userId : undefined${chatPropFragment}${contextPropFragment}${breakoutPropFragment} }`\n : `{ data: window.__GADGET_DATA__, viewport: window.__GADGET_VIEWPORT__${chatPropFragment}${contextPropFragment}${breakoutPropFragment} }`;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <script src=\"https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js\"></script>\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { background: transparent; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; min-height: 100%; }\n #root { min-height: 100vh; }\n .gadget-error {\n padding: 16px;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 8px;\n color: #991b1b;\n font-family: monospace;\n font-size: 13px;\n white-space: pre-wrap;\n word-break: break-word;\n }\n .gadget-error-title {\n font-weight: 600;\n margin-bottom: 8px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n </style>\n</head>\n<body>\n <div id=\"root\"></div>\n <script>\n window.__GADGET_DATA__ = ${serializedData};\n window.__GADGET_VIEWPORT__ = '${viewport}';\n\n window.gadget = {\n data: window.__GADGET_DATA__,\n assets: ${serializedAssets},\n\n breakout: {\n _active: false,\n _originalRect: null,\n request: function() {\n if (window.gadget.breakout._active) return;\n window.parent.postMessage({ type: 'gadget-request-breakout' }, '*');\n },\n exit: function() {\n if (!window.gadget.breakout._active) return;\n window.gadget.breakout._active = false;\n window.gadget.breakout._originalRect = null;\n window.parent.postMessage({ type: 'gadget-exit-breakout' }, '*');\n window.__rerenderGadget();\n },\n },\n\n callAction: function(input) {\n var requestId = Math.random().toString(36).slice(2) + Date.now().toString(36);\n return new Promise(function(resolve, reject) {\n var handler = function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-action-response' && d.requestId === requestId) {\n window.removeEventListener('message', handler);\n if (d.error) reject(new Error(d.error));\n else resolve(d.result);\n }\n };\n window.addEventListener('message', handler);\n setTimeout(function() {\n window.removeEventListener('message', handler);\n reject(new Error('Action timed out'));\n }, 30000);\n window.parent.postMessage({ type: 'gadget-action-request', requestId: requestId, input: input }, '*');\n });\n },\n };\n${stateBridgeScript}\n${chatBridgeScript}\n${contextBridgeScript}\n\n window.__gadgetRoot = null;\n window.__gadgetComponent = null;\n\n window.__rerenderGadget = function() {\n if (window.__gadgetRoot && window.__gadgetComponent) {\n var C = window.__gadgetComponent;\n var EB = window.__gadgetErrorBoundary;\n window.__gadgetRoot.render(\n React.createElement(EB, null, React.createElement(C, ${componentProps}))\n );\n }\n };\n\n window.addEventListener('message', function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-set-data') {\n window.__GADGET_DATA__ = d.data;\n window.gadget.data = d.data;\n window.__rerenderGadget();\n }\n if (d && d.type === 'gadget-breakout-started') {\n window.gadget.breakout._active = true;\n window.gadget.breakout._originalRect = d.originalRect || null;\n window.__rerenderGadget();\n }\n if (d && d.type === 'gadget-update-component' && d.code) {\n try {\n var compiled = Babel.transform(d.code, { presets: ['react'] }).code;\n var fn = new Function('React', 'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef',\n compiled + '\\\\nreturn typeof Gadget !== \"undefined\" ? Gadget : null;');\n var NewComponent = fn(React, React.useState, React.useEffect, React.useCallback, React.useMemo, React.useRef);\n if (NewComponent) {\n window.__gadgetComponent = NewComponent;\n if (window.__gadgetErrorBoundaryInstance) {\n window.__gadgetErrorBoundaryInstance.setState({ error: null });\n }\n window.__rerenderGadget();\n }\n } catch (err) {\n console.error('[gadget-hmr] Component update failed:', err);\n }\n }\n });\n </script>\n <script type=\"text/babel\" data-type=\"module\">\n const { useState, useEffect, useCallback, useMemo, useRef } = React;\n\n class ErrorBoundary extends React.Component {\n constructor(props) {\n super(props);\n this.state = { error: null };\n window.__gadgetErrorBoundaryInstance = this;\n }\n static getDerivedStateFromError(error) {\n return { error };\n }\n render() {\n if (this.state.error) {\n return React.createElement('div', { className: 'gadget-error' },\n React.createElement('div', { className: 'gadget-error-title' }, 'Runtime Error'),\n this.state.error.message\n );\n }\n return this.props.children;\n }\n }\n window.__gadgetErrorBoundary = ErrorBoundary;\n\n try {\n ${escapedCode}\n\n const ComponentToRender = typeof Gadget !== 'undefined' ? Gadget : null;\n\n if (ComponentToRender) {\n window.__gadgetComponent = ComponentToRender;\n const root = ReactDOM.createRoot(document.getElementById('root'));\n window.__gadgetRoot = root;\n root.render(\n <ErrorBoundary>\n <ComponentToRender data={window.__GADGET_DATA__} viewport={window.__GADGET_VIEWPORT__}${stateEnabled ? ' state={window.gadget.state ? { shared: window.gadget.state.shared, user: window.gadget.state.user } : undefined} userId={window.gadget.state ? window.gadget.state.userId : undefined}' : ''}${chatEnabled ? ' chat={window.gadget.chat}' : ''}${contextEnabled ? ' context={window.gadget.context}' : ''} breakout={{ active: window.gadget.breakout._active, originalRect: window.gadget.breakout._originalRect, request: window.gadget.breakout.request, exit: window.gadget.breakout.exit }} />\n </ErrorBoundary>\n );\n } else {\n document.getElementById('root').innerHTML =\n '<div class=\"gadget-error\"><div class=\"gadget-error-title\">No component found</div>Define a function called Gadget.</div>';\n }\n } catch (err) {\n document.getElementById('root').innerHTML =\n '<div class=\"gadget-error\"><div class=\"gadget-error-title\">Error</div>' +\n err.message.replace(/</g, '<').replace(/>/g, '>') +\n '</div>';\n }\n\n const rootEl = document.getElementById('root');\n const observer = new ResizeObserver(() => {\n window.parent.postMessage({\n type: 'gadget-resize',\n height: rootEl.scrollHeight,\n }, '*');\n });\n observer.observe(rootEl);\n </script>\n</body>\n</html>`;\n}\n", "/**\n * Enables the breakout protocol on a gadget iframe.\n *\n * When a gadget calls `breakout.request()`, this handler promotes the iframe\n * to a fullscreen transparent overlay so the gadget can render across the\n * entire viewport (e.g. dice rolls, confetti, fullscreen interactions).\n * When the gadget calls `breakout.exit()`, the iframe is restored.\n *\n * Usage:\n * import { setupGadgetBreakout } from '@gigabuddy/chat';\n * const cleanup = setupGadgetBreakout(iframeElement);\n *\n * Inside gadget component code, use the `breakout` prop:\n * function Gadget({ breakout, ...props }) {\n * // breakout.active \u2014 boolean, true when in fullscreen mode\n * // breakout.originalRect \u2014 { x, y, width, height } of the original position\n * // breakout.request() \u2014 enter fullscreen overlay\n * // breakout.exit() \u2014 return to inline\n * }\n */\nexport function setupGadgetBreakout(iframe: HTMLIFrameElement): () => void {\n const savedStyles: Record<string, string> = {};\n const STYLE_KEYS = [\n 'position',\n 'top',\n 'left',\n 'width',\n 'height',\n 'zIndex',\n 'background',\n 'border',\n 'borderRadius',\n 'maxWidth',\n 'maxHeight',\n 'margin',\n 'transform',\n 'pointerEvents',\n ] as const;\n\n function handler(event: MessageEvent): void {\n if (event.source !== iframe.contentWindow) return;\n const d = event.data;\n if (!d || typeof d !== 'object') return;\n\n if (d.type === 'gadget-request-breakout') {\n const rect = iframe.getBoundingClientRect();\n\n // Save current inline styles\n for (const key of STYLE_KEYS) {\n savedStyles[key] = iframe.style[key as keyof CSSStyleDeclaration] as string;\n }\n\n // Promote to fullscreen overlay\n Object.assign(iframe.style, {\n position: 'fixed',\n top: '0',\n left: '0',\n width: '100vw',\n height: '100vh',\n zIndex: '99999',\n background: 'transparent',\n border: 'none',\n borderRadius: '0',\n maxWidth: 'none',\n maxHeight: 'none',\n margin: '0',\n transform: 'none',\n pointerEvents: 'auto',\n });\n\n // Tell the gadget its original position for seamless visual continuity\n iframe.contentWindow?.postMessage(\n {\n type: 'gadget-breakout-started',\n originalRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },\n },\n '*',\n );\n }\n\n if (d.type === 'gadget-exit-breakout') {\n for (const key of STYLE_KEYS) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (iframe.style as any)[key] = savedStyles[key] ?? '';\n }\n }\n }\n\n window.addEventListener('message', handler);\n return () => window.removeEventListener('message', handler);\n}\n"],
|
|
5
|
-
"mappings": ";AA0BO,SAAS,qBACd,eACA,OAAgB,CAAC,GACjB,SAOQ;AACR,QAAM,iBAAiB,KAAK,UAAU,IAAI;AAC1C,QAAM,WAAW,SAAS,YAAY;AACtC,QAAM,eAAe,SAAS,gBAAgB;AAC9C,QAAM,cAAc,SAAS,eAAe;AAC5C,QAAM,iBAAiB,SAAS,kBAAkB;AAClD,QAAM,cAAc,cAAc,QAAQ,eAAe,aAAa;AACtE,QAAM,mBAAmB,KAAK,UAAU,SAAS,UAAU,CAAC,CAAC;AAE7D,QAAM,oBAAoB,eACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAgDA;AAEJ,QAAM,mBAAmB,cACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WA+CA;AAEJ,QAAM,sBAAsB,iBACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAgDA;AAGJ,QAAM,mBAAmB,cAAc,+BAA+B;AACtE,QAAM,sBAAsB,iBAAiB,qCAAqC;AAClF,QAAM,uBACJ;AACF,QAAM,iBAAiB,eACnB,8PAA8P,gBAAgB,GAAG,mBAAmB,GAAG,oBAAoB,OAC3T,uEAAuE,gBAAgB,GAAG,mBAAmB,GAAG,oBAAoB;AAExI,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAkCsB,cAAc;AAAA,oCACT,QAAQ;AAAA;AAAA;AAAA;AAAA,gBAI5B,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsC9B,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iEAU4C,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QA6DvE,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oGAUiF,eAAe,4LAA4L,EAAE,GAAG,cAAc,+BAA+B,EAAE,GAAG,iBAAiB,qCAAqC,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyB9Z;;;ACnXO,SAAS,oBAAoB,QAAuC;AACzE,QAAM,cAAsC,CAAC;AAC7C,QAAM,aAAa;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,WAAS,QAAQ,OAA2B;AAC1C,QAAI,MAAM,WAAW,OAAO;AAAe;AAC3C,UAAM,IAAI,MAAM;AAChB,QAAI,CAAC,KAAK,OAAO,MAAM;AAAU;AAEjC,QAAI,EAAE,SAAS,2BAA2B;AACxC,YAAM,OAAO,OAAO,sBAAsB;AAG1C,iBAAW,OAAO,YAAY;AAC5B,oBAAY,GAAG,IAAI,OAAO,MAAM,GAAgC;AAAA,MAClE;
|
|
4
|
+
"sourcesContent": ["/**\n * Creates a self-contained HTML string for rendering a gadget component in an iframe.\n *\n * Loads React 18, ReactDOM, Babel (for JSX transpilation), and Tailwind CSS from CDNs.\n * Sets up the full gadget bridge: state, breakout, actions, chat, context, and hot-swap.\n *\n * Usage:\n * import { createGadgetRenderer, setupGadgetBreakout } from '@gigabuddy/chat';\n *\n * const html = createGadgetRenderer(componentCode, data, { viewport: 'compact', stateEnabled: true });\n * iframe.srcdoc = html;\n * setupGadgetBreakout(iframe);\n *\n * The gadget component receives these props:\n * function Gadget({ data, viewport, state, userId, breakout, chat, context })\n *\n * Bridge protocol (postMessage):\n * - Host \u2192 iframe: `gadget-set-data` \u2014 push new props\n * - Host \u2192 iframe: `gadget-state-init` / `gadget-state-update` \u2014 state changes\n * - Host \u2192 iframe: `gadget-update-component` \u2014 hot-swap component code\n * - Host \u2192 iframe: `gadget-breakout-started` \u2014 breakout activated with originalRect\n * - iframe \u2192 Host: `gadget-interaction` \u2014 user dispatched an action\n * - iframe \u2192 Host: `gadget-resize` \u2014 auto-height\n * - iframe \u2192 Host: `gadget-request-breakout` / `gadget-exit-breakout` \u2014 breakout lifecycle\n * - iframe \u2192 Host: `gadget-action-request` / `gadget-chat-request` / `gadget-context-request` \u2014 API calls\n */\nexport function createGadgetRenderer(\n componentCode: string,\n data: unknown = {},\n options?: {\n viewport?: 'compact' | 'full' | 'mobile';\n stateEnabled?: boolean;\n chatEnabled?: boolean;\n contextEnabled?: boolean;\n assets?: Record<string, { url: string; name: string }>;\n },\n): string {\n const serializedData = JSON.stringify(data);\n const viewport = options?.viewport ?? 'full';\n const stateEnabled = options?.stateEnabled ?? false;\n const chatEnabled = options?.chatEnabled ?? false;\n const contextEnabled = options?.contextEnabled ?? false;\n const escapedCode = componentCode.replace(/<\\/script>/g, '<\\\\/script>');\n const serializedAssets = JSON.stringify(options?.assets ?? {});\n\n const stateBridgeScript = stateEnabled\n ? `\n window.gadget.state = {\n shared: {},\n user: {},\n userId: null,\n _sharedCallbacks: [],\n _userCallbacks: [],\n\n dispatch: function(action, data) {\n window.parent.postMessage({ type: 'gadget-interaction', action: action, data: data || {} }, '*');\n },\n\n onSharedChange: function(callback) {\n window.gadget.state._sharedCallbacks.push(callback);\n callback(window.gadget.state.shared);\n return function() {\n var i = window.gadget.state._sharedCallbacks.indexOf(callback);\n if (i !== -1) window.gadget.state._sharedCallbacks.splice(i, 1);\n };\n },\n\n onUserChange: function(callback) {\n window.gadget.state._userCallbacks.push(callback);\n callback(window.gadget.state.user);\n return function() {\n var i = window.gadget.state._userCallbacks.indexOf(callback);\n if (i !== -1) window.gadget.state._userCallbacks.splice(i, 1);\n };\n },\n };\n\n window.addEventListener('message', function(event) {\n var d = event.data;\n if (d && (d.type === 'gadget-state-init' || d.type === 'gadget-state-update')) {\n if (d.userId !== undefined) {\n window.gadget.state.userId = d.userId;\n }\n if (d.shared !== undefined) {\n window.gadget.state.shared = d.shared;\n window.gadget.state._sharedCallbacks.forEach(function(cb) { try { cb(d.shared); } catch(e) {} });\n }\n if (d.user !== undefined) {\n window.gadget.state.user = d.user;\n window.gadget.state._userCallbacks.forEach(function(cb) { try { cb(d.user); } catch(e) {} });\n }\n window.__rerenderGadget();\n }\n });`\n : '';\n\n const chatBridgeScript = chatEnabled\n ? `\n window.gadget.chat = {\n _request: function(method, params) {\n var requestId = Math.random().toString(36).slice(2) + Date.now().toString(36);\n return new Promise(function(resolve, reject) {\n var handler = function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-chat-response' && d.requestId === requestId) {\n window.removeEventListener('message', handler);\n if (d.error) reject(new Error(d.error));\n else resolve(d.result);\n }\n };\n window.addEventListener('message', handler);\n setTimeout(function() { window.removeEventListener('message', handler); reject(new Error('Timed out')); }, 30000);\n window.parent.postMessage({ type: 'gadget-chat-request', requestId: requestId, method: method, params: params || {} }, '*');\n });\n },\n\n _eventCallbacks: [],\n\n createChannel: function(opts) { return window.gadget.chat._request('createChannel', opts); },\n findOrCreateChannel: function(name, opts) { return window.gadget.chat._request('findOrCreateChannel', { name: name, description: opts && opts.description }); },\n listChannels: function() { return window.gadget.chat._request('listChannels'); },\n joinChannel: function(conversationId) { return window.gadget.chat._request('joinChannel', { conversationId: conversationId }); },\n sendMessage: function(conversationId, text) { return window.gadget.chat._request('sendMessage', { conversationId: conversationId, text: text }); },\n getMessages: function(conversationId, opts) { return window.gadget.chat._request('getMessages', { conversationId: conversationId, limit: opts && opts.limit, before: opts && opts.before }); },\n updateChannel: function(conversationId, updates) { return window.gadget.chat._request('updateChannel', { conversationId: conversationId, description: updates && updates.description }); },\n createInvite: function(conversationId) { return window.gadget.chat._request('createInvite', { conversationId: conversationId }); },\n redeemInvite: function(token) { return window.gadget.chat._request('redeemInvite', { token: token }); },\n\n onEvent: function(callback) {\n window.gadget.chat._eventCallbacks.push(callback);\n return function() {\n var i = window.gadget.chat._eventCallbacks.indexOf(callback);\n if (i !== -1) window.gadget.chat._eventCallbacks.splice(i, 1);\n };\n },\n };\n\n window.addEventListener('message', function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-chat-event') {\n window.gadget.chat._eventCallbacks.forEach(function(cb) { try { cb(d.event); } catch(e) {} });\n window.__rerenderGadget();\n }\n });`\n : '';\n\n const contextBridgeScript = contextEnabled\n ? `\n window.gadget.context = {\n _request: function(method, params) {\n var requestId = Math.random().toString(36).slice(2) + Date.now().toString(36);\n return new Promise(function(resolve, reject) {\n var handler = function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-context-response' && d.id === requestId) {\n window.removeEventListener('message', handler);\n if (d.error) reject(new Error(d.error));\n else resolve(d.result);\n }\n };\n window.addEventListener('message', handler);\n setTimeout(function() { window.removeEventListener('message', handler); reject(new Error('Timed out')); }, 30000);\n window.parent.postMessage({ type: 'gadget-context-request', id: requestId, method: method, params: params || {} }, '*');\n });\n },\n\n _eventCallbacks: [],\n\n listTypes: function() { return window.gadget.context._request('listTypes', {}); },\n getType: function(contextType) { return window.gadget.context._request('getType', { contextType: contextType }); },\n listInstances: function(params) { return window.gadget.context._request('listInstances', params || {}); },\n getInstance: function(id) { return window.gadget.context._request('getInstance', { id: id }); },\n createInstance: function(params) { return window.gadget.context._request('createInstance', params); },\n updateInstance: function(id, data) { return window.gadget.context._request('updateInstance', { id: id, data: data }); },\n deleteInstance: function(id) { return window.gadget.context._request('deleteInstance', { id: id }); },\n searchKnowledge: function(params) { return window.gadget.context._request('searchKnowledge', params); },\n getEnriched: function(id) { return window.gadget.context._request('getEnriched', { id: id }); },\n getRelated: function(id) { return window.gadget.context._request('getRelated', { id: id }); },\n\n onEvent: function(callback) {\n window.gadget.context._eventCallbacks.push(callback);\n return function() {\n var i = window.gadget.context._eventCallbacks.indexOf(callback);\n if (i !== -1) window.gadget.context._eventCallbacks.splice(i, 1);\n };\n },\n };\n\n window.addEventListener('message', function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-context-event') {\n window.gadget.context._eventCallbacks.forEach(function(cb) { try { cb(d.event); } catch(e) {} });\n window.__rerenderGadget();\n }\n });`\n : '';\n\n // When state is enabled, pass state prop alongside data and viewport\n const chatPropFragment = chatEnabled ? ', chat: window.gadget.chat' : '';\n const contextPropFragment = contextEnabled ? ', context: window.gadget.context' : '';\n const breakoutPropFragment =\n ', breakout: { active: window.gadget.breakout._active, originalRect: window.gadget.breakout._originalRect, request: window.gadget.breakout.request, exit: window.gadget.breakout.exit }';\n const componentProps = stateEnabled\n ? `{ data: window.__GADGET_DATA__, viewport: window.__GADGET_VIEWPORT__, state: window.gadget.state ? { shared: window.gadget.state.shared, user: window.gadget.state.user } : undefined, userId: window.gadget.state ? window.gadget.state.userId : undefined${chatPropFragment}${contextPropFragment}${breakoutPropFragment} }`\n : `{ data: window.__GADGET_DATA__, viewport: window.__GADGET_VIEWPORT__${chatPropFragment}${contextPropFragment}${breakoutPropFragment} }`;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <script src=\"https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js\"></script>\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { background: transparent; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; min-height: 100%; }\n #root { min-height: 100vh; }\n .gadget-error {\n padding: 16px;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 8px;\n color: #991b1b;\n font-family: monospace;\n font-size: 13px;\n white-space: pre-wrap;\n word-break: break-word;\n }\n .gadget-error-title {\n font-weight: 600;\n margin-bottom: 8px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n </style>\n</head>\n<body>\n <div id=\"root\"></div>\n <script>\n window.__GADGET_DATA__ = ${serializedData};\n window.__GADGET_VIEWPORT__ = '${viewport}';\n\n window.gadget = {\n data: window.__GADGET_DATA__,\n assets: ${serializedAssets},\n\n breakout: {\n _active: false,\n _originalRect: null,\n request: function() {\n if (window.gadget.breakout._active) return;\n window.parent.postMessage({ type: 'gadget-request-breakout' }, '*');\n },\n exit: function() {\n if (!window.gadget.breakout._active) return;\n window.gadget.breakout._active = false;\n window.gadget.breakout._originalRect = null;\n window.parent.postMessage({ type: 'gadget-exit-breakout' }, '*');\n window.__rerenderGadget();\n },\n },\n\n callAction: function(input) {\n var requestId = Math.random().toString(36).slice(2) + Date.now().toString(36);\n return new Promise(function(resolve, reject) {\n var handler = function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-action-response' && d.requestId === requestId) {\n window.removeEventListener('message', handler);\n if (d.error) reject(new Error(d.error));\n else resolve(d.result);\n }\n };\n window.addEventListener('message', handler);\n setTimeout(function() {\n window.removeEventListener('message', handler);\n reject(new Error('Action timed out'));\n }, 30000);\n window.parent.postMessage({ type: 'gadget-action-request', requestId: requestId, input: input }, '*');\n });\n },\n };\n${stateBridgeScript}\n${chatBridgeScript}\n${contextBridgeScript}\n\n window.__gadgetRoot = null;\n window.__gadgetComponent = null;\n\n window.__rerenderGadget = function() {\n if (window.__gadgetRoot && window.__gadgetComponent) {\n var C = window.__gadgetComponent;\n var EB = window.__gadgetErrorBoundary;\n window.__gadgetRoot.render(\n React.createElement(EB, null, React.createElement(C, ${componentProps}))\n );\n }\n };\n\n window.addEventListener('message', function(event) {\n var d = event.data;\n if (d && d.type === 'gadget-set-data') {\n window.__GADGET_DATA__ = d.data;\n window.gadget.data = d.data;\n window.__rerenderGadget();\n }\n if (d && d.type === 'gadget-breakout-started') {\n window.gadget.breakout._active = true;\n window.gadget.breakout._originalRect = d.originalRect || null;\n window.__rerenderGadget();\n }\n if (d && d.type === 'gadget-update-component' && d.code) {\n try {\n var compiled = Babel.transform(d.code, { presets: ['react'] }).code;\n var fn = new Function('React', 'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef',\n compiled + '\\\\nreturn typeof Gadget !== \"undefined\" ? Gadget : null;');\n var NewComponent = fn(React, React.useState, React.useEffect, React.useCallback, React.useMemo, React.useRef);\n if (NewComponent) {\n window.__gadgetComponent = NewComponent;\n if (window.__gadgetErrorBoundaryInstance) {\n window.__gadgetErrorBoundaryInstance.setState({ error: null });\n }\n window.__rerenderGadget();\n }\n } catch (err) {\n console.error('[gadget-hmr] Component update failed:', err);\n }\n }\n });\n </script>\n <script type=\"text/babel\" data-type=\"module\">\n const { useState, useEffect, useCallback, useMemo, useRef } = React;\n\n class ErrorBoundary extends React.Component {\n constructor(props) {\n super(props);\n this.state = { error: null };\n window.__gadgetErrorBoundaryInstance = this;\n }\n static getDerivedStateFromError(error) {\n return { error };\n }\n render() {\n if (this.state.error) {\n return React.createElement('div', { className: 'gadget-error' },\n React.createElement('div', { className: 'gadget-error-title' }, 'Runtime Error'),\n this.state.error.message\n );\n }\n return this.props.children;\n }\n }\n window.__gadgetErrorBoundary = ErrorBoundary;\n\n try {\n ${escapedCode}\n\n const ComponentToRender = typeof Gadget !== 'undefined' ? Gadget : null;\n\n if (ComponentToRender) {\n window.__gadgetComponent = ComponentToRender;\n const root = ReactDOM.createRoot(document.getElementById('root'));\n window.__gadgetRoot = root;\n root.render(\n <ErrorBoundary>\n <ComponentToRender data={window.__GADGET_DATA__} viewport={window.__GADGET_VIEWPORT__}${stateEnabled ? ' state={window.gadget.state ? { shared: window.gadget.state.shared, user: window.gadget.state.user } : undefined} userId={window.gadget.state ? window.gadget.state.userId : undefined}' : ''}${chatEnabled ? ' chat={window.gadget.chat}' : ''}${contextEnabled ? ' context={window.gadget.context}' : ''} breakout={{ active: window.gadget.breakout._active, originalRect: window.gadget.breakout._originalRect, request: window.gadget.breakout.request, exit: window.gadget.breakout.exit }} />\n </ErrorBoundary>\n );\n } else {\n document.getElementById('root').innerHTML =\n '<div class=\"gadget-error\"><div class=\"gadget-error-title\">No component found</div>Define a function called Gadget.</div>';\n }\n } catch (err) {\n document.getElementById('root').innerHTML =\n '<div class=\"gadget-error\"><div class=\"gadget-error-title\">Error</div>' +\n err.message.replace(/</g, '<').replace(/>/g, '>') +\n '</div>';\n }\n\n const rootEl = document.getElementById('root');\n const observer = new ResizeObserver(() => {\n window.parent.postMessage({\n type: 'gadget-resize',\n height: rootEl.scrollHeight,\n }, '*');\n });\n observer.observe(rootEl);\n </script>\n</body>\n</html>`;\n}\n", "/**\n * Enables the breakout protocol on a gadget iframe.\n *\n * When a gadget calls `breakout.request()`, this handler promotes the iframe\n * to a fullscreen transparent overlay so the gadget can render across the\n * entire viewport (e.g. dice rolls, confetti, fullscreen interactions).\n * When the gadget calls `breakout.exit()`, the iframe is restored.\n *\n * Usage:\n * import { setupGadgetBreakout } from '@gigabuddy/chat';\n * const cleanup = setupGadgetBreakout(iframeElement);\n *\n * Inside gadget component code, use the `breakout` prop:\n * function Gadget({ breakout, ...props }) {\n * // breakout.active \u2014 boolean, true when in fullscreen mode\n * // breakout.originalRect \u2014 { x, y, width, height } of the original position\n * // breakout.request() \u2014 enter fullscreen overlay\n * // breakout.exit() \u2014 return to inline\n * }\n */\nexport function setupGadgetBreakout(iframe: HTMLIFrameElement): () => void {\n const savedStyles: Record<string, string> = {};\n const STYLE_KEYS = [\n 'position',\n 'top',\n 'left',\n 'width',\n 'height',\n 'zIndex',\n 'background',\n 'border',\n 'borderRadius',\n 'maxWidth',\n 'maxHeight',\n 'margin',\n 'transform',\n 'pointerEvents',\n 'overflow',\n 'visibility',\n ] as const;\n\n // Track ancestor overrides so we can restore them\n const ancestorOverrides: { el: HTMLElement; key: string; original: string }[] = [];\n let placeholderDiv: HTMLDivElement | null = null;\n\n function clearAncestorClipping(): void {\n let el = iframe.parentElement;\n while (el && el !== document.body && el !== document.documentElement) {\n const computed = getComputedStyle(el);\n const overflow = computed.overflow + computed.overflowX + computed.overflowY;\n const hasClipping = /hidden|auto|scroll|clip/.test(overflow);\n const hasContainingBlock =\n computed.transform !== 'none' ||\n computed.willChange === 'transform' ||\n computed.contain !== 'none' ||\n computed.filter !== 'none';\n\n if (hasClipping || hasContainingBlock) {\n if (hasClipping) {\n ancestorOverrides.push({ el, key: 'overflow', original: el.style.overflow });\n ancestorOverrides.push({ el, key: 'overflowX', original: el.style.overflowX });\n ancestorOverrides.push({ el, key: 'overflowY', original: el.style.overflowY });\n el.style.overflow = 'visible';\n el.style.overflowX = 'visible';\n el.style.overflowY = 'visible';\n }\n if (hasContainingBlock && computed.transform !== 'none') {\n ancestorOverrides.push({ el, key: 'transform', original: el.style.transform });\n el.style.transform = 'none';\n }\n if (hasContainingBlock && computed.contain !== 'none') {\n ancestorOverrides.push({ el, key: 'contain', original: el.style.contain });\n el.style.contain = 'none';\n }\n }\n el = el.parentElement;\n }\n }\n\n function restoreAncestorClipping(): void {\n for (const { el, key, original } of ancestorOverrides) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (el.style as any)[key] = original;\n }\n ancestorOverrides.length = 0;\n }\n\n function handler(event: MessageEvent): void {\n if (event.source !== iframe.contentWindow) return;\n const d = event.data;\n if (!d || typeof d !== 'object') return;\n\n if (d.type === 'gadget-request-breakout') {\n const rect = iframe.getBoundingClientRect();\n\n // Save current inline styles and attributes\n for (const key of STYLE_KEYS) {\n savedStyles[key] = iframe.style[key as keyof CSSStyleDeclaration] as string;\n }\n savedStyles['_scrolling'] = iframe.getAttribute('scrolling') ?? '';\n\n // Insert a placeholder to preserve layout space while iframe is position:fixed\n placeholderDiv = document.createElement('div');\n placeholderDiv.style.width = `${rect.width}px`;\n placeholderDiv.style.height = `${rect.height}px`;\n placeholderDiv.style.flexShrink = '0';\n iframe.parentElement?.insertBefore(placeholderDiv, iframe);\n\n // Remove scrolling restriction during breakout\n iframe.removeAttribute('scrolling');\n\n // Neutralize ancestor clipping so position:fixed works viewport-wide\n clearAncestorClipping();\n\n // Promote to fullscreen overlay (hidden initially to avoid single-frame flicker\n // while gadget repositions its content based on originalRect)\n Object.assign(iframe.style, {\n position: 'fixed',\n top: '0',\n left: '0',\n width: '100vw',\n height: '100vh',\n zIndex: '99999',\n background: 'transparent',\n border: 'none',\n borderRadius: '0',\n maxWidth: 'none',\n maxHeight: 'none',\n margin: '0',\n transform: 'none',\n pointerEvents: 'none',\n overflow: 'visible',\n visibility: 'hidden',\n });\n\n // Tell the gadget its original position for seamless visual continuity\n iframe.contentWindow?.postMessage(\n {\n type: 'gadget-breakout-started',\n originalRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },\n },\n '*',\n );\n\n // Reveal after gadget has repositioned (double-rAF ensures both the\n // style application and the gadget's postMessage handler have run)\n requestAnimationFrame(() =>\n requestAnimationFrame(() => {\n iframe.style.visibility = 'visible';\n }),\n );\n }\n\n if (d.type === 'gadget-exit-breakout') {\n // Remove placeholder\n if (placeholderDiv) {\n placeholderDiv.remove();\n placeholderDiv = null;\n }\n\n // Restore iframe styles\n for (const key of STYLE_KEYS) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (iframe.style as any)[key] = savedStyles[key] ?? '';\n }\n if (savedStyles['_scrolling']) {\n iframe.setAttribute('scrolling', savedStyles['_scrolling']);\n }\n\n // Restore ancestor clipping\n restoreAncestorClipping();\n }\n }\n\n window.addEventListener('message', handler);\n return () => {\n if (placeholderDiv) {\n placeholderDiv.remove();\n placeholderDiv = null;\n }\n restoreAncestorClipping();\n window.removeEventListener('message', handler);\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|