From b9e8677160a6ff720b95b356fb961503b6773546 Mon Sep 17 00:00:00 2001 From: adiguba Date: Thu, 10 Oct 2024 21:51:48 +0200 Subject: [PATCH 1/3] hidden with transition (poc) --- .../client/visitors/RegularElement.js | 2 ++ .../client/visitors/TransitionDirective.js | 10 ++++++++- .../svelte/src/compiler/types/template.d.ts | 2 +- packages/svelte/src/constants.js | 1 + .../client/dom/elements/attributes.js | 22 +++++++++++++++++++ .../client/dom/elements/transitions.js | 14 +++++++++++- packages/svelte/src/internal/client/index.js | 3 ++- packages/svelte/types/index.d.ts | 2 +- 8 files changed, 51 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 9be435156353..e0ad34f5bcb7 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -545,6 +545,8 @@ function build_element_attribute_update_assignment(element, node_id, attribute, update = b.stmt(b.call('$.set_value', node_id, value)); } else if (name === 'checked') { update = b.stmt(b.call('$.set_checked', node_id, value)); + } else if (name === 'hidden') { + update = b.stmt(b.call('$.set_hidden', node_id, value)); } else if (is_dom_property(name)) { update = b.stmt(b.assignment('=', b.member(node_id, name), value)); } else { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/TransitionDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/TransitionDirective.js index e331f3647295..fa505840cdcc 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/TransitionDirective.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/TransitionDirective.js @@ -1,7 +1,12 @@ /** @import { Expression } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ -import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../../constants.js'; +import { + TRANSITION_GLOBAL, + TRANSITION_HIDDEN, + TRANSITION_IN, + TRANSITION_OUT +} from '../../../../../constants.js'; import * as b from '../../../../utils/builders.js'; import { parse_directive_name } from './shared/utils.js'; @@ -13,6 +18,9 @@ export function TransitionDirective(node, context) { let flags = node.modifiers.includes('global') ? TRANSITION_GLOBAL : 0; if (node.intro) flags |= TRANSITION_IN; if (node.outro) flags |= TRANSITION_OUT; + if (node.modifiers.includes('hidden')) { + flags |= TRANSITION_HIDDEN; + } const args = [ b.literal(flags), diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 9da0320432d4..2848ccb2882b 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -251,7 +251,7 @@ export namespace AST { name: string; /** The 'y' in `transition:x={y}` */ expression: null | Expression; - modifiers: Array<'local' | 'global'>; + modifiers: Array<'local' | 'global' | 'hidden'>; /** True if this is a `transition:` or `in:` directive */ intro: boolean; /** True if this is a `transition:` or `out:` directive */ diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index 03fddc5ebd28..84a04e7f2a04 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -14,6 +14,7 @@ export const PROPS_IS_LAZY_INITIAL = 1 << 4; export const TRANSITION_IN = 1; export const TRANSITION_OUT = 1 << 1; export const TRANSITION_GLOBAL = 1 << 2; +export const TRANSITION_HIDDEN = 1 << 3; export const TEMPLATE_FRAGMENT = 1; export const TEMPLATE_USE_IMPORT_NODE = 1 << 1; diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index db3ac5fa4e7e..98d033a26a0c 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -74,6 +74,28 @@ export function set_checked(element, checked) { element.checked = checked; } +/** + * @param {Element & { hidden: boolean, __tm?: import('#client').TransitionManager}} element + * @param {boolean} hidden + */ +export function set_hidden(element, hidden) { + // @ts-expect-error + var attributes = (element.__attributes ??= {}); + + if (attributes.hidden === (attributes.hidden = !!hidden)) return; + + if (element.__tm) { + if (hidden) { + element.__tm.out(() => (element.hidden = true)); + } else { + element.hidden = false; + element.__tm.in(); + } + } else { + element.hidden = hidden; + } +} + /** * @param {Element} element * @param {string} attribute diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index b473d0029d36..ef89d9ecdcc1 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -5,7 +5,12 @@ import { active_effect, untrack } from '../../runtime.js'; import { loop } from '../../loop.js'; import { should_intro } from '../../render.js'; import { current_each_item } from '../blocks/each.js'; -import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js'; +import { + TRANSITION_GLOBAL, + TRANSITION_HIDDEN, + TRANSITION_IN, + TRANSITION_OUT +} from '../../../../constants.js'; import { BLOCK_EFFECT, EFFECT_RAN, EFFECT_TRANSPARENT } from '../../constants.js'; import { queue_micro_task } from '../task.js'; @@ -169,6 +174,7 @@ export function transition(flags, element, get_fn, get_params) { var is_outro = (flags & TRANSITION_OUT) !== 0; var is_both = is_intro && is_outro; var is_global = (flags & TRANSITION_GLOBAL) !== 0; + var is_hidden = (flags & TRANSITION_HIDDEN) !== 0; /** @type {'in' | 'out' | 'both'} */ var direction = is_both ? 'both' : is_intro ? 'in' : 'out'; @@ -243,6 +249,12 @@ export function transition(flags, element, get_fn, get_params) { } }; + if (is_hidden) { + // @ts-expect-error + element.__tm = transition; + return; + } + var e = /** @type {Effect} */ (active_effect); (e.transitions ??= []).push(transition); diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 306fc69ca745..8eff6e88a6c8 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -33,7 +33,8 @@ export { set_xlink_attribute, handle_lazy_img, set_value, - set_checked + set_checked, + set_hidden } from './dom/elements/attributes.js'; export { set_class, set_svg_class, set_mathml_class, toggle_class } from './dom/elements/class.js'; export { apply, event, delegate, replay_events } from './dom/elements/events.js'; diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index e64abef0c5ca..4fe73c15150d 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1060,7 +1060,7 @@ declare module 'svelte/compiler' { name: string; /** The 'y' in `transition:x={y}` */ expression: null | Expression; - modifiers: Array<'local' | 'global'>; + modifiers: Array<'local' | 'global' | 'hidden'>; /** True if this is a `transition:` or `in:` directive */ intro: boolean; /** True if this is a `transition:` or `out:` directive */ From 9692532008208e10ce660e400d0a8a2fe3d2da4b Mon Sep 17 00:00:00 2001 From: adiguba Date: Mon, 14 Oct 2024 20:38:17 +0200 Subject: [PATCH 2/3] rename modifier 'hidden' into 'this' fix bug with in:/out: directive support for spreading with set_attributes() --- .../client/visitors/TransitionDirective.js | 6 ++--- .../svelte/src/compiler/types/template.d.ts | 2 +- packages/svelte/src/constants.js | 2 +- .../client/dom/elements/attributes.js | 27 ++++++++++++++++--- .../client/dom/elements/transitions.js | 8 +++--- packages/svelte/types/index.d.ts | 2 +- 6 files changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/TransitionDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/TransitionDirective.js index fa505840cdcc..89dc073e8f39 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/TransitionDirective.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/TransitionDirective.js @@ -3,7 +3,7 @@ /** @import { ComponentContext } from '../types' */ import { TRANSITION_GLOBAL, - TRANSITION_HIDDEN, + TRANSITION_THIS, TRANSITION_IN, TRANSITION_OUT } from '../../../../../constants.js'; @@ -18,8 +18,8 @@ export function TransitionDirective(node, context) { let flags = node.modifiers.includes('global') ? TRANSITION_GLOBAL : 0; if (node.intro) flags |= TRANSITION_IN; if (node.outro) flags |= TRANSITION_OUT; - if (node.modifiers.includes('hidden')) { - flags |= TRANSITION_HIDDEN; + if (node.modifiers.includes('this')) { + flags |= TRANSITION_THIS; } const args = [ diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 2848ccb2882b..413da995324a 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -251,7 +251,7 @@ export namespace AST { name: string; /** The 'y' in `transition:x={y}` */ expression: null | Expression; - modifiers: Array<'local' | 'global' | 'hidden'>; + modifiers: Array<'local' | 'global' | 'this'>; /** True if this is a `transition:` or `in:` directive */ intro: boolean; /** True if this is a `transition:` or `out:` directive */ diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index 84a04e7f2a04..5c27c59730cd 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -14,7 +14,7 @@ export const PROPS_IS_LAZY_INITIAL = 1 << 4; export const TRANSITION_IN = 1; export const TRANSITION_OUT = 1 << 1; export const TRANSITION_GLOBAL = 1 << 2; -export const TRANSITION_HIDDEN = 1 << 3; +export const TRANSITION_THIS = 1 << 3; export const TEMPLATE_FRAGMENT = 1; export const TEMPLATE_USE_IMPORT_NODE = 1 << 1; diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 98d033a26a0c..1f4140146e9c 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -75,7 +75,7 @@ export function set_checked(element, checked) { } /** - * @param {Element & { hidden: boolean, __tm?: import('#client').TransitionManager}} element + * @param {HTMLElement} element * @param {boolean} hidden */ export function set_hidden(element, hidden) { @@ -84,12 +84,29 @@ export function set_hidden(element, hidden) { if (attributes.hidden === (attributes.hidden = !!hidden)) return; - if (element.__tm) { + /** @type {import('#client').TransitionManager[] | undefined} */ + // @ts-expect-error + const tm = element.__tm; + if (tm) { if (hidden) { - element.__tm.out(() => (element.hidden = true)); + var remaining = tm.length; + var check = () => { + if (--remaining == 0) { + // cleanup + for (var transition of tm) { + transition.stop(); + } + element.hidden = true; + } + }; + for (var transition of tm) { + transition.out(check); + } } else { element.hidden = false; - element.__tm.in(); + for (const transition of tm) { + transition.in(); + } } } else { element.hidden = hidden; @@ -292,6 +309,8 @@ export function set_attributes( } else if (key === '__value' || (key === 'value' && value != null)) { // @ts-ignore element.value = element[key] = element.__value = value; + } else if (key === 'hidden') { + set_hidden(/** @type {HTMLElement} */ (element), value); } else { var name = key; if (!preserve_attribute_case) { diff --git a/packages/svelte/src/internal/client/dom/elements/transitions.js b/packages/svelte/src/internal/client/dom/elements/transitions.js index ef89d9ecdcc1..235181c4fa8d 100644 --- a/packages/svelte/src/internal/client/dom/elements/transitions.js +++ b/packages/svelte/src/internal/client/dom/elements/transitions.js @@ -7,7 +7,7 @@ import { should_intro } from '../../render.js'; import { current_each_item } from '../blocks/each.js'; import { TRANSITION_GLOBAL, - TRANSITION_HIDDEN, + TRANSITION_THIS, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js'; @@ -174,7 +174,7 @@ export function transition(flags, element, get_fn, get_params) { var is_outro = (flags & TRANSITION_OUT) !== 0; var is_both = is_intro && is_outro; var is_global = (flags & TRANSITION_GLOBAL) !== 0; - var is_hidden = (flags & TRANSITION_HIDDEN) !== 0; + var is_this = (flags & TRANSITION_THIS) !== 0; /** @type {'in' | 'out' | 'both'} */ var direction = is_both ? 'both' : is_intro ? 'in' : 'out'; @@ -249,9 +249,9 @@ export function transition(flags, element, get_fn, get_params) { } }; - if (is_hidden) { + if (is_this) { // @ts-expect-error - element.__tm = transition; + (element.__tm ??= []).push(transition); return; } diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 4fe73c15150d..34f6da270355 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1060,7 +1060,7 @@ declare module 'svelte/compiler' { name: string; /** The 'y' in `transition:x={y}` */ expression: null | Expression; - modifiers: Array<'local' | 'global' | 'hidden'>; + modifiers: Array<'local' | 'global' | 'this'>; /** True if this is a `transition:` or `in:` directive */ intro: boolean; /** True if this is a `transition:` or `out:` directive */ From f5f6317600204fed80298524081fc54fca30e02b Mon Sep 17 00:00:00 2001 From: adiguba Date: Mon, 14 Oct 2024 21:58:02 +0200 Subject: [PATCH 3/3] fix type --- packages/svelte/src/compiler/types/legacy-nodes.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/types/legacy-nodes.d.ts b/packages/svelte/src/compiler/types/legacy-nodes.d.ts index 2bd5fbbfa6d2..59ff38ad7278 100644 --- a/packages/svelte/src/compiler/types/legacy-nodes.d.ts +++ b/packages/svelte/src/compiler/types/legacy-nodes.d.ts @@ -187,7 +187,7 @@ export interface LegacyTransition extends BaseNode { name: string; /** The 'y' in `transition:x={y}` */ expression: null | Expression; - modifiers: Array<'local' | 'global'>; + modifiers: Array<'local' | 'global' | 'this'>; /** True if this is a `transition:` or `in:` directive */ intro: boolean; /** True if this is a `transition:` or `out:` directive */