125 lines
3.2 KiB
JavaScript
125 lines
3.2 KiB
JavaScript
import { LionFieldset } from '@lion/ui/fieldset.js';
|
|
|
|
/**
|
|
* @typedef {import('../../form-core/types/registration/FormRegistrarMixinTypes.js').FormRegistrarHost} FormRegistrarHost
|
|
*/
|
|
|
|
const throwFormNodeError = () => {
|
|
throw new Error(
|
|
'No form node found. Did you put a <form> element inside your custom-form element?',
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @param {FormRegistrarHost} formEl
|
|
* @returns {boolean}
|
|
*/
|
|
function hasFocusableChildren(formEl) {
|
|
// this implies all children have the same type (either all of them are focusable or none of them are)
|
|
return formEl.formElements?.some(child => child._focusableNode);
|
|
}
|
|
|
|
/**
|
|
* LionForm: form wrapper providing extra features and integration with lion-field elements.
|
|
*
|
|
* @customElement lion-form
|
|
*/
|
|
// eslint-disable-next-line no-unused-vars
|
|
export class LionForm extends LionFieldset {
|
|
constructor() {
|
|
super();
|
|
/** @protected */
|
|
this._submit = this._submit.bind(this);
|
|
/** @protected */
|
|
this._reset = this._reset.bind(this);
|
|
}
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
this.__registerEventsForLionForm();
|
|
|
|
// @override LionFieldset: makes sure a11y is handled by ._formNode
|
|
this.removeAttribute('role');
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
super.disconnectedCallback();
|
|
this.__teardownEventsForLionForm();
|
|
}
|
|
|
|
get _formNode() {
|
|
return /** @type {HTMLFormElement} */ (this.querySelector('form'));
|
|
}
|
|
|
|
submit() {
|
|
if (this._formNode) {
|
|
// Firefox requires cancelable flag, otherwise we cannot preventDefault
|
|
// Firefox still runs default handlers for untrusted events :\
|
|
this._formNode.dispatchEvent(new Event('submit', { cancelable: true }));
|
|
} else {
|
|
throwFormNodeError();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Event} ev
|
|
* @protected
|
|
*/
|
|
_submit(ev) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
this.submitGroup();
|
|
this.dispatchEvent(new Event('submit', { bubbles: true }));
|
|
|
|
if (this.hasFeedbackFor?.includes('error')) {
|
|
this._setFocusOnFirstErroneousFormElement(/** @type { * & FormRegistrarHost } */ (this));
|
|
}
|
|
}
|
|
|
|
reset() {
|
|
if (this._formNode) {
|
|
this._formNode.reset();
|
|
} else {
|
|
throwFormNodeError();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Event} ev
|
|
* @protected
|
|
*/
|
|
_reset(ev) {
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
this.resetGroup();
|
|
this.dispatchEvent(new Event('reset', { bubbles: true }));
|
|
}
|
|
|
|
/**
|
|
* @param {FormRegistrarHost} element
|
|
* @protected
|
|
*/
|
|
_setFocusOnFirstErroneousFormElement(element) {
|
|
const firstFormElWithError =
|
|
element.formElements.find(child => child.hasFeedbackFor.includes('error')) ||
|
|
element.formElements[0];
|
|
|
|
if (hasFocusableChildren(firstFormElWithError)) {
|
|
this._setFocusOnFirstErroneousFormElement(firstFormElWithError);
|
|
} else {
|
|
firstFormElWithError._focusableNode.focus();
|
|
}
|
|
}
|
|
|
|
/** @private */
|
|
__registerEventsForLionForm() {
|
|
this._formNode.addEventListener('submit', this._submit);
|
|
this._formNode.addEventListener('reset', this._reset);
|
|
}
|
|
|
|
/** @private */
|
|
__teardownEventsForLionForm() {
|
|
this._formNode.removeEventListener('submit', this._submit);
|
|
this._formNode.removeEventListener('reset', this._reset);
|
|
}
|
|
}
|