On this pageFunctions
Ui/Dialog
/**
* Returns the framework-managed id the dialog's `aria-describedby` points at,
* the `-dialog-description` suffix on `model.id`.
*
* The primary path is spreading `RenderInfo`'s `description` onto your
* description element (`h.p([...description], [...])`), which carries this id
* for you. Reach for this helper only when you need the id as a value outside
* `toView`: a Command that calls `getElementById`, a cross-element
* `aria-describedby`, or a test. Do not hand-roll the id string.
*/
(model: Dialog.Model): string/** Creates an initial dialog model from a config. Defaults to closed and non-animated. */
(config: InitConfig): Dialog.Model/**
* Returns the framework-managed id the dialog's `aria-labelledby` points at,
* the `-dialog-title` suffix on `model.id`.
*
* The primary path is spreading `RenderInfo`'s `title` onto your heading
* (`h.h2([...title], [...])`), which carries this id for you. Reach for this
* helper only when you need the id as a value outside `toView`: a Command that
* calls `getElementById`, a cross-element `aria-describedby`, or a test. Do not
* hand-roll the id string.
*/
(model: Dialog.Model): string/**
* Configuration for creating a dialog model with `init`. The `id` must be
* non-empty and unique within the document: it keys the dialog element, its
* ARIA references, and the framework's per-dialog resource cleanup, so a
* duplicate or empty id breaks cleanup accounting.
*
* The dialog derives framework-managed ids from this `id`: `-dialog-title`,
* `-dialog-description`, and `-panel` (the animation panel). Spread
* `RenderInfo`'s `title` / `description` onto your heading and description
* elements rather than constructing those ids yourself.
*/
type InitConfig = Readonly<{
focusSelector: string
id: string
isAnimated: boolean
isOpen: boolean
}>/**
* Render-time payload published to the consumer's `toView`.
*
* - `dialog`: attributes for the native `<dialog>` element. Carries
* the id, ARIA labelling, `open` prop, positioning style, the
* `OnCancel` handler that wires Escape to `RequestedClose`, and an
* `OnUnmount` backstop that releases framework hygiene (scroll lock,
* focus trap, return focus) if the element is removed from the DOM
* while still open, such as navigating away from a route-keyed subtree.
* The consumer MUST render an `h.dialog(...)` element so the framework
* can open and close it, and so the unmount backstop can fire.
* - `backdrop`: attributes for the backdrop element. Includes the
* Animation data attributes and the `OnClick` handler that closes
* the dialog on outside-click (suppressed while a leave animation
* is in progress).
* - `panel`: attributes for the panel element. Includes the panel id
* (`${model.id}-panel`) and the Animation data attributes.
* - `title`: attributes for the accessible-name heading. Carries the
* framework-managed id the dialog's `aria-labelledby` points at. Spread
* onto your heading element (`h.h2([...title], [...])`) so labelling
* wires up without hand-rolling the id.
* - `description`: attributes for the description element. Carries the
* framework-managed id the dialog's `aria-describedby` points at. Spread
* onto your description element (`h.p([...description], [...])`).
* - `initialFocus`: attributes for the element that should receive focus when
* the dialog opens. Spread onto that element (`h.input([...initialFocus])`).
* A configured `focusSelector` (see `init`) takes precedence, and focus
* falls back to the default when no element carries the group.
* - `closeButton`: attributes for an in-panel close control such as a Cancel
* or dismiss button. Carries the `OnClick` handler that closes the
* dialog (suppressed while a leave animation is in progress). Spread
* onto your own button so a plain close needs no parent message. Sets
* `type="button"` so that a close control inside a `form` element in the
* panel closes without also submitting the form. Spread a later `h.Type`
* to override it.
* - `isVisible`: derived from `isOpen` and the Animation
* `transitionState`. The consumer renders backdrop + panel only
* while this is true.
*/
type RenderInfo = Readonly<{
backdrop: ReadonlyArray<ChildAttribute>
closeButton: ReadonlyArray<ChildAttribute>
description: ReadonlyArray<ChildAttribute>
dialog: ReadonlyArray<ChildAttribute>
initialFocus: ReadonlyArray<ChildAttribute>
isVisible: boolean
panel: ReadonlyArray<ChildAttribute>
title: ReadonlyArray<ChildAttribute>
}>/** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field. */
type ViewInputs = Readonly<{
toView: (render: RenderInfo) => Html
}>/**
* Calls `close()` on the native dialog element and unlocks page scroll when
* the close released the resources `ShowDialog` installed. A close that runs
* before the show has installed them leaves the lock alone. When the show
* then fails, it releases the lock itself. When the show succeeds, update
* closes the dialog again. If the dialog element is gone by the time the
* close runs, the Command calls `Dom.releaseDialogResources` instead. That
* releases the scroll lock, focus trap, return focus, and stack entry if the
* dialog still holds them.
*/
const CloseDialog: CommandDefinitionWithArgs<"CloseDialog", {
id: String
}, Effect<{
_tag: "CompletedCloseDialog"
}, never, never>>/** Union of all messages the dialog component can produce. */
const Message: MessageUnion<{
CompletedCloseDialog: {}
CompletedReleaseDialogResources: {}
FailedShowDialog: {}
GotAnimationMessage: {
message: MessageUnion<{
CompletedWaitForPaint: {}
EndedAnimation: {}
Hid: {}
Showed: {}
}>
}
RequestedClose: {}
RequestedOpen: {}
SucceededShowDialog: {}
Unmounted: {}
}>/** Schema for the dialog component's state, tracking its unique ID, open/closed status, animation support, and animation lifecycle phase. */
const Model: Struct<{
animation: Struct<{
id: String
isShowing: Boolean
transitionState: Literals<readonly ["Idle", "EnterStart", "EnterAnimating", "LeaveStart", "LeaveAnimating"]>
}>
id: String
isAnimated: Boolean
isOpen: Boolean
maybeFocusSelector: Option<String>
}>/** Union of out-messages the dialog component can produce. */
const OutMessage: MessageUnion<{
Closed: {}
Opened: {}
}>/**
* Releases the framework hygiene the dialog holds while open (scroll lock,
* focus trap, return focus, stack entry) when the element unmounts without a
* purposeful close. Idempotent: a no-op if the dialog already released its
* resources through `CloseDialog`.
*/
const ReleaseDialogResources: CommandDefinitionWithArgs<"ReleaseDialogResources", {
id: String
}, Effect<{
_tag: "CompletedReleaseDialogResources"
}, never, never>>/**
* Locks page scroll and opens the native dialog element through
* `Dom.showDialog`, which calls `show()` (not native `showModal()`) so other
* high-z-index overlays stay interactive. It layers the dialog with a high
* z-index, traps focus, and dispatches a `cancel` event on Esc. The Dialog
* component supplies its own backdrop. If the dialog element is gone by the
* time the show runs, the lock is released and the Command reports
* `FailedShowDialog`. A closed dialog has no `OnUnmount`, so nothing else
* would release the lock. The update function then closes the Model. Without
* this close, the dialog would render open with no lock and no focus trap.
* The lock is also released if the Command is interrupted while it waits.
*/
const ShowDialog: CommandDefinitionWithArgs<"ShowDialog", {
focusSelector: String
id: String
}, Effect<{
_tag: "SucceededShowDialog"
} | {
_tag: "FailedShowDialog"
}, never, never>>/**
* Renders a headless dialog component backed by the native `<dialog>`
* element. `ShowDialog` opens it through `Dom.showDialog`, which uses `show()`
* (not native `showModal()`) with a high z-index, a focus trap, a
* component-supplied backdrop, and a `cancel` event dispatched on Esc.
*/
const view: SubmodelView<Dialog.Model, {
_tag: "RequestedOpen"
} | {
_tag: "RequestedClose"
} | {
_tag: "SucceededShowDialog"
} | {
_tag: "FailedShowDialog"
} | {
_tag: "CompletedCloseDialog"
} | {
_tag: "Unmounted"
} | {
_tag: "CompletedReleaseDialogResources"
} | {
_tag: "GotAnimationMessage"
message: {
_tag: "Showed"
} | {
_tag: "Hid"
} | {
_tag: "CompletedWaitForPaint"
} | {
_tag: "EndedAnimation"
}
}, Readonly<{
toView: (render: RenderInfo) => Html
}>>