Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Display directive #14795

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/svelte/src/compiler/phases/1-parse/state/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,25 @@ function read_static_attribute(parser) {
function read_attribute(parser) {
const start = parser.index;

if (parser.eat('#display={')) {
parser.allow_whitespace();
const expression = read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);

/** @type {AST.DisplayDirective} */
const display = {
type: 'DisplayDirective',
start,
end: parser.index,
expression,
metadata: {
expression: create_expression_metadata()
}
};
return display;
}

if (parser.eat('{')) {
parser.allow_whitespace();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
/** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { SourceLocation } from '#shared' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
Expand All @@ -22,7 +22,8 @@ import {
build_attribute_value,
build_class_directives,
build_style_directives,
build_set_attributes
build_set_attributes,
build_display_directive
} from './shared/element.js';
import { process_children } from './shared/fragment.js';
import {
Expand Down Expand Up @@ -82,6 +83,12 @@ export function RegularElement(node, context) {
/** @type {AST.StyleDirective[]} */
const style_directives = [];

/** @type {AST.DisplayDirective | null} */
let display_directive = null;

/** @type {AST.StyleDirective | null} */
let style_display = null;

/** @type {Array<AST.AnimateDirective | AST.BindDirective | AST.OnDirective | AST.TransitionDirective | AST.UseDirective>} */
const other_directives = [];

Expand Down Expand Up @@ -152,6 +159,19 @@ export function RegularElement(node, context) {
has_use = true;
other_directives.push(attribute);
break;

case 'DisplayDirective':
display_directive = attribute;
break;
}
}

if (display_directive) {
// When we have a #display directive, the style:display directive must be handheld differently
const index = style_directives.findIndex((d) => d.name === 'display');
if (index >= 0) {
style_display = style_directives[index];
style_directives.splice(index, 1);
}
}

Expand Down Expand Up @@ -403,17 +423,22 @@ export function RegularElement(node, context) {
}
}

