allow transitions and animations to work within iframes (#3625)

pull/4564/head
Jacob Wright 5 years ago committed by GitHub
parent c46b3727f1
commit 966aae3420
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,7 @@
* Allow `<svelte:self>` to be used in a slot ([#2798](https://github.com/sveltejs/svelte/issues/2798))
* Expose object of unknown props in `$$restProps` ([#2930](https://github.com/sveltejs/svelte/issues/2930))
* Allow transitions and animations to work within iframes ([#3624](https://github.com/sveltejs/svelte/issues/3624))
* Disallow binding directly to `const` variables ([#4479](https://github.com/sveltejs/svelte/issues/4479))
* Fix updating keyed `{#each}` blocks with `{:else}` ([#4536](https://github.com/sveltejs/svelte/issues/4536), [#4549](https://github.com/sveltejs/svelte/issues/4549))
* Fix hydration of top-level content ([#4542](https://github.com/sveltejs/svelte/issues/4542))

@ -1,9 +1,13 @@
import { element } from './dom';
import { raf } from './environment';
let stylesheet;
interface ExtendedDoc extends Document {
__svelte_stylesheet: CSSStyleSheet;
__svelte_rules: Record<string, true>;
}
const active_docs = new Set<ExtendedDoc>();
let active = 0;
let current_rules = {};
// https://github.com/darkskyapp/string-hash/blob/master/index.js
function hash(str: string) {
@ -25,14 +29,12 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b:
const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`;
const name = `__svelte_${hash(rule)}_${uid}`;
const doc = node.ownerDocument as ExtendedDoc;
active_docs.add(doc);
const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet);
const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {});
if (!current_rules[name]) {
if (!stylesheet) {
const style = element('style');
document.head.appendChild(style);
stylesheet = style.sheet;
}
current_rules[name] = true;
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
}
@ -45,22 +47,28 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b:
}
export function delete_rule(node: Element & ElementCSSInlineStyle, name?: string) {
node.style.animation = (node.style.animation || '')
.split(', ')
.filter(name
? anim => anim.indexOf(name) < 0 // remove specific animation
: anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations
)
.join(', ');
if (name && !--active) clear_rules();
const previous = (node.style.animation || '').split(', ');
const next = previous.filter(name
? anim => anim.indexOf(name) < 0 // remove specific animation
: anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations
);
const deleted = previous.length - next.length;
if (deleted) {
node.style.animation = next.join(', ');
active -= deleted;
if (!active) clear_rules();
}
}
export function clear_rules() {
raf(() => {
if (active) return;
let i = stylesheet.cssRules.length;
while (i--) stylesheet.deleteRule(i);
current_rules = {};
active_docs.forEach(doc => {
const stylesheet = doc.__svelte_stylesheet;
let i = stylesheet.cssRules.length;
while (i--) stylesheet.deleteRule(i);
doc.__svelte_rules = {};
});
active_docs.clear();
});
}

@ -0,0 +1,16 @@
<script>
export let visible;
function foo() {
return {
duration: 100,
css: t => {
return `opacity: ${t}`;
}
};
}
</script>
{#if visible}
<div transition:foo></div>
{/if}

@ -0,0 +1,58 @@
<script>
import { onMount, onDestroy, tick } from 'svelte';
export let component;
let frame;
let doc;
let content;
$: mountComponent(doc, component);
$: updateProps($$props);
function mountComponent(doc) {
if (content) content.$destroy();
if (doc && component) {
const { component, ...props } = $$props;
content = new component({ target: doc.body, props });
}
}
function updateProps(props) {
if (content) {
const { component, ...rest } = props;
content.$set(rest);
}
}
function loadHandler() {
doc = frame.contentDocument;
// import styles
Array.from(document.querySelectorAll('style, link[rel="stylesheet"]'))
.forEach(node => doc.head.appendChild(node.cloneNode(true)));
}
onMount(async () => {
await tick();
if (frame.contentDocument.readyState === 'complete' && frame.contentDocument.defaultView) {
loadHandler();
} else {
frame.addEventListener('load', loadHandler);
}
});
onDestroy(() => {
if (frame) frame.removeEventListener('load', loadHandler);
if (content) content.$destroy();
});
</script>
<iframe bind:this={frame} title="frame"></iframe>
<style>
iframe {
border: none;
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,18 @@
export default {
skip_if_ssr: true,
async test({ assert, component, target, window, raf }) {
const frame = target.querySelector('iframe');
await Promise.resolve();
component.visible = true;
const div = frame.contentDocument.querySelector('div');
raf.tick(25);
component.visible = false;
raf.tick(26);
assert.ok(~div.style.animation.indexOf('25ms'));
},
};

@ -0,0 +1,8 @@
<script>
import Frame from './Frame.svelte';
import Foo from './Foo.svelte';
export let visible;
</script>
<Frame component={Foo} {visible}/>
Loading…
Cancel
Save