@lion/ui 0.21.1-menu-system.0 → 0.22.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 (185) hide show
  1. package/components/button/src/LionButton.js +0 -4
  2. package/components/form/test/lion-form.test.js +4 -3
  3. package/components/form-core/src/form-group/FormGroupMixin.js +37 -0
  4. package/components/form-core/test-suites/form-group/FormGroupMixin.suite.js +25 -0
  5. package/components/input-amount-dropdown/src/LionInputAmountDropdown.js +2 -2
  6. package/components/input-amount-dropdown/test/lion-input-amount-dropdown-integrations.test.js +12 -3
  7. package/components/input-amount-dropdown/test-suites/LionInputAmountDropdown.suite.js +10 -3
  8. package/components/overlays/src/OverlayController.js +111 -296
  9. package/components/overlays/src/OverlayMixin.js +17 -76
  10. package/components/overlays/src/utils/adopt-styles.js +1 -1
  11. package/components/overlays/src/utils/is-equal-config.js +6 -15
  12. package/components/overlays/test/OverlayController.test.js +24 -144
  13. package/components/overlays/test-suites/OverlayMixin.suite.js +8 -8
  14. package/components/overlays/types/ArrowMixinTypes.ts +3 -1
  15. package/components/overlays/types/OverlayConfig.ts +5 -20
  16. package/components/overlays/types/OverlayMixinTypes.ts +0 -2
  17. package/custom-elements.json +6260 -13587
  18. package/dist-types/components/button/src/LionButton.d.ts +0 -1
  19. package/dist-types/components/button/src/LionButton.d.ts.map +1 -1
  20. package/dist-types/components/combobox/src/LionCombobox.d.ts +1 -1
  21. package/dist-types/components/dialog/src/LionDialog.d.ts +2 -6
  22. package/dist-types/components/dialog/src/LionDialog.d.ts.map +1 -1
  23. package/dist-types/components/form-core/src/form-group/FormGroupMixin.d.ts +1 -0
  24. package/dist-types/components/form-core/src/form-group/FormGroupMixin.d.ts.map +1 -1
  25. package/dist-types/components/form-core/test-suites/form-group/FormGroupMixin.suite.d.ts.map +1 -1
  26. package/dist-types/components/input-amount-dropdown/test-suites/LionInputAmountDropdown.suite.d.ts.map +1 -1
  27. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +1 -1
  28. package/dist-types/components/overlays/src/OverlayController.d.ts +5 -22
  29. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  30. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  31. package/dist-types/components/overlays/src/utils/is-equal-config.d.ts.map +1 -1
  32. package/dist-types/components/overlays/types/ArrowMixinTypes.d.ts.map +1 -1
  33. package/dist-types/components/overlays/types/OverlayConfig.d.ts +2 -12
  34. package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
  35. package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts +0 -1
  36. package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts.map +1 -1
  37. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +2 -6
  38. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  39. package/dist-types/components/tooltip/src/LionTooltip.d.ts +1 -1
  40. package/dist-types/exports/overlays.d.ts +0 -6
  41. package/dist-types/exports/overlays.d.ts.map +1 -1
  42. package/dist-types/tsconfig.tsbuildinfo +1 -1
  43. package/docs/components/checkbox-group/overview.md +2 -2
  44. package/docs/components/collapsible/overview.md +1 -1
  45. package/docs/components/combobox/api-table.md +39 -53
  46. package/docs/components/combobox/overview.md +1 -1
  47. package/docs/components/combobox/use-cases.md +1 -1
  48. package/docs/components/dialog/api-table.md +6 -32
  49. package/docs/components/dialog/overview.md +1 -1
  50. package/docs/components/fieldset/overview.md +5 -5
  51. package/docs/components/form/overview.md +3 -3
  52. package/docs/components/form/use-cases.md +2 -2
  53. package/docs/components/icon/use-cases.md +1 -1
  54. package/docs/components/input/overview.md +1 -1
  55. package/docs/components/input-amount/overview.md +3 -3
  56. package/docs/components/input-date/overview.md +3 -3
  57. package/docs/components/input-datepicker/api-table.md +45 -63
  58. package/docs/components/input-datepicker/overview.md +5 -5
  59. package/docs/components/input-email/overview.md +2 -2
  60. package/docs/components/input-file/overview.md +1 -1
  61. package/docs/components/input-iban/overview.md +2 -2
  62. package/docs/components/input-range/overview.md +2 -2
  63. package/docs/components/input-stepper/overview.md +1 -1
  64. package/docs/components/input-tel/overview.md +1 -1
  65. package/docs/components/input-tel/use-cases.md +6 -6
  66. package/docs/components/input-tel-dropdown/overview.md +1 -1
  67. package/docs/components/listbox/use-cases.md +1 -1
  68. package/docs/components/radio-group/overview.md +3 -3
  69. package/docs/components/select/use-cases.md +1 -1
  70. package/docs/components/select-rich/api-table.md +33 -50
  71. package/docs/components/select-rich/use-cases.md +2 -2
  72. package/docs/components/tabs/examples.md +1 -1
  73. package/docs/components/tooltip/api-table.md +7 -33
  74. package/docs/fundamentals/index.md +4 -4
  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/api-table.md +43 -46
  86. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  87. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  88. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  89. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  90. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  91. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  92. package/docs/guides/how-to/create-a-form.md +4 -4
  93. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  94. package/docs/guides/how-to/index.md +1 -1
  95. package/docs/guides/index.md +8 -8
  96. package/docs/guides/principles/index.md +5 -5
  97. package/docs/guides/principles/styling.md +1 -1
  98. package/exports/overlays.js +0 -6
  99. package/overlays.js +0 -6
  100. package/package.json +6 -2
  101. package/vscode.css-custom-data.json +5 -0
  102. package/vscode.html-custom-data.json +789 -0
  103. package/web-types.json +2016 -0
  104. package/components/menu/CHANGELOG.md +0 -37
  105. package/components/menu/README.md +0 -334
  106. package/components/menu/index.js +0 -2
  107. package/components/menu/lion-menu.js +0 -3
  108. package/components/menu/package.json +0 -46
  109. package/components/menu/requirements.temp.md +0 -12
  110. package/components/menu/src/InteractiveListMixin.js +0 -840
  111. package/components/menu/src/LionMenu.js +0 -421
  112. package/components/menu/src/MoreButtonMenuMixin.js +0 -278
  113. package/components/menu/src/MultiLevelListMixin.js +0 -305
  114. package/components/menu/src/utils/isInView.js +0 -26
  115. package/components/menu/src/utils/listItemInteractions.js +0 -124
  116. package/components/menu/test/InteractiveListMixin.test.js +0 -3
  117. package/components/menu/test/MoreButtonMenuMixin.test.js +0 -4
  118. package/components/menu/test/MultiLevelListMixin.test.js +0 -3
  119. package/components/menu/test/lion-menu-interactions.test.js +0 -3
  120. package/components/menu/test/lion-menu.test.js +0 -14
  121. package/components/menu/test-helpers/mimicKeyPress.js +0 -9
  122. package/components/menu/test-suites/InteractiveListMixin.suite.js +0 -454
  123. package/components/menu/test-suites/LionMenu.suite.js +0 -114
  124. package/components/menu/test-suites/LionMenuInteractions.suite.js +0 -583
  125. package/components/menu/test-suites/MoreButtonMenuMixin.suite.js +0 -372
  126. package/components/menu/test-suites/MultiLevelListMixin.suite.js +0 -303
  127. package/components/menu/types/InteractiveListMixinTypes.d.ts +0 -84
  128. package/components/overlays/NEXT.md +0 -49
  129. package/dist-types/components/menu/index.d.ts +0 -3
  130. package/dist-types/components/menu/index.d.ts.map +0 -1
  131. package/dist-types/components/menu/lion-menu.d.ts +0 -2
  132. package/dist-types/components/menu/lion-menu.d.ts.map +0 -1
  133. package/dist-types/components/menu/src/MoreButtonMenuMixin.d.ts +0 -39
  134. package/dist-types/components/menu/src/MoreButtonMenuMixin.d.ts.map +0 -1
  135. package/dist-types/components/menu/src/utils/isInView.d.ts +0 -9
  136. package/dist-types/components/menu/src/utils/isInView.d.ts.map +0 -1
  137. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts +0 -48
  138. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts.map +0 -1
  139. package/dist-types/components/menu/test/InteractiveListMixin.test.d.ts +0 -2
  140. package/dist-types/components/menu/test/InteractiveListMixin.test.d.ts.map +0 -1
  141. package/dist-types/components/menu/test/MoreButtonMenuMixin.test.d.ts +0 -2
  142. package/dist-types/components/menu/test/MoreButtonMenuMixin.test.d.ts.map +0 -1
  143. package/dist-types/components/menu/test/MultiLevelListMixin.test.d.ts +0 -2
  144. package/dist-types/components/menu/test/MultiLevelListMixin.test.d.ts.map +0 -1
  145. package/dist-types/components/menu/test/lion-menu-interactions.test.d.ts +0 -2
  146. package/dist-types/components/menu/test/lion-menu-interactions.test.d.ts.map +0 -1
  147. package/dist-types/components/menu/test/lion-menu.test.d.ts +0 -2
  148. package/dist-types/components/menu/test/lion-menu.test.d.ts.map +0 -1
  149. package/dist-types/components/menu/test-helpers/mimicKeyPress.d.ts +0 -7
  150. package/dist-types/components/menu/test-helpers/mimicKeyPress.d.ts.map +0 -1
  151. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts +0 -11
  152. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts.map +0 -1
  153. package/dist-types/components/menu/test-suites/LionMenu.suite.d.ts +0 -9
  154. package/dist-types/components/menu/test-suites/LionMenu.suite.d.ts.map +0 -1
  155. package/dist-types/components/menu/test-suites/LionMenuInteractions.suite.d.ts +0 -5
  156. package/dist-types/components/menu/test-suites/LionMenuInteractions.suite.d.ts.map +0 -1
  157. package/dist-types/components/menu/test-suites/MoreButtonMenuMixin.suite.d.ts +0 -11
  158. package/dist-types/components/menu/test-suites/MoreButtonMenuMixin.suite.d.ts.map +0 -1
  159. package/dist-types/components/menu/test-suites/MultiLevelListMixin.suite.d.ts +0 -11
  160. package/dist-types/components/menu/test-suites/MultiLevelListMixin.suite.d.ts.map +0 -1
  161. package/dist-types/exports/define/lion-item.d.ts +0 -2
  162. package/dist-types/exports/define/lion-item.d.ts.map +0 -1
  163. package/dist-types/exports/define/lion-menu.d.ts +0 -2
  164. package/dist-types/exports/define/lion-menu.d.ts.map +0 -1
  165. package/dist-types/exports/menu-test-suites.d.ts +0 -4
  166. package/dist-types/exports/menu-test-suites.d.ts.map +0 -1
  167. package/dist-types/exports/menu.d.ts +0 -3
  168. package/dist-types/exports/menu.d.ts.map +0 -1
  169. package/docs/components/menu/api-table.md +0 -121
  170. package/docs/components/menu/applyMenuDemoStyles.js +0 -173
  171. package/docs/components/menu/extensions.md +0 -262
  172. package/docs/components/menu/index.md +0 -13
  173. package/docs/components/menu/overview.md +0 -422
  174. package/docs/components/menu/src/DemoNavigationBar.js +0 -441
  175. package/docs/components/menu/src/DemoSpinButton.js +0 -215
  176. package/docs/components/menu/src/DemoToolbar.js +0 -101
  177. package/docs/components/menu/src/DemoTree.js +0 -55
  178. package/docs/components/menu/src/navigation-bar-types.js +0 -41
  179. package/docs/components/menu/use-cases.md +0 -74
  180. package/exports/define/lion-item.js +0 -3
  181. package/exports/define/lion-menu.js +0 -3
  182. package/exports/menu-test-suites.js +0 -4
  183. package/exports/menu.js +0 -3
  184. package/menu-test-suites.js +0 -4
  185. package/menu.js +0 -3
@@ -3,7 +3,6 @@ import { deepContains } from './utils/deep-contains.js';
3
3
  import { overlayShadowDomStyle } from './overlayShadowDomStyle.js';
4
4
  import { _adoptStyleUtils } from './utils/adopt-styles.js';
5
5
  import { getFocusableElements } from './utils/get-focusable-elements.js';
6
- import { isEqualConfig } from './utils/is-equal-config.js';
7
6
 
8
7
  /**
9
8
  * @typedef {import('@lion/ui/types/overlays.js').OverlayPhase} OverlayPhase
@@ -33,37 +32,27 @@ import { isEqualConfig } from './utils/is-equal-config.js';
33
32
  * `<slot name="my-content">` belonging to `<div slot="content"/>` will be wrapped with wrappingDialogNodeL1 and contentWrapperNodeL2
34
33
  * inside shadow dom. With the help of temp markers, `<slot name="my-content">`'s original position will be respected.
35
34
  *
36
- * @param {{
37
- * wrappingDialogNodeL1: HTMLDialogElement|HTMLDivElement;
38
- * contentWrapperNodeL2:Element;
39
- * contentNodeL3: Element;
40
- * }} opts
35
+ * @param {{ wrappingDialogNodeL1:HTMLDialogElement|HTMLDivElement; contentWrapperNodeL2:Element; contentNodeL3: Element }} opts
41
36
  */
42
37
  function rearrangeNodes({ wrappingDialogNodeL1, contentWrapperNodeL2, contentNodeL3 }) {
43
- // if contentWrapperNode is provided by the user,
44
- // we assume it lives in shadow dom around a slot.
45
- const hasLegacyMethodOfProvidingWrapperNode = Boolean(contentWrapperNodeL2.isConnected);
46
- // We could be initialized via a directive (in offline dom). It's important that we know about the parents,
47
- // as we cannot deal with single content nodes
48
- const hasContentNodeAttachmentPoints = Boolean(contentNodeL3.parentNode);
49
-
50
- if (!hasLegacyMethodOfProvidingWrapperNode && !hasContentNodeAttachmentPoints) {
38
+ if (!(contentWrapperNodeL2.isConnected || contentNodeL3.isConnected)) {
51
39
  throw new Error(
52
- '[OverlayController] Could not find a render target, makes sure contentNode has a parent element (or contentWrapperNode is connected)',
40
+ '[OverlayController] Could not find a render target, since the provided contentNode is not connected to the DOM. Make sure that it is connected, e.g. by doing "document.body.appendChild(contentNode)", before passing it on.',
53
41
  );
54
42
  }
55
43
 
56
- /** @type {Node} */
57
44
  let parentElement;
58
- const tempMarker = document.createComment('overlay-insertion-marker');
45
+ const tempMarker = document.createComment('tempMarker');
59
46
 
60
- if (hasLegacyMethodOfProvidingWrapperNode) {
47
+ if (contentWrapperNodeL2.isConnected) {
61
48
  // This is the case when contentWrapperNode (living in shadow dom, wrapping <slot name="my-content-outlet">) is already provided via controller.
62
49
  parentElement = contentWrapperNodeL2.parentElement || contentWrapperNodeL2.getRootNode();
63
50
  parentElement.insertBefore(tempMarker, contentWrapperNodeL2);
64
51
  // Wrap...
65
52
  wrappingDialogNodeL1.appendChild(contentWrapperNodeL2);
66
- } else {
53
+ }
54
+ // if contentNodeL3.isConnected
55
+ else {
67
56
  const contentIsProjected = contentNodeL3.assignedSlot;
68
57
  if (contentIsProjected) {
69
58
  parentElement =
@@ -104,22 +93,7 @@ function rearrangeNodes({ wrappingDialogNodeL1, contentWrapperNodeL2, contentNod
104
93
  * ```
105
94
  */
106
95
  parentElement.insertBefore(wrappingDialogNodeL1, tempMarker);
107
-
108
- return function cleanup() {
109
- if (hasLegacyMethodOfProvidingWrapperNode) {
110
- parentElement?.insertBefore(contentWrapperNodeL2, tempMarker);
111
- } else {
112
- parentElement?.insertBefore(contentNodeL3, tempMarker);
113
- if (parentElement.contains(contentWrapperNodeL2)) {
114
- contentWrapperNodeL2.remove();
115
- }
116
- }
117
- parentElement?.removeChild(tempMarker);
118
-
119
- if (parentElement.contains(wrappingDialogNodeL1)) {
120
- parentElement?.removeChild(wrappingDialogNodeL1);
121
- }
122
- };
96
+ parentElement?.removeChild(tempMarker);
123
97
  }
124
98
 
125
99
  /**
@@ -132,21 +106,6 @@ async function preloadPopper() {
132
106
 
133
107
  const childDialogsClosedInEventLoopWeakmap = new WeakMap();
134
108
 
135
- /**
136
- * @param {HTMLElement | null} el
137
- * @param {string} selector
138
- * @returns {HTMLElement | null}
139
- */
140
- function deepClosest(el, selector) {
141
- // @ts-ignore - comparing HTMLElement to document/window for safety
142
- return (
143
- // @ts-ignore - type comparison check
144
- (el && el !== document && el !== window && el.closest(selector)) ||
145
- // @ts-ignore - host property on ShadowRoot
146
- deepClosest(el.getRootNode().host, selector)
147
- );
148
- }
149
-
150
109
  /**
151
110
  * OverlayController is the fundament for every single type of overlay. With the right
152
111
  * configuration, it can be used to build (modal) dialogs, tooltips, dropdowns, popovers,
@@ -277,20 +236,6 @@ export class OverlayController extends EventTarget {
277
236
  placement: 'center',
278
237
  },
279
238
  zIndex: 9999,
280
- isActivated: true,
281
- elementToFocusOnShow: false,
282
- // This means that content will have visually-hidden / sr-only styles. This is handy for:
283
- // - hidden menus that should be "indexable" by screen readers (like the Links list in VO: https://support.apple.com/en-gb/guide/voiceover/mchlp2719/mac)
284
- // - "more" menus that put content in a dropdown that do not fit in the current row
285
- // - menus that should generally open on tab.
286
- // @ts-ignore - hideVisually is an extension property
287
- hideVisually: false,
288
- requireConnectedNodes: true,
289
-
290
- // hides children overlays when parent is closed...
291
- syncChildrenCloseState: false,
292
- // // In next major, we remove this prop. Now we disable it for backwards compatibility and enable it in the places we need it internally
293
- // _shouldTeardownDomStructure: false,
294
239
  };
295
240
 
296
241
  this._contentId = `overlay-content--${Math.random().toString(36).slice(2, 10)}`;
@@ -304,8 +249,6 @@ export class OverlayController extends EventTarget {
304
249
  this.__escKeyHandlerCalled = false;
305
250
  }
306
251
 
307
- __hasSetup = false;
308
-
309
252
  /**
310
253
  * The invokerNode
311
254
  * @type {HTMLElement | undefined}
@@ -324,7 +267,7 @@ export class OverlayController extends EventTarget {
324
267
 
325
268
  /**
326
269
  * Determines the connection point in DOM (body vs next to invoker).
327
- * @type {'global' | 'local' | 'custom' | undefined}
270
+ * @type {'global' | 'local' | undefined}
328
271
  */
329
272
  get placementMode() {
330
273
  return this.config?.placementMode;
@@ -549,14 +492,17 @@ export class OverlayController extends EventTarget {
549
492
  * @param { OverlayConfig } cfgToAdd
550
493
  */
551
494
  updateConfig(cfgToAdd) {
495
+ // Teardown all previous configs
496
+ this.teardown();
497
+
552
498
  /**
553
499
  * @type {OverlayConfig}
554
500
  * @private
555
501
  */
556
- const prevConfig = this.config;
502
+ this.__prevConfig = this.config;
557
503
 
558
504
  /** @type {OverlayConfig} */
559
- const newConfig = {
505
+ this.config = {
560
506
  ...this._defaultConfig, // our basic ingredients
561
507
  ...this.__sharedConfig, // the initial configured overlayController
562
508
  ...cfgToAdd, // the added config
@@ -572,18 +518,12 @@ export class OverlayController extends EventTarget {
572
518
  },
573
519
  };
574
520
 
575
- const shouldUpdate = !this.__hasSetup || !isEqualConfig(prevConfig, newConfig);
576
- if (!shouldUpdate) return;
577
- // Teardown all previous configs
578
-
579
- this.teardown();
580
-
581
- this.config = newConfig;
582
-
583
521
  /** @private */
584
522
  this.__validateConfiguration(this.config);
585
523
  /** @protected */
586
524
  this._init();
525
+ /** @private */
526
+ this.__elementToFocusAfterHide = undefined;
587
527
 
588
528
  if (!this.#isRegisteredOnManager()) {
589
529
  this.manager.add(this);
@@ -605,7 +545,7 @@ export class OverlayController extends EventTarget {
605
545
  '[OverlayController] You need to provide a .placementMode ("global"|"local")',
606
546
  );
607
547
  }
608
- if (!['global', 'local', 'custom'].includes(newConfig.placementMode)) {
548
+ if (!['global', 'local'].includes(newConfig.placementMode)) {
609
549
  throw new Error(
610
550
  `[OverlayController] "${newConfig.placementMode}" is not a valid .placementMode, use ("global"|"local")`,
611
551
  );
@@ -624,23 +564,14 @@ export class OverlayController extends EventTarget {
624
564
  * @protected
625
565
  */
626
566
  _init() {
627
- if (!this.config.isActivated) return;
628
-
629
- // TODO: should not be behind a flag when we are fully tearing down...
630
- // TODO 2: When we move away from dialog (use popover to paint to top layer)
631
- // and popper.js, (use popover and anchor positioning and popperjs as fallback, but we can put styles on contentNode),
632
- // we dont need to create complex wrappers
633
- // if (!this.__contentHasBeenInitialized) {
634
- this.__initContentDomStructure();
635
- // this.__contentHasBeenInitialized = true;
636
- // }
637
- this.__initVisibility();
567
+ if (!this.__contentHasBeenInitialized) {
568
+ this.__initContentDomStructure();
569
+ this.__contentHasBeenInitialized = true;
570
+ }
638
571
 
639
572
  // Reset all positioning styles (local, c.q. Popper) and classes (global)
640
- if (this.contentWrapperNode !== this.contentNode) {
641
- this.contentWrapperNode.removeAttribute('style');
642
- this.contentWrapperNode.removeAttribute('class');
643
- }
573
+ this.contentWrapperNode.removeAttribute('style');
574
+ this.contentWrapperNode.removeAttribute('class');
644
575
 
645
576
  if (this.placementMode === 'local') {
646
577
  // Lazily load Popper as soon as the first local overlay is used...
@@ -650,27 +581,8 @@ export class OverlayController extends EventTarget {
650
581
  }
651
582
  this.__handleOverlayStyles({ phase: 'init' });
652
583
  this._handleFeatures({ phase: 'init' });
653
-
654
- /** @private */
655
- this.__elementToFocusAfterHide = undefined;
656
-
657
- this.contentNode.dataset.content = '';
658
-
659
- // Use event delegation to listen for clicks on close buttons...
660
- this.contentNode.addEventListener('click', this.#hideOnCloseButtonClick);
661
-
662
- this.__hasSetup = true;
663
584
  }
664
585
 
665
- #hideOnCloseButtonClick = (/** @type {{ target: any; }} */ ev) => {
666
- const isOurCloseButton =
667
- ev.target.hasAttribute('data-close') &&
668
- deepClosest(ev.target, '[data-content]') === this.contentNode;
669
- if (!isOurCloseButton) return;
670
-
671
- this.hide();
672
- };
673
-
674
586
  /**
675
587
  * @param {{ phase: OverlayPhase }} config
676
588
  * @private
@@ -711,108 +623,84 @@ export class OverlayController extends EventTarget {
711
623
  * @private
712
624
  */
713
625
  __initContentDomStructure() {
714
- if (this.placementMode !== 'custom') {
715
- const wrappingDialogElement = document.createElement('dialog');
716
- // We use a dialog for its visual capabilities: it renders to the top layer.
717
- // A11y will depend on the type of overlay and is arranged on contentNode level.
718
- // Also see: https://www.scottohara.me/blog/2019/03/05/open-dialog.html
719
- //
720
- // The role="dialog" is set on the contentNode (or another role), so role="none"
721
- // is valid here, although AXE complains about this setup.
722
- // For now we need to add `ignoredRules: ['aria-allowed-role']` in your AXE tests.
723
- // see: https://lion.js.org/fundamentals/systems/overlays/rationale/#considerations
724
- wrappingDialogElement.setAttribute('role', 'none');
725
- wrappingDialogElement.setAttribute('data-overlay-outer-wrapper', '');
726
- // N.B. position: fixed is needed to escape out of 'overflow: hidden'
727
- // We give a high z-index for non-modal dialogs, so that we at least win from all siblings of our
728
- // parent stacking context
729
- // padding reset so we don't get a weird dialog visual square showing up
730
- wrappingDialogElement.style.cssText = `display:none; z-index: ${this.config.zIndex}; padding: 0;`;
731
- // @ts-ignore - 'custom' is a valid extension of placementMode
732
- if (this.config.placementMode === 'custom') {
733
- // The user should have full freedom to control the content node, so its wrapper nodes should remain neutral.
734
- wrappingDialogElement.style.cssText += `position: static;`;
735
- }
736
- this.__wrappingDialogNode = wrappingDialogElement;
626
+ const wrappingDialogElement = document.createElement('dialog');
627
+ // We use a dialog for its visual capabilities: it renders to the top layer.
628
+ // A11y will depend on the type of overlay and is arranged on contentNode level.
629
+ // Also see: https://www.scottohara.me/blog/2019/03/05/open-dialog.html
630
+ //
631
+ // The role="dialog" is set on the contentNode (or another role), so role="none"
632
+ // is valid here, although AXE complains about this setup.
633
+ // For now we need to add `ignoredRules: ['aria-allowed-role']` in your AXE tests.
634
+ // see: https://lion.js.org/fundamentals/systems/overlays/rationale/#considerations
635
+ wrappingDialogElement.setAttribute('role', 'none');
636
+ wrappingDialogElement.setAttribute('data-overlay-outer-wrapper', '');
637
+ // N.B. position: fixed is needed to escape out of 'overflow: hidden'
638
+ // We give a high z-index for non-modal dialogs, so that we at least win from all siblings of our
639
+ // parent stacking context
640
+ // padding reset so we don't get a weird dialog visual square showing up
641
+ wrappingDialogElement.style.cssText = `display:none; z-index: ${this.config.zIndex}; padding: 0;`;
642
+ this.__wrappingDialogNode = wrappingDialogElement;
737
643
 
738
- /**
739
- * Based on the configuration of the developer, multiple scenarios are accounted for
740
- * A. We already have a contentWrapperNode ()
741
- */
742
- if (!this.config?.contentWrapperNode) {
743
- this.__contentWrapperNode = document.createElement('div');
744
- }
745
- this.contentWrapperNode.setAttribute('data-id', 'content-wrapper');
746
- // 'hack' that makes sure popperjs (that is applied one level lower) works correctly in deeply nested shadow roots
747
- this.contentWrapperNode.style.transform = 'translateZ(0px)';
748
-
749
- this.__rearrangeNodesCleanup = rearrangeNodes({
750
- wrappingDialogNodeL1: wrappingDialogElement,
751
- contentWrapperNodeL2: this.contentWrapperNode,
752
- contentNodeL3: this.contentNode,
753
- // requireConnectedNodes: Boolean(this.config.requireConnectedNodes),
754
- });
755
- wrappingDialogElement.open = true;
644
+ /**
645
+ * Based on the configuration of the developer, multiple scenarios are accounted for
646
+ * A. We already have a contentWrapperNode ()
647
+ */
648
+ if (!this.config?.contentWrapperNode) {
649
+ this.__contentWrapperNode = document.createElement('div');
650
+ }
651
+ this.contentWrapperNode.setAttribute('data-id', 'content-wrapper');
652
+ // 'hack' that makes sure popperjs (that is applied one level lower) works correctly in deeply nested shadow roots
653
+ this.contentWrapperNode.style.transform = 'translateZ(0px)';
756
654
 
757
- if (this.isTooltip) {
758
- // needed to prevent tooltip getting focus in Safari and Firefox
759
- wrappingDialogElement.setAttribute('tabindex', '-1');
760
- }
761
- this.contentWrapperNode.style.zIndex = '1';
762
- if (getComputedStyle(this.contentNode).position === 'absolute') {
763
- // Having a _contWrapperNode and a contentNode with 'position:absolute' results in
764
- // computed height of 0...
765
- this.contentNode.style.position = 'static';
766
- }
655
+ rearrangeNodes({
656
+ wrappingDialogNodeL1: wrappingDialogElement,
657
+ contentWrapperNodeL2: this.contentWrapperNode,
658
+ contentNodeL3: this.contentNode,
659
+ });
660
+ wrappingDialogElement.open = true;
767
661
 
768
- // Here we prevent any interference of the native <dialog> element with the keyboard behavior
769
- // as defined by the OverlayController. This is needed until we can configure `closedby="none"`
770
- // on the native dialog for all browsers: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog#closedby
771
- const hasClosedBySupport = HTMLDialogElement && 'closedBy' in HTMLDialogElement.prototype;
772
- if (hasClosedBySupport) {
773
- // @ts-ignore - closedBy is a newer property not in all TypeScript versions
774
- wrappingDialogElement.closedBy = 'none';
775
- } else {
776
- wrappingDialogElement.addEventListener(
777
- 'keydown',
778
- (/** @type {* & KeyboardEvent} */ event) => {
779
- if (event.key === 'Escape') {
780
- event.preventDefault();
781
- }
782
- },
783
- );
784
- wrappingDialogElement.addEventListener(
785
- 'keyup',
786
- (/** @type {* & KeyboardEvent} */ event) => {
787
- if (event.key === 'Escape') {
788
- event.preventDefault();
789
- }
790
- },
791
- );
792
- wrappingDialogElement.addEventListener('cancel', event => {
793
- event.stopPropagation();
794
- });
795
- wrappingDialogElement.addEventListener('close', event => {
796
- event.stopPropagation();
797
- });
798
- }
799
- } else {
800
- // quick hack to make none-popper/global flows work...
801
- this.__contentWrapperNode = this.contentNode;
802
- this.__wrappingDialogNode = this.contentNode;
662
+ if (this.isTooltip) {
663
+ // needed to prevent tooltip getting focus in Safari and Firefox
664
+ wrappingDialogElement.setAttribute('tabindex', '-1');
803
665
  }
804
- }
805
666
 
806
- __initVisibility() {
807
- // TODO: allow defaultOpen?
808
- /** @type {HTMLDialogElement} */ (this.__wrappingDialogNode).style.display = 'none';
809
- }
667
+ this.__wrappingDialogNode.style.display = 'none';
668
+ this.contentWrapperNode.style.zIndex = '1';
810
669
 
811
- // Clean up the DOM structure, leaving it as we found it.
812
- // TODO: __teardownContentDomStructure method... use Resettable (rename ro Restorable) found in VisibilitToggleCtrl
813
- __teardownVisibility() {
814
- // TODO: full structure. For now we just reset display prop...
815
- /** @type {HTMLDialogElement} */ (this.__wrappingDialogNode).style.display = '';
670
+ if (getComputedStyle(this.contentNode).position === 'absolute') {
671
+ // Having a _contWrapperNode and a contentNode with 'position:absolute' results in
672
+ // computed height of 0...
673
+ this.contentNode.style.position = 'static';
674
+ }
675
+
676
+ // Here we prevent any interference of the native <dialog> element with the keyboard behavior
677
+ // as defined by the OverlayController. This is needed until we can configure `closedby="none"`
678
+ // on the native dialog for all browsers: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog#closedby
679
+ const hasClosedBySupport = HTMLDialogElement && 'closedBy' in HTMLDialogElement.prototype;
680
+ if (hasClosedBySupport) {
681
+ // @ts-ignore [closedBy is feature-detected above]
682
+ wrappingDialogElement.closedBy = 'none';
683
+ } else {
684
+ wrappingDialogElement.addEventListener(
685
+ 'keydown',
686
+ (/** @type {* & KeyboardEvent} */ event) => {
687
+ if (event.key === 'Escape') {
688
+ event.preventDefault();
689
+ }
690
+ },
691
+ );
692
+ wrappingDialogElement.addEventListener('keyup', (/** @type {* & KeyboardEvent} */ event) => {
693
+ if (event.key === 'Escape') {
694
+ event.preventDefault();
695
+ }
696
+ });
697
+ wrappingDialogElement.addEventListener('cancel', event => {
698
+ event.stopPropagation();
699
+ });
700
+ wrappingDialogElement.addEventListener('close', event => {
701
+ event.stopPropagation();
702
+ });
703
+ }
816
704
  }
817
705
 
818
706
  /**
@@ -906,7 +794,7 @@ export class OverlayController extends EventTarget {
906
794
  }
907
795
 
908
796
  get isShown() {
909
- return Boolean(this.__wrappingDialogNode && this.__wrappingDialogNode.style.display !== 'none');
797
+ return Boolean(this.__wrappingDialogNode?.style.display !== 'none');
910
798
  }
911
799
 
912
800
  /**
@@ -924,13 +812,6 @@ export class OverlayController extends EventTarget {
924
812
  this._showResolve = resolve;
925
813
  });
926
814
 
927
- // Teardown() removes the content dom structure we created (contentWrapperNode +
928
- // wrapping <dialog>). Make sure it exists again before the manager (which sets
929
- // our elevation on the wrapping node) and the show logic below touch it.
930
- if (!this.__wrappingDialogNode) {
931
- this._init();
932
- }
933
-
934
815
  if (this.manager) {
935
816
  this.manager.show(this);
936
817
  }
@@ -947,7 +828,6 @@ export class OverlayController extends EventTarget {
947
828
  if ('HTMLDialogElement' in window && this.__wrappingDialogNode instanceof HTMLDialogElement) {
948
829
  this.__wrappingDialogNode.open = true;
949
830
  }
950
-
951
831
  // @ts-ignore
952
832
  this.__wrappingDialogNode.style.display = '';
953
833
  this._keepBodySize({ phase: 'before-show' });
@@ -961,14 +841,6 @@ export class OverlayController extends EventTarget {
961
841
  contentNode: this.contentNode,
962
842
  });
963
843
  }
964
-
965
- // a styling/debug hook for open state
966
- this.contentNode.setAttribute('data-open', '');
967
-
968
- if (this.config.elementToFocusOnShow) {
969
- this.contentNode.focus({ preventScroll: true });
970
- }
971
-
972
844
  /** @type {function} */
973
845
  (this._showResolve)();
974
846
  }
@@ -1001,7 +873,6 @@ export class OverlayController extends EventTarget {
1001
873
  await this.__createPopperInstance();
1002
874
  /** @type {import('@popperjs/core').Instance} */ (this._popper).forceUpdate();
1003
875
  }
1004
- // N.B. leave it up to the user if placementMode === 'custom'
1005
876
  }
1006
877
 
1007
878
  /**
@@ -1009,7 +880,9 @@ export class OverlayController extends EventTarget {
1009
880
  * @protected
1010
881
  */
1011
882
  _keepBodySize({ phase }) {
1012
- if (!this.preventsScroll) return;
883
+ if (!this.preventsScroll) {
884
+ return;
885
+ }
1013
886
 
1014
887
  this.manager.requestToKeepBodySize({ phase });
1015
888
  }
@@ -1019,9 +892,6 @@ export class OverlayController extends EventTarget {
1019
892
  * @event hide right after the overlay is hidden
1020
893
  */
1021
894
  async hide() {
1022
- // Function like a no-op for dynamic edge cases...
1023
- if (!this.config.isActivated) return;
1024
-
1025
895
  this._hideComplete = new Promise(resolve => {
1026
896
  this._hideResolve = resolve;
1027
897
  });
@@ -1054,6 +924,7 @@ export class OverlayController extends EventTarget {
1054
924
  if ('HTMLDialogElement' in window && this.__wrappingDialogNode instanceof HTMLDialogElement) {
1055
925
  this.__wrappingDialogNode.close();
1056
926
  }
927
+
1057
928
  // @ts-ignore
1058
929
  this.__wrappingDialogNode.style.display = 'none';
1059
930
  this._handleFeatures({ phase: 'hide' });
@@ -1062,8 +933,6 @@ export class OverlayController extends EventTarget {
1062
933
  this._restoreFocus();
1063
934
  }
1064
935
  /** @type {function} */ (this._hideResolve)();
1065
-
1066
- this.contentNode.removeAttribute('data-open');
1067
936
  }
1068
937
 
1069
938
  /**
@@ -1174,53 +1043,23 @@ export class OverlayController extends EventTarget {
1174
1043
  if (this.visibilityTriggerFunction) {
1175
1044
  this._handleVisibilityTriggers({ phase });
1176
1045
  }
1177
- if (this.config.syncChildrenCloseState) {
1178
- this._handleSyncChildrenCloseState({ phase });
1179
- }
1180
- if (this.config.elementToFocusOnShow) {
1181
- this._handleElementToFocusOnShow({ phase });
1182
- }
1183
- }
1184
-
1185
- /**
1186
- * @param {{phase: OverlayPhase}} opts
1187
- * @returns {void}
1188
- */
1189
- _handleElementToFocusOnShow({ phase }) {
1190
- if (phase === 'init') {
1191
- this.contentNode?.setAttribute('tabindex', '-1');
1192
- } else if (phase === 'teardown') {
1193
- // TODO: capture initial attr, use Resettable mechanism of VisibilityToggleCtrl in whole Controller
1194
- this.contentNode?.removeAttribute('tabindex');
1195
- }
1196
- }
1197
-
1198
- /**
1199
- * @param {{phase: OverlayPhase}} opts
1200
- * @returns {void}
1201
- */
1202
- _handleSyncChildrenCloseState({ phase }) {
1203
- if (phase !== 'hide') return;
1204
-
1205
- const visibleChildren = this.manager.shownList.filter(
1206
- ctrl => ctrl !== this && deepContains(this.contentNode, ctrl.contentNode),
1207
- );
1208
- visibleChildren.forEach(ctrl => ctrl.hide());
1209
1046
  }
1210
1047
 
1211
1048
  /**
1212
1049
  * @param {{ phase: OverlayPhase }} config
1213
1050
  */
1214
1051
  _handleVisibilityTriggers({ phase }) {
1215
- if (typeof this.visibilityTriggerFunction !== 'function') return;
1216
-
1217
- // Here we initialize the __visibilityTriggerHandler of our invokerNode. It's important that we ONLY do this on init,
1218
- // otherwise we risk not being able to properly clean up listeners...
1219
- if (phase === 'init') {
1220
- this.__visibilityTriggerHandler = this.visibilityTriggerFunction({ controller: this });
1052
+ if (typeof this.visibilityTriggerFunction === 'function') {
1053
+ if (phase === 'init') {
1054
+ this.__visibilityTriggerHandler = this.visibilityTriggerFunction({
1055
+ phase,
1056
+ controller: this,
1057
+ });
1058
+ }
1059
+ if (this.__visibilityTriggerHandler[phase]) {
1060
+ this.__visibilityTriggerHandler[phase]();
1061
+ }
1221
1062
  }
1222
- // Here we run the appropriate lifecycle, if defined in our handler
1223
- this.__visibilityTriggerHandler[phase]?.();
1224
1063
  }
1225
1064
 
1226
1065
  /**
@@ -1377,9 +1216,7 @@ export class OverlayController extends EventTarget {
1377
1216
  if (phase === 'show') {
1378
1217
  this.#handleShiftKeyPress();
1379
1218
  this.#handleFocusInsideDialog();
1380
- // @ts-ignore - HTMLDialogElement methods
1381
1219
  this.__wrappingDialogNode?.close();
1382
- // @ts-ignore - HTMLDialogElement methods
1383
1220
  this.__wrappingDialogNode?.showModal();
1384
1221
  /**
1385
1222
  * At this moment `#handleFocusInsideDialog` should handle the focus.
@@ -1634,7 +1471,6 @@ export class OverlayController extends EventTarget {
1634
1471
  }
1635
1472
 
1636
1473
  teardown() {
1637
- if (!this.__hasSetup) return;
1638
1474
  this.__handleOverlayStyles({ phase: 'teardown' });
1639
1475
  if (this.isShown) {
1640
1476
  this._keepBodySize({ phase: 'teardown' });
@@ -1643,27 +1479,6 @@ export class OverlayController extends EventTarget {
1643
1479
  if (this.#isRegisteredOnManager()) {
1644
1480
  this.manager.remove(this);
1645
1481
  }
1646
-
1647
- this.contentNode?.removeEventListener('click', this.#hideOnCloseButtonClick);
1648
-
1649
- // if (this.config._shouldTeardownDomStructure) {
1650
- this.__rearrangeNodesCleanup?.();
1651
- this.__rearrangeNodesCleanup = undefined;
1652
- // }
1653
- this.__teardownVisibility();
1654
-
1655
- // When we created the contentWrapperNode/wrapping <dialog> ourselves, restore the
1656
- // original dom structure and drop the (now detached) references, so that a
1657
- // subsequent setup recreates them and `contentWrapperNode`/`content` no longer
1658
- // expose stale nodes.
1659
- if (this.contentWrapperNode !== this.contentNode) {
1660
- this.contentWrapperNode?.removeAttribute('style');
1661
- this.contentWrapperNode?.removeAttribute('class');
1662
- this.__contentWrapperNode = undefined;
1663
- this.__wrappingDialogNode = undefined;
1664
- }
1665
-
1666
- this.__hasSetup = false;
1667
1482
  }
1668
1483
 
1669
1484
  /** @private */