@lion/ui 0.9.0 → 0.10.0

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 (135) hide show
  1. package/components/combobox/src/LionCombobox.js +6 -2
  2. package/components/combobox/test/lion-combobox.test.js +58 -3
  3. package/components/core/test/ScopedElementsMixin.test.js +3 -3
  4. package/components/dialog/test/lion-dialog.test.js +54 -0
  5. package/components/dialog/test-helpers/test-router.js +43 -0
  6. package/components/form-core/src/FormatMixin.js +66 -18
  7. package/components/form-core/test-suites/FormatMixin.suite.js +107 -8
  8. package/components/form-core/types/FormatMixinTypes.ts +4 -1
  9. package/components/form-integrations/test/dialog-integrations.test.js +6 -2
  10. package/components/form-integrations/test/form-integrations.test.js +2 -2
  11. package/components/input-amount/src/parsers.js +3 -3
  12. package/components/input-amount/test/lion-input-amount.test.js +36 -2
  13. package/components/input-amount/test/parsers.test.js +11 -0
  14. package/components/input-datepicker/translations/es.js +1 -1
  15. package/components/input-stepper/src/LionInputStepper.js +70 -42
  16. package/components/input-stepper/test/lion-input-stepper.test.js +48 -12
  17. package/components/input-tel-dropdown/src/LionInputTelDropdown.js +7 -6
  18. package/components/input-tel-dropdown/test/LionInputTelDropdown.test.js +50 -0
  19. package/components/localize/src/number/getDecimalSeparator.js +1 -1
  20. package/components/localize/src/number/parseNumber.js +9 -6
  21. package/components/localize/test/number/parseNumber.test.js +11 -0
  22. package/components/localize/types/LocalizeMixinTypes.ts +3 -3
  23. package/components/overlays/src/OverlayController.js +4 -5
  24. package/components/overlays/src/OverlayMixin.js +20 -14
  25. package/components/overlays/test/OverlayController.test.js +11 -8
  26. package/components/overlays/test-suites/OverlayMixin.suite.js +184 -9
  27. package/components/overlays/types/ArrowMixinTypes.ts +11 -2
  28. package/components/overlays/types/OverlayConfig.ts +15 -13
  29. package/components/select-rich/src/LionSelectRich.js +3 -1
  30. package/components/select-rich/test/lion-select-rich.test.js +44 -0
  31. package/custom-elements.json +1337 -115
  32. package/dist-types/components/combobox/src/LionCombobox.d.ts.map +1 -1
  33. package/dist-types/components/dialog/test/lion-dialog.test.d.ts.map +1 -1
  34. package/dist-types/components/dialog/test-helpers/test-router.d.ts +2 -0
  35. package/dist-types/components/dialog/test-helpers/test-router.d.ts.map +1 -0
  36. package/dist-types/components/form-core/src/FormatMixin.d.ts +1 -1
  37. package/dist-types/components/form-core/src/FormatMixin.d.ts.map +1 -1
  38. package/dist-types/components/form-core/test-suites/FormatMixin.suite.d.ts.map +1 -1
  39. package/dist-types/components/form-core/types/FormatMixinTypes.d.ts +3 -2
  40. package/dist-types/components/form-core/types/FormatMixinTypes.d.ts.map +1 -1
  41. package/dist-types/components/form-integrations/test/dialog-integrations.test.d.ts.map +1 -1
  42. package/dist-types/components/input-amount/src/parsers.d.ts +2 -2
  43. package/dist-types/components/input-amount/src/parsers.d.ts.map +1 -1
  44. package/dist-types/components/input-amount/test/lion-input-amount.test.d.ts.map +1 -1
  45. package/dist-types/components/input-stepper/src/LionInputStepper.d.ts +22 -24
  46. package/dist-types/components/input-stepper/src/LionInputStepper.d.ts.map +1 -1
  47. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts.map +1 -1
  48. package/dist-types/components/input-tel-dropdown/test/LionInputTelDropdown.test.d.ts.map +1 -1
  49. package/dist-types/components/localize/src/number/parseNumber.d.ts.map +1 -1
  50. package/dist-types/components/localize/types/LocalizeMixinTypes.d.ts +3 -2
  51. package/dist-types/components/localize/types/LocalizeMixinTypes.d.ts.map +1 -1
  52. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  53. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  54. package/dist-types/components/overlays/test-suites/OverlayMixin.suite.d.ts +4 -4
  55. package/dist-types/components/overlays/test-suites/OverlayMixin.suite.d.ts.map +1 -1
  56. package/dist-types/components/overlays/types/ArrowMixinTypes.d.ts +2 -2
  57. package/dist-types/components/overlays/types/ArrowMixinTypes.d.ts.map +1 -1
  58. package/dist-types/components/overlays/types/OverlayConfig.d.ts +4 -4
  59. package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
  60. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +1 -1
  61. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  62. package/dist-types/components/select-rich/test/lion-select-rich.test.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/api-table.md +1 -0
  68. package/docs/components/combobox/overview.md +1 -1
  69. package/docs/components/combobox/use-cases.md +1 -1
  70. package/docs/components/dialog/api-table.md +1 -0
  71. package/docs/components/dialog/overview.md +1 -1
  72. package/docs/components/fieldset/overview.md +5 -5
  73. package/docs/components/form/overview.md +3 -3
  74. package/docs/components/form/use-cases.md +2 -2
  75. package/docs/components/icon/use-cases.md +1 -1
  76. package/docs/components/input/api-table.md +1 -1
  77. package/docs/components/input/overview.md +1 -1
  78. package/docs/components/input-amount/api-table.md +1 -1
  79. package/docs/components/input-amount/overview.md +3 -3
  80. package/docs/components/input-date/api-table.md +1 -1
  81. package/docs/components/input-date/overview.md +3 -3
  82. package/docs/components/input-datepicker/api-table.md +2 -1
  83. package/docs/components/input-datepicker/overview.md +5 -5
  84. package/docs/components/input-email/api-table.md +1 -1
  85. package/docs/components/input-email/overview.md +2 -2
  86. package/docs/components/input-file/api-table.md +1 -1
  87. package/docs/components/input-file/overview.md +1 -1
  88. package/docs/components/input-iban/api-table.md +1 -1
  89. package/docs/components/input-iban/overview.md +2 -2
  90. package/docs/components/input-range/api-table.md +1 -1
  91. package/docs/components/input-range/overview.md +2 -2
  92. package/docs/components/input-stepper/api-table.md +4 -4
  93. package/docs/components/input-stepper/overview.md +1 -1
  94. package/docs/components/input-tel/api-table.md +1 -1
  95. package/docs/components/input-tel/overview.md +1 -1
  96. package/docs/components/input-tel/use-cases.md +6 -6
  97. package/docs/components/input-tel-dropdown/api-table.md +1 -1
  98. package/docs/components/input-tel-dropdown/overview.md +1 -1
  99. package/docs/components/listbox/api-table.md +1 -1
  100. package/docs/components/listbox/use-cases.md +1 -1
  101. package/docs/components/radio-group/api-table.md +1 -1
  102. package/docs/components/radio-group/overview.md +3 -3
  103. package/docs/components/select/api-table.md +1 -1
  104. package/docs/components/select/use-cases.md +1 -1
  105. package/docs/components/select-rich/api-table.md +1 -0
  106. package/docs/components/select-rich/use-cases.md +1 -1
  107. package/docs/components/switch/api-table.md +1 -1
  108. package/docs/components/tabs/examples.md +1 -1
  109. package/docs/components/textarea/api-table.md +1 -1
  110. package/docs/components/tooltip/api-table.md +1 -0
  111. package/docs/fundamentals/index.md +4 -4
  112. package/docs/fundamentals/node-tools/providence-analytics/overview.md +5 -5
  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/overview.md +4 -4
  124. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  125. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  126. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  127. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  128. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  129. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  130. package/docs/guides/how-to/create-a-form.md +4 -4
  131. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  132. package/docs/guides/index.md +8 -8
  133. package/docs/guides/principles/index.md +5 -5
  134. package/docs/guides/principles/styling.md +1 -1
  135. package/package.json +1 -1
@@ -24,6 +24,8 @@ export const OverlayMixinImplementation = superclass =>
24
24
  };
25
25
  }
26
26
 
27
+ #hasSetup = false;
28
+
27
29
  constructor() {
28
30
  super();
29
31
  /**
@@ -173,22 +175,23 @@ export const OverlayMixinImplementation = superclass =>
173
175
  }
174
176
  }
175
177
 
176
- /**
177
- * @param {import('lit-element').PropertyValues } changedProperties
178
- */
179
- firstUpdated(changedProperties) {
180
- super.firstUpdated(changedProperties);
178
+ connectedCallback() {
179
+ super.connectedCallback();
181
180
 
182
- this._setupOverlayCtrl();
181
+ this.updateComplete.then(() => {
182
+ if (this.#hasSetup) return;
183
+ this._setupOverlayCtrl();
184
+ this.#hasSetup = true;
185
+ });
183
186
  }
184
187
 
185
188
  async disconnectedCallback() {
186
189
  super.disconnectedCallback();
187
190
 
188
- if (!this._overlayCtrl) return;
189
- if (await this.#isMovingInDom()) return;
190
-
191
- this._teardownOverlayCtrl();
191
+ if (await this._isPermanentlyDisconnected()) {
192
+ this._teardownOverlayCtrl();
193
+ this.#hasSetup = false;
194
+ }
192
195
  }
193
196
 
194
197
  /**
@@ -238,6 +241,8 @@ export const OverlayMixinImplementation = superclass =>
238
241
 
239
242
  /** @protected */
240
243
  _setupOverlayCtrl() {
244
+ if (this._overlayCtrl) return;
245
+
241
246
  /** @type {OverlayController} */
242
247
  this._overlayCtrl = this._defineOverlay({
243
248
  contentNode: this._overlayContentNode,
@@ -253,11 +258,12 @@ export const OverlayMixinImplementation = superclass =>
253
258
 
254
259
  /** @protected */
255
260
  _teardownOverlayCtrl() {
261
+ if (!this._overlayCtrl) return;
262
+
256
263
  this._teardownOpenCloseListeners();
257
264
  this.__teardownSyncFromOverlayController();
258
265
 
259
- /** @type {OverlayController} */
260
- (this._overlayCtrl).teardown();
266
+ /** @type {OverlayController} */ (this._overlayCtrl).teardown();
261
267
  }
262
268
 
263
269
  /**
@@ -396,9 +402,9 @@ export const OverlayMixinImplementation = superclass =>
396
402
  * Before we decide to teardown, let's wait to see if we were not just moving nodes around.
397
403
  * @return {Promise<boolean>}
398
404
  */
399
- async #isMovingInDom() {
405
+ async _isPermanentlyDisconnected() {
400
406
  await this.updateComplete;
401
- return this.isConnected;
407
+ return !this.isConnected;
402
408
  }
403
409
  };
404
410
  export const OverlayMixin = dedupeMixin(OverlayMixinImplementation);
@@ -103,12 +103,12 @@ const withLocalTestConfig = () =>
103
103
  async function createNestedEscControllers(parentContent) {
104
104
  const childContent = /** @type {HTMLDivElement} */ (parentContent.querySelector('div[id]'));
105
105
  // Assert valid fixure
106
- const isValid =
107
- (parentContent.id === 'parent-overlay--hidesOnEsc' ||
108
- parentContent.id === 'parent-overlay--hidesOnOutsideEsc') &&
109
- (childContent.id === 'child-overlay--hidesOnEsc' ||
110
- childContent.id === 'child-overlay--hidesOnOutsideEsc');
111
- if (!isValid) {
106
+ const isValidFixture =
107
+ (parentContent.id.startsWith('parent-overlay--hidesOnEsc') ||
108
+ parentContent.id.startsWith('parent-overlay--hidesOnOutsideEsc')) &&
109
+ (childContent.id.startsWith('child-overlay--hidesOnEsc') ||
110
+ childContent.id.startsWith('child-overlay--hidesOnOutsideEsc'));
111
+ if (!isValidFixture) {
112
112
  throw new Error('Provide a valid fixture');
113
113
  }
114
114
 
@@ -117,8 +117,8 @@ async function createNestedEscControllers(parentContent) {
117
117
  shadowRootParent.appendChild(childContent);
118
118
  }
119
119
 
120
- const parentHasOutsideOnEsc = parentContent.id === 'parent-overlay--hidesOnOutsideEsc';
121
- const childHasOutsideOnEsc = childContent.id === 'child-overlay--hidesOnOutsideEsc';
120
+ const parentHasOutsideOnEsc = parentContent.id.startsWith('parent-overlay--hidesOnOutsideEsc');
121
+ const childHasOutsideOnEsc = childContent.id.startsWith('child-overlay--hidesOnOutsideEsc');
122
122
 
123
123
  const parentConfig = parentHasOutsideOnEsc ? { hidesOnOutsideEsc: true } : { hidesOnEsc: true };
124
124
  const childConfig = childHasOutsideOnEsc ? { hidesOnOutsideEsc: true } : { hidesOnEsc: true };
@@ -787,6 +787,9 @@ describe('OverlayController', () => {
787
787
 
788
788
  expect(parentOverlay.isShown).to.be.false;
789
789
  expect(childOverlay.isShown).to.be.true;
790
+
791
+ await childOverlay.teardown();
792
+ await parentOverlay.teardown();
790
793
  });
791
794
 
792
795
  it('on [Escape] press outside overlays: parent stays shown, child hides', async () => {
@@ -1,18 +1,25 @@
1
- import { expect, fixture, html, nextFrame, aTimeout } from '@open-wc/testing';
2
-
3
- import sinon from 'sinon';
1
+ import {
2
+ unsafeStatic,
3
+ nextFrame,
4
+ aTimeout,
5
+ defineCE,
6
+ fixture,
7
+ expect,
8
+ html,
9
+ } from '@open-wc/testing';
4
10
  import { overlays as overlaysManager, OverlayController } from '@lion/ui/overlays.js';
5
-
6
- import '@lion/ui/define/lion-dialog.js';
7
11
  import { browserDetection } from '@lion/ui/core.js';
12
+ import { cache } from 'lit/directives/cache.js';
13
+ import '@lion/ui/define/lion-dialog.js';
14
+ import { LitElement } from 'lit';
15
+ import sinon from 'sinon';
8
16
 
9
17
  /**
10
- * @typedef {import('../types/OverlayConfig.js').OverlayConfig} OverlayConfig
11
18
  * @typedef {import('../types/OverlayMixinTypes.js').DefineOverlayConfig} DefineOverlayConfig
12
- * @typedef {import('../types/OverlayMixinTypes.js').OverlayHost} OverlayHost
13
- * @typedef {import('../types/OverlayMixinTypes.js').OverlayMixin} OverlayMixin
14
- * @typedef {import('lit').LitElement} LitElement
15
19
  * @typedef {LitElement & OverlayHost & {_overlayCtrl:OverlayController}} OverlayEl
20
+ * @typedef {import('../types/OverlayMixinTypes.js').OverlayMixin} OverlayMixin
21
+ * @typedef {import('../types/OverlayMixinTypes.js').OverlayHost} OverlayHost
22
+ * @typedef {import('../types/OverlayConfig.js').OverlayConfig} OverlayConfig
16
23
  */
17
24
 
18
25
  function getGlobalOverlayCtrls() {
@@ -331,6 +338,173 @@ export function runOverlayMixinSuite({ tagString, tag, suffix = '' }) {
331
338
  }).to.not.throw;
332
339
  stub.restore();
333
340
  });
341
+
342
+ describe('Teardown', () => {
343
+ it('does not teardown when moving dom nodes', async () => {
344
+ const el = /** @type {OverlayEl} */ (
345
+ await fixture(html`
346
+ <${tag}>
347
+ <div slot="content">content</div>
348
+ <button slot="invoker">invoker button</button>
349
+ </${tag}>
350
+ `)
351
+ );
352
+ // @ts-expect-error [allow-protected] in tests
353
+ const teardownSpy = sinon.spy(el, '_teardownOverlayCtrl');
354
+ // @ts-expect-error [allow-protected] in tests
355
+ const spyDisconnected = sinon.spy(el, '_isPermanentlyDisconnected');
356
+
357
+ // move around in dom
358
+ const newParent = document.createElement('div');
359
+ document.body.appendChild(newParent);
360
+ newParent.appendChild(el);
361
+ expect(spyDisconnected.callCount).to.equal(1);
362
+
363
+ const otherParent = document.createElement('div');
364
+ document.body.appendChild(otherParent);
365
+ otherParent.appendChild(el);
366
+ await aTimeout(0);
367
+
368
+ expect(spyDisconnected.callCount).to.equal(2);
369
+ expect(teardownSpy.callCount).to.equal(0);
370
+
371
+ // simulate a permanent disconnect
372
+ otherParent.removeChild(el);
373
+ await aTimeout(0);
374
+
375
+ expect(teardownSpy.callCount).to.equal(1);
376
+ });
377
+
378
+ it('allows to disconnect and reconnect later', async () => {
379
+ const el = /** @type {OverlayEl} */ (
380
+ await fixture(html`
381
+ <${tag}>
382
+ <div slot="content">content</div>
383
+ <button slot="invoker">invoker button</button>
384
+ </${tag}>
385
+ `)
386
+ );
387
+ // @ts-expect-error [allow-protected] in tests
388
+ const teardownSpy = sinon.spy(el, '_teardownOverlayCtrl');
389
+ // @ts-expect-error [allow-protected] in tests
390
+ const spyDisconnected = sinon.spy(el, '_isPermanentlyDisconnected');
391
+ // @ts-expect-error [allow-protected] in tests
392
+ const setupSpy = sinon.spy(el, '_setupOverlayCtrl');
393
+
394
+ // 'permanently' disconnect (although we will reconnect later)
395
+ const offlineParent = document.createElement('div');
396
+ offlineParent.appendChild(el);
397
+ await aTimeout(0);
398
+
399
+ expect(spyDisconnected.callCount).to.equal(1);
400
+ expect(teardownSpy.callCount).to.equal(1);
401
+
402
+ // reconnect
403
+ document.body.appendChild(offlineParent);
404
+ await aTimeout(0);
405
+
406
+ expect(setupSpy.callCount).to.equal(1);
407
+ });
408
+
409
+ it('works with cache directive when reconnected', async () => {
410
+ class CachingContext extends LitElement {
411
+ static properties = {
412
+ switched: { type: Boolean },
413
+ };
414
+
415
+ constructor() {
416
+ super();
417
+
418
+ this.switched = false;
419
+ }
420
+
421
+ get overlayEl() {
422
+ return this.shadowRoot?.querySelector('#myOverlay');
423
+ }
424
+
425
+ render() {
426
+ return html`${cache(
427
+ this.switched
428
+ ? html`something else`
429
+ : html`<${tag} id="myOverlay">
430
+ <div slot="content">content b</div>
431
+ <button slot="invoker">invoker button b</button>
432
+ </${tag}>`,
433
+ )}`;
434
+ }
435
+ }
436
+
437
+ const cachingTagName = defineCE(CachingContext);
438
+ const cachingTag = unsafeStatic(cachingTagName);
439
+
440
+ const el = /** @type {CachingContext} */ (
441
+ await fixture(html`
442
+ <${cachingTag}></${cachingTag}>
443
+ `)
444
+ );
445
+
446
+ // @ts-expect-error [allow-protected] in tests
447
+ const teardownSpy = sinon.spy(el.overlayEl, '_teardownOverlayCtrl');
448
+ // @ts-expect-error [allow-protected] in tests
449
+ const spyDisconnected = sinon.spy(el.overlayEl, '_isPermanentlyDisconnected');
450
+ // @ts-expect-error [allow-protected] in tests
451
+ const setupSpy = sinon.spy(el.overlayEl, '_setupOverlayCtrl');
452
+
453
+ el.switched = true;
454
+ // render the new content
455
+ await el.updateComplete;
456
+ // wait for the teardown to complete
457
+ await el.updateComplete;
458
+
459
+ expect(spyDisconnected.callCount).to.equal(1);
460
+ expect(teardownSpy.callCount).to.equal(1);
461
+ expect(setupSpy.callCount).to.equal(0);
462
+
463
+ el.switched = false;
464
+ await el.updateComplete;
465
+
466
+ expect(setupSpy.callCount).to.equal(1);
467
+ });
468
+
469
+ it('correctly removes event listeners when disconnected from dom', async () => {
470
+ const el = /** @type {OverlayEl} */ (
471
+ await fixture(html`
472
+ <${tag}>
473
+ <div slot="content">content</div>
474
+ <button slot="invoker">invoker button</button>
475
+ </${tag}>
476
+ `)
477
+ );
478
+
479
+ const eventRemoveSpy = sinon.spy(el._overlayCtrl, 'removeEventListener');
480
+ el.parentNode?.removeChild(el);
481
+ await el.updateComplete;
482
+
483
+ expect(eventRemoveSpy.callCount).to.equal(0);
484
+
485
+ await el.updateComplete;
486
+ const hasOneInstanceFor = (
487
+ /** @type {string} */ eventName,
488
+ /** @type {any[][]} */ eventsToSearch,
489
+ /** @type {string} */ methodToRemove,
490
+ ) =>
491
+ Boolean(
492
+ eventsToSearch.filter(
493
+ ([eventToSearch, methodToSearch]) =>
494
+ eventToSearch === eventName && methodToSearch === methodToRemove,
495
+ ).length,
496
+ );
497
+
498
+ // @ts-expect-error [allow-private] in tests
499
+ expect(hasOneInstanceFor('show', eventRemoveSpy.args, el.__onOverlayCtrlShow)).to.be.true;
500
+ // @ts-expect-error [allow-private] in tests
501
+ expect(hasOneInstanceFor('hide', eventRemoveSpy.args, el.__onOverlayCtrlHide)).to.be.true;
502
+ // @ts-expect-error [allow-private] in tests
503
+ expect(hasOneInstanceFor('before-show', eventRemoveSpy.args, el.__onBeforeShow)).to.be.true;
504
+ // @ts-expect-error [allow-private] in tests
505
+ expect(hasOneInstanceFor('before-hide', eventRemoveSpy.args, el.__onBeforeHide)).to.be.true;
506
+ });
507
+ });
334
508
  });
335
509
 
336
510
  describe(`OverlayMixin${suffix} nested`, () => {
@@ -388,6 +562,7 @@ export function runOverlayMixinSuite({ tagString, tag, suffix = '' }) {
388
562
  const moveTarget = /** @type {OverlayEl} */ (await fixture('<div id="target"></div>'));
389
563
  moveTarget.appendChild(el);
390
564
  await el.updateComplete;
565
+
391
566
  expect(getGlobalOverlayCtrls().length).to.equal(1);
392
567
  }
393
568
  });
@@ -4,25 +4,34 @@ import { Options as PopperOptions, State } from '@popperjs/core/lib/popper.js';
4
4
  import { OverlayConfig } from './OverlayConfig.js';
5
5
 
6
6
  export declare class ArrowHost {
7
- static get properties(): {
7
+ static properties: {
8
8
  hasArrow: {
9
9
  type: BooleanConstructor;
10
10
  reflect: boolean;
11
11
  attribute: string;
12
12
  };
13
13
  };
14
+
14
15
  hasArrow: boolean;
16
+
15
17
  repositionComplete: Promise<void>;
16
18
 
17
- static get styles(): CSSResultArray;
19
+ static styles: CSSResultArray;
18
20
 
19
21
  render(): TemplateResult;
22
+
20
23
  protected _arrowTemplate(): TemplateResult;
24
+
21
25
  protected _arrowNodeTemplate(): TemplateResult;
26
+
22
27
  protected _defineOverlayConfig(): OverlayConfig;
28
+
23
29
  protected _getPopperArrowConfig(popperConfigToExtendFrom: Partial<PopperOptions>): Partial<PopperOptions>;
30
+
24
31
  private __setupRepositionCompletePromise(): void;
32
+
25
33
  get _arrowNode(): Element | null;
34
+
26
35
  private __syncFromPopperState(data: Partial<State>): void;
27
36
  }
28
37
 
@@ -1,5 +1,20 @@
1
1
  import { Options } from '@popperjs/core';
2
2
 
3
+ export type ViewportPlacement =
4
+ | 'center'
5
+ | 'top-left'
6
+ | 'top'
7
+ | 'top-right'
8
+ | 'right'
9
+ | 'bottom-right'
10
+ | 'bottom'
11
+ | 'bottom-left'
12
+ | 'left';
13
+
14
+
15
+ export interface ViewportConfig {
16
+ placement: ViewportPlacement;
17
+ }
3
18
  export interface OverlayConfig {
4
19
  // Positioning
5
20
 
@@ -80,17 +95,4 @@ export interface OverlayConfig {
80
95
  visibilityTriggerFunction?: Function;
81
96
  }
82
97
 
83
- export type ViewportPlacement =
84
- | 'center'
85
- | 'top-left'
86
- | 'top'
87
- | 'top-right'
88
- | 'right'
89
- | 'bottom-right'
90
- | 'bottom'
91
- | 'bottom-left'
92
- | 'left';
93
98
 
94
- export interface ViewportConfig {
95
- placement: ViewportPlacement;
96
- }
@@ -423,6 +423,7 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
423
423
  */
424
424
  _teardownOverlayCtrl() {
425
425
  super._teardownOverlayCtrl();
426
+
426
427
  this._overlayCtrl.removeEventListener('show', this.__overlayOnShow);
427
428
  this._overlayCtrl.removeEventListener('before-show', this.__overlayBeforeShow);
428
429
  this._overlayCtrl.removeEventListener('hide', this.__overlayOnHide);
@@ -434,11 +435,12 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
434
435
  * @protected
435
436
  */
436
437
  async _alignInvokerWidth() {
438
+ await this.updateComplete;
439
+
437
440
  if (!this._overlayCtrl?.content) {
438
441
  return;
439
442
  }
440
443
 
441
- await this.updateComplete;
442
444
  const initContentDisplay = this._overlayCtrl.content.style.display;
443
445
  const initContentMinWidth = this._overlayCtrl.contentWrapperNode.style.minWidth;
444
446
  const initContentWidth = this._overlayCtrl.contentWrapperNode.style.width;
@@ -7,6 +7,7 @@ import '@lion/ui/define/lion-select-rich.js';
7
7
  import '@lion/ui/define/lion-listbox.js';
8
8
  import '@lion/ui/define/lion-option.js';
9
9
  import { LitElement } from 'lit';
10
+ import sinon from 'sinon';
10
11
  import {
11
12
  fixture as _fixture,
12
13
  unsafeStatic,
@@ -711,6 +712,49 @@ describe('lion-select-rich', () => {
711
712
  });
712
713
  });
713
714
 
715
+ describe('Teardown', () => {
716
+ it('correctly removes event listeners when disconnected from dom', async () => {
717
+ const el = await fixture(html`
718
+ <lion-select-rich label="age">
719
+ <lion-option .choiceValue=${10}>Item 1</lion-option>
720
+ <lion-option .choiceValue=${20}>Item 2</lion-option>
721
+ </lion-select-rich>
722
+ `);
723
+
724
+ const { _overlayCtrl } = getSelectRichMembers(el);
725
+ const eventRemoveSpy = sinon.spy(_overlayCtrl, 'removeEventListener');
726
+
727
+ el.parentNode?.removeChild(el);
728
+
729
+ expect(eventRemoveSpy.callCount).to.equal(0);
730
+
731
+ await el.updateComplete;
732
+ expect(eventRemoveSpy.callCount).to.equal(0);
733
+
734
+ await el.updateComplete;
735
+
736
+ const hasOneInstanceFor = (
737
+ /** @type {string} */ eventName,
738
+ /** @type {any[][]} */ eventsToSearch,
739
+ /** @type {string} */ methodToRemove,
740
+ ) =>
741
+ Boolean(
742
+ eventsToSearch.filter(
743
+ ([eventToSearch, methodToSearch]) =>
744
+ eventToSearch === eventName && methodToSearch === methodToRemove,
745
+ ).length,
746
+ );
747
+
748
+ // @ts-expect-error [allow-private] in tests
749
+ expect(hasOneInstanceFor('show', eventRemoveSpy.args, el.__overlayOnShow)).to.be.true;
750
+ // @ts-expect-error [allow-private] in tests
751
+ expect(hasOneInstanceFor('before-show', eventRemoveSpy.args, el.__overlayBeforeShow)).to.be
752
+ .true;
753
+ // @ts-expect-error [allow-private] in tests
754
+ expect(hasOneInstanceFor('hide', eventRemoveSpy.args, el.__overlayOnHide)).to.be.true;
755
+ });
756
+ });
757
+
714
758
  describe('Subclassers', () => {
715
759
  it('allows to override the type of overlay', async () => {
716
760
  const mySelectTagString = defineCE(