This spec regards a general implementation for components that should be form-associated, including checkboxes, radios, text inputs, and other components which store user input and should be captured during form submission.
- FACE: form-associated custom elements
Components that are intended to replace a native form element (input, textarea, select) should generally behave like their native counterpart. One key aspect to this is associating the element with the parent form. To do this, we will expose a mechanism to expose form-association to custom elements that can be shared across implementations with maximum re-use.
Any custom element that should associate a value to a form.
The implementation will be an abstract class that will extend FASTElement
. The class will expose implementations for all of the common form element capabilities. It will also expose and manage an implementation for when the FACE APIs are not available.
Another possible implementation is a decorator, but there are a few challenges with that approach:
- decorators cannot augment the class type directly, so we would need to use a pattern similar to https://www.typescriptlang.org/docs/handbook/mixins.html to gain accurate type definitions
- Decorators cannot (easily?) provide a default implementation that can be overridden by the extending class. Using an abstract class allows straight overrides of implementation or merging of implementation with super
The implementation will standardize an interface between two common cases: browsers with FACE API support and browsers without.
For browsers with support, methods and properties will generally proxy to the ElementInternals
implementation.
The implementation will manage a "proxy" element that will be appended to the light-dom. This proxy element will serve as the custom-element's association to the form. All relevant properties will be forwarded to this element, and certain API calls will retrieve data from it.
public static formAssociated: boolean
- Requirement of FACE API. This will feature-detect the API and resolve a value corresponding to feature-support.
public readonly validity: ValidityState
- Returns the validity of the component.
public readonly form: HTMLFormElement | null
- The associated form element.
public readonly validationMessage: string
- The current validation message of the element.
public readonly labels: Node[]
- Labels associated to the element. See risks and challenges.
protected abstract proxy: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
- A proxy element that will be appended to the DOM when FACE APIs are unavailable. It will server as the custom-elements connection to the form.
protected elementInternals: ElementInternals
- Provides form-association through dedicated APIs.
protected setFormValue(value: File | string | FormData | null, state?: File | string | FormData): void
- When using
elementInternals
, this will set the value in the form. With no FACE support, this will do nothing because the value will automatically be associated by the proxy element. This can be overridden as necessary by components with more advanced behavior.
- When using
protected handleKeyPress(): void
- Will trigger implicit submission when
enter
key is pressed to match standard input behavior. Can be overridden or extended.
- Will trigger implicit submission when
protected setValidity(flags: ValidityStateFlags, message?: string, anchor?: HTMLElement): void;
- With form association support, will set the validity of the component. With no form association, this will do nothing unless a message is passed. If a message is passed, the proxy element's
setCustomValidity
method will be invoked with the value of the message.
- With form association support, will set the validity of the component. With no form association, this will do nothing unless a message is passed. If a message is passed, the proxy element's
id: string
- The id attribute of the component. Used for label association.
value: string | File | FormData
- When provided as a content attribute, value must be a string. When provided as an IDL property, value can be a
string
,File
, orFormData
- When provided as a content attribute, value must be a string. When provided as an IDL property, value can be a
disabled: boolean
- Boolean attribute. Disables the form control, ensuring it doesn't participate in form submission
name: string
- The name of the element. Allows access by name from the associated form.
required: boolean
- Boolean attribute.
Accessing the labels
property of a native input returns a NodeList
. In cases where the FACE APIs are available, the implementation from ElementInternals.labels
works as expected. In cases where we implement a proxy element though, we need to construct the label set from the following:
- any parent
<label>
element. Can be retrieved from the proxy element'slabels
property - any
<label>
element with[for="$id"]
, where $id is theid
attribute of the custom element. Because the custom element does not reflect it'sid
attribute to the proxy element (that would result in two elements with the same ID in the DOM), the labels are not automatically associated to thelabels
property of the proxy.
NodeList
s are not constructable in JavaScript, thus this property is standardized to Node[]
due to the need to construct the label when implementing with a proxy element. The use of Node[] is intended to map as close to NodeList
as possible.
Clicking a label of a native input element can have several side effects. First (and in general) it will focus the element. In cases like radio and checkbox, it changes the value of the input. There are likely other side-effects.
How this works under the hood seems to be that clicking the label also invokes a click event on the element the label labels.
However, because (in proxy element cases) the label's aren't actually associated to the element, the click event on the custom element isn't ever fired. This is the case for all label's using the for
attribute to make the association. Wrapping labels (<label><custom-element></label>
) do seem to fire the click event on the custom element.
See (next steps)[#next-steps].
No accessibility concerns for this utility. Accessibility will be the concern of the implementing class.
No globalization concerns.
No new security concerns. Form data sanitation will be the responsibility of the consuming application.
Potentially using a MutationObserver to attach new click handlers to label elements. TBD on if we want to do this.
No Dependencies
TBD. Form association APIs are very new so JSDOM is not likely to expose the feature. It might make sense to do in-browser tests against browsers both with and without support to ensure parity.
- Form Participation API Explained
- Creating a form-associated custom element
- More capable form controls
- Implicit submission
- We will solve label-clicking for browsers with no FACE support in the future. A promising approach would be to catch click events on the parent form and delegate focus from there. The edge-case this does not address is elements that are not a descendent of the form element but are still associated using the
form
content attributes. - Determine how autofocus needs to be handled
- Determine how to react to fieldset / form disabling in non-FACE browsers