diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 36dc1e60446f..f34999c04e74 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -20,6 +20,30 @@ function hash(str: string) { return hash >>> 0; } +export function create_static_rule(node: Element & ElementCSSInlineStyle, rule: string) { + const class_name = `__svelte_${hash(rule)}`; + const doc = node.ownerDocument; + const { stylesheet, rules } = managed_styles.get(doc) || create_style_information(doc, node); + + if (!rules[class_name]) { + rules[class_name] = true; + stylesheet.insertRule(`.${class_name} {${rule}}`, stylesheet.cssRules.length); + } + + node.classList.add(class_name); + + active += 1; + return class_name; +} + +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(); +} + function create_style_information(doc: Document | ShadowRoot, node: Element & ElementCSSInlineStyle) { const info = { stylesheet: append_empty_stylesheet(node), rules: {} }; managed_styles.set(doc, info); diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 4705c0ac9aad..bf5e54059f14 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'; @@ -93,12 +93,14 @@ export function create_in_transition(node: Element & ElementCSSInlineStyle, fn: const options: TransitionOptions = { direction: 'in' }; let config = fn(node, params, options); 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() { @@ -107,9 +109,11 @@ export function create_in_transition(node: Element & ElementCSSInlineStyle, fn: duration = 300, easing = linear, tick = noop, - css + css, + static_css } = config || null_transition; + if (static_css) static_class_name = create_static_rule(node, static_css); if (css) animation_name = create_rule(node, 0, 1, duration, delay, easing, css, uid++); tick(0, 1); @@ -176,6 +180,7 @@ export function create_out_transition(node: Element & ElementCSSInlineStyle, fn: const options: TransitionOptions = { direction: 'out' }; let config = fn(node, params, options); let running = true; + let static_class_name; let animation_name; const group = outros; @@ -188,9 +193,11 @@ export function create_out_transition(node: Element & ElementCSSInlineStyle, fn: duration = 300, easing = linear, tick = noop, - css + css, + static_css } = config || null_transition; + if (static_css) static_class_name = create_static_rule(node, static_css); if (css) animation_name = create_rule(node, 1, 0, duration, delay, easing, css); const start_time = now() + delay; @@ -242,6 +249,7 @@ export function create_out_transition(node: Element & ElementCSSInlineStyle, fn: if (running) { if (animation_name) delete_rule(node, animation_name); + if (static_class_name) delete_static_rule(node, static_class_name); running = false; } } @@ -275,6 +283,14 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline let running_program: Program | null = null; let pending_program: PendingProgram | null = 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); @@ -301,7 +317,8 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline duration = 300, easing = linear, tick = noop, - css + css, + static_css } = config || null_transition; const program: PendingProgram = { @@ -320,6 +337,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 (static_css && !static_class_name) { + static_class_name = create_static_rule(node, static_css); + } if (css) { clear_animation(); animation_name = create_rule(node, t, b, duration, delay, easing, css); @@ -353,6 +373,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); @@ -387,6 +408,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 7ea08a02d2bb..ebcafe160c54 100644 --- a/src/runtime/transition/index.ts +++ b/src/runtime/transition/index.ts @@ -4,6 +4,7 @@ import { assign, split_css_unit, is_function } from 'svelte/internal'; export type EasingFunction = (t: number) => number; export interface TransitionConfig { + static_css?: string; delay?: number; duration?: number; easing?: EasingFunction; @@ -120,12 +121,14 @@ export function slide(node: Element, { const margin_end_value = parseFloat(style[`margin${capitalized_secondary_properties[1]}`]); const border_width_start_value = parseFloat(style[`border${capitalized_secondary_properties[0]}Width`]); const border_width_end_value = parseFloat(style[`border${capitalized_secondary_properties[1]}Width`]); + const display_override = style.display.includes('table') ? 'display:block;' : ''; + return { delay, duration, easing, + static_css: display_override + 'overflow:hidden;', css: t => - 'overflow: hidden;' + `opacity: ${Math.min(t * 20, 1) * opacity};` + `${primary_property}: ${t * primary_property_value}px;` + `padding-${secondary_properties[0]}: ${t * padding_start_value}px;` + diff --git a/test/runtime/samples/transition-css-in-out-in-with-static/_config.js b/test/runtime/samples/transition-css-in-out-in-with-static/_config.js new file mode 100644 index 000000000000..39a4401e8a5a --- /dev/null +++ b/test/runtime/samples/transition-css-in-out-in-with-static/_config.js @@ -0,0 +1,30 @@ +export default { + test({ assert, component, target, window, raf }) { + component.visible = true; + const div = target.querySelector('div'); + + assert.equal(div.className, '__svelte_2375698960'); + assert.equal(div.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); + + raf.tick(50); + component.visible = false; + + // both in and out styles + assert.equal(div.className, '__svelte_2375698960 __svelte_1786671888'); + assert.equal(div.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both, __svelte_3750847757_0 100ms linear 0ms 1 both'); + + raf.tick(75); + component.visible = true; + + // reset to in styles (in transition in progress) + assert.equal(div.style.animation, '__svelte_3809512021_1 100ms linear 0ms 1 both'); + assert.equal(div.className, '__svelte_2375698960'); + + // 100ms after in transition started at 75 + raf.tick(175); + + // reset original styles after in transition is complete + assert.equal(div.style.animation, ''); + assert.equal(div.className, ''); + } +}; diff --git a/test/runtime/samples/transition-css-in-out-in-with-static/main.svelte b/test/runtime/samples/transition-css-in-out-in-with-static/main.svelte new file mode 100644 index 000000000000..71cf88c06699 --- /dev/null +++ b/test/runtime/samples/transition-css-in-out-in-with-static/main.svelte @@ -0,0 +1,27 @@ + + +{#if visible} +
+{/if} \ No newline at end of file