diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 8060e65a5d3f..687987148c02 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -18,6 +18,32 @@ function hash(str: string) { return hash >>> 0; } +export function create_static_rule(node: Element & ElementCSSInlineStyle, rule: string, uid: number = 0) { + const className = `__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[className]) { + current_rules[className] = true; + stylesheet.insertRule(`.${className} {${rule}}`, stylesheet.cssRules.length); + } + + node.classList.add(className); + + active += 1; + return className; +} + +export function delete_static_rule(node: Element & ElementCSSInlineStyle, name: string) { + if (name) { + node.classList.remove(name); // remove specific class + } + active -= 1; + if (!active) clear_rules(); +} + export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: number, duration: number, delay: number, ease: (t: number) => number, fn: (t: number, u: number) => string, uid: number = 0) { const step = 16.666 / duration; let keyframes = '{\n'; diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 2fc81cc21f25..3fc2cb1b00b3 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -1,7 +1,7 @@ import { identity as linear, is_function, noop, run_all } from './utils'; import { now } from './environment'; import { loop } from './loop'; -import { create_rule, delete_rule } from './style_manager'; +import { create_rule, delete_rule, create_static_rule, delete_static_rule } from './style_manager'; import { custom_event } from './dom'; import { add_render_callback } from './scheduler'; import { TransitionConfig } from '../transition'; @@ -72,12 +72,14 @@ type TransitionFn = (node: Element, params: any) => TransitionConfig; export function create_in_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any) { let config = fn(node, params); let running = false; + let static_class_name; let animation_name; let task; let uid = 0; function cleanup() { if (animation_name) delete_rule(node, animation_name); + if (static_class_name) delete_static_rule(node, static_class_name); } function go() { @@ -86,9 +88,11 @@ export function create_in_transition(node: Element & ElementCSSInlineStyle, fn: duration = 300, easing = linear, tick = noop, - css + css, + staticCss, } = config || null_transition; + if (staticCss) static_class_name = create_static_rule(node, staticCss, uid++); if (css) animation_name = create_rule(node, 0, 1, duration, delay, easing, css, uid++); tick(0, 1); @@ -233,6 +237,14 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline let running_program = null; let pending_program = null; let animation_name = null; + let static_class_name = null; + + function clear_static_css() { + if (static_class_name) { + delete_static_rule(node, static_class_name); + static_class_name = null; + } + } function clear_animation() { if (animation_name) delete_rule(node, animation_name); @@ -259,7 +271,8 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline duration = 300, easing = linear, tick = noop, - css + css, + staticCss } = config || null_transition; const program = { @@ -278,6 +291,9 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline } else { // if this is an intro, and there's a delay, we need to do // an initial tick and/or apply CSS animation immediately + if (staticCss && !static_class_name) { + static_class_name = create_static_rule(node, staticCss); + } if (css) { clear_animation(); animation_name = create_rule(node, t, b, duration, delay, easing, css); @@ -311,6 +327,7 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline if (running_program.b) { // intro — we can tidy up immediately clear_animation(); + clear_static_css(); } else { // outro — needs to be coordinated if (!--running_program.group.r) run_all(running_program.group.c); @@ -345,6 +362,7 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline end() { clear_animation(); + clear_static_css(); running_program = pending_program = null; } }; diff --git a/src/runtime/transition/index.ts b/src/runtime/transition/index.ts index 3a978a146a6b..49124914dc30 100644 --- a/src/runtime/transition/index.ts +++ b/src/runtime/transition/index.ts @@ -8,6 +8,7 @@ export interface TransitionConfig { duration?: number; easing?: EasingFunction; css?: (t: number, u: number) => string; + staticCss?: string; tick?: (t: number, u: number) => void; } @@ -115,12 +116,19 @@ export function slide(node: Element, { const border_top_width = parseFloat(style.borderTopWidth); const border_bottom_width = parseFloat(style.borderBottomWidth); + let displayOverride = ''; + // May want to use a whitelist instead + if (style.display.includes('table')) { + displayOverride = 'display: block;'; + } + return { + // Support a custom className to be added before and cleaned up after the thing. + staticCss: displayOverride + 'overflow: hidden;', delay, duration, easing, css: t => - 'overflow: hidden;' + `opacity: ${Math.min(t * 20, 1) * opacity};` + `height: ${t * height}px;` + `padding-top: ${t * padding_top}px;` +