Skip to content

Commit d7c92d9

Browse files
committed
first implementation for display directive
1 parent 1d773ef commit d7c92d9

File tree

7 files changed

+220
-9
lines changed

7 files changed

+220
-9
lines changed

packages/svelte/src/compiler/phases/1-parse/state/element.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,25 @@ function read_static_attribute(parser) {
485485
function read_attribute(parser) {
486486
const start = parser.index;
487487

488+
if (parser.eat('#display={')) {
489+
parser.allow_whitespace();
490+
const expression = read_expression(parser);
491+
parser.allow_whitespace();
492+
parser.eat('}', true);
493+
494+
/** @type {AST.DisplayDirective} */
495+
const display = {
496+
type: 'DisplayDirective',
497+
start,
498+
end: parser.index,
499+
expression,
500+
metadata: {
501+
expression: create_expression_metadata()
502+
}
503+
};
504+
return display;
505+
}
506+
488507
if (parser.eat('{')) {
489508
parser.allow_whitespace();
490509

packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ export function RegularElement(node, context) {
8282
/** @type {AST.StyleDirective[]} */
8383
const style_directives = [];
8484

85+
/** @type {AST.DisplayDirective | null} */
86+
let display_directive = null;
87+
88+
/** @type {AST.StyleDirective | null} */
89+
let style_display = null;
90+
8591
/** @type {Array<AST.AnimateDirective | AST.BindDirective | AST.OnDirective | AST.TransitionDirective | AST.UseDirective>} */
8692
const other_directives = [];
8793

@@ -152,6 +158,19 @@ export function RegularElement(node, context) {
152158
has_use = true;
153159
other_directives.push(attribute);
154160
break;
161+
162+
case 'DisplayDirective':
163+
display_directive = attribute;
164+
break;
165+
}
166+
}
167+
168+
if (display_directive) {
169+
// When we have a #display directive, the style:display directive must be handheld differently
170+
const index = style_directives.findIndex((d) => d.name === 'display');
171+
if (index >= 0) {
172+
style_display = style_directives[index];
173+
style_directives.splice(index, 1);
155174
}
156175
}
157176

@@ -403,7 +422,45 @@ export function RegularElement(node, context) {
403422
}
404423
}
405424

406-
if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) {
425+
if (display_directive) {
426+
const block = b.block([
427+
...child_state.init,
428+
...element_state.init,
429+
child_state.update.length > 0 ? build_render_statement(child_state.update) : b.empty,
430+
...child_state.after_update,
431+
...element_state.after_update
432+
]);
433+
434+
const visibility = b.thunk(
435+
/** @type {Expression} */ (context.visit(display_directive.expression))
436+
);
437+
438+
/** @type {Expression | undefined} */
439+
let value = undefined;
440+
441+
/** @type {Expression | undefined} */
442+
let important = undefined;
443+
444+
if (style_display) {
445+
value =
446+
style_display.value === true
447+
? build_getter({ name: style_display.name, type: 'Identifier' }, context.state)
448+
: build_attribute_value(style_display.value, context).value;
449+
450+
if (style_display.metadata.expression.has_call) {
451+
const id = b.id(state.scope.generate('style_directive'));
452+
453+
state.init.push(b.const(id, create_derived(state, b.thunk(value))));
454+
value = b.call('$.get', id);
455+
}
456+
value = b.thunk(value);
457+
important = style_display.modifiers.includes('important') ? b.true : undefined;
458+
}
459+
460+
context.state.init.push(
461+
b.stmt(b.call('$.display', node_id, visibility, b.arrow([], block), value, important))
462+
);
463+
} else if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) {
407464
// Wrap children in `{...}` to avoid declaration conflicts
408465
context.state.init.push(
409466
b.block([

packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { Expression, Literal } from 'estree' */
1+
/** @import { Expression, Literal, Property } from 'estree' */
22
/** @import { AST, Namespace } from '#compiler' */
33
/** @import { ComponentContext, ComponentServerTransformState } from '../../types.js' */
44
import {
@@ -43,6 +43,9 @@ export function build_element_attributes(node, context) {
4343
/** @type {AST.StyleDirective[]} */
4444
const style_directives = [];
4545

46+
/** @type {AST.DisplayDirective | null} */
47+
let display_directive = null;
48+
4649
/** @type {Expression | null} */
4750
let content = null;
4851

@@ -189,6 +192,8 @@ export function build_element_attributes(node, context) {
189192
style_directives.push(attribute);
190193
} else if (attribute.type === 'LetDirective') {
191194
// do nothing, these are handled inside `build_inline_component`
195+
} else if (attribute.type === 'DisplayDirective') {
196+
display_directive = attribute;
192197
} else {
193198
context.visit(attribute);
194199
}
@@ -204,8 +209,9 @@ export function build_element_attributes(node, context) {
204209
}
205210
}
206211

207-
if (style_directives.length > 0 && !has_spread) {
212+
if ((display_directive !== null || style_directives.length > 0) && !has_spread) {
208213
build_style_directives(
214+
display_directive,
209215
style_directives,
210216
/** @type {AST.Attribute | null} */ (attributes[style_index] ?? null),
211217
context
@@ -216,7 +222,14 @@ export function build_element_attributes(node, context) {
216222
}
217223

218224
if (has_spread) {
219-
build_element_spread_attributes(node, attributes, style_directives, class_directives, context);
225+
build_element_spread_attributes(
226+
node,
227+
attributes,
228+
display_directive,
229+
style_directives,
230+
class_directives,
231+
context
232+
);
220233
} else {
221234
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
222235
if (attribute.value === true || is_text_attribute(attribute)) {
@@ -282,13 +295,15 @@ function get_attribute_name(element, attribute) {
282295
*
283296
* @param {AST.RegularElement | AST.SvelteElement} element
284297
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
298+
* @param {AST.DisplayDirective | null } display_directive
285299
* @param {AST.StyleDirective[]} style_directives
286300
* @param {AST.ClassDirective[]} class_directives
287301
* @param {ComponentContext} context
288302
*/
289303
function build_element_spread_attributes(
290304
element,
291305
attributes,
306+
display_directive,
292307
style_directives,
293308
class_directives,
294309
context
@@ -314,7 +329,7 @@ function build_element_spread_attributes(
314329
classes = b.object(properties);
315330
}
316331

317-
if (style_directives.length > 0) {
332+
if (style_directives.length > 0 || display_directive !== null) {
318333
const properties = style_directives.map((directive) =>
319334
b.init(
320335
directive.name,
@@ -323,7 +338,7 @@ function build_element_spread_attributes(
323338
: build_attribute_value(directive.value, context, true)
324339
)
325340
);
326-
341+
handle_display_directive(display_directive, properties, context);
327342
styles = b.object(properties);
328343
}
329344

@@ -402,11 +417,32 @@ function build_class_directives(class_directives, class_attribute) {
402417
}
403418

404419
/**
420+
* @param {AST.DisplayDirective | null} display_directive
421+
* @param {Property[]} styles
422+
* @param {ComponentContext} context
423+
*/
424+
function handle_display_directive(display_directive, styles, context) {
425+
if (display_directive !== null) {
426+
let display = styles.find((s) => s.key.type === 'Identifier' && s.key.name === 'display');
427+
if (display === undefined) {
428+
display = b.init('display', b.literal(null));
429+
styles.push(display);
430+
}
431+
display.value = b.conditional(
432+
/** @type {Expression} */ (context.visit(display_directive.expression)),
433+
/** @type {Expression} */ (display.value),
434+
b.literal('none !important')
435+
);
436+
}
437+
}
438+
439+
/**
440+
* @param {AST.DisplayDirective | null} display_directive
405441
* @param {AST.StyleDirective[]} style_directives
406442
* @param {AST.Attribute | null} style_attribute
407443
* @param {ComponentContext} context
408444
*/
409-
function build_style_directives(style_directives, style_attribute, context) {
445+
function build_style_directives(display_directive, style_directives, style_attribute, context) {
410446
const styles = style_directives.map((directive) => {
411447
let value =
412448
directive.value === true
@@ -418,6 +454,8 @@ function build_style_directives(style_directives, style_attribute, context) {
418454
return b.init(directive.name, value);
419455
});
420456

457+
handle_display_directive(display_directive, styles, context);
458+
421459
const arg =
422460
style_attribute === null
423461
? b.object(styles)

packages/svelte/src/compiler/types/template.d.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,15 @@ export namespace AST {
494494
};
495495
}
496496

497+
export interface DisplayDirective extends BaseNode {
498+
type: 'DisplayDirective';
499+
expression: Expression;
500+
/** @internal */
501+
metadata: {
502+
expression: ExpressionMetadata;
503+
};
504+
}
505+
497506
export interface Script extends BaseNode {
498507
type: 'Script';
499508
context: 'default' | 'module';
@@ -511,7 +520,8 @@ export namespace AST {
511520
| AST.OnDirective
512521
| AST.StyleDirective
513522
| AST.TransitionDirective
514-
| AST.UseDirective;
523+
| AST.UseDirective
524+
| AST.DisplayDirective;
515525

516526
export type Block =
517527
| AST.EachBlock
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { UNINITIALIZED } from '../../../../constants.js';
2+
import { block, template_effect } from '../../reactivity/effects.js';
3+
import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
4+
5+
/**
6+
* @template V
7+
* @param {HTMLElement} node
8+
* @param {() => any} get_visibility
9+
* @param {() => void} render_fn
10+
* @param {()=>string|null} [get_value]
11+
* @param {boolean} [default_important]
12+
* @returns {void}
13+
*/
14+
export function display(node, get_visibility, render_fn, get_value, default_important) {
15+
if (hydrating) {
16+
hydrate_next();
17+
}
18+
19+
var anchor = node;
20+
21+
/** @type {boolean | typeof UNINITIALIZED} */
22+
let prev_visible = UNINITIALIZED;
23+
/** @type {string | null | undefined | typeof UNINITIALIZED} */
24+
let prev_value = UNINITIALIZED;
25+
26+
const effect = block(render_fn);
27+
template_effect(() => {
28+
const visible = !!get_visibility();
29+
const value = visible ? get_value?.() : 'none';
30+
31+
if (visible === prev_visible && value === prev_value) {
32+
return;
33+
}
34+
35+
const transitions = effect.transitions;
36+
const run_transitions = prev_visible !== UNINITIALIZED && transitions?.length;
37+
38+
if (visible || !run_transitions) {
39+
if (value == null) {
40+
anchor.style.removeProperty('display');
41+
} else {
42+
anchor.style.setProperty(
43+
'display',
44+
value,
45+
!visible || default_important ? 'important' : ''
46+
);
47+
}
48+
}
49+
50+
if (run_transitions) {
51+
if (visible) {
52+
// Start show transition
53+
for (const transition of transitions) {
54+
transition.in();
55+
}
56+
} else {
57+
var remaining = transitions.length;
58+
var check = () => {
59+
if (--remaining == 0) {
60+
// cleanup
61+
for (var transition of transitions) {
62+
transition.stop();
63+
}
64+
anchor.style.setProperty('display', 'none', 'important');
65+
}
66+
};
67+
for (var transition of transitions) {
68+
transition.out(check);
69+
}
70+
}
71+
}
72+
73+
prev_visible = visible;
74+
prev_value = value;
75+
});
76+
77+
if (hydrating) {
78+
anchor = /** @type {HTMLElement} */ (hydrate_node);
79+
}
80+
}

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export { inspect } from './dev/inspect.js';
1717
export { await_block as await } from './dom/blocks/await.js';
1818
export { if_block as if } from './dom/blocks/if.js';
1919
export { key_block as key } from './dom/blocks/key.js';
20+
export { display } from './dom/blocks/display.js';
2021
export { css_props } from './dom/blocks/css-props.js';
2122
export { index, each } from './dom/blocks/each.js';
2223
export { html } from './dom/blocks/html.js';

packages/svelte/types/index.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1269,6 +1269,11 @@ declare module 'svelte/compiler' {
12691269
expression: Expression;
12701270
}
12711271

1272+
export interface DisplayDirective extends BaseNode {
1273+
type: 'DisplayDirective';
1274+
expression: Expression;
1275+
}
1276+
12721277
export interface Script extends BaseNode {
12731278
type: 'Script';
12741279
context: 'default' | 'module';
@@ -1286,7 +1291,8 @@ declare module 'svelte/compiler' {
12861291
| AST.OnDirective
12871292
| AST.StyleDirective
12881293
| AST.TransitionDirective
1289-
| AST.UseDirective;
1294+
| AST.UseDirective
1295+
| AST.DisplayDirective;
12901296

12911297
export type Block =
12921298
| AST.EachBlock

0 commit comments

Comments
 (0)