@lion/ui 0.8.5 → 0.8.7

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 (105) hide show
  1. package/components/calendar/src/LionCalendar.js +5 -1
  2. package/components/combobox/test/lion-combobox.test.js +13 -11
  3. package/components/core/src/ScopedElementsMixin.js +2 -2
  4. package/components/core/test/ScopedElementsMixin.test.js +34 -1
  5. package/components/core/test/SlotMixin.test.js +19 -33
  6. package/components/core/test-helpers/isActiveElement.js +14 -0
  7. package/components/dialog/test/lion-dialog.test.js +6 -5
  8. package/components/form/test/lion-form.test.js +20 -18
  9. package/components/form-core/test/lion-field.test.js +15 -14
  10. package/components/form-core/test-suites/NativeTextFieldMixin.suite.js +8 -6
  11. package/components/form-core/test-suites/ValidateMixin.suite.js +7 -7
  12. package/components/form-integrations/test/dialog-integrations.test.js +6 -5
  13. package/components/input-datepicker/test/lion-input-datepicker.test.js +25 -1
  14. package/components/overlays/src/OverlayController.js +25 -1
  15. package/components/overlays/src/OverlayMixin.js +17 -4
  16. package/components/overlays/src/utils/adopt-styles.js +3 -3
  17. package/components/overlays/src/utils/contain-focus.js +1 -1
  18. package/components/overlays/test/OverlayController.test.js +46 -13
  19. package/components/overlays/test/utils-tests/contain-focus.test.js +21 -16
  20. package/components/select-rich/test/lion-select-rich.test.js +15 -17
  21. package/components/switch/test/lion-switch.test.js +7 -6
  22. package/custom-elements.json +157 -7
  23. package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
  24. package/dist-types/components/combobox/test/lion-combobox.test.d.ts.map +1 -1
  25. package/dist-types/components/{overlays/src/utils/get-deep-active-element.d.ts → core/src/getDeepActiveElement.d.ts} +1 -1
  26. package/dist-types/components/core/src/getDeepActiveElement.d.ts.map +1 -0
  27. package/dist-types/components/core/test/SlotMixin.test.d.ts.map +1 -1
  28. package/dist-types/components/core/test-helpers/isActiveElement.d.ts +13 -0
  29. package/dist-types/components/core/test-helpers/isActiveElement.d.ts.map +1 -0
  30. package/dist-types/components/dialog/test/lion-dialog.test.d.ts.map +1 -1
  31. package/dist-types/components/form/test/lion-form.test.d.ts.map +1 -1
  32. package/dist-types/components/form-core/test/lion-field.test.d.ts +2 -2
  33. package/dist-types/components/form-core/test/lion-field.test.d.ts.map +1 -1
  34. package/dist-types/components/form-core/test-suites/NativeTextFieldMixin.suite.d.ts +2 -2
  35. package/dist-types/components/form-core/test-suites/NativeTextFieldMixin.suite.d.ts.map +1 -1
  36. package/dist-types/components/form-integrations/test/dialog-integrations.test.d.ts.map +1 -1
  37. package/dist-types/components/overlays/src/OverlayController.d.ts +2 -0
  38. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  39. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  40. package/dist-types/components/overlays/test/OverlayController.test.d.ts.map +1 -1
  41. package/dist-types/components/select-rich/test/lion-select-rich.test.d.ts +2 -2
  42. package/dist-types/components/select-rich/test/lion-select-rich.test.d.ts.map +1 -1
  43. package/dist-types/components/switch/test/lion-switch.test.d.ts +1 -1
  44. package/dist-types/components/switch/test/lion-switch.test.d.ts.map +1 -1
  45. package/dist-types/exports/overlays.d.ts +2 -2
  46. package/dist-types/tsconfig.tsbuildinfo +1 -1
  47. package/docs/components/checkbox-group/overview.md +2 -2
  48. package/docs/components/collapsible/overview.md +1 -1
  49. package/docs/components/combobox/overview.md +1 -1
  50. package/docs/components/combobox/use-cases.md +1 -1
  51. package/docs/components/dialog/overview.md +1 -1
  52. package/docs/components/fieldset/overview.md +5 -5
  53. package/docs/components/form/overview.md +3 -3
  54. package/docs/components/form/use-cases.md +157 -8
  55. package/docs/components/icon/use-cases.md +1 -1
  56. package/docs/components/input/overview.md +1 -1
  57. package/docs/components/input-amount/overview.md +3 -3
  58. package/docs/components/input-date/overview.md +3 -3
  59. package/docs/components/input-datepicker/overview.md +5 -5
  60. package/docs/components/input-email/overview.md +2 -2
  61. package/docs/components/input-file/overview.md +1 -1
  62. package/docs/components/input-iban/overview.md +2 -2
  63. package/docs/components/input-range/overview.md +2 -2
  64. package/docs/components/input-stepper/overview.md +1 -1
  65. package/docs/components/input-tel/overview.md +1 -1
  66. package/docs/components/input-tel/use-cases.md +6 -6
  67. package/docs/components/input-tel-dropdown/overview.md +1 -1
  68. package/docs/components/listbox/use-cases.md +1 -1
  69. package/docs/components/radio-group/overview.md +3 -3
  70. package/docs/components/select/use-cases.md +1 -1
  71. package/docs/components/select-rich/use-cases.md +1 -1
  72. package/docs/components/tabs/examples.md +1 -1
  73. package/docs/fundamentals/index.md +4 -4
  74. package/docs/fundamentals/node-tools/providence-analytics/overview.md +42 -139
  75. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  76. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  77. package/docs/fundamentals/rationales/accessibility.md +1 -1
  78. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  79. package/docs/fundamentals/systems/core/overview.md +1 -1
  80. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  81. package/docs/fundamentals/systems/form/model-value.md +1 -1
  82. package/docs/fundamentals/systems/form/overview.md +28 -28
  83. package/docs/fundamentals/systems/form/validate.md +2 -2
  84. package/docs/fundamentals/systems/localize/overview.md +4 -4
  85. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  86. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  87. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  88. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  89. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  90. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  91. package/docs/guides/how-to/create-a-form.md +4 -4
  92. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  93. package/docs/guides/index.md +8 -8
  94. package/docs/guides/principles/index.md +5 -5
  95. package/docs/guides/principles/styling.md +1 -1
  96. package/exports/overlays.js +3 -2
  97. package/overlays.js +3 -2
  98. package/package.json +1 -1
  99. package/dist-types/components/overlays/src/utils/get-deep-active-element.d.ts.map +0 -1
  100. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +0 -68
  101. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +0 -111
  102. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +0 -69
  103. package/docs/fundamentals/node-tools/providence-analytics/assets/_mermaid.svg.js +0 -1226
  104. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +0 -22
  105. /package/components/{overlays/src/utils/get-deep-active-element.js → core/src/getDeepActiveElement.js} +0 -0
@@ -364,7 +364,11 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
364
364
  if (this.selectedDate) {
365
365
  this.focusSelectedDate();
366
366
  } else {
367
- this.centralDate = /** @type {Date} */ (this.__initialCentralDate);
367
+ if (!this.__isEnabledDate(/** @type {Date} */ (this.__initialCentralDate))) {
368
+ this.centralDate = this.findNearestEnabledDate(this.__initialCentralDate);
369
+ } else {
370
+ this.centralDate = /** @type {Date} */ (this.__initialCentralDate);
371
+ }
368
372
  this.focusCentralDate();
369
373
  }
370
374
  }
@@ -1,20 +1,22 @@
1
- import {
2
- getComboboxMembers,
3
- getFilteredOptionValues,
4
- mimicKeyPress,
5
- mimicUserTyping,
6
- mimicUserTypingAdvanced,
7
- } from '@lion/ui/combobox-test-helpers.js';
1
+ import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
2
+ import { Required, Unparseable } from '@lion/ui/form-core.js';
3
+ import { sendKeys } from '@web/test-runner-commands';
8
4
  import { LionCombobox } from '@lion/ui/combobox.js';
9
5
  import { browserDetection } from '@lion/ui/core.js';
10
6
  import '@lion/ui/define/lion-combobox.js';
11
7
  import '@lion/ui/define/lion-listbox.js';
12
8
  import '@lion/ui/define/lion-option.js';
13
- import { Required, Unparseable } from '@lion/ui/form-core.js';
14
- import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
15
- import { sendKeys } from '@web/test-runner-commands';
16
9
  import { LitElement } from 'lit';
17
10
  import sinon from 'sinon';
11
+ import {
12
+ getFilteredOptionValues,
13
+ mimicUserTypingAdvanced,
14
+ getComboboxMembers,
15
+ mimicUserTyping,
16
+ mimicKeyPress,
17
+ } from '@lion/ui/combobox-test-helpers.js';
18
+
19
+ import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
18
20
 
19
21
  /**
20
22
  * @typedef {import('../types/SelectionDisplay.js').SelectionDisplay} SelectionDisplay
@@ -986,7 +988,7 @@ describe('lion-combobox', () => {
986
988
  options[0].click();
987
989
  await el.updateComplete;
988
990
  expect(el.opened).to.equal(false);
989
- expect(document.activeElement).to.equal(_inputNode);
991
+ expect(isActiveElement(_inputNode)).to.be.true;
990
992
 
991
993
  // step [4]
992
994
  await el.updateComplete;
@@ -89,8 +89,8 @@ const ScopedElementsMixinImplementation = superclass =>
89
89
  */
90
90
  defineScopedElement(tagName, classToBeRegistered) {
91
91
  const registeredClass = this.registry.get(tagName);
92
- const isAlreadyRegistered = registeredClass && registeredClass === classToBeRegistered;
93
- if (isAlreadyRegistered && !supportsScopedRegistry()) {
92
+ const isNewClassWithSameName = registeredClass && registeredClass !== classToBeRegistered;
93
+ if (!supportsScopedRegistry() && isNewClassWithSameName) {
94
94
  // eslint-disable-next-line no-console
95
95
  console.error(
96
96
  [
@@ -6,9 +6,9 @@ import {
6
6
  } from '@lit-labs/testing/fixtures.js';
7
7
  import { LitElement, html } from 'lit';
8
8
  import sinon from 'sinon';
9
- import { browserDetection } from '../src/browserDetection.js';
10
9
 
11
10
  import { ScopedElementsMixin, supportsScopedRegistry } from '../src/ScopedElementsMixin.js';
11
+ import { browserDetection } from '../src/browserDetection.js';
12
12
 
13
13
  const hasRealScopedRegistrySupport = supportsScopedRegistry();
14
14
  const originalShadowRootProps = {
@@ -78,6 +78,8 @@ describe('ScopedElementsMixin', () => {
78
78
 
79
79
  describe('When scoped registries are supported', () => {
80
80
  it('registers elements on local registry', async () => {
81
+ if (!hasRealScopedRegistrySupport) return;
82
+
81
83
  const ceDefineSpy = sinon.spy(customElements, 'define');
82
84
 
83
85
  const el = /** @type {ScopedElementsHost} */ (
@@ -127,5 +129,36 @@ describe('ScopedElementsMixin', () => {
127
129
  expect(ceDefineSpy.calledWith('scoped-elements-child-no-reg')).to.be.true;
128
130
  ceDefineSpy.restore();
129
131
  });
132
+
133
+ it('fails when different classes are registered under different name', async () => {
134
+ class ScopedElementsHostNoReg2 extends ScopedElementsMixin(LitElement) {
135
+ static scopedElements = { 'scoped-elements-child-no-reg': class extends HTMLElement {} };
136
+
137
+ render() {
138
+ return html`<scoped-elements-child-no-reg></scoped-elements-child-no-reg>`;
139
+ }
140
+ }
141
+ customElements.define('scoped-elements-host-no-reg-2', ScopedElementsHostNoReg2);
142
+
143
+ const errorSpy = sinon.spy(console, 'error');
144
+ /** @type {ScopedElementsHostNoReg2} */ (
145
+ await fixture(html`<scoped-elements-host-no-reg></scoped-elements-host-no-reg>`)
146
+ );
147
+ /** @type {ScopedElementsHostNoReg2} */ (
148
+ await fixture(html`<scoped-elements-host-no-reg-2></scoped-elements-host-no-reg-2>`)
149
+ );
150
+
151
+ expect(errorSpy.args[0][0]).to.equal(
152
+ [
153
+ 'You are trying to re-register the "scoped-elements-child-no-reg" custom element with a different class via ScopedElementsMixin.',
154
+ 'This is only possible with a CustomElementRegistry.',
155
+ 'Your browser does not support this feature so you will need to load a polyfill for it.',
156
+ 'Load "@webcomponents/scoped-custom-element-registry" before you register ANY web component to the global customElements registry.',
157
+ 'e.g. add "<script src="/node_modules/@webcomponents/scoped-custom-element-registry/scoped-custom-element-registry.min.js"></script>" as your first script tag.',
158
+ 'For more details you can visit https://open-wc.org/docs/development/scoped-elements/',
159
+ ].join('\n'),
160
+ );
161
+ errorSpy.restore();
162
+ });
130
163
  });
131
164
  });
@@ -1,31 +1,15 @@
1
- import sinon from 'sinon';
2
1
  import { defineCE, expect, fixture, fixtureSync, unsafeStatic, html } from '@open-wc/testing';
3
- import { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';
4
2
  import { SlotMixin } from '@lion/ui/core.js';
5
3
  import { LitElement } from 'lit';
4
+ import sinon from 'sinon';
5
+
6
+ import { ScopedElementsMixin, supportsScopedRegistry } from '../src/ScopedElementsMixin.js';
7
+ import { isActiveElement } from '../test-helpers/isActiveElement.js';
6
8
 
7
9
  /**
8
10
  * @typedef {import('../types/SlotMixinTypes.js').SlotHost} SlotHost
9
11
  */
10
12
 
11
- // @ts-ignore
12
- const createElementNative = ShadowRoot.prototype.createElement;
13
- function mockScopedRegistry() {
14
- const outputObj = { createElementCallCount: 0 };
15
- // @ts-expect-error wait for browser support
16
- ShadowRoot.prototype.createElement = (tagName, options) => {
17
- outputObj.createElementCallCount += 1;
18
- // Return an element that lit can use as render target
19
- return createElementNative(tagName, options);
20
- };
21
- return outputObj;
22
- }
23
-
24
- function unMockScopedRegistry() {
25
- // @ts-expect-error wait for browser support
26
- ShadowRoot.prototype.createElement = createElementNative;
27
- }
28
-
29
13
  describe('SlotMixin', () => {
30
14
  it('inserts provided element into light dom and sets slot', async () => {
31
15
  const tag = defineCE(
@@ -227,12 +211,12 @@ describe('SlotMixin', () => {
227
211
  );
228
212
  const el = /** @type {* & SlotHost} */ (await fixture(`<${tag}></${tag}>`));
229
213
  el._focusableNode.focus();
230
- expect(document.activeElement).to.equal(el._focusableNode);
214
+ expect(isActiveElement(el._focusableNode)).to.be.true;
231
215
 
232
216
  el.currentValue = 1;
233
217
  await el.updateComplete;
234
218
 
235
- expect(document.activeElement).to.equal(el._focusableNode);
219
+ expect(isActiveElement(el._focusableNode)).to.be.true;
236
220
  });
237
221
 
238
222
  it('keeps focus after rerendering complex shadow root into slot', async () => {
@@ -294,7 +278,7 @@ describe('SlotMixin', () => {
294
278
  el.currentValue = 1;
295
279
  await el.updateComplete;
296
280
 
297
- expect(document.activeElement).to.equal(el._focusableNode);
281
+ expect(isActiveElement(el._focusableNode)).to.be.true;
298
282
  expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode);
299
283
  });
300
284
 
@@ -352,12 +336,12 @@ describe('SlotMixin', () => {
352
336
 
353
337
  el._focusableNode._buttonNode.focus();
354
338
 
355
- expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode);
339
+ expect(isActiveElement(el._focusableNode._buttonNode, { deep: true })).to.be.true;
356
340
 
357
341
  el.currentValue = 1;
358
342
  await el.updateComplete;
359
343
 
360
- expect(document.activeElement).to.equal(el._focusableNode);
344
+ expect(isActiveElement(el._focusableNode)).to.be.true;
361
345
  expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode);
362
346
  });
363
347
 
@@ -568,8 +552,11 @@ describe('SlotMixin', () => {
568
552
  });
569
553
 
570
554
  describe('Scoped Registries', () => {
571
- it('supports scoped elements when polyfill loaded', async () => {
572
- const outputObj = mockScopedRegistry();
555
+ it('supports scoped elements when scoped registries supported (or polyfill loaded)', async () => {
556
+ if (!supportsScopedRegistry()) return;
557
+
558
+ // @ts-expect-error
559
+ const createElSpy = sinon.spy(ShadowRoot.prototype, 'createElement');
573
560
 
574
561
  class ScopedEl extends LitElement {}
575
562
 
@@ -600,14 +587,14 @@ describe('SlotMixin', () => {
600
587
  const tag = unsafeStatic(tagName);
601
588
  await fixture(html`<${tag}></${tag}>`);
602
589
 
603
- expect(outputObj.createElementCallCount).to.equal(1);
590
+ expect(createElSpy.callCount).to.equal(1);
604
591
 
605
- unMockScopedRegistry();
592
+ createElSpy.restore();
606
593
  });
607
594
 
608
- it('does not scope elements when polyfill not loaded', async () => {
609
- // @ts-expect-error
610
- ShadowRoot.prototype.createElement = null;
595
+ it('does not scope elements when scoped registries not supported (or polyfill not loaded)', async () => {
596
+ if (supportsScopedRegistry()) return;
597
+
611
598
  class ScopedEl extends LitElement {}
612
599
 
613
600
  const tagName = defineCE(
@@ -645,7 +632,6 @@ describe('SlotMixin', () => {
645
632
 
646
633
  document.body.removeChild(renderTarget);
647
634
  docSpy.restore();
648
- unMockScopedRegistry();
649
635
  });
650
636
  });
651
637
  });
@@ -0,0 +1,14 @@
1
+ import { getDeepActiveElement } from '../src/getDeepActiveElement.js';
2
+ /**
3
+ * Readable alternative for `expect(el).to.equal(document.activeElement);`.
4
+ * While this is readable by itself, it makes Web Test Runner hang completely in many occasions.
5
+ * Therefore it's better to write:
6
+ * `expect(isActiveElement(el)).to.be.true;`
7
+ * @param {Element} el
8
+ * @param {{deep?: boolean}} opts
9
+ * @returns {boolean}
10
+ */
11
+ export function isActiveElement(el, { deep = false } = {}) {
12
+ const activeEl = deep ? getDeepActiveElement() : document.activeElement;
13
+ return el === activeEl;
14
+ }
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable lit-a11y/no-autofocus */
2
2
  import { expect, fixture as _fixture, html, unsafeStatic, aTimeout } from '@open-wc/testing';
3
3
  import { runOverlayMixinSuite } from '../../overlays/test-suites/OverlayMixin.suite.js';
4
+ import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
4
5
  import '@lion/ui/define/lion-dialog.js';
5
6
 
6
7
  /**
@@ -89,8 +90,8 @@ describe('lion-dialog', () => {
89
90
  const invokerNode = el._overlayInvokerNode;
90
91
  invokerNode.focus();
91
92
  invokerNode.click();
92
- const contentNode = el.querySelector('[slot="content"]');
93
- expect(document.activeElement).to.equal(contentNode);
93
+ const contentNode = /** @type {Element} */ (el.querySelector('[slot="content"]'));
94
+ expect(isActiveElement(contentNode)).to.be.true;
94
95
  });
95
96
 
96
97
  it('sets focus on autofocused element', async () => {
@@ -107,8 +108,8 @@ describe('lion-dialog', () => {
107
108
  const invokerNode = el._overlayInvokerNode;
108
109
  invokerNode.focus();
109
110
  invokerNode.click();
110
- const input = el.querySelector('input');
111
- expect(document.activeElement).to.equal(input);
111
+ const input = /** @type {Element} */ (el.querySelector('input'));
112
+ expect(isActiveElement(input)).to.be.true;
112
113
  });
113
114
 
114
115
  it('with trapsKeyboardFocus set to false the focus stays on the invoker', async () => {
@@ -125,7 +126,7 @@ describe('lion-dialog', () => {
125
126
  const invokerNode = el._overlayInvokerNode;
126
127
  invokerNode.focus();
127
128
  invokerNode.click();
128
- expect(document.activeElement).to.equal(invokerNode);
129
+ expect(isActiveElement(invokerNode)).to.be.true;
129
130
  });
130
131
 
131
132
  it('opened-changed event should send detail object with opened state', async () => {
@@ -1,25 +1,27 @@
1
- import { LionFieldset } from '@lion/ui/fieldset.js';
2
- import '@lion/ui/define/lion-fieldset.js';
3
1
  import { LionField, Required } from '@lion/ui/form-core.js';
4
- import '@lion/ui/define/lion-field.js';
5
2
  import '@lion/ui/define/lion-validation-feedback.js';
6
- import '@lion/ui/define/lion-listbox.js';
7
- import '@lion/ui/define/lion-option.js';
3
+ import { LionFieldset } from '@lion/ui/fieldset.js';
8
4
  import '@lion/ui/define/lion-checkbox-group.js';
9
- import '@lion/ui/define/lion-checkbox.js';
10
5
  import '@lion/ui/define/lion-radio-group.js';
6
+ import '@lion/ui/define/lion-fieldset.js';
7
+ import '@lion/ui/define/lion-checkbox.js';
8
+ import '@lion/ui/define/lion-listbox.js';
9
+ import '@lion/ui/define/lion-option.js';
10
+ import '@lion/ui/define/lion-field.js';
11
11
  import '@lion/ui/define/lion-radio.js';
12
12
  import '@lion/ui/define/lion-form.js';
13
+ import { spy } from 'sinon';
13
14
  import {
15
+ fixture as _fixture,
16
+ unsafeStatic,
14
17
  aTimeout,
15
18
  defineCE,
19
+ oneEvent,
16
20
  expect,
17
- fixture as _fixture,
18
21
  html,
19
- oneEvent,
20
- unsafeStatic,
21
22
  } from '@open-wc/testing';
22
- import { spy } from 'sinon';
23
+
24
+ import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
23
25
 
24
26
  /**
25
27
  * @typedef {import('../src/LionForm.js').LionForm} LionForm
@@ -225,7 +227,7 @@ describe('<lion-form>', () => {
225
227
  button.click();
226
228
  expect(dispatchSpy.args[0][0].type).to.equal('submit');
227
229
  // @ts-ignore [allow-protected] in test
228
- expect(document.activeElement).to.equal(el.formElements[1]._inputNode);
230
+ expect(isActiveElement(el.formElements[1]._inputNode)).to.be.true;
229
231
  });
230
232
 
231
233
  it('sets focus on submit to the first erroneous form element within a fieldset', async () => {
@@ -248,7 +250,7 @@ describe('<lion-form>', () => {
248
250
  expect(dispatchSpy.args[0][0].type).to.equal('submit');
249
251
  const fieldset = el.formElements[0];
250
252
  // @ts-ignore [allow-protected] in test
251
- expect(document.activeElement).to.equal(fieldset.formElements[1]._inputNode);
253
+ expect(isActiveElement(fieldset.formElements[1]._inputNode)).to.be.true;
252
254
  });
253
255
 
254
256
  it('sets focus on submit to the first form element within a erroneous fieldset', async () => {
@@ -268,7 +270,7 @@ describe('<lion-form>', () => {
268
270
  button.click();
269
271
  expect(dispatchSpy.args[0][0].type).to.equal('submit');
270
272
  const fieldset = el.formElements[0];
271
- expect(document.activeElement).to.equal(fieldset.formElements[0]._inputNode);
273
+ expect(isActiveElement(fieldset.formElements[0]._inputNode)).to.be.true;
272
274
  });
273
275
 
274
276
  it('sets focus on submit to the first form element within a erroneous fieldset within another fieldset', async () => {
@@ -290,7 +292,7 @@ describe('<lion-form>', () => {
290
292
  const childFieldsetEl = parentFieldSetEl.formElements[0];
291
293
  const inputEl = childFieldsetEl.formElements[0];
292
294
  button.click();
293
- expect(document.activeElement).to.equal(inputEl._focusableNode);
295
+ expect(isActiveElement(inputEl._focusableNode)).to.be.true;
294
296
  });
295
297
 
296
298
  it('sets focus on submit to the first form element within a erroneous listbox', async () => {
@@ -308,7 +310,7 @@ describe('<lion-form>', () => {
308
310
  const button = /** @type {HTMLButtonElement} */ (el.querySelector('button'));
309
311
  button.click();
310
312
  const listboxEl = el.formElements[0];
311
- expect(document.activeElement).to.equal(listboxEl._inputNode);
313
+ expect(isActiveElement(listboxEl._inputNode)).to.be.true;
312
314
  });
313
315
 
314
316
  it('sets focus on submit to the first form element within a erroneous listbox within a fieldset', async () => {
@@ -329,7 +331,7 @@ describe('<lion-form>', () => {
329
331
  button.click();
330
332
  const fieldsetEl = el.formElements[0];
331
333
  const listboxEl = fieldsetEl.formElements[0];
332
- expect(document.activeElement).to.equal(listboxEl._inputNode);
334
+ expect(isActiveElement(listboxEl._inputNode)).to.be.true;
333
335
  });
334
336
 
335
337
  it('sets focus on submit to the first form element within a erroneous checkbox-group', async () => {
@@ -348,7 +350,7 @@ describe('<lion-form>', () => {
348
350
  const checkboxGroupEl = el.formElements[0];
349
351
  const checkboxEl = checkboxGroupEl.formElements[0];
350
352
  button.click();
351
- expect(document.activeElement).to.equal(checkboxEl._focusableNode);
353
+ expect(isActiveElement(checkboxEl._focusableNode)).to.be.true;
352
354
  });
353
355
 
354
356
  it('sets focus on submit to the first form element within a erroneous radio-group', async () => {
@@ -370,6 +372,6 @@ describe('<lion-form>', () => {
370
372
  const radioGroupEl = el.formElements[0];
371
373
  const radioEl = radioGroupEl.formElements[0];
372
374
  button.click();
373
- expect(document.activeElement).to.equal(radioEl._focusableNode);
375
+ expect(isActiveElement(radioEl._focusableNode)).to.be.true;
374
376
  });
375
377
  });
@@ -1,27 +1,28 @@
1
- import { unsafeHTML } from 'lit/directives/unsafe-html.js';
2
- import { Required, Validator } from '@lion/ui/form-core.js';
3
- import '@lion/ui/define/lion-field.js';
4
1
  import { getFormControlMembers } from '@lion/ui/form-core-test-helpers.js';
5
2
  import { getLocalizeManager } from '@lion/ui/localize-no-side-effects.js';
6
3
  import { localizeTearDown } from '@lion/ui/localize-test-helpers.js';
4
+ import { Required, Validator } from '@lion/ui/form-core.js';
5
+ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
6
+ import '@lion/ui/define/lion-field.js';
7
+ import sinon from 'sinon';
7
8
  import {
8
- expect,
9
- fixture,
10
- html,
11
- triggerBlurFor,
12
9
  triggerFocusFor,
10
+ triggerBlurFor,
13
11
  unsafeStatic,
12
+ fixture,
13
+ expect,
14
+ html,
14
15
  } from '@open-wc/testing';
15
- import sinon from 'sinon';
16
+
17
+ import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
16
18
 
17
19
  /**
18
- * @typedef {import('../src/LionField.js').LionField} LionField
19
- * @typedef {import('../types/FormControlMixinTypes.js').FormControlHost} FormControlHost
20
20
  * @typedef {FormControlHost & HTMLElement & {_parentFormGroup?:HTMLElement, checked?:boolean}} FormControl
21
+ * @typedef {HTMLElement & {shadowRoot: HTMLElement, assignedNodes: Function}} ShadowHTMLElement
22
+ * @typedef {import('../types/FormControlMixinTypes.js').FormControlHost} FormControlHost
23
+ * @typedef {import('../src/LionField.js').LionField} LionField
21
24
  */
22
25
 
23
- /** @typedef {HTMLElement & {shadowRoot: HTMLElement, assignedNodes: Function}} ShadowHTMLElement */
24
-
25
26
  const tagString = 'lion-field';
26
27
  const tag = unsafeStatic(tagString);
27
28
  const inputSlotString = '<input slot="input" />';
@@ -106,7 +107,7 @@ describe('<lion-field>', () => {
106
107
 
107
108
  await triggerFocusFor(el);
108
109
 
109
- expect(document.activeElement).to.equal(_inputNode);
110
+ expect(isActiveElement(_inputNode)).to.be.true;
110
111
  expect(cbFocusHost.callCount).to.equal(1);
111
112
  expect(cbFocusNativeInput.callCount).to.equal(1);
112
113
  expect(cbBlurHost.callCount).to.equal(0);
@@ -117,7 +118,7 @@ describe('<lion-field>', () => {
117
118
  expect(cbBlurNativeInput.callCount).to.equal(1);
118
119
 
119
120
  await triggerFocusFor(el);
120
- expect(document.activeElement).to.equal(_inputNode);
121
+ expect(isActiveElement(_inputNode)).to.be.true;
121
122
  expect(cbFocusHost.callCount).to.equal(2);
122
123
  expect(cbFocusNativeInput.callCount).to.equal(2);
123
124
 
@@ -1,14 +1,16 @@
1
- import { LitElement } from 'lit';
2
- import { getFormControlMembers } from '@lion/ui/form-core-test-helpers.js';
3
1
  import { defineCE, expect, fixture, html, triggerFocusFor, unsafeStatic } from '@open-wc/testing';
4
- import { sendKeys } from '@web/test-runner-commands';
5
- import { spy } from 'sinon';
2
+ import { getFormControlMembers } from '@lion/ui/form-core-test-helpers.js';
6
3
  import { NativeTextFieldMixin } from '@lion/ui/form-core.js';
4
+ import { sendKeys } from '@web/test-runner-commands';
7
5
  import { browserDetection } from '@lion/ui/core.js';
6
+ import { LitElement } from 'lit';
7
+ import { spy } from 'sinon';
8
+
9
+ import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
8
10
 
9
11
  /**
10
- * @typedef {import('../types/FormControlMixinTypes.js').FormControlHost} FormControlHost
11
12
  * @typedef {ArrayConstructor | ObjectConstructor | NumberConstructor | BooleanConstructor | StringConstructor | DateConstructor | 'iban' | 'email'} modelValueType
13
+ * @typedef {import('../types/FormControlMixinTypes.js').FormControlHost} FormControlHost
12
14
  */
13
15
 
14
16
  /**
@@ -56,7 +58,7 @@ export function runNativeTextFieldMixinSuite(customConfig) {
56
58
  const { _inputNode } = getFormControlMembers(el);
57
59
  await triggerFocusFor(el);
58
60
  await el.updateComplete;
59
- expect(document.activeElement).to.equal(_inputNode);
61
+ expect(isActiveElement(_inputNode)).to.be.true;
60
62
  await sendKeys({
61
63
  press: 'h',
62
64
  });
@@ -2,25 +2,25 @@ import { LitElement } from 'lit';
2
2
  import { aTimeout, defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
3
3
  import {
4
4
  getFormControlMembers,
5
- AlwaysInvalid,
6
- AlwaysValid,
7
5
  AsyncAlwaysInvalid,
8
6
  AsyncAlwaysValid,
7
+ AlwaysInvalid,
8
+ AlwaysValid,
9
9
  } from '@lion/ui/form-core-test-helpers.js';
10
10
  import sinon from 'sinon';
11
11
  import {
12
+ ResultValidator,
13
+ ValidateMixin,
12
14
  EqualsLength,
15
+ Unparseable,
13
16
  MaxLength,
14
17
  MinLength,
15
- Required,
16
- ResultValidator,
17
- Unparseable,
18
- ValidateMixin,
19
18
  Validator,
19
+ Required,
20
20
  } from '@lion/ui/form-core.js';
21
21
 
22
- import '@lion/ui/define/lion-field.js';
23
22
  import '@lion/ui/define/lion-validation-feedback.js';
23
+ import '@lion/ui/define/lion-field.js';
24
24
 
25
25
  /**
26
26
  * @typedef {import('@lion/ui/form-core.js').LionField} LionField
@@ -1,13 +1,14 @@
1
1
  /* eslint-disable lit-a11y/no-autofocus */
2
2
  import { expect, fixture } from '@open-wc/testing';
3
- import { html } from 'lit';
4
- import { getAllTagNames } from './helpers/helpers.js';
5
- import './helpers/umbrella-form.js';
6
- import '@lion/ui/define/lion-dialog.js';
7
3
  import '@lion/ui/define/lion-checkbox.js';
4
+ import '@lion/ui/define/lion-dialog.js';
8
5
  import '@lion/ui/define/lion-option.js';
9
6
  import '@lion/ui/define/lion-radio.js';
7
+ import { html } from 'lit';
10
8
 
9
+ import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
10
+ import { getAllTagNames } from './helpers/helpers.js';
11
+ import './helpers/umbrella-form.js';
11
12
  /**
12
13
  * @typedef {import('./helpers/umbrella-form.js').UmbrellaForm} UmbrellaForm
13
14
  * @typedef {import('../../dialog/src/LionDialog.js').LionDialog} LionDialog
@@ -89,6 +90,6 @@ describe('Form inside dialog Integrations', () => {
89
90
  el._overlayInvokerNode.click();
90
91
  const lionInput = el.querySelector('[name="input"]');
91
92
  // @ts-expect-error [allow-protected-in-tests]
92
- expect(document.activeElement).to.equal(lionInput._focusableNode);
93
+ expect(isActiveElement(lionInput._focusableNode)).to.be.true;
93
94
  });
94
95
  });
@@ -19,9 +19,10 @@ import '@lion/ui/define/lion-input-datepicker.js';
19
19
  */
20
20
  function getProtectedMembersDatepicker(datepickerEl) {
21
21
  // @ts-ignore
22
- const { __invokerId: invokerId } = datepickerEl;
22
+ const { __invokerId: invokerId, _calendarNode: calendarNode } = datepickerEl;
23
23
  return {
24
24
  invokerId,
25
+ calendarNode,
25
26
  };
26
27
  }
27
28
 
@@ -410,6 +411,29 @@ describe('<lion-input-datepicker>', () => {
410
411
  expect(el.__calendarMaxDate.toString()).to.equal(new Date('2019/07/15').toString());
411
412
  });
412
413
 
414
+ it('should update the central date of Calendar if updated validator made the current central date disabled', async () => {
415
+ const initialValidators = [new MaxDate(new Date('2000/01/01'))];
416
+ const updatedValidators = [new MaxDate(new Date('2020/01/01'))];
417
+
418
+ const el = await fixture(html`
419
+ <lion-input-datepicker .validators="${initialValidators}"></lion-input-datepicker>
420
+ `);
421
+ const obj = new DatepickerInputObject(el);
422
+ await obj.openCalendar();
423
+
424
+ expect(getProtectedMembersDatepicker(el).calendarNode.centralDate.toString()).to.equal(
425
+ new Date('2000/01/01').toString(),
426
+ );
427
+
428
+ await obj.closeCalendar();
429
+ el.validators = updatedValidators;
430
+ await obj.openCalendar();
431
+
432
+ expect(getProtectedMembersDatepicker(el).calendarNode.centralDate.toString()).to.equal(
433
+ new Date('2020/01/01').toString(),
434
+ );
435
+ });
436
+
413
437
  it('should show error on invalid date passed to modelValue', async () => {
414
438
  const myDate = new Date('foo');
415
439
  const el = await fixture(html`
@@ -192,7 +192,6 @@ export class OverlayController extends EventTarget {
192
192
  zIndex: 9999,
193
193
  };
194
194
 
195
- this.manager.add(this);
196
195
  /** @protected */
197
196
  this._contentId = `overlay-content--${Math.random().toString(36).slice(2, 10)}`;
198
197
  /** @private */
@@ -475,6 +474,14 @@ export class OverlayController extends EventTarget {
475
474
  this._init();
476
475
  /** @private */
477
476
  this.__elementToFocusAfterHide = undefined;
477
+
478
+ if (!this.#isRegisteredOnManager()) {
479
+ this.manager.add(this);
480
+ }
481
+ }
482
+
483
+ #isRegisteredOnManager() {
484
+ return Boolean(this.manager.list.find(ctrl => this === ctrl));
478
485
  }
479
486
 
480
487
  /**
@@ -1285,6 +1292,14 @@ export class OverlayController extends EventTarget {
1285
1292
  wasMouseUpInside = false;
1286
1293
  });
1287
1294
  };
1295
+
1296
+ /** @type {EventListenerOrEventListenerObject} */
1297
+ this.__onWindowBlur = () => {
1298
+ // When the current window loses the focus (clicking outside iframe) the overlay gets hidden
1299
+ setTimeout(() => {
1300
+ this.hide();
1301
+ });
1302
+ };
1288
1303
  }
1289
1304
 
1290
1305
  this.contentWrapperNode[addOrRemoveListener](
@@ -1321,6 +1336,11 @@ export class OverlayController extends EventTarget {
1321
1336
  (this.__onDocumentMouseUp),
1322
1337
  true,
1323
1338
  );
1339
+ window[addOrRemoveListener](
1340
+ 'blur',
1341
+ /** @type {EventListenerOrEventListenerObject} */
1342
+ (this.__onWindowBlur),
1343
+ );
1324
1344
  }
1325
1345
 
1326
1346
  /**
@@ -1341,6 +1361,10 @@ export class OverlayController extends EventTarget {
1341
1361
  this.__handleOverlayStyles({ phase: 'teardown' });
1342
1362
  this._handleFeatures({ phase: 'teardown' });
1343
1363
  this.__wrappingDialogNode?.removeEventListener('cancel', this.__cancelHandler);
1364
+
1365
+ if (this.#isRegisteredOnManager()) {
1366
+ this.manager.remove(this);
1367
+ }
1344
1368
  }
1345
1369
 
1346
1370
  /** @private */