@texaryn/web-components
v0.4.2
Published
A <texaryn-form> custom element over the Texaryn runtime, with native default widgets
Maintainers
Readme
@texaryn/web-components
A <texaryn-form> custom element for Texaryn, rendering native form controls
in light DOM. Usable from any framework, or from none.
Status: pre-1.0. Public APIs may change before 1.0.
Install
pnpm add @texaryn/core @texaryn/schema-json @texaryn/web-componentsThe package depends on @texaryn/core only. It has no framework dependency
and loads no stylesheet.
Quick start
Registration is explicit, so importing the package defines nothing on its own:
import { createJsonSchemaAdapter } from '@texaryn/schema-json'
import { createDefaultRegistry, defineTexarynForm } from '@texaryn/web-components'
import type { TexarynFormElement } from '@texaryn/web-components'
defineTexarynForm()
const port = await createJsonSchemaAdapter({
type: 'object',
properties: { name: { type: 'string', title: 'Name' } },
required: ['name'],
})
const form = document.createElement('texaryn-form') as TexarynFormElement
form.registry = createDefaultRegistry()
form.errorSummary = true
form.options = { initialData: { name: '' }, onSubmit: (data) => console.log(data) }
form.port = port
document.body.append(form)Two ownership modes
The element either creates a runtime or borrows one, and the two are mutually exclusive: setting both throws.
portplusoptionsis the managed mode. The element creates a runtime and destroys it when the element is removed from the document.optionsis read when that runtime is created, so set it beforeport; settingportagain rebuilds the runtime with the current options.runtimeis the primitive input. A runtime assigned this way is borrowed and never destroyed by the element, which is what lets an application own one runtime and render it through several bindings.
Being removed from the document is not the same as being discarded. Moving the
element between parents fires disconnectedCallback too, so disposal is
deferred and skipped if the element is connected again, and a reparent keeps
the runtime, the controls and their values.
Events
Native events are left alone. The controls are real elements in light DOM, so
input, change and blur bubble on their own. The element adds only its own
namespaced events, each carrying a store snapshot as detail:
texaryn-data-change, the current form datatexaryn-submission-change, the current submission state
Submission
The element renders one <form novalidate> and turns its submit event into
the Submit command, so a type="submit" button placed inside the form works
the way a consumer expects. The runtime owns validation, which is why the
native novalidate is set and required fields carry aria-required rather
than the native required attribute.
Ids
Every id is <prefix>-<nodeId>-<suffix>, where the prefix is the element's own
id attribute when it has one and an allocated value otherwise. Two forms on
one page therefore share no ids, and every for and aria-describedby
resolves inside its own element.
Messages
messages on the element, or in mountForm's options, takes a whole
FormMessages set from @texaryn/core and replaces every word the built-in
widgets invent. Setting it on a mounted element switches the copy in place.
Custom widgets read ctx.messages on the render context.
Error summary
mountErrorSummary(container, form, { focus }) renders a named group, headed
by an h2, listing the runtime's visible errors as the first child of
container, and nothing while there are none. It takes the Mount that
mountForm returned, so its links share the namespace the form was mounted
under: #<prefix>-<nodeId>-input, the id the built-in widgets give their
control, which a custom widget that wants summary navigation gives its own.
It takes focus once a failed submit settles, once per attempt; focus: false
keeps it passive, for all but one summary when one runtime is rendered twice.
setMessages on the returned mount follows a locale switch in place, and
unmounting the summary leaves the form mounted. A caller composing the two
primitives calls setMessages on both mounts; the element does that for its
own.
On the element, error-summary present mounts the summary as the first child
of the element's <form> and error-summary="no-focus" mounts it passive;
errorSummary and errorSummaryFocus reflect the attribute and its value.
Toggling either applies live, and a value set while the element is detached
applies when it next mounts. The summary is not a live region: the fields
already announce their own errors, and the focus move is what speaks the
heading.
Key exports
defineTexarynFormTexarynFormElementcreateDefaultRegistrymountForm(container, runtime, { registry, idPrefix, messages }), returning aMountcarryingruntime,idPrefixandsetMessagesmountErrorSummary(container, form, { focus }), returning anErrorSummaryMountwithsetMessagesmakeIdtextInput,numberInput,checkbox,select,textareaobjectLayout,arrayControl- types:
DomWidget,WidgetFactory,NodeBinding,RenderContext,Mount,ErrorSummaryOptions,ErrorSummaryMount
Not in this release
Shadow DOM, formAssociated and ElementInternals, nesting inside another
<form>, group and layout containers beyond objects and arrays, and text and
action nodes.
Related packages
@texaryn/core, runtime and renderer contracts@texaryn/schema-json, JSON Schema adapter@texaryn/react, the React binding over the same runtime@texaryn/vue, the Vue binding over the same runtime
See the repository README for the complete architecture.
License
Apache-2.0
