@lion/ui 0.0.9 → 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 (133) 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/custom-elements.json +251 -362
  32. package/dist-types/components/core/src/SlotMixin.d.ts.map +1 -1
  33. package/dist-types/components/form-integrations/test/helpers/umbrella-form.d.ts +4 -0
  34. package/dist-types/components/form-integrations/test/helpers/umbrella-form.d.ts.map +1 -1
  35. package/dist-types/components/form-integrations/test/model-value-consistency.test.d.ts.map +1 -1
  36. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +3 -3
  37. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
  38. package/dist-types/components/input-datepicker/src/localizeNamespaceLoader.d.ts +4 -0
  39. package/dist-types/components/input-datepicker/src/localizeNamespaceLoader.d.ts.map +1 -0
  40. package/dist-types/components/overlays/src/OverlayController.d.ts +46 -101
  41. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  42. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  43. package/dist-types/components/overlays/src/OverlaysManager.d.ts +0 -7
  44. package/dist-types/components/overlays/src/OverlaysManager.d.ts.map +1 -1
  45. package/dist-types/components/overlays/src/overlayDocumentStyle.d.ts +2 -0
  46. package/dist-types/components/overlays/src/overlayDocumentStyle.d.ts.map +1 -0
  47. package/dist-types/components/overlays/src/overlayShadowDomStyle.d.ts +2 -0
  48. package/dist-types/components/overlays/src/overlayShadowDomStyle.d.ts.map +1 -0
  49. package/dist-types/components/overlays/src/singleton.d.ts +2 -1
  50. package/dist-types/components/overlays/src/singleton.d.ts.map +1 -1
  51. package/dist-types/components/overlays/test-suites/OverlayMixin.suite.d.ts.map +1 -1
  52. package/dist-types/components/overlays/types/OverlayConfig.d.ts +14 -10
  53. package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
  54. package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts +2 -2
  55. package/dist-types/components/overlays/types/OverlayMixinTypes.d.ts.map +1 -1
  56. package/dist-types/components/select-rich/src/LionSelectInvoker.d.ts +3 -27
  57. package/dist-types/components/select-rich/src/LionSelectInvoker.d.ts.map +1 -1
  58. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  59. package/dist-types/components/select-rich/test/lion-select-rich.test.d.ts.map +1 -1
  60. package/dist-types/exports/input-datepicker.d.ts +0 -1
  61. package/dist-types/exports/overlays.d.ts +0 -1
  62. package/dist-types/exports/types/overlays.d.ts +1 -0
  63. package/dist-types/exports/types/overlays.d.ts.map +1 -1
  64. package/dist-types/tsconfig.tsbuildinfo +1 -1
  65. package/docs/components/checkbox-group/overview.md +2 -2
  66. package/docs/components/collapsible/overview.md +1 -1
  67. package/docs/components/combobox/overview.md +1 -1
  68. package/docs/components/combobox/use-cases.md +1 -1
  69. package/docs/components/dialog/overview.md +5 -9
  70. package/docs/components/dialog/use-cases.md +43 -87
  71. package/docs/components/fieldset/overview.md +5 -5
  72. package/docs/components/form/overview.md +3 -3
  73. package/docs/components/icon/use-cases.md +1 -1
  74. package/docs/components/input/overview.md +1 -1
  75. package/docs/components/input-amount/overview.md +3 -3
  76. package/docs/components/input-date/overview.md +3 -3
  77. package/docs/components/input-datepicker/overview.md +5 -5
  78. package/docs/components/input-email/overview.md +2 -2
  79. package/docs/components/input-iban/overview.md +2 -2
  80. package/docs/components/input-range/overview.md +2 -2
  81. package/docs/components/input-stepper/overview.md +1 -1
  82. package/docs/components/input-tel/overview.md +1 -1
  83. package/docs/components/input-tel/use-cases.md +6 -6
  84. package/docs/components/input-tel-dropdown/overview.md +1 -1
  85. package/docs/components/radio-group/overview.md +3 -3
  86. package/docs/components/select-rich/use-cases.md +1 -1
  87. package/docs/components/tabs/examples.md +1 -1
  88. package/docs/fundamentals/index.md +4 -4
  89. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  90. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  91. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  92. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  93. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  94. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  95. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  96. package/docs/fundamentals/systems/core/overview.md +1 -1
  97. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  98. package/docs/fundamentals/systems/form/model-value.md +1 -1
  99. package/docs/fundamentals/systems/form/overview.md +28 -28
  100. package/docs/fundamentals/systems/form/use-cases.md +4 -0
  101. package/docs/fundamentals/systems/form/validate.md +1 -1
  102. package/docs/fundamentals/systems/localize/overview.md +4 -4
  103. package/docs/fundamentals/systems/overlays/assets/{applyDemoOverlayStyles.js → applyDemoOverlayStyles.mjs} +2 -1
  104. package/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.mjs +119 -0
  105. package/docs/fundamentals/systems/overlays/assets/{demo-overlay-backdrop.js → demo-overlay-backdrop.mjs} +3 -4
  106. package/docs/fundamentals/systems/overlays/assets/demo-overlay-positioning.mjs +537 -0
  107. package/docs/fundamentals/systems/overlays/assets/umbrella-form.js +9 -2
  108. package/docs/fundamentals/systems/overlays/configuration.md +56 -54
  109. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  110. package/docs/fundamentals/systems/overlays/use-cases.md +83 -77
  111. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  112. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  113. package/docs/guides/how-to/create-a-custom-field.md +6 -6
  114. package/docs/guides/how-to/extend-a-native-input.md +3 -3
  115. package/docs/guides/index.md +8 -8
  116. package/docs/guides/principles/index.md +5 -5
  117. package/docs/guides/principles/styling.md +1 -1
  118. package/exports/input-datepicker.js +0 -1
  119. package/exports/overlays.js +0 -1
  120. package/exports/types/overlays.ts +1 -0
  121. package/package.json +2 -1
  122. package/components/input-datepicker/src/LionCalendarOverlayFrame.js +0 -125
  123. package/components/overlays/src/globalOverlaysStyle.js +0 -139
  124. package/dist-types/components/input-datepicker/src/LionCalendarOverlayFrame.d.ts +0 -10
  125. package/dist-types/components/input-datepicker/src/LionCalendarOverlayFrame.d.ts.map +0 -1
  126. package/dist-types/components/overlays/src/globalOverlaysStyle.d.ts +0 -2
  127. package/dist-types/components/overlays/src/globalOverlaysStyle.d.ts.map +0 -1
  128. package/dist-types/exports/define/lion-calendar-overlay-frame.d.ts +0 -2
  129. package/dist-types/exports/define/lion-calendar-overlay-frame.d.ts.map +0 -1
  130. package/docs/fundamentals/systems/overlays/assets/demo-overlay-system.js +0 -42
  131. package/docs/fundamentals/systems/overlays/form-integration.md +0 -65
  132. package/docs/fundamentals/systems/overlays/scope.md +0 -176
  133. package/exports/define/lion-calendar-overlay-frame.js +0 -3
@@ -20,6 +20,17 @@ import { simulateTab } from '../src/utils/simulate-tab.js';
20
20
  * @typedef {import('../types/OverlayConfig.js').ViewportPlacement} ViewportPlacement
21
21
  */
22
22
 
23
+ const wrappingDialogNodeStyle = 'display: none; z-index: 9999;';
24
+
25
+ /**
26
+ * @param {HTMLElement} node
27
+ */
28
+ function normalizeOverlayContentWapper(node) {
29
+ if (node.hasAttribute('style') && !node.style.cssText) {
30
+ node.removeAttribute('style');
31
+ }
32
+ }
33
+
23
34
  /**
24
35
  * @param {OverlayController} overlayControllerEl
25
36
  */
@@ -66,7 +77,7 @@ describe('OverlayController', () => {
66
77
  ...withGlobalTestConfig(),
67
78
  });
68
79
  expect(ctrl.content).not.to.be.undefined;
69
- expect(ctrl.contentNode.parentElement).to.equal(ctrl.content);
80
+ expect(ctrl.contentNode.parentElement).to.equal(ctrl.contentWrapperNode);
70
81
  });
71
82
 
72
83
  describe('Z-index on local overlays', () => {
@@ -106,17 +117,21 @@ describe('OverlayController', () => {
106
117
  contentNode: await createZNode('auto', { mode: 'global' }),
107
118
  });
108
119
  await ctrl.show();
109
- expect(ctrl.content.style.zIndex).to.equal('1');
120
+ // @ts-expect-error find out why config would/could be undfined
121
+ expect(ctrl.content.style.zIndex).to.equal(`${ctrl.config.zIndex + 1}`);
110
122
  ctrl.updateConfig({ contentNode: await createZNode('auto', { mode: 'inline' }) });
111
123
  await ctrl.show();
112
- expect(ctrl.content.style.zIndex).to.equal('1');
124
+ // @ts-expect-error find out why config would/could be undfined
125
+ expect(ctrl.content.style.zIndex).to.equal(`${ctrl.config.zIndex + 1}`);
113
126
 
114
127
  ctrl.updateConfig({ contentNode: await createZNode('0', { mode: 'global' }) });
115
128
  await ctrl.show();
116
- expect(ctrl.content.style.zIndex).to.equal('1');
129
+ // @ts-expect-error find out why config would/could be undfined
130
+ expect(ctrl.content.style.zIndex).to.equal(`${ctrl.config.zIndex + 1}`);
117
131
  ctrl.updateConfig({ contentNode: await createZNode('0', { mode: 'inline' }) });
118
132
  await ctrl.show();
119
- expect(ctrl.content.style.zIndex).to.equal('1');
133
+ // @ts-expect-error find out why config would/could be undfined
134
+ expect(ctrl.content.style.zIndex).to.equal(`${ctrl.config.zIndex + 1}`);
120
135
  });
121
136
 
122
137
  it.skip("doesn't set a z-index when contentNode already has >= 1", async () => {
@@ -147,42 +162,7 @@ describe('OverlayController', () => {
147
162
  });
148
163
  });
149
164
 
150
- describe('Render target', () => {
151
- it('creates global target for placement mode "global"', async () => {
152
- const ctrl = new OverlayController({
153
- ...withGlobalTestConfig(),
154
- });
155
- const { renderTarget } = getProtectedMembers(ctrl);
156
- expect(renderTarget).to.equal(overlays.globalRootNode);
157
- });
158
-
159
- it.skip('creates local target next to sibling for placement mode "local"', async () => {
160
- const ctrl = new OverlayController({
161
- ...withLocalTestConfig(),
162
- invokerNode: /** @type {HTMLElement} */ (await fixture(html`<button>Invoker</button>`)),
163
- });
164
- const { renderTarget } = getProtectedMembers(ctrl);
165
- expect(renderTarget).to.be.undefined;
166
- expect(ctrl.content).to.equal(ctrl.invokerNode?.nextElementSibling);
167
- });
168
-
169
- it('keeps local target for placement mode "local" when already connected', async () => {
170
- const parentNode = /** @type {HTMLElement} */ (
171
- await fixture(html`
172
- <div id="parent">
173
- <div id="content">Content</div>
174
- </div>
175
- `)
176
- );
177
- const contentNode = /** @type {HTMLElement} */ (parentNode.querySelector('#content'));
178
- const ctrl = new OverlayController({
179
- ...withLocalTestConfig(),
180
- contentNode,
181
- });
182
- const { renderTarget } = getProtectedMembers(ctrl);
183
- expect(renderTarget).to.equal(parentNode);
184
- });
185
-
165
+ describe('Offline content', () => {
186
166
  it('throws when passing a content node that was created "offline"', async () => {
187
167
  const contentNode = document.createElement('div');
188
168
  const createOverlayController = () => {
@@ -197,113 +177,250 @@ describe('OverlayController', () => {
197
177
  });
198
178
 
199
179
  it('succeeds when passing a content node that was created "online"', async () => {
200
- const contentNode = document.createElement('div');
201
- document.body.appendChild(contentNode);
180
+ const contentNode = /** @type {HTMLElement} */ (fixtureSync('<div>'));
202
181
  const overlay = new OverlayController({
203
182
  ...withLocalTestConfig(),
204
183
  contentNode,
205
184
  });
206
- const { renderTarget } = getProtectedMembers(overlay);
207
-
208
185
  expect(overlay.contentNode.isConnected).to.be.true;
209
- expect(renderTarget).to.not.be.undefined;
210
186
  });
211
187
  });
212
188
  });
213
189
 
214
190
  // TODO: Add teardown feature tests
215
- describe('Teardown', () => {
216
- it('removes the contentWrapperNode from global rootnode upon teardown', async () => {
217
- const ctrl = new OverlayController({
218
- ...withGlobalTestConfig(),
219
- });
220
-
221
- expect(ctrl.manager.globalRootNode.children.length).to.equal(1);
222
- ctrl.teardown();
223
- expect(ctrl.manager.globalRootNode.children.length).to.equal(0);
224
- });
191
+ describe('Teardown', () => {});
225
192
 
226
- it('[global] restores contentNode if it was/is a projected node', async () => {
227
- const shadowHost = document.createElement('div');
228
- shadowHost.id = 'shadowHost';
229
- shadowHost.attachShadow({ mode: 'open' });
230
- /** @type {ShadowRoot} */
231
- (shadowHost.shadowRoot).innerHTML = `
232
- <div id="contentWrapperNode">
233
- <slot name="contentNode"></slot>
234
- <my-arrow></my-arrow>
235
- </div>
236
- `;
237
- const contentNode = document.createElement('div');
238
- contentNode.slot = 'contentNode';
239
- shadowHost.appendChild(contentNode);
193
+ describe('Node Configuration', () => {
194
+ describe('Content', async () => {
195
+ it('accepts a .contentNode for displaying content of the overlay', async () => {
196
+ const myContentNode = /** @type {HTMLElement} */ (fixtureSync('<p>direct node</p>'));
197
+ const ctrl = new OverlayController({
198
+ ...withGlobalTestConfig(),
199
+ contentNode: myContentNode,
200
+ });
201
+ expect(ctrl.contentNode).to.have.trimmed.text('direct node');
202
+ expect(ctrl.contentNode).to.equal(myContentNode);
203
+ });
204
+
205
+ describe('Embedded dom structure', async () => {
206
+ describe('When projected in shadow dom', async () => {
207
+ it('wraps a .contentWrapperNode for style application and a <dialog role="none"> for top layer paints', async () => {
208
+ const tagString = defineCE(
209
+ class extends HTMLElement {
210
+ constructor() {
211
+ super();
212
+ this.attachShadow({ mode: 'open' });
213
+ }
214
+
215
+ connectedCallback() {
216
+ /** @type {ShadowRoot} */
217
+ (this.shadowRoot).innerHTML = '<slot name="content"></slot>';
218
+ this.innerHTML = '<div slot="content">projected</div>';
219
+ }
220
+ },
221
+ );
222
+
223
+ const el = /** @type {HTMLElement} */ (fixtureSync(`<${tagString}></${tagString}>`));
224
+ const myContentNode = /** @type {HTMLElement} */ (el.querySelector('[slot=content]'));
225
+ const ctrl = new OverlayController({
226
+ ...withGlobalTestConfig(),
227
+ contentNode: myContentNode,
228
+ });
229
+
230
+ expect(ctrl.contentNode.assignedSlot?.parentElement).to.equal(ctrl.contentWrapperNode);
231
+ expect(ctrl.contentWrapperNode.parentElement?.tagName).to.equal('DIALOG');
232
+
233
+ normalizeOverlayContentWapper(ctrl.contentWrapperNode);
234
+
235
+ // The total dom structure created...
236
+ expect(el).shadowDom.to.equal(`
237
+ <dialog data-overlay-outer-wrapper="" open="" role="none" style="${wrappingDialogNodeStyle}">
238
+ <div data-id="content-wrapper">
239
+ <slot name="content">
240
+ </slot>
241
+ </div>
242
+ </dialog>
243
+ `);
244
+
245
+ expect(el).lightDom.to.equal(`<div slot="content">projected</div>`);
246
+ });
247
+ });
240
248
 
241
- const wrapper = /** @type {HTMLElement} */ (await fixture('<div id="wrapper"></div>'));
242
- // Ensure the contentNode is connected to DOM
243
- wrapper.appendChild(shadowHost);
249
+ describe('When in light dom', async () => {
250
+ it('wraps a .contentWrapperNode for style application and a <dialog role="none"> for top layer paints', async () => {
251
+ const el = fixtureSync('<section><div id="content">non projected</div></section>');
252
+ const myContentNode = /** @type {HTMLElement} */ (el.querySelector('#content'));
253
+ const ctrl = new OverlayController({
254
+ ...withGlobalTestConfig(),
255
+ contentNode: myContentNode,
256
+ });
257
+ expect(ctrl.contentNode.parentElement).to.equal(ctrl.contentWrapperNode);
258
+ expect(ctrl.contentWrapperNode.parentElement?.tagName).to.equal('DIALOG');
259
+
260
+ normalizeOverlayContentWapper(ctrl.contentWrapperNode);
261
+
262
+ // The total dom structure created...
263
+ expect(el).lightDom.to.equal(`
264
+ <dialog data-overlay-outer-wrapper="" open="" role="none" style="${wrappingDialogNodeStyle}">
265
+ <div data-id="content-wrapper">
266
+ <div id="content">non projected</div>
267
+ </div>
268
+ </dialog>
269
+ `);
270
+ });
271
+ });
244
272
 
245
- // has one child = <div slot="contentNode"></div>
246
- expect(shadowHost.children.length).to.equal(1);
273
+ describe('When .contenWrapperNode provided', async () => {
274
+ it('keeps the .contentWrapperNode for style application and wraps a <dialog role="none"> for top layer paints', async () => {
275
+ const tagString = defineCE(
276
+ class extends HTMLElement {
277
+ constructor() {
278
+ super();
279
+ this.attachShadow({ mode: 'open' });
280
+ }
281
+
282
+ connectedCallback() {
283
+ /** @type {ShadowRoot} */
284
+ (this.shadowRoot).innerHTML = '<div><slot name="content"></slot></div>';
285
+ this.innerHTML = '<div slot="content">projected</div>';
286
+ }
287
+ },
288
+ );
289
+
290
+ const el = /** @type {HTMLElement} */ (fixtureSync(`<${tagString}></${tagString}>`));
291
+ const myContentNode = /** @type {HTMLElement} */ (el.querySelector('[slot=content]'));
292
+ const myContentWrapper = /** @type {HTMLElement} */ (
293
+ el.shadowRoot?.querySelector('div')
294
+ );
295
+ const ctrl = new OverlayController({
296
+ ...withGlobalTestConfig(),
297
+ contentNode: myContentNode,
298
+ contentWrapperNode: myContentWrapper,
299
+ });
300
+
301
+ normalizeOverlayContentWapper(ctrl.contentWrapperNode);
302
+
303
+ // The total dom structure created...
304
+ expect(el).shadowDom.to.equal(`
305
+ <dialog data-overlay-outer-wrapper="" open="" role="none" style="${wrappingDialogNodeStyle}">
306
+ <div data-id="content-wrapper">
307
+ <slot name="content"></slot>
308
+ </div>
309
+ </dialog>
310
+ `);
311
+ });
247
312
 
248
- const ctrl = new OverlayController({
249
- ...withLocalTestConfig(),
250
- placementMode: 'global',
251
- contentNode,
252
- contentWrapperNode: shadowHost,
313
+ it("uses the .contentWrapperNode as container for Popper's arrow", async () => {
314
+ const tagString = defineCE(
315
+ class extends HTMLElement {
316
+ constructor() {
317
+ super();
318
+ this.attachShadow({ mode: 'open' });
319
+ }
320
+
321
+ connectedCallback() {
322
+ /** @type {ShadowRoot} */
323
+ (this.shadowRoot).innerHTML = `
324
+ <div>
325
+ <div id="arrow"></div>
326
+ <slot name="content"></slot>
327
+ </div>`;
328
+ this.innerHTML = '<div slot="content">projected</div>';
329
+ }
330
+ },
331
+ );
332
+
333
+ const el = /** @type {HTMLElement} */ (fixtureSync(`<${tagString}></${tagString}>`));
334
+ const myContentNode = /** @type {HTMLElement} */ (el.querySelector('[slot=content]'));
335
+ const myContentWrapper = /** @type {HTMLElement} */ (
336
+ el.shadowRoot?.querySelector('div')
337
+ );
338
+ const ctrl = new OverlayController({
339
+ ...withLocalTestConfig(),
340
+ contentNode: myContentNode,
341
+ contentWrapperNode: myContentWrapper,
342
+ });
343
+
344
+ normalizeOverlayContentWapper(ctrl.contentWrapperNode);
345
+
346
+ // The total dom structure created...
347
+ expect(el).shadowDom.to.equal(`
348
+ <dialog data-overlay-outer-wrapper="" open="" role="none" style="${wrappingDialogNodeStyle}">
349
+ <div data-id="content-wrapper">
350
+ <div id="arrow"></div>
351
+ <slot name="content"></slot>
352
+ </div>
353
+ </dialog>
354
+ `);
355
+ });
356
+ });
253
357
  });
254
-
255
- // has no children as content gets moved to the body
256
- expect(shadowHost.children.length).to.equal(0);
257
- ctrl.teardown();
258
-
259
- // restores original light dom in teardown
260
- expect(shadowHost.children.length).to.equal(1);
261
358
  });
262
- });
263
359
 
264
- describe('Node Configuration', () => {
265
- it('accepts an .contentNode<Node> to directly set content', async () => {
266
- const ctrl = new OverlayController({
267
- ...withGlobalTestConfig(),
268
- contentNode: /** @type {HTMLElement} */ (await fixture('<p>direct node</p>')),
360
+ describe('Invoker / Reference', async () => {
361
+ it('accepts a .invokerNode to directly set invoker', async () => {
362
+ const myInvokerNode = /** @type {HTMLElement} */ (fixtureSync('<button>invoke</button>'));
363
+ const ctrl = new OverlayController({
364
+ ...withGlobalTestConfig(),
365
+ invokerNode: myInvokerNode,
366
+ });
367
+ expect(ctrl.invokerNode).to.equal(myInvokerNode);
368
+ expect(ctrl.referenceNode).to.equal(undefined);
269
369
  });
270
- expect(ctrl.contentNode).to.have.trimmed.text('direct node');
271
- });
272
370
 
273
- it('accepts an .invokerNode<Node> to directly set invoker', async () => {
274
- const ctrl = new OverlayController({
275
- ...withGlobalTestConfig(),
276
- invokerNode: /** @type {HTMLElement} */ (await fixture('<button>invoke</button>')),
371
+ it('accepts a .referenceNode as positioning anchor different from .invokerNode', async () => {
372
+ const myInvokerNode = /** @type {HTMLElement} */ (fixtureSync('<button>invoke</button>'));
373
+ const myReferenceNode = /** @type {HTMLElement} */ (fixtureSync('<div>anchor</div>'));
374
+ const ctrl = new OverlayController({
375
+ ...withGlobalTestConfig(),
376
+ invokerNode: myInvokerNode,
377
+ referenceNode: myReferenceNode,
378
+ });
379
+ expect(ctrl.referenceNode).to.equal(myReferenceNode);
380
+ expect(ctrl.invokerNode).to.not.equal(ctrl.referenceNode);
277
381
  });
278
- expect(ctrl.invokerNode).to.have.trimmed.text('invoke');
279
382
  });
280
383
 
281
- describe('When contentWrapperNode projects contentNode', () => {
282
- it('recognizes projected contentNode', async () => {
283
- const shadowHost = document.createElement('div');
284
- shadowHost.attachShadow({ mode: 'open' });
285
- /** @type {ShadowRoot} */ (shadowHost.shadowRoot).innerHTML = `
286
- <div id="contentWrapperNode">
287
- <slot name="contentNode"></slot>
288
- <my-arrow></my-arrow>
289
- </div>
290
- `;
291
- const contentNode = document.createElement('div');
292
- contentNode.slot = 'contentNode';
293
- shadowHost.appendChild(contentNode);
384
+ describe('Backdrop', () => {
385
+ it('creates a .backdropNode inside <dialog> for guaranteed top layer paints and positioning opportunities', async () => {
386
+ const tagString = defineCE(
387
+ class extends HTMLElement {
388
+ constructor() {
389
+ super();
390
+ this.attachShadow({ mode: 'open' });
391
+ }
294
392
 
295
- // Ensure the contentNode is connected to DOM
296
- document.body.appendChild(shadowHost);
393
+ connectedCallback() {
394
+ /** @type {ShadowRoot} */
395
+ (this.shadowRoot).innerHTML = '<slot name="content"></slot>';
396
+ this.innerHTML = '<div slot="content">projected</div>';
397
+ }
398
+ },
399
+ );
400
+
401
+ const el = /** @type {HTMLElement} */ (fixtureSync(`<${tagString}></${tagString}>`));
402
+ const myContentNode = /** @type {HTMLElement} */ (el.querySelector('[slot=content]'));
297
403
 
298
404
  const ctrl = new OverlayController({
299
- ...withLocalTestConfig(),
300
- contentNode,
301
- contentWrapperNode: /** @type {HTMLElement} */ (
302
- /** @type {ShadowRoot} */ (shadowHost.shadowRoot).getElementById('contentWrapperNode')
303
- ),
405
+ ...withGlobalTestConfig(),
406
+ contentNode: myContentNode,
407
+ hasBackdrop: true,
304
408
  });
305
409
 
306
- expect(ctrl.__isContentNodeProjected).to.be.true;
410
+ normalizeOverlayContentWapper(ctrl.contentWrapperNode);
411
+
412
+ // The total dom structure created...
413
+ expect(el).shadowDom.to.equal(
414
+ `
415
+ <dialog data-overlay-outer-wrapper="" open="" role="none" style="${wrappingDialogNodeStyle}">
416
+ <div class="overlays__backdrop"></div>
417
+ <div data-id="content-wrapper">
418
+ <slot name="content">
419
+ </slot>
420
+ </div>
421
+ </dialog>
422
+ `,
423
+ );
307
424
  });
308
425
  });
309
426
 
@@ -446,6 +563,18 @@ describe('OverlayController', () => {
446
563
  document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
447
564
  expect(ctrl.isShown).to.be.true;
448
565
  });
566
+
567
+ it('does not hide when [escape] is pressed with modal <dialog> and "hidesOnEsc" is false', async () => {
568
+ const ctrl = new OverlayController({
569
+ ...withGlobalTestConfig(),
570
+ trapsKeyboardFocus: true,
571
+ hidesOnEsc: false,
572
+ });
573
+ await ctrl.show();
574
+ ctrl.contentNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
575
+ await aTimeout(0);
576
+ expect(ctrl.isShown).to.be.true;
577
+ });
449
578
  });
450
579
 
451
580
  describe('hidesOnOutsideEsc', () => {
@@ -760,6 +889,35 @@ describe('OverlayController', () => {
760
889
  expect(document.activeElement).to.equal(input);
761
890
  });
762
891
 
892
+ it('supports elementToFocusAfterHide option when shadowRoot involved involved', async () => {
893
+ const input = /** @type {HTMLElement} */ (await fixture('<input />'));
894
+ const contentNode = /** @type {HTMLElement} */ (
895
+ await fixture('<div><textarea></textarea></div>')
896
+ );
897
+
898
+ const shadowEl = document.createElement('div');
899
+ shadowEl.attachShadow({ mode: 'open' });
900
+ /** @type {ShadowRoot} */ (shadowEl.shadowRoot).innerHTML = `<slot></slot>`;
901
+ shadowEl.appendChild(contentNode);
902
+ document.body.appendChild(shadowEl);
903
+
904
+ const ctrl = new OverlayController({
905
+ ...withGlobalTestConfig(),
906
+ elementToFocusAfterHide: input,
907
+ contentNode,
908
+ });
909
+
910
+ await ctrl.show();
911
+ const textarea = /** @type {HTMLTextAreaElement} */ (contentNode.querySelector('textarea'));
912
+ textarea.focus();
913
+ expect(document.activeElement).to.equal(textarea);
914
+
915
+ await ctrl.hide();
916
+ expect(document.activeElement).to.equal(input);
917
+
918
+ document.body.removeChild(shadowEl);
919
+ });
920
+
763
921
  it(`only sets focus when outside world didn't take over already`, async () => {
764
922
  const input = /** @type {HTMLElement} */ (await fixture('<input />'));
765
923
  const outsideButton = /** @type {HTMLButtonElement} */ (await fixture('<button></button>'));
@@ -810,10 +968,10 @@ describe('OverlayController', () => {
810
968
  });
811
969
 
812
970
  await ctrl.show();
813
- expect(getComputedStyle(document.body).overflow).to.equal('hidden');
971
+ expect(Array.from(document.body.classList)).to.contain('overlays-scroll-lock');
814
972
 
815
973
  await ctrl.hide();
816
- expect(getComputedStyle(document.body).overflow).to.equal('visible');
974
+ expect(Array.from(document.body.classList)).to.not.contain('overlays-scroll-lock');
817
975
  });
818
976
 
819
977
  it('keeps preventing of scrolling when multiple overlays are opened and closed', async () => {
@@ -829,7 +987,7 @@ describe('OverlayController', () => {
829
987
  await ctrl0.show();
830
988
  await ctrl1.show();
831
989
  await ctrl1.hide();
832
- expect(getComputedStyle(document.body).overflow).to.equal('hidden');
990
+ expect(Array.from(document.body.classList)).to.contain('overlays-scroll-lock');
833
991
  });
834
992
  });
835
993
 
@@ -856,7 +1014,7 @@ describe('OverlayController', () => {
856
1014
  hasBackdrop: true,
857
1015
  });
858
1016
  await controllerWithBackdrop.show();
859
- expect(controllerWithBackdrop.backdropNode).to.have.class('global-overlays__backdrop');
1017
+ expect(controllerWithBackdrop.backdropNode).to.have.class('overlays__backdrop');
860
1018
  });
861
1019
 
862
1020
  it('reenables the backdrop when shown/hidden/shown', async () => {
@@ -865,10 +1023,10 @@ describe('OverlayController', () => {
865
1023
  hasBackdrop: true,
866
1024
  });
867
1025
  await ctrl.show();
868
- expect(ctrl.backdropNode).to.have.class('global-overlays__backdrop');
1026
+ expect(ctrl.backdropNode).to.have.class('overlays__backdrop');
869
1027
  await ctrl.hide();
870
1028
  await ctrl.show();
871
- expect(ctrl.backdropNode).to.have.class('global-overlays__backdrop');
1029
+ expect(ctrl.backdropNode).to.have.class('overlays__backdrop');
872
1030
  });
873
1031
 
874
1032
  it('adds and stacks backdrops if .hasBackdrop is enabled', async () => {
@@ -877,14 +1035,14 @@ describe('OverlayController', () => {
877
1035
  hasBackdrop: true,
878
1036
  });
879
1037
  await ctrl0.show();
880
- expect(ctrl0.backdropNode).to.have.class('global-overlays__backdrop');
1038
+ expect(ctrl0.backdropNode).to.have.class('overlays__backdrop');
881
1039
 
882
1040
  const ctrl1 = new OverlayController({
883
1041
  ...withGlobalTestConfig(),
884
1042
  hasBackdrop: false,
885
1043
  });
886
1044
  await ctrl1.show();
887
- expect(ctrl0.backdropNode).to.have.class('global-overlays__backdrop');
1045
+ expect(ctrl0.backdropNode).to.have.class('overlays__backdrop');
888
1046
  expect(ctrl1.backdropNode).to.be.undefined;
889
1047
 
890
1048
  const ctrl2 = new OverlayController({
@@ -893,9 +1051,9 @@ describe('OverlayController', () => {
893
1051
  });
894
1052
  await ctrl2.show();
895
1053
 
896
- expect(ctrl0.backdropNode).to.have.class('global-overlays__backdrop');
1054
+ expect(ctrl0.backdropNode).to.have.class('overlays__backdrop');
897
1055
  expect(ctrl1.backdropNode).to.be.undefined;
898
- expect(ctrl2.backdropNode).to.have.class('global-overlays__backdrop');
1056
+ expect(ctrl2.backdropNode).to.have.class('overlays__backdrop');
899
1057
  });
900
1058
  });
901
1059
 
@@ -1003,24 +1161,24 @@ describe('OverlayController', () => {
1003
1161
  await ctrl0.show();
1004
1162
  await ctrl1.show();
1005
1163
  await ctrl2.show(); // blocking
1006
- expect(ctrl0.content).to.not.be.displayed;
1007
- expect(ctrl1.content).to.not.be.displayed;
1008
- expect(ctrl2.content).to.be.displayed;
1164
+ expect(ctrl0.__wrappingDialogNode).to.not.be.displayed;
1165
+ expect(ctrl1.__wrappingDialogNode).to.not.be.displayed;
1166
+ expect(ctrl2.__wrappingDialogNode).to.be.displayed;
1009
1167
 
1010
1168
  await ctrl3.show();
1011
1169
  await ctrl3._showComplete;
1012
- expect(ctrl3.content).to.be.displayed;
1170
+ expect(ctrl3.__wrappingDialogNode).to.be.displayed;
1013
1171
 
1014
1172
  await ctrl2.hide();
1015
1173
  await ctrl2._hideComplete;
1016
- expect(ctrl0.content).to.be.displayed;
1017
- expect(ctrl1.content).to.be.displayed;
1174
+ expect(ctrl0.__wrappingDialogNode).to.be.displayed;
1175
+ expect(ctrl1.__wrappingDialogNode).to.be.displayed;
1018
1176
 
1019
1177
  await ctrl2.show(); // blocking
1020
- expect(ctrl0.content).to.not.be.displayed;
1021
- expect(ctrl1.content).to.not.be.displayed;
1022
- expect(ctrl2.content).to.be.displayed;
1023
- expect(ctrl3.content).to.not.be.displayed;
1178
+ expect(ctrl0.__wrappingDialogNode).to.not.be.displayed;
1179
+ expect(ctrl1.__wrappingDialogNode).to.not.be.displayed;
1180
+ expect(ctrl2.__wrappingDialogNode).to.be.displayed;
1181
+ expect(ctrl3.__wrappingDialogNode).to.not.be.displayed;
1024
1182
  });
1025
1183
 
1026
1184
  it('keeps backdrop status when used in combination with blocking', async () => {
@@ -1197,11 +1355,11 @@ describe('OverlayController', () => {
1197
1355
 
1198
1356
  ctrl.hide();
1199
1357
 
1200
- expect(getComputedStyle(ctrl.contentWrapperNode).display).to.equal('block');
1358
+ expect(getComputedStyle(ctrl.content).display).to.equal('block');
1201
1359
  setTimeout(() => {
1202
1360
  hideTransitionFinished();
1203
1361
  setTimeout(() => {
1204
- expect(getComputedStyle(ctrl.contentWrapperNode).display).to.equal('none');
1362
+ expect(getComputedStyle(ctrl.content).display).to.equal('none');
1205
1363
  done();
1206
1364
  }, 0);
1207
1365
  }, 0);
@@ -1285,15 +1443,11 @@ describe('OverlayController', () => {
1285
1443
  });
1286
1444
 
1287
1445
  ctrl.show();
1288
- expect(
1289
- ctrl.contentWrapperNode.classList.contains('global-overlays__overlay-container--center'),
1290
- );
1446
+ expect(ctrl.contentWrapperNode.classList.contains('overlays__overlay-container--center'));
1291
1447
  expect(ctrl.isShown).to.be.true;
1292
1448
 
1293
1449
  ctrl.updateConfig({ viewportConfig: { placement: 'top-right' } });
1294
- expect(
1295
- ctrl.contentWrapperNode.classList.contains('global-overlays__overlay-container--top-right'),
1296
- );
1450
+ expect(ctrl.contentWrapperNode.classList.contains('overlays__overlay-container--top-right'));
1297
1451
  expect(ctrl.isShown).to.be.true;
1298
1452
  });
1299
1453
  });
@@ -1379,89 +1533,9 @@ describe('OverlayController', () => {
1379
1533
  expect(properlyInstantiated).to.be.true;
1380
1534
  });
1381
1535
 
1382
- it('adds attributes inert and aria-hidden="true" on all siblings of rootNode if an overlay is shown', async () => {
1383
- const ctrl = new OverlayController({
1384
- ...withGlobalTestConfig(),
1385
- trapsKeyboardFocus: true,
1386
- });
1387
-
1388
- const sibling1 = document.createElement('div');
1389
- const sibling2 = document.createElement('div');
1390
- document.body.insertBefore(sibling1, ctrl.manager.globalRootNode);
1391
- document.body.appendChild(sibling2);
1392
-
1393
- await ctrl.show();
1394
-
1395
- [sibling1, sibling2].forEach(sibling => {
1396
- expect(sibling).to.have.attribute('aria-hidden', 'true');
1397
- expect(sibling).to.have.attribute('inert');
1398
- });
1399
- expect(ctrl.content.hasAttribute('aria-hidden')).to.be.false;
1400
- expect(ctrl.content.hasAttribute('inert')).to.be.false;
1401
-
1402
- await ctrl.hide();
1403
-
1404
- [sibling1, sibling2].forEach(sibling => {
1405
- expect(sibling).to.not.have.attribute('aria-hidden');
1406
- expect(sibling).to.not.have.attribute('inert');
1407
- });
1408
-
1409
- // cleanup
1410
- document.body.removeChild(sibling1);
1411
- document.body.removeChild(sibling2);
1412
- });
1413
-
1414
- /**
1415
- * style.userSelect:
1416
- * - chrome: 'none'
1417
- * - rest: undefined
1418
- *
1419
- * style.pointerEvents:
1420
- * - chrome: auto
1421
- * - IE11: visiblePainted
1422
- */
1423
- it('disables pointer events and selection on inert elements', async () => {
1424
- const ctrl = new OverlayController({
1425
- ...withGlobalTestConfig(),
1426
- trapsKeyboardFocus: true,
1427
- });
1428
-
1429
- // show+hide are needed to create a root node
1430
- await ctrl.show();
1431
- await ctrl.hide();
1432
-
1433
- const sibling1 = document.createElement('div');
1434
- const sibling2 = document.createElement('div');
1435
- document.body.insertBefore(sibling1, ctrl.manager.globalRootNode);
1436
- document.body.appendChild(sibling2);
1437
-
1438
- await ctrl.show();
1439
-
1440
- [sibling1, sibling2].forEach(sibling => {
1441
- expect(window.getComputedStyle(sibling).userSelect).to.be.oneOf(['none', undefined]);
1442
- expect(window.getComputedStyle(sibling).pointerEvents).to.equal('none');
1443
- });
1444
-
1445
- expect(window.getComputedStyle(ctrl.contentNode).userSelect).to.be.oneOf(['auto', undefined]);
1446
- expect(window.getComputedStyle(ctrl.contentNode).pointerEvents).to.be.oneOf([
1447
- 'auto',
1448
- 'visiblePainted',
1449
- ]);
1450
-
1451
- await ctrl.hide();
1452
-
1453
- [sibling1, sibling2].forEach(sibling => {
1454
- expect(window.getComputedStyle(sibling).userSelect).to.be.oneOf(['auto', undefined]);
1455
- expect(window.getComputedStyle(sibling).pointerEvents).to.be.oneOf([
1456
- 'auto',
1457
- 'visiblePainted',
1458
- ]);
1459
- });
1460
-
1461
- // cleanup
1462
- document.body.removeChild(sibling1);
1463
- document.body.removeChild(sibling2);
1464
- });
1536
+ // TODO: check if we covered all functionality. "Inertness" should be handled by the platform with a modal overlay...
1537
+ it.skip('adds attributes inert and aria-hidden="true" on all siblings of rootNode if an overlay is shown', async () => {});
1538
+ it.skip('disables pointer events and selection on inert elements', async () => {});
1465
1539
 
1466
1540
  describe('Tooltip', () => {
1467
1541
  it('adds [aria-describedby] on invoker', async () => {
@@ -1616,47 +1690,6 @@ describe('OverlayController', () => {
1616
1690
  }).to.throw('[OverlayController] You need to provide a .contentNode');
1617
1691
  });
1618
1692
 
1619
- it('throws if contentNodeWrapper is not provided for projected contentNode', async () => {
1620
- const shadowHost = document.createElement('div');
1621
- shadowHost.attachShadow({ mode: 'open' });
1622
- /** @type {ShadowRoot} */ (shadowHost.shadowRoot).innerHTML = `
1623
- <div id="contentWrapperNode">
1624
- <slot name="contentNode"></slot>
1625
- <my-arrow></my-arrow>
1626
- </div>
1627
- `;
1628
- const contentNode = document.createElement('div');
1629
- contentNode.slot = 'contentNode';
1630
- shadowHost.appendChild(contentNode);
1631
-
1632
- // Ensure the contentNode is connected to DOM
1633
- document.body.appendChild(shadowHost);
1634
-
1635
- expect(() => {
1636
- new OverlayController({
1637
- ...withLocalTestConfig(),
1638
- contentNode,
1639
- });
1640
- }).to.throw(
1641
- '[OverlayController] You need to provide a .contentWrapperNode when .contentNode is projected',
1642
- );
1643
- });
1644
-
1645
- it('throws if placementMode is global for a tooltip', async () => {
1646
- const contentNode = document.createElement('div');
1647
- document.body.appendChild(contentNode);
1648
- expect(() => {
1649
- new OverlayController({
1650
- placementMode: 'global',
1651
- contentNode,
1652
- isTooltip: true,
1653
- handlesAccessibility: true,
1654
- });
1655
- }).to.throw(
1656
- '[OverlayController] .isTooltip should be configured with .placementMode "local"',
1657
- );
1658
- });
1659
-
1660
1693
  it('throws if handlesAccessibility is false for a tooltip', async () => {
1661
1694
  const contentNode = document.createElement('div');
1662
1695
  document.body.appendChild(contentNode);