first implementation for display directive

pull/14795/head
adiguba 2 years ago
parent 1d773ef3a4
commit d7c92d9e90

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

@ -82,6 +82,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 = [];
@ -152,6 +158,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);
}
}
@ -403,7 +422,45 @@ export function RegularElement(node, context) {
}
}
if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) {
if (display_directive) {
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
]);
const visibility = b.thunk(
/** @type {Expression} */ (context.visit(display_directive.expression))
);
/** @type {Expression | undefined} */
let value = undefined;
/** @type {Expression | undefined} */
let important = undefined;
if (style_display) {
value =
style_display.value === true
? build_getter({ name: style_display.name, type: 'Identifier' }, context.state)
: build_attribute_value(style_display.value, context).value;
if (style_display.metadata.expression.has_call) {
const id = b.id(state.scope.generate('style_directive'));
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
value = b.call('$.get', id);
}
value = b.thunk(value);
important = style_display.modifiers.includes('important') ? b.true : undefined;
}
context.state.init.push(
b.stmt(b.call('$.display', node_id, visibility, b.arrow([], block), value, important))
);
} else if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) {
// Wrap children in `{...}` to avoid declaration conflicts
context.state.init.push(
b.block([

@ -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 {
@ -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;
@ -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);
}
@ -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
@ -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)) {
@ -282,6 +295,7 @@ 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
@ -289,6 +303,7 @@ function get_attribute_name(element, attribute) {
function build_element_spread_attributes(
element,
attributes,
display_directive,
style_directives,
class_directives,
context
@ -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,
@ -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);
}
@ -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
@ -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)

@ -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';
@ -511,7 +520,8 @@ export namespace AST {
| AST.OnDirective
| AST.StyleDirective
| AST.TransitionDirective
| AST.UseDirective;
| AST.UseDirective
| AST.DisplayDirective;
export type Block =
| AST.EachBlock

@ -0,0 +1,80 @@
import { UNINITIALIZED } from '../../../../constants.js';
import { block, template_effect } from '../../reactivity/effects.js';
import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
/**
* @template V
* @param {HTMLElement} node
* @param {() => any} get_visibility
* @param {() => void} render_fn
* @param {()=>string|null} [get_value]
* @param {boolean} [default_important]
* @returns {void}
*/
export function display(node, get_visibility, render_fn, get_value, default_important) {
if (hydrating) {
hydrate_next();
}
var anchor = node;
/** @type {boolean | typeof UNINITIALIZED} */
let prev_visible = UNINITIALIZED;
/** @type {string | null | undefined | typeof UNINITIALIZED} */
let prev_value = UNINITIALIZED;
const effect = block(render_fn);
template_effect(() => {
const visible = !!get_visibility();
const value = visible ? get_value?.() : 'none';
if (visible === prev_visible && value === prev_value) {
return;
}
const transitions = effect.transitions;
const run_transitions = prev_visible !== UNINITIALIZED && transitions?.length;
if (visible || !run_transitions) {
if (value == null) {
anchor.style.removeProperty('display');
} else {
anchor.style.setProperty(
'display',
value,
!visible || default_important ? 'important' : ''
);
}
}
if (run_transitions) {
if (visible) {
// Start show transition
for (const transition of transitions) {
transition.in();
}
} else {
var remaining = transitions.length;
var check = () => {
if (--remaining == 0) {
// cleanup
for (var transition of transitions) {
transition.stop();
}
anchor.style.setProperty('display', 'none', 'important');
}
};
for (var transition of transitions) {
transition.out(check);
}
}
}
prev_visible = visible;
prev_value = value;
});
if (hydrating) {
anchor = /** @type {HTMLElement} */ (hydrate_node);
}
}

@ -17,6 +17,7 @@ export { inspect } from './dev/inspect.js';
export { await_block as await } from './dom/blocks/await.js';
export { if_block as if } from './dom/blocks/if.js';
export { key_block as key } from './dom/blocks/key.js';
export { display } from './dom/blocks/display.js';
export { css_props } from './dom/blocks/css-props.js';
export { index, each } from './dom/blocks/each.js';
export { html } from './dom/blocks/html.js';

@ -1269,6 +1269,11 @@ declare module 'svelte/compiler' {
expression: Expression;
}
export interface DisplayDirective extends BaseNode {
type: 'DisplayDirective';
expression: Expression;
}
export interface Script extends BaseNode {
type: 'Script';
context: 'default' | 'module';
@ -1286,7 +1291,8 @@ declare module 'svelte/compiler' {
| AST.OnDirective
| AST.StyleDirective
| AST.TransitionDirective
| AST.UseDirective;
| AST.UseDirective
| AST.DisplayDirective;
export type Block =
| AST.EachBlock

Loading…
Cancel
Save