diff --git a/package-lock.json b/package-lock.json index 3909c68604..9bc83bd7d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "@tiptap/starter-kit": "^3.0.7", "@tiptap/suggestion": "^3.4.2", "@xyflow/svelte": "^0.1.19", + "alpinejs": "^3.15.0", "async": "^3.2.5", "bits-ui": "^0.21.15", "chart.js": "^4.5.0", @@ -4569,6 +4570,21 @@ "@types/estree": "^1.0.0" } }, + "node_modules/@vue/reactivity": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", + "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.1.5" + } + }, + "node_modules/@vue/shared": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", + "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", + "license": "MIT" + }, "node_modules/@webreflection/fetch": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@webreflection/fetch/-/fetch-0.1.5.tgz", @@ -4672,6 +4688,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/alpinejs": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.0.tgz", + "integrity": "sha512-lpokA5okCF1BKh10LG8YjqhfpxyHBk4gE7boIgVHltJzYoM7O9nK3M7VlntLEJGsVmu7U/RzUWajmHREGT38Eg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "~3.1.1" + } + }, "node_modules/amator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz", diff --git a/package.json b/package.json index f577cfdcb6..aa3c6d644c 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@tiptap/starter-kit": "^3.0.7", "@tiptap/suggestion": "^3.4.2", "@xyflow/svelte": "^0.1.19", + "alpinejs": "^3.15.0", "async": "^3.2.5", "bits-ui": "^0.21.15", "chart.js": "^4.5.0", diff --git a/src/lib/components/common/FullHeightIframe.svelte b/src/lib/components/common/FullHeightIframe.svelte index 7757e4d780..92af13b913 100644 --- a/src/lib/components/common/FullHeightIframe.svelte +++ b/src/lib/components/common/FullHeightIframe.svelte @@ -60,9 +60,50 @@ } // When the iframe loads, try same-origin resize (cross-origin will noop) - function onLoad() { + const onLoad = async () => { requestAnimationFrame(resizeSameOrigin); - } + + // If we're injecting Alpine into srcdoc iframe and sameOrigin allowed + if (iframeDoc && allowSameOrigin && iframe?.contentWindow) { + const alpineDirectives = [ + 'x-data', + 'x-init', + 'x-show', + 'x-bind', + 'x-on', + 'x-text', + 'x-html', + 'x-model', + 'x-modelable', + 'x-ref', + 'x-for', + 'x-if', + 'x-effect', + 'x-transition', + 'x-cloak', + 'x-ignore', + 'x-teleport', + 'x-id' + ]; + + const isAlpine = alpineDirectives.some((dir) => iframeDoc?.includes(dir)); + + if (isAlpine) { + const { default: Alpine } = await import('alpinejs'); + const win = iframe.contentWindow as Window & { Alpine?: typeof Alpine }; + + // Assign Alpine + win.Alpine = Alpine; + + // Initialize inside iframe DOM + try { + Alpine.start(); + } catch (e) { + console.error('Error starting Alpine inside iframe:', e); + } + } + } + }; // Ensure event listener bound only while component lives onMount(() => {