-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(svelte): frame for svelte (#3171)
* feat: svelte portal * feat: Frame for svelte
- Loading branch information
Showing
11 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
17 changes: 17 additions & 0 deletions
17
packages/svelte/src/lib/components/frame/examples/basic.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<script lang="ts"> | ||
import { Frame } from '@ark-ui/svelte/frame' | ||
</script> | ||
|
||
<Frame title="Custom Frame" style="border: 1px solid #ccc; width: 100%; height: var(--height)"> | ||
{#snippet head()} | ||
<style> | ||
body { | ||
color: #f96743; | ||
} | ||
</style> | ||
{/snippet} | ||
<div style="padding: 40px"> | ||
<h1>Hello from inside the frame!</h1> | ||
<p>This content is rendered within our custom frame component using a Portal.</p> | ||
</div> | ||
</Frame> |
23 changes: 23 additions & 0 deletions
23
packages/svelte/src/lib/components/frame/examples/script.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<script lang="ts"> | ||
import { Frame } from '@ark-ui/svelte/frame' | ||
let frameRef: HTMLIFrameElement | undefined | ||
</script> | ||
|
||
<Frame | ||
title="Custom Frame" | ||
bind:ref={frameRef} | ||
onMount={() => { | ||
const doc = frameRef?.contentDocument | ||
if (!doc) return | ||
const script = doc.createElement('script') | ||
script.innerHTML = 'console.log("Hello from inside the frame!")' | ||
doc.body.appendChild(script) | ||
}} | ||
style="border: 1px solid #ccc; width: 100%; height: var(--height)" | ||
> | ||
<div style="padding: 40px"> | ||
<h1>Hello from inside the frame!</h1> | ||
<p>This content is rendered within our custom frame component using a Portal.</p> | ||
</div> | ||
</Frame> |
20 changes: 20 additions & 0 deletions
20
packages/svelte/src/lib/components/frame/examples/src-doc.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<script lang="ts"> | ||
import { Frame } from '@ark-ui/svelte/frame' | ||
const srcDoc = `<html> | ||
<head> | ||
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" /> | ||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" /> | ||
<base target=_blank> | ||
</head> | ||
<body style='overflow: hidden'> | ||
<div></div> | ||
</body> | ||
</html>` | ||
</script> | ||
|
||
<Frame title="Custom Frame" style="border: 1px solid #ccc; width: 100%;" srcdoc={srcDoc}> | ||
<h1 style="font-family: Open Sans, sans-serif;">Hello from inside the frame!</h1> | ||
<p>This content is rendered within our custom frame component using a Portal.</p> | ||
<p>The frame has custom initial content, including Font Awesome and Open Sans font.</p> | ||
</Frame> |
19 changes: 19 additions & 0 deletions
19
packages/svelte/src/lib/components/frame/frame-content.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<script lang="ts"> | ||
import type { Snippet } from 'svelte' | ||
interface FrameContentProps { | ||
onMount?(): void | ||
onUnmount?(): void | ||
children: Snippet | ||
} | ||
const { onMount, onUnmount, children }: FrameContentProps = $props() | ||
$effect(() => { | ||
onMount?.() | ||
return onUnmount | ||
}) | ||
</script> | ||
|
||
{@render children()} |
28 changes: 28 additions & 0 deletions
28
packages/svelte/src/lib/components/frame/frame.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import type { Meta } from '@storybook/svelte' | ||
import BasicExample from './examples/basic.svelte' | ||
import ScriptExample from './examples/script.svelte' | ||
import SrcDocExample from './examples/src-doc.svelte' | ||
|
||
const meta = { | ||
title: 'Components / Frame', | ||
} as Meta | ||
|
||
export default meta | ||
|
||
export const Basic = { | ||
render: () => ({ | ||
Component: BasicExample, | ||
}), | ||
} | ||
|
||
export const Script = { | ||
render: () => ({ | ||
Component: ScriptExample, | ||
}), | ||
} | ||
|
||
export const SrcDoc = { | ||
render: () => ({ | ||
Component: SrcDocExample, | ||
}), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
<script module lang="ts"> | ||
import type { Snippet } from 'svelte' | ||
import type { HTMLIframeAttributes } from 'svelte/elements' | ||
import type { Assign } from '../../types' | ||
export interface FrameBaseProps { | ||
/** Additional content to be inserted into the frame's <head> */ | ||
head?: Snippet | ||
/** Callback function to be executed when the frame is mounted */ | ||
onMount?: () => void | ||
/** Callback function to be executed when the frame is unmounted */ | ||
onUnmount?: () => void | ||
srcdoc?: string | ||
ref?: HTMLIFrameElement | ||
} | ||
export interface FrameProps extends Assign<HTMLIframeAttributes, FrameBaseProps> {} | ||
const CUSTOM_ROOT_CLASS = 'frame-root' | ||
const resetStyle = | ||
'<style>*,*::before,*::after { margin: 0; padding: 0; box-sizing: border-box; }</style>' | ||
const initialSrcDoc = `<html><head>${resetStyle}</head><body><div class="${CUSTOM_ROOT_CLASS}"></div></body></html>` | ||
function getMountNode(frame: HTMLIFrameElement) { | ||
const doc = frame.contentWindow?.document | ||
if (!doc) return null | ||
return doc.body.querySelector<HTMLElement>(`.${CUSTOM_ROOT_CLASS}`) || doc.body | ||
} | ||
</script> | ||
|
||
<script lang="ts"> | ||
import { EnvironmentProvider } from '@ark-ui/svelte/environment' | ||
import Portal from '$lib/components/portal/portal.svelte' | ||
import FrameContent from './frame-content.svelte' | ||
let { head, onMount, onUnmount, srcdoc, ref = $bindable(), ...localProps }: FrameProps = $props() | ||
let frameRef: HTMLIFrameElement | undefined = $state() | ||
let mountNode: HTMLElement | null = $derived(frameRef ? getMountNode(frameRef) : null) | ||
$effect(() => { | ||
if (!frameRef) return | ||
const doc = frameRef.contentWindow?.document | ||
if (!doc) return | ||
doc.open() | ||
doc.write(srcdoc ?? initialSrcDoc) | ||
doc.close() | ||
}) | ||
$effect(() => { | ||
if (!frameRef || !frameRef.contentDocument) return | ||
const win = frameRef.contentWindow as Window & typeof globalThis | ||
if (!win || !mountNode) return | ||
const exec = () => { | ||
if (!(mountNode && frameRef && frameRef.contentDocument)) return | ||
const rootEl = frameRef.contentDocument?.documentElement | ||
if (!rootEl) return | ||
frameRef.style.setProperty('--width', `${mountNode.scrollWidth}px`) | ||
frameRef.style.setProperty('--height', `${mountNode.scrollHeight}px`) | ||
} | ||
const resizeObserver = new win.ResizeObserver(exec) | ||
exec() | ||
if (frameRef.contentDocument) { | ||
resizeObserver.observe(mountNode) | ||
} | ||
return () => { | ||
resizeObserver.disconnect() | ||
} | ||
}) | ||
</script> | ||
|
||
<iframe bind:this={frameRef} bind:this={ref} {...localProps}> | ||
<EnvironmentProvider value={() => frameRef?.contentDocument ?? document}> | ||
{#if mountNode} | ||
<Portal container={mountNode}> | ||
<FrameContent {onMount} {onUnmount}> | ||
{#if localProps?.children} | ||
{@render localProps.children()} | ||
{/if} | ||
</FrameContent> | ||
</Portal> | ||
{/if} | ||
{#if mountNode && head && frameRef?.contentDocument?.head} | ||
<Portal container={frameRef.contentDocument.head}> | ||
{@render head()} | ||
</Portal> | ||
{/if} | ||
</EnvironmentProvider> | ||
</iframe> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default as Frame } from './frame.svelte' | ||
export type { FrameBaseProps, FrameProps } from './frame.svelte' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default as Portal } from './portal.svelte' | ||
export type { PortalProps } from './portal.svelte' |
9 changes: 9 additions & 0 deletions
9
packages/svelte/src/lib/components/portal/portal-consumer.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<script lang="ts"> | ||
import type { Snippet } from 'svelte' | ||
const { children }: { children: Snippet } = $props() | ||
</script> | ||
|
||
{#if children} | ||
{@render children()} | ||
{/if} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<script module lang="ts"> | ||
import type { Snippet } from 'svelte' | ||
export interface PortalProps { | ||
container?: HTMLElement | ||
children: Snippet | ||
} | ||
</script> | ||
|
||
<script lang="ts"> | ||
/** | ||
* @see https://github.com/sveltejs/svelte/issues/7082 | ||
*/ | ||
import { getAllContexts, mount, unmount } from 'svelte' | ||
import PortalConsumer from './portal-consumer.svelte' | ||
const { container = document.body, children }: PortalProps = $props() | ||
const context = getAllContexts() | ||
$effect(() => { | ||
mount(PortalConsumer, { target: container, props: { children }, context }) | ||
return () => unmount(PortalConsumer) | ||
}) | ||
</script> |