if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) {
if (display_directive || node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) {
// Wrap children in `{...}` to avoid declaration conflicts
context.state.init.push(
b.block([
...child_state.init,
...element_state.init,
child_state.update.length > 0 ? build_render_statement(child_state.update) : b.empty,
...child_state.after_update,
...element_state.after_update
])
);
const block = b.block([
...child_state.init,
...element_state.init,
child_state.update.length > 0 ? build_render_statement(child_state.update) : b.empty,
...child_state.after_update,
...element_state.after_update
]);
if (display_directive) {
context.state.init.push(
build_display_directive(node_id, display_directive, style_display, block, context)
);
} else {
context.state.init.push(block);
}
} else if (node.fragment.metadata.dynamic) {
context.state.init.push(...child_state.init, ...element_state.init);
context.state.update.push(...child_state.update);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { determine_namespace_for_children } from '../../utils.js';
import {
build_attribute_value,
build_class_directives,
build_display_directive,
build_set_attributes,
build_style_directives
} from './shared/element.js';
Expand All @@ -36,6 +37,12 @@ export function SvelteElement(node, context) {
/** @type {AST.StyleDirective[]} */
const style_directives = [];

/** @type {AST.DisplayDirective | null} */
let display_directive = null;

/** @type {AST.StyleDirective | null} */
let style_display = null;

/** @type {ExpressionStatement[]} */
const lets = [];

Expand Down Expand Up @@ -73,11 +80,21 @@ export function SvelteElement(node, context) {
} else if (attribute.type === 'OnDirective') {
const handler = /** @type {Expression} */ (context.visit(attribute, inner_context.state));
inner_context.state.after_update.push(b.stmt(handler));
} else if (attribute.type === 'DisplayDirective') {
display_directive = attribute;
} else {
context.visit(attribute, inner_context.state);
}
}

if (display_directive !== null) {
const idx = style_directives.findIndex((d) => d.name === 'display');
if (idx > 0) {
style_display = style_directives[idx];
style_directives.splice(idx, 1);
}
}

// Let bindings first, they can be used on attributes
context.state.init.push(...lets); // create computeds in the outer context; the dynamic element is the single child of this slot

Expand Down Expand Up @@ -140,14 +157,22 @@ export function SvelteElement(node, context) {

const location = dev && locator(node.start);

let render_element = b.block(inner);

if (display_directive) {
render_element = b.block([
build_display_directive(element_id, display_directive, style_display, render_element, context)
]);
}

context.state.init.push(
b.stmt(
b.call(
'$.element',
context.state.node,
get_tag,
node.metadata.svg || node.metadata.mathml ? b.true : b.false,
inner.length > 0 && b.arrow([element_id, b.id('$$anchor')], b.block(inner)),
inner.length > 0 && b.arrow([element_id, b.id('$$anchor')], render_element),
dynamic_namespace && b.thunk(build_attribute_value(dynamic_namespace, context).value),
location && b.array([b.literal(location.line), b.literal(location.column)])
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import { Expression, Identifier, ObjectExpression } from 'estree' */
/** @import { BlockStatement, Expression, Identifier, ObjectExpression } from 'estree' */
/** @import { AST, Namespace } from '#compiler' */
/** @import { ComponentClientTransformState, ComponentContext } from '../../types' */
import { normalize_attribute } from '../../../../../../utils.js';
Expand Down Expand Up @@ -214,3 +214,38 @@ export function get_attribute_name(element, attribute) {

return attribute.name;
}

/**
* @param {Identifier} node_id
* @param {AST.DisplayDirective} display
* @param {AST.StyleDirective | null} style
* @param {BlockStatement} block
* @param {ComponentContext} context
*/
export function build_display_directive(node_id, display, style, block, context) {
const visibility = b.thunk(/** @type {Expression} */ (context.visit(display.expression)));

/** @type {Expression | undefined} */
let value = undefined;

/** @type {Expression | undefined} */
let important = undefined;

if (style) {
value =
style.value === true
? build_getter({ name: style.name, type: 'Identifier' }, context.state)
: build_attribute_value(style.value, context).value;

if (style.metadata.expression.has_call) {
const id = b.id(context.state.scope.generate('style_directive'));

context.state.init.push(b.const(id, create_derived(context.state, b.thunk(value))));
value = b.call('$.get', id);
}
value = b.thunk(value);
important = style.modifiers.includes('important') ? b.true : undefined;
}

return b.stmt(b.call('$.display', node_id, visibility, b.arrow([], block), value, important));
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import { Expression, Literal } from 'estree' */
/** @import { Expression, Literal, Property } from 'estree' */
/** @import { AST, Namespace } from '#compiler' */
/** @import { ComponentContext, ComponentServerTransformState } from '../../types.js' */
import {
Expand Down Expand Up @@ -43,6 +43,9 @@ export function build_element_attributes(node, context) {
/** @type {AST.StyleDirective[]} */
const style_directives = [];

/** @type {AST.DisplayDirective | null} */
let display_directive = null;

/** @type {Expression | null} */
let content = null;

Expand Down Expand Up @@ -189,6 +192,8 @@ export function build_element_attributes(node, context) {
style_directives.push(attribute);
} else if (attribute.type === 'LetDirective') {
// do nothing, these are handled inside `build_inline_component`
} else if (attribute.type === 'DisplayDirective') {
display_directive = attribute;
} else {
context.visit(attribute);
}
Expand All @@ -204,8 +209,9 @@ export function build_element_attributes(node, context) {
}
}

if (style_directives.length > 0 && !has_spread) {
if ((display_directive !== null || style_directives.length > 0) && !has_spread) {
build_style_directives(
display_directive,
style_directives,
/** @type {AST.Attribute | null} */ (attributes[style_index] ?? null),
context
Expand All @@ -216,7 +222,14 @@ export function build_element_attributes(node, context) {
}

if (has_spread) {
build_element_spread_attributes(node, attributes, style_directives, class_directives, context);
build_element_spread_attributes(
node,
attributes,
display_directive,
style_directives,
class_directives,
context
);
} else {
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
if (attribute.value === true || is_text_attribute(attribute)) {
Expand Down Expand Up @@ -282,13 +295,15 @@ function get_attribute_name(element, attribute) {
*
* @param {AST.RegularElement | AST.SvelteElement} element
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
* @param {AST.DisplayDirective | null } display_directive
* @param {AST.StyleDirective[]} style_directives
* @param {AST.ClassDirective[]} class_directives
* @param {ComponentContext} context
*/
function build_element_spread_attributes(
element,
attributes,
display_directive,
style_directives,
class_directives,
context
Expand All @@ -314,7 +329,7 @@ function build_element_spread_attributes(
classes = b.object(properties);
}

if (style_directives.length > 0) {
if (style_directives.length > 0 || display_directive !== null) {
const properties = style_directives.map((directive) =>
b.init(
directive.name,
Expand All @@ -323,7 +338,7 @@ function build_element_spread_attributes(
: build_attribute_value(directive.value, context, true)
)
);

handle_display_directive(display_directive, properties, context);
styles = b.object(properties);
}

Expand Down Expand Up @@ -402,11 +417,32 @@ function build_class_directives(class_directives, class_attribute) {
}

/**
* @param {AST.DisplayDirective | null} display_directive
* @param {Property[]} styles
* @param {ComponentContext} context
*/
function handle_display_directive(display_directive, styles, context) {
if (display_directive !== null) {
let display = styles.find((s) => s.key.type === 'Identifier' && s.key.name === 'display');
if (display === undefined) {
display = b.init('display', b.literal(null));
styles.push(display);
}
display.value = b.conditional(
/** @type {Expression} */ (context.visit(display_directive.expression)),
/** @type {Expression} */ (display.value),
b.literal('none !important')
);
}
}

/**
* @param {AST.DisplayDirective | null} display_directive
* @param {AST.StyleDirective[]} style_directives
* @param {AST.Attribute | null} style_attribute
* @param {ComponentContext} context
*/
function build_style_directives(style_directives, style_attribute, context) {
function build_style_directives(display_directive, style_directives, style_attribute, context) {
const styles = style_directives.map((directive) => {
let value =
directive.value === true
Expand All @@ -418,6 +454,8 @@ function build_style_directives(style_directives, style_attribute, context) {
return b.init(directive.name, value);
});

handle_display_directive(display_directive, styles, context);

const arg =
style_attribute === null
? b.object(styles)
Expand Down
12 changes: 11 additions & 1 deletion packages/svelte/src/compiler/types/template.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,15 @@ export namespace AST {
};
}

export interface DisplayDirective extends BaseNode {
type: 'DisplayDirective';
expression: Expression;
/** @internal */
metadata: {
expression: ExpressionMetadata;
};
}

export interface Script extends BaseNode {
type: 'Script';
context: 'default' | 'module';
Expand All @@ -511,7 +520,8 @@ export namespace AST {
| AST.OnDirective
| AST.StyleDirective
| AST.TransitionDirective
| AST.UseDirective;
| AST.UseDirective
| AST.DisplayDirective;

export type Block =
| AST.EachBlock
Expand Down
Loading
Loading