@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/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, 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, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, setVirtualKeyboardOverlaysContent, subscribeVirtualKeyboardGeometryChange, getVirtualKeyboardOverlayHeight } from "@jsenv/dom";
|
|
6
|
+
import { createPubSub, elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, 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, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, setVirtualKeyboardOverlaysContent, subscribeVirtualKeyboardGeometryChange, getVirtualKeyboardOverlayHeight } 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";
|
|
@@ -6554,6 +6554,12 @@ const isAimedAtSelfInteractionsBelow = (
|
|
|
6554
6554
|
if (!target || typeof target.closest !== "function") {
|
|
6555
6555
|
return false;
|
|
6556
6556
|
}
|
|
6557
|
+
if (event.type === "keydown" || event.type === "keyup") {
|
|
6558
|
+
// A key is no press: the claim is the pointer's. An Escape pressed while
|
|
6559
|
+
// the focus rests on the claimer asks the controls around it — the sheet
|
|
6560
|
+
// it sits in closing.
|
|
6561
|
+
return false;
|
|
6562
|
+
}
|
|
6557
6563
|
const claimer = target.closest(CLICK_SELECTOR);
|
|
6558
6564
|
if (!claimer) {
|
|
6559
6565
|
return false;
|
|
@@ -16120,6 +16126,17 @@ const createOpenController = (
|
|
|
16120
16126
|
{ opened: false, event: closeEvent },
|
|
16121
16127
|
);
|
|
16122
16128
|
};
|
|
16129
|
+
// A cancel an Escape asked for, read with what Escape means for this popup.
|
|
16130
|
+
const readCloseDetail = (e, detail) => {
|
|
16131
|
+
if (
|
|
16132
|
+
detail?.isCancel &&
|
|
16133
|
+
controller.escapeEffect !== "cancel" &&
|
|
16134
|
+
findEvent(e, isEscapeKeydown)
|
|
16135
|
+
) {
|
|
16136
|
+
return { ...detail, isCancel: false };
|
|
16137
|
+
}
|
|
16138
|
+
return detail;
|
|
16139
|
+
};
|
|
16123
16140
|
const controller = {
|
|
16124
16141
|
opened: false,
|
|
16125
16142
|
// What the DOM currently shows, as opposed to what the controller has
|
|
@@ -16151,6 +16168,11 @@ const createOpenController = (
|
|
|
16151
16168
|
// whole point: learning it afterwards means the content mounted on the
|
|
16152
16169
|
// previous subject first.
|
|
16153
16170
|
onOpen: null,
|
|
16171
|
+
// What an Escape means for this popup, whoever heard the key — its own
|
|
16172
|
+
// shortcut, its trigger's, the group it stands in, a bubble's: a cancel
|
|
16173
|
+
// (what it holds reverts), unless its owner says otherwise (a Picker's
|
|
16174
|
+
// escapeEffect="close"). Read from the close the key asked for.
|
|
16175
|
+
escapeEffect: "cancel",
|
|
16154
16176
|
// The counterpart, set only when the popup was told to throw its content
|
|
16155
16177
|
// away on close (`mount="while-opened"`). Called from performClose above.
|
|
16156
16178
|
unmountContent: null,
|
|
@@ -16320,7 +16342,7 @@ const createOpenController = (
|
|
|
16320
16342
|
return true;
|
|
16321
16343
|
}
|
|
16322
16344
|
const requestCloseEvent = new CustomEvent("navi_request_close", {
|
|
16323
|
-
detail: { event: e, ...detail },
|
|
16345
|
+
detail: { event: e, ...readCloseDetail(e, detail) },
|
|
16324
16346
|
cancelable: true,
|
|
16325
16347
|
});
|
|
16326
16348
|
chainEvent(requestCloseEvent, e);
|
|
@@ -16330,10 +16352,7 @@ const createOpenController = (
|
|
|
16330
16352
|
// the popup (a PopupGroup, a dialog), closing what this popup kept open,
|
|
16331
16353
|
// or what is still closing with it (a lifting dialog closes a frame
|
|
16332
16354
|
// later).
|
|
16333
|
-
const escapeKeydown = findEvent(
|
|
16334
|
-
requestCloseEvent,
|
|
16335
|
-
(event) => event.type === "keydown" && event.key === "Escape",
|
|
16336
|
-
);
|
|
16355
|
+
const escapeKeydown = findEvent(requestCloseEvent, isEscapeKeydown);
|
|
16337
16356
|
if (escapeKeydown) {
|
|
16338
16357
|
escapeKeydown.preventDefault();
|
|
16339
16358
|
}
|
|
@@ -16355,7 +16374,7 @@ const createOpenController = (
|
|
|
16355
16374
|
return;
|
|
16356
16375
|
}
|
|
16357
16376
|
const closeEvent = new CustomEvent("navi_close", {
|
|
16358
|
-
detail: { event: e, ...detail },
|
|
16377
|
+
detail: { event: e, ...readCloseDetail(e, detail) },
|
|
16359
16378
|
});
|
|
16360
16379
|
chainEvent(closeEvent, e);
|
|
16361
16380
|
// Skips onRequestClose entirely — there is no choice here.
|
|
@@ -16366,6 +16385,9 @@ const createOpenController = (
|
|
|
16366
16385
|
};
|
|
16367
16386
|
|
|
16368
16387
|
// A click a keyboard made (detail 0) is not one: nothing pressed for it.
|
|
16388
|
+
const isEscapeKeydown = (event) =>
|
|
16389
|
+
event.type === "keydown" && event.key === "Escape";
|
|
16390
|
+
|
|
16369
16391
|
const isEventOfAPress = (event) =>
|
|
16370
16392
|
event.type === "pointerdown" ||
|
|
16371
16393
|
event.type === "mousedown" ||
|
|
@@ -18006,8 +18028,10 @@ const openCallout = (
|
|
|
18006
18028
|
}
|
|
18007
18029
|
calloutCloseButton.tabIndex = -1;
|
|
18008
18030
|
calloutCloseButton.setAttribute("navi-focus-delegate", "");
|
|
18031
|
+
// One Escape closes one popup: an Escape already spent by one in front
|
|
18032
|
+
// of the callout — a menu opened from its content — is not this one's.
|
|
18009
18033
|
const onAnchorKeydown = (e) => {
|
|
18010
|
-
if (e.key === "Escape") {
|
|
18034
|
+
if (e.key === "Escape" && !e.defaultPrevented) {
|
|
18011
18035
|
requestClose(e, "escape_key");
|
|
18012
18036
|
e.stopPropagation();
|
|
18013
18037
|
e.preventDefault();
|
|
@@ -18022,7 +18046,7 @@ const openCallout = (
|
|
|
18022
18046
|
// Usually callout is inside anchor so the anchor would see the escape being pressed
|
|
18023
18047
|
// but callout can be appended outside anchor when anchor cannot receive children
|
|
18024
18048
|
const onCalloutKeydown = (e) => {
|
|
18025
|
-
if (e.key === "Escape") {
|
|
18049
|
+
if (e.key === "Escape" && !e.defaultPrevented) {
|
|
18026
18050
|
requestClose(e, "escape_key");
|
|
18027
18051
|
e.stopPropagation();
|
|
18028
18052
|
e.preventDefault();
|
|
@@ -18068,21 +18092,23 @@ const openCallout = (
|
|
|
18068
18092
|
//
|
|
18069
18093
|
// Only for the ones that cannot be heard otherwise, and only while they are
|
|
18070
18094
|
// open: a callout the anchor can hear about is left to the anchor, so
|
|
18071
|
-
// Escape goes on meaning what it means where the user is.
|
|
18095
|
+
// Escape goes on meaning what it means where the user is. Heard once the
|
|
18096
|
+
// key has been through what it was pressed in: one that answered it (a
|
|
18097
|
+
// popup closing, a field clearing) spent it.
|
|
18072
18098
|
if (anchorElement && anchorElement.contains(document.activeElement)) {
|
|
18073
18099
|
break close_on_escape_from_anywhere;
|
|
18074
18100
|
}
|
|
18075
18101
|
const onDocumentKeydown = (e) => {
|
|
18076
|
-
if (e.key !== "Escape") {
|
|
18102
|
+
if (e.key !== "Escape" || e.defaultPrevented) {
|
|
18077
18103
|
return;
|
|
18078
18104
|
}
|
|
18079
18105
|
requestClose(e, "escape_key");
|
|
18080
18106
|
e.stopPropagation();
|
|
18081
18107
|
e.preventDefault();
|
|
18082
18108
|
};
|
|
18083
|
-
document.addEventListener("keydown", onDocumentKeydown
|
|
18109
|
+
document.addEventListener("keydown", onDocumentKeydown);
|
|
18084
18110
|
addTeardown(() => {
|
|
18085
|
-
document.removeEventListener("keydown", onDocumentKeydown
|
|
18111
|
+
document.removeEventListener("keydown", onDocumentKeydown);
|
|
18086
18112
|
});
|
|
18087
18113
|
}
|
|
18088
18114
|
{
|
|
@@ -34354,7 +34380,7 @@ const readOuterScroll = (element) => {
|
|
|
34354
34380
|
};
|
|
34355
34381
|
|
|
34356
34382
|
const isDocumentScroller = (node) => {
|
|
34357
|
-
return node === document.documentElement
|
|
34383
|
+
return node === document.documentElement;
|
|
34358
34384
|
};
|
|
34359
34385
|
|
|
34360
34386
|
const isPinnedToViewport = (node) => {
|
|
@@ -37580,12 +37606,12 @@ const whilePageRenders = async (page, change, wait = armRouteRenderWait()) => {
|
|
|
37580
37606
|
// (`overflow: clip`) holds nothing and is walked straight through — see
|
|
37581
37607
|
// is_scrollable.js.
|
|
37582
37608
|
const pagesScrollTheDocument = element => {
|
|
37583
|
-
|
|
37609
|
+
// What window.scrollTo moves. A body whose overflow is the viewport's is
|
|
37610
|
+
// walked through to it (see overflowGoesToViewport in @jsenv/dom); a body
|
|
37611
|
+
// the walk stops on scrolls itself, out of the viewport's reach.
|
|
37612
|
+
return getScrollContainer(element, {
|
|
37584
37613
|
includeHidden: true
|
|
37585
|
-
});
|
|
37586
|
-
// html and body are one answer: whichever of them the walk stops on, what
|
|
37587
|
-
// scrolls is the viewport, which is what window.scrollTo moves.
|
|
37588
|
-
return scrollContainer === document.documentElement || scrollContainer === document.body;
|
|
37614
|
+
}) === document.documentElement;
|
|
37589
37615
|
};
|
|
37590
37616
|
|
|
37591
37617
|
// A page of the row: a route, and the params that say which of its tabs when
|
|
@@ -65975,6 +66001,8 @@ const engageGroup = (groupEl, group, {
|
|
|
65975
66001
|
const themeColor = readPopupThemeColor(groupEl, group.themeColor);
|
|
65976
66002
|
const focusedBefore = document.activeElement;
|
|
65977
66003
|
const focusWasInGroup = Boolean(focusedBefore) && focusedBefore !== groupEl && groupEl.contains(focusedBefore);
|
|
66004
|
+
// What the focus goes back to, when nothing holds it any more at the close.
|
|
66005
|
+
const focusOrigin = !focusWasInGroup && focusedBefore && focusedBefore !== document.body && focusedBefore !== groupEl ? focusedBefore : null;
|
|
65978
66006
|
const ringBefore = Boolean(focusedBefore?.matches?.(":focus-visible"));
|
|
65979
66007
|
const {
|
|
65980
66008
|
scrollTop,
|
|
@@ -65982,12 +66010,7 @@ const engageGroup = (groupEl, group, {
|
|
|
65982
66010
|
} = groupEl;
|
|
65983
66011
|
// A gesture on a box fixed in the top layer goes to the viewport: only a
|
|
65984
66012
|
// place that scrolls with the viewport moves under it.
|
|
65985
|
-
const
|
|
65986
|
-
const placeScrollsWithViewport = (placeScroller === document.scrollingElement ||
|
|
65987
|
-
// Reported as a scroll container on an app setting `body {
|
|
65988
|
-
// overflow-x: hidden }` (its computed overflow-y turns auto), it never
|
|
65989
|
-
// scrolls itself: the viewport does.
|
|
65990
|
-
placeScroller === document.body && bodyOverflowIsTheViewports()) && !findSelfOrAncestorFixedPosition(groupEl);
|
|
66013
|
+
const placeScrollsWithViewport = getScrollContainer(groupEl) === document.scrollingElement && !findSelfOrAncestorFixedPosition(groupEl);
|
|
65991
66014
|
const placeholder = createPlaceholder(groupStyle, rect, baselineDescent);
|
|
65992
66015
|
groupEl.before(placeholder);
|
|
65993
66016
|
groupEl.style.setProperty("--x-popup-group-padding", `${groupStyle.paddingTop} ${groupStyle.paddingRight} ${groupStyle.paddingBottom} ${groupStyle.paddingLeft}`);
|
|
@@ -65995,25 +66018,49 @@ const engageGroup = (groupEl, group, {
|
|
|
65995
66018
|
if (silent) {
|
|
65996
66019
|
groupEl.setAttribute("navi-transitions-off", "");
|
|
65997
66020
|
}
|
|
65998
|
-
// showModal() runs the dialog focusing steps
|
|
65999
|
-
// marked autofocus, or else the first focusable the group holds
|
|
66000
|
-
// scrolls the group's own scroller to it
|
|
66001
|
-
//
|
|
66002
|
-
//
|
|
66003
|
-
//
|
|
66004
|
-
//
|
|
66005
|
-
|
|
66021
|
+
// showModal() runs the dialog focusing steps — it focuses the first element
|
|
66022
|
+
// marked autofocus, or else the first focusable the group holds, and
|
|
66023
|
+
// scrolls the group's own scroller to it — and records what held the focus,
|
|
66024
|
+
// for close() to give it back there. A native autofocus is otherwise never
|
|
66025
|
+
// used (see the focus skill): these exist for the length of the call, to
|
|
66026
|
+
// steer the browser's own pick, not to transfer anything.
|
|
66027
|
+
//
|
|
66028
|
+
// From inside the group the pick is what holds the focus already, any other
|
|
66029
|
+
// mark set aside, so nothing moves. From outside, a stop is put first in the
|
|
66030
|
+
// group and focused: it is what showModal() picks and records, and it is
|
|
66031
|
+
// gone by the close, which then moves nothing (see cleanup) — what the focus
|
|
66032
|
+
// came from, a sheet around the group, could not be kept from taking it
|
|
66033
|
+
// back. The focus goes on to the group itself, so no peer is focused on the
|
|
66034
|
+
// way.
|
|
66035
|
+
let focusStop = null;
|
|
66036
|
+
if (!focusWasInGroup) {
|
|
66037
|
+
focusStop = document.createElement("span");
|
|
66038
|
+
focusStop.setAttribute("navi-out-of-flow", "");
|
|
66039
|
+
focusStop.setAttribute("aria-hidden", "true");
|
|
66040
|
+
focusStop.tabIndex = -1;
|
|
66041
|
+
focusStop.style.cssText = "position: absolute; width: 0; height: 0; overflow: hidden";
|
|
66042
|
+
groupEl.prepend(focusStop);
|
|
66043
|
+
moveFocusTo(focusStop, {
|
|
66044
|
+
focusVisible: false
|
|
66045
|
+
});
|
|
66046
|
+
}
|
|
66047
|
+
const autofocusTarget = focusWasInGroup ? focusedBefore : focusStop;
|
|
66006
66048
|
const autofocusWasSet = autofocusTarget.hasAttribute("autofocus");
|
|
66007
|
-
const autofocusSetAside =
|
|
66049
|
+
const autofocusSetAside = [...groupEl.querySelectorAll("[autofocus]")].filter(element => element !== autofocusTarget);
|
|
66008
66050
|
for (const element of autofocusSetAside) {
|
|
66009
66051
|
element.removeAttribute("autofocus");
|
|
66010
66052
|
}
|
|
66011
66053
|
if (!autofocusWasSet) {
|
|
66012
66054
|
autofocusTarget.setAttribute("autofocus", "");
|
|
66013
66055
|
}
|
|
66056
|
+
// Chromium takes a <dialog> without a tabindex for no focus target. Lent
|
|
66057
|
+
// for the whole engagement: taken back from the group holding the focus,
|
|
66058
|
+
// it would lose it.
|
|
66059
|
+
const tabindexLent = Boolean(focusStop) && !groupEl.hasAttribute("tabindex");
|
|
66014
66060
|
try {
|
|
66015
66061
|
groupEl.showModal();
|
|
66016
66062
|
} catch (error) {
|
|
66063
|
+
focusStop?.remove();
|
|
66017
66064
|
placeholder.remove();
|
|
66018
66065
|
clearPlacement(groupEl);
|
|
66019
66066
|
groupEl.removeAttribute("navi-transitions-off");
|
|
@@ -66026,6 +66073,15 @@ const engageGroup = (groupEl, group, {
|
|
|
66026
66073
|
element.setAttribute("autofocus", "");
|
|
66027
66074
|
}
|
|
66028
66075
|
}
|
|
66076
|
+
if (focusStop) {
|
|
66077
|
+
if (tabindexLent) {
|
|
66078
|
+
groupEl.tabIndex = -1;
|
|
66079
|
+
}
|
|
66080
|
+
moveFocusTo(groupEl, {
|
|
66081
|
+
focusVisible: false
|
|
66082
|
+
});
|
|
66083
|
+
focusStop.remove();
|
|
66084
|
+
}
|
|
66029
66085
|
// Measured once the group has left the flow, where a parent sized by its
|
|
66030
66086
|
// content no longer counts it. Only a box given its width by what holds it
|
|
66031
66087
|
// goes on following that width (a rotation while engaged); one sized by its
|
|
@@ -66048,9 +66104,6 @@ const engageGroup = (groupEl, group, {
|
|
|
66048
66104
|
focusVisible: ringBefore
|
|
66049
66105
|
});
|
|
66050
66106
|
}
|
|
66051
|
-
// What showModal() left focused when the focus came from outside: that is
|
|
66052
|
-
// the browser's pick, not a restore of navi's, at the close (see cleanup).
|
|
66053
|
-
const focusLeftByShowModal = focusWasInGroup ? null : document.activeElement;
|
|
66054
66107
|
if (silent) {
|
|
66055
66108
|
// The wall's starting style is resolved with no transition to play from
|
|
66056
66109
|
// it; giving the transition back afterwards changes no value, so nothing
|
|
@@ -66082,33 +66135,55 @@ const engageGroup = (groupEl, group, {
|
|
|
66082
66135
|
placeholder.remove();
|
|
66083
66136
|
if (leavingTree) {
|
|
66084
66137
|
// Closed once the commit is over, as a Dialog leaving the tree is (see
|
|
66085
|
-
// its close cleanup)
|
|
66086
|
-
//
|
|
66087
|
-
// takes the group out of the top layer and leaves its `open`
|
|
66088
|
-
// attribute, which the reveal would show.
|
|
66138
|
+
// its close cleanup). A park takes the group out of the top layer and
|
|
66139
|
+
// leaves its `open` attribute, which the reveal would show.
|
|
66089
66140
|
queueMicrotask(() => {
|
|
66090
66141
|
if (groupEl.open) {
|
|
66091
66142
|
groupEl.close();
|
|
66092
66143
|
}
|
|
66144
|
+
if (tabindexLent) {
|
|
66145
|
+
groupEl.removeAttribute("tabindex");
|
|
66146
|
+
}
|
|
66093
66147
|
});
|
|
66148
|
+
if (focusOrigin) {
|
|
66149
|
+
// The focus left with the group: back where it was, if that stays.
|
|
66150
|
+
giveFocusBackAfterCommit(focusOrigin, () => {
|
|
66151
|
+
moveFocusTo(focusOrigin, {
|
|
66152
|
+
focusVisible: ringBefore
|
|
66153
|
+
});
|
|
66154
|
+
});
|
|
66155
|
+
}
|
|
66094
66156
|
return;
|
|
66095
66157
|
}
|
|
66096
|
-
// close() hands the focus back to whatever held it at showModal() time
|
|
66097
|
-
// (focusedBefore), undoing where a popup of the group, or the user, put
|
|
66098
|
-
// it since. Unless no popup moved the focus since showModal() did, that
|
|
66099
|
-
// element is made no focus target for the call, so the focus stays where
|
|
66100
|
-
// it is: inert — or, when the focus is inside it (the sheet around the
|
|
66101
|
-
// popover around the group, focused by a press on its blank), without
|
|
66102
|
-
// its tabindex, inert taking the focus with it. A <dialog> stays
|
|
66103
|
-
// focusable without one: around a Dialog sheet, close() focuses it and
|
|
66104
|
-
// the moveFocusTo below brings the focus back.
|
|
66105
66158
|
const focusedBeforeClose = document.activeElement;
|
|
66159
|
+
if (!focusWasInGroup) {
|
|
66160
|
+
// What showModal() recorded is the stop, gone by now: close() moves
|
|
66161
|
+
// nothing, and the focus stays where a popup of the group, or the
|
|
66162
|
+
// user, put it. Only one still on the group, or lost, goes back to
|
|
66163
|
+
// where it came from.
|
|
66164
|
+
if (groupEl.open) {
|
|
66165
|
+
groupEl.close();
|
|
66166
|
+
}
|
|
66167
|
+
if (focusOrigin && focusOrigin.isConnected && (!focusedBeforeClose || focusedBeforeClose === document.body || focusedBeforeClose === groupEl)) {
|
|
66168
|
+
moveFocusTo(focusOrigin, {
|
|
66169
|
+
focusVisible: ringBefore
|
|
66170
|
+
});
|
|
66171
|
+
}
|
|
66172
|
+
if (tabindexLent) {
|
|
66173
|
+
groupEl.removeAttribute("tabindex");
|
|
66174
|
+
}
|
|
66175
|
+
return;
|
|
66176
|
+
}
|
|
66177
|
+
// Coming from inside the group, the focus was recorded where it was,
|
|
66178
|
+
// and close() gives it back there, undoing where a popup of the group,
|
|
66179
|
+
// or the user, put it since. Unless nothing moved it, that element is
|
|
66180
|
+
// made no focus target for the call: inert — or, holding the focus
|
|
66181
|
+
// inside it, without its tabindex, inert taking the focus with it.
|
|
66106
66182
|
const ringBeforeClose = Boolean(focusedBeforeClose?.matches?.(":focus-visible"));
|
|
66107
|
-
const
|
|
66108
|
-
const
|
|
66109
|
-
const
|
|
66110
|
-
const
|
|
66111
|
-
const tabindexSetAside = holdsFocusInside && focusedBefore.tagName !== "DIALOG" ? focusedBefore.getAttribute("tabindex") : null;
|
|
66183
|
+
const focusMovedInGroup = focusWasInGroup && focusedBeforeClose && focusedBeforeClose !== document.body && focusedBeforeClose !== groupEl && focusedBeforeClose !== focusedBefore && focusedBeforeClose.isConnected && focusedBefore.isConnected;
|
|
66184
|
+
const holdsFocusInside = focusMovedInGroup && focusedBefore.contains(focusedBeforeClose);
|
|
66185
|
+
const madeInert = focusMovedInGroup && !holdsFocusInside && !focusedBefore.inert;
|
|
66186
|
+
const tabindexSetAside = holdsFocusInside ? focusedBefore.getAttribute("tabindex") : null;
|
|
66112
66187
|
if (madeInert) {
|
|
66113
66188
|
focusedBefore.inert = true;
|
|
66114
66189
|
}
|
|
@@ -66126,10 +66201,13 @@ const engageGroup = (groupEl, group, {
|
|
|
66126
66201
|
if (tabindexSetAside !== null) {
|
|
66127
66202
|
focusedBefore.setAttribute("tabindex", tabindexSetAside);
|
|
66128
66203
|
}
|
|
66204
|
+
if (tabindexLent) {
|
|
66205
|
+
groupEl.removeAttribute("tabindex");
|
|
66206
|
+
}
|
|
66129
66207
|
}
|
|
66130
|
-
// Where the engine moved it anyway
|
|
66131
|
-
// tabindex
|
|
66132
|
-
if (
|
|
66208
|
+
// Where the engine moved it anyway: an element focusable without a
|
|
66209
|
+
// tabindex.
|
|
66210
|
+
if (focusMovedInGroup && document.activeElement !== focusedBeforeClose) {
|
|
66133
66211
|
moveFocusTo(focusedBeforeClose, {
|
|
66134
66212
|
focusVisible: ringBeforeClose
|
|
66135
66213
|
});
|
|
@@ -66313,7 +66391,8 @@ const engageGroup = (groupEl, group, {
|
|
|
66313
66391
|
* screen it is synthesized at touchend and hit-tested again, with the group
|
|
66314
66392
|
* still up, so a finger that scrolled the group (pointercancel, no mousedown)
|
|
66315
66393
|
* closes nothing. A pointerdown cancelled downstream (a drag source) brings no
|
|
66316
|
-
* mousedown: its click decides instead
|
|
66394
|
+
* mousedown: its click decides instead, or the start of the drag it turns
|
|
66395
|
+
* into, which brings no click. A popup that captures the outside is
|
|
66317
66396
|
* answered at the pointerdown itself: nothing of that press may reach a peer.
|
|
66318
66397
|
*/
|
|
66319
66398
|
const armPressArbiter = (groupEl, group, engagement) => {
|
|
@@ -66482,7 +66561,28 @@ const armPressArbiter = (groupEl, group, engagement) => {
|
|
|
66482
66561
|
engagement.checkAfterTask();
|
|
66483
66562
|
}
|
|
66484
66563
|
};
|
|
66485
|
-
|
|
66564
|
+
// A press that becomes a drag brings no click: decided at the click, it
|
|
66565
|
+
// would leave the open popup over a card being carried away, its own
|
|
66566
|
+
// trigger maybe. A drag starting outside the open popup closes it, as a
|
|
66567
|
+
// press there does. Nothing is left to hand over to then, so the group goes
|
|
66568
|
+
// back into the page at once — where the drag can land: what is outside the
|
|
66569
|
+
// group is inert while it is engaged.
|
|
66570
|
+
const onDragStart = dragStartEvent => {
|
|
66571
|
+
const openMember = findMemberPressedOutside(dragStartEvent);
|
|
66572
|
+
if (openMember) {
|
|
66573
|
+
const pressOutside = readPressOutside(openMember);
|
|
66574
|
+
if (pressOutside !== "capture") {
|
|
66575
|
+
openMember.openController.requestClose(dragStartEvent, {
|
|
66576
|
+
isCancel: pressOutside === "cancel"
|
|
66577
|
+
});
|
|
66578
|
+
}
|
|
66579
|
+
}
|
|
66580
|
+
if (engagement.pressClosed) {
|
|
66581
|
+
engagement.pressClosed = false;
|
|
66582
|
+
group.disengageIfNothingOpen();
|
|
66583
|
+
}
|
|
66584
|
+
};
|
|
66585
|
+
const listeners = [["pointerdown", onPointerDown], ["mousedown", onMouseDown], ["click", onClick], ["pointerup", onPointerUp], ["pointercancel", onPointerCancel], ["navi_drag_start", onDragStart]];
|
|
66486
66586
|
for (const [type, listener] of listeners) {
|
|
66487
66587
|
document.addEventListener(type, listener, {
|
|
66488
66588
|
capture: true
|
|
@@ -66718,8 +66818,6 @@ const readAncestorClip = (element, elementStyle) => {
|
|
|
66718
66818
|
if (elementStyle.position === "fixed") {
|
|
66719
66819
|
return null;
|
|
66720
66820
|
}
|
|
66721
|
-
// Read on body, an overflow that is the viewport's clips nothing.
|
|
66722
|
-
const bodyClipsNothing = bodyOverflowIsTheViewports();
|
|
66723
66821
|
let left = -Infinity;
|
|
66724
66822
|
let top = -Infinity;
|
|
66725
66823
|
let right = Infinity;
|
|
@@ -66741,9 +66839,10 @@ const readAncestorClip = (element, elementStyle) => {
|
|
|
66741
66839
|
overflowY,
|
|
66742
66840
|
position
|
|
66743
66841
|
} = ancestorStyle;
|
|
66744
|
-
|
|
66745
|
-
const
|
|
66746
|
-
const
|
|
66842
|
+
// Read on a body whose overflow is the viewport's, it clips nothing.
|
|
66843
|
+
const clipsNothing = overflowGoesToViewport(ancestor);
|
|
66844
|
+
const clipsX = !clipsNothing && overflowX !== "visible";
|
|
66845
|
+
const clipsY = !clipsNothing && overflowY !== "visible";
|
|
66747
66846
|
if (clipsX || clipsY) {
|
|
66748
66847
|
clipped = true;
|
|
66749
66848
|
const ancestorRect = ancestor.getBoundingClientRect();
|
|
@@ -66783,12 +66882,6 @@ const readAncestorClip = (element, elementStyle) => {
|
|
|
66783
66882
|
bottom
|
|
66784
66883
|
} : null;
|
|
66785
66884
|
};
|
|
66786
|
-
// The overflow of <body> is the viewport's when the root leaves its own
|
|
66787
|
-
// visible: body itself then neither scrolls nor clips.
|
|
66788
|
-
const bodyOverflowIsTheViewports = () => {
|
|
66789
|
-
const rootStyle = getComputedStyle(document.documentElement);
|
|
66790
|
-
return rootStyle.overflowX === "visible" && rootStyle.overflowY === "visible";
|
|
66791
|
-
};
|
|
66792
66885
|
const isContainingBlockForAbsolute = style => style.position !== "static" || style.transform !== "none" || style.translate !== "none" || style.rotate !== "none" || style.scale !== "none" || style.perspective !== "none" || style.filter !== "none" ||
|
|
66793
66886
|
// Prefixed alone before Safari 18.
|
|
66794
66887
|
(style.backdropFilter ?? style.webkitBackdropFilter ?? "none") !== "none" || /\b(?:layout|paint|strict|content)\b/.test(style.contain) ||
|
|
@@ -69886,9 +69979,13 @@ const useDialogProps = props => {
|
|
|
69886
69979
|
};
|
|
69887
69980
|
const onKeyDownShortcuts = createOnKeyDownForShortcuts({
|
|
69888
69981
|
escape: e => {
|
|
69889
|
-
//
|
|
69890
|
-
//
|
|
69891
|
-
|
|
69982
|
+
// Answered at the keydown, modal or not: the innermost popup takes the
|
|
69983
|
+
// key and spends it (requestClose prevents it), so one Escape closes
|
|
69984
|
+
// one popup. A modal's native "cancel" comes only once the keydown has
|
|
69985
|
+
// been dispatched, and the key would reach a popup around this one
|
|
69986
|
+
// first — a picker whose drawing holds this dialog. onCancel below is
|
|
69987
|
+
// left the close requests that are no keydown: the back gesture.
|
|
69988
|
+
if (!openController.opened) {
|
|
69892
69989
|
return null;
|
|
69893
69990
|
}
|
|
69894
69991
|
return {
|
|
@@ -70069,9 +70166,9 @@ const useDialogProps = props => {
|
|
|
70069
70166
|
onKeyDownShortcuts(e);
|
|
70070
70167
|
},
|
|
70071
70168
|
"onCancel": e => {
|
|
70072
|
-
// The dialog's own "cancel"
|
|
70073
|
-
//
|
|
70074
|
-
// onKeyDownShortcuts above) fires on the dialog element itself. But a child
|
|
70169
|
+
// The dialog's own "cancel" — a close request that is no keydown (the
|
|
70170
|
+
// back gesture), an Escape being answered at its keydown (see
|
|
70171
|
+
// onKeyDownShortcuts above) — fires on the dialog element itself. But a child
|
|
70075
70172
|
// <input type="file"> also fires a BUBBLING "cancel" when the user
|
|
70076
70173
|
// dismisses the file chooser (per the HTML spec), and it lands here
|
|
70077
70174
|
// too; only the dialog's own cancel means "close".
|
|
@@ -72700,6 +72797,10 @@ const PickerCustom = props => {
|
|
|
72700
72797
|
}
|
|
72701
72798
|
};
|
|
72702
72799
|
});
|
|
72800
|
+
// Whoever hears the Escape — this picker's trigger, its popup, the group
|
|
72801
|
+
// it stands in, its bubble — asks for a cancel; the controller reads it
|
|
72802
|
+
// with this (see escapeEffect in open_controller.js).
|
|
72803
|
+
openController.escapeEffect = escapeEffect;
|
|
72703
72804
|
// scroll <button> of the picker into view when opening it
|
|
72704
72805
|
// -> would be overriden by dialog.jsx or popover.jsx
|
|
72705
72806
|
// so ideally openEffect should be either protective or a pubSub to allow multiple callbacks
|
|
@@ -72925,13 +73026,13 @@ const PickerCustom = props => {
|
|
|
72925
73026
|
if (!openController.opened) {
|
|
72926
73027
|
return null;
|
|
72927
73028
|
}
|
|
72928
|
-
const isCancel = escapeEffect === "cancel";
|
|
72929
73029
|
return {
|
|
72930
|
-
name:
|
|
73030
|
+
name: "escape_to_cancel",
|
|
72931
73031
|
intent: "read",
|
|
72932
73032
|
allowed: () => {
|
|
73033
|
+
// Read with escapeEffect by the controller (see above).
|
|
72933
73034
|
requestClose(e, {
|
|
72934
|
-
isCancel
|
|
73035
|
+
isCancel: true
|
|
72935
73036
|
});
|
|
72936
73037
|
e.preventDefault(); // prevent browser from closing the dialog (if any)
|
|
72937
73038
|
}
|
|
@@ -73082,10 +73183,7 @@ const PickerCustom = props => {
|
|
|
73082
73183
|
}
|
|
73083
73184
|
return renderResolver(PickerContentInsidePopup, {
|
|
73084
73185
|
...pickerProps,
|
|
73085
|
-
mode
|
|
73086
|
-
// What a callout's own Escape means (see PickerCalloutPopup): the bubble
|
|
73087
|
-
// answers the key itself when the focus is in it.
|
|
73088
|
-
escapeEffect
|
|
73186
|
+
mode
|
|
73089
73187
|
});
|
|
73090
73188
|
};
|
|
73091
73189
|
|
|
@@ -73200,7 +73298,6 @@ const PickerContentInsidePopup = props => {
|
|
|
73200
73298
|
calloutIcon,
|
|
73201
73299
|
calloutCloseButton,
|
|
73202
73300
|
calloutCloseByScroll,
|
|
73203
|
-
escapeEffect,
|
|
73204
73301
|
...rest
|
|
73205
73302
|
} = props;
|
|
73206
73303
|
const isPopover = mode === "popover";
|
|
@@ -73249,7 +73346,6 @@ const PickerContentInsidePopup = props => {
|
|
|
73249
73346
|
,
|
|
73250
73347
|
backdrop: backdrop,
|
|
73251
73348
|
pressOutside: pressOutside,
|
|
73252
|
-
escapeEffect: escapeEffect,
|
|
73253
73349
|
children: jsx(PopupModeContext.Provider, {
|
|
73254
73350
|
value: mode,
|
|
73255
73351
|
children: children
|
|
@@ -73347,7 +73443,6 @@ const PickerCalloutPopup = ({
|
|
|
73347
73443
|
closeByScroll,
|
|
73348
73444
|
backdrop,
|
|
73349
73445
|
pressOutside,
|
|
73350
|
-
escapeEffect,
|
|
73351
73446
|
onnavi_request_open,
|
|
73352
73447
|
onnavi_request_close,
|
|
73353
73448
|
onnavi_request_confirm,
|
|
@@ -73429,14 +73524,14 @@ const PickerCalloutPopup = ({
|
|
|
73429
73524
|
// Not skipped: the callout moves the focus into the picker when it is
|
|
73430
73525
|
// elsewhere, which is what lets Escape find the callout right away.
|
|
73431
73526
|
skipFocus: false,
|
|
73432
|
-
// An Escape the callout answered itself (the focus in the bubble)
|
|
73433
|
-
//
|
|
73527
|
+
// An Escape the callout answered itself (the focus in the bubble) is
|
|
73528
|
+
// the picker's Escape (see escapeEffect in open_controller.js).
|
|
73434
73529
|
onClose: ({
|
|
73435
73530
|
event,
|
|
73436
73531
|
reason
|
|
73437
73532
|
}) => {
|
|
73438
73533
|
openController.close(event, {
|
|
73439
|
-
isCancel: reason === "escape_key"
|
|
73534
|
+
isCancel: reason === "escape_key"
|
|
73440
73535
|
});
|
|
73441
73536
|
}
|
|
73442
73537
|
});
|