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