diff --git a/docs/docs/components/input.md b/docs/docs/components/input.md index 066412575..78f5f9c4a 100644 --- a/docs/docs/components/input.md +++ b/docs/docs/components/input.md @@ -5,17 +5,7 @@ layout: component.njk --- ```html {.example} - - - - - +
``` {% raw %} diff --git a/docs/docs/resources/changelog.md b/docs/docs/resources/changelog.md index d6d6a244d..da7151d86 100644 --- a/docs/docs/resources/changelog.md +++ b/docs/docs/resources/changelog.md @@ -16,6 +16,7 @@ New versions of Web Awesome are released as-needed and generally occur when a cr - Checkboxes will no longer have a `checked` attribute set when their `checked` property is changed. IE: `el.checked = true`. Instead, use the `:state(:checked)` and for unsupported browsers, use `[data-checked]` - `data-optional`, `data-required`, `data-invalid`, `data-valid`, `data-user-invalid`, and `data-user-valid` have all been renamed to have a `data-wa-*` prefix. Like so: `data-wa-valid`, `data-wa-invalid`, to avoid any conflicts with user provided attributes. - `` and `` now use `:state(checked)` and `[data-wa-checked]` for CSS styling their "checked" state. The "checked" attribute now maps to `defaultChecked` just like native HTML checkboxes. +- `getFormControls()` has been removed. We use Form Associated Custom Elements now and can reliably grab Web Awesome Elements via `formElement.elements`. - Added `setKitCode()` and `getKitCode()` functions as well as support for setting kit codes declaratively with `data-webawesome-kit` diff --git a/docs/docs/resources/contributing.md b/docs/docs/resources/contributing.md index 10e88f28f..ba607cf37 100644 --- a/docs/docs/resources/contributing.md +++ b/docs/docs/resources/contributing.md @@ -411,6 +411,9 @@ Form controls should support submission and validation through the following con - All form controls must have an `invalid` property that reflects their validity - All form controls should mirror their native validation attributes such as `required`, `pattern`, `minlength`, `maxlength`, etc. when possible and use the `MirrorValidator`. - All form controls must be tested to work with the standard `
` element +- Form controls that **DO NOT** have an editable value such as a button only need `@property({ reflect: true }) value` +- Form controls that **DO** have an editable value such as an input or textarea should have: `@property({ attribute: false }) value` and `@property({ attribute: "value", reflect: true }) defaultValue`. We do this to align with how native form controls work. +- Form controls which have an editable property such as `checked` or `selected` should also have a `defaultSelected` and `defaultChecked` property respectively for use when the form is "reset". ### System Icons diff --git a/src/components/color-picker/color-picker.ts b/src/components/color-picker/color-picker.ts index e75bc1eeb..50ca8d87e 100644 --- a/src/components/color-picker/color-picker.ts +++ b/src/components/color-picker/color-picker.ts @@ -8,7 +8,7 @@ import { clamp } from '../../internal/math.js'; import { classMap } from 'lit/directives/class-map.js'; import { customElement, eventOptions, property, query, state } from 'lit/decorators.js'; import { drag } from '../../internal/drag.js'; -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import { LocalizeController } from '../../utilities/localize.js'; import { RequiredValidator } from '../../internal/validators/required-validator.js'; diff --git a/src/components/icon-button/icon-button.test.ts b/src/components/icon-button/icon-button.test.ts index 3e1d3e026..1b10810bb 100644 --- a/src/components/icon-button/icon-button.test.ts +++ b/src/components/icon-button/icon-button.test.ts @@ -9,7 +9,7 @@ describe('', () => { it('default properties', async () => { const el = await fixture(html` `); - expect(el.name).to.be.undefined; + expect(el.name).to.be.null; expect(el.library).to.be.undefined; expect(el.src).to.be.undefined; expect(el.href).to.be.undefined; diff --git a/src/components/input/input.test.ts b/src/components/input/input.test.ts index 3450021b2..cc02bfdc7 100644 --- a/src/components/input/input.test.ts +++ b/src/components/input/input.test.ts @@ -18,7 +18,7 @@ describe('', async () => { expect(el.type).to.equal('text'); expect(el.size).to.equal('medium'); - expect(el.name).to.equal(''); + expect(el.name).to.equal(null); expect(el.value).to.equal(''); expect(el.defaultValue).to.equal(''); expect(el.title).to.equal(''); diff --git a/src/components/input/input.ts b/src/components/input/input.ts index cae666494..b9be4d25f 100644 --- a/src/components/input/input.ts +++ b/src/components/input/input.ts @@ -290,7 +290,10 @@ export default class WaInput extends WebAwesomeFormAssociatedElement { const button = [...form.elements].find((el: HTMLButtonElement) => el.type === "submit" && !el.disabled) as undefined | HTMLButtonElement | WaButton - if (!button) return + if (!button) { + form.requestSubmit(null) + return + } if (button.tagName.toLowerCase() === "button") { form.requestSubmit(button) diff --git a/src/components/radio-button/radio-button.ts b/src/components/radio-button/radio-button.ts index 1ca70cb5f..83e323813 100644 --- a/src/components/radio-button/radio-button.ts +++ b/src/components/radio-button/radio-button.ts @@ -3,7 +3,6 @@ import { customElement, property, query, state } from 'lit/decorators.js'; import { HasSlotController } from '../../internal/slot.js'; import { html } from 'lit/static-html.js'; import { ifDefined } from 'lit/directives/if-defined.js'; -import { LitElement } from 'lit'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; @@ -135,7 +134,7 @@ export default class WaRadioButton extends WebAwesomeFormAssociatedElement { })} aria-disabled=${this.disabled} type="button" - .value=${ifDefined(this.value)} + value=${ifDefined(this.value)} tabindex="${this.checked ? '0' : '-1'}" @blur=${this.handleBlur} @focus=${this.handleFocus} diff --git a/src/components/radio-group/radio-group.ts b/src/components/radio-group/radio-group.ts index 7a44779e9..203017eb7 100644 --- a/src/components/radio-group/radio-group.ts +++ b/src/components/radio-group/radio-group.ts @@ -183,26 +183,6 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement { } } - private syncRadios() { - if (customElements.get('wa-radio') && customElements.get('wa-radio-button')) { - this.syncRadioElements(); - return; - } - - if (customElements.get('wa-radio')) { - this.syncRadioElements(); - } else { - customElements.whenDefined('wa-radio').then(() => this.syncRadios()); - } - - if (customElements.get('wa-radio-button')) { - this.syncRadioElements(); - } else { - // Rerun this handler when or is registered - customElements.whenDefined('wa-radio-button').then(() => this.syncRadios()); - } - } - /** * We use the first available radio as the validationTarget similar to native HTML that shows the validation popup on * the first radio element. @@ -218,7 +198,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement { @watch('size', { waitUntilFirstUpdate: true }) handleSizeChange() { - this.syncRadios(); + this.syncRadioElements(); } formResetCallback(...args: Parameters) { @@ -291,7 +271,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement { const hasHelpTextSlot = this.hasSlotController.test('help-text'); const hasLabel = this.label ? true : !!hasLabelSlot; const hasHelpText = this.helpText ? true : !!hasHelpTextSlot; - const defaultSlot = html` `; + const defaultSlot = html` `; return html`
{ + if (!this.disabled) { + this.checked = true; + } + }; + render() { return html` ', async () => { it('default properties', async () => { const el = await fixture(html` `); - expect(el.name).to.equal(''); + expect(el.name).to.equal(null); expect(el.value).to.be.null; expect(el.title).to.equal(''); expect(el.disabled).to.be.false; diff --git a/src/components/switch/switch.ts b/src/components/switch/switch.ts index a4cdb1497..d8e65e953 100644 --- a/src/components/switch/switch.ts +++ b/src/components/switch/switch.ts @@ -1,6 +1,5 @@ import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query, state } from 'lit/decorators.js'; -import { defaultValue } from '../../internal/default-value.js'; import { HasSlotController } from '../../internal/slot.js'; import { html } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; diff --git a/src/components/textarea/textarea.test.ts b/src/components/textarea/textarea.test.ts index 977bef614..99badd5a2 100644 --- a/src/components/textarea/textarea.test.ts +++ b/src/components/textarea/textarea.test.ts @@ -15,7 +15,7 @@ describe('', async () => { const el = await fixture(html` `); expect(el.size).to.equal('medium'); - expect(el.name).to.equal(''); + expect(el.name).to.equal(null); expect(el.value).to.equal(''); expect(el.defaultValue).to.equal(''); expect(el.title).to.equal(''); diff --git a/src/components/textarea/textarea.ts b/src/components/textarea/textarea.ts index 7ec108597..6ddb8908b 100644 --- a/src/components/textarea/textarea.ts +++ b/src/components/textarea/textarea.ts @@ -61,7 +61,10 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement { @property({ reflect: true }) name: string | null = null; /** The current value of the textarea, submitted as a name/value pair with form data. */ - @property({ attribute: false }) value = ''; + @property({ attribute: false }) value: null | string = ''; + + /** The default value of the form control. Primarily used for resetting the form control. */ + @property({ reflect: true, attribute: 'value' }) defaultValue: null | string = ''; /** The textarea's size. */ @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium'; @@ -141,9 +144,6 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement { */ @property() inputmode: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url'; - /** The default value of the form control. Primarily used for resetting the form control. */ - @property({ reflect: true, attribute: 'value' }) defaultValue: string = ''; - connectedCallback() { super.connectedCallback(); @@ -215,7 +215,6 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement { /** Removes focus from the textarea. */ blur() { this.input.blur(); - // this.checkValidity(); } /** Selects all the text in the textarea. */ diff --git a/src/internal/form.ts b/src/internal/form.ts deleted file mode 100644 index 201aaacbc..000000000 --- a/src/internal/form.ts +++ /dev/null @@ -1,476 +0,0 @@ -import type { ReactiveController, ReactiveControllerHost } from 'lit'; -import type { WebAwesomeFormControl } from './webawesome-element.js'; -import type WaButton from '../components/button/button.js'; - -// -// We store a WeakMap of forms + controls so we can keep references to all Web Awesome controls within a given form. As -// elements connect and disconnect to/from the DOM, their containing form is used as the key and the form control is -// added and removed from the form's set, respectively. -// -export const formCollections: WeakMap> = new WeakMap(); - -// -// We store a WeakMap of reportValidity() overloads so we can override it when form controls connect to the DOM and -// restore the original behavior when they disconnect. -// -const reportValidityOverloads: WeakMap boolean> = new WeakMap(); -const checkValidityOverloads: WeakMap boolean> = new WeakMap(); - -// -// We store a Set of controls that users have interacted with. This allows us to determine the interaction state -// without littering the DOM with additional data attributes. -// -const userInteractedControls: WeakSet = new WeakSet(); - -// -// We store a WeakMap of interactions for each form control so we can track when all conditions are met for validation. -// -const interactions = new WeakMap(); - -export interface FormControlControllerOptions { - /** A function that returns the form containing the form control. */ - form: (input: WebAwesomeFormControl) => HTMLFormElement | null; - /** A function that returns the form control's name, which will be submitted with the form data. */ - name: (input: WebAwesomeFormControl) => string; - /** A function that returns the form control's current value. */ - value: (input: WebAwesomeFormControl) => unknown | unknown[]; - /** A function that returns the form control's default value. */ - defaultValue: (input: WebAwesomeFormControl) => unknown | unknown[]; - /** A function that returns the form control's current disabled state. If disabled, the value won't be submitted. */ - disabled: (input: WebAwesomeFormControl) => boolean; - /** - * A function that maps to the form control's reportValidity() function. When the control is invalid, this will - * prevent submission and trigger the browser's constraint violation warning. - */ - reportValidity: (input: WebAwesomeFormControl) => boolean; - - /** - * A function that maps to the form control's `checkValidity()` function. When the control is invalid, this will return false. - * this is helpful is you want to check validation without triggering the native browser constraint violation warning. - */ - checkValidity: (input: WebAwesomeFormControl) => boolean; - /** A function that sets the form control's value */ - setValue: (input: WebAwesomeFormControl, value: unknown) => void; - /** - * An array of event names to listen to. When all events in the list are emitted, the control will receive validity - * states such as user-valid and user-invalid.user interacted validity states. */ - assumeInteractionOn: string[]; -} - -/** A reactive controller to allow form controls to participate in form submission, validation, etc. */ -export class FormControlController implements ReactiveController { - host: WebAwesomeFormControl & ReactiveControllerHost; - form?: HTMLFormElement | null; - options: FormControlControllerOptions; - - constructor(host: ReactiveControllerHost & WebAwesomeFormControl, options?: Partial) { - (this.host = host).addController(this); - this.options = { - form: input => { - // If there's a form attribute, use it to find the target form by id - // Controls may not always reflect the 'form' property. For example, `` doesn't reflect. - const formId = input.form; - - if (formId) { - const root = input.getRootNode() as Document | ShadowRoot | HTMLElement; - const form = root.querySelector(`#${formId}`); - - if (form) { - return form as HTMLFormElement; - } - } - - return input.closest('form'); - }, - name: input => input.name || '', - value: input => input.value, - defaultValue: input => input.defaultValue, - disabled: input => input.disabled ?? false, - reportValidity: input => (typeof input.reportValidity === 'function' ? input.reportValidity() : true), - checkValidity: input => (typeof input.checkValidity === 'function' ? input.checkValidity() : true), - setValue: (input, value: string) => (input.value = value), - assumeInteractionOn: ['wa-input'], - ...options - }; - } - - hostConnected() { - const form = this.options.form(this.host); - - if (form) { - this.attachForm(form); - } - - // Listen for interactions - interactions.set(this.host, []); - this.options.assumeInteractionOn.forEach(event => { - this.host.addEventListener(event, this.handleInteraction); - }); - } - - hostDisconnected() { - this.detachForm(); - - // Clean up interactions - interactions.delete(this.host); - this.options.assumeInteractionOn.forEach(event => { - this.host.removeEventListener(event, this.handleInteraction); - }); - } - - hostUpdated() { - const form = this.options.form(this.host); - - // Detach if the form no longer exists - if (!form) { - this.detachForm(); - } - - // If the form has changed, reattach it - if (form && this.form !== form) { - this.detachForm(); - this.attachForm(form); - } - - if (this.host.hasUpdated) { - this.setValidity(this.host.validity.valid); - } - } - - private attachForm(form?: HTMLFormElement) { - if (form) { - this.form = form; - - // Add this element to the form's collection - if (formCollections.has(this.form)) { - formCollections.get(this.form)!.add(this.host); - } else { - formCollections.set(this.form, new Set([this.host])); - } - - this.form.addEventListener('formdata', this.handleFormData); - this.form.addEventListener('submit', this.handleFormSubmit); - this.form.addEventListener('reset', this.handleFormReset); - - // Overload the form's reportValidity() method so it looks at Web Awesome form controls - if (!reportValidityOverloads.has(this.form)) { - reportValidityOverloads.set(this.form, this.form.reportValidity); - this.form.reportValidity = () => this.reportFormValidity(); - } - - // Overload the form's checkValidity() method so it looks at Web Awesome form controls - if (!checkValidityOverloads.has(this.form)) { - checkValidityOverloads.set(this.form, this.form.checkValidity); - this.form.checkValidity = () => this.checkFormValidity(); - } - } else { - this.form = undefined; - } - } - - private detachForm() { - if (!this.form) return; - - const formCollection = formCollections.get(this.form); - - if (!formCollection) { - return; - } - - // Remove this host from the form's collection - formCollection.delete(this.host); - - // Check to make sure there's no other form controls in the collection. If we do this - // without checking if any other controls are still in the collection, then we will wipe out the - // validity checks for all other elements. - // see: https://github.com/shoelace-style/shoelace/issues/1703 - if (formCollection.size <= 0) { - this.form.removeEventListener('formdata', this.handleFormData); - this.form.removeEventListener('submit', this.handleFormSubmit); - this.form.removeEventListener('reset', this.handleFormReset); - - // Remove the overload and restore the original method - if (reportValidityOverloads.has(this.form)) { - this.form.reportValidity = reportValidityOverloads.get(this.form)!; - reportValidityOverloads.delete(this.form); - } - - if (checkValidityOverloads.has(this.form)) { - this.form.checkValidity = checkValidityOverloads.get(this.form)!; - checkValidityOverloads.delete(this.form); - } - - // So it looks weird here to not always set the form to undefined. But I _think_ if we unattach this.form here, - // we end up in this fun spot where future validity checks don't have a reference to the form validity handler. - // First form element in sets the validity handler. So we can't clean up `this.form` until there are no other form elements in the form. - this.form = undefined; - } - } - - private handleFormData = (event: FormDataEvent) => { - const disabled = this.options.disabled(this.host); - const name = this.options.name(this.host); - const value = this.options.value(this.host); - - // For buttons, we only submit the value if they were the submitter. This is currently done in doAction() by - // injecting the name/value on a temporary button, so we can just skip them here. - const isButton = this.host.tagName.toLowerCase() === 'wa-button'; - - if ( - this.host.isConnected && - !disabled && - !isButton && - typeof name === 'string' && - name.length > 0 && - typeof value !== 'undefined' - ) { - if (Array.isArray(value)) { - (value as unknown[]).forEach(val => { - event.formData.append(name, (val as string | number | boolean).toString()); - }); - } else { - event.formData.append(name, (value as string | number | boolean).toString()); - } - } - }; - - private handleFormSubmit = (event: Event) => { - const disabled = this.options.disabled(this.host); - const reportValidity = this.options.reportValidity; - - // Update the interacted state for all controls when the form is submitted - if (this.form && !this.form.noValidate) { - formCollections.get(this.form)?.forEach(control => { - this.setUserInteracted(control, true); - }); - } - - if (this.form && !this.form.noValidate && !disabled && !reportValidity(this.host)) { - event.preventDefault(); - event.stopImmediatePropagation(); - } - }; - - private handleFormReset = () => { - this.options.setValue(this.host, this.options.defaultValue(this.host)); - this.setUserInteracted(this.host, false); - interactions.set(this.host, []); - }; - - private handleInteraction = (event: Event) => { - const emittedEvents = interactions.get(this.host)!; - - if (!emittedEvents.includes(event.type)) { - emittedEvents.push(event.type); - } - - // Mark it as user-interacted as soon as all associated events have been emitted - if (emittedEvents.length === this.options.assumeInteractionOn.length) { - this.setUserInteracted(this.host, true); - } - }; - - private checkFormValidity = () => { - // - // This is very similar to the `reportFormValidity` function, but it does not trigger native constraint validation - // Allow the user to simply check if the form is valid and handling validity in their own way. - // - // We preserve the original method in a WeakMap, but we don't call it from the overload because that would trigger - // validations in an unexpected order. When the element disconnects, we revert to the original behavior. This won't - // be necessary once we can use ElementInternals. - // - // Note that we're also honoring the form's novalidate attribute. - // - if (this.form && !this.form.noValidate) { - // This seems sloppy, but checking all elements will cover native inputs, Web Awesome inputs, and other custom - // elements that support the constraint validation API. - const elements = this.form.querySelectorAll('*'); - - for (const element of elements) { - if (typeof element.checkValidity === 'function') { - if (!element.checkValidity()) { - return false; - } - } - } - } - - return true; - }; - - private reportFormValidity = () => { - // - // Web Awesome form controls work hard to act like regular form controls. They support the Constraint Validation API - // and its associated methods such as setCustomValidity() and reportValidity(). However, the HTMLFormElement also - // has a reportValidity() method that will trigger validation on all child controls. Since we're not yet using - // ElementInternals, we need to overload this method so it looks for any element with the reportValidity() method. - // - // We preserve the original method in a WeakMap, but we don't call it from the overload because that would trigger - // validations in an unexpected order. When the element disconnects, we revert to the original behavior. This won't - // be necessary once we can use ElementInternals. - // - // Note that we're also honoring the form's novalidate attribute. - // - if (this.form && !this.form.noValidate) { - // This seems sloppy, but checking all elements will cover native inputs, Web Awesome inputs, and other custom - // elements that support the constraint validation API. - const elements = this.form.querySelectorAll('*'); - - for (const element of elements) { - if (typeof element.reportValidity === 'function') { - if (!element.reportValidity()) { - return false; - } - } - } - } - - return true; - }; - - private setUserInteracted(el: WebAwesomeFormControl, hasInteracted: boolean) { - if (hasInteracted) { - userInteractedControls.add(el); - } else { - userInteractedControls.delete(el); - } - - el.requestUpdate(); - } - - private doAction(type: 'submit' | 'reset', submitter?: HTMLInputElement | WaButton) { - if (this.form) { - const button = document.createElement('button'); - button.type = type; - button.style.position = 'absolute'; - button.style.width = '0'; - button.style.height = '0'; - button.style.clipPath = 'inset(50%)'; - button.style.overflow = 'hidden'; - button.style.whiteSpace = 'nowrap'; - - // Pass name, value, and form attributes through to the temporary button - if (submitter) { - button.name = submitter.name; - button.value = submitter.value; - - ['formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget'].forEach(attr => { - if (submitter.hasAttribute(attr)) { - button.setAttribute(attr, submitter.getAttribute(attr)!); - } - }); - } - - this.form.append(button); - button.click(); - button.remove(); - } - } - - /** Returns the associated `` element, if one exists. */ - getForm() { - return this.form ?? null; - } - - /** Resets the form, restoring all the control to their default value */ - reset(submitter?: HTMLInputElement | WaButton) { - this.doAction('reset', submitter); - } - - /** Submits the form, triggering validation and form data injection. */ - submit(submitter?: HTMLInputElement | WaButton) { - // Calling form.submit() bypasses the submit event and constraint validation. To prevent this, we can inject a - // native submit button into the form, "click" it, then remove it to simulate a standard form submission. - this.doAction('submit', submitter); - } - - /** - * Synchronously sets the form control's validity. Call this when you know the future validity but need to update - * the host element immediately, i.e. before Lit updates the component in the next update. - */ - setValidity(isValid: boolean) { - const host = this.host; - const hasInteracted = Boolean(userInteractedControls.has(host)); - const required = Boolean(host.required); - - // - // We're mapping the following "states" to data attributes. In the future, we can use ElementInternals.states to - // create a similar mapping, but instead of [data-invalid] it will look like :--invalid. - // - // See this RFC for more details: https://github.com/shoelace-style/shoelace/issues/1011 - // - host.toggleAttribute('data-required', required); - host.toggleAttribute('data-optional', !required); - host.toggleAttribute('data-invalid', !isValid); - host.toggleAttribute('data-valid', isValid); - host.toggleAttribute('data-user-invalid', !isValid && hasInteracted); - host.toggleAttribute('data-user-valid', isValid && hasInteracted); - } - - /** - * Updates the form control's validity based on the current value of `host.validity.valid`. Call this when anything - * that affects constraint validation changes so the component receives the correct validity states. - */ - updateValidity() { - const host = this.host; - this.setValidity(host.validity.valid); - } - - /** - * Dispatches a non-bubbling, cancelable custom event of type `wa-invalid`. - * If the `wa-invalid` event will be cancelled then the original `invalid` - * event (which may have been passed as argument) will also be cancelled. - * If no original `invalid` event has been passed then the `wa-invalid` - * event will be cancelled before being dispatched. - */ - emitInvalidEvent(originalInvalidEvent?: Event) { - const slInvalidEvent = new CustomEvent>('wa-invalid', { - bubbles: false, - composed: false, - cancelable: true, - detail: {} - }); - - if (!originalInvalidEvent) { - slInvalidEvent.preventDefault(); - } - - if (!this.host.dispatchEvent(slInvalidEvent)) { - originalInvalidEvent?.preventDefault(); - } - } -} - -/* - * Predefined common validity states. - * All of them are read-only. - */ - -// A validity state object that represents `valid` -export const validValidityState: ValidityState = Object.freeze({ - badInput: false, - customError: false, - patternMismatch: false, - rangeOverflow: false, - rangeUnderflow: false, - stepMismatch: false, - tooLong: false, - tooShort: false, - typeMismatch: false, - valid: true, - valueMissing: false -}); - -// A validity state object that represents `value missing` -export const valueMissingValidityState: ValidityState = Object.freeze({ - ...validValidityState, - valid: false, - valueMissing: true -}); - -// A validity state object that represents a custom error -export const customErrorValidityState: ValidityState = Object.freeze({ - ...validValidityState, - valid: false, - customError: true -}); diff --git a/src/internal/webawesome-element.ts b/src/internal/webawesome-element.ts index 5ad3c51ce..effab4723 100644 --- a/src/internal/webawesome-element.ts +++ b/src/internal/webawesome-element.ts @@ -115,7 +115,7 @@ export interface WebAwesomeFormControl extends WebAwesomeElement { checked?: boolean; defaultSelected?: boolean; selected?: boolean; - form?: string; + form?: string | null; // Constraint validation attributes pattern?: string; diff --git a/src/utilities/form.ts b/src/utilities/form.ts index 03842e2c0..b27cbd3a8 100644 --- a/src/utilities/form.ts +++ b/src/utilities/form.ts @@ -1,5 +1,3 @@ -import { formCollections } from '../internal/form.js'; - /** * Serializes a form and returns a plain object. If a form control with the same name appears more than once, the * property will be converted to an array. @@ -23,23 +21,3 @@ export function serialize(form: HTMLFormElement) { return object; } - -/** - * Returns all form controls that are associated with the specified form. Includes both native and Web Awesome form - * controls. Use this function in lieu of the `HTMLFormElement.elements` property, which doesn't recognize Web Awesome - * form controls. - */ -export function getFormControls(form: HTMLFormElement) { - const rootNode = form.getRootNode() as Document | ShadowRoot; - const allNodes = [...rootNode.querySelectorAll('*')]; - const formControls = [...form.elements]; - const collection = formCollections.get(form); - const waFormControls = collection ? Array.from(collection) : []; - - // To return form controls in the right order, we sort by DOM index - return [...formControls, ...waFormControls].sort((a: Element, b: Element) => { - if (allNodes.indexOf(a) < allNodes.indexOf(b)) return -1; - if (allNodes.indexOf(a) > allNodes.indexOf(b)) return 1; - return 0; - }); -}