svelte-display implementation

pull/8443/head
adiguba 4 years ago
parent 6ac7038e47
commit a816f02711

2
package-lock.json generated

@ -6,7 +6,7 @@
"packages": { "packages": {
"": { "": {
"name": "svelte", "name": "svelte",
"version": "3.49.0", "version": "3.51.0",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@ampproject/remapping": "^0.3.0", "@ampproject/remapping": "^0.3.0",

@ -281,5 +281,25 @@ export default {
invalid_component_style_directive: { invalid_component_style_directive: {
code: 'invalid-component-style-directive', code: 'invalid-component-style-directive',
message: 'Style directives cannot be used on components' 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`
})
}; };

@ -25,6 +25,7 @@ import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query'; 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 { 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_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); const aria_attribute_set = new Set(aria_attributes);
@ -217,6 +218,7 @@ export default class Element extends Node {
intro?: Transition = null; intro?: Transition = null;
outro?: Transition = null; outro?: Transition = null;
animation?: Animation = null; animation?: Animation = null;
display? : SvelteDirective = null;
children: INode[]; children: INode[];
namespace: string; namespace: string;
needs_manual_style_scoping: boolean; needs_manual_style_scoping: boolean;
@ -355,6 +357,22 @@ export default class Element extends Node {
this.animation = new Animation(component, this, scope, node); this.animation = new Animation(component, this, scope, node);
break; 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: default:
throw new Error(`Not implemented: ${node.type}`); throw new Error(`Not implemented: ${node.type}`);
} }
@ -385,7 +403,7 @@ export default class Element extends Node {
this.validate_bindings(); this.validate_bindings();
this.validate_content(); this.validate_content();
} }
this.validate_display_directive();
} }
validate_attributes() { 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() { is_media_node() {
return this.name === 'audio' || this.name === 'video'; return this.name === 'audio' || this.name === 'video';
} }

@ -77,6 +77,9 @@ export default class InlineComponent extends Node {
case 'StyleDirective': case 'StyleDirective':
return component.error(node, compiler_errors.invalid_component_style_directive); 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: default:
throw new Error(`Not implemented: ${node.type}`); throw new Error(`Not implemented: ${node.type}`);
} }

@ -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);
}
}
}

@ -33,6 +33,7 @@ import ThenBlock from './ThenBlock';
import Title from './Title'; import Title from './Title';
import Transition from './Transition'; import Transition from './Transition';
import Window from './Window'; 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 // 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 // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
@ -64,6 +65,7 @@ export type INode = Action
| Slot | Slot
| SlotTemplate | SlotTemplate
| StyleDirective | StyleDirective
| SvelteDisplayDirective
| Tag | Tag
| Text | Text
| ThenBlock | ThenBlock

@ -163,7 +163,7 @@ export default class ElementWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); 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({ this.child_dynamic_element_block = block.child({
comment: create_debugging_comment(node, renderer.component), comment: create_debugging_comment(node, renderer.component),
name: renderer.component.get_unique_name('create_dynamic_element'), name: renderer.component.get_unique_name('create_dynamic_element'),
@ -273,15 +273,15 @@ export default class ElementWrapper extends Wrapper {
(x`#nodes` as unknown) as Identifier (x`#nodes` as unknown) as Identifier
); );
const previous_tag = block.get_unique_name('previous_tag');
const tag = this.node.tag_expr.manipulate(block); const tag = this.node.tag_expr.manipulate(block);
block.add_variable(previous_tag, tag);
if (this.renderer.options.dev) {
block.chunks.init.push(b` block.chunks.init.push(b`
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} @validate_dynamic_element(${tag});
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} @validate_void_dynamic_element(${tag});
let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx);
`); `);
}
block.chunks.create.push(b` block.chunks.create.push(b`
if (${this.var}) ${this.var}.c(); if (${this.var}) ${this.var}.c();
@ -297,6 +297,14 @@ export default class ElementWrapper extends Wrapper {
if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); 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 {
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 anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const has_transitions = !!(this.node.intro || this.node.outro); 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 not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
@ -336,6 +344,9 @@ export default class ElementWrapper extends Wrapper {
} }
${previous_tag} = ${tag}; ${previous_tag} = ${tag};
`); `);
}
if (this.child_dynamic_element_block.has_intros) { if (this.child_dynamic_element_block.has_intros) {
block.chunks.intro.push(b`@transition_in(${this.var});`); 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_animation(block);
this.add_classes(block); this.add_classes(block);
this.add_styles(block); this.add_styles(block);
this.add_display(block);
this.add_manual_style_scoping(block); this.add_manual_style_scoping(block);
if (nodes && this.renderer.options.hydratable && !this.void) { 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) { add_manual_style_scoping(block) {
if (this.node.needs_manual_style_scoping) { if (this.node.needs_manual_style_scoping) {
const updater = b`@toggle_class(${this.var}, "${this.node.component.stylesheet.id}", true);`; const updater = b`@toggle_class(${this.var}, "${this.node.component.stylesheet.id}", true);`;

@ -48,6 +48,11 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
return p`"${name}": ${expression}`; 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 = const style_expression =
style_expression_list.length > 0 && style_expression_list.length > 0 &&
x`{ ${style_expression_list} }`; x`{ ${style_expression_list} }`;

@ -48,7 +48,8 @@ export type DirectiveType = 'Action'
| 'EventHandler' | 'EventHandler'
| 'Let' | 'Let'
| 'Ref' | 'Ref'
| 'Transition'; | 'Transition'
| 'SvelteDirective';
interface BaseDirective extends BaseNode { interface BaseDirective extends BaseNode {
type: DirectiveType; type: DirectiveType;

@ -286,6 +286,15 @@ function read_tag_name(parser: Parser) {
return name; 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<string>) { function read_attribute(parser: Parser, unique_names: Set<string>) {
const start = parser.index; const start = parser.index;
@ -419,7 +428,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
} }
// Directive name is expression, e.g. <p class:isRed /> // Directive name is expression, e.g. <p class:isRed />
if (!directive.expression && (type === 'Binding' || type === 'Class')) { if (!directive.expression && use_name_as_expression(type, directive_name)) {
directive.expression = { directive.expression = {
start: directive.start + colon_index + 1, start: directive.start + colon_index + 1,
end: directive.end, end: directive.end,
@ -452,6 +461,7 @@ function get_directive_type(name: string): DirectiveType {
if (name === 'let') return 'Let'; if (name === 'let') return 'Let';
if (name === 'ref') return 'Ref'; if (name === 'ref') return 'Ref';
if (name === 'in' || name === 'out' || name === 'transition') return 'Transition'; if (name === 'in' || name === 'out' || name === 'transition') return 'Transition';
if (name === 'svelte') return 'SvelteDirective';
} }
function read_attribute_value(parser: Parser) { function read_attribute_value(parser: Parser) {

@ -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) { export function select_option(select, value) {
for (let i = 0; i < select.options.length; i += 1) { for (let i = 0; i < select.options.length; i += 1) {
const option = select.options[i]; const option = select.options[i];

Loading…
Cancel
Save