-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
base: main
Are you sure you want to change the base?
feat: Display directive #14795
Conversation
|
preview: https://svelte-dev-git-preview-svelte-14795-svelte.vercel.app/ this is an automated message |
|
Wouldn't it be possible for this to be a block? Kinda like a vue v-show directive equivalent? {#show visible}
<p transition:slide>hello world</p>
{/show} |
It could only work by setting the style And yes it's an equivalent to Vue |
I would prefer a block too, it works basically like an
Also I don't think this feature is important enough to deserve its own syntax as we can already do this: <div style:display={visible}></div> Pair that with a getter, a setter and factory pattern to turn it into something like: <script>
const handler = visibility_handler("flex", false); //Create an object with a setter and a getter that switches state between "flex" and "none!important" with an initial value of "none!important" as indicated by the second argument
</script>
`<div style:display={handler.visible}></div>` Which allows you to do things like: handler.toggle();
handler.visible = false; //assignment is captured via its setter. handler.visible getter now returns "none!important"
handler.visible = true; //assignment is captured via its setter. handler.visible now returns "flex" as it was memoized
handler.visible = "inline" //assignment is captured via its setter. The initial "flex" value is overridden
handler.hide();
handler.show(); |
But in order to hide the content, we need an element on which to set => How do you hide that : {#display visible}
Hello World !
{/display}
I known that |
This is really sweet! Hope in some shape or form this can be incorporated. I also wonder if this could be a block, this way it would be intuitive and basically only way to do it, vs if someone used and if block and the directive.
I think it can also play well with the transition I think it would make sense to use the Please read to the end as this has been more like a discovery journey. <script>
import { fade } from 'svelte/transition';
let signal = $state(true);
</script>
<!-- toggle #if condition -->
<button onclick={() => signal = !signal}>Fade in / out</button>
<!-- short form with functions predefined -->
{#if:transition=[in, out] signal}
<!-- every top element will run through in and out callbacks, including inside components and snippets -->
<div transition:fade>fades in and out</div>
{/if}
<!-- shortest form with a function that returns a tuple with for in and out -->
{#if:transition=display() signal}
<!-- every top element will run through in and out callbacks, including inside components and snippets -->
<div transition:fade>fades in and out</div>
{/if}
<!-- long form inlined functions, in out functions run for every top element -->
{#if:transition=[node => node.removeAttr('style'), node => node.style.display = 'none')] signal}
<!-- every top element will run through in and out callbacks, , including inside components and snippets -->
<div transition:fade>fades in and out</div>
{/if} The syntax for {#if:on:transition=[in, out] signal} Svelte could provide a super basic, like above, with svelte provided actions: <script>
import { fade } from 'svelte/transition'
import { display } from 'svelte/transition/actions';
let signal = $state(true);
</script>
{#if:transition=display() signal}
<!-- every top element will run through in and out callbacks, , including inside components and snippets -->
<div transition:fade>fades in and out</div>
{/if} SimplificationBut, maybe even simpler, {#if:action=[truthy, falsy]}
{/if} a more fleshed out example, and actually, it probably just makes sense to use the same syntax as for the <script>
import { fade } from 'svelte/transition'
import { display } from 'svelte/transition/actions';
// display or any custom function that has to return a tuple of functions for truthy and falsy
// display() => [(node: HTMLElement) => void, (node: HTMLElement) => void]
// example of what `display` could return: [node => node.removeAttr('style'), node => node.style.display = 'none')]
let signal = $state(true);
</script>
{#if:action:display signal}
<div transition:fade>fades in and out</div>
{/if}
<!-- or `action:` can be `use:` as it's shorter and already used in a similar fashion -->
{#if:use:display signal}
<div transition:fade>fades in and out</div>
{/if} And to reiterate:
NOTE: when a block mounts, the behavior would be the same as for the current block behavior: the transitions don't run and thus the |
A block will have more constraints : no component at top-level, but also no text content, If <div transition:slide style:display={"block", visible}>
...
</div> |
Perhaps something like |
I mean, you can't apply svelte transitions to text node or to Actually, my bad,
I thought about it initially but it seemed too confusing with the regular usage. |
We can't hide them either. |
These are really weird edge cases, and they can be either wrapped in elements , just not placed in the same block or wrapped by another if block. I think Anyway, I’ll be happy with whichever way ends up being supported. |
I'm failing to see how this proposal is better than: <div style:display={visible ? 'block' : 'none'}> What am I missing? |
Probably this part
|
Transitions ! But maybe this
|
Ah! I see. Yes, that part in userland would be a No No. Ok. Two things:
|
Yes it's a new syntax, and I'm not sure this is the best choice...
There is no "standard" way to hide a component... |
As far as avoiding a wrapper for components, it would be possible with a block directive (again I don't care which way ends up being supported). Any element with a defined transition, at any nested level regardless whether within a component or snippet, will be animated. And this way, elements only define transitions (vs transitions and directives), and as a developer you decide on the behavior at the block level vs at every single element, including 3rd party svelte components. |
How did you hide an unknown component, a text node, or any stuff generated by It's work with |
Since unmount would not happen, nor elements would be removed from DOM, |
This ⬆️ (and the comments above) basically only define the behavior / specification. As far as implementation, I don't know. I think it's doable to figure this out with DOM. E.g. a comment for start and end of a block can be inserted and then the top children figured out with something like As far as transitions, I'm assuming they're registered somewhere if the top block waits for them to finish, no matter the nesting level. So, I'm assuming, it can do the same before making the dom elements invisible. And for triggering the For SSR, the block will behave the same way as now either render or not render anything depending on the condition. |
Setting Ex : how do you handle this ? {#display visible}
<Comp />
{/display} |
Wrapping it in a |
{#display visible}
<Comp />
{/display} How about having Svelte enumerate all top-level elements at the block's position and adding setting the display on each of them? |
so, as I mentioned, one way is to find the elements inside the components and set the ones at the top as Otherwise, whatever is inside the block could be wrapped in one div with |
Wouldn't it be more performant to just change the div's style instead of removing it? |
if we're talking about a wrapper div, it's really fast to just move the elements after the last sibling just above the block. I'm not sure performance is a concern here with animations and all, especially compared to the usual destruction and recreation of nodes / components. Otherwise, we introduce another html element that could disrupt the css while the elements are visible (while they're not invisible, it should be fine with reactivity and css doesn't matter). And I think in this case we might as well have devs create their own wrapper. |
Also, if the actions are customizable as I suggested, if a default behavior is not satisfactory, a custom one can provided. As long as the |
This one is the most promising one, given the current state of AST nodes in Svelte. I've observed that Svelte core maintainers try to use ESTree specification (for JavaScript syntax) as much as possible, so the newcomers don't need to be confused by new things that doesn't exist neither in HTML or JavaScript. <div transition:slide style:display={["block", visible]}>
<!-- ... -->
</div> |
This may cause conflict if the component use a Moving all nodes to an hidden wrapper might work, but it add an operation and make SSR more complex. IMHO the goals is not to make a replacement for |
I just added an alternative using a modifier on style:display : <div transition:slide style:display|transition={visible ? null : 'none'}> ... </div>
-- or equivalent when using a boolean :
<div transition:slide style:display|transition={visible}> ... </div> Updated demo : |
yeah, if someone has a reactive
As I mentioned, the SSR would remain unchanged as it works now: either not mount / render anything if falsy or just render the items if truthy. To run animations on mount (initial condition is falsy), the reactive signal / condition is changed to truthy in And it seems that it would be an advantage to use a conditional block not to render elements / load components if the initial condition is falsy or if it never even ends up changing VS with an element directive the contents are always rendered. By advantage, I mean that on initial load it would be less html to transfer from SSR, less to hydrate and less js to run. On a large set of components / elements wrapped in an element directive the slowdown could be somewhat significant. This can be remedied though by using an As far as the client-side svelte generated code, it would have to insert a comment before and after the block and get a reference to each in order to figure out the top elements inside the block, including the text nodes and then wrap these top elements (everything between the comment markers) with a wrapper element. Using comments is not something new to svelte as it uses comments for hydration markers and text nodes. However, the wrapper could just always be "applied", including during SSR. I just didn't want to introduce another wrapper to the generated markup. However, this is something that svelte already does with css custom properties passed in to components:
I think the overall goal is to make transitions run without having to rely purely on mount / unmount. An element directive is one way to accomplish this and using a conditional block ( There are pros and cons that are worth exploring. As far as other block advantages, the customizable actions on conditional blocks are also more flexible in terms not only being able to apply the For components, it would not require manually wrapping them with an html element vs a style directive and instead provide a managed one, and possibly the managed wrapper would only be applied while the condition is false and elements are not visible. The |
Even without
But... I want the content to be rendered, even on SSR ! I don't need a wrapper node or a complex block that will hide multiple node by moving stuff in an hidden element, or any other magic trick. I just want to toggle the visibility of an HTML node, using Svelte's transition... |
so this is not a good option, to set display on child nodes, but the wrapper would work.
why do you want to spend the time in SSR to render something that is not visible and then spend the time hydrating it client-side? Even if it's never even going to be shown? It may not be just one element, it could be a wrapper for multiple components or multiple wrappers for multiple components. I've seen examples with people wanting to do this with quite a bit of elements / components. But if you do need the SSR output for something invisible, it can be done with
But with the element display directive, you have to use a wrapper node anyway to wrap components which is a common use case. The conditional block can contain any number of elements, including just one. And it's not that rare to use transitions on more than one element at a time. This is not that magical. Creating a wrapper element is already done for css properties via The block is not that complex. I don't know about what it would take to implement but I doubt it's significantly more complex than an element directive. But it also could be easier. The fact is that transitions already work with a conditional block. The syntax is no more complex than what it is now or the element directive. I personally find the block syntax more obvious and readable, especially given that I already know that transitions depend on the block's condition. current syntax: {#if something === true}
<div transition:slide>My amazing content</div>
{/if} conditional block action: {#if:use:display something === true}
<div transition:slide>My amazing content</div>
{/if} element directive: <div transition:slide style:display|transition={something === true ? 'block' : 'none'}>My amazing content</div> I didn't check the source code but the conditional blocks must already be somehow integrated with transitions if they're able to wait until transitions finish - but it could be that they're completely unaware of them. What about having to be aware and learn two very different syntaxes for transitions support: one with the conditional block and one with the element directive? What about possible conflicts for transitions between the current behavior of an What about supporting the
That's one way of saying it. Either way it's a toggle of a signal that triggers transitions to run. |
For SEO, or preloading the DOM to prevent rebuild...
Adding a wrapper can be confusing : {#if visible}
content
{/if}
{#if:display visible}
content
{/if} => Why did the second create a wrapper, and not the first-one ?
<div transition:slide style:display|transition={something}>My amazing content</div>
Because it's two different thing. On one side the content is rendered dynamically, on the other site it's just show/hidden.
I don't see any problems with that. |
WIP : Another possible solution for #9976
This add a new
#display
directive for DOM elements.This directive will allow to show/hide an element using the Svelte transition API, without destroying it.
=> When visible is "falsy", the element will be hidden using a
display: none !important
.Otherwise it will be show either by removing the display style, or setting the value of
style:display
.Note : I known that
#display={...]
is not usual for directive but I think it's more expressive...In any case we could replace it with something like
svelte:display={...}
It's still a work in progress, and need some works that I can do if the syntax of this PR is accepted...
Before submitting the PR, please make sure you do the following
feat:
,fix:
,chore:
, ordocs:
.packages/svelte/src
, add a changeset (npx changeset
).Tests and linting
pnpm test
and lint the project withpnpm lint