feat: support cross-root registration with form-element-register event

This commit is contained in:
Max Larionov 2024-04-02 15:07:30 +02:00 committed by Thijs Louisse
parent 79a64674f8
commit 37deecd2a3
4 changed files with 85 additions and 3 deletions

View file

@ -0,0 +1,5 @@
---
'@lion/ui': patch
---
Added support for cross-root registration by adding a flag to composed property of form-element-register event.

View file

@ -28,6 +28,19 @@ const FormRegisteringMixinImplementation = superclass =>
* @type {FormRegistrarHost | undefined}
*/
this._parentFormGroup = undefined;
/**
* To encourage accessibility best practices, `form-element-register` events
* do not pierce through shadow roots. This forces the developer to create form groups and fieldsets that automatically allow the creation of accessible relationships in the same dom tree.
Use this option if you know what you're doing. It will then be possible to nest FormControls
inside shadow dom. See https://lion-web.netlify.app/fundamentals/rationales/accessibility/#shadow-roots-and-accessibility
*/
this.allowCrossRootRegistration = false;
}
static get properties() {
return {
allowCrossRootRegistration: { type: Boolean, attribute: 'allow-cross-root-registration' },
};
}
connectedCallback() {
@ -36,6 +49,7 @@ const FormRegisteringMixinImplementation = superclass =>
new CustomEvent('form-element-register', {
detail: { element: this },
bubbles: true,
composed: Boolean(this.allowCrossRootRegistration),
}),
);
}

View file

@ -1,11 +1,12 @@
import { LitElement } from 'lit';
import { uuid } from '@lion/ui/core.js';
import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
import {
FormRegisteringMixin,
FormRegistrarMixin,
FormRegistrarPortalMixin,
} from '@lion/ui/form-core.js';
import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
import { LitElement } from 'lit';
import sinon from 'sinon';
/**
* @typedef {Object} customConfig
@ -256,6 +257,60 @@ export const runRegistrationSuite = customConfig => {
]);
});
describe('FormRegisteringMixin', () => {
it('propagates the form-element-register event through the shadowDom', async () => {
const eventSpy = sinon.spy();
const withShadowFormControlStr = defineCE(
class extends FormRegistrarMixin(LitElement) {
render() {
return html`
<${childTag}
id="child"
@form-element-register=${eventSpy}
allow-cross-root-registration
>
</${childTag}>`;
}
},
);
const withShadowFormControlTag = unsafeStatic(withShadowFormControlStr);
const el = /** @type {RegistrarClass} */ (
await fixture(html`
<${withShadowFormControlTag}>
</${withShadowFormControlTag}>
`)
);
expect(eventSpy).to.have.been.calledOnce;
expect(eventSpy.getCall(0).args[0].composed).to.equal(true);
expect(el.formElements).to.deep.equal([el.shadowRoot?.querySelector('#child')]);
});
it('dispatches the form-element-register event with compose true if allowCrossRootRegistration is set', async () => {
const eventSpy = sinon.spy();
/** @type {RegisteringClass} */ (
await fixture(html`
<${childTag}
@form-element-register=${eventSpy}
allow-cross-root-registration
>
</${childTag}>
`)
);
expect(eventSpy).to.have.been.calledOnce;
expect(eventSpy.getCall(0).args[0].composed).to.equal(true);
});
it('dispatches the form-element-register event with compose false if allowCrossRootRegistration is not set', async () => {
const eventSpy = sinon.spy();
/** @type {RegisteringClass} */ (
await fixture(html`
<${childTag} @form-element-register=${eventSpy}></${childTag}>
`)
);
expect(eventSpy).to.have.been.calledOnce;
expect(eventSpy.getCall(0).args[0].composed).to.equal(false);
});
});
describe('FormRegistrarPortalMixin', () => {
it('forwards registrations to the .registrationTarget', async () => {
const el = /** @type {RegistrarClass} */ (

View file

@ -1,5 +1,5 @@
import { LitElement } from 'lit';
import { Constructor } from '@open-wc/dedupe-mixin';
import { LitElement } from 'lit';
import { FormRegistrarHost } from './FormRegistrarMixinTypes.js';
@ -8,6 +8,14 @@ export declare class FormRegisteringHost {
* The name the host is registered with to a parent
*/
name: string;
/**
* To encourage accessibility best practices, `form-element-register` events
* do not pierce through shadow roots. This forces the developer to create form groups and fieldsets that
* automatically allow the creation of accessible relationships in the same dom tree.
* Use this option if you know what you're doing. It will then be possible to nest FormControls
* inside shadow dom. See https://lion-web.netlify.app/fundamentals/rationales/accessibility#shadow-roots-and-accessibility
*/
allowCrossRootRegistration: boolean;
/**
* The registrar this FormControl registers to, Usually a descendant of FormGroup or