@lion/ui 0.0.8 → 0.0.10

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 (147) hide show
  1. package/components/core/src/SlotMixin.js +2 -1
  2. package/components/dialog/test/lion-dialog.test.js +3 -7
  3. package/components/form-integrations/test/form-group-methods.test.js +6 -4
  4. package/components/form-integrations/test/form-integrations.test.js +10 -5
  5. package/components/form-integrations/test/helpers/umbrella-form.js +10 -0
  6. package/components/form-integrations/test/model-value-consistency.test.js +29 -20
  7. package/components/input-datepicker/src/LionInputDatepicker.js +64 -100
  8. package/components/input-datepicker/src/localizeNamespaceLoader.js +188 -0
  9. package/components/input-datepicker/test/lion-input-datepicker.test.js +15 -20
  10. package/components/input-datepicker/test-helpers/DatepickerInputObject.js +3 -3
  11. package/components/input-tel/src/parsers.js +1 -1
  12. package/components/listbox/test-suites/ListboxMixin.suite.js +3 -3
  13. package/components/localize/test/number/formatNumber.test.js +2 -6
  14. package/components/overlays/src/OverlayController.js +236 -289
  15. package/components/overlays/src/OverlayMixin.js +17 -18
  16. package/components/overlays/src/OverlaysManager.js +28 -53
  17. package/components/overlays/src/overlayDocumentStyle.js +24 -0
  18. package/components/overlays/src/overlayShadowDomStyle.js +138 -0
  19. package/components/overlays/src/singleton.js +3 -1
  20. package/components/overlays/test/ArrowMixin.test.js +0 -1
  21. package/components/overlays/test/OverlayController.test.js +306 -273
  22. package/components/overlays/test/OverlaysManager.test.js +23 -32
  23. package/components/overlays/test/global-positioning.test.js +4 -15
  24. package/components/overlays/test/local-positioning.test.js +65 -38
  25. package/components/overlays/test-suites/OverlayMixin.suite.js +18 -26
  26. package/components/overlays/types/OverlayConfig.ts +32 -11
  27. package/components/overlays/types/OverlayMixinTypes.ts +2 -2
  28. package/components/select-rich/src/LionSelectInvoker.js +2 -3
  29. package/components/select-rich/src/LionSelectRich.js +22 -20
  30. package/components/select-rich/test/lion-select-rich.test.js +5 -7
  31. package/components/validate-messages/src/loadDefaultFeedbackMessages.js +2 -181
  32. package/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.js +194 -0
  33. package/components/validate-messages/test/loadDefaultFeedbackMessages.test.js +1 -18
  34. package/components/validate-messages/test/loadDefaultFeedbackMessagesNoSideEffects.js +49 -0
  35. package/custom-elements.json +370 -362
  36. package/dist-types/components/core/src/SlotMixin.d.ts.map +1 -1
  37. package/dist-types/components/form-integrations/test/helpers/umbrella-form.d.ts +4 -0
  38. package/dist-types/components/form-integrations/test/helpers/umbrella-form.d.ts.map +1 -1
  39. package/dist-types/components/form-integrations/test/model-value-consistency.test.d.ts.map +1 -1
  40. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +3 -3
  41. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
  42. package/dist-types/components/input-datepicker/src/localizeNamespaceLoader.d.ts +4 -0
  43. package/dist-types/components/input-datepicker/src/localizeNamespaceLoader.d.ts.map +1 -0
  44. package/dist-types/components/overlays/src/OverlayController.d.ts +46 -101
  45. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  46. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  47. package/dist-types/components/overlays/src/OverlaysManager.d.ts +0 -7
  48. package/dist-types/components/overlays/src/OverlaysManager.d.ts.map +1 -1
  49. package/dist-types/components/overlays/src/overlayDocumentStyle.d.ts +2 -0
  50. package/dist-types/components/overlays/src/overlayDocumentStyle.d.ts.map +1 -0
  51. package/dist-types/components/overlays/src/overlayShadowDomStyle.d.ts +2 -0
  52. package/dist-types/components/overlays/src/overlayShadowDomStyle.d.ts.map +1 -0
  53. package/dist-types/components/overlays/src/singleton.d.ts +2 -1
  54. package/dist-types/components/overlays/src/singleton.d.ts.map +1 -1
  55. package/dist-types/components/overlays/test-suites/OverlayMixin.suite.d.ts.map +1 -1
  56. package/dist-types/components/overlays/types/OverlayConfig.d.ts +14 -10
  57. package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
  58. package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts +2 -2
  59. package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts.map +1 -1
  60. package/dist-types/components/select-rich/src/LionSelectInvoker.d.ts +3 -27
  61. package/dist-types/components/select-rich/src/LionSelectInvoker.d.ts.map +1 -1
  62. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  63. package/dist-types/components/select-rich/test/lion-select-rich.test.d.ts.map +1 -1
  64. package/dist-types/components/validate-messages/src/loadDefaultFeedbackMessages.d.ts +3 -0
  65. package/dist-types/components/validate-messages/src/loadDefaultFeedbackMessages.d.ts.map +1 -1
  66. package/dist-types/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.d.ts +10 -0
  67. package/dist-types/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.d.ts.map +1 -0
  68. package/dist-types/components/validate-messages/test/loadDefaultFeedbackMessages.test.d.ts.map +1 -1
  69. package/dist-types/components/validate-messages/test/loadDefaultFeedbackMessagesNoSideEffects.d.ts +2 -0
  70. package/dist-types/components/validate-messages/test/loadDefaultFeedbackMessagesNoSideEffects.d.ts.map +1 -0
  71. package/dist-types/exports/input-datepicker.d.ts +0 -1
  72. package/dist-types/exports/overlays.d.ts +0 -1
  73. package/dist-types/exports/types/overlays.d.ts +1 -0
  74. package/dist-types/exports/types/overlays.d.ts.map +1 -1
  75. package/dist-types/exports/validate-messages-no-side-effects.d.ts +2 -0
  76. package/dist-types/exports/validate-messages-no-side-effects.d.ts.map +1 -0
  77. package/dist-types/tsconfig.tsbuildinfo +1 -1
  78. package/docs/components/checkbox-group/overview.md +2 -2
  79. package/docs/components/collapsible/overview.md +1 -1
  80. package/docs/components/combobox/overview.md +1 -1
  81. package/docs/components/combobox/use-cases.md +1 -1
  82. package/docs/components/dialog/overview.md +5 -9
  83. package/docs/components/dialog/use-cases.md +43 -87
  84. package/docs/components/fieldset/overview.md +5 -5
  85. package/docs/components/form/overview.md +3 -3
  86. package/docs/components/icon/use-cases.md +1 -1
  87. package/docs/components/input/overview.md +1 -1
  88. package/docs/components/input-amount/overview.md +3 -3
  89. package/docs/components/input-date/overview.md +3 -3
  90. package/docs/components/input-datepicker/overview.md +5 -5
  91. package/docs/components/input-email/overview.md +2 -2
  92. package/docs/components/input-iban/overview.md +2 -2
  93. package/docs/components/input-range/overview.md +2 -2
  94. package/docs/components/input-stepper/overview.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/overview.md +1 -1
  98. package/docs/components/radio-group/overview.md +3 -3
  99. package/docs/components/select-rich/use-cases.md +1 -1
  100. package/docs/components/tabs/examples.md +1 -1
  101. package/docs/fundamentals/index.md +4 -4
  102. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  103. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  104. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  105. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  106. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  107. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  108. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  109. package/docs/fundamentals/systems/core/overview.md +1 -1
  110. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  111. package/docs/fundamentals/systems/form/model-value.md +1 -1
  112. package/docs/fundamentals/systems/form/overview.md +28 -28
  113. package/docs/fundamentals/systems/form/use-cases.md +4 -0
  114. package/docs/fundamentals/systems/form/validate.md +1 -1
  115. package/docs/fundamentals/systems/localize/overview.md +4 -4
  116. package/docs/fundamentals/systems/overlays/assets/{applyDemoOverlayStyles.js → applyDemoOverlayStyles.mjs} +2 -1
  117. package/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.mjs +119 -0
  118. package/docs/fundamentals/systems/overlays/assets/{demo-overlay-backdrop.js → demo-overlay-backdrop.mjs} +3 -4
  119. package/docs/fundamentals/systems/overlays/assets/demo-overlay-positioning.mjs +537 -0
  120. package/docs/fundamentals/systems/overlays/assets/umbrella-form.js +9 -2
  121. package/docs/fundamentals/systems/overlays/configuration.md +56 -54
  122. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  123. package/docs/fundamentals/systems/overlays/use-cases.md +83 -77
  124. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  125. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  126. package/docs/guides/how-to/create-a-custom-field.md +6 -6
  127. package/docs/guides/how-to/extend-a-native-input.md +3 -3
  128. package/docs/guides/index.md +8 -8
  129. package/docs/guides/principles/index.md +5 -5
  130. package/docs/guides/principles/styling.md +1 -1
  131. package/exports/input-datepicker.js +0 -1
  132. package/exports/overlays.js +0 -1
  133. package/exports/types/overlays.ts +1 -0
  134. package/exports/validate-messages-no-side-effects.js +1 -0
  135. package/package.json +2 -1
  136. package/components/input-datepicker/src/LionCalendarOverlayFrame.js +0 -125
  137. package/components/overlays/src/globalOverlaysStyle.js +0 -139
  138. package/dist-types/components/input-datepicker/src/LionCalendarOverlayFrame.d.ts +0 -10
  139. package/dist-types/components/input-datepicker/src/LionCalendarOverlayFrame.d.ts.map +0 -1
  140. package/dist-types/components/overlays/src/globalOverlaysStyle.d.ts +0 -2
  141. package/dist-types/components/overlays/src/globalOverlaysStyle.d.ts.map +0 -1
  142. package/dist-types/exports/define/lion-calendar-overlay-frame.d.ts +0 -2
  143. package/dist-types/exports/define/lion-calendar-overlay-frame.d.ts.map +0 -1
  144. package/docs/fundamentals/systems/overlays/assets/demo-overlay-system.js +0 -42
  145. package/docs/fundamentals/systems/overlays/form-integration.md +0 -65
  146. package/docs/fundamentals/systems/overlays/scope.md +0 -176
  147. package/exports/define/lion-calendar-overlay-frame.js +0 -3
@@ -1,89 +1,117 @@
1
1
  import { EventTargetShim } from '@lion/ui/core.js';
2
+ import { adoptStyles } from 'lit';
2
3
  import { overlays } from './singleton.js';
3
4
  import { containFocus } from './utils/contain-focus.js';
5
+ import { overlayShadowDomStyle } from './overlayShadowDomStyle.js';
4
6
 
5
7
  /**
6
- * @typedef {import('../types/OverlayConfig.js').OverlayConfig} OverlayConfig
7
- * @typedef {import('../types/OverlayConfig.js').ViewportConfig} ViewportConfig
8
- * @typedef {import('@popperjs/core/lib/popper.js').createPopper} Popper
9
- * @typedef {import('@popperjs/core/lib/popper.js').Options} PopperOptions
10
- * @typedef {import('@popperjs/core/lib/enums.js').Placement} Placement
8
+ * @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig
9
+ * @typedef {import('@lion/ui/types/overlays.js').ViewportConfig} ViewportConfig
10
+ * @typedef {import('@popperjs/core').createPopper} Popper
11
+ * @typedef {import('@popperjs/core').Options} PopperOptions
12
+ * @typedef {import('@popperjs/core').Placement} Placement
11
13
  * @typedef {{ createPopper: Popper }} PopperModule
12
14
  * @typedef {'setup'|'init'|'teardown'|'before-show'|'show'|'hide'|'add'|'remove'} OverlayPhase
13
15
  */
14
16
 
17
+ /**
18
+ * From:
19
+ * - wrappingDialogNodeL1: `<dialog role="none"/>`
20
+ * - contentWrapperNodeL2: `<div id="content-wrapper-node"/>`
21
+ * - contentNodeL3: `<div slot="my-content"/>`
22
+ * To:
23
+ * ```html
24
+ * <dialog role="none">
25
+ * <div id="content-wrapper-node">
26
+ * <!-- this was the (slot for) original content node -->
27
+ * <slot name="my-content"></slot>
28
+ * </div>
29
+ * </dialog>
30
+ * ```
31
+ *
32
+ * `<slot name="my-content">` belonging to `<div slot="content"/>` will be wrapped with wrappingDialogNodeL1 and contentWrapperNodeL2
33
+ * inside shadow dom. With the help of temp markers, `<slot name="my-content">`'s original position will be respected.
34
+ *
35
+ * @param {{ wrappingDialogNodeL1:HTMLDialogElement|HTMLDivElement; contentWrapperNodeL2:Element; contentNodeL3: Element }} opts
36
+ */
37
+ function rearrangeNodes({ wrappingDialogNodeL1, contentWrapperNodeL2, contentNodeL3 }) {
38
+ if (!(contentWrapperNodeL2.isConnected || contentNodeL3.isConnected)) {
39
+ throw new Error(
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.',
41
+ );
42
+ }
43
+
44
+ let parentElement;
45
+ const tempMarker = document.createComment('temp-marker');
46
+
47
+ if (contentWrapperNodeL2.isConnected) {
48
+ // This is the case when contentWrapperNode (living in shadow dom, wrapping <slot name="my-content-outlet">) is already provided via controller.
49
+ parentElement = contentWrapperNodeL2.parentElement || contentWrapperNodeL2.getRootNode();
50
+ parentElement.insertBefore(tempMarker, contentWrapperNodeL2);
51
+ // Wrap...
52
+ wrappingDialogNodeL1.appendChild(contentWrapperNodeL2);
53
+ }
54
+ // if contentNodeL3.isConnected
55
+ else {
56
+ const contentIsProjected = contentNodeL3.assignedSlot;
57
+ if (contentIsProjected) {
58
+ parentElement =
59
+ contentNodeL3.assignedSlot.parentElement || contentNodeL3.assignedSlot.getRootNode();
60
+ parentElement.insertBefore(tempMarker, contentNodeL3.assignedSlot);
61
+ wrappingDialogNodeL1.appendChild(contentWrapperNodeL2);
62
+ // Important: we do not move around contentNodeL3, but the assigned slot
63
+ contentWrapperNodeL2.appendChild(contentNodeL3.assignedSlot);
64
+ } else {
65
+ parentElement = contentNodeL3.parentElement || contentNodeL3.getRootNode();
66
+ parentElement.insertBefore(tempMarker, contentNodeL3);
67
+ wrappingDialogNodeL1.appendChild(contentWrapperNodeL2);
68
+ contentWrapperNodeL2.appendChild(contentNodeL3);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * From:
74
+ * ```html
75
+ * #shadow-root:
76
+ * <div>
77
+ * <!-- tempMarker -->
78
+ * <slot name="x"/>
79
+ * </div>
80
+ * ```
81
+ *
82
+ * To:
83
+ * ```html
84
+ * #shadow-root:
85
+ * <div>
86
+ * <!-- tempMarker -->
87
+ * <dialog role="none">
88
+ * <div id="content-wrapper-node">
89
+ * <slot name="x"/>
90
+ * </div>
91
+ * </dialog>
92
+ * </div>
93
+ * ```
94
+ */
95
+ parentElement.insertBefore(wrappingDialogNodeL1, tempMarker);
96
+ parentElement?.removeChild(tempMarker);
97
+ }
98
+
15
99
  /**
16
100
  * @returns {Promise<PopperModule>}
17
101
  */
18
102
  async function preloadPopper() {
19
- /* eslint-disable import/no-extraneous-dependencies */ // -- [external]: import complains about untyped module, but we typecast it ourselves
20
- // @ts-ignore
103
+ // @ts-ignore [external]: import complains about untyped module, but we typecast it ourselves
21
104
  return /** @type {* & Promise<PopperModule>} */ (import('@popperjs/core/dist/esm/popper.js'));
22
- /* eslint-enable import/no-extraneous-dependencies */
23
105
  }
24
106
 
25
- const GLOBAL_OVERLAYS_CONTAINER_CLASS = 'global-overlays__overlay-container';
26
- const GLOBAL_OVERLAYS_CLASS = 'global-overlays__overlay';
27
107
  // @ts-expect-error [external]: CSS not yet typed
28
108
  const supportsCSSTypedObject = window.CSS?.number && document.body.attributeStyleMap?.set;
29
109
 
30
110
  /**
31
- * @desc OverlayController is the fundament for every single type of overlay. With the right
111
+ * OverlayController is the fundament for every single type of overlay. With the right
32
112
  * configuration, it can be used to build (modal) dialogs, tooltips, dropdowns, popovers,
33
113
  * bottom/top/left/right sheets etc.
34
114
  *
35
- * ### About contentNode, contentWrapperNode and renderTarget.
36
- *
37
- * #### contentNode
38
- * Node containing actual overlay contents.
39
- * It will not be touched by the OverlayController, it will only set attributes needed
40
- * for accessibility.
41
- *
42
- * #### contentWrapperNode
43
- * The 'positioning' element.
44
- * For local overlays, this node will be provided to Popper and all
45
- * inline positioning styles will be added here. It will also act as the container of an arrow
46
- * element (the arrow needs to be a sibling of contentNode for Popper to work correctly).
47
- * When projecting a contentNode from a shadowRoot, it is essential to have the wrapper in
48
- * shadow dom, so that contentNode can be styled via `::slotted` from the shadow root.
49
- * The Popper arrow can then be styled from that same shadow root as well.
50
- * For global overlays, the contentWrapperNode will be appended to the globalRootNode structure.
51
- *
52
- * #### renderTarget
53
- * Usually the parent node of contentWrapperNode that either exists locally or globally.
54
- * When a responsive scenario is created (in which we switch from global to local or vice versa)
55
- * we need to know where we should reappend contentWrapperNode (or contentNode in case it's projected)
56
- *
57
- * So a regular flow can be summarized as follows:
58
- * 1. Application Developer spawns an OverlayController with a contentNode reference
59
- * 2. OverlayController will create a contentWrapperNode around contentNode (or consumes when provided)
60
- * 3. contentWrapperNode will be appended to the right renderTarget
61
- *
62
- * There are subtle differences depending on the following factors:
63
- * - whether in global/local placement mode
64
- * - whether contentNode projected
65
- * - whether an arrow is provided
66
- *
67
- * This leads to the following possible combinations:
68
- * - [l1]. local + no content projection + no arrow
69
- * - [l2]. local + content projection + no arrow
70
- * - [l3]. local + no content projection + arrow
71
- * - [l4]. local + content projection + arrow
72
- * - [g1]. global
73
- *
74
- * #### html structure for a content projected node
75
- * <div id="contentWrapperNode">
76
- * <slot name="contentNode"></slot>
77
- * <div x-arrow></div>
78
- * </div>
79
- *
80
- * Structure above depicts [l4]
81
- * So in case of [l1] and [l3], the <slot> element would be a regular element
82
- * In case of [l1] and [l2], there would be no arrow.
83
- * Note that a contentWrapperNode should be provided for [l2], [l3] and [l4]
84
- * In case of a global overlay ([g1]), it's enough to provide just the contentNode.
85
- * In case of a local overlay or a responsive overlay switching from placementMode, one should
86
- * always configure as if it were a local overlay.
87
115
  */
88
116
  export class OverlayController extends EventTargetShim {
89
117
  /**
@@ -97,6 +125,9 @@ export class OverlayController extends EventTargetShim {
97
125
  /** @private */
98
126
  this.__sharedConfig = config;
99
127
 
128
+ /** @type {OverlayConfig} */
129
+ this.config = {};
130
+
100
131
  /**
101
132
  * @type {OverlayConfig}
102
133
  * @protected
@@ -123,7 +154,7 @@ export class OverlayController extends EventTargetShim {
123
154
  handlesAccessibility: false,
124
155
  popperConfig: {
125
156
  placement: 'top',
126
- strategy: 'absolute',
157
+ strategy: 'fixed',
127
158
  modifiers: [
128
159
  {
129
160
  name: 'preventOverflow',
@@ -156,6 +187,7 @@ export class OverlayController extends EventTargetShim {
156
187
  viewportConfig: {
157
188
  placement: 'center',
158
189
  },
190
+ zIndex: 9999,
159
191
  };
160
192
 
161
193
  this.manager.add(this);
@@ -163,25 +195,11 @@ export class OverlayController extends EventTargetShim {
163
195
  this._contentId = `overlay-content--${Math.random().toString(36).substr(2, 10)}`;
164
196
  /** @private */
165
197
  this.__originalAttrs = new Map();
166
- if (this._defaultConfig.contentNode) {
167
- if (!this._defaultConfig.contentNode.isConnected) {
168
- throw new Error(
169
- '[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.',
170
- );
171
- }
172
- this.__isContentNodeProjected = Boolean(this._defaultConfig.contentNode.assignedSlot);
173
- }
174
198
  this.updateConfig(config);
175
199
  /** @private */
176
200
  this.__hasActiveTrapsKeyboardFocus = false;
177
201
  /** @private */
178
202
  this.__hasActiveBackdrop = true;
179
- /**
180
- * @type {HTMLElement | undefined}
181
- * @private
182
- */
183
- this.__backdropNodeToBeTornDown = undefined;
184
-
185
203
  /** @private */
186
204
  this.__escKeyHandler = this.__escKeyHandler.bind(this);
187
205
  }
@@ -196,10 +214,10 @@ export class OverlayController extends EventTargetShim {
196
214
 
197
215
  /**
198
216
  * The contentWrapperNode
199
- * @type {HTMLElement}
217
+ * @type {HTMLDialogElement | HTMLDivElement}
200
218
  */
201
219
  get content() {
202
- return /** @type {HTMLElement} */ (this.contentWrapperNode);
220
+ return /** @type {HTMLDialogElement | HTMLDivElement} */ (this.__wrappingDialogNode);
203
221
  }
204
222
 
205
223
  /**
@@ -385,28 +403,6 @@ export class OverlayController extends EventTargetShim {
385
403
  return /** @type {ViewportConfig} */ (this.config?.viewportConfig);
386
404
  }
387
405
 
388
- /**
389
- * Usually the parent node of contentWrapperNode that either exists locally or globally.
390
- * When a responsive scenario is created (in which we switch from global to local or vice versa)
391
- * we need to know where we should reappend contentWrapperNode (or contentNode in case it's
392
- * projected).
393
- * @type {HTMLElement}
394
- * @protected
395
- */
396
- get _renderTarget() {
397
- /** config [g1] */
398
- if (this.placementMode === 'global') {
399
- return this.manager.globalRootNode;
400
- }
401
- /** config [l2] or [l4] */
402
- if (this.__isContentNodeProjected) {
403
- // @ts-expect-error [external]: fix Node types
404
- return this.__originalContentParent?.getRootNode().host;
405
- }
406
- /** config [l1] or [l3] */
407
- return /** @type {HTMLElement} */ (this.__originalContentParent);
408
- }
409
-
410
406
  /**
411
407
  * @desc The element our local overlay will be positioned relative to.
412
408
  * @type {HTMLElement | undefined}
@@ -420,12 +416,8 @@ export class OverlayController extends EventTargetShim {
420
416
  * @param {number} value
421
417
  */
422
418
  set elevation(value) {
423
- if (this.contentWrapperNode) {
424
- this.contentWrapperNode.style.zIndex = `${value}`;
425
- }
426
- if (this.backdropNode) {
427
- this.backdropNode.style.zIndex = `${value}`;
428
- }
419
+ // @ts-expect-error find out why config would/could be undfined
420
+ this.__wrappingDialogNode.style.zIndex = `${this.config.zIndex + value}`;
429
421
  }
430
422
 
431
423
  /**
@@ -450,7 +442,7 @@ export class OverlayController extends EventTargetShim {
450
442
  * @type {OverlayConfig}
451
443
  * @private
452
444
  */
453
- this.__prevConfig = this.config || {};
445
+ this.__prevConfig = this.config;
454
446
 
455
447
  /** @type {OverlayConfig} */
456
448
  this.config = {
@@ -472,9 +464,9 @@ export class OverlayController extends EventTargetShim {
472
464
  };
473
465
 
474
466
  /** @private */
475
- this.__validateConfiguration(/** @type {OverlayConfig} */ (this.config));
467
+ this.__validateConfiguration(this.config);
476
468
  /** @protected */
477
- this._init({ cfgToAdd });
469
+ this._init();
478
470
  /** @private */
479
471
  this.__elementToFocusAfterHide = undefined;
480
472
  }
@@ -498,16 +490,6 @@ export class OverlayController extends EventTargetShim {
498
490
  if (!newConfig.contentNode) {
499
491
  throw new Error('[OverlayController] You need to provide a .contentNode');
500
492
  }
501
- if (this.__isContentNodeProjected && !newConfig.contentWrapperNode) {
502
- throw new Error(
503
- '[OverlayController] You need to provide a .contentWrapperNode when .contentNode is projected',
504
- );
505
- }
506
- if (newConfig.isTooltip && newConfig.placementMode !== 'local') {
507
- throw new Error(
508
- '[OverlayController] .isTooltip should be configured with .placementMode "local"',
509
- );
510
- }
511
493
  if (newConfig.isTooltip && !newConfig.handlesAccessibility) {
512
494
  throw new Error(
513
495
  '[OverlayController] .isTooltip only takes effect when .handlesAccessibility is enabled',
@@ -519,85 +501,95 @@ export class OverlayController extends EventTargetShim {
519
501
  }
520
502
 
521
503
  /**
522
- * @param {{ cfgToAdd: OverlayConfig }} options
523
504
  * @protected
524
505
  */
525
- _init({ cfgToAdd }) {
526
- this.__initContentWrapperNode({ cfgToAdd });
527
- this.__initConnectionTarget();
506
+ _init() {
507
+ if (!this.__contentHasBeenInitialized) {
508
+ this.__initContentDomStructure();
509
+ this.__contentHasBeenInitialized = true;
510
+ }
511
+
512
+ // Reset all positioning styles (local, c.q. Popper) and classes (global)
513
+ this.contentWrapperNode.removeAttribute('style');
514
+ this.contentWrapperNode.removeAttribute('class');
528
515
 
529
516
  if (this.placementMode === 'local') {
530
517
  // Lazily load Popper if not done yet
531
518
  if (!OverlayController.popperModule) {
532
- // a@ts-expect-error FIXME: for some reason createPopper is missing here
533
519
  OverlayController.popperModule = preloadPopper();
534
520
  }
535
521
  }
522
+ const rootNode = /** @type {ShadowRoot} */ (this.contentWrapperNode.getRootNode());
523
+ adoptStyles(rootNode, [...(rootNode.adoptedStyleSheets || []), overlayShadowDomStyle]);
536
524
  this._handleFeatures({ phase: 'init' });
537
525
  }
538
526
 
539
- /** @private */
540
- __initConnectionTarget() {
541
- // Now, add our node to the right place in dom (renderTarget)
542
- if (this.contentWrapperNode !== this.__prevConfig?.contentWrapperNode) {
543
- if (this.config?.placementMode === 'global' || !this.__isContentNodeProjected) {
544
- /** @type {HTMLElement} */
545
- (this.contentWrapperNode).appendChild(this.contentNode);
546
- }
547
- }
548
-
549
- if (!this._renderTarget) {
550
- return;
551
- }
552
-
553
- if (this.__isContentNodeProjected && this.placementMode === 'local') {
554
- // We add the contentNode in its slot, so that it will be projected by contentWrapperNode
555
- this._renderTarget.appendChild(this.contentNode);
556
- } else {
557
- const isInsideRenderTarget = this._renderTarget === this.contentWrapperNode.parentNode;
558
- const nodeContainsTarget = this.contentWrapperNode.contains(this._renderTarget);
559
- if (!isInsideRenderTarget && !nodeContainsTarget) {
560
- // contentWrapperNode becomes the direct (non projected) parent of contentNode
561
- this._renderTarget.appendChild(this.contentWrapperNode);
562
- }
563
- }
564
- }
565
-
566
527
  /**
567
- * Cleanup ._contentWrapperNode. We do this, because creating a fresh wrapper
568
- * can lead to problems with event listeners...
569
- * @param {{ cfgToAdd: OverlayConfig }} options
528
+ * Here we arrange our content node via:
529
+ * 1. HTMLDialogElement: the content will always be painted to the browser's top layer
530
+ * - no matter what context the contentNode lives in, the overlay will be painted correctly via the <dialog> element,
531
+ * even if 'overflow:hidden' or a css transform is applied in its parent hierarchy.
532
+ * - the dialog element will be unstyled, but will span the whole screen
533
+ * - a backdrop element will be a child of the dialog element, so it leverages the capabilities of the parent
534
+ * (filling the whole screen if wanted an always painted to top layer)
535
+ * 2. ContentWrapper: the content receives the right positioning styles in a clean/non conflicting way:
536
+ * - local positioning: receive inline (position) styling that can never conflict with the already existing computed styles
537
+ * - global positioning: receive flex (child) classes that position the content correctly relative to the viewport
538
+ *
539
+ * The resulting structure that will be created looks like this:
540
+ *
541
+ * ...
542
+ * <dialog role="none">
543
+ * <div id="optional-backdrop"></div>
544
+ * <div id="content-wrapper-node">
545
+ * <!-- this was the (slot for) original content node -->
546
+ * <slot name="content"></slot>
547
+ * </div>
548
+ * </dialog>
549
+ * ...
550
+ *
570
551
  * @private
571
552
  */
572
- __initContentWrapperNode({ cfgToAdd }) {
573
- if (this.config?.contentWrapperNode && this.placementMode === 'local') {
574
- /** config [l2],[l3],[l4] */
575
- this.__contentWrapperNode = this.config.contentWrapperNode;
576
- } else {
577
- /** config [l1],[g1] */
553
+ __initContentDomStructure() {
554
+ const wrappingDialogElement = document.createElement(
555
+ this.config?._noDialogEl ? 'div' : 'dialog',
556
+ );
557
+ // We use a dialog for its visual capabilities: it renders to the top layer.
558
+ // A11y will depend on the type of overlay and is arranged on contentNode level.
559
+ // Also see: https://www.scottohara.me/blog/2019/03/05/open-dialog.html
560
+ wrappingDialogElement.setAttribute('role', 'none');
561
+ wrappingDialogElement.setAttribute('data-overlay-outer-wrapper', '');
562
+ // N.B. position: fixed is needed to escape out of 'overflow: hidden'
563
+ // We give a high z-index for non-modal dialogs, so that we at least win from all siblings of our
564
+ // parent stacking context
565
+ wrappingDialogElement.style.cssText = `display:none; z-index: ${this.config.zIndex};`;
566
+ this.__wrappingDialogNode = wrappingDialogElement;
567
+
568
+ /**
569
+ * Based on the configuration of the developer, multiple scenarios are accounted for
570
+ * A. We already have a contentWrapperNode ()
571
+ */
572
+ if (!this.config?.contentWrapperNode) {
578
573
  this.__contentWrapperNode = document.createElement('div');
579
574
  }
575
+ this.contentWrapperNode.setAttribute('data-id', 'content-wrapper');
576
+
577
+ rearrangeNodes({
578
+ wrappingDialogNodeL1: wrappingDialogElement,
579
+ contentWrapperNodeL2: this.contentWrapperNode,
580
+ contentNodeL3: this.contentNode,
581
+ });
582
+ // @ts-ignore
583
+ wrappingDialogElement.open = true;
580
584
 
581
- this.contentWrapperNode.style.cssText = '';
582
- this.contentWrapperNode.style.display = 'none';
585
+ this.__wrappingDialogNode.style.display = 'none';
586
+ this.contentWrapperNode.style.zIndex = '1';
583
587
 
584
588
  if (getComputedStyle(this.contentNode).position === 'absolute') {
585
589
  // Having a _contWrapperNode and a contentNode with 'position:absolute' results in
586
590
  // computed height of 0...
587
591
  this.contentNode.style.position = 'static';
588
592
  }
589
-
590
- if (this.__isContentNodeProjected && this.contentWrapperNode.isConnected) {
591
- // We need to keep track of the original local context.
592
- /** config [l2], [l4] */
593
- this.__originalContentParent = /** @type {HTMLElement} */ (
594
- this.contentWrapperNode.parentNode
595
- );
596
- } else if (cfgToAdd.contentNode && cfgToAdd.contentNode.isConnected) {
597
- // We need to keep track of the original local context.
598
- /** config [l1], [l3], [g1] */
599
- this.__originalContentParent = /** @type {HTMLElement} */ (this.contentNode?.parentNode);
600
- }
601
593
  }
602
594
 
603
595
  /**
@@ -613,7 +605,7 @@ export class OverlayController extends EventTargetShim {
613
605
  if (phase === 'setup') {
614
606
  const zIndexNumber = Number(getComputedStyle(this.contentNode).zIndex);
615
607
  if (zIndexNumber < 1 || Number.isNaN(zIndexNumber)) {
616
- this.contentWrapperNode.style.zIndex = '1';
608
+ this.contentNode.style.zIndex = '1';
617
609
  }
618
610
  }
619
611
  }
@@ -686,7 +678,7 @@ export class OverlayController extends EventTargetShim {
686
678
  }
687
679
 
688
680
  get isShown() {
689
- return Boolean(this.contentWrapperNode.style.display !== 'none');
681
+ return Boolean(this.__wrappingDialogNode?.style.display !== 'none');
690
682
  }
691
683
 
692
684
  /**
@@ -717,7 +709,11 @@ export class OverlayController extends EventTargetShim {
717
709
  const event = new CustomEvent('before-show', { cancelable: true });
718
710
  this.dispatchEvent(event);
719
711
  if (!event.defaultPrevented) {
720
- this.contentWrapperNode.style.display = '';
712
+ if (this.__wrappingDialogNode instanceof HTMLDialogElement) {
713
+ this.__wrappingDialogNode.show();
714
+ }
715
+ // @ts-ignore
716
+ this.__wrappingDialogNode.style.display = '';
721
717
  this._keepBodySize({ phase: 'before-show' });
722
718
  await this._handleFeatures({ phase: 'show' });
723
719
  this._keepBodySize({ phase: 'show' });
@@ -739,11 +735,17 @@ export class OverlayController extends EventTargetShim {
739
735
  */
740
736
  async _handlePosition({ phase }) {
741
737
  if (this.placementMode === 'global') {
742
- const addOrRemove = phase === 'show' ? 'add' : 'remove';
743
- const placementClass = `${GLOBAL_OVERLAYS_CONTAINER_CLASS}--${this.viewportConfig.placement}`;
744
- this.contentWrapperNode.classList[addOrRemove](GLOBAL_OVERLAYS_CONTAINER_CLASS);
745
- this.contentWrapperNode.classList[addOrRemove](placementClass);
746
- this.contentNode.classList[addOrRemove](GLOBAL_OVERLAYS_CLASS);
738
+ const placementClass = `overlays__overlay-container--${this.viewportConfig.placement}`;
739
+
740
+ if (phase === 'show') {
741
+ this.contentWrapperNode.classList.add('overlays__overlay-container');
742
+ this.contentWrapperNode.classList.add(placementClass);
743
+ this.contentNode.classList.add('overlays__overlay');
744
+ } else if (phase === 'hide') {
745
+ this.contentWrapperNode.classList.remove('overlays__overlay-container');
746
+ this.contentWrapperNode.classList.remove(placementClass);
747
+ this.contentNode.classList.remove('overlays__overlay');
748
+ }
747
749
  } else if (this.placementMode === 'local' && phase === 'show') {
748
750
  /**
749
751
  * Popper is weird about properly positioning the popper element when it is recreated so
@@ -753,7 +755,7 @@ export class OverlayController extends EventTargetShim {
753
755
  * This is however necessary for initial placement.
754
756
  */
755
757
  await this.__createPopperInstance();
756
- /** @type {Popper} */ (this._popper).forceUpdate();
758
+ this._popper.forceUpdate();
757
759
  }
758
760
  }
759
761
 
@@ -829,7 +831,11 @@ export class OverlayController extends EventTargetShim {
829
831
  contentNode: this.contentNode,
830
832
  });
831
833
 
832
- this.contentWrapperNode.style.display = 'none';
834
+ if (this.__wrappingDialogNode instanceof HTMLDialogElement) {
835
+ this.__wrappingDialogNode.close();
836
+ }
837
+ // @ts-ignore
838
+ this.__wrappingDialogNode.style.display = 'none';
833
839
  this._handleFeatures({ phase: 'hide' });
834
840
  this._keepBodySize({ phase: 'hide' });
835
841
  this.dispatchEvent(new Event('hide'));
@@ -843,6 +849,7 @@ export class OverlayController extends EventTargetShim {
843
849
  *
844
850
  * @param {{backdropNode:HTMLElement, contentNode:HTMLElement}} hideConfig
845
851
  */
852
+ // @ts-ignore
846
853
  // eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars
847
854
  async transitionHide(hideConfig) {}
848
855
 
@@ -851,48 +858,27 @@ export class OverlayController extends EventTargetShim {
851
858
  * @protected
852
859
  */
853
860
  // eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars
854
- async _transitionHide(hideConfig) {
861
+ async _transitionHide({ backdropNode, contentNode }) {
855
862
  // `this.transitionHide` is a hook for our users
856
- await this.transitionHide({ backdropNode: this.backdropNode, contentNode: this.contentNode });
857
-
858
- if (hideConfig.backdropNode) {
859
- hideConfig.backdropNode.classList.remove(
860
- `${this.placementMode}-overlays__backdrop--animation-in`,
861
- );
862
- /** @type {() => void} */
863
- let afterFadeOut = () => {};
864
- hideConfig.backdropNode.classList.add(
865
- `${this.placementMode}-overlays__backdrop--animation-out`,
866
- );
867
- this.__backdropAnimation = new Promise(resolve => {
868
- afterFadeOut = () => {
869
- if (hideConfig.backdropNode) {
870
- hideConfig.backdropNode.classList.remove(
871
- `${this.placementMode}-overlays__backdrop--animation-out`,
872
- );
873
- hideConfig.backdropNode.classList.remove(
874
- `${this.placementMode}-overlays__backdrop--visible`,
875
- );
876
- hideConfig.backdropNode.removeEventListener('animationend', afterFadeOut);
877
- }
878
- resolve(undefined);
879
- };
880
- });
881
-
882
- hideConfig.backdropNode.addEventListener('animationend', afterFadeOut);
863
+ await this.transitionHide({ backdropNode, contentNode });
864
+ this._handlePosition({ phase: 'hide' });
865
+ if (!backdropNode) {
866
+ return;
883
867
  }
868
+ backdropNode.classList.remove(`overlays__backdrop--animation-in`);
884
869
  }
885
870
 
886
871
  /**
887
872
  * To be overridden by subclassers
888
873
  *
889
- * @param {{backdropNode:HTMLElement, contentNode:HTMLElement}} showConfig
874
+ * @param {{backdropNode:HTMLElement; contentNode:HTMLElement}} showConfig
890
875
  */
876
+ // @ts-ignore
891
877
  // eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars
892
878
  async transitionShow(showConfig) {}
893
879
 
894
880
  /**
895
- * @param {{backdropNode:HTMLElement, contentNode:HTMLElement}} showConfig
881
+ * @param {{backdropNode:HTMLElement; contentNode:HTMLElement}} showConfig
896
882
  */
897
883
  // eslint-disable-next-line class-methods-use-this, no-empty-function, no-unused-vars
898
884
  async _transitionShow(showConfig) {
@@ -900,28 +886,25 @@ export class OverlayController extends EventTargetShim {
900
886
  await this.transitionShow({ backdropNode: this.backdropNode, contentNode: this.contentNode });
901
887
 
902
888
  if (showConfig.backdropNode) {
903
- showConfig.backdropNode.classList.add(
904
- `${this.placementMode}-overlays__backdrop--animation-in`,
905
- );
889
+ showConfig.backdropNode.classList.add(`overlays__backdrop--animation-in`);
906
890
  }
907
891
  }
908
892
 
909
893
  /** @protected */
910
894
  _restoreFocus() {
911
- const { activeElement } = /** @type {* & ShadowRoot} */ (
912
- this.__contentWrapperNode
913
- ).getRootNode();
914
- // We only are allowed to move focus if we (still) 'own' it.
915
- // Otherwise we assume the 'outside world' has, purposefully, taken over
916
- if (
917
- activeElement &&
918
- /** @type {HTMLElement} */ (this.__contentWrapperNode).contains(activeElement)
919
- ) {
920
- if (this.elementToFocusAfterHide) {
921
- this.elementToFocusAfterHide.focus();
922
- } else {
923
- activeElement.blur();
924
- }
895
+ // We only are allowed to move focus if we (still) 'own' the active element.
896
+ // Otherwise we assume the 'outside world' has purposefully taken over
897
+ const { activeElement } = /** @type {ShadowRoot} */ (this.contentNode.getRootNode());
898
+ const weStillOwnActiveElement =
899
+ activeElement instanceof HTMLElement && this.contentNode.contains(activeElement);
900
+ if (!weStillOwnActiveElement) {
901
+ return;
902
+ }
903
+
904
+ if (this.elementToFocusAfterHide) {
905
+ this.elementToFocusAfterHide.focus();
906
+ } else {
907
+ activeElement.blur();
925
908
  }
926
909
  }
927
910
 
@@ -1010,55 +993,28 @@ export class OverlayController extends EventTargetShim {
1010
993
  * @protected
1011
994
  */
1012
995
  _handleBackdrop({ phase }) {
996
+ // eslint-disable-next-line default-case
1013
997
  switch (phase) {
1014
998
  case 'init': {
1015
- if (!this.backdropNode) {
1016
- this.__backdropNode = document.createElement('div');
1017
- /** @type {HTMLElement} */
1018
- (this.backdropNode).slot = 'backdrop';
1019
- /** @type {HTMLElement} */
1020
- (this.backdropNode).classList.add(`${this.placementMode}-overlays__backdrop`);
1021
- }
1022
-
1023
- let insertionAnchor = /** @type {HTMLElement} */ (this.contentNode.parentNode);
1024
- let insertionBefore = this.contentNode;
1025
- if (this.placementMode === 'global') {
1026
- insertionAnchor = /** @type {HTMLElement} */ (this.contentWrapperNode.parentElement);
1027
- insertionBefore = this.contentWrapperNode;
999
+ if (!this.__backdropInitialized) {
1000
+ if (!this.config?.backdropNode) {
1001
+ this.__backdropNode = document.createElement('div');
1002
+ // If backdropNode existed in config, styles are applied by implementing party
1003
+ this.__backdropNode.classList.add(`overlays__backdrop`);
1004
+ }
1005
+ // @ts-ignore
1006
+ this.__wrappingDialogNode.prepend(this.backdropNode);
1007
+ this.__backdropInitialized = true;
1028
1008
  }
1029
- insertionAnchor.insertBefore(this.backdropNode, insertionBefore);
1030
1009
  break;
1031
1010
  }
1032
1011
  case 'show':
1033
- this.backdropNode.classList.add(`${this.placementMode}-overlays__backdrop--visible`);
1012
+ this.backdropNode.classList.add(`overlays__backdrop--visible`);
1034
1013
  this.__hasActiveBackdrop = true;
1035
1014
  break;
1036
1015
  case 'hide':
1037
- if (!this.backdropNode) {
1038
- return;
1039
- }
1040
1016
  this.__hasActiveBackdrop = false;
1041
1017
  break;
1042
- case 'teardown':
1043
- if (!this.backdropNode || !this.backdropNode.parentNode) {
1044
- return;
1045
- }
1046
- if (this.__backdropAnimation) {
1047
- this.__backdropNodeToBeTornDown = this.backdropNode;
1048
-
1049
- this.__backdropAnimation.then(() => {
1050
- if (this.__backdropNodeToBeTornDown && this.__backdropNodeToBeTornDown.parentNode) {
1051
- this.__backdropNodeToBeTornDown.parentNode.removeChild(
1052
- this.__backdropNodeToBeTornDown,
1053
- );
1054
- }
1055
- });
1056
- } else {
1057
- this.backdropNode.parentNode.removeChild(this.backdropNode);
1058
- }
1059
- this.__backdropNode = undefined;
1060
- break;
1061
- /* no default */
1062
1018
  }
1063
1019
  }
1064
1020
 
@@ -1072,7 +1028,16 @@ export class OverlayController extends EventTargetShim {
1072
1028
  */
1073
1029
  _handleTrapsKeyboardFocus({ phase }) {
1074
1030
  if (phase === 'show') {
1031
+ // @ts-ignore
1032
+ if ('showModal' in this.__wrappingDialogNode) {
1033
+ // @ts-ignore
1034
+ this.__wrappingDialogNode.close();
1035
+ // @ts-ignore
1036
+ this.__wrappingDialogNode.showModal();
1037
+ }
1038
+ // else {
1075
1039
  this.enableTrapsKeyboardFocus();
1040
+ // }
1076
1041
  } else if (phase === 'hide' || phase === 'teardown') {
1077
1042
  this.disableTrapsKeyboardFocus();
1078
1043
  }
@@ -1271,24 +1236,6 @@ export class OverlayController extends EventTargetShim {
1271
1236
 
1272
1237
  teardown() {
1273
1238
  this._handleFeatures({ phase: 'teardown' });
1274
-
1275
- if (this.placementMode === 'global' && this.__isContentNodeProjected) {
1276
- /** @type {HTMLElement} */ (this.__originalContentParent).appendChild(this.contentNode);
1277
- }
1278
-
1279
- // Remove the content node wrapper from the global rootnode
1280
- this._teardownContentWrapperNode();
1281
- }
1282
-
1283
- /** @protected */
1284
- _teardownContentWrapperNode() {
1285
- if (
1286
- this.placementMode === 'global' &&
1287
- this.contentWrapperNode &&
1288
- this.contentWrapperNode.parentNode
1289
- ) {
1290
- this.contentWrapperNode.parentNode.removeChild(this.contentWrapperNode);
1291
- }
1292
1239
  }
1293
1240
 
1294
1241
  /** @private */