feat: add types for fieldset and form
This commit is contained in:
parent
9a74bfdc6e
commit
6f08e9296b
5 changed files with 86 additions and 58 deletions
6
.changeset/silly-chairs-grow.md
Normal file
6
.changeset/silly-chairs-grow.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@lion/fieldset': minor
|
||||
'@lion/form': minor
|
||||
---
|
||||
|
||||
Added types for form and fieldset packages.
|
||||
|
|
@ -18,13 +18,14 @@ import { FormGroupMixin } from '@lion/form-core';
|
|||
* cannot be accessed individually via object keys.
|
||||
*
|
||||
* @customElement lion-fieldset
|
||||
* @extends {LitElement}
|
||||
*/
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110
|
||||
export class LionFieldset extends FormGroupMixin(LitElement) {
|
||||
constructor() {
|
||||
super();
|
||||
/** @override from FormRegistrarMixin */
|
||||
/** @override FormRegistrarMixin */
|
||||
this._isFormOrFieldset = true;
|
||||
this._repropagationRole = 'fieldset'; // configures FormControlMixin
|
||||
/** @override FormControlMixin */
|
||||
this._repropagationRole = 'fieldset';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,38 @@
|
|||
import { LionFieldset } from '@lion/fieldset';
|
||||
|
||||
const throwFormNodeError = () => {
|
||||
throw new Error(
|
||||
'No form node found. Did you put a <form> element inside your custom-form element?',
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* LionForm: form wrapper providing extra features and integration with lion-field elements.
|
||||
*
|
||||
* @customElement lion-form
|
||||
* @extends {LionFieldset}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export class LionForm extends LionFieldset {
|
||||
constructor() {
|
||||
super();
|
||||
/** @param {Event} ev */
|
||||
this._submit = ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.submitGroup();
|
||||
this.dispatchEvent(new Event('submit', { bubbles: true }));
|
||||
};
|
||||
/** @param {Event} ev */
|
||||
this._reset = ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.resetGroup();
|
||||
this.dispatchEvent(new Event('reset', { bubbles: true }));
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (super.connectedCallback) {
|
||||
super.connectedCallback();
|
||||
}
|
||||
super.connectedCallback();
|
||||
this.__registerEventsForLionForm();
|
||||
|
||||
// @override LionFieldset: makes sure a11y is handled by ._formNode
|
||||
|
|
@ -19,39 +40,32 @@ export class LionForm extends LionFieldset {
|
|||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (super.disconnectedCallback) {
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
super.disconnectedCallback();
|
||||
this.__teardownEventsForLionForm();
|
||||
}
|
||||
|
||||
get _formNode() {
|
||||
return this.querySelector('form');
|
||||
return /** @type {HTMLFormElement} */ (this.querySelector('form'));
|
||||
}
|
||||
|
||||
submit() {
|
||||
this._formNode.submit();
|
||||
if (this._formNode) {
|
||||
this._formNode.submit();
|
||||
} else {
|
||||
throwFormNodeError();
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._formNode.reset();
|
||||
if (this._formNode) {
|
||||
this._formNode.reset();
|
||||
} else {
|
||||
throwFormNodeError();
|
||||
}
|
||||
}
|
||||
|
||||
__registerEventsForLionForm() {
|
||||
this._submit = ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.submitGroup();
|
||||
this.dispatchEvent(new Event('submit', { bubbles: true }));
|
||||
};
|
||||
this._formNode.addEventListener('submit', this._submit);
|
||||
|
||||
this._reset = ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.resetGroup();
|
||||
this.dispatchEvent(new Event('reset', { bubbles: true }));
|
||||
};
|
||||
this._formNode.addEventListener('reset', this._reset);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
expect,
|
||||
fixture,
|
||||
fixture as _fixture,
|
||||
html,
|
||||
oneEvent,
|
||||
aTimeout,
|
||||
|
|
@ -15,6 +15,13 @@ import '@lion/fieldset/lion-fieldset.js';
|
|||
|
||||
import '../lion-form.js';
|
||||
|
||||
/**
|
||||
|
||||
* @typedef {import('../src/LionForm').LionForm} LionForm
|
||||
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
||||
*/
|
||||
const fixture = /** @type {(arg: TemplateResult) => Promise<LionForm>} */ (_fixture);
|
||||
|
||||
const childTagString = defineCE(
|
||||
class extends LionField {
|
||||
get slots() {
|
||||
|
|
@ -25,40 +32,38 @@ const childTagString = defineCE(
|
|||
},
|
||||
);
|
||||
const childTag = unsafeStatic(childTagString);
|
||||
const formTagString = 'lion-form';
|
||||
const formTag = unsafeStatic(formTagString);
|
||||
|
||||
describe('<lion-form>', () => {
|
||||
it('is an instance of LionFieldSet', async () => {
|
||||
const el = await fixture(html`
|
||||
<${formTag}>
|
||||
<form>
|
||||
</form>
|
||||
</${formTag}>
|
||||
<lion-form>
|
||||
<form></form>
|
||||
</lion-form>
|
||||
`);
|
||||
expect(el).to.be.instanceOf(LionFieldset);
|
||||
});
|
||||
|
||||
it('relies on the native form for its accessible role', async () => {
|
||||
const el = await fixture(html`
|
||||
<${formTag}>
|
||||
<form>
|
||||
</form>
|
||||
</${formTag}>
|
||||
<lion-form>
|
||||
<form></form>
|
||||
</lion-form>
|
||||
`);
|
||||
expect(el.getAttribute('role')).to.be.null;
|
||||
});
|
||||
|
||||
it('has a custom reset that gets triggered by native reset', async () => {
|
||||
const withDefaults = await fixture(html`
|
||||
<${formTag}>
|
||||
<lion-form>
|
||||
<form>
|
||||
<${childTag} name="firstName" .modelValue="${'Foo'}"></${childTag}>
|
||||
<input type="reset" value="reset-button" />
|
||||
</form>
|
||||
</${formTag}>
|
||||
</lion-form>
|
||||
`);
|
||||
const resetButton = withDefaults.querySelector('input[type=reset]');
|
||||
const resetButton = /** @type {HTMLInputElement} */ (withDefaults.querySelector(
|
||||
'input[type=reset]',
|
||||
));
|
||||
|
||||
withDefaults.formElements.firstName.modelValue = 'updatedFoo';
|
||||
expect(withDefaults.modelValue).to.deep.equal({
|
||||
|
|
@ -84,11 +89,11 @@ describe('<lion-form>', () => {
|
|||
|
||||
it('dispatches reset events', async () => {
|
||||
const el = await fixture(html`
|
||||
<${formTag}>
|
||||
<lion-form>
|
||||
<form>
|
||||
<${childTag} name="firstName" .modelValue="${'Foo'}"></${childTag}>
|
||||
</form>
|
||||
</${formTag}>
|
||||
</lion-form>
|
||||
`);
|
||||
|
||||
setTimeout(() => el.reset());
|
||||
|
|
@ -103,27 +108,27 @@ describe('<lion-form>', () => {
|
|||
it('works with the native submit event (triggered via a button)', async () => {
|
||||
const submitSpy = spy();
|
||||
const el = await fixture(html`
|
||||
<${formTag} @submit=${submitSpy}>
|
||||
<lion-form @submit=${submitSpy}>
|
||||
<form>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
</${formTag}>
|
||||
</lion-form>
|
||||
`);
|
||||
|
||||
const button = el.querySelector('button');
|
||||
const button = /** @type {HTMLButtonElement} */ (el.querySelector('button'));
|
||||
button.click();
|
||||
expect(submitSpy.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('dispatches submit events', async () => {
|
||||
const el = await fixture(html`
|
||||
<${formTag}>
|
||||
<lion-form>
|
||||
<form>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
</${formTag}>
|
||||
</lion-form>
|
||||
`);
|
||||
const button = el.querySelector('button');
|
||||
const button = /** @type {HTMLButtonElement} */ (el.querySelector('button'));
|
||||
setTimeout(() => button.click());
|
||||
const submitEv = await oneEvent(el, 'submit');
|
||||
expect(submitEv).to.be.instanceOf(Event);
|
||||
|
|
@ -135,29 +140,29 @@ describe('<lion-form>', () => {
|
|||
|
||||
it('handles internal submit handler before dispatch', async () => {
|
||||
const el = await fixture(html`
|
||||
<${formTag}>
|
||||
<lion-form>
|
||||
<form>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
</${formTag}>
|
||||
</lion-form>
|
||||
`);
|
||||
const button = el.querySelector('button');
|
||||
const button = /** @type {HTMLButtonElement} */ (el.querySelector('button'));
|
||||
const internalHandlerSpy = spy(el, 'submitGroup');
|
||||
const dispatchSpy = spy(el, 'dispatchEvent');
|
||||
await aTimeout();
|
||||
await aTimeout(0);
|
||||
button.click();
|
||||
expect(internalHandlerSpy).to.be.calledBefore(dispatchSpy);
|
||||
});
|
||||
|
||||
it('handles internal submit handler before dispatch', async () => {
|
||||
const el = await fixture(html`
|
||||
<${formTag}>
|
||||
<lion-form>
|
||||
<form>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
</${formTag}>
|
||||
</lion-form>
|
||||
`);
|
||||
const button = el.querySelector('button');
|
||||
const button = /** @type {HTMLButtonElement} */ (el.querySelector('button'));
|
||||
const internalHandlerSpy = spy(el, 'submitGroup');
|
||||
const dispatchSpy = spy(el, 'dispatchEvent');
|
||||
button.click();
|
||||
|
|
@ -167,13 +172,13 @@ describe('<lion-form>', () => {
|
|||
|
||||
it('handles internal reset handler before dispatch', async () => {
|
||||
const el = await fixture(html`
|
||||
<${formTag}>
|
||||
<lion-form>
|
||||
<form>
|
||||
<button type="reset">submit</button>
|
||||
</form>
|
||||
</${formTag}>
|
||||
</lion-form>
|
||||
`);
|
||||
const button = el.querySelector('button');
|
||||
const button = /** @type {HTMLButtonElement} */ (el.querySelector('button'));
|
||||
const internalHandlerSpy = spy(el, 'resetGroup');
|
||||
const dispatchSpy = spy(el, 'dispatchEvent');
|
||||
button.click();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
"packages/button/src/**/*.js",
|
||||
"packages/checkbox-group/**/*.js",
|
||||
"packages/core/**/*.js",
|
||||
"packages/fieldset/**/*.js",
|
||||
"packages/form/**/*.js",
|
||||
"packages/form-core/**/*.js",
|
||||
"packages/input/**/*.js",
|
||||
"packages/input-amount/**/*.js",
|
||||
|
|
|
|||
Loading…
Reference in a new issue