# presence Keeps 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`. ```ts Signatures function createPresence(read: () => boolean): Presence; function presence(p: Presence, read: () => boolean, opts?: PresenceOptions): () => void; function animationsDone(el: Element | null): Promise; ``` `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 ```ehtml app/shared/templates/panel/index.ehtml import { createPresence, presence, type Presence } from "@elements/ui/behaviors"; open))>
p.stop = presence(p, () => open, { element: () => (e.target as HTMLElement).firstElementChild })} onremove={() => p.stop?.()}>
``` ```css 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](../templates/combobox), [Dialog](../templates/dialog), [DropdownMenu](../templates/dropdown-menu), [ContextMenu](../templates/context-menu), [Menubar](../templates/menubar), [NavigationMenu](../templates/navigation-menu), [Popover](../templates/popover), [Select](../templates/select). `animationsDone` alone: [Collapsible](../templates/collapsible), [Sheet](../templates/sheet), [Toast](../templates/toast).