From a816f027116220d28b088d545d6053863d335e0d Mon Sep 17 00:00:00 2001 From: adiguba Date: Wed, 12 Oct 2022 22:04:55 +0200 Subject: [PATCH 1/4] svelte-display implementation --- package-lock.json | 2 +- src/compiler/compile/compiler_errors.ts | 22 ++- src/compiler/compile/nodes/Element.ts | 30 +++- src/compiler/compile/nodes/InlineComponent.ts | 3 + src/compiler/compile/nodes/SvelteDirective.ts | 22 +++ src/compiler/compile/nodes/interfaces.ts | 2 + .../render_dom/wrappers/Element/index.ts | 143 ++++++++++++------ .../compile/render_ssr/handlers/Element.ts | 5 + src/compiler/interfaces.ts | 3 +- src/compiler/parse/state/tag.ts | 12 +- src/runtime/internal/dom.ts | 4 + 11 files changed, 197 insertions(+), 51 deletions(-) create mode 100644 src/compiler/compile/nodes/SvelteDirective.ts diff --git a/package-lock.json b/package-lock.json index dec2c8ef0ecb..1f8f623dee3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "svelte", - "version": "3.49.0", + "version": "3.51.0", "license": "MIT", "devDependencies": { "@ampproject/remapping": "^0.3.0", diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index c1a7d8bc5cbd..63c0e691f9b3 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -281,5 +281,25 @@ export default { invalid_component_style_directive: { code: 'invalid-component-style-directive', message: 'Style directives cannot be used on components' - } + }, + invalid_component_svelte_directive: (name) => ({ + code: 'invalid-component-svelte-directive', + message: `svelte:${name} directives cannot be used on components` + }), + duplicate_directive: (directive) => ({ + code: 'duplicate-directive', + message: `An element can only have one '${directive}' directive` + }), + invalid_directive: (directive) => ({ + code: 'invalid-directive', + message: `'${directive}' is not a valid directive` + }), + invalid_modifier: { + code: 'invalid-modifier', + message: 'No modifier allowed on this directive' + }, + directive_conflict: (directive1, directive2) => ({ + code: 'directive-conflict', + message: `Cannot use ${directive1} and ${directive2} on the same element` + }) }; diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 79f2800437bf..09435f9b060f 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -25,6 +25,7 @@ import compiler_warnings from '../compiler_warnings'; import compiler_errors from '../compiler_errors'; import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query'; import { is_interactive_element, is_non_interactive_roles, is_presentation_role, is_interactive_roles, is_hidden_from_screen_reader, is_semantic_role_element } from '../utils/a11y'; +import SvelteDirective from './SvelteDirective'; const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); const aria_attribute_set = new Set(aria_attributes); @@ -217,6 +218,7 @@ export default class Element extends Node { intro?: Transition = null; outro?: Transition = null; animation?: Animation = null; + display? : SvelteDirective = null; children: INode[]; namespace: string; needs_manual_style_scoping: boolean; @@ -355,6 +357,22 @@ export default class Element extends Node { this.animation = new Animation(component, this, scope, node); break; + case 'SvelteDirective': + switch (node.name) { + case 'display': + if (this.display) { + component.error(node, + compiler_errors.duplicate_directive('svelte:' + node.name)); + } else { + this.display = new SvelteDirective(component, this, scope, node); + } + break; + default: + component.error(node, + compiler_errors.invalid_directive('svelte:' + node.name)); + } + break; + default: throw new Error(`Not implemented: ${node.type}`); } @@ -385,7 +403,7 @@ export default class Element extends Node { this.validate_bindings(); this.validate_content(); } - + this.validate_display_directive(); } validate_attributes() { @@ -941,6 +959,16 @@ export default class Element extends Node { }); } + validate_display_directive() { + if (!this.display) { + return; + } + if (this.styles.find(d => d.name === 'display')) { + this.component.error(this.display, + compiler_errors.directive_conflict('svelte:display', 'style:display')); + } + } + is_media_node() { return this.name === 'audio' || this.name === 'video'; } diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index 8871c5f306dd..4853f15e0d50 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -77,6 +77,9 @@ export default class InlineComponent extends Node { case 'StyleDirective': return component.error(node, compiler_errors.invalid_component_style_directive); + case 'SvelteDirective': + return component.error(node, compiler_errors.invalid_component_svelte_directive(node.name)); + default: throw new Error(`Not implemented: ${node.type}`); } diff --git a/src/compiler/compile/nodes/SvelteDirective.ts b/src/compiler/compile/nodes/SvelteDirective.ts new file mode 100644 index 000000000000..03d4b0199c0b --- /dev/null +++ b/src/compiler/compile/nodes/SvelteDirective.ts @@ -0,0 +1,22 @@ +import Node from './shared/Node'; +import Expression from './shared/Expression'; +import Component from '../Component'; +import TemplateScope from './shared/TemplateScope'; +import { TemplateNode } from '../../interfaces'; +import Element from './Element'; +import compiler_errors from '../compiler_errors'; + +export default class SvelteDirective extends Node { + type: 'SvelteDirective'; + expression: Expression; + + constructor(component: Component, parent: Element, scope: TemplateScope, info: TemplateNode) { + super(component, parent, scope, info); + + this.expression = new Expression(component, this, scope, info.expression); + + if (info.modifiers && info.modifiers.length) { + component.error(info, compiler_errors.invalid_modifier); + } + } +} diff --git a/src/compiler/compile/nodes/interfaces.ts b/src/compiler/compile/nodes/interfaces.ts index f023cad25c9f..0b8046aa0dc8 100644 --- a/src/compiler/compile/nodes/interfaces.ts +++ b/src/compiler/compile/nodes/interfaces.ts @@ -33,6 +33,7 @@ import ThenBlock from './ThenBlock'; import Title from './Title'; import Transition from './Transition'; import Window from './Window'; +import SvelteDisplayDirective from './SvelteDirective'; // note: to write less types each of types in union below should have type defined as literal // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions @@ -64,6 +65,7 @@ export type INode = Action | Slot | SlotTemplate | StyleDirective +| SvelteDisplayDirective | Tag | Text | ThenBlock diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 4f0b49729c8c..23fb74041d9f 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -163,7 +163,7 @@ export default class ElementWrapper extends Wrapper { ) { super(renderer, block, parent, node); - if (node.is_dynamic_element && block.type !== CHILD_DYNAMIC_ELEMENT_BLOCK) { + if ((node.display || node.is_dynamic_element) && block.type !== CHILD_DYNAMIC_ELEMENT_BLOCK) { this.child_dynamic_element_block = block.child({ comment: create_debugging_comment(node, renderer.component), name: renderer.component.get_unique_name('create_dynamic_element'), @@ -273,16 +273,16 @@ export default class ElementWrapper extends Wrapper { (x`#nodes` as unknown) as Identifier ); - const previous_tag = block.get_unique_name('previous_tag'); + const tag = this.node.tag_expr.manipulate(block); - block.add_variable(previous_tag, tag); - - block.chunks.init.push(b` - ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} - ${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} - let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx); - `); + if (this.renderer.options.dev) { + block.chunks.init.push(b` + @validate_dynamic_element(${tag}); + @validate_void_dynamic_element(${tag}); + `); + } + block.chunks.create.push(b` if (${this.var}) ${this.var}.c(); `); @@ -297,45 +297,56 @@ export default class ElementWrapper extends Wrapper { if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); `); - const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); - const has_transitions = !!(this.node.intro || this.node.outro); - const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; - - block.chunks.update.push(b` - if (${tag}) { - if (!${previous_tag}) { - ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); - ${this.var}.c(); - ${has_transitions && b`@transition_in(${this.var})`} - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); - } else if (${not_equal}(${previous_tag}, ${tag})) { - ${this.var}.d(1); - ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} - ${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} - ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); - ${this.var}.c(); - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); - } else { - ${this.var}.p(#ctx, #dirty); - } - } else if (${previous_tag}) { - ${ - has_transitions - ? b` - @group_outros(); - @transition_out(${this.var}, 1, 1, () => { + if (this.node.display && tag.type === 'Literal') { + block.chunks.init.push(b`const ${this.var} = ${this.child_dynamic_element_block.name}(#ctx);`); + block.chunks.update.push(b`${this.var}.p(#ctx, #dirty);`); + } else { + const previous_tag = block.get_unique_name('previous_tag'); + block.add_variable(previous_tag, tag); + block.chunks.init.push(b`let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx);`); + + const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + const has_transitions = !!(this.node.intro || this.node.outro); + const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; + + block.chunks.update.push(b` + if (${tag}) { + if (!${previous_tag}) { + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + ${has_transitions && b`@transition_in(${this.var})`} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else if (${not_equal}(${previous_tag}, ${tag})) { + ${this.var}.d(1); + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + ${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else { + ${this.var}.p(#ctx, #dirty); + } + } else if (${previous_tag}) { + ${ + has_transitions + ? b` + @group_outros(); + @transition_out(${this.var}, 1, 1, () => { + ${this.var} = null; + }); + @check_outros(); + ` + : b` + ${this.var}.d(1); ${this.var} = null; - }); - @check_outros(); - ` - : b` - ${this.var}.d(1); - ${this.var} = null; - ` + ` + } } - } - ${previous_tag} = ${tag}; - `); + ${previous_tag} = ${tag}; + `); + } + + if (this.child_dynamic_element_block.has_intros) { block.chunks.intro.push(b`@transition_in(${this.var});`); @@ -480,6 +491,7 @@ export default class ElementWrapper extends Wrapper { this.add_animation(block); this.add_classes(block); this.add_styles(block); + this.add_display(block); this.add_manual_style_scoping(block); if (nodes && this.renderer.options.hydratable && !this.void) { @@ -1129,6 +1141,45 @@ export default class ElementWrapper extends Wrapper { }); } + add_display(block: Block) { + const display = this.node.display; + if (display === null) { + return; + } + + const snippet = display.expression.manipulate(block); + const dependencies = display.expression.dynamic_dependencies(); + const has_dependancies = dependencies.length > 0; + + const update_display = b`@set_display(${this.var}, ${snippet})`; + block.chunks.hydrate.push(update_display); + + if (has_dependancies) { + const update_current = (this.node.intro || this.node.outro) + ? x`#current = false` + : null; + + const dirty = block.renderer.dirty(Array.from(dependencies)); + block.chunks.update.push(b` + if (${dirty}) { + if (${snippet}) { + ${update_current} + ${update_display} + @transition_in(this, 1); + } else { + @group_outros(); + @transition_out(this, 1, 0, () => { + ${update_display} + }); + @check_outros(); + } + } + `); + } + } + + + add_manual_style_scoping(block) { if (this.node.needs_manual_style_scoping) { const updater = b`@toggle_class(${this.var}, "${this.node.component.stylesheet.id}", true);`; diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 2af0343b2aef..c5821aa8dbe9 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -48,6 +48,11 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio return p`"${name}": ${expression}`; }); + if (node.display) { + const snippet = node.display.expression.node; + style_expression_list.push(p`"display": (${snippet} ? "none !important" : null)`); + } + const style_expression = style_expression_list.length > 0 && x`{ ${style_expression_list} }`; diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index 248175da5995..73eb5fc397c0 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -48,7 +48,8 @@ export type DirectiveType = 'Action' | 'EventHandler' | 'Let' | 'Ref' -| 'Transition'; +| 'Transition' +| 'SvelteDirective'; interface BaseDirective extends BaseNode { type: DirectiveType; diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index 4be47f25b089..915770261bc8 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -286,6 +286,15 @@ function read_tag_name(parser: Parser) { return name; } +function use_name_as_expression(type:string, name:string):boolean { + if (type === 'Binding' || type === 'Class') { + return true; + } else if (type === 'SvelteDirective') { + return name === 'display'; + } + return false; +} + function read_attribute(parser: Parser, unique_names: Set) { const start = parser.index; @@ -419,7 +428,7 @@ function read_attribute(parser: Parser, unique_names: Set) { } // Directive name is expression, e.g.

