presence
elements man ui/behaviors/presenceKeeps a part mounted while its exit animation plays. The runtime removes a
node the moment its e:if turns false, so a part that animates out has to
stay rendered after open goes false. A Presence is the small reactive
object that does that, and presence() keeps it in step with open.
Signaturesfunction createPresence(read: () => boolean): Presence; function presence(p: Presence, read: () => boolean, opts?: PresenceOptions): () => void; function animationsDone(el: Element | null): Promise<void>;
createPresence reads open once, untracked, so a private initializer does
not re-run. presence returns stop; store it on p.stop and call it from
onremove. animationsDone resolves when every finite animation and
transition on el and its subtree has finished, for a controller that waits
on an animation of its own.
The Presence
| Field | Type | What it is |
|---|---|---|
mounted |
boolean |
Render the part while true: e:if={p.mounted}. |
state |
"open" | "closed" |
Render as data-state={p.state}; CSS runs the enter animation off [data-state=open] and the exit off [data-state=closed]. |
phase |
"entering" | "open" | "leaving" | "closed" |
The whole lifecycle. Anything a leaving part must stop doing (being a Tab stop, being measured) reads the phase, not the open flag: a leaving part is still on screen. |
stop |
() => void |
Set by presence(); call it from onremove. |
When open turns true: mounted is true, state is "open", phase is
"entering" until the enter animation ends. When it turns false: state is
"closed" and phase "leaving"; once every finite animation and
transition on the element has finished, phase is "closed" and mounted
is false. With no animation (reduced motion, a theme with zero durations) it
unmounts on the next frame. Reopening during the exit cancels the unmount.
Options
| Option | Type | Default | What it does |
|---|---|---|---|
element |
() => Element | null |
none | The animated element, the one carrying data-state. Without it the part unmounts on the next frame, skipping any exit animation. |
onEnterEnd |
() => void |
none | Called once the enter animations finish after the part opens. Not called for a part that starts open. |
onExitStart |
() => void |
none | Called when the exit starts, as state turns "closed". |
onExitEnd |
() => void |
none | Called after the exit animations finish, just before mounted turns false. |
inertWhileLeaving |
boolean |
false |
Makes the element inert from the moment it starts to leave, so Tab and the pointer pass it by while it animates out. |
With element, the part also gets two transition attributes:
data-starting-style for its first frame after it opens (style the hidden
look there and a CSS transition runs to the open one) and
data-ending-style while it exits (style the hidden look there too, and the
unmount waits for that transition).
In a template of your own
app/shared/templates/panel/index.ehtmlimport { createPresence, presence, type Presence } from "@elements/ui/behaviors"; <Panel (open: boolean = false, private p: Presence = createPresence(() => open))> <div class="panel" oninsert={(e) => p.stop = presence(p, () => open, { element: () => (e.target as HTMLElement).firstElementChild })} onremove={() => p.stop?.()}> <div e:if={p.mounted} class="panel-content" data-state={p.state}><slot/></div> </div> </Panel>
app/shared/templates/panel/style.css.panel-content[data-state="open"] { animation: panel-in var(--duration-normal) var(--ease-out); } .panel-content[data-state="closed"] { animation: panel-out var(--duration-normal) var(--ease-out); } @keyframes panel-in { from { opacity: 0; transform: translateY(-4px); } } @keyframes panel-out { to { opacity: 0; transform: translateY(-4px); } }
Writes
p.mounted, p.state and p.phase; on element(), data-starting-style
for the first frame, data-ending-style during the exit, and inert while
leaving with inertWhileLeaving.
Used by
Combobox (opens in a new tab), Dialog (opens in a new tab),
DropdownMenu (opens in a new tab), ContextMenu (opens in a new tab),
Menubar (opens in a new tab), NavigationMenu (opens in a new tab),
Popover (opens in a new tab), Select (opens in a new tab).
animationsDone alone: Collapsible (opens in a new tab),
Sheet (opens in a new tab), Toast (opens in a new tab).