@lion/ui 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/components/calendar/src/LionCalendar.js +6 -0
- package/components/core/src/DisabledWithTabIndexMixin.js +7 -0
- package/components/core/src/ScopedElementsMixin.js +58 -37
- package/components/core/test/ScopedElementsMixin.test.js +131 -0
- package/components/core/test/ssr-definitions/ScopedElementsHost.define.js +1 -0
- package/components/form/src/LionForm.js +3 -12
- package/components/form/test/lion-form.test.js +88 -1
- package/components/form-core/src/FormControlMixin.js +6 -0
- package/components/form-core/src/registration/FormRegisteringMixin.js +1 -1
- package/components/form-core/src/utils/SyncUpdatableMixin.js +7 -0
- package/components/form-core/src/validate/LionValidationFeedback.js +4 -2
- package/components/form-core/test/validate/lion-validation-feedback.test.js +12 -0
- package/components/form-core/types/registration/FormRegisteringMixinTypes.ts +1 -1
- package/components/icon/src/LionIcon.js +6 -0
- package/components/listbox/test-suites/ListboxMixin.suite.js +2 -2
- package/components/localize/src/LocalizeManager.js +259 -204
- package/components/overlays/src/OverlayController.js +1 -1
- package/components/overlays/src/OverlayMixin.js +7 -0
- package/components/progress-indicator/src/LionProgressIndicator.js +6 -0
- package/components/steps/src/LionStep.js +6 -0
- package/components/steps/src/LionSteps.js +6 -0
- package/components/tabs/src/LionTabs.js +6 -0
- package/components/tooltip/src/LionTooltip.js +6 -0
- package/custom-elements.json +792 -330
- package/dist-types/components/calendar/src/LionCalendar.d.ts +5 -0
- package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
- package/dist-types/components/core/src/DisabledWithTabIndexMixin.d.ts.map +1 -1
- package/dist-types/components/core/src/ScopedElementsMixin.d.ts +43 -6
- package/dist-types/components/core/src/ScopedElementsMixin.d.ts.map +1 -1
- package/dist-types/components/core/test/ScopedElementsMixin.test.d.ts +2 -0
- package/dist-types/components/core/test/ScopedElementsMixin.test.d.ts.map +1 -0
- package/dist-types/components/core/test/ssr-definitions/ScopedElementsHost.define.d.ts +2 -0
- package/dist-types/components/core/test/ssr-definitions/ScopedElementsHost.define.d.ts.map +1 -0
- package/dist-types/components/form/src/LionForm.d.ts.map +1 -1
- package/dist-types/components/form/test/lion-form.test.d.ts.map +1 -1
- package/dist-types/components/form-core/src/FormControlMixin.d.ts.map +1 -1
- package/dist-types/components/form-core/src/utils/SyncUpdatableMixin.d.ts.map +1 -1
- package/dist-types/components/form-core/src/validate/LionValidationFeedback.d.ts.map +1 -1
- package/dist-types/components/form-core/types/registration/FormRegisteringMixinTypes.d.ts +1 -1
- package/dist-types/components/form-integrations/test/helpers/helpers.d.ts +3 -3
- package/dist-types/components/form-integrations/test/helpers/helpers.d.ts.map +1 -1
- package/dist-types/components/icon/src/LionIcon.d.ts +5 -0
- package/dist-types/components/icon/src/LionIcon.d.ts.map +1 -1
- package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +1 -1
- package/dist-types/components/input-file/src/LionInputFile.d.ts +1 -1
- package/dist-types/components/input-file/src/LionSelectedFileList.d.ts +1 -1
- package/dist-types/components/localize/src/LocalizeManager.d.ts +109 -112
- package/dist-types/components/localize/src/LocalizeManager.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
- package/dist-types/components/progress-indicator/src/LionProgressIndicator.d.ts +5 -0
- package/dist-types/components/progress-indicator/src/LionProgressIndicator.d.ts.map +1 -1
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts +1 -1
- package/dist-types/components/steps/src/LionStep.d.ts +5 -0
- package/dist-types/components/steps/src/LionStep.d.ts.map +1 -1
- package/dist-types/components/steps/src/LionSteps.d.ts +5 -0
- package/dist-types/components/steps/src/LionSteps.d.ts.map +1 -1
- package/dist-types/components/switch/src/LionSwitch.d.ts +1 -1
- package/dist-types/components/tabs/src/LionTabs.d.ts +5 -0
- package/dist-types/components/tabs/src/LionTabs.d.ts.map +1 -1
- package/dist-types/components/tooltip/src/LionTooltip.d.ts +5 -0
- package/dist-types/components/tooltip/src/LionTooltip.d.ts.map +1 -1
- package/dist-types/tsconfig.tsbuildinfo +1 -1
- package/docs/components/checkbox-group/api-table.md +2 -2
- package/docs/components/checkbox-group/overview.md +2 -2
- package/docs/components/collapsible/overview.md +1 -1
- package/docs/components/combobox/overview.md +1 -1
- package/docs/components/combobox/use-cases.md +1 -1
- package/docs/components/dialog/overview.md +1 -1
- package/docs/components/fieldset/overview.md +5 -5
- package/docs/components/form/overview.md +3 -3
- package/docs/components/icon/use-cases.md +1 -1
- package/docs/components/input/api-table.md +2 -2
- package/docs/components/input/overview.md +1 -1
- package/docs/components/input-amount/api-table.md +2 -2
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-date/api-table.md +2 -2
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-datepicker/api-table.md +2 -2
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-email/api-table.md +2 -2
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-file/api-table.md +2 -2
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/api-table.md +2 -2
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-range/api-table.md +2 -2
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/api-table.md +2 -2
- package/docs/components/input-stepper/overview.md +1 -1
- package/docs/components/input-tel/api-table.md +2 -2
- package/docs/components/input-tel/overview.md +1 -1
- package/docs/components/input-tel/use-cases.md +6 -6
- package/docs/components/input-tel-dropdown/api-table.md +2 -2
- package/docs/components/input-tel-dropdown/overview.md +1 -1
- package/docs/components/listbox/api-table.md +1 -1
- package/docs/components/listbox/use-cases.md +1 -1
- package/docs/components/radio-group/api-table.md +2 -2
- package/docs/components/radio-group/overview.md +3 -3
- package/docs/components/select/api-table.md +2 -2
- package/docs/components/select/use-cases.md +1 -1
- package/docs/components/select-rich/api-table.md +1 -1
- package/docs/components/select-rich/use-cases.md +1 -1
- package/docs/components/switch/api-table.md +2 -2
- package/docs/components/tabs/examples.md +1 -1
- package/docs/components/textarea/api-table.md +2 -2
- package/docs/fundamentals/index.md +4 -4
- package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
- package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
- package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
- package/docs/fundamentals/rationales/accessibility.md +1 -1
- package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
- package/docs/fundamentals/systems/core/overview.md +1 -1
- package/docs/fundamentals/systems/form/api-table.md +1 -1
- package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
- package/docs/fundamentals/systems/form/model-value.md +1 -1
- package/docs/fundamentals/systems/form/overview.md +28 -28
- package/docs/fundamentals/systems/form/validate.md +2 -2
- package/docs/fundamentals/systems/localize/api-table.md +38 -40
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/overlays/configuration.md +2 -2
- package/docs/fundamentals/systems/overlays/overview.md +5 -5
- package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
- package/docs/fundamentals/tools/helpers/overview.md +1 -1
- package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
- package/docs/guides/how-to/create-a-custom-field.md +5 -5
- package/docs/guides/how-to/create-a-form.md +4 -4
- package/docs/guides/how-to/extend-a-native-input.md +2 -2
- package/docs/guides/index.md +8 -8
- package/docs/guides/principles/index.md +5 -5
- package/docs/guides/principles/styling.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,15 +26,15 @@
|
|
|
26
26
|
</p>
|
|
27
27
|
|
|
28
28
|
<p align="center">
|
|
29
|
-
<a href="https://lion
|
|
29
|
+
<a href="https://lion.js.org">Website</a>
|
|
30
30
|
·
|
|
31
|
-
<a href="https://lion
|
|
31
|
+
<a href="https://lion.js.org/fundamentals/">Fundamentals</a>
|
|
32
32
|
·
|
|
33
|
-
<a href="https://lion
|
|
33
|
+
<a href="https://lion.js.org/guides/">Guides</a>
|
|
34
34
|
·
|
|
35
|
-
<a href="https://lion
|
|
35
|
+
<a href="https://lion.js.org/components/">Components</a>
|
|
36
36
|
·
|
|
37
|
-
<a href="https://lion
|
|
37
|
+
<a href="https://lion.js.org/blog/">Blog</a>
|
|
38
38
|
</p>
|
|
39
39
|
|
|
40
40
|
**Lion is a set of highly performant, accessible and flexible Web Components.!**
|
|
@@ -50,7 +50,7 @@ They provide an unopinionated, white-label layer that can be extended to your ow
|
|
|
50
50
|
> Note: Our demo examples might look simple and not very stylish. This is on purpose. They are designed to be basic so you can easily add your own styles to them to match your intended design, without dealing with styles that are already there.
|
|
51
51
|
|
|
52
52
|
<p align="center">
|
|
53
|
-
<a href="https://lion
|
|
53
|
+
<a href="https://lion.js.org/guides/"><strong>Explore the Lion Guides ▶</strong></a>
|
|
54
54
|
</p>
|
|
55
55
|
|
|
56
56
|
## How to install
|
|
@@ -158,7 +158,7 @@ This means you only have to apply your own Design System: by delivering styles,
|
|
|
158
158
|
|
|
159
159
|
## Coding guidelines
|
|
160
160
|
|
|
161
|
-
Check out our [coding guidelines](https://lion
|
|
161
|
+
Check out our [coding guidelines](https://lion.js.org/guides/principles/definitions-and-terms/) for more detailed information.
|
|
162
162
|
|
|
163
163
|
## How to contribute
|
|
164
164
|
|
|
@@ -369,6 +369,12 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
+
/**
|
|
373
|
+
* // TODO: check if this is a false positive or if we can improve
|
|
374
|
+
* @configure ReactiveElement
|
|
375
|
+
*/
|
|
376
|
+
static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
|
|
377
|
+
|
|
372
378
|
/**
|
|
373
379
|
* @private
|
|
374
380
|
*/
|
|
@@ -49,6 +49,13 @@ const DisabledWithTabIndexMixinImplementation = superclass =>
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* // TODO: check if this is a false positive or if we can improve
|
|
54
|
+
* @configure ReactiveElement
|
|
55
|
+
*/
|
|
56
|
+
// @ts-expect-error
|
|
57
|
+
static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
|
|
58
|
+
|
|
52
59
|
/**
|
|
53
60
|
* @param {number} value
|
|
54
61
|
* @private
|
|
@@ -1,6 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
|
2
|
+
import { adoptStyles, isServer } from 'lit';
|
|
3
|
+
import { ScopedElementsMixin as OpenWcLitScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('../../form-core/types/validate/ValidateMixinTypes.js').ScopedElementsMap} ScopedElementsMap
|
|
7
|
+
* @typedef {import('@open-wc/dedupe-mixin').Constructor<ScopedElementsHost>} ScopedElementsHostConstructor
|
|
8
|
+
* @typedef {import('@open-wc/scoped-elements/lit-element.js').ScopedElementsHost} ScopedElementsHost
|
|
9
|
+
* @typedef {import('./types.js').ScopedElementsHostV2Constructor} ScopedElementsHostV2Constructor
|
|
10
|
+
* @typedef {import('@open-wc/dedupe-mixin').Constructor<LitElement>} LitElementConstructor
|
|
11
|
+
* @typedef {import('lit').CSSResultOrNative} CSSResultOrNative
|
|
12
|
+
* @typedef {typeof import('lit').LitElement} TypeofLitElement
|
|
13
|
+
* @typedef {import('lit').LitElement} LitElement
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export function supportsScopedRegistry() {
|
|
17
|
+
return Boolean(
|
|
18
|
+
// @ts-expect-error
|
|
19
|
+
globalThis.ShadowRoot?.prototype.createElement && globalThis.ShadowRoot?.prototype.importNode,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
2
24
|
* This file is combination of '@open-wc/scoped-elements@v3/lit-element.js' and '@open-wc/scoped-elements@v3/html-element.js'.
|
|
3
25
|
* Then on top of those, some code from '@open-wc/scoped-elements@v2' is brought to to make polyfill not mandatory.
|
|
26
|
+
* This can be a great help for ssr scenarios, allowing elements to be consumed without needing knowledge about internall
|
|
27
|
+
* consumption.
|
|
28
|
+
* (N.B. at this point in time, this is limited to the scenario where there's one version of lion on the page).
|
|
4
29
|
*
|
|
5
30
|
* ## Considerations
|
|
6
31
|
* In its current state, the [scoped-custom-element-registry](https://github.com/webcomponents/polyfills/tree/master/packages/scoped-custom-element-registry) draft spec has uncertainties:
|
|
@@ -21,29 +46,7 @@
|
|
|
21
46
|
* This can be beneficial for performance, bundle size, ease of use and SSR capabilities.
|
|
22
47
|
*
|
|
23
48
|
* We will keep a close eye on developments in spec and polyfill, and will re-evaluate the scoped-elements approach when the time is right.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
|
27
|
-
import { adoptStyles } from 'lit';
|
|
28
|
-
import { ScopedElementsMixin as OpenWcLitScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @typedef {import('@open-wc/scoped-elements/lit-element.js').ScopedElementsHost} ScopedElementsHost
|
|
32
|
-
* @typedef {import('../../form-core/types/validate/ValidateMixinTypes.js').ScopedElementsMap} ScopedElementsMap
|
|
33
|
-
* @typedef {import('lit').CSSResultOrNative} CSSResultOrNative
|
|
34
|
-
* @typedef {import('lit').LitElement} LitElement
|
|
35
|
-
* @typedef {typeof import('lit').LitElement} TypeofLitElement
|
|
36
|
-
* @typedef {import('@open-wc/dedupe-mixin').Constructor<LitElement>} LitElementConstructor
|
|
37
|
-
* @typedef {import('@open-wc/dedupe-mixin').Constructor<ScopedElementsHost>} ScopedElementsHostConstructor
|
|
38
|
-
* @typedef {import('./types.js').ScopedElementsHostV2Constructor} ScopedElementsHostV2Constructor
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
const supportsScopedRegistry = Boolean(
|
|
42
|
-
// @ts-expect-error
|
|
43
|
-
ShadowRoot.prototype.createElement && ShadowRoot.prototype.importNode,
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
/**
|
|
49
|
+
*
|
|
47
50
|
* @template {LitElementConstructor} T
|
|
48
51
|
* @param {T} superclass
|
|
49
52
|
* @return {T & ScopedElementsHostConstructor & ScopedElementsHostV2Constructor}
|
|
@@ -51,8 +54,29 @@ const supportsScopedRegistry = Boolean(
|
|
|
51
54
|
const ScopedElementsMixinImplementation = superclass =>
|
|
52
55
|
/** @type {ScopedElementsHost} */
|
|
53
56
|
class ScopedElementsHost extends OpenWcLitScopedElementsMixin(superclass) {
|
|
57
|
+
constructor() {
|
|
58
|
+
super();
|
|
59
|
+
|
|
60
|
+
if (isServer) {
|
|
61
|
+
// We are on the server: this means we can't support scoped registries...
|
|
62
|
+
// So we must treat it like the "no-polyfill scenario", that registers scoped
|
|
63
|
+
// elements used for internal composition on the global registry.
|
|
64
|
+
// On the client that would happen in connectedCallback, so we do it here...
|
|
65
|
+
// N.B. keep in mind that this does not work when we have multiple element (versions)
|
|
66
|
+
// with the same name. (like multiple versions of lion extension layers).
|
|
67
|
+
// If we want to support this, we must re-introduce the shim-behavior of ScopedElementsMixin v1
|
|
68
|
+
// to make this work with ssr as well.
|
|
69
|
+
// @ts-expect-error
|
|
70
|
+
this.registry = customElements;
|
|
71
|
+
// @ts-expect-error
|
|
72
|
+
for (const [name, klass] of Object.entries(this.constructor.scopedElements || {})) {
|
|
73
|
+
this.defineScopedElement(name, klass);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
54
78
|
createScopedElement(/** @type {string} */ tagName) {
|
|
55
|
-
const root = supportsScopedRegistry ? this.shadowRoot : document;
|
|
79
|
+
const root = supportsScopedRegistry() ? this.shadowRoot : document;
|
|
56
80
|
// @ts-expect-error polyfill to support createElement on shadowRoot is loaded
|
|
57
81
|
return root.createElement(tagName);
|
|
58
82
|
}
|
|
@@ -61,12 +85,12 @@ const ScopedElementsMixinImplementation = superclass =>
|
|
|
61
85
|
* Defines a scoped element.
|
|
62
86
|
*
|
|
63
87
|
* @param {string} tagName
|
|
64
|
-
* @param {typeof HTMLElement}
|
|
88
|
+
* @param {typeof HTMLElement} classToBeRegistered
|
|
65
89
|
*/
|
|
66
|
-
defineScopedElement(tagName,
|
|
67
|
-
// @ts-ignore
|
|
90
|
+
defineScopedElement(tagName, classToBeRegistered) {
|
|
68
91
|
const registeredClass = this.registry.get(tagName);
|
|
69
|
-
|
|
92
|
+
const isAlreadyRegistered = registeredClass && registeredClass === classToBeRegistered;
|
|
93
|
+
if (isAlreadyRegistered && !supportsScopedRegistry()) {
|
|
70
94
|
// eslint-disable-next-line no-console
|
|
71
95
|
console.error(
|
|
72
96
|
[
|
|
@@ -80,10 +104,8 @@ const ScopedElementsMixinImplementation = superclass =>
|
|
|
80
104
|
);
|
|
81
105
|
}
|
|
82
106
|
if (!registeredClass) {
|
|
83
|
-
|
|
84
|
-
return this.registry.define(tagName, klass);
|
|
107
|
+
return this.registry.define(tagName, classToBeRegistered);
|
|
85
108
|
}
|
|
86
|
-
// @ts-ignore
|
|
87
109
|
return this.registry.get(tagName);
|
|
88
110
|
}
|
|
89
111
|
|
|
@@ -92,12 +114,12 @@ const ScopedElementsMixinImplementation = superclass =>
|
|
|
92
114
|
* @returns {ShadowRoot}
|
|
93
115
|
*/
|
|
94
116
|
attachShadow(options) {
|
|
95
|
-
// @ts-
|
|
117
|
+
// @ts-expect-error
|
|
96
118
|
const { scopedElements } = /** @type {typeof ScopedElementsHost} */ (this.constructor);
|
|
97
119
|
|
|
98
120
|
const shouldCreateRegistry =
|
|
99
121
|
!this.registry ||
|
|
100
|
-
// @ts-
|
|
122
|
+
// @ts-expect-error
|
|
101
123
|
(this.registry === this.constructor.__registry &&
|
|
102
124
|
!Object.prototype.hasOwnProperty.call(this.constructor, '__registry'));
|
|
103
125
|
|
|
@@ -108,8 +130,7 @@ const ScopedElementsMixinImplementation = superclass =>
|
|
|
108
130
|
* This is important specifically for superclasses/inheritance
|
|
109
131
|
*/
|
|
110
132
|
if (shouldCreateRegistry) {
|
|
111
|
-
|
|
112
|
-
this.registry = supportsScopedRegistry ? new CustomElementRegistry() : customElements;
|
|
133
|
+
this.registry = supportsScopedRegistry() ? new CustomElementRegistry() : customElements;
|
|
113
134
|
for (const [tagName, klass] of Object.entries(scopedElements ?? {})) {
|
|
114
135
|
this.defineScopedElement(tagName, klass);
|
|
115
136
|
}
|
|
@@ -131,7 +152,7 @@ const ScopedElementsMixinImplementation = superclass =>
|
|
|
131
152
|
);
|
|
132
153
|
|
|
133
154
|
const createdRoot = this.attachShadow(shadowRootOptions);
|
|
134
|
-
if (supportsScopedRegistry) {
|
|
155
|
+
if (supportsScopedRegistry()) {
|
|
135
156
|
// @ts-expect-error
|
|
136
157
|
this.renderOptions.creationScope = createdRoot;
|
|
137
158
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { expect, fixture } from '@open-wc/testing';
|
|
2
|
+
import {
|
|
3
|
+
ssrNonHydratedFixture,
|
|
4
|
+
ssrHydratedFixture,
|
|
5
|
+
csrFixture,
|
|
6
|
+
} from '@lit-labs/testing/fixtures.js';
|
|
7
|
+
import { LitElement, html } from 'lit';
|
|
8
|
+
import sinon from 'sinon';
|
|
9
|
+
import { browserDetection } from '../src/browserDetection.js';
|
|
10
|
+
|
|
11
|
+
import { ScopedElementsMixin, supportsScopedRegistry } from '../src/ScopedElementsMixin.js';
|
|
12
|
+
|
|
13
|
+
const hasRealScopedRegistrySupport = supportsScopedRegistry();
|
|
14
|
+
const originalShadowRootProps = {
|
|
15
|
+
// @ts-expect-error
|
|
16
|
+
createElement: globalThis.ShadowRoot?.prototype.createElement,
|
|
17
|
+
// @ts-expect-error
|
|
18
|
+
importNode: globalThis.ShadowRoot?.prototype.importNode,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Even though the polyfill might be loaded in this test or we run it in a browser supporting these features,
|
|
22
|
+
// we mock "no support", so that `supportsScopedRegistry()` returns false inside ScopedElementsMixin..
|
|
23
|
+
function mockNoRegistrySupport() {
|
|
24
|
+
// Are we on a server or do we have no polyfill? Nothing to be done here...
|
|
25
|
+
if (!hasRealScopedRegistrySupport) return;
|
|
26
|
+
|
|
27
|
+
// This will be enough to make the `supportsScopedRegistry()` check fail inside ScopedElementsMixin and bypass scoped registries
|
|
28
|
+
globalThis.ShadowRoot = globalThis.ShadowRoot || { prototype: {} };
|
|
29
|
+
// @ts-expect-error
|
|
30
|
+
globalThis.ShadowRoot.prototype.createElement = null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
mockNoRegistrySupport.restore = () => {
|
|
34
|
+
// Are we on a server or do we have no polyfill? Nothing to be done here...
|
|
35
|
+
if (!hasRealScopedRegistrySupport) return;
|
|
36
|
+
|
|
37
|
+
// @ts-expect-error
|
|
38
|
+
globalThis.ShadowRoot.prototype.createElement = originalShadowRootProps.createElement;
|
|
39
|
+
// @ts-expect-error
|
|
40
|
+
globalThis.ShadowRoot.prototype.importNode = originalShadowRootProps.importNode;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
class ScopedElementsChild extends LitElement {
|
|
44
|
+
render() {
|
|
45
|
+
return html`<span>I'm a child</span>`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class ScopedElementsHost extends ScopedElementsMixin(LitElement) {
|
|
50
|
+
static scopedElements = { 'scoped-elements-child': ScopedElementsChild };
|
|
51
|
+
|
|
52
|
+
render() {
|
|
53
|
+
return html`<scoped-elements-child></scoped-elements-child>`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
customElements.define('scoped-elements-host', ScopedElementsHost);
|
|
57
|
+
|
|
58
|
+
describe('ScopedElementsMixin', () => {
|
|
59
|
+
it('renders child elements correctly (that were not registered yet on global registry)', async () => {
|
|
60
|
+
// customElements.define('scoped-elements-child', ScopedElementsChild);
|
|
61
|
+
for (const _fixture of [csrFixture, ssrNonHydratedFixture, ssrHydratedFixture]) {
|
|
62
|
+
const el = await _fixture(html`<scoped-elements-host></scoped-elements-host>`, {
|
|
63
|
+
// we must provide modules atm
|
|
64
|
+
modules: ['./ssr-definitions/ScopedElementsHost.define.js'],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Wait for FF support
|
|
68
|
+
if (!browserDetection.isFirefox) {
|
|
69
|
+
expect(
|
|
70
|
+
el.shadowRoot?.querySelector('scoped-elements-child')?.shadowRoot?.innerHTML,
|
|
71
|
+
).to.contain("<span>I'm a child</span>");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// @ts-expect-error
|
|
75
|
+
expect(el.registry.get('scoped-elements-child')).to.not.be.undefined;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('When scoped registries are supported', () => {
|
|
80
|
+
it('registers elements on local registry', async () => {
|
|
81
|
+
const ceDefineSpy = sinon.spy(customElements, 'define');
|
|
82
|
+
|
|
83
|
+
const el = /** @type {ScopedElementsHost} */ (
|
|
84
|
+
await fixture(html`<scoped-elements-host></scoped-elements-host>`)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// @ts-expect-error
|
|
88
|
+
expect(el.registry.get('scoped-elements-child')).to.equal(ScopedElementsChild);
|
|
89
|
+
expect(el.registry).to.not.equal(customElements);
|
|
90
|
+
expect(ceDefineSpy.calledWith('scoped-elements-child')).to.be.false;
|
|
91
|
+
|
|
92
|
+
ceDefineSpy.restore();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('When scoped registries are not supported', () => {
|
|
97
|
+
class ScopedElementsChildNoReg extends LitElement {
|
|
98
|
+
render() {
|
|
99
|
+
return html`<span>I'm a child</span>`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class ScopedElementsHostNoReg extends ScopedElementsMixin(LitElement) {
|
|
104
|
+
static scopedElements = { 'scoped-elements-child-no-reg': ScopedElementsChildNoReg };
|
|
105
|
+
|
|
106
|
+
render() {
|
|
107
|
+
return html`<scoped-elements-child-no-reg></scoped-elements-child-no-reg>`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
before(() => {
|
|
111
|
+
mockNoRegistrySupport();
|
|
112
|
+
customElements.define('scoped-elements-host-no-reg', ScopedElementsHostNoReg);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
after(() => {
|
|
116
|
+
mockNoRegistrySupport.restore();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('registers elements', async () => {
|
|
120
|
+
const ceDefineSpy = sinon.spy(customElements, 'define');
|
|
121
|
+
|
|
122
|
+
const el = /** @type {ScopedElementsHostNoReg} */ (
|
|
123
|
+
await fixture(html`<scoped-elements-host-no-reg></scoped-elements-host-no-reg>`)
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
expect(el.registry).to.equal(customElements);
|
|
127
|
+
expect(ceDefineSpy.calledWith('scoped-elements-child-no-reg')).to.be.true;
|
|
128
|
+
ceDefineSpy.restore();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// ... empty file needed for '@lit-labs/testing/fixtures.js'
|
|
@@ -10,15 +10,6 @@ const throwFormNodeError = () => {
|
|
|
10
10
|
);
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* @param {FormRegistrarHost} formEl
|
|
15
|
-
* @returns {boolean}
|
|
16
|
-
*/
|
|
17
|
-
function hasFocusableChildren(formEl) {
|
|
18
|
-
// this implies all children have the same type (either all of them are focusable or none of them are)
|
|
19
|
-
return formEl.formElements?.some(child => child._focusableNode);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
13
|
/**
|
|
23
14
|
* LionForm: form wrapper providing extra features and integration with lion-field elements.
|
|
24
15
|
*
|
|
@@ -104,10 +95,10 @@ export class LionForm extends LionFieldset {
|
|
|
104
95
|
element.formElements.find(child => child.hasFeedbackFor.includes('error')) ||
|
|
105
96
|
element.formElements[0];
|
|
106
97
|
|
|
107
|
-
if (
|
|
108
|
-
this._setFocusOnFirstErroneousFormElement(firstFormElWithError);
|
|
109
|
-
} else {
|
|
98
|
+
if (firstFormElWithError._focusableNode) {
|
|
110
99
|
firstFormElWithError._focusableNode.focus();
|
|
100
|
+
} else {
|
|
101
|
+
this._setFocusOnFirstErroneousFormElement(firstFormElWithError);
|
|
111
102
|
}
|
|
112
103
|
}
|
|
113
104
|
|
|
@@ -5,6 +5,10 @@ import '@lion/ui/define/lion-field.js';
|
|
|
5
5
|
import '@lion/ui/define/lion-validation-feedback.js';
|
|
6
6
|
import '@lion/ui/define/lion-listbox.js';
|
|
7
7
|
import '@lion/ui/define/lion-option.js';
|
|
8
|
+
import '@lion/ui/define/lion-checkbox-group.js';
|
|
9
|
+
import '@lion/ui/define/lion-checkbox.js';
|
|
10
|
+
import '@lion/ui/define/lion-radio-group.js';
|
|
11
|
+
import '@lion/ui/define/lion-radio.js';
|
|
8
12
|
import '@lion/ui/define/lion-form.js';
|
|
9
13
|
import {
|
|
10
14
|
aTimeout,
|
|
@@ -264,10 +268,31 @@ describe('<lion-form>', () => {
|
|
|
264
268
|
button.click();
|
|
265
269
|
expect(dispatchSpy.args[0][0].type).to.equal('submit');
|
|
266
270
|
const fieldset = el.formElements[0];
|
|
267
|
-
// @ts-ignore [allow-protected] in test
|
|
268
271
|
expect(document.activeElement).to.equal(fieldset.formElements[0]._inputNode);
|
|
269
272
|
});
|
|
270
273
|
|
|
274
|
+
it('sets focus on submit to the first form element within a erroneous fieldset within another fieldset', async () => {
|
|
275
|
+
const el = await fixture(html`
|
|
276
|
+
<lion-form>
|
|
277
|
+
<form>
|
|
278
|
+
<lion-fieldset name="parentFieldset">
|
|
279
|
+
<lion-fieldset name="childFieldset" .validators="${[new Required()]}">
|
|
280
|
+
<${childTag} name="firstName"></${childTag}>
|
|
281
|
+
</lion-fieldset>
|
|
282
|
+
</lion-fieldset>
|
|
283
|
+
|
|
284
|
+
<button type="submit">submit</button>
|
|
285
|
+
</form>
|
|
286
|
+
</lion-form>
|
|
287
|
+
`);
|
|
288
|
+
const button = /** @type {HTMLButtonElement} */ (el.querySelector('button'));
|
|
289
|
+
const parentFieldSetEl = el.formElements[0];
|
|
290
|
+
const childFieldsetEl = parentFieldSetEl.formElements[0];
|
|
291
|
+
const inputEl = childFieldsetEl.formElements[0];
|
|
292
|
+
button.click();
|
|
293
|
+
expect(document.activeElement).to.equal(inputEl._focusableNode);
|
|
294
|
+
});
|
|
295
|
+
|
|
271
296
|
it('sets focus on submit to the first form element within a erroneous listbox', async () => {
|
|
272
297
|
const el = await fixture(html`
|
|
273
298
|
<lion-form>
|
|
@@ -285,4 +310,66 @@ describe('<lion-form>', () => {
|
|
|
285
310
|
const listboxEl = el.formElements[0];
|
|
286
311
|
expect(document.activeElement).to.equal(listboxEl._inputNode);
|
|
287
312
|
});
|
|
313
|
+
|
|
314
|
+
it('sets focus on submit to the first form element within a erroneous listbox within a fieldset', async () => {
|
|
315
|
+
const el = await fixture(html`
|
|
316
|
+
<lion-form>
|
|
317
|
+
<form>
|
|
318
|
+
<lion-fieldset name="fieldset">
|
|
319
|
+
<lion-listbox name="name" .validators="${[new Required()]}">
|
|
320
|
+
<lion-option value="a">a</lion-option>
|
|
321
|
+
<lion-option value="b">b</lion-option>
|
|
322
|
+
</lion-listbox>
|
|
323
|
+
</lion-fieldset>
|
|
324
|
+
<button type="submit">submit</button>
|
|
325
|
+
</form>
|
|
326
|
+
</lion-form>
|
|
327
|
+
`);
|
|
328
|
+
const button = /** @type {HTMLButtonElement} */ (el.querySelector('button'));
|
|
329
|
+
button.click();
|
|
330
|
+
const fieldsetEl = el.formElements[0];
|
|
331
|
+
const listboxEl = fieldsetEl.formElements[0];
|
|
332
|
+
expect(document.activeElement).to.equal(listboxEl._inputNode);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('sets focus on submit to the first form element within a erroneous checkbox-group', async () => {
|
|
336
|
+
const el = await fixture(html`
|
|
337
|
+
<lion-form>
|
|
338
|
+
<form>
|
|
339
|
+
<lion-checkbox-group name="name" .validators="${[new Required()]}">
|
|
340
|
+
<lion-checkbox .choiceValue=${'a'} label="a"></lion-checkbox>
|
|
341
|
+
<lion-checkbox .choiceValue=${'b'} label="b"></lion-checkbox>
|
|
342
|
+
</lion-checkbox-group>
|
|
343
|
+
<button type="submit">submit</button>
|
|
344
|
+
</form>
|
|
345
|
+
</lion-form>
|
|
346
|
+
`);
|
|
347
|
+
const button = /** @type {HTMLButtonElement} */ (el.querySelector('button'));
|
|
348
|
+
const checkboxGroupEl = el.formElements[0];
|
|
349
|
+
const checkboxEl = checkboxGroupEl.formElements[0];
|
|
350
|
+
button.click();
|
|
351
|
+
expect(document.activeElement).to.equal(checkboxEl._focusableNode);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('sets focus on submit to the first form element within a erroneous radio-group', async () => {
|
|
355
|
+
const el = await fixture(html`
|
|
356
|
+
<lion-form>
|
|
357
|
+
<form>
|
|
358
|
+
<lion-radio-group name="name" .validators="${[new Required()]}">
|
|
359
|
+
<lion-radio .choiceValue=${'a'} label="a"></lion-radio>
|
|
360
|
+
<lion-radio .choiceValue=${'b'} label="b"></lion-radio>
|
|
361
|
+
</lion-radio-group>
|
|
362
|
+
</lion-fieldset>
|
|
363
|
+
|
|
364
|
+
<button type="submit">submit</button>
|
|
365
|
+
</form>
|
|
366
|
+
</lion-form>
|
|
367
|
+
`);
|
|
368
|
+
const button = /** @type {HTMLButtonElement} */ (el.querySelector('button'));
|
|
369
|
+
|
|
370
|
+
const radioGroupEl = el.formElements[0];
|
|
371
|
+
const radioEl = radioGroupEl.formElements[0];
|
|
372
|
+
button.click();
|
|
373
|
+
expect(document.activeElement).to.equal(radioEl._focusableNode);
|
|
374
|
+
});
|
|
288
375
|
});
|
|
@@ -152,6 +152,12 @@ const FormControlMixinImplementation = superclass =>
|
|
|
152
152
|
return /** @type {LionValidationFeedback} */ (this.__getDirectSlotChild('feedback'));
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/**
|
|
156
|
+
* TODO: check if this is a false positive or if we can improve
|
|
157
|
+
* @configure ReactiveElement
|
|
158
|
+
*/
|
|
159
|
+
static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
|
|
160
|
+
|
|
155
161
|
constructor() {
|
|
156
162
|
super();
|
|
157
163
|
|
|
@@ -40,7 +40,7 @@ const FormRegisteringMixinImplementation = superclass =>
|
|
|
40
40
|
* To encourage accessibility best practices, `form-element-register` events
|
|
41
41
|
* 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.
|
|
42
42
|
Use this option if you know what you're doing. It will then be possible to nest FormControls
|
|
43
|
-
inside shadow dom. See https://lion
|
|
43
|
+
inside shadow dom. See https://lion.js.org/fundamentals/rationales/accessibility/#shadow-roots-and-accessibility
|
|
44
44
|
*/
|
|
45
45
|
this.allowCrossRootRegistration = false;
|
|
46
46
|
}
|
|
@@ -56,6 +56,13 @@ const SyncUpdatableMixinImplementation = superclass =>
|
|
|
56
56
|
this.__SyncUpdatableNamespace.connected = false;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* // TODO: check if this is a false positive or if we can improve
|
|
61
|
+
* @configure ReactiveElement
|
|
62
|
+
*/
|
|
63
|
+
// @ts-expect-error
|
|
64
|
+
static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
|
|
65
|
+
|
|
59
66
|
/**
|
|
60
67
|
* Makes the propertyAccessor.`hasChanged` compatible in synchronous updates
|
|
61
68
|
* @param {string} name
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { css, html, LitElement } from 'lit';
|
|
1
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
2
2
|
import { LocalizeMixin } from '@lion/ui/localize-no-side-effects.js';
|
|
3
3
|
import { localizeNamespaceLoader } from '../localizeNamespaceLoader.js';
|
|
4
4
|
|
|
@@ -90,7 +90,9 @@ export class LionValidationFeedback extends LocalizeMixin(LitElement) {
|
|
|
90
90
|
this.feedbackData.map(
|
|
91
91
|
({ message, type, validator }) => html`
|
|
92
92
|
<div class="validation-feedback__type">
|
|
93
|
-
${
|
|
93
|
+
${message && type
|
|
94
|
+
? this._localizeManager.msg(`lion-form-core:validation${capitalize(type)}`)
|
|
95
|
+
: nothing}
|
|
94
96
|
</div>
|
|
95
97
|
${this._messageTemplate({ message, type, validator })}
|
|
96
98
|
`,
|
|
@@ -91,6 +91,18 @@ describe('lion-validation-feedback', () => {
|
|
|
91
91
|
expect(validationFeedbackType?.textContent?.trim()).to.equal('Info');
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
it('it does not share the type if there is no message', async () => {
|
|
95
|
+
const el = /** @type {LionValidationFeedback} */ (
|
|
96
|
+
await fixture(html`<lion-validation-feedback></lion-validation-feedback>`)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
el.feedbackData = [{ message: '', type: 'error', validator: new AlwaysInvalid() }];
|
|
100
|
+
await el.updateComplete;
|
|
101
|
+
|
|
102
|
+
const validationFeedbackType = el.shadowRoot?.querySelector('.validation-feedback__type');
|
|
103
|
+
expect(validationFeedbackType?.textContent?.trim()).to.not.equal('Error');
|
|
104
|
+
});
|
|
105
|
+
|
|
94
106
|
describe('accessibility', () => {
|
|
95
107
|
it('passes a11y audit when with a message', async () => {
|
|
96
108
|
const el = /** @type {LionValidationFeedback} */ (
|
|
@@ -16,7 +16,7 @@ export declare class FormRegisteringHost {
|
|
|
16
16
|
* do not pierce through shadow roots. This forces the developer to create form groups and fieldsets that
|
|
17
17
|
* automatically allow the creation of accessible relationships in the same dom tree.
|
|
18
18
|
* Use this option if you know what you're doing. It will then be possible to nest FormControls
|
|
19
|
-
* inside shadow dom. See https://lion
|
|
19
|
+
* inside shadow dom. See https://lion.js.org/fundamentals/rationales/accessibility#shadow-roots-and-accessibility
|
|
20
20
|
*/
|
|
21
21
|
allowCrossRootRegistration: boolean;
|
|
22
22
|
|
|
@@ -102,6 +102,12 @@ export class LionIcon extends LitElement {
|
|
|
102
102
|
this.__svg = nothing;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* TODO: check if this is a false positive or if we can improve
|
|
107
|
+
* @configure ReactiveElement
|
|
108
|
+
*/
|
|
109
|
+
static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
|
|
110
|
+
|
|
105
111
|
/** @param {import('lit').PropertyValues} changedProperties */
|
|
106
112
|
update(changedProperties) {
|
|
107
113
|
super.update(changedProperties);
|
|
@@ -400,7 +400,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|
|
400
400
|
`);
|
|
401
401
|
await el.updateComplete;
|
|
402
402
|
await el.updateComplete; // need 2 awaits as overlay.show is an async function
|
|
403
|
-
// for more info about why we need the ignoreRules, see: https://lion
|
|
403
|
+
// for more info about why we need the ignoreRules, see: https://lion.js.org/fundamentals/systems/overlays/rationale/#considerations
|
|
404
404
|
await expect(el).to.be.accessible({ ignoredRules: ['aria-allowed-role'] });
|
|
405
405
|
});
|
|
406
406
|
|
|
@@ -412,7 +412,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|
|
412
412
|
<${optionTag} .choiceValue=${20}>Item 2</${optionTag}>
|
|
413
413
|
</${tag}>
|
|
414
414
|
`);
|
|
415
|
-
// for more info about why we need the ignoreRules, see: https://lion
|
|
415
|
+
// for more info about why we need the ignoreRules, see: https://lion.js.org/fundamentals/systems/overlays/rationale/#considerations
|
|
416
416
|
await expect(el).to.be.accessible({ ignoredRules: ['aria-allowed-role'] });
|
|
417
417
|
});
|
|
418
418
|
|