- if (!directive.expression && (type === 'Binding' || type === 'Class')) { + if (!directive.expression && use_name_as_expression(type, directive_name)) { directive.expression = { start: directive.start + colon_index + 1, end: directive.end, @@ -452,6 +461,7 @@ function get_directive_type(name: string): DirectiveType { if (name === 'let') return 'Let'; if (name === 'ref') return 'Ref'; if (name === 'in' || name === 'out' || name === 'transition') return 'Transition'; + if (name === 'svelte') return 'SvelteDirective'; } function read_attribute_value(parser: Parser) { diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index a1c0e1c0aa80..4aa30eb2505d 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -545,6 +545,10 @@ export function set_style(node, key, value, important) { } } +export function set_display(node, value) { + set_style(node, 'display', value ? null : 'none', 1); +} + export function select_option(select, value) { for (let i = 0; i < select.options.length; i += 1) { const option = select.options[i]; From ae57475f8b3299215b8ae008934abc7d426736a8 Mon Sep 17 00:00:00 2001 From: adiguba Date: Sat, 4 Mar 2023 09:08:56 +0100 Subject: [PATCH 2/4] rollback to upstream/master --- src/compiler/compile/compiler_errors.ts | 24 +-- src/compiler/compile/nodes/Element.ts | 30 +--- src/compiler/compile/nodes/InlineComponent.ts | 3 - src/compiler/compile/nodes/SvelteDirective.ts | 22 --- src/compiler/compile/nodes/interfaces.ts | 2 - .../render_dom/wrappers/Element/index.ts | 158 ++++++++---------- .../compile/render_ssr/handlers/Element.ts | 5 - src/compiler/interfaces.ts | 3 +- src/compiler/parse/state/tag.ts | 12 +- src/runtime/internal/dom.ts | 4 - 10 files changed, 74 insertions(+), 189 deletions(-) delete mode 100644 src/compiler/compile/nodes/SvelteDirective.ts diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index c5e00be89a5d..b0ffead6e023 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -286,28 +286,8 @@ export default { code: 'invalid-component-style-directive', message: 'Style directives cannot be used on components' }, - invalid_style_directive_modifier: (valid: string) => ({ + invalid_style_directive_modifier: (valid: string) => ({ code: 'invalid-style-directive-modifier', message: `Valid modifiers for style directives are: ${valid}` - }), - invalid_component_svelte_directive: (name) => ({ - code: 'invalid-component-svelte-directive', - message: `svelte:${name} directives cannot be used on components` - }), - duplicate_directive: (directive) => ({ - code: 'duplicate-directive', - message: `An element can only have one '${directive}' directive` - }), - invalid_directive: (directive) => ({ - code: 'invalid-directive', - message: `'${directive}' is not a valid directive` - }), - invalid_modifier: { - code: 'invalid-modifier', - message: 'No modifier allowed on this directive' - }, - directive_conflict: (directive1, directive2) => ({ - code: 'directive-conflict', - message: `Cannot use ${directive1} and ${directive2} on the same element` - }) + }) }; diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index bc7000e38afd..416f1d7b3169 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -25,7 +25,6 @@ import compiler_warnings from '../compiler_warnings'; import compiler_errors from '../compiler_errors'; import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query'; import { is_interactive_element, is_non_interactive_roles, is_presentation_role, is_interactive_roles, is_hidden_from_screen_reader, is_semantic_role_element } from '../utils/a11y'; -import SvelteDirective from './SvelteDirective'; const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); const aria_attribute_set = new Set(aria_attributes); @@ -286,7 +285,6 @@ export default class Element extends Node { intro?: Transition = null; outro?: Transition = null; animation?: Animation = null; - display? : SvelteDirective = null; children: INode[]; namespace: string; needs_manual_style_scoping: boolean; @@ -427,22 +425,6 @@ export default class Element extends Node { this.animation = new Animation(component, this, scope, node); break; - case 'SvelteDirective': - switch (node.name) { - case 'display': - if (this.display) { - component.error(node, - compiler_errors.duplicate_directive('svelte:' + node.name)); - } else { - this.display = new SvelteDirective(component, this, scope, node); - } - break; - default: - component.error(node, - compiler_errors.invalid_directive('svelte:' + node.name)); - } - break; - default: throw new Error(`Not implemented: ${node.type}`); } @@ -473,7 +455,7 @@ export default class Element extends Node { this.validate_bindings(); this.validate_content(); } - this.validate_display_directive(); + } validate_attributes() { @@ -1099,16 +1081,6 @@ export default class Element extends Node { }); } - validate_display_directive() { - if (!this.display) { - return; - } - if (this.styles.find(d => d.name === 'display')) { - this.component.error(this.display, - compiler_errors.directive_conflict('svelte:display', 'style:display')); - } - } - is_media_node() { return this.name === 'audio' || this.name === 'video'; } diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index 62ba607f94ad..e9ac86a55cf5 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -77,9 +77,6 @@ export default class InlineComponent extends Node { case 'StyleDirective': return component.error(node, compiler_errors.invalid_component_style_directive); - - case 'SvelteDirective': - return component.error(node, compiler_errors.invalid_component_svelte_directive(node.name)); default: throw new Error(`Not implemented: ${node.type}`); diff --git a/src/compiler/compile/nodes/SvelteDirective.ts b/src/compiler/compile/nodes/SvelteDirective.ts deleted file mode 100644 index 03d4b0199c0b..000000000000 --- a/src/compiler/compile/nodes/SvelteDirective.ts +++ /dev/null @@ -1,22 +0,0 @@ -import Node from './shared/Node'; -import Expression from './shared/Expression'; -import Component from '../Component'; -import TemplateScope from './shared/TemplateScope'; -import { TemplateNode } from '../../interfaces'; -import Element from './Element'; -import compiler_errors from '../compiler_errors'; - -export default class SvelteDirective extends Node { - type: 'SvelteDirective'; - expression: Expression; - - constructor(component: Component, parent: Element, scope: TemplateScope, info: TemplateNode) { - super(component, parent, scope, info); - - this.expression = new Expression(component, this, scope, info.expression); - - if (info.modifiers && info.modifiers.length) { - component.error(info, compiler_errors.invalid_modifier); - } - } -} diff --git a/src/compiler/compile/nodes/interfaces.ts b/src/compiler/compile/nodes/interfaces.ts index 0b8046aa0dc8..f023cad25c9f 100644 --- a/src/compiler/compile/nodes/interfaces.ts +++ b/src/compiler/compile/nodes/interfaces.ts @@ -33,7 +33,6 @@ import ThenBlock from './ThenBlock'; import Title from './Title'; import Transition from './Transition'; import Window from './Window'; -import SvelteDisplayDirective from './SvelteDirective'; // note: to write less types each of types in union below should have type defined as literal // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions @@ -65,7 +64,6 @@ export type INode = Action | Slot | SlotTemplate | StyleDirective -| SvelteDisplayDirective | Tag | Text | ThenBlock diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 0562fc6d897c..eaef3a3b6949 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -191,7 +191,7 @@ export default class ElementWrapper extends Wrapper { this.class_dependencies = []; - if ((node.display || node.is_dynamic_element) && block.type !== CHILD_DYNAMIC_ELEMENT_BLOCK) { + if (node.is_dynamic_element && block.type !== CHILD_DYNAMIC_ELEMENT_BLOCK) { this.child_dynamic_element_block = block.child({ comment: create_debugging_comment(node, renderer.component), name: renderer.component.get_unique_name('create_dynamic_element'), @@ -303,13 +303,12 @@ export default class ElementWrapper extends Wrapper { const is_tag_dynamic = this.node.tag_expr.dynamic_dependencies().length > 0; const tag = this.node.tag_expr.manipulate(block); - if (this.renderer.options.dev) { - block.chunks.init.push(b` - @validate_dynamic_element(${tag}); - @validate_void_dynamic_element(${tag}); - `); - } - + block.chunks.init.push(b` + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + ${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} + let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx); + `); + block.chunks.create.push(b` if (${this.var}) ${this.var}.c(); `); @@ -324,53 +323,72 @@ export default class ElementWrapper extends Wrapper { if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); `); - if (this.node.display && tag.type === 'Literal') { - block.chunks.init.push(b`const ${this.var} = ${this.child_dynamic_element_block.name}(#ctx);`); - block.chunks.update.push(b`${this.var}.p(#ctx, #dirty);`); - } else { + if (is_tag_dynamic) { const previous_tag = block.get_unique_name('previous_tag'); block.add_variable(previous_tag, tag); - block.chunks.init.push(b`let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx);`); - - const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); - const has_transitions = !!(this.node.intro || this.node.outro); - const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; - - block.chunks.update.push(b` - if (${tag}) { - if (!${previous_tag}) { - ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); - ${this.var}.c(); - ${has_transitions && b`@transition_in(${this.var})`} - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); - } else if (${not_equal}(${previous_tag}, ${tag})) { - ${this.var}.d(1); - ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} - ${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} - ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); - ${this.var}.c(); - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); - } else { - ${this.var}.p(#ctx, #dirty); - } - } else if (${previous_tag}) { - ${ - has_transitions - ? b` - @group_outros(); - @transition_out(${this.var}, 1, 1, () => { + const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + const has_transitions = !!(this.node.intro || this.node.outro); + const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; + + const tag_will_be_removed = block.get_unique_name('tag_will_be_removed'); + if (has_transitions) { + block.add_variable(tag_will_be_removed, x`false`); + } + + block.chunks.update.push(b` + if (${tag}) { + if (!${previous_tag}) { + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${previous_tag} = ${tag}; + ${this.var}.c(); + ${has_transitions && b`@transition_in(${this.var})`} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else if (${not_equal}(${previous_tag}, ${tag})) { + ${this.var}.d(1); + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + ${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${previous_tag} = ${tag}; + ${this.var}.c(); + ${has_transitions && b`if (${tag_will_be_removed}) { + ${tag_will_be_removed} = false; + @transition_in(${this.var}) + }`} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else { + ${has_transitions && b`if (${tag_will_be_removed}) { + ${tag_will_be_removed} = false; + @transition_in(${this.var}) + }`} + ${this.var}.p(#ctx, #dirty); + } + } else if (${previous_tag}) { + ${has_transitions + ? b` + ${tag_will_be_removed} = true; + @group_outros(); + @transition_out(${this.var}, 1, 1, () => { + ${this.var} = null; + ${previous_tag} = ${tag}; + ${tag_will_be_removed} = false; + }); + @check_outros(); + ` + : b` + ${this.var}.d(1); ${this.var} = null; - }); - @check_outros(); - ` - : b` - ${this.var}.d(1); - ${this.var} = null; - ` + ${previous_tag} = ${tag}; + ` } - } - ${previous_tag} = ${tag}; - `); + } + `); + } else { + block.chunks.update.push(b` + if (${tag}) { + ${this.var}.p(#ctx, #dirty); + } + `); + } if (this.child_dynamic_element_block.has_intros) { block.chunks.intro.push(b`@transition_in(${this.var});`); @@ -519,7 +537,6 @@ export default class ElementWrapper extends Wrapper { this.add_animation(block); this.add_classes(block); this.add_styles(block); - this.add_display(block); this.add_manual_style_scoping(block); if (nodes && this.renderer.options.hydratable && !this.void) { @@ -1188,43 +1205,6 @@ export default class ElementWrapper extends Wrapper { }); } - add_display(block: Block) { - const display = this.node.display; - if (display === null) { - return; - } - - const snippet = display.expression.manipulate(block); - const dependencies = display.expression.dynamic_dependencies(); - const has_dependancies = dependencies.length > 0; - - const update_display = b`@set_display(${this.var}, ${snippet})`; - block.chunks.hydrate.push(update_display); - - if (has_dependancies) { - const update_current = (this.node.intro || this.node.outro) - ? x`#current = false` - : null; - - const dirty = block.renderer.dirty(Array.from(dependencies)); - block.chunks.update.push(b` - if (${dirty}) { - if (${snippet}) { - ${update_current} - ${update_display} - @transition_in(this, 1); - } else { - @group_outros(); - @transition_out(this, 1, 0, () => { - ${update_display} - }); - @check_outros(); - } - } - `); - } - } - add_manual_style_scoping(block: Block) { if (this.node.needs_manual_style_scoping) { const updater = b`@toggle_class(${this.var}, "${this.node.component.stylesheet.id}", true);`; diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 0d416f779dd3..f1dd86dd2bb7 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -51,11 +51,6 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio return p`"${name}": ${expression}`; }); - if (node.display) { - const snippet = node.display.expression.node; - style_expression_list.push(p`"display": (${snippet} ? "none !important" : null)`); - } - const style_expression = style_expression_list.length > 0 && x`{ ${style_expression_list} }`; diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index ae93eb069eb6..402f9ff5e143 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -48,8 +48,7 @@ export type DirectiveType = 'Action' | 'EventHandler' | 'Let' | 'Ref' -| 'Transition' -| 'SvelteDirective'; +| 'Transition'; interface BaseDirective extends BaseNode { type: DirectiveType; diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index 7a4c8ccb6eaa..cbd3213f531b 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -294,15 +294,6 @@ function read_tag_name(parser: Parser) { return name; } -function use_name_as_expression(type:string, name:string):boolean { - if (type === 'Binding' || type === 'Class') { - return true; - } else if (type === 'SvelteDirective') { - return name === 'display'; - } - return false; -} - // eslint-disable-next-line no-useless-escape const regex_token_ending_character = /[\s=\/>"']/; const regex_starts_with_quote_characters = /^["']/; @@ -440,7 +431,7 @@ function read_attribute(parser: Parser, unique_names: Set) { } // Directive name is expression, e.g.

- if (!directive.expression && use_name_as_expression(type, directive_name)) { + if (!directive.expression && (type === 'Binding' || type === 'Class')) { directive.expression = { start: directive.start + colon_index + 1, end: directive.end, @@ -473,7 +464,6 @@ function get_directive_type(name: string): DirectiveType { if (name === 'let') return 'Let'; if (name === 'ref') return 'Ref'; if (name === 'in' || name === 'out' || name === 'transition') return 'Transition'; - if (name === 'svelte') return 'SvelteDirective'; } function read_attribute_value(parser: Parser) { diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 35cd6d6cc5b0..70e7dcd7f8f5 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -606,10 +606,6 @@ export function set_style(node, key, value, important) { } } -export function set_display(node, value) { - set_style(node, 'display', value ? null : 'none', 1); -} - export function select_option(select, value) { for (let i = 0; i < select.options.length; i += 1) { const option = select.options[i]; From 26b3cbe5716eae4da56bd4c185a7fb702c44e5f5 Mon Sep 17 00:00:00 2001 From: Fred Martini Date: Tue, 21 Mar 2023 11:00:21 +0100 Subject: [PATCH 3/4] catch and report errors on handler --- src/runtime/internal/lifecycle.ts | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index e75bbdc501f4..0d9f2462ca56 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -88,9 +88,9 @@ export function createEventDispatcher(): < // TODO are there situations where events could be dispatched // in a server (non-DOM) environment? const event = custom_event(type, detail, { cancelable }); - callbacks.slice().forEach(fn => { - fn.call(component, event); - }); + + invoke_callbacks(callbacks, component, event); + return !event.defaultPrevented; } @@ -151,6 +151,25 @@ export function bubble(component, event) { if (callbacks) { // @ts-ignore - callbacks.slice().forEach(fn => fn.call(this, event)); + invoke_callbacks(callbacks, this, event); } } + +// Invoke all callbacks, even if an error occur +function invoke_callbacks(callbacks, source, event) { + callbacks.slice().forEach(fn => { + try { + return fn.call(source, event); + } catch (err) { + // @ts-ignore + if (window.reportError) { + // @ts-ignore + window.reportError(err); + } else { + setTimeout(() => { + throw err; + }, 0); + } + } + }); +} From 668ed8cda83507d9d49818803ca2d7081c465609 Mon Sep 17 00:00:00 2001 From: adiguba Date: Sat, 1 Apr 2023 11:32:03 +0200 Subject: [PATCH 4/4] clean code --- src/runtime/internal/lifecycle.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index 0d9f2462ca56..0cf5b5425009 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -88,9 +88,7 @@ export function createEventDispatcher(): < // TODO are there situations where events could be dispatched // in a server (non-DOM) environment? const event = custom_event(type, detail, { cancelable }); - invoke_callbacks(callbacks, component, event); - return !event.defaultPrevented; } @@ -159,7 +157,7 @@ export function bubble(component, event) { function invoke_callbacks(callbacks, source, event) { callbacks.slice().forEach(fn => { try { - return fn.call(source, event); + fn.call(source, event); } catch (err) { // @ts-ignore if (window.reportError) {