@atlaskit/top-layer 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @atlaskit/top-layer
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`58c9d421e96b1`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/58c9d421e96b1) -
8
+ Improves the jsdom test polyfill (`testing/polyfill`) to better match WHATWG popover spec
9
+ semantics:
10
+ - Adds the `el.popover` IDL getter/setter so feature-detection (`supportsPopoverHint()` etc) works
11
+ in jsdom and `popover="hint"` is exercised in tests rather than silently falling back to
12
+ `popover="auto"`.
13
+ - Implements a separate hint stack so opening a `hint` does not close open `auto` popovers;
14
+ opening an `auto` closes all hints; light-dismiss and Escape walk both stacks per spec.
15
+ - Implements proper "topmost popover ancestor" lookup that walks both DOM ancestry and the invoker
16
+ chain (via `popovertarget`), so opening a popover from a button inside another popover does not
17
+ close the parent.
18
+ - Light-dismiss now uses pointerdown + pointerup (with `mousedown` + `mouseup` fallback) and a
19
+ same-target check so drag-out and drag-in gestures do not dismiss popovers.
20
+ - `togglePopover()` now returns the actual resulting open state.
21
+ - Per-method patching (`showPopover` / `hidePopover` / `togglePopover`) so partial native support
22
+ in future jsdom versions still patches the missing methods.
23
+ - Disconnected popovers are pruned from the open stacks (defends against test-pollution).
24
+ - Clears any inline `opacity: 0` set by callers (such as `use-anchor-positioning`'s
25
+ "hide-until-measured" guard) once the open toggle fires, since jsdom does not deliver
26
+ `ResizeObserver` callbacks.
27
+ - Treats `popover=""` (empty value) as `auto` per spec.
28
+
3
29
  ## 0.3.0
4
30
 
5
31
  ### Minor Changes
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ var _bindEventListener = require("bind-event-listener");
3
4
  /* eslint-disable compat/compat */
4
5
  /**
5
6
  * Popover API & HTMLDialogElement polyfills for jsdom.
@@ -9,10 +10,15 @@
9
10
  * including timing semantics validated against Chromium via Playwright.
10
11
  *
11
12
  * Behaviour:
12
- * - beforetoggle: sync, cancelable on show only. toggle: async task, not cancelable.
13
+ * - beforetoggle: sync, cancelable on popover show only. toggle: async task, never cancelable.
13
14
  * - Rapid show/hide coalesces into one toggle event (originalOldState to finalNewState).
14
15
  * - Dialog: showModal/show fire beforetoggle+toggle. close fires toggle then close event.
15
16
  * - Popover open state tracked via `data-popover-open` (no `:popover-open` in jsdom).
17
+ * - Auto popovers: maintain an open stack, perform light-dismiss on document mousedown
18
+ * outside the open chain, and dismiss the topmost on Escape.
19
+ * - On open, removes any inline `opacity: 0` from the popover element so callers that
20
+ * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-positioning`) become
21
+ * visible in jsdom, where layout-driven RO callbacks never fire.
16
22
  *
17
23
  * Companion `toBeVisible` patch lives at `./to-be-visible`.
18
24
  *
@@ -169,86 +175,550 @@ function createDialogToggleScheduler() {
169
175
  }
170
176
 
171
177
  // ─── Popover API ────────────────────────────────────────────────────────────
178
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html
172
179
 
173
- if (typeof HTMLElement !== 'undefined' && !HTMLElement.prototype.showPopover) {
174
- var schedulePopoverToggle = createToggleScheduler();
175
- HTMLElement.prototype.showPopover = function showPopover() {
176
- if (!this.hasAttribute('popover')) {
177
- throw new DOMException("Failed to execute 'showPopover' on 'HTMLElement': ".concat(describeElement({
180
+ // `popover` attribute valid values. Empty / "auto" -> "auto"; unknown -> "manual".
181
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#attr-popover
182
+
183
+ function getPopoverMode(_ref7) {
184
+ var element = _ref7.element;
185
+ if (!element.hasAttribute('popover')) {
186
+ return null;
187
+ }
188
+ var raw = element.getAttribute('popover');
189
+ if (raw === null || raw === '' || raw === 'auto') {
190
+ return 'auto';
191
+ }
192
+ if (raw === 'hint') {
193
+ return 'hint';
194
+ }
195
+ return 'manual';
196
+ }
197
+
198
+ // IDL property: `el.popover` reflects the popover content attribute. Setter
199
+ // writes verbatim; getter canonicalises (unknown -> "manual", empty -> "auto").
200
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#dom-popover
201
+
202
+ if (typeof HTMLElement !== 'undefined' && !Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'popover')) {
203
+ Object.defineProperty(HTMLElement.prototype, 'popover', {
204
+ configurable: true,
205
+ enumerable: true,
206
+ get: function get() {
207
+ return getPopoverMode({
178
208
  element: this
179
- }), " is not a popover element. Ensure the element has a 'popover' attribute (e.g. <div popover>)."), 'NotSupportedError');
209
+ });
210
+ },
211
+ set: function set(value) {
212
+ if (value === null) {
213
+ this.removeAttribute('popover');
214
+ return;
215
+ }
216
+ // Per spec, the IDL setter writes the value verbatim; the getter then
217
+ // canonicalises (so setting an unknown value is reflected as "manual"
218
+ // only when read back, but the attribute keeps the raw string).
219
+ this.setAttribute('popover', String(value));
180
220
  }
181
- if (!this.isConnected) {
182
- throw new DOMException("Failed to execute 'showPopover' on 'HTMLElement': ".concat(describeElement({
183
- element: this
184
- }), " is not connected to the document. Ensure the element has been added to the DOM (e.g. via render() or document.body.appendChild()) before calling showPopover()."), 'InvalidStateError');
221
+ });
222
+ }
223
+
224
+ // Open auto popovers in open-order. Used for light-dismiss, Escape, and
225
+ // hide-all-popovers-until on new auto opens.
226
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#showing-auto-popover-list
227
+ var openAutoStack = [];
228
+
229
+ // Open hint popovers in open-order. Hints have their own stack: hint closes
230
+ // other hints, auto closes all hints, hint never closes autos.
231
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#showing-hint-popover-list
232
+ var openHintStack = [];
233
+ function isPopoverOpen(_ref8) {
234
+ var element = _ref8.element;
235
+ return element.hasAttribute('data-popover-open');
236
+ }
237
+
238
+ // Stack lookup: returns the stack the element belongs to (or null for manual /
239
+ // non-popovers). Used by hide-handlers to remove from the correct stack and by
240
+ // dismissal logic to find the topmost relevant popover.
241
+ function stackForMode(_ref9) {
242
+ var mode = _ref9.mode;
243
+ if (mode === 'auto') {
244
+ return openAutoStack;
245
+ }
246
+ if (mode === 'hint') {
247
+ return openHintStack;
248
+ }
249
+ return null;
250
+ }
251
+
252
+ // Polyfill-only. Real browsers reveal the popover once `ResizeObserver`
253
+ // callbacks fire after the first layout pass. jsdom has no layout, so we clear
254
+ // any inline `opacity: 0` (set by callers like `use-anchor-positioning`) once
255
+ // the open toggle has fired.
256
+ function clearOpacityOnOpenToggle(_ref0) {
257
+ var element = _ref0.element;
258
+ var unbind = (0, _bindEventListener.bind)(element, {
259
+ type: 'toggle',
260
+ listener: function listener(event) {
261
+ var toggleEvent = event;
262
+ if (toggleEvent.newState !== 'open') {
263
+ return;
264
+ }
265
+ element.style.removeProperty('opacity');
266
+ unbind();
185
267
  }
186
- if (this.hasAttribute('data-popover-open')) {
187
- return; // already showing — no-op
268
+ });
269
+ }
270
+
271
+ // Spec: "topmost popover ancestor" walks both DOM ancestry AND the invoker
272
+ // chain (an invoker is a button whose `popovertarget` references an open
273
+ // popover). When opening popover B from a button inside popover A, A is
274
+ // considered B's ancestor and stays open. This walks ancestors of the target,
275
+ // hopping through any invoker references discovered along the way.
276
+ // Find a popover's invoker: an element with `popovertarget=<popover.id>`.
277
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#popover-target-element
278
+ function findInvokerForPopover(_ref1) {
279
+ var popover = _ref1.popover;
280
+ if (!popover.id || !popover.ownerDocument) {
281
+ return null;
282
+ }
283
+ var escaped = (typeof CSS !== 'undefined' && CSS.escape ? CSS.escape : String)(popover.id);
284
+ var selector = "[popovertarget=\"".concat(escaped, "\"]");
285
+ return popover.ownerDocument.querySelector(selector);
286
+ }
287
+
288
+ // True if `candidate` is reachable from `start` via DOM ancestry plus invoker
289
+ // hops. Spec: https://html.spec.whatwg.org/multipage/popover.html#topmost-popover-ancestor
290
+ function reachesViaAncestryOrInvoker(_ref10) {
291
+ var start = _ref10.start,
292
+ candidate = _ref10.candidate;
293
+ var found = walkAncestorChain({
294
+ start: start,
295
+ visitor: function visitor(_ref11) {
296
+ var node = _ref11.node;
297
+ if (node === candidate) {
298
+ return true;
299
+ }
300
+ // Hop into the invoked popover's chain when this node is an invoker.
301
+ if (node instanceof HTMLElement) {
302
+ var popovertarget = node.getAttribute('popovertarget');
303
+ if (popovertarget && node.ownerDocument) {
304
+ var referenced = node.ownerDocument.getElementById(popovertarget);
305
+ if (referenced && referenced !== node) {
306
+ if (reachesViaAncestryOrInvoker({
307
+ start: referenced,
308
+ candidate: candidate
309
+ })) {
310
+ return true;
311
+ }
312
+ }
313
+ }
314
+ }
315
+ return null;
188
316
  }
317
+ });
318
+ return found === true;
319
+ }
189
320
 
190
- // sync, cancelable on show only
191
- var allowed = fireToggleEvent({
192
- element: this,
193
- type: 'beforetoggle',
194
- oldState: 'closed',
195
- newState: 'open',
196
- cancelable: true
321
+ // Is `candidate` an ancestor of `target` via DOM nesting OR invoker chain?
322
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#topmost-popover-ancestor
323
+ function isInChainTo(_ref12) {
324
+ var candidate = _ref12.candidate,
325
+ target = _ref12.target;
326
+ if (!(target instanceof Node)) {
327
+ return false;
328
+ }
329
+ if (reachesViaAncestryOrInvoker({
330
+ start: target,
331
+ candidate: candidate
332
+ })) {
333
+ return true;
334
+ }
335
+ // If target is itself a popover, also walk its invoker's chain (a button
336
+ // elsewhere in the DOM that has `popovertarget=<target.id>`).
337
+ if (target instanceof HTMLElement && target.hasAttribute('popover')) {
338
+ var invoker = findInvokerForPopover({
339
+ popover: target
197
340
  });
198
- if (!allowed) {
199
- return;
341
+ if (invoker && reachesViaAncestryOrInvoker({
342
+ start: invoker,
343
+ candidate: candidate
344
+ })) {
345
+ return true;
346
+ }
347
+ }
348
+ return false;
349
+ }
350
+
351
+ // Hide every popover in `stack` that is not an ancestor of `target`. With a
352
+ // null target, closes the entire stack.
353
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#hide-all-popovers-until
354
+ function hidePopoversInStackNotInChainTo(_ref13) {
355
+ var stack = _ref13.stack,
356
+ target = _ref13.target;
357
+ var snapshot = stack.slice();
358
+ for (var i = snapshot.length - 1; i >= 0; i -= 1) {
359
+ var popover = snapshot[i];
360
+ if (isInChainTo({
361
+ candidate: popover,
362
+ target: target
363
+ })) {
364
+ break;
365
+ }
366
+ popover.hidePopover();
367
+ }
368
+ }
369
+
370
+ // Two-phase light-dismiss: pointerdown stores the nearest open popover for
371
+ // the target; pointerup dismisses popovers that are not in that popover's
372
+ // chain when the same target is hit. Drag-out / drag-in does not dismiss.
373
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#light-dismiss-open-popovers
374
+ var pointerdownState = {
375
+ target: null
376
+ };
377
+
378
+ // If `node` is an invoker for an open popover, return that popover.
379
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#popover-target-element
380
+ function invokedOpenPopover(_ref14) {
381
+ var node = _ref14.node;
382
+ if (!(node instanceof HTMLElement)) {
383
+ return null;
384
+ }
385
+ var popovertarget = node.getAttribute('popovertarget');
386
+ if (!popovertarget || !node.ownerDocument) {
387
+ return null;
388
+ }
389
+ var referenced = node.ownerDocument.getElementById(popovertarget);
390
+ if (!referenced || referenced === node || !isPopoverOpen({
391
+ element: referenced
392
+ })) {
393
+ return null;
394
+ }
395
+ return referenced;
396
+ }
397
+
398
+ // Cycle-safe ancestor walker. Returns the first non-null `visitor` result.
399
+ function walkAncestorChain(_ref15) {
400
+ var start = _ref15.start,
401
+ visitor = _ref15.visitor;
402
+ var seen = new Set();
403
+ function step(node) {
404
+ if (!node || seen.has(node)) {
405
+ return null;
200
406
  }
201
- this.setAttribute('data-popover-open', '');
202
- schedulePopoverToggle({
203
- element: this,
204
- newState: 'open'
407
+ seen.add(node);
408
+ var result = visitor({
409
+ node: node
205
410
  });
206
- };
207
- HTMLElement.prototype.hidePopover = function hidePopover() {
208
- if (!this.hasAttribute('popover')) {
209
- throw new DOMException("Failed to execute 'hidePopover' on 'HTMLElement': ".concat(describeElement({
210
- element: this
211
- }), " is not a popover element. Ensure the element has a 'popover' attribute (e.g. <div popover>)."), 'NotSupportedError');
411
+ if (result !== null) {
412
+ return result;
212
413
  }
213
- // Browser does not throw for disconnected elements on hide — just no-op
214
- if (!this.isConnected || !this.hasAttribute('data-popover-open')) {
215
- return;
414
+ return step(node.parentNode);
415
+ }
416
+ return step(start);
417
+ }
418
+
419
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#nearest-inclusive-open-popover
420
+ function nearestOpenPopover(_ref16) {
421
+ var target = _ref16.target;
422
+ return walkAncestorChain({
423
+ start: target,
424
+ visitor: function visitor(_ref17) {
425
+ var node = _ref17.node;
426
+ if (node instanceof HTMLElement && isPopoverOpen({
427
+ element: node
428
+ })) {
429
+ return node;
430
+ }
431
+ return invokedOpenPopover({
432
+ node: node
433
+ });
434
+ }
435
+ });
436
+ }
437
+ function handleDocumentPointerdown(event) {
438
+ if (openAutoStack.length === 0 && openHintStack.length === 0) {
439
+ return;
440
+ }
441
+ var target = event.target instanceof Node ? event.target : null;
442
+ pointerdownState.target = nearestOpenPopover({
443
+ target: target
444
+ });
445
+ }
446
+ function handleDocumentPointerup(event) {
447
+ if (openAutoStack.length === 0 && openHintStack.length === 0) {
448
+ pointerdownState.target = null;
449
+ return;
450
+ }
451
+ var target = event.target instanceof Node ? event.target : null;
452
+ var ancestor = nearestOpenPopover({
453
+ target: target
454
+ });
455
+ var sameTarget = ancestor === pointerdownState.target;
456
+ pointerdownState.target = null;
457
+ if (!sameTarget) {
458
+ // User pointerdowned inside a popover and released elsewhere (or vice
459
+ // versa). Treat as a drag and do not dismiss per spec.
460
+ return;
461
+ }
462
+ // Hints are dismissed first (they sit "above" autos for dismissal purposes
463
+ // even though they live in their own stack), then autos. When ancestor is a
464
+ // hint, autos are unaffected; when ancestor is an auto or null, hints close
465
+ // entirely (because no hint is the target's ancestor).
466
+ var ancestorMode = ancestor ? getPopoverMode({
467
+ element: ancestor
468
+ }) : null;
469
+ if (ancestorMode === 'hint') {
470
+ hidePopoversInStackNotInChainTo({
471
+ stack: openHintStack,
472
+ target: target
473
+ });
474
+ return;
475
+ }
476
+ hidePopoversInStackNotInChainTo({
477
+ stack: openHintStack,
478
+ target: null
479
+ });
480
+ hidePopoversInStackNotInChainTo({
481
+ stack: openAutoStack,
482
+ target: target
483
+ });
484
+ }
485
+
486
+ // Topmost still-connected popover in `stack`, pruning disconnected entries
487
+ // (test pollution where a popover was removed from the DOM without hidePopover).
488
+ // Polyfill-only defensive cleanup; not in the spec.
489
+ function topmostConnected(_ref18) {
490
+ var stack = _ref18.stack;
491
+ while (stack.length > 0) {
492
+ var candidate = stack[stack.length - 1];
493
+ if (candidate.isConnected) {
494
+ return candidate;
216
495
  }
496
+ stack.pop();
497
+ }
498
+ return null;
499
+ }
217
500
 
218
- // not cancelable on hide
219
- fireToggleEvent({
220
- element: this,
221
- type: 'beforetoggle',
222
- oldState: 'open',
223
- newState: 'closed',
224
- cancelable: false
501
+ // Escape closes the topmost open popover. Hints are dismissed before autos.
502
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#close-watcher
503
+ function handleDocumentKeydown(event) {
504
+ if (event.key !== 'Escape') {
505
+ return;
506
+ }
507
+ var topHint = topmostConnected({
508
+ stack: openHintStack
509
+ });
510
+ if (topHint) {
511
+ topHint.hidePopover();
512
+ return;
513
+ }
514
+ var topAuto = topmostConnected({
515
+ stack: openAutoStack
516
+ });
517
+ if (topAuto) {
518
+ topAuto.hidePopover();
519
+ }
520
+ }
521
+ if (typeof HTMLElement !== 'undefined') {
522
+ var schedulePopoverToggle = createToggleScheduler();
523
+
524
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#dom-showpopover
525
+ if (!HTMLElement.prototype.showPopover) {
526
+ HTMLElement.prototype.showPopover = function showPopover() {
527
+ if (!this.hasAttribute('popover')) {
528
+ throw new DOMException("Failed to execute 'showPopover' on 'HTMLElement': ".concat(describeElement({
529
+ element: this
530
+ }), " is not a popover element. Ensure the element has a 'popover' attribute (e.g. <div popover>)."), 'NotSupportedError');
531
+ }
532
+ if (!this.isConnected) {
533
+ throw new DOMException("Failed to execute 'showPopover' on 'HTMLElement': ".concat(describeElement({
534
+ element: this
535
+ }), " is not connected to the document. Ensure the element has been added to the DOM (e.g. via render() or document.body.appendChild()) before calling showPopover()."), 'InvalidStateError');
536
+ }
537
+ if (isPopoverOpen({
538
+ element: this
539
+ })) {
540
+ return; // already showing — no-op
541
+ }
542
+
543
+ // sync, cancelable on show only
544
+ var allowed = fireToggleEvent({
545
+ element: this,
546
+ type: 'beforetoggle',
547
+ oldState: 'closed',
548
+ newState: 'open',
549
+ cancelable: true
550
+ });
551
+ if (!allowed) {
552
+ return;
553
+ }
554
+
555
+ // Spec-driven dismissal of popovers in other stacks before opening:
556
+ // - opening an `auto`: close all hints; close autos not in the new
557
+ // popover's ancestor chain.
558
+ // - opening a `hint`: close hints not in the new popover's chain;
559
+ // leave autos alone.
560
+ // - opening a `manual`: leave both stacks untouched.
561
+ var mode = getPopoverMode({
562
+ element: this
563
+ });
564
+ if (mode === 'auto') {
565
+ hidePopoversInStackNotInChainTo({
566
+ stack: openHintStack,
567
+ target: null
568
+ });
569
+ hidePopoversInStackNotInChainTo({
570
+ stack: openAutoStack,
571
+ target: this
572
+ });
573
+ } else if (mode === 'hint') {
574
+ hidePopoversInStackNotInChainTo({
575
+ stack: openHintStack,
576
+ target: this
577
+ });
578
+ }
579
+ this.setAttribute('data-popover-open', '');
580
+ var stack = stackForMode({
581
+ mode: mode
582
+ });
583
+ if (stack && !stack.includes(this)) {
584
+ stack.push(this);
585
+ }
586
+ clearOpacityOnOpenToggle({
587
+ element: this
588
+ });
589
+ schedulePopoverToggle({
590
+ element: this,
591
+ newState: 'open'
592
+ });
593
+ };
594
+ }
595
+
596
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#dom-hidepopover
597
+ if (!HTMLElement.prototype.hidePopover) {
598
+ HTMLElement.prototype.hidePopover = function hidePopover() {
599
+ if (!this.hasAttribute('popover')) {
600
+ throw new DOMException("Failed to execute 'hidePopover' on 'HTMLElement': ".concat(describeElement({
601
+ element: this
602
+ }), " is not a popover element. Ensure the element has a 'popover' attribute (e.g. <div popover>)."), 'NotSupportedError');
603
+ }
604
+ // Browser does not throw for disconnected elements on hide. No-op.
605
+ if (!this.isConnected || !isPopoverOpen({
606
+ element: this
607
+ })) {
608
+ return;
609
+ }
610
+
611
+ // not cancelable on hide
612
+ fireToggleEvent({
613
+ element: this,
614
+ type: 'beforetoggle',
615
+ oldState: 'open',
616
+ newState: 'closed',
617
+ cancelable: false
618
+ });
619
+ this.removeAttribute('data-popover-open');
620
+ var stack = stackForMode({
621
+ mode: getPopoverMode({
622
+ element: this
623
+ })
624
+ });
625
+ if (stack) {
626
+ var stackIndex = stack.indexOf(this);
627
+ if (stackIndex !== -1) {
628
+ stack.splice(stackIndex, 1);
629
+ }
630
+ }
631
+ schedulePopoverToggle({
632
+ element: this,
633
+ newState: 'closed'
634
+ });
635
+ };
636
+ }
637
+
638
+ // Spec: https://html.spec.whatwg.org/multipage/popover.html#dom-togglepopover
639
+ if (!HTMLElement.prototype.togglePopover) {
640
+ HTMLElement.prototype.togglePopover = function togglePopover(force) {
641
+ var wasOpen = isPopoverOpen({
642
+ element: this
643
+ });
644
+ if (typeof force === 'boolean') {
645
+ if (force && !wasOpen) {
646
+ this.showPopover();
647
+ } else if (!force && wasOpen) {
648
+ this.hidePopover();
649
+ }
650
+ } else if (wasOpen) {
651
+ this.hidePopover();
652
+ } else {
653
+ this.showPopover();
654
+ }
655
+
656
+ // Return the actual resulting state, which differs from `force` when
657
+ // `beforetoggle` was preventDefaulted on show.
658
+ return isPopoverOpen({
659
+ element: this
660
+ });
661
+ };
662
+ }
663
+
664
+ // Document-level light-dismiss for `popover="auto"` and `popover="hint"`.
665
+ // Spec uses pointerdown + pointerup with same-target check; we also wire
666
+ // `mousedown` + `mouseup` because jsdom does not fire pointer events for
667
+ // fireEvent.click() / userEvent.click() in older jsdom versions.
668
+
669
+ if (typeof document !== 'undefined' && !document.__atTopLayerLightDismiss) {
670
+ document.__atTopLayerLightDismiss = true;
671
+
672
+ // capture phase so application handlers cannot stop propagation before us
673
+ var pointerdownAdapter = function pointerdownAdapter(event) {
674
+ return handleDocumentPointerdown(event);
675
+ };
676
+ var pointerupAdapter = function pointerupAdapter(event) {
677
+ return handleDocumentPointerup(event);
678
+ };
679
+ (0, _bindEventListener.bind)(document, {
680
+ type: 'pointerdown',
681
+ listener: pointerdownAdapter,
682
+ options: {
683
+ capture: true
684
+ }
225
685
  });
226
- this.removeAttribute('data-popover-open');
227
- schedulePopoverToggle({
228
- element: this,
229
- newState: 'closed'
686
+ (0, _bindEventListener.bind)(document, {
687
+ type: 'pointerup',
688
+ listener: pointerupAdapter,
689
+ options: {
690
+ capture: true
691
+ }
230
692
  });
231
- };
232
- HTMLElement.prototype.togglePopover = function togglePopover(force) {
233
- var isOpen = this.hasAttribute('data-popover-open');
234
- if (typeof force === 'boolean') {
235
- if (force && !isOpen) {
236
- this.showPopover();
237
- } else if (!force && isOpen) {
238
- this.hidePopover();
693
+ // Fallback for environments / fireEvent paths that fire mouse events
694
+ // only. The same handlers work for MouseEvent because we only read
695
+ // `target`. PointerEvent extends MouseEvent so the runtime cast is safe.
696
+ (0, _bindEventListener.bind)(document, {
697
+ type: 'mousedown',
698
+ listener: pointerdownAdapter,
699
+ options: {
700
+ capture: true
239
701
  }
240
- return force;
241
- }
242
- if (isOpen) {
243
- this.hidePopover();
244
- return false;
245
- }
246
- this.showPopover();
247
- return true;
248
- };
702
+ });
703
+ (0, _bindEventListener.bind)(document, {
704
+ type: 'mouseup',
705
+ listener: pointerupAdapter,
706
+ options: {
707
+ capture: true
708
+ }
709
+ });
710
+ (0, _bindEventListener.bind)(document, {
711
+ type: 'keydown',
712
+ listener: handleDocumentKeydown,
713
+ options: {
714
+ capture: true
715
+ }
716
+ });
717
+ }
249
718
  }
250
719
 
251
720
  // ─── HTMLDialogElement ──────────────────────────────────────────────────────
721
+ // Spec: https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element
252
722
 
253
723
  if (typeof HTMLDialogElement !== 'undefined') {
254
724
  var proto = HTMLDialogElement.prototype;