@lion/ui 0.8.4 → 0.8.6

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 (75) hide show
  1. package/components/core/src/ScopedElementsMixin.js +2 -2
  2. package/components/core/test/ScopedElementsMixin.test.js +34 -1
  3. package/components/core/test/SlotMixin.test.js +13 -28
  4. package/components/form-core/src/FormControlMixin.js +1 -1
  5. package/components/form-core/src/validate/ValidateMixin.js +9 -4
  6. package/components/form-core/src/validate/Validator.js +17 -3
  7. package/components/form-core/test/validate/Validator.test.js +8 -0
  8. package/components/form-core/test-suites/ValidateMixin.suite.js +7 -7
  9. package/components/overlays/src/OverlayController.js +25 -1
  10. package/components/overlays/src/OverlayMixin.js +17 -4
  11. package/components/overlays/src/utils/adopt-styles.js +3 -3
  12. package/components/overlays/test/OverlayController.test.js +33 -2
  13. package/custom-elements.json +582 -0
  14. package/dist-types/components/core/test/SlotMixin.test.d.ts.map +1 -1
  15. package/dist-types/components/form-core/src/validate/ValidateMixin.d.ts.map +1 -1
  16. package/dist-types/components/form-core/src/validate/Validator.d.ts +16 -6
  17. package/dist-types/components/form-core/src/validate/Validator.d.ts.map +1 -1
  18. package/dist-types/components/overlays/src/OverlayController.d.ts +2 -0
  19. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  20. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  21. package/dist-types/tsconfig.tsbuildinfo +1 -1
  22. package/docs/components/checkbox-group/overview.md +2 -2
  23. package/docs/components/collapsible/overview.md +1 -1
  24. package/docs/components/combobox/overview.md +1 -1
  25. package/docs/components/combobox/use-cases.md +1 -1
  26. package/docs/components/dialog/overview.md +1 -1
  27. package/docs/components/fieldset/overview.md +5 -5
  28. package/docs/components/form/overview.md +3 -3
  29. package/docs/components/icon/use-cases.md +1 -1
  30. package/docs/components/input/overview.md +1 -1
  31. package/docs/components/input-amount/overview.md +3 -3
  32. package/docs/components/input-date/overview.md +3 -3
  33. package/docs/components/input-datepicker/overview.md +5 -5
  34. package/docs/components/input-email/overview.md +2 -2
  35. package/docs/components/input-file/overview.md +1 -1
  36. package/docs/components/input-iban/overview.md +2 -2
  37. package/docs/components/input-range/overview.md +2 -2
  38. package/docs/components/input-stepper/overview.md +1 -1
  39. package/docs/components/input-tel/overview.md +1 -1
  40. package/docs/components/input-tel/use-cases.md +6 -6
  41. package/docs/components/input-tel-dropdown/overview.md +1 -1
  42. package/docs/components/listbox/use-cases.md +1 -1
  43. package/docs/components/radio-group/overview.md +3 -3
  44. package/docs/components/select/use-cases.md +1 -1
  45. package/docs/components/select-rich/use-cases.md +1 -1
  46. package/docs/components/tabs/examples.md +1 -1
  47. package/docs/fundamentals/index.md +4 -4
  48. package/docs/fundamentals/node-tools/providence-analytics/overview.md +42 -139
  49. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  50. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  51. package/docs/fundamentals/rationales/accessibility.md +1 -1
  52. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  53. package/docs/fundamentals/systems/core/overview.md +1 -1
  54. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  55. package/docs/fundamentals/systems/form/model-value.md +1 -1
  56. package/docs/fundamentals/systems/form/overview.md +28 -28
  57. package/docs/fundamentals/systems/form/validate.md +2 -2
  58. package/docs/fundamentals/systems/localize/overview.md +4 -4
  59. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  60. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  61. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  62. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  63. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  64. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  65. package/docs/guides/how-to/create-a-form.md +4 -4
  66. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  67. package/docs/guides/index.md +8 -8
  68. package/docs/guides/principles/index.md +5 -5
  69. package/docs/guides/principles/styling.md +1 -1
  70. package/package.json +1 -1
  71. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +0 -68
  72. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +0 -111
  73. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +0 -69
  74. package/docs/fundamentals/node-tools/providence-analytics/assets/_mermaid.svg.js +0 -1226
  75. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +0 -22
@@ -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,14 @@
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';
6
7
 
7
8
  /**
8
9
  * @typedef {import('../types/SlotMixinTypes.js').SlotHost} SlotHost
9
10
  */
10
11
 
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
12
  describe('SlotMixin', () => {
30
13
  it('inserts provided element into light dom and sets slot', async () => {
31
14
  const tag = defineCE(
@@ -568,8 +551,11 @@ describe('SlotMixin', () => {
568
551
  });
569
552
 
570
553
  describe('Scoped Registries', () => {
571
- it('supports scoped elements when polyfill loaded', async () => {
572
- const outputObj = mockScopedRegistry();
554
+ it('supports scoped elements when scoped registries supported (or polyfill loaded)', async () => {
555
+ if (!supportsScopedRegistry()) return;
556
+
557
+ // @ts-expect-error
558
+ const createElSpy = sinon.spy(ShadowRoot.prototype, 'createElement');
573
559
 
574
560
  class ScopedEl extends LitElement {}
575
561
 
@@ -600,14 +586,14 @@ describe('SlotMixin', () => {
600
586
  const tag = unsafeStatic(tagName);
601
587
  await fixture(html`<${tag}></${tag}>`);
602
588
 
603
- expect(outputObj.createElementCallCount).to.equal(1);
589
+ expect(createElSpy.callCount).to.equal(1);
604
590
 
605
- unMockScopedRegistry();
591
+ createElSpy.restore();
606
592
  });
607
593
 
608
- it('does not scope elements when polyfill not loaded', async () => {
609
- // @ts-expect-error
610
- ShadowRoot.prototype.createElement = null;
594
+ it('does not scope elements when scoped registries not supported (or polyfill not loaded)', async () => {
595
+ if (supportsScopedRegistry()) return;
596
+
611
597
  class ScopedEl extends LitElement {}
612
598
 
613
599
  const tagName = defineCE(
@@ -645,7 +631,6 @@ describe('SlotMixin', () => {
645
631
 
646
632
  document.body.removeChild(renderTarget);
647
633
  docSpy.restore();
648
- unMockScopedRegistry();
649
634
  });
650
635
  });
651
636
  });
@@ -274,7 +274,7 @@ const FormControlMixinImplementation = superclass =>
274
274
  super.updated(changedProperties);
275
275
 
276
276
  if (changedProperties.has('disabled')) {
277
- this._inputNode?.setAttribute('aria-disabled', this.disabled.toString());
277
+ this._inputNode?.setAttribute('aria-disabled', `${Boolean(this.disabled)}`);
278
278
  }
279
279
 
280
280
  if (changedProperties.has('_ariaLabelledNodes')) {
@@ -10,10 +10,10 @@ import { SyncUpdatableMixin } from '../utils/SyncUpdatableMixin.js';
10
10
  import { LionValidationFeedback } from './LionValidationFeedback.js';
11
11
  import { ResultValidator as MetaValidator } from './ResultValidator.js';
12
12
  import { Unparseable } from './Unparseable.js';
13
- import { Validator } from './Validator.js';
14
13
  import { Required } from './validators/Required.js';
15
14
  import { FormControlMixin } from '../FormControlMixin.js';
16
-
15
+ // eslint-disable-next-line no-unused-vars
16
+ import { Validator } from './Validator.js';
17
17
  // TODO: [v1] make all @readOnly => @readonly and actually make sure those values cannot be set
18
18
 
19
19
  /**
@@ -460,7 +460,9 @@ export const ValidateMixinImplementation = superclass =>
460
460
 
461
461
  if (isEmpty) {
462
462
  const hasSingleValue = !(/** @type {* & ValidateHost} */ (this)._isFormOrFieldset);
463
- const requiredValidator = this._allValidators.find(v => v instanceof Required);
463
+ const requiredValidator = this._allValidators.find(
464
+ v => /** @type {typeof Validator} */ (v.constructor)?.validatorName === 'Required',
465
+ );
464
466
  if (requiredValidator) {
465
467
  this.__syncValidationResult = [{ validator: requiredValidator, outcome: true }];
466
468
  }
@@ -685,7 +687,10 @@ export const ValidateMixinImplementation = superclass =>
685
687
  }
686
688
 
687
689
  for (const validatorToSetup of this._allValidators) {
688
- if (!(validatorToSetup instanceof Validator)) {
690
+ // disable dot notation to avoid the renaming for the prop during build/minification
691
+ const validatorCtor = /** @type {typeof Validator} */ (validatorToSetup.constructor);
692
+ // eslint-disable-next-line dot-notation
693
+ if (validatorCtor['_$isValidator$'] === undefined) {
689
694
  // throws in constructor are not visible to end user so we do both
690
695
  const errorType = Array.isArray(validatorToSetup) ? 'array' : typeof validatorToSetup;
691
696
  const errorMessage = `Validators array only accepts class instances of Validator. Type "${errorType}" found. This may be caused by having multiple installations of "@lion/ui/form-core.js".`;
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * @typedef {import('../../types/validate/index.js').FeedbackMessageData} FeedbackMessageData
3
- * @typedef {import('../../types/validate/index.js').ValidatorParam} ValidatorParam
4
- * @typedef {import('../../types/validate/index.js').ValidatorConfig} ValidatorConfig
5
3
  * @typedef {import('../../types/validate/index.js').ValidatorOutcome} ValidatorOutcome
6
- * @typedef {import('../../types/validate/index.js').ValidatorName} ValidatorName
4
+ * @typedef {import('../../types/validate/index.js').ValidatorConfig} ValidatorConfig
5
+ * @typedef {import('../../types/validate/index.js').ValidatorParam} ValidatorParam
7
6
  * @typedef {import('../../types/validate/index.js').ValidationType} ValidationType
7
+ * @typedef {import('../../types/validate/index.js').ValidatorName} ValidatorName
8
8
  * @typedef {import('../FormControlMixin.js').FormControlHost} FormControlHost
9
9
  */
10
10
 
@@ -32,8 +32,22 @@ export class Validator extends EventTarget {
32
32
  * @type {ValidationType}
33
33
  */
34
34
  this.type = config?.type || 'error';
35
+ /**
36
+ * Disable dot notation to avoid the renaming for the prop during build/minification
37
+ */
35
38
  }
36
39
 
40
+ /**
41
+ * This unique marker allows us to identify Validator instances across different lion versions.
42
+ * It's meant to be used as a replacement for an instanceof check.
43
+ *
44
+ * N.B. it's important to keep the bracket notation, as `static _$isValidator$ = true;` will
45
+ * be renamed during minification and that leads to different names across different
46
+ * lion versions.
47
+ * @type {boolean}
48
+ */
49
+ static ['_$isValidator$'] = true;
50
+
37
51
  /**
38
52
  * The name under which validation results get registered. For convenience and predictability, this
39
53
  * should always be the same as the constructor name (since it will be obfuscated in js builds,
@@ -1,3 +1,4 @@
1
+ /* eslint-disable dot-notation */
1
2
  import { LitElement } from 'lit';
2
3
  import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
3
4
  import sinon from 'sinon';
@@ -187,6 +188,13 @@ describe('Validator', () => {
187
188
  expect(disconnectSpy.calledWith(el)).to.equal(true);
188
189
  });
189
190
 
191
+ it('adds static ["_$isValidator$"] property as a marker to identify the Validator class across different lion versions (as instanceof cannot be used)', async () => {
192
+ const myValidator = new Validator();
193
+
194
+ expect(myValidator.constructor['_$isValidator$']).to.exist;
195
+ expect(myValidator.constructor['_$isValidator$']).to.be.true;
196
+ });
197
+
190
198
  describe('Types', () => {
191
199
  it('has type "error" by default', async () => {
192
200
  expect(new Validator().type).to.equal('error');
@@ -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
@@ -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 */
@@ -182,11 +182,13 @@ export const OverlayMixinImplementation = superclass =>
182
182
  this._setupOverlayCtrl();
183
183
  }
184
184
 
185
- disconnectedCallback() {
185
+ async disconnectedCallback() {
186
186
  super.disconnectedCallback();
187
- if (this._overlayCtrl) {
188
- this._teardownOverlayCtrl();
189
- }
187
+
188
+ if (!this._overlayCtrl) return;
189
+ if (await this.#isMovingInDom()) return;
190
+
191
+ this._teardownOverlayCtrl();
190
192
  }
191
193
 
192
194
  /**
@@ -253,6 +255,7 @@ export const OverlayMixinImplementation = superclass =>
253
255
  _teardownOverlayCtrl() {
254
256
  this._teardownOpenCloseListeners();
255
257
  this.__teardownSyncFromOverlayController();
258
+
256
259
  /** @type {OverlayController} */
257
260
  (this._overlayCtrl).teardown();
258
261
  }
@@ -387,5 +390,15 @@ export const OverlayMixinImplementation = superclass =>
387
390
  ctrl._popper.update();
388
391
  }
389
392
  }
393
+
394
+ /**
395
+ * When we're moving around in dom, disconnectedCallback gets called.
396
+ * Before we decide to teardown, let's wait to see if we were not just moving nodes around.
397
+ * @return {Promise<boolean>}
398
+ */
399
+ async #isMovingInDom() {
400
+ await this.updateComplete;
401
+ return this.isConnected;
402
+ }
390
403
  };
391
404
  export const OverlayMixin = dedupeMixin(OverlayMixinImplementation);
@@ -25,7 +25,7 @@ export const _adoptStyleUtils = {
25
25
  adoptStyles: undefined,
26
26
  };
27
27
 
28
- const styleCache = new Map();
28
+ const styleCache = new WeakMap();
29
29
 
30
30
  /**
31
31
  * @param {CSSStyleSheet} cssStyleSheet
@@ -81,10 +81,10 @@ function adoptStyleWhenAdoptedStylesheetsNotSupported(
81
81
  */
82
82
  function handleCache(renderRoot, style, { teardown = false } = {}) {
83
83
  let haltFurtherExecution = false;
84
- if (!styleCache.has(renderRoot)) {
84
+ if (renderRoot && !styleCache.has(renderRoot)) {
85
85
  styleCache.set(renderRoot, []);
86
86
  }
87
- const addedStylesForRoot = styleCache.get(renderRoot);
87
+ const addedStylesForRoot = styleCache.get(renderRoot) ?? [];
88
88
  const foundStyle = addedStylesForRoot.find(
89
89
  (/** @type {import("lit").CSSResultOrNative} */ addedStyle) => style === addedStyle,
90
90
  );
@@ -36,6 +36,14 @@ async function mimicEscapePress(element) {
36
36
  await aTimeout(0);
37
37
  }
38
38
 
39
+ /**
40
+ * @param {OverlayController} ctrlToFind
41
+ * @returns {boolean}
42
+ */
43
+ function isRegisteredOnManager(ctrlToFind) {
44
+ return Boolean(ctrlToFind.manager?.list?.find(ctrl => ctrlToFind === ctrl));
45
+ }
46
+
39
47
  /**
40
48
  * Make sure that all browsers serialize html in a similar way
41
49
  * (Firefox tends to output empty style attrs)
@@ -275,8 +283,18 @@ describe('OverlayController', () => {
275
283
  });
276
284
  });
277
285
 
278
- // TODO: Add teardown feature tests
279
- describe('Teardown', () => {});
286
+ // TODO: Add more teardown feature tests
287
+ describe('Teardown', () => {
288
+ it('unregisters itself from overlayManager', async () => {
289
+ const ctrl = new OverlayController(withGlobalTestConfig());
290
+
291
+ expect(isRegisteredOnManager(ctrl)).to.be.true;
292
+
293
+ ctrl.teardown();
294
+
295
+ expect(isRegisteredOnManager(ctrl)).to.be.false;
296
+ });
297
+ });
280
298
 
281
299
  describe('Node Configuration', () => {
282
300
  describe('Content', async () => {
@@ -1092,6 +1110,19 @@ describe('OverlayController', () => {
1092
1110
 
1093
1111
  expect(ctrl.isShown).to.be.true;
1094
1112
  });
1113
+
1114
+ it('hides when window is blurred (useful for iframes)', async () => {
1115
+ const ctrl = new OverlayController({
1116
+ ...withGlobalTestConfig(),
1117
+ hidesOnOutsideClick: true,
1118
+ });
1119
+ await ctrl.show();
1120
+
1121
+ window.dispatchEvent(new Event('blur'));
1122
+ await aTimeout(0);
1123
+
1124
+ expect(ctrl.isShown).to.be.false;
1125
+ });
1095
1126
  });
1096
1127
 
1097
1128
  describe('elementToFocusAfterHide', () => {