Skip to content

Interface: PlayDomOptions

API / @xmachines/play-dom / PlayDomOptions

Defined in: packages/play-dom/src/types.ts:23

The options of PlayRenderer.

This type extends UIProviderOptions, which gives functions, directives, validationFunctions, navigate, onRenderError, onConfirm, and confirmDialog. Each render pass puts them into DomRenderContext, and a component implementation reads them at ctx.ctx.*.

Extends

Properties

PropertyTypeDescriptionInherited fromDefined in
confirmDialog?booleanfalse turns the default confirm dialog off, for a host whose component shows the pending confirmation through ctx.confirmation. Without onConfirm, the default (true) shows the dialog on document.body for each binding with confirm that waits.UIProviderOptions.confirmDialog-
directives?DirectiveDefinition<ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>>[]Custom directives — user-defined $-prefixed dynamic values that extend the spec expression language (e.g. { $format: "currency", value: ... }). Each entry is a DirectiveDefinition built with defineDirective from @xmachines/json-render-core. The array is converted to a DirectiveRegistry once per render pass and forwarded to resolveElementProps via PropResolutionContext.directives. Built-in expressions ($state, $cond, etc.) always take precedence over custom directives. Mirrors the directives prop on JSONUIProvider / JsonUIProvider introduced in Json-render 0.19. Example import { defineDirective, resolvePropValue } from "@xmachines/json-render-core"; import { z } from "zod"; const formatDirective = defineDirective({ name: "$format", description: "Locale-aware number formatting.", schema: z.object({ $format: z.enum(["number", "currency"]), value: z.unknown() }), resolve(value, ctx) { const resolved = resolvePropValue(value.value, ctx); return new Intl.NumberFormat().format(resolved as number); }, }); renderSpec(spec, store, registry, { directives: [formatDirective] });UIProviderOptions.directives-
fallback?HTMLElement | (() => HTMLElement) | nullThe optional element that the renderer shows when currentView is null, and after a failure of a rebuild that it contains. The renderer shows it for EVERY null view, and not for the first mount only: a view that returns to null after a view that was not null also shows it. The four framework providers hold the same rule for their placeholder content. The renderer shows it after a failure of a rebuild too. This option turns nothing on: the renderer contains such a failure always, and this option says only what the empty container shows. The next view that renders clears the container, which removes the element. One element belongs to one renderer. appendChild MOVES a node. Two renderers that hold the same element therefore take it from each other on every null view, and the container of the first one goes empty without a notice. Build one element for each PlayRenderer. A FUNCTION also works here. The renderer calls it at the first moment that it needs the element, and it keeps the result, so a renderer whose view is never null builds nothing. CreatePlayUIOptions.fallback passes such a function for each mount. A producer that THROWS is contained on BOTH paths — a null view, and a rebuild that failed — and the renderer writes it to console.error and NOT to PlayDomOptions.onError. On the second path the renderer calls the producer from inside the containment of the view, and a second trip through onError would report the failure of the fallback as a failure of the view. The container stays empty.-packages/play-dom/src/types.ts:136
functions?Record<string, ComputedFunction>Named compute functions for { $computed: "name", args: {...} } prop expressions. Each function receives the resolved args object and returns the computed value. Forwarded directly to resolveElementProps via PropResolutionContext.functions, matching the FunctionsContext provider in the framework renderers. Without this option, $computed expressions resolve to undefined (no throw). Example renderSpec(spec, store, registry, { functions: { fullName: (args) => ${args.first} ${args.last}, formatCurrency: (args) => new Intl.NumberFormat().format(args.amount as number), }, }); Mirrors the functions prop on JSONUIProvider / JsonUIProvider.UIProviderOptions.functions-
loading?booleanWith the value true, the spec is still streaming, for example from an AI provider. The renderer gives the flag to renderSpec. A component implementation therefore reads ctx.ctx.loading, and it can render a skeleton state. The flag also stops each warning about an absent child during the ingestion of the stream, because an element of a reference can still be absent from the incremental spec. This option matches the loading prop of the framework renderer providers.-packages/play-dom/src/types.ts:54
navigate?(path) => voidProgrammatic navigation function. Called automatically when an action binding resolves with onSuccess: { navigate: "/path" }. The resolved path string is passed as the sole argument. Mirrors the navigate prop in ActionProvider from the framework renderers, where it is captured in the execute() closure. Also available to component implementations directly via ctx.ctx.navigate for cases where navigation needs to be triggered outside of an action binding. Example import { useNavigate } from "my-router"; const navigate = useNavigate(); renderSpec(spec, store, registry, { navigate }); Spec binding that triggers navigation: { "on": { "click": { "action": "doSomething", "onSuccess": { "navigate": "/dashboard" } } } }UIProviderOptions.navigate-
onConfirm?ConfirmHandlerConfirmation hook for actions that declare confirm in their binding. Consulted by emit() before executing a confirm-gated action. Return (or resolve) true to execute, false to skip. Without this handler, the binding waits as the pending confirmation of the tree (ctx.confirmation), and the default confirm dialog shows it. See ConfirmHandler for the full contract.UIProviderOptions.onConfirm-
onError?(error, reset) => voidThe optional handler of a failure of the view: a failure of the root, or of a complete rebuild. This option is NOT the same as onRenderError, which UIProviderOptions gives: the inner renderer routes a render error of one component below the root, and a rejection of one action handler, to onRenderError. It contains each of them per element, and the rest of the view stays. onError receives a failure that leaves no view: the component, a prop, or the visible condition of the ROOT throws, or the resolution of the store of the view throws. The inner renderer also gives a failure of the root to onRenderError, with info.kind "root". The renderer CONTAINS such a failure, with this option and without it: it resets the state of the failed rebuild, so that the next emission makes a complete render again, and it clears the container. connect() renders the first view synchronously, and a bad initial view does not make connect() throw. A WRITE of the store takes the same path. The inner renderer shows the write at its commit point, a microtask after the write, and it resolves the props of every element that the write touched there. A $computed function of the root that throws on the new state fails the root. The renderer contains such a failure as well, and the action handler that wrote the state receives no exception. This option says WHERE the report goes. Without it, the renderer writes the contained failure to console.error. A handler that THROWS reaches no caller: the renderer contains that throw too, and it writes the throw to console.error. A host that must escalate a failure raises it from a task of its own — rethrowLater(error) of @xmachines/core/utils — which reaches the global handler of the page and leaves the containment whole. The host installs @xmachines/core itself. The five renderers hold this one rule. The second parameter is the RESET, for a retry that the host starts — a “Retry” button of its own. It renders the view that the actor holds at the MOMENT OF THE CALL, so a host that keeps the callback cannot rewind the screen to the view that failed. A reset that the host calls from inside this handler does nothing, because no input changed between the two attempts, and a reset after disconnect() does nothing. A reset belongs to ONE connection. connect() starts a connection, and the reset of a report of an older connection does nothing. A host that calls connect() again from inside this handler abandons the connection that reported. The “Retry” button of that report must not render into the connection that took its place. Call the reset of the newest report. This option matches the onError prop of the framework renderer providers, and the five renderers hold the same three rules for the reset.-packages/play-dom/src/types.ts:104
onRenderError?OnRenderErrorUnified error handler, (error, name, info), for every failure in code that the renderer runs. Each failure reaches it one time: - info.kind "element" — a component renderer below the root throws, an effect of it throws, or a read of its children after the pass fails - "props" / "visible" — a prop expression or the visible condition of an element throws; the boundary of the parent fails - "root" — the root fails (its component, a prop, or its visible condition); it has no boundary above it. The renderer shows nothing and throws nothing, also without a handler. A new version of the root renders again - "action" / "watch" — an on chain or a watch chain fails name is the element type (for props and visible, the type of the boundary that fails), or the action name for action and watch. info.key, info.type and info.action name the element and the action. A handler with two parameters still works. When provided, suppresses the console.error fallback for each of them. Without it, the renderer reads the handler that defineRegistry(catalog, { onRenderError }) put on the registry, and then writes one [json-render] console line. A handler that throws gives its error to reportError one time, and no other element fails. Forwarded into renderSpec as the onRenderError parameter and into DomRenderContext.onRenderError so both emit() and watch handlers route their errors through the same channel as component render errors. Example renderSpec(spec, store, registry, { onRenderError: (err, name, info) => { myErrorReporter.capture(err, { context: name, ...info }); }, });UIProviderOptions.onRenderError-
registryResult?DefineRegistryResultThe result of defineRegistry. It gives the registry and the factory of the handlers. With this option, PlayRenderer connects setState and getState of the StateStore on @xstate/store to the factory of the handlers for you. This is the preferred way, because each action then always receives a live setState and a live state on the current store.-packages/play-dom/src/types.ts:32
store?StateStoreThe optional external StateStore, for example from xstateStoreStateStore in @xmachines/json-render-xstate. With this option, PlayRenderer works in the controlled mode: it ignores spec.state, and this store is the single source of truth of the UI state, such as a form value. Without this option, the renderer makes a new @xstate/store atom for each view transition, with the values of spec.state.-packages/play-dom/src/types.ts:42
validationFunctions?Record<string, (value, args?) => boolean>Custom validation functions for inline field validation. Each function receives (value, args?) and returns true (valid) or false (invalid). Functions are available to component implementations via ctx.ctx.validationFunctions and should be passed as customFunctions to runValidationCheck / runValidation from @xmachines/json-render-core. Mirrors customFunctions in ValidationProvider from the framework renderers. The DOM renderer has no automatic ValidationProvider tree — validation must be invoked explicitly by component implementations. Example import { runValidationCheck } from "@xmachines/json-render-core"; renderSpec(spec, store, registry, { validationFunctions: { isEven: (value) => typeof value === "number" && value % 2 === 0, phoneNumber: (value) => /^\+?[\d\s\-()]{7,}$/.test(String(value)), }, }); // Inside a ComponentFn: const MyField: ComponentFn<typeof catalog, "MyField"> = ({ ctx }) => { const result = runValidationCheck( { type: "isEven", message: "must be even" }, { value: someValue, stateModel: {}, customFunctions: ctx.ctx.validationFunctions }, ); // result.valid, result.message };UIProviderOptions.validationFunctions-