@jsenv/navi 0.29.465 → 0.29.466
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/dist/dev/jsenv_navi.js +185 -90
- package/dist/dev/jsenv_navi.js.map +9 -9
- package/dist/jsenv_navi.js +185 -90
- package/dist/jsenv_navi.js.map +9 -9
- package/docs/popup_backdrop.md +5 -5
- package/package.json +2 -2
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* using @jsenv/navi as intended.
|
|
4
4
|
*/
|
|
5
5
|
import { installImportMetaCssBuild, coarsePointerSignal, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
|
|
6
|
-
import { createPubSub, elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, createEventGroupLogger, findEvent, mergeOneStyle, findFocusable, getElementSignature, chainEvent, isTouchDrivenEvent, getKeyboardEventDefaultAction, isPressDrivenClick, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, createValueEffect, findFocusDelegateTarget, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, getScrollContainerSet, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, keepTouchRefusable, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, createInternalCustomEvent, activeElementSignal, normalizeStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, getScrollContainer, watchWheelTravel, scrollRoomTowards, trapScrollInside, scrollIntoViewScoped, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, isScrollable, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, getVirtualKeyboardOverlayHeight, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, setVirtualKeyboardOverlaysContent, subscribeVirtualKeyboardGeometryChange } from "@jsenv/dom";
|
|
6
|
+
import { createPubSub, elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, createEventGroupLogger, findEvent, mergeOneStyle, findFocusable, getElementSignature, chainEvent, isTouchDrivenEvent, getKeyboardEventDefaultAction, isPressDrivenClick, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, createValueEffect, findFocusDelegateTarget, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, getScrollContainerSet, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, keepTouchRefusable, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, createInternalCustomEvent, activeElementSignal, normalizeStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, getScrollContainer, watchWheelTravel, scrollRoomTowards, trapScrollInside, scrollIntoViewScoped, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, isScrollable, resolveOklchLightness, contrastColor, overflowGoesToViewport, parsePositionArea, snapToPixel, trapFocusInside, getVirtualKeyboardOverlayHeight, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, setVirtualKeyboardOverlaysContent, subscribeVirtualKeyboardGeometryChange } from "@jsenv/dom";
|
|
7
7
|
export { chainEvent, clickIsSuppressed, contrastColor, createDragGestureController, dragAfterIntent, findEvent, markDragSource, startDragTo } from "@jsenv/dom";
|
|
8
8
|
import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
|
|
9
9
|
import { options, isValidElement, createContext, render as render$1, h, toChildArray, createRef, cloneElement, createElement, Fragment as Fragment$1 } from "preact";
|
|
@@ -6799,6 +6799,12 @@ const isAimedAtSelfInteractionsBelow = (
|
|
|
6799
6799
|
if (!target || typeof target.closest !== "function") {
|
|
6800
6800
|
return false;
|
|
6801
6801
|
}
|
|
6802
|
+
if (event.type === "keydown" || event.type === "keyup") {
|
|
6803
|
+
// A key is no press: the claim is the pointer's. An Escape pressed while
|
|
6804
|
+
// the focus rests on the claimer asks the controls around it — the sheet
|
|
6805
|
+
// it sits in closing.
|
|
6806
|
+
return false;
|
|
6807
|
+
}
|
|
6802
6808
|
const claimer = target.closest(CLICK_SELECTOR);
|
|
6803
6809
|
if (!claimer) {
|
|
6804
6810
|
return false;
|
|
@@ -16715,6 +16721,17 @@ const createOpenController = (
|
|
|
16715
16721
|
{ opened: false, event: closeEvent },
|
|
16716
16722
|
);
|
|
16717
16723
|
};
|
|
16724
|
+
// A cancel an Escape asked for, read with what Escape means for this popup.
|
|
16725
|
+
const readCloseDetail = (e, detail) => {
|
|
16726
|
+
if (
|
|
16727
|
+
detail?.isCancel &&
|
|
16728
|
+
controller.escapeEffect !== "cancel" &&
|
|
16729
|
+
findEvent(e, isEscapeKeydown)
|
|
16730
|
+
) {
|
|
16731
|
+
return { ...detail, isCancel: false };
|
|
16732
|
+
}
|
|
16733
|
+
return detail;
|
|
16734
|
+
};
|
|
16718
16735
|
const controller = {
|
|
16719
16736
|
opened: false,
|
|
16720
16737
|
// What the DOM currently shows, as opposed to what the controller has
|
|
@@ -16746,6 +16763,11 @@ const createOpenController = (
|
|
|
16746
16763
|
// whole point: learning it afterwards means the content mounted on the
|
|
16747
16764
|
// previous subject first.
|
|
16748
16765
|
onOpen: null,
|
|
16766
|
+
// What an Escape means for this popup, whoever heard the key — its own
|
|
16767
|
+
// shortcut, its trigger's, the group it stands in, a bubble's: a cancel
|
|
16768
|
+
// (what it holds reverts), unless its owner says otherwise (a Picker's
|
|
16769
|
+
// escapeEffect="close"). Read from the close the key asked for.
|
|
16770
|
+
escapeEffect: "cancel",
|
|
16749
16771
|
// The counterpart, set only when the popup was told to throw its content
|
|
16750
16772
|
// away on close (`mount="while-opened"`). Called from performClose above.
|
|
16751
16773
|
unmountContent: null,
|
|
@@ -16915,7 +16937,7 @@ const createOpenController = (
|
|
|
16915
16937
|
return true;
|
|
16916
16938
|
}
|
|
16917
16939
|
const requestCloseEvent = new CustomEvent("navi_request_close", {
|
|
16918
|
-
detail: { event: e, ...detail },
|
|
16940
|
+
detail: { event: e, ...readCloseDetail(e, detail) },
|
|
16919
16941
|
cancelable: true,
|
|
16920
16942
|
});
|
|
16921
16943
|
chainEvent(requestCloseEvent, e);
|
|
@@ -16925,10 +16947,7 @@ const createOpenController = (
|
|
|
16925
16947
|
// the popup (a PopupGroup, a dialog), closing what this popup kept open,
|
|
16926
16948
|
// or what is still closing with it (a lifting dialog closes a frame
|
|
16927
16949
|
// later).
|
|
16928
|
-
const escapeKeydown = findEvent(
|
|
16929
|
-
requestCloseEvent,
|
|
16930
|
-
(event) => event.type === "keydown" && event.key === "Escape",
|
|
16931
|
-
);
|
|
16950
|
+
const escapeKeydown = findEvent(requestCloseEvent, isEscapeKeydown);
|
|
16932
16951
|
if (escapeKeydown) {
|
|
16933
16952
|
escapeKeydown.preventDefault();
|
|
16934
16953
|
}
|
|
@@ -16950,7 +16969,7 @@ const createOpenController = (
|
|
|
16950
16969
|
return;
|
|
16951
16970
|
}
|
|
16952
16971
|
const closeEvent = new CustomEvent("navi_close", {
|
|
16953
|
-
detail: { event: e, ...detail },
|
|
16972
|
+
detail: { event: e, ...readCloseDetail(e, detail) },
|
|
16954
16973
|
});
|
|
16955
16974
|
chainEvent(closeEvent, e);
|
|
16956
16975
|
// Skips onRequestClose entirely — there is no choice here.
|
|
@@ -16961,6 +16980,9 @@ const createOpenController = (
|
|
|
16961
16980
|
};
|
|
16962
16981
|
|
|
16963
16982
|
// A click a keyboard made (detail 0) is not one: nothing pressed for it.
|
|
16983
|
+
const isEscapeKeydown = (event) =>
|
|
16984
|
+
event.type === "keydown" && event.key === "Escape";
|
|
16985
|
+
|
|
16964
16986
|
const isEventOfAPress = (event) =>
|
|
16965
16987
|
event.type === "pointerdown" ||
|
|
16966
16988
|
event.type === "mousedown" ||
|
|
@@ -18617,8 +18639,10 @@ const openCallout = (
|
|
|
18617
18639
|
}
|
|
18618
18640
|
calloutCloseButton.tabIndex = -1;
|
|
18619
18641
|
calloutCloseButton.setAttribute("navi-focus-delegate", "");
|
|
18642
|
+
// One Escape closes one popup: an Escape already spent by one in front
|
|
18643
|
+
// of the callout — a menu opened from its content — is not this one's.
|
|
18620
18644
|
const onAnchorKeydown = (e) => {
|
|
18621
|
-
if (e.key === "Escape") {
|
|
18645
|
+
if (e.key === "Escape" && !e.defaultPrevented) {
|
|
18622
18646
|
requestClose(e, "escape_key");
|
|
18623
18647
|
e.stopPropagation();
|
|
18624
18648
|
e.preventDefault();
|
|
@@ -18633,7 +18657,7 @@ const openCallout = (
|
|
|
18633
18657
|
// Usually callout is inside anchor so the anchor would see the escape being pressed
|
|
18634
18658
|
// but callout can be appended outside anchor when anchor cannot receive children
|
|
18635
18659
|
const onCalloutKeydown = (e) => {
|
|
18636
|
-
if (e.key === "Escape") {
|
|
18660
|
+
if (e.key === "Escape" && !e.defaultPrevented) {
|
|
18637
18661
|
requestClose(e, "escape_key");
|
|
18638
18662
|
e.stopPropagation();
|
|
18639
18663
|
e.preventDefault();
|
|
@@ -18679,21 +18703,23 @@ const openCallout = (
|
|
|
18679
18703
|
//
|
|
18680
18704
|
// Only for the ones that cannot be heard otherwise, and only while they are
|
|
18681
18705
|
// open: a callout the anchor can hear about is left to the anchor, so
|
|
18682
|
-
// Escape goes on meaning what it means where the user is.
|
|
18706
|
+
// Escape goes on meaning what it means where the user is. Heard once the
|
|
18707
|
+
// key has been through what it was pressed in: one that answered it (a
|
|
18708
|
+
// popup closing, a field clearing) spent it.
|
|
18683
18709
|
if (anchorElement && anchorElement.contains(document.activeElement)) {
|
|
18684
18710
|
break close_on_escape_from_anywhere;
|
|
18685
18711
|
}
|
|
18686
18712
|
const onDocumentKeydown = (e) => {
|
|
18687
|
-
if (e.key !== "Escape") {
|
|
18713
|
+
if (e.key !== "Escape" || e.defaultPrevented) {
|
|
18688
18714
|
return;
|
|
18689
18715
|
}
|
|
18690
18716
|
requestClose(e, "escape_key");
|
|
18691
18717
|
e.stopPropagation();
|
|
18692
18718
|
e.preventDefault();
|
|
18693
18719
|
};
|
|
18694
|
-
document.addEventListener("keydown", onDocumentKeydown
|
|
18720
|
+
document.addEventListener("keydown", onDocumentKeydown);
|
|
18695
18721
|
addTeardown(() => {
|
|
18696
|
-
document.removeEventListener("keydown", onDocumentKeydown
|
|
18722
|
+
document.removeEventListener("keydown", onDocumentKeydown);
|
|
18697
18723
|
});
|
|
18698
18724
|
}
|
|
18699
18725
|
{
|
|
@@ -35312,7 +35338,7 @@ const readOuterScroll = (element) => {
|
|
|
35312
35338
|
};
|
|
35313
35339
|
|
|
35314
35340
|
const isDocumentScroller = (node) => {
|
|
35315
|
-
return node === document.documentElement
|
|
35341
|
+
return node === document.documentElement;
|
|
35316
35342
|
};
|
|
35317
35343
|
|
|
35318
35344
|
const isPinnedToViewport = (node) => {
|
|
@@ -38538,12 +38564,12 @@ const whilePageRenders = async (page, change, wait = armRouteRenderWait()) => {
|
|
|
38538
38564
|
// (`overflow: clip`) holds nothing and is walked straight through — see
|
|
38539
38565
|
// is_scrollable.js.
|
|
38540
38566
|
const pagesScrollTheDocument = element => {
|
|
38541
|
-
|
|
38567
|
+
// What window.scrollTo moves. A body whose overflow is the viewport's is
|
|
38568
|
+
// walked through to it (see overflowGoesToViewport in @jsenv/dom); a body
|
|
38569
|
+
// the walk stops on scrolls itself, out of the viewport's reach.
|
|
38570
|
+
return getScrollContainer(element, {
|
|
38542
38571
|
includeHidden: true
|
|
38543
|
-
});
|
|
38544
|
-
// html and body are one answer: whichever of them the walk stops on, what
|
|
38545
|
-
// scrolls is the viewport, which is what window.scrollTo moves.
|
|
38546
|
-
return scrollContainer === document.documentElement || scrollContainer === document.body;
|
|
38572
|
+
}) === document.documentElement;
|
|
38547
38573
|
};
|
|
38548
38574
|
|
|
38549
38575
|
// A page of the row: a route, and the params that say which of its tabs when
|
|
@@ -67280,6 +67306,8 @@ const engageGroup = (groupEl, group, {
|
|
|
67280
67306
|
const themeColor = readPopupThemeColor(groupEl, group.themeColor);
|
|
67281
67307
|
const focusedBefore = document.activeElement;
|
|
67282
67308
|
const focusWasInGroup = Boolean(focusedBefore) && focusedBefore !== groupEl && groupEl.contains(focusedBefore);
|
|
67309
|
+
// What the focus goes back to, when nothing holds it any more at the close.
|
|
67310
|
+
const focusOrigin = !focusWasInGroup && focusedBefore && focusedBefore !== document.body && focusedBefore !== groupEl ? focusedBefore : null;
|
|
67283
67311
|
const ringBefore = Boolean(focusedBefore?.matches?.(":focus-visible"));
|
|
67284
67312
|
const {
|
|
67285
67313
|
scrollTop,
|
|
@@ -67287,12 +67315,7 @@ const engageGroup = (groupEl, group, {
|
|
|
67287
67315
|
} = groupEl;
|
|
67288
67316
|
// A gesture on a box fixed in the top layer goes to the viewport: only a
|
|
67289
67317
|
// place that scrolls with the viewport moves under it.
|
|
67290
|
-
const
|
|
67291
|
-
const placeScrollsWithViewport = (placeScroller === document.scrollingElement ||
|
|
67292
|
-
// Reported as a scroll container on an app setting `body {
|
|
67293
|
-
// overflow-x: hidden }` (its computed overflow-y turns auto), it never
|
|
67294
|
-
// scrolls itself: the viewport does.
|
|
67295
|
-
placeScroller === document.body && bodyOverflowIsTheViewports()) && !findSelfOrAncestorFixedPosition(groupEl);
|
|
67318
|
+
const placeScrollsWithViewport = getScrollContainer(groupEl) === document.scrollingElement && !findSelfOrAncestorFixedPosition(groupEl);
|
|
67296
67319
|
const placeholder = createPlaceholder(groupStyle, rect, baselineDescent);
|
|
67297
67320
|
groupEl.before(placeholder);
|
|
67298
67321
|
groupEl.style.setProperty("--x-popup-group-padding", `${groupStyle.paddingTop} ${groupStyle.paddingRight} ${groupStyle.paddingBottom} ${groupStyle.paddingLeft}`);
|
|
@@ -67300,25 +67323,49 @@ const engageGroup = (groupEl, group, {
|
|
|
67300
67323
|
if (silent) {
|
|
67301
67324
|
groupEl.setAttribute("navi-transitions-off", "");
|
|
67302
67325
|
}
|
|
67303
|
-
// showModal() runs the dialog focusing steps
|
|
67304
|
-
// marked autofocus, or else the first focusable the group holds
|
|
67305
|
-
// scrolls the group's own scroller to it
|
|
67306
|
-
//
|
|
67307
|
-
//
|
|
67308
|
-
//
|
|
67309
|
-
//
|
|
67310
|
-
|
|
67326
|
+
// showModal() runs the dialog focusing steps — it focuses the first element
|
|
67327
|
+
// marked autofocus, or else the first focusable the group holds, and
|
|
67328
|
+
// scrolls the group's own scroller to it — and records what held the focus,
|
|
67329
|
+
// for close() to give it back there. A native autofocus is otherwise never
|
|
67330
|
+
// used (see the focus skill): these exist for the length of the call, to
|
|
67331
|
+
// steer the browser's own pick, not to transfer anything.
|
|
67332
|
+
//
|
|
67333
|
+
// From inside the group the pick is what holds the focus already, any other
|
|
67334
|
+
// mark set aside, so nothing moves. From outside, a stop is put first in the
|
|
67335
|
+
// group and focused: it is what showModal() picks and records, and it is
|
|
67336
|
+
// gone by the close, which then moves nothing (see cleanup) — what the focus
|
|
67337
|
+
// came from, a sheet around the group, could not be kept from taking it
|
|
67338
|
+
// back. The focus goes on to the group itself, so no peer is focused on the
|
|
67339
|
+
// way.
|
|
67340
|
+
let focusStop = null;
|
|
67341
|
+
if (!focusWasInGroup) {
|
|
67342
|
+
focusStop = document.createElement("span");
|
|
67343
|
+
focusStop.setAttribute("navi-out-of-flow", "");
|
|
67344
|
+
focusStop.setAttribute("aria-hidden", "true");
|
|
67345
|
+
focusStop.tabIndex = -1;
|
|
67346
|
+
focusStop.style.cssText = "position: absolute; width: 0; height: 0; overflow: hidden";
|
|
67347
|
+
groupEl.prepend(focusStop);
|
|
67348
|
+
moveFocusTo(focusStop, {
|
|
67349
|
+
focusVisible: false
|
|
67350
|
+
});
|
|
67351
|
+
}
|
|
67352
|
+
const autofocusTarget = focusWasInGroup ? focusedBefore : focusStop;
|
|
67311
67353
|
const autofocusWasSet = autofocusTarget.hasAttribute("autofocus");
|
|
67312
|
-
const autofocusSetAside =
|
|
67354
|
+
const autofocusSetAside = [...groupEl.querySelectorAll("[autofocus]")].filter(element => element !== autofocusTarget);
|
|
67313
67355
|
for (const element of autofocusSetAside) {
|
|
67314
67356
|
element.removeAttribute("autofocus");
|
|
67315
67357
|
}
|
|
67316
67358
|
if (!autofocusWasSet) {
|
|
67317
67359
|
autofocusTarget.setAttribute("autofocus", "");
|
|
67318
67360
|
}
|
|
67361
|
+
// Chromium takes a <dialog> without a tabindex for no focus target. Lent
|
|
67362
|
+
// for the whole engagement: taken back from the group holding the focus,
|
|
67363
|
+
// it would lose it.
|
|
67364
|
+
const tabindexLent = Boolean(focusStop) && !groupEl.hasAttribute("tabindex");
|
|
67319
67365
|
try {
|
|
67320
67366
|
groupEl.showModal();
|
|
67321
67367
|
} catch (error) {
|
|
67368
|
+
focusStop?.remove();
|
|
67322
67369
|
placeholder.remove();
|
|
67323
67370
|
clearPlacement(groupEl);
|
|
67324
67371
|
groupEl.removeAttribute("navi-transitions-off");
|
|
@@ -67331,6 +67378,15 @@ const engageGroup = (groupEl, group, {
|
|
|
67331
67378
|
element.setAttribute("autofocus", "");
|
|
67332
67379
|
}
|
|
67333
67380
|
}
|
|
67381
|
+
if (focusStop) {
|
|
67382
|
+
if (tabindexLent) {
|
|
67383
|
+
groupEl.tabIndex = -1;
|
|
67384
|
+
}
|
|
67385
|
+
moveFocusTo(groupEl, {
|
|
67386
|
+
focusVisible: false
|
|
67387
|
+
});
|
|
67388
|
+
focusStop.remove();
|
|
67389
|
+
}
|
|
67334
67390
|
// Measured once the group has left the flow, where a parent sized by its
|
|
67335
67391
|
// content no longer counts it. Only a box given its width by what holds it
|
|
67336
67392
|
// goes on following that width (a rotation while engaged); one sized by its
|
|
@@ -67353,9 +67409,6 @@ const engageGroup = (groupEl, group, {
|
|
|
67353
67409
|
focusVisible: ringBefore
|
|
67354
67410
|
});
|
|
67355
67411
|
}
|
|
67356
|
-
// What showModal() left focused when the focus came from outside: that is
|
|
67357
|
-
// the browser's pick, not a restore of navi's, at the close (see cleanup).
|
|
67358
|
-
const focusLeftByShowModal = focusWasInGroup ? null : document.activeElement;
|
|
67359
67412
|
if (silent) {
|
|
67360
67413
|
// The wall's starting style is resolved with no transition to play from
|
|
67361
67414
|
// it; giving the transition back afterwards changes no value, so nothing
|
|
@@ -67387,33 +67440,55 @@ const engageGroup = (groupEl, group, {
|
|
|
67387
67440
|
placeholder.remove();
|
|
67388
67441
|
if (leavingTree) {
|
|
67389
67442
|
// Closed once the commit is over, as a Dialog leaving the tree is (see
|
|
67390
|
-
// its close cleanup)
|
|
67391
|
-
//
|
|
67392
|
-
// takes the group out of the top layer and leaves its `open`
|
|
67393
|
-
// attribute, which the reveal would show.
|
|
67443
|
+
// its close cleanup). A park takes the group out of the top layer and
|
|
67444
|
+
// leaves its `open` attribute, which the reveal would show.
|
|
67394
67445
|
queueMicrotask(() => {
|
|
67395
67446
|
if (groupEl.open) {
|
|
67396
67447
|
groupEl.close();
|
|
67397
67448
|
}
|
|
67449
|
+
if (tabindexLent) {
|
|
67450
|
+
groupEl.removeAttribute("tabindex");
|
|
67451
|
+
}
|
|
67398
67452
|
});
|
|
67453
|
+
if (focusOrigin) {
|
|
67454
|
+
// The focus left with the group: back where it was, if that stays.
|
|
67455
|
+
giveFocusBackAfterCommit(focusOrigin, () => {
|
|
67456
|
+
moveFocusTo(focusOrigin, {
|
|
67457
|
+
focusVisible: ringBefore
|
|
67458
|
+
});
|
|
67459
|
+
});
|
|
67460
|
+
}
|
|
67399
67461
|
return;
|
|
67400
67462
|
}
|
|
67401
|
-
// close() hands the focus back to whatever held it at showModal() time
|
|
67402
|
-
// (focusedBefore), undoing where a popup of the group, or the user, put
|
|
67403
|
-
// it since. Unless no popup moved the focus since showModal() did, that
|
|
67404
|
-
// element is made no focus target for the call, so the focus stays where
|
|
67405
|
-
// it is: inert — or, when the focus is inside it (the sheet around the
|
|
67406
|
-
// popover around the group, focused by a press on its blank), without
|
|
67407
|
-
// its tabindex, inert taking the focus with it. A <dialog> stays
|
|
67408
|
-
// focusable without one: around a Dialog sheet, close() focuses it and
|
|
67409
|
-
// the moveFocusTo below brings the focus back.
|
|
67410
67463
|
const focusedBeforeClose = document.activeElement;
|
|
67464
|
+
if (!focusWasInGroup) {
|
|
67465
|
+
// What showModal() recorded is the stop, gone by now: close() moves
|
|
67466
|
+
// nothing, and the focus stays where a popup of the group, or the
|
|
67467
|
+
// user, put it. Only one still on the group, or lost, goes back to
|
|
67468
|
+
// where it came from.
|
|
67469
|
+
if (groupEl.open) {
|
|
67470
|
+
groupEl.close();
|
|
67471
|
+
}
|
|
67472
|
+
if (focusOrigin && focusOrigin.isConnected && (!focusedBeforeClose || focusedBeforeClose === document.body || focusedBeforeClose === groupEl)) {
|
|
67473
|
+
moveFocusTo(focusOrigin, {
|
|
67474
|
+
focusVisible: ringBefore
|
|
67475
|
+
});
|
|
67476
|
+
}
|
|
67477
|
+
if (tabindexLent) {
|
|
67478
|
+
groupEl.removeAttribute("tabindex");
|
|
67479
|
+
}
|
|
67480
|
+
return;
|
|
67481
|
+
}
|
|
67482
|
+
// Coming from inside the group, the focus was recorded where it was,
|
|
67483
|
+
// and close() gives it back there, undoing where a popup of the group,
|
|
67484
|
+
// or the user, put it since. Unless nothing moved it, that element is
|
|
67485
|
+
// made no focus target for the call: inert — or, holding the focus
|
|
67486
|
+
// inside it, without its tabindex, inert taking the focus with it.
|
|
67411
67487
|
const ringBeforeClose = Boolean(focusedBeforeClose?.matches?.(":focus-visible"));
|
|
67412
|
-
const
|
|
67413
|
-
const
|
|
67414
|
-
const
|
|
67415
|
-
const
|
|
67416
|
-
const tabindexSetAside = holdsFocusInside && focusedBefore.tagName !== "DIALOG" ? focusedBefore.getAttribute("tabindex") : null;
|
|
67488
|
+
const focusMovedInGroup = focusWasInGroup && focusedBeforeClose && focusedBeforeClose !== document.body && focusedBeforeClose !== groupEl && focusedBeforeClose !== focusedBefore && focusedBeforeClose.isConnected && focusedBefore.isConnected;
|
|
67489
|
+
const holdsFocusInside = focusMovedInGroup && focusedBefore.contains(focusedBeforeClose);
|
|
67490
|
+
const madeInert = focusMovedInGroup && !holdsFocusInside && !focusedBefore.inert;
|
|
67491
|
+
const tabindexSetAside = holdsFocusInside ? focusedBefore.getAttribute("tabindex") : null;
|
|
67417
67492
|
if (madeInert) {
|
|
67418
67493
|
focusedBefore.inert = true;
|
|
67419
67494
|
}
|
|
@@ -67431,10 +67506,13 @@ const engageGroup = (groupEl, group, {
|
|
|
67431
67506
|
if (tabindexSetAside !== null) {
|
|
67432
67507
|
focusedBefore.setAttribute("tabindex", tabindexSetAside);
|
|
67433
67508
|
}
|
|
67509
|
+
if (tabindexLent) {
|
|
67510
|
+
groupEl.removeAttribute("tabindex");
|
|
67511
|
+
}
|
|
67434
67512
|
}
|
|
67435
|
-
// Where the engine moved it anyway
|
|
67436
|
-
// tabindex
|
|
67437
|
-
if (
|
|
67513
|
+
// Where the engine moved it anyway: an element focusable without a
|
|
67514
|
+
// tabindex.
|
|
67515
|
+
if (focusMovedInGroup && document.activeElement !== focusedBeforeClose) {
|
|
67438
67516
|
moveFocusTo(focusedBeforeClose, {
|
|
67439
67517
|
focusVisible: ringBeforeClose
|
|
67440
67518
|
});
|
|
@@ -67618,7 +67696,8 @@ const engageGroup = (groupEl, group, {
|
|
|
67618
67696
|
* screen it is synthesized at touchend and hit-tested again, with the group
|
|
67619
67697
|
* still up, so a finger that scrolled the group (pointercancel, no mousedown)
|
|
67620
67698
|
* closes nothing. A pointerdown cancelled downstream (a drag source) brings no
|
|
67621
|
-
* mousedown: its click decides instead
|
|
67699
|
+
* mousedown: its click decides instead, or the start of the drag it turns
|
|
67700
|
+
* into, which brings no click. A popup that captures the outside is
|
|
67622
67701
|
* answered at the pointerdown itself: nothing of that press may reach a peer.
|
|
67623
67702
|
*/
|
|
67624
67703
|
const armPressArbiter = (groupEl, group, engagement) => {
|
|
@@ -67787,7 +67866,28 @@ const armPressArbiter = (groupEl, group, engagement) => {
|
|
|
67787
67866
|
engagement.checkAfterTask();
|
|
67788
67867
|
}
|
|
67789
67868
|
};
|
|
67790
|
-
|
|
67869
|
+
// A press that becomes a drag brings no click: decided at the click, it
|
|
67870
|
+
// would leave the open popup over a card being carried away, its own
|
|
67871
|
+
// trigger maybe. A drag starting outside the open popup closes it, as a
|
|
67872
|
+
// press there does. Nothing is left to hand over to then, so the group goes
|
|
67873
|
+
// back into the page at once — where the drag can land: what is outside the
|
|
67874
|
+
// group is inert while it is engaged.
|
|
67875
|
+
const onDragStart = dragStartEvent => {
|
|
67876
|
+
const openMember = findMemberPressedOutside(dragStartEvent);
|
|
67877
|
+
if (openMember) {
|
|
67878
|
+
const pressOutside = readPressOutside(openMember);
|
|
67879
|
+
if (pressOutside !== "capture") {
|
|
67880
|
+
openMember.openController.requestClose(dragStartEvent, {
|
|
67881
|
+
isCancel: pressOutside === "cancel"
|
|
67882
|
+
});
|
|
67883
|
+
}
|
|
67884
|
+
}
|
|
67885
|
+
if (engagement.pressClosed) {
|
|
67886
|
+
engagement.pressClosed = false;
|
|
67887
|
+
group.disengageIfNothingOpen();
|
|
67888
|
+
}
|
|
67889
|
+
};
|
|
67890
|
+
const listeners = [["pointerdown", onPointerDown], ["mousedown", onMouseDown], ["click", onClick], ["pointerup", onPointerUp], ["pointercancel", onPointerCancel], ["navi_drag_start", onDragStart]];
|
|
67791
67891
|
for (const [type, listener] of listeners) {
|
|
67792
67892
|
document.addEventListener(type, listener, {
|
|
67793
67893
|
capture: true
|
|
@@ -68023,8 +68123,6 @@ const readAncestorClip = (element, elementStyle) => {
|
|
|
68023
68123
|
if (elementStyle.position === "fixed") {
|
|
68024
68124
|
return null;
|
|
68025
68125
|
}
|
|
68026
|
-
// Read on body, an overflow that is the viewport's clips nothing.
|
|
68027
|
-
const bodyClipsNothing = bodyOverflowIsTheViewports();
|
|
68028
68126
|
let left = -Infinity;
|
|
68029
68127
|
let top = -Infinity;
|
|
68030
68128
|
let right = Infinity;
|
|
@@ -68046,9 +68144,10 @@ const readAncestorClip = (element, elementStyle) => {
|
|
|
68046
68144
|
overflowY,
|
|
68047
68145
|
position
|
|
68048
68146
|
} = ancestorStyle;
|
|
68049
|
-
|
|
68050
|
-
const
|
|
68051
|
-
const
|
|
68147
|
+
// Read on a body whose overflow is the viewport's, it clips nothing.
|
|
68148
|
+
const clipsNothing = overflowGoesToViewport(ancestor);
|
|
68149
|
+
const clipsX = !clipsNothing && overflowX !== "visible";
|
|
68150
|
+
const clipsY = !clipsNothing && overflowY !== "visible";
|
|
68052
68151
|
if (clipsX || clipsY) {
|
|
68053
68152
|
clipped = true;
|
|
68054
68153
|
const ancestorRect = ancestor.getBoundingClientRect();
|
|
@@ -68088,12 +68187,6 @@ const readAncestorClip = (element, elementStyle) => {
|
|
|
68088
68187
|
bottom
|
|
68089
68188
|
} : null;
|
|
68090
68189
|
};
|
|
68091
|
-
// The overflow of <body> is the viewport's when the root leaves its own
|
|
68092
|
-
// visible: body itself then neither scrolls nor clips.
|
|
68093
|
-
const bodyOverflowIsTheViewports = () => {
|
|
68094
|
-
const rootStyle = getComputedStyle(document.documentElement);
|
|
68095
|
-
return rootStyle.overflowX === "visible" && rootStyle.overflowY === "visible";
|
|
68096
|
-
};
|
|
68097
68190
|
const isContainingBlockForAbsolute = style => style.position !== "static" || style.transform !== "none" || style.translate !== "none" || style.rotate !== "none" || style.scale !== "none" || style.perspective !== "none" || style.filter !== "none" ||
|
|
68098
68191
|
// Prefixed alone before Safari 18.
|
|
68099
68192
|
(style.backdropFilter ?? style.webkitBackdropFilter ?? "none") !== "none" || /\b(?:layout|paint|strict|content)\b/.test(style.contain) ||
|
|
@@ -71253,9 +71346,13 @@ const useDialogProps = props => {
|
|
|
71253
71346
|
};
|
|
71254
71347
|
const onKeyDownShortcuts = createOnKeyDownForShortcuts({
|
|
71255
71348
|
escape: e => {
|
|
71256
|
-
//
|
|
71257
|
-
//
|
|
71258
|
-
|
|
71349
|
+
// Answered at the keydown, modal or not: the innermost popup takes the
|
|
71350
|
+
// key and spends it (requestClose prevents it), so one Escape closes
|
|
71351
|
+
// one popup. A modal's native "cancel" comes only once the keydown has
|
|
71352
|
+
// been dispatched, and the key would reach a popup around this one
|
|
71353
|
+
// first — a picker whose drawing holds this dialog. onCancel below is
|
|
71354
|
+
// left the close requests that are no keydown: the back gesture.
|
|
71355
|
+
if (!openController.opened) {
|
|
71259
71356
|
return null;
|
|
71260
71357
|
}
|
|
71261
71358
|
return {
|
|
@@ -71436,9 +71533,9 @@ const useDialogProps = props => {
|
|
|
71436
71533
|
onKeyDownShortcuts(e);
|
|
71437
71534
|
},
|
|
71438
71535
|
"onCancel": e => {
|
|
71439
|
-
// The dialog's own "cancel"
|
|
71440
|
-
//
|
|
71441
|
-
// onKeyDownShortcuts above) fires on the dialog element itself. But a child
|
|
71536
|
+
// The dialog's own "cancel" — a close request that is no keydown (the
|
|
71537
|
+
// back gesture), an Escape being answered at its keydown (see
|
|
71538
|
+
// onKeyDownShortcuts above) — fires on the dialog element itself. But a child
|
|
71442
71539
|
// <input type="file"> also fires a BUBBLING "cancel" when the user
|
|
71443
71540
|
// dismisses the file chooser (per the HTML spec), and it lands here
|
|
71444
71541
|
// too; only the dialog's own cancel means "close".
|
|
@@ -74119,6 +74216,10 @@ const PickerCustom = props => {
|
|
|
74119
74216
|
}
|
|
74120
74217
|
};
|
|
74121
74218
|
});
|
|
74219
|
+
// Whoever hears the Escape — this picker's trigger, its popup, the group
|
|
74220
|
+
// it stands in, its bubble — asks for a cancel; the controller reads it
|
|
74221
|
+
// with this (see escapeEffect in open_controller.js).
|
|
74222
|
+
openController.escapeEffect = escapeEffect;
|
|
74122
74223
|
// scroll <button> of the picker into view when opening it
|
|
74123
74224
|
// -> would be overriden by dialog.jsx or popover.jsx
|
|
74124
74225
|
// so ideally openEffect should be either protective or a pubSub to allow multiple callbacks
|
|
@@ -74344,13 +74445,13 @@ const PickerCustom = props => {
|
|
|
74344
74445
|
if (!openController.opened) {
|
|
74345
74446
|
return null;
|
|
74346
74447
|
}
|
|
74347
|
-
const isCancel = escapeEffect === "cancel";
|
|
74348
74448
|
return {
|
|
74349
|
-
name:
|
|
74449
|
+
name: "escape_to_cancel",
|
|
74350
74450
|
intent: "read",
|
|
74351
74451
|
allowed: () => {
|
|
74452
|
+
// Read with escapeEffect by the controller (see above).
|
|
74352
74453
|
requestClose(e, {
|
|
74353
|
-
isCancel
|
|
74454
|
+
isCancel: true
|
|
74354
74455
|
});
|
|
74355
74456
|
e.preventDefault(); // prevent browser from closing the dialog (if any)
|
|
74356
74457
|
}
|
|
@@ -74502,10 +74603,7 @@ const PickerCustom = props => {
|
|
|
74502
74603
|
}
|
|
74503
74604
|
return renderResolver(PickerContentInsidePopup, {
|
|
74504
74605
|
...pickerProps,
|
|
74505
|
-
mode
|
|
74506
|
-
// What a callout's own Escape means (see PickerCalloutPopup): the bubble
|
|
74507
|
-
// answers the key itself when the focus is in it.
|
|
74508
|
-
escapeEffect
|
|
74606
|
+
mode
|
|
74509
74607
|
});
|
|
74510
74608
|
};
|
|
74511
74609
|
|
|
@@ -74620,7 +74718,6 @@ const PickerContentInsidePopup = props => {
|
|
|
74620
74718
|
calloutIcon,
|
|
74621
74719
|
calloutCloseButton,
|
|
74622
74720
|
calloutCloseByScroll,
|
|
74623
|
-
escapeEffect,
|
|
74624
74721
|
...rest
|
|
74625
74722
|
} = props;
|
|
74626
74723
|
const isPopover = mode === "popover";
|
|
@@ -74669,7 +74766,6 @@ const PickerContentInsidePopup = props => {
|
|
|
74669
74766
|
,
|
|
74670
74767
|
backdrop: backdrop,
|
|
74671
74768
|
pressOutside: pressOutside,
|
|
74672
|
-
escapeEffect: escapeEffect,
|
|
74673
74769
|
children: jsx(PopupModeContext.Provider, {
|
|
74674
74770
|
value: mode,
|
|
74675
74771
|
children: children
|
|
@@ -74767,7 +74863,6 @@ const PickerCalloutPopup = ({
|
|
|
74767
74863
|
closeByScroll,
|
|
74768
74864
|
backdrop,
|
|
74769
74865
|
pressOutside,
|
|
74770
|
-
escapeEffect,
|
|
74771
74866
|
onnavi_request_open,
|
|
74772
74867
|
onnavi_request_close,
|
|
74773
74868
|
onnavi_request_confirm,
|
|
@@ -74849,14 +74944,14 @@ const PickerCalloutPopup = ({
|
|
|
74849
74944
|
// Not skipped: the callout moves the focus into the picker when it is
|
|
74850
74945
|
// elsewhere, which is what lets Escape find the callout right away.
|
|
74851
74946
|
skipFocus: false,
|
|
74852
|
-
// An Escape the callout answered itself (the focus in the bubble)
|
|
74853
|
-
//
|
|
74947
|
+
// An Escape the callout answered itself (the focus in the bubble) is
|
|
74948
|
+
// the picker's Escape (see escapeEffect in open_controller.js).
|
|
74854
74949
|
onClose: ({
|
|
74855
74950
|
event,
|
|
74856
74951
|
reason
|
|
74857
74952
|
}) => {
|
|
74858
74953
|
openController.close(event, {
|
|
74859
|
-
isCancel: reason === "escape_key"
|
|
74954
|
+
isCancel: reason === "escape_key"
|
|
74860
74955
|
});
|
|
74861
74956
|
}
|
|
74862
74957
|
});
|