add getFormControls() method

pull/1128/head
Cory LaViska 2023-01-13 14:06:58 -05:00
rodzic 7e37c51856
commit aa65077b12
5 zmienionych plików z 96 dodań i 29 usunięć

Wyświetl plik

@ -355,3 +355,18 @@ This example demonstrates custom validation styles using `data-user-invalid` and
}
</style>
```
## Getting Associated Form Controls
At this time, using [`HTMLFormElement.elements`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements) will not return Shoelace form controls because the browser is unaware of their status as custom element form controls. Fortunately, Shoelace provides an `elements()` function that does something very similar. However, instead of returning an [`HTMLFormControlsCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormControlsCollection), it returns an array of HTML and Shoelace form controls in the order they appear in the DOM.
```js
import { getFormControls } from '@shoelace-style/shoelace/dist/utilities/form.js';
const form = document.querySelector('#my-form');
const formControls = getFormControls(form);
console.log(formControls); // e.g. [input, sl-input, ...]
```
?> You probably don't need this function! If you're gathering form data for submission, you probably want to look at the [Data Serialization](#data-serializing) instead.

Wyświetl plik

@ -15,6 +15,7 @@ New versions of Shoelace are released as-needed and generally occur when a criti
- Added `sl-hover` event to `<sl-rating>` [#1125](https://github.com/shoelace-style/shoelace/issues/1125)
- Added the `@documentation` tag with a link to the docs for each component
- Added the `form` attribute to all form controls to allow placing them outside of a `<form>` element [#1130](https://github.com/shoelace-style/shoelace/issues/1130)
- Added the `getFormControls()` function as an alternative to `HTMLFormElement.elements`
- Fixed a bug in `<sl-select>` that prevented placeholders from showing when `multiple` was used [#1109](https://github.com/shoelace-style/shoelace/issues/1109)
- Fixed a bug in `<sl-select>` that caused tags to not be rounded when using the `pill` attribute [#1117](https://github.com/shoelace-style/shoelace/issues/1117)
- Fixed a bug in `<sl-select>` where the `sl-change` and `sl-input` events didn't weren't emitted when removing tags [#1119](https://github.com/shoelace-style/shoelace/issues/1119)

Wyświetl plik

@ -1,7 +1,8 @@
import { expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';
import { sendKeys } from '@web/test-runner-commands';
import sinon from 'sinon';
import { serialize } from '../../utilities/form';
// @ts-expect-error - The getFormControls() function must come from the same dist
import { getFormControls, serialize } from '../../../dist/utilities/form.js';
import type SlInput from './input';
describe('<sl-input>', () => {
@ -235,33 +236,6 @@ describe('<sl-input>', () => {
expect(formData.get('a')).to.equal('1');
});
it('should submit with the correct form when the form attribute changes (FormControlController)', async () => {
const el = await fixture<HTMLFormElement>(html`
<div>
<form id="f1">
<input type="hidden" name="b" value="2" />
<sl-button type="submit">Submit</sl-button>
</form>
<form id="f2">
<input type="hidden" name="c" value="3" />
<sl-button type="submit">Submit</sl-button>
</form>
<sl-input form="f1" name="a" value="1"></sl-input>
</div>
`);
const form = el.querySelector<HTMLFormElement>('#f2')!;
const input = document.querySelector('sl-input')!;
input.form = 'f2';
await input.updateComplete;
const formData = new FormData(form);
expect(formData.get('a')).to.equal('1');
expect(formData.get('b')).to.be.null;
expect(formData.get('c')).to.equal('3');
});
});
describe('when resetting a form', () => {
@ -448,4 +422,59 @@ describe('<sl-input>', () => {
expect(input.spellcheck).to.be.false;
});
});
describe('when using FormControlController', () => {
it('should submit with the correct form when the form attribute changes', async () => {
const el = await fixture<HTMLFormElement>(html`
<div>
<form id="f1">
<input type="hidden" name="b" value="2" />
<sl-button type="submit">Submit</sl-button>
</form>
<form id="f2">
<input type="hidden" name="c" value="3" />
<sl-button type="submit">Submit</sl-button>
</form>
<sl-input form="f1" name="a" value="1"></sl-input>
</div>
`);
const form = el.querySelector<HTMLFormElement>('#f2')!;
const input = document.querySelector('sl-input')!;
input.form = 'f2';
await input.updateComplete;
const formData = new FormData(form);
expect(formData.get('a')).to.equal('1');
expect(formData.get('b')).to.be.null;
expect(formData.get('c')).to.equal('3');
});
});
describe('when using the getFormControls() function', () => {
it('should return both native and Shoelace form controls in the correct DOM order', async () => {
const el = await fixture<HTMLFormElement>(html`
<div>
<input type="text" name="a" value="1" form="f1" />
<sl-input type="text" name="b" value="2" form="f1"></sl-input>
<form id="f1">
<input type="hidden" name="c" value="3" />
<input type="text" name="d" value="4" />
<sl-input name="e" value="5"></sl-input>
<textarea name="f">6</textarea>
<sl-textarea name="g" value="7"></sl-textarea>
<sl-checkbox name="h" value="8"></sl-checkbox>
</form>
<input type="text" name="i" value="9" form="f1" />
<sl-input type="text" name="j" value="10" form="f1"></sl-input>
</div>
`);
const form = el.querySelector<HTMLFormElement>('form')!;
const formControls = getFormControls(form);
expect(formControls.length).to.equal(10);
expect(formControls.map((fc: HTMLInputElement) => fc.value).join('')).to.equal('12345678910');
});
});
});

Wyświetl plik

@ -7,7 +7,7 @@ import type { ReactiveController, ReactiveControllerHost } from 'lit';
// 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.
//
const formCollections: WeakMap<HTMLFormElement, Set<ShoelaceFormControl>> = new WeakMap();
export const formCollections: WeakMap<HTMLFormElement, Set<ShoelaceFormControl>> = new WeakMap();
//
// We store a WeakMap of controls that users have interacted with. This allows us to determine the interaction state

Wyświetl plik

@ -1,3 +1,5 @@
import { formCollections } from '../internal/form';
/**
* 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.
@ -21,3 +23,23 @@ export function serialize(form: HTMLFormElement) {
return object;
}
/**
* Returns all form controls that are associated with the specified form. Includes both native and Shoelace form
* controls. Use this function in lieu of the `HTMLFormElement.elements` property, which doesn't recognize Shoelace
* 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 shoelaceFormControls = collection ? Array.from(collection) : [];
// To return form controls in the right order, we sort by DOM index
return [...formControls, ...shoelaceFormControls].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;
});
}