@spectrum-web-components/overlay 1.0.2 → 1.0.3

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 (74) hide show
  1. package/LICENSE +201 -0
  2. package/custom-elements.json +189 -73
  3. package/package.json +8 -7
  4. package/src/Overlay.d.ts +348 -18
  5. package/src/Overlay.dev.js +271 -12
  6. package/src/Overlay.dev.js.map +2 -2
  7. package/src/Overlay.js +4 -4
  8. package/src/Overlay.js.map +3 -3
  9. package/src/PlacementController.d.ts +118 -1
  10. package/src/PlacementController.dev.js +75 -0
  11. package/src/PlacementController.dev.js.map +2 -2
  12. package/src/PlacementController.js.map +2 -2
  13. package/src/overlay.css.dev.js +1 -1
  14. package/src/overlay.css.dev.js.map +1 -1
  15. package/src/overlay.css.js +1 -1
  16. package/src/overlay.css.js.map +1 -1
  17. package/stories/index.js +48 -0
  18. package/stories/index.js.map +7 -0
  19. package/stories/overlay-directive.stories.js +324 -0
  20. package/stories/overlay-directive.stories.js.map +7 -0
  21. package/stories/overlay-element.stories.js +675 -0
  22. package/stories/overlay-element.stories.js.map +7 -0
  23. package/stories/overlay-story-components.js +338 -0
  24. package/stories/overlay-story-components.js.map +7 -0
  25. package/stories/overlay.stories.js +1397 -0
  26. package/stories/overlay.stories.js.map +7 -0
  27. package/test/benchmark/basic-test.js +40 -0
  28. package/test/benchmark/basic-test.js.map +7 -0
  29. package/test/benchmark/directive-test.js +43 -0
  30. package/test/benchmark/directive-test.js.map +7 -0
  31. package/test/benchmark/element-test.js +40 -0
  32. package/test/benchmark/element-test.js.map +7 -0
  33. package/test/benchmark/lazy-test.js +47 -0
  34. package/test/benchmark/lazy-test.js.map +7 -0
  35. package/test/index.js +605 -0
  36. package/test/index.js.map +7 -0
  37. package/test/overlay-directive.test-vrt.js +5 -0
  38. package/test/overlay-directive.test-vrt.js.map +7 -0
  39. package/test/overlay-directive.test.js +162 -0
  40. package/test/overlay-directive.test.js.map +7 -0
  41. package/test/overlay-element.test-vrt.js +5 -0
  42. package/test/overlay-element.test-vrt.js.map +7 -0
  43. package/test/overlay-element.test.js +934 -0
  44. package/test/overlay-element.test.js.map +7 -0
  45. package/test/overlay-lifecycle.test.js +139 -0
  46. package/test/overlay-lifecycle.test.js.map +7 -0
  47. package/test/overlay-memory.test.js +10 -0
  48. package/test/overlay-memory.test.js.map +7 -0
  49. package/test/overlay-timer.test.js +118 -0
  50. package/test/overlay-timer.test.js.map +7 -0
  51. package/test/overlay-trigger-click.test.js +164 -0
  52. package/test/overlay-trigger-click.test.js.map +7 -0
  53. package/test/overlay-trigger-directive.test.js +75 -0
  54. package/test/overlay-trigger-directive.test.js.map +7 -0
  55. package/test/overlay-trigger-extended.test.js +235 -0
  56. package/test/overlay-trigger-extended.test.js.map +7 -0
  57. package/test/overlay-trigger-hover-click.test.js +225 -0
  58. package/test/overlay-trigger-hover-click.test.js.map +7 -0
  59. package/test/overlay-trigger-hover.test.js +308 -0
  60. package/test/overlay-trigger-hover.test.js.map +7 -0
  61. package/test/overlay-trigger-longpress.test.js +549 -0
  62. package/test/overlay-trigger-longpress.test.js.map +7 -0
  63. package/test/overlay-trigger-sync.test.js +5 -0
  64. package/test/overlay-trigger-sync.test.js.map +7 -0
  65. package/test/overlay-trigger.test.js +5 -0
  66. package/test/overlay-trigger.test.js.map +7 -0
  67. package/test/overlay-update.test.js +28 -0
  68. package/test/overlay-update.test.js.map +7 -0
  69. package/test/overlay-v1.test.js +569 -0
  70. package/test/overlay-v1.test.js.map +7 -0
  71. package/test/overlay.test-vrt.js +5 -0
  72. package/test/overlay.test-vrt.js.map +7 -0
  73. package/test/overlay.test.js +776 -0
  74. package/test/overlay.test.js.map +7 -0
@@ -41,14 +41,14 @@ import {
41
41
  SlottableRequestEvent
42
42
  } from "./slottable-request-event.dev.js";
43
43
  import styles from "./overlay.css.js";
44
- const supportsPopover = "showPopover" in document.createElement("div");
45
- let OverlayFeatures = OverlayDialog(AbstractOverlay);
46
- if (supportsPopover) {
47
- OverlayFeatures = OverlayPopover(OverlayFeatures);
44
+ const browserSupportsPopover = "showPopover" in document.createElement("div");
45
+ let ComputedOverlayBase = OverlayDialog(AbstractOverlay);
46
+ if (browserSupportsPopover) {
47
+ ComputedOverlayBase = OverlayPopover(ComputedOverlayBase);
48
48
  } else {
49
- OverlayFeatures = OverlayNoPopover(OverlayFeatures);
49
+ ComputedOverlayBase = OverlayNoPopover(ComputedOverlayBase);
50
50
  }
51
- const _Overlay = class _Overlay extends OverlayFeatures {
51
+ const _Overlay = class _Overlay extends ComputedOverlayBase {
52
52
  constructor() {
53
53
  super(...arguments);
54
54
  this._delayed = false;
@@ -57,14 +57,34 @@ const _Overlay = class _Overlay extends OverlayFeatures {
57
57
  this._open = false;
58
58
  /**
59
59
  * The state in which the last `request-slottable` event was dispatched.
60
- * Do not allow overlays from dispatching the same state twice in a row.
60
+ *
61
+ * This property ensures that overlays do not dispatch the same state twice in a row.
62
+ *
63
+ * @type {boolean}
64
+ * @default false
61
65
  */
62
66
  this.lastRequestSlottableState = false;
63
67
  this.receivesFocus = "auto";
64
68
  this._state = "closed";
65
69
  this.triggerElement = null;
66
70
  this.type = "auto";
71
+ /**
72
+ * Tracks whether the overlay was previously open.
73
+ * This is used to restore the open state when re-enabling the overlay.
74
+ *
75
+ * @type {boolean}
76
+ * @default false
77
+ */
67
78
  this.wasOpen = false;
79
+ /**
80
+ * Handles the focus out event to close the overlay if the focus moves outside of it.
81
+ *
82
+ * This method ensures that the overlay is closed when the focus moves to an element
83
+ * outside of the overlay, unless the focus is moved to a related element.
84
+ *
85
+ * @private
86
+ * @param {FocusEvent} event - The focus out event.
87
+ */
68
88
  this.closeOnFocusOut = (event) => {
69
89
  if (!event.relatedTarget) {
70
90
  return;
@@ -107,9 +127,24 @@ const _Overlay = class _Overlay extends OverlayFeatures {
107
127
  this.wasOpen = false;
108
128
  }
109
129
  }
130
+ /**
131
+ * Determines if the overlay has a non-virtual trigger element.
132
+ *
133
+ * @returns {boolean} `true` if the trigger element is not a virtual trigger, otherwise `false`.
134
+ */
110
135
  get hasNonVirtualTrigger() {
111
136
  return !!this.triggerElement && !(this.triggerElement instanceof VirtualTrigger);
112
137
  }
138
+ /**
139
+ * Provides an instance of the `PlacementController` for managing the positioning
140
+ * of the overlay relative to its trigger element.
141
+ *
142
+ * If the `PlacementController` instance does not already exist, it is created and
143
+ * assigned to the `_placementController` property.
144
+ *
145
+ * @protected
146
+ * @returns {PlacementController} The `PlacementController` instance.
147
+ */
113
148
  get placementController() {
114
149
  if (!this._placementController) {
115
150
  this._placementController = new PlacementController(this);
@@ -146,15 +181,36 @@ const _Overlay = class _Overlay extends OverlayFeatures {
146
181
  }
147
182
  this.requestUpdate("state", oldState);
148
183
  }
184
+ /**
185
+ * Provides an instance of the `ElementResolutionController` for managing the element
186
+ * that the overlay should be associated with. If the instance does not already exist,
187
+ * it is created and assigned to the `_elementResolver` property.
188
+ *
189
+ * @protected
190
+ * @returns {ElementResolutionController} The `ElementResolutionController` instance.
191
+ */
149
192
  get elementResolver() {
150
193
  if (!this._elementResolver) {
151
194
  this._elementResolver = new ElementResolutionController(this);
152
195
  }
153
196
  return this._elementResolver;
154
197
  }
198
+ /**
199
+ * Determines if the overlay uses a dialog.
200
+ * Returns `true` if the overlay type is "modal" or "page".
201
+ *
202
+ * @private
203
+ * @returns {boolean} `true` if the overlay uses a dialog, otherwise `false`.
204
+ */
155
205
  get usesDialog() {
156
206
  return this.type === "modal" || this.type === "page";
157
207
  }
208
+ /**
209
+ * Determines the value for the popover attribute based on the overlay type.
210
+ *
211
+ * @private
212
+ * @returns {'auto' | 'manual' | undefined} The popover value or undefined if not applicable.
213
+ */
158
214
  get popoverValue() {
159
215
  const hasPopoverAttribute = "popover" in this;
160
216
  if (!hasPopoverAttribute) {
@@ -170,14 +226,30 @@ const _Overlay = class _Overlay extends OverlayFeatures {
170
226
  return this.type;
171
227
  }
172
228
  }
173
- get requiresPosition() {
229
+ /**
230
+ * Determines if the overlay requires positioning based on its type and state.
231
+ *
232
+ * @protected
233
+ * @returns {boolean} True if the overlay requires positioning, otherwise false.
234
+ */
235
+ get requiresPositioning() {
174
236
  if (this.type === "page" || !this.open) return false;
175
237
  if (!this.triggerElement || !this.placement && this.type !== "hint")
176
238
  return false;
177
239
  return true;
178
240
  }
241
+ /**
242
+ * Manages the positioning of the overlay relative to its trigger element.
243
+ *
244
+ * This method calculates the necessary parameters for positioning the overlay,
245
+ * such as offset, placement, and tip padding, and then delegates the actual
246
+ * positioning to the `PlacementController`.
247
+ *
248
+ * @protected
249
+ * @override
250
+ */
179
251
  managePosition() {
180
- if (!this.requiresPosition || !this.open) return;
252
+ if (!this.requiresPositioning || !this.open) return;
181
253
  const offset = this.offset || 0;
182
254
  const trigger = this.triggerElement;
183
255
  const placement = this.placement || "right";
@@ -190,6 +262,16 @@ const _Overlay = class _Overlay extends OverlayFeatures {
190
262
  type: this.type
191
263
  });
192
264
  }
265
+ /**
266
+ * Manages the process of opening the popover.
267
+ *
268
+ * This method handles the necessary steps to open the popover, including managing delays,
269
+ * ensuring the popover is in the DOM, making transitions, and applying focus.
270
+ *
271
+ * @protected
272
+ * @override
273
+ * @returns {Promise<void>} A promise that resolves when the popover has been fully opened.
274
+ */
193
275
  async managePopoverOpen() {
194
276
  super.managePopoverOpen();
195
277
  const targetOpenState = this.open;
@@ -210,6 +292,18 @@ const _Overlay = class _Overlay extends OverlayFeatures {
210
292
  }
211
293
  await this.applyFocus(targetOpenState, focusEl);
212
294
  }
295
+ /**
296
+ * Applies focus to the appropriate element after the popover has been opened.
297
+ *
298
+ * This method handles the focus management for the overlay, ensuring that the correct
299
+ * element receives focus based on the overlay's type and state.
300
+ *
301
+ * @protected
302
+ * @override
303
+ * @param {boolean} targetOpenState - The target open state of the overlay.
304
+ * @param {HTMLElement | null} focusEl - The element to focus after opening the popover.
305
+ * @returns {Promise<void>} A promise that resolves when the focus has been applied.
306
+ */
213
307
  async applyFocus(targetOpenState, focusEl) {
214
308
  if (this.receivesFocus === "false" || this.type === "hint") {
215
309
  return;
@@ -224,6 +318,15 @@ const _Overlay = class _Overlay extends OverlayFeatures {
224
318
  }
225
319
  focusEl == null ? void 0 : focusEl.focus();
226
320
  }
321
+ /**
322
+ * Returns focus to the trigger element if the overlay is closed.
323
+ *
324
+ * This method ensures that focus is returned to the trigger element when the overlay is closed,
325
+ * unless the overlay is of type "hint" or the focus is already outside the overlay.
326
+ *
327
+ * @protected
328
+ * @override
329
+ */
227
330
  returnFocus() {
228
331
  var _a;
229
332
  if (this.open || this.type === "hint") return;
@@ -248,6 +351,16 @@ const _Overlay = class _Overlay extends OverlayFeatures {
248
351
  this.triggerElement.focus();
249
352
  }
250
353
  }
354
+ /**
355
+ * Manages the process of opening or closing the overlay.
356
+ *
357
+ * This method handles the necessary steps to open or close the overlay, including updating the state,
358
+ * managing the overlay stack, and handling focus events.
359
+ *
360
+ * @protected
361
+ * @param {boolean} oldOpen - The previous open state of the overlay.
362
+ * @returns {Promise<void>} A promise that resolves when the overlay has been fully managed.
363
+ */
251
364
  async manageOpen(oldOpen) {
252
365
  if (!this.isConnected && this.open) return;
253
366
  if (!this.hasUpdated) {
@@ -305,6 +418,14 @@ const _Overlay = class _Overlay extends OverlayFeatures {
305
418
  }
306
419
  }
307
420
  }
421
+ /**
422
+ * Binds event handling strategies to the overlay based on the specified trigger interaction.
423
+ *
424
+ * This method sets up the appropriate event handling strategy for the overlay, ensuring that
425
+ * it responds correctly to user interactions such as clicks, hovers, or long presses.
426
+ *
427
+ * @protected
428
+ */
308
429
  bindEvents() {
309
430
  var _a;
310
431
  (_a = this.strategy) == null ? void 0 : _a.abort();
@@ -318,11 +439,29 @@ const _Overlay = class _Overlay extends OverlayFeatures {
318
439
  }
319
440
  );
320
441
  }
442
+ /**
443
+ * Handles the `beforetoggle` event to manage the overlay's state.
444
+ *
445
+ * This method checks the new state of the event and calls `handleBrowserClose`
446
+ * if the new state is not 'open'.
447
+ *
448
+ * @protected
449
+ * @param {Event & { newState: string }} event - The `beforetoggle` event with the new state.
450
+ */
321
451
  handleBeforetoggle(event) {
322
452
  if (event.newState !== "open") {
323
453
  this.handleBrowserClose(event);
324
454
  }
325
455
  }
456
+ /**
457
+ * Handles the browser's close event to manage the overlay's state.
458
+ *
459
+ * This method stops the propagation of the event and closes the overlay if it is not
460
+ * actively opening. If the overlay is actively opening, it calls `manuallyKeepOpen`.
461
+ *
462
+ * @protected
463
+ * @param {Event} event - The browser's close event.
464
+ */
326
465
  handleBrowserClose(event) {
327
466
  var _a;
328
467
  event.stopPropagation();
@@ -332,11 +471,28 @@ const _Overlay = class _Overlay extends OverlayFeatures {
332
471
  }
333
472
  this.manuallyKeepOpen();
334
473
  }
474
+ /**
475
+ * Manually keeps the overlay open.
476
+ *
477
+ * This method sets the overlay to open, allows placement updates, and manages the open state.
478
+ *
479
+ * @public
480
+ * @override
481
+ */
335
482
  manuallyKeepOpen() {
336
483
  this.open = true;
337
484
  this.placementController.allowPlacementUpdate = true;
338
485
  this.manageOpen(false);
339
486
  }
487
+ /**
488
+ * Handles the `slotchange` event to manage the overlay's state.
489
+ *
490
+ * This method checks if there are any elements in the slot. If there are no elements,
491
+ * it releases the description from the strategy. If there are elements and the trigger
492
+ * is non-virtual, it prepares the description for the trigger element.
493
+ *
494
+ * @protected
495
+ */
340
496
  handleSlotchange() {
341
497
  var _a, _b;
342
498
  if (!this.elements.length) {
@@ -347,11 +503,30 @@ const _Overlay = class _Overlay extends OverlayFeatures {
347
503
  );
348
504
  }
349
505
  }
506
+ /**
507
+ * Determines whether the overlay should prevent closing.
508
+ *
509
+ * This method checks the `willPreventClose` flag and resets it to `false`.
510
+ * It returns the value of the `willPreventClose` flag.
511
+ *
512
+ * @public
513
+ * @returns {boolean} `true` if the overlay should prevent closing, otherwise `false`.
514
+ */
350
515
  shouldPreventClose() {
351
516
  const shouldPreventClose = this.willPreventClose;
352
517
  this.willPreventClose = false;
353
518
  return shouldPreventClose;
354
519
  }
520
+ /**
521
+ * Requests slottable content for the overlay.
522
+ *
523
+ * This method dispatches a `SlottableRequestEvent` to request or remove slottable content
524
+ * based on the current open state of the overlay. It ensures that the same state is not
525
+ * dispatched twice in a row.
526
+ *
527
+ * @protected
528
+ * @override
529
+ */
355
530
  requestSlottable() {
356
531
  if (this.lastRequestSlottableState === this.open) {
357
532
  return;
@@ -367,6 +542,15 @@ const _Overlay = class _Overlay extends OverlayFeatures {
367
542
  );
368
543
  this.lastRequestSlottableState = this.open;
369
544
  }
545
+ /**
546
+ * Lifecycle method called before the component updates.
547
+ *
548
+ * This method handles various tasks before the component updates, such as setting an ID,
549
+ * managing the open state, resolving the trigger element, and binding events.
550
+ *
551
+ * @override
552
+ * @param {PropertyValues} changes - The properties that have changed.
553
+ */
370
554
  willUpdate(changes) {
371
555
  var _a;
372
556
  if (!this.hasAttribute("id")) {
@@ -395,6 +579,15 @@ const _Overlay = class _Overlay extends OverlayFeatures {
395
579
  this.bindEvents();
396
580
  }
397
581
  }
582
+ /**
583
+ * Lifecycle method called after the component updates.
584
+ *
585
+ * This method handles various tasks after the component updates, such as updating the placement
586
+ * attribute, resetting the overlay position, and clearing the overlay position based on the state.
587
+ *
588
+ * @override
589
+ * @param {PropertyValues} changes - The properties that have changed.
590
+ */
398
591
  updated(changes) {
399
592
  super.updated(changes);
400
593
  if (changes.has("placement")) {
@@ -411,23 +604,50 @@ const _Overlay = class _Overlay extends OverlayFeatures {
411
604
  this.placementController.clearOverlayPosition();
412
605
  }
413
606
  }
607
+ /**
608
+ * Renders the content of the overlay.
609
+ *
610
+ * This method returns a template result containing a slot element. The slot element
611
+ * listens for the `slotchange` event to manage the overlay's state.
612
+ *
613
+ * @protected
614
+ * @returns {TemplateResult} The template result containing the slot element.
615
+ */
414
616
  renderContent() {
415
617
  return html`
416
618
  <slot @slotchange=${this.handleSlotchange}></slot>
417
619
  `;
418
620
  }
621
+ /**
622
+ * Generates a style map for the dialog element.
623
+ *
624
+ * This method returns an object containing CSS custom properties for the dialog element.
625
+ * The `--swc-overlay-open-count` custom property is set to the current open count of overlays.
626
+ *
627
+ * @private
628
+ * @returns {StyleInfo} The style map for the dialog element.
629
+ */
419
630
  get dialogStyleMap() {
420
631
  return {
421
632
  "--swc-overlay-open-count": _Overlay.openCount.toString()
422
633
  };
423
634
  }
635
+ /**
636
+ * Renders the dialog element for the overlay.
637
+ *
638
+ * This method returns a template result containing a dialog element. The dialog element
639
+ * includes various attributes and event listeners to manage the overlay's state and behavior.
640
+ *
641
+ * @protected
642
+ * @returns {TemplateResult} The template result containing the dialog element.
643
+ */
424
644
  renderDialog() {
425
645
  return html`
426
646
  <dialog
427
647
  class="dialog"
428
648
  part="dialog"
429
649
  placement=${ifDefined(
430
- this.requiresPosition ? this.placement || "right" : void 0
650
+ this.requiresPositioning ? this.placement || "right" : void 0
431
651
  )}
432
652
  style=${styleMap(this.dialogStyleMap)}
433
653
  @close=${this.handleBrowserClose}
@@ -439,13 +659,22 @@ const _Overlay = class _Overlay extends OverlayFeatures {
439
659
  </dialog>
440
660
  `;
441
661
  }
662
+ /**
663
+ * Renders the popover element for the overlay.
664
+ *
665
+ * This method returns a template result containing a div element styled as a popover.
666
+ * The popover element includes various attributes and event listeners to manage the overlay's state and behavior.
667
+ *
668
+ * @protected
669
+ * @returns {TemplateResult} The template result containing the popover element.
670
+ */
442
671
  renderPopover() {
443
672
  return html`
444
673
  <div
445
674
  class="dialog"
446
675
  part="dialog"
447
676
  placement=${ifDefined(
448
- this.requiresPosition ? this.placement || "right" : void 0
677
+ this.requiresPositioning ? this.placement || "right" : void 0
449
678
  )}
450
679
  popover=${ifDefined(this.popoverValue)}
451
680
  style=${styleMap(this.dialogStyleMap)}
@@ -457,6 +686,15 @@ const _Overlay = class _Overlay extends OverlayFeatures {
457
686
  </div>
458
687
  `;
459
688
  }
689
+ /**
690
+ * Renders the overlay component.
691
+ *
692
+ * This method returns a template result containing either a dialog or popover element
693
+ * based on the overlay type. It also includes a slot for longpress descriptors.
694
+ *
695
+ * @override
696
+ * @returns {TemplateResult} The template result containing the overlay content.
697
+ */
460
698
  render() {
461
699
  const isDialog = this.type === "modal" || this.type === "page";
462
700
  return html`
@@ -464,6 +702,13 @@ const _Overlay = class _Overlay extends OverlayFeatures {
464
702
  <slot name="longpress-describedby-descriptor"></slot>
465
703
  `;
466
704
  }
705
+ /**
706
+ * Lifecycle method called when the component is added to the DOM.
707
+ *
708
+ * This method sets up event listeners and binds events if the component has already updated.
709
+ *
710
+ * @override
711
+ */
467
712
  connectedCallback() {
468
713
  super.connectedCallback();
469
714
  this.addEventListener("close", () => {
@@ -473,6 +718,13 @@ const _Overlay = class _Overlay extends OverlayFeatures {
473
718
  this.bindEvents();
474
719
  }
475
720
  }
721
+ /**
722
+ * Lifecycle method called when the component is removed from the DOM.
723
+ *
724
+ * This method releases the description from the strategy and updates the 'open' property.
725
+ *
726
+ * @override
727
+ */
476
728
  disconnectedCallback() {
477
729
  var _a;
478
730
  (_a = this.strategy) == null ? void 0 : _a.releaseDescription();
@@ -481,6 +733,14 @@ const _Overlay = class _Overlay extends OverlayFeatures {
481
733
  }
482
734
  };
483
735
  _Overlay.styles = [styles];
736
+ /**
737
+ * Tracks the number of overlays that have been opened.
738
+ *
739
+ * This static property is used to manage the stacking context of multiple overlays.
740
+ *
741
+ * @type {number}
742
+ * @default 1
743
+ */
484
744
  _Overlay.openCount = 1;
485
745
  __decorateClass([
486
746
  property({ type: Boolean })
@@ -495,7 +755,6 @@ __decorateClass([
495
755
  queryAssignedElements({
496
756
  flatten: true,
497
757
  selector: ':not([slot="longpress-describedby-descriptor"], slot)'
498
- // gather only elements slotted into the default slot
499
758
  })
500
759
  ], _Overlay.prototype, "elements", 2);
501
760
  __decorateClass([