@atlaskit/top-layer 0.3.0 → 0.5.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 +35 -0
- package/dist/cjs/internal/use-anchor-positioning.js +23 -8
- package/dist/cjs/testing/polyfill.js +533 -63
- package/dist/es2019/internal/use-anchor-positioning.js +23 -8
- package/dist/es2019/testing/polyfill.js +543 -63
- package/dist/esm/internal/use-anchor-positioning.js +23 -8
- package/dist/esm/testing/polyfill.js +533 -63
- package/dist/types/testing/polyfill.d.ts +7 -30
- package/dist/types-ts4.5/testing/polyfill.d.ts +7 -30
- package/package.json +1 -1
|
@@ -220,7 +220,6 @@ export function useAnchorPosition(_ref5) {
|
|
|
220
220
|
forceFallbackPositioning = _ref5$forceFallbackPo === void 0 ? false : _ref5$forceFallbackPo,
|
|
221
221
|
arrow = _ref5.arrow;
|
|
222
222
|
var id = useId();
|
|
223
|
-
var anchorName = "--anchor-".concat(id.replace(/:/g, ''));
|
|
224
223
|
useLayoutEffect(function () {
|
|
225
224
|
var trigger = anchorRef.current;
|
|
226
225
|
var popover = popoverRef.current;
|
|
@@ -234,6 +233,9 @@ export function useAnchorPosition(_ref5) {
|
|
|
234
233
|
var crossAxisShiftCssValue = resolvedPlacement.offset.crossAxisShift.value;
|
|
235
234
|
var shouldForceFallback = Boolean(forceFallbackPositioning);
|
|
236
235
|
if (supportsAnchorPositioning() && !shouldForceFallback) {
|
|
236
|
+
// Reuse an existing `anchor-name` if the element already has one.
|
|
237
|
+
// Another popover might already be anchored to the old name.
|
|
238
|
+
var anchorName = trigger.style.getPropertyValue('anchor-name') || "--anchor-".concat(id.replace(/:/g, ''));
|
|
237
239
|
var gap = edgeMargin({
|
|
238
240
|
placement: resolvedPlacement,
|
|
239
241
|
offset: gapCssValue
|
|
@@ -331,13 +333,26 @@ export function useAnchorPosition(_ref5) {
|
|
|
331
333
|
// because that's where useAnchorPosition sets the margin gap.
|
|
332
334
|
popover.setAttribute('data-ds-popover-arrow', '');
|
|
333
335
|
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* **We are never cleaning up anchor names**
|
|
339
|
+
*
|
|
340
|
+
* _Rationale_
|
|
341
|
+
*
|
|
342
|
+
* - Multiple popovers can share the same anchor element.
|
|
343
|
+
* - Sometimes the `useAnchorPosition` hook and `Popover`
|
|
344
|
+
* are not in the same component.
|
|
345
|
+
* - There can be times when, even with reference counting, we
|
|
346
|
+
* could remove the anchor-name while it's still being used
|
|
347
|
+
* by something else - especially with async react updates
|
|
348
|
+
* which can cause tearing
|
|
349
|
+
*
|
|
350
|
+
* _More_
|
|
351
|
+
*
|
|
352
|
+
* See `notes/decisions/anchor-name-lifetime.md`
|
|
353
|
+
*/
|
|
354
|
+
trigger.style.setProperty('anchor-name', anchorName);
|
|
334
355
|
var _undoPositioning = combine(setStyle({
|
|
335
|
-
el: trigger,
|
|
336
|
-
styles: [{
|
|
337
|
-
property: 'anchor-name',
|
|
338
|
-
value: anchorName
|
|
339
|
-
}]
|
|
340
|
-
}), setStyle({
|
|
341
356
|
el: popover,
|
|
342
357
|
styles: popoverStyles
|
|
343
358
|
}), arrow ? function () {
|
|
@@ -469,5 +484,5 @@ export function useAnchorPosition(_ref5) {
|
|
|
469
484
|
popover.style.removeProperty('opacity');
|
|
470
485
|
});
|
|
471
486
|
return undoPositioning;
|
|
472
|
-
}, [anchorRef, popoverRef, placement,
|
|
487
|
+
}, [anchorRef, popoverRef, placement, forceFallbackPositioning, arrow, id]);
|
|
473
488
|
}
|
|
@@ -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,
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
})
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
185
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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 (
|
|
197
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
newState: 'open'
|
|
405
|
+
seen.add(node);
|
|
406
|
+
var result = visitor({
|
|
407
|
+
node: node
|
|
203
408
|
});
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
684
|
+
bind(document, {
|
|
685
|
+
type: 'pointerup',
|
|
686
|
+
listener: pointerupAdapter,
|
|
687
|
+
options: {
|
|
688
|
+
capture: true
|
|
689
|
+
}
|
|
228
690
|
});
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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;
|