@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.
Files changed (136) hide show
  1. package/README.md +7 -7
  2. package/components/calendar/src/LionCalendar.js +6 -0
  3. package/components/core/src/DisabledWithTabIndexMixin.js +7 -0
  4. package/components/core/src/ScopedElementsMixin.js +58 -37
  5. package/components/core/test/ScopedElementsMixin.test.js +131 -0
  6. package/components/core/test/ssr-definitions/ScopedElementsHost.define.js +1 -0
  7. package/components/form/src/LionForm.js +3 -12
  8. package/components/form/test/lion-form.test.js +88 -1
  9. package/components/form-core/src/FormControlMixin.js +6 -0
  10. package/components/form-core/src/registration/FormRegisteringMixin.js +1 -1
  11. package/components/form-core/src/utils/SyncUpdatableMixin.js +7 -0
  12. package/components/form-core/src/validate/LionValidationFeedback.js +4 -2
  13. package/components/form-core/test/validate/lion-validation-feedback.test.js +12 -0
  14. package/components/form-core/types/registration/FormRegisteringMixinTypes.ts +1 -1
  15. package/components/icon/src/LionIcon.js +6 -0
  16. package/components/listbox/test-suites/ListboxMixin.suite.js +2 -2
  17. package/components/localize/src/LocalizeManager.js +259 -204
  18. package/components/overlays/src/OverlayController.js +1 -1
  19. package/components/overlays/src/OverlayMixin.js +7 -0
  20. package/components/progress-indicator/src/LionProgressIndicator.js +6 -0
  21. package/components/steps/src/LionStep.js +6 -0
  22. package/components/steps/src/LionSteps.js +6 -0
  23. package/components/tabs/src/LionTabs.js +6 -0
  24. package/components/tooltip/src/LionTooltip.js +6 -0
  25. package/custom-elements.json +792 -330
  26. package/dist-types/components/calendar/src/LionCalendar.d.ts +5 -0
  27. package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
  28. package/dist-types/components/core/src/DisabledWithTabIndexMixin.d.ts.map +1 -1
  29. package/dist-types/components/core/src/ScopedElementsMixin.d.ts +43 -6
  30. package/dist-types/components/core/src/ScopedElementsMixin.d.ts.map +1 -1
  31. package/dist-types/components/core/test/ScopedElementsMixin.test.d.ts +2 -0
  32. package/dist-types/components/core/test/ScopedElementsMixin.test.d.ts.map +1 -0
  33. package/dist-types/components/core/test/ssr-definitions/ScopedElementsHost.define.d.ts +2 -0
  34. package/dist-types/components/core/test/ssr-definitions/ScopedElementsHost.define.d.ts.map +1 -0
  35. package/dist-types/components/form/src/LionForm.d.ts.map +1 -1
  36. package/dist-types/components/form/test/lion-form.test.d.ts.map +1 -1
  37. package/dist-types/components/form-core/src/FormControlMixin.d.ts.map +1 -1
  38. package/dist-types/components/form-core/src/utils/SyncUpdatableMixin.d.ts.map +1 -1
  39. package/dist-types/components/form-core/src/validate/LionValidationFeedback.d.ts.map +1 -1
  40. package/dist-types/components/form-core/types/registration/FormRegisteringMixinTypes.d.ts +1 -1
  41. package/dist-types/components/form-integrations/test/helpers/helpers.d.ts +3 -3
  42. package/dist-types/components/form-integrations/test/helpers/helpers.d.ts.map +1 -1
  43. package/dist-types/components/icon/src/LionIcon.d.ts +5 -0
  44. package/dist-types/components/icon/src/LionIcon.d.ts.map +1 -1
  45. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +1 -1
  46. package/dist-types/components/input-file/src/LionInputFile.d.ts +1 -1
  47. package/dist-types/components/input-file/src/LionSelectedFileList.d.ts +1 -1
  48. package/dist-types/components/localize/src/LocalizeManager.d.ts +109 -112
  49. package/dist-types/components/localize/src/LocalizeManager.d.ts.map +1 -1
  50. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  51. package/dist-types/components/progress-indicator/src/LionProgressIndicator.d.ts +5 -0
  52. package/dist-types/components/progress-indicator/src/LionProgressIndicator.d.ts.map +1 -1
  53. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +1 -1
  54. package/dist-types/components/steps/src/LionStep.d.ts +5 -0
  55. package/dist-types/components/steps/src/LionStep.d.ts.map +1 -1
  56. package/dist-types/components/steps/src/LionSteps.d.ts +5 -0
  57. package/dist-types/components/steps/src/LionSteps.d.ts.map +1 -1
  58. package/dist-types/components/switch/src/LionSwitch.d.ts +1 -1
  59. package/dist-types/components/tabs/src/LionTabs.d.ts +5 -0
  60. package/dist-types/components/tabs/src/LionTabs.d.ts.map +1 -1
  61. package/dist-types/components/tooltip/src/LionTooltip.d.ts +5 -0
  62. package/dist-types/components/tooltip/src/LionTooltip.d.ts.map +1 -1
  63. package/dist-types/tsconfig.tsbuildinfo +1 -1
  64. package/docs/components/checkbox-group/api-table.md +2 -2
  65. package/docs/components/checkbox-group/overview.md +2 -2
  66. package/docs/components/collapsible/overview.md +1 -1
  67. package/docs/components/combobox/overview.md +1 -1
  68. package/docs/components/combobox/use-cases.md +1 -1
  69. package/docs/components/dialog/overview.md +1 -1
  70. package/docs/components/fieldset/overview.md +5 -5
  71. package/docs/components/form/overview.md +3 -3
  72. package/docs/components/icon/use-cases.md +1 -1
  73. package/docs/components/input/api-table.md +2 -2
  74. package/docs/components/input/overview.md +1 -1
  75. package/docs/components/input-amount/api-table.md +2 -2
  76. package/docs/components/input-amount/overview.md +3 -3
  77. package/docs/components/input-date/api-table.md +2 -2
  78. package/docs/components/input-date/overview.md +3 -3
  79. package/docs/components/input-datepicker/api-table.md +2 -2
  80. package/docs/components/input-datepicker/overview.md +5 -5
  81. package/docs/components/input-email/api-table.md +2 -2
  82. package/docs/components/input-email/overview.md +2 -2
  83. package/docs/components/input-file/api-table.md +2 -2
  84. package/docs/components/input-file/overview.md +1 -1
  85. package/docs/components/input-iban/api-table.md +2 -2
  86. package/docs/components/input-iban/overview.md +2 -2
  87. package/docs/components/input-range/api-table.md +2 -2
  88. package/docs/components/input-range/overview.md +2 -2
  89. package/docs/components/input-stepper/api-table.md +2 -2
  90. package/docs/components/input-stepper/overview.md +1 -1
  91. package/docs/components/input-tel/api-table.md +2 -2
  92. package/docs/components/input-tel/overview.md +1 -1
  93. package/docs/components/input-tel/use-cases.md +6 -6
  94. package/docs/components/input-tel-dropdown/api-table.md +2 -2
  95. package/docs/components/input-tel-dropdown/overview.md +1 -1
  96. package/docs/components/listbox/api-table.md +1 -1
  97. package/docs/components/listbox/use-cases.md +1 -1
  98. package/docs/components/radio-group/api-table.md +2 -2
  99. package/docs/components/radio-group/overview.md +3 -3
  100. package/docs/components/select/api-table.md +2 -2
  101. package/docs/components/select/use-cases.md +1 -1
  102. package/docs/components/select-rich/api-table.md +1 -1
  103. package/docs/components/select-rich/use-cases.md +1 -1
  104. package/docs/components/switch/api-table.md +2 -2
  105. package/docs/components/tabs/examples.md +1 -1
  106. package/docs/components/textarea/api-table.md +2 -2
  107. package/docs/fundamentals/index.md +4 -4
  108. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  109. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  110. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  111. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  112. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  113. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  114. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  115. package/docs/fundamentals/rationales/accessibility.md +1 -1
  116. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  117. package/docs/fundamentals/systems/core/overview.md +1 -1
  118. package/docs/fundamentals/systems/form/api-table.md +1 -1
  119. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  120. package/docs/fundamentals/systems/form/model-value.md +1 -1
  121. package/docs/fundamentals/systems/form/overview.md +28 -28
  122. package/docs/fundamentals/systems/form/validate.md +2 -2
  123. package/docs/fundamentals/systems/localize/api-table.md +38 -40
  124. package/docs/fundamentals/systems/localize/overview.md +4 -4
  125. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  126. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  127. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  128. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  129. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  130. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  131. package/docs/guides/how-to/create-a-form.md +4 -4
  132. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  133. package/docs/guides/index.md +8 -8
  134. package/docs/guides/principles/index.md +5 -5
  135. package/docs/guides/principles/styling.md +1 -1
  136. 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-web.netlify.app">Website</a>
29
+ <a href="https://lion.js.org">Website</a>
30
30
  ·
31
- <a href="https://lion-web.netlify.app/fundamentals/">Fundamentals</a>
31
+ <a href="https://lion.js.org/fundamentals/">Fundamentals</a>
32
32
  ·
33
- <a href="https://lion-web.netlify.app/guides/">Guides</a>
33
+ <a href="https://lion.js.org/guides/">Guides</a>
34
34
  ·
35
- <a href="https://lion-web.netlify.app/components/">Components</a>
35
+ <a href="https://lion.js.org/components/">Components</a>
36
36
  ·
37
- <a href="https://lion-web.netlify.app/blog/">Blog</a>
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-web.netlify.app/guides/"><strong>Explore the Lion Guides&nbsp;&nbsp;▶</strong></a>
53
+ <a href="https://lion.js.org/guides/"><strong>Explore the Lion Guides&nbsp;&nbsp;▶</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-web.netlify.app/guides/principles/definitions-and-terms/) for more detailed information.
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} klass
88
+ * @param {typeof HTMLElement} classToBeRegistered
65
89
  */
66
- defineScopedElement(tagName, klass) {
67
- // @ts-ignore
90
+ defineScopedElement(tagName, classToBeRegistered) {
68
91
  const registeredClass = this.registry.get(tagName);
69
- if (registeredClass && supportsScopedRegistry === false && registeredClass !== klass) {
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
- // @ts-ignore
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-ignore
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-ignore
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
- // @ts-ignore
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 (hasFocusableChildren(firstFormElWithError)) {
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-web.netlify.app/fundamentals/rationales/accessibility/#shadow-roots-and-accessibility
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
- ${this._localizeManager.msg(`lion-form-core:validation${capitalize(type)}`)}
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-web.netlify.app/fundamentals/rationales/accessibility#shadow-roots-and-accessibility
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-web.netlify.app/fundamentals/systems/overlays/rationale/#considerations
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-web.netlify.app/fundamentals/systems/overlays/rationale/#considerations
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