@jsenv/navi 0.29.465 → 0.29.467
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 +195 -93
- package/dist/dev/jsenv_navi.js.map +11 -11
- package/dist/jsenv_navi.js +195 -93
- package/dist/jsenv_navi.js.map +11 -11
- 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
|
{
|
|
@@ -33380,8 +33406,9 @@ const onDocumentClick = (clickEvent) => {
|
|
|
33380
33406
|
// application sees ONE click, on the control it was aimed at, rather than
|
|
33381
33407
|
// two: this one falling through and the one below.
|
|
33382
33408
|
clickEvent.stopPropagation();
|
|
33409
|
+
const ClickEventConstructor = clickEvent.constructor;
|
|
33383
33410
|
pressable.dispatchEvent(
|
|
33384
|
-
new
|
|
33411
|
+
new ClickEventConstructor(clickEvent.type, clickEvent),
|
|
33385
33412
|
);
|
|
33386
33413
|
return;
|
|
33387
33414
|
}
|
|
@@ -34354,7 +34381,7 @@ const readOuterScroll = (element) => {
|
|
|
34354
34381
|
};
|
|
34355
34382
|
|
|
34356
34383
|
const isDocumentScroller = (node) => {
|
|
34357
|
-
return node === document.documentElement
|
|
34384
|
+
return node === document.documentElement;
|
|
34358
34385
|
};
|
|
34359
34386
|
|
|
34360
34387
|
const isPinnedToViewport = (node) => {
|
|
@@ -37580,12 +37607,12 @@ const whilePageRenders = async (page, change, wait = armRouteRenderWait()) => {
|
|
|
37580
37607
|
// (`overflow: clip`) holds nothing and is walked straight through — see
|
|
37581
37608
|
// is_scrollable.js.
|
|
37582
37609
|
const pagesScrollTheDocument = element => {
|
|
37583
|
-
|
|
37610
|
+
// What window.scrollTo moves. A body whose overflow is the viewport's is
|
|
37611
|
+
// walked through to it (see overflowGoesToViewport in @jsenv/dom); a body
|
|
37612
|
+
// the walk stops on scrolls itself, out of the viewport's reach.
|
|
37613
|
+
return getScrollContainer(element, {
|
|
37584
37614
|
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;
|
|
37615
|
+
}) === document.documentElement;
|
|
37589
37616
|
};
|
|
37590
37617
|
|
|
37591
37618
|
// A page of the row: a route, and the params that say which of its tabs when
|
|
@@ -65975,6 +66002,8 @@ const engageGroup = (groupEl, group, {
|
|
|
65975
66002
|
const themeColor = readPopupThemeColor(groupEl, group.themeColor);
|
|
65976
66003
|
const focusedBefore = document.activeElement;
|
|
65977
66004
|
const focusWasInGroup = Boolean(focusedBefore) && focusedBefore !== groupEl && groupEl.contains(focusedBefore);
|
|
66005
|
+
// What the focus goes back to, when nothing holds it any more at the close.
|
|
66006
|
+
const focusOrigin = !focusWasInGroup && focusedBefore && focusedBefore !== document.body && focusedBefore !== groupEl ? focusedBefore : null;
|
|
65978
66007
|
const ringBefore = Boolean(focusedBefore?.matches?.(":focus-visible"));
|
|
65979
66008
|
const {
|
|
65980
66009
|
scrollTop,
|
|
@@ -65982,12 +66011,7 @@ const engageGroup = (groupEl, group, {
|
|
|
65982
66011
|
} = groupEl;
|
|
65983
66012
|
// A gesture on a box fixed in the top layer goes to the viewport: only a
|
|
65984
66013
|
// 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);
|
|
66014
|
+
const placeScrollsWithViewport = getScrollContainer(groupEl) === document.scrollingElement && !findSelfOrAncestorFixedPosition(groupEl);
|
|
65991
66015
|
const placeholder = createPlaceholder(groupStyle, rect, baselineDescent);
|
|
65992
66016
|
groupEl.before(placeholder);
|
|
65993
66017
|
groupEl.style.setProperty("--x-popup-group-padding", `${groupStyle.paddingTop} ${groupStyle.paddingRight} ${groupStyle.paddingBottom} ${groupStyle.paddingLeft}`);
|
|
@@ -65995,25 +66019,49 @@ const engageGroup = (groupEl, group, {
|
|
|
65995
66019
|
if (silent) {
|
|
65996
66020
|
groupEl.setAttribute("navi-transitions-off", "");
|
|
65997
66021
|
}
|
|
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
|
-
|
|
66022
|
+
// showModal() runs the dialog focusing steps — it focuses the first element
|
|
66023
|
+
// marked autofocus, or else the first focusable the group holds, and
|
|
66024
|
+
// scrolls the group's own scroller to it — and records what held the focus,
|
|
66025
|
+
// for close() to give it back there. A native autofocus is otherwise never
|
|
66026
|
+
// used (see the focus skill): these exist for the length of the call, to
|
|
66027
|
+
// steer the browser's own pick, not to transfer anything.
|
|
66028
|
+
//
|
|
66029
|
+
// From inside the group the pick is what holds the focus already, any other
|
|
66030
|
+
// mark set aside, so nothing moves. From outside, a stop is put first in the
|
|
66031
|
+
// group and focused: it is what showModal() picks and records, and it is
|
|
66032
|
+
// gone by the close, which then moves nothing (see cleanup) — what the focus
|
|
66033
|
+
// came from, a sheet around the group, could not be kept from taking it
|
|
66034
|
+
// back. The focus goes on to the group itself, so no peer is focused on the
|
|
66035
|
+
// way.
|
|
66036
|
+
let focusStop = null;
|
|
66037
|
+
if (!focusWasInGroup) {
|
|
66038
|
+
focusStop = document.createElement("span");
|
|
66039
|
+
focusStop.setAttribute("navi-out-of-flow", "");
|
|
66040
|
+
focusStop.setAttribute("aria-hidden", "true");
|
|
66041
|
+
focusStop.tabIndex = -1;
|
|
66042
|
+
focusStop.style.cssText = "position: absolute; width: 0; height: 0; overflow: hidden";
|
|
66043
|
+
groupEl.prepend(focusStop);
|
|
66044
|
+
moveFocusTo(focusStop, {
|
|
66045
|
+
focusVisible: false
|
|
66046
|
+
});
|
|
66047
|
+
}
|
|
66048
|
+
const autofocusTarget = focusWasInGroup ? focusedBefore : focusStop;
|
|
66006
66049
|
const autofocusWasSet = autofocusTarget.hasAttribute("autofocus");
|
|
66007
|
-
const autofocusSetAside =
|
|
66050
|
+
const autofocusSetAside = [...groupEl.querySelectorAll("[autofocus]")].filter(element => element !== autofocusTarget);
|
|
66008
66051
|
for (const element of autofocusSetAside) {
|
|
66009
66052
|
element.removeAttribute("autofocus");
|
|
66010
66053
|
}
|
|
66011
66054
|
if (!autofocusWasSet) {
|
|
66012
66055
|
autofocusTarget.setAttribute("autofocus", "");
|
|
66013
66056
|
}
|
|
66057
|
+
// Chromium takes a <dialog> without a tabindex for no focus target. Lent
|
|
66058
|
+
// for the whole engagement: taken back from the group holding the focus,
|
|
66059
|
+
// it would lose it.
|
|
66060
|
+
const tabindexLent = Boolean(focusStop) && !groupEl.hasAttribute("tabindex");
|
|
66014
66061
|
try {
|
|
66015
66062
|
groupEl.showModal();
|
|
66016
66063
|
} catch (error) {
|
|
66064
|
+
focusStop?.remove();
|
|
66017
66065
|
placeholder.remove();
|
|
66018
66066
|
clearPlacement(groupEl);
|
|
66019
66067
|
groupEl.removeAttribute("navi-transitions-off");
|
|
@@ -66026,6 +66074,15 @@ const engageGroup = (groupEl, group, {
|
|
|
66026
66074
|
element.setAttribute("autofocus", "");
|
|
66027
66075
|
}
|
|
66028
66076
|
}
|
|
66077
|
+
if (focusStop) {
|
|
66078
|
+
if (tabindexLent) {
|
|
66079
|
+
groupEl.tabIndex = -1;
|
|
66080
|
+
}
|
|
66081
|
+
moveFocusTo(groupEl, {
|
|
66082
|
+
focusVisible: false
|
|
66083
|
+
});
|
|
66084
|
+
focusStop.remove();
|
|
66085
|
+
}
|
|
66029
66086
|
// Measured once the group has left the flow, where a parent sized by its
|
|
66030
66087
|
// content no longer counts it. Only a box given its width by what holds it
|
|
66031
66088
|
// goes on following that width (a rotation while engaged); one sized by its
|
|
@@ -66048,9 +66105,6 @@ const engageGroup = (groupEl, group, {
|
|
|
66048
66105
|
focusVisible: ringBefore
|
|
66049
66106
|
});
|
|
66050
66107
|
}
|
|
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
66108
|
if (silent) {
|
|
66055
66109
|
// The wall's starting style is resolved with no transition to play from
|
|
66056
66110
|
// it; giving the transition back afterwards changes no value, so nothing
|
|
@@ -66082,33 +66136,55 @@ const engageGroup = (groupEl, group, {
|
|
|
66082
66136
|
placeholder.remove();
|
|
66083
66137
|
if (leavingTree) {
|
|
66084
66138
|
// 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.
|
|
66139
|
+
// its close cleanup). A park takes the group out of the top layer and
|
|
66140
|
+
// leaves its `open` attribute, which the reveal would show.
|
|
66089
66141
|
queueMicrotask(() => {
|
|
66090
66142
|
if (groupEl.open) {
|
|
66091
66143
|
groupEl.close();
|
|
66092
66144
|
}
|
|
66145
|
+
if (tabindexLent) {
|
|
66146
|
+
groupEl.removeAttribute("tabindex");
|
|
66147
|
+
}
|
|
66093
66148
|
});
|
|
66149
|
+
if (focusOrigin) {
|
|
66150
|
+
// The focus left with the group: back where it was, if that stays.
|
|
66151
|
+
giveFocusBackAfterCommit(focusOrigin, () => {
|
|
66152
|
+
moveFocusTo(focusOrigin, {
|
|
66153
|
+
focusVisible: ringBefore
|
|
66154
|
+
});
|
|
66155
|
+
});
|
|
66156
|
+
}
|
|
66094
66157
|
return;
|
|
66095
66158
|
}
|
|
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
66159
|
const focusedBeforeClose = document.activeElement;
|
|
66160
|
+
if (!focusWasInGroup) {
|
|
66161
|
+
// What showModal() recorded is the stop, gone by now: close() moves
|
|
66162
|
+
// nothing, and the focus stays where a popup of the group, or the
|
|
66163
|
+
// user, put it. Only one still on the group, or lost, goes back to
|
|
66164
|
+
// where it came from.
|
|
66165
|
+
if (groupEl.open) {
|
|
66166
|
+
groupEl.close();
|
|
66167
|
+
}
|
|
66168
|
+
if (focusOrigin && focusOrigin.isConnected && (!focusedBeforeClose || focusedBeforeClose === document.body || focusedBeforeClose === groupEl)) {
|
|
66169
|
+
moveFocusTo(focusOrigin, {
|
|
66170
|
+
focusVisible: ringBefore
|
|
66171
|
+
});
|
|
66172
|
+
}
|
|
66173
|
+
if (tabindexLent) {
|
|
66174
|
+
groupEl.removeAttribute("tabindex");
|
|
66175
|
+
}
|
|
66176
|
+
return;
|
|
66177
|
+
}
|
|
66178
|
+
// Coming from inside the group, the focus was recorded where it was,
|
|
66179
|
+
// and close() gives it back there, undoing where a popup of the group,
|
|
66180
|
+
// or the user, put it since. Unless nothing moved it, that element is
|
|
66181
|
+
// made no focus target for the call: inert — or, holding the focus
|
|
66182
|
+
// inside it, without its tabindex, inert taking the focus with it.
|
|
66106
66183
|
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;
|
|
66184
|
+
const focusMovedInGroup = focusWasInGroup && focusedBeforeClose && focusedBeforeClose !== document.body && focusedBeforeClose !== groupEl && focusedBeforeClose !== focusedBefore && focusedBeforeClose.isConnected && focusedBefore.isConnected;
|
|
66185
|
+
const holdsFocusInside = focusMovedInGroup && focusedBefore.contains(focusedBeforeClose);
|
|
66186
|
+
const madeInert = focusMovedInGroup && !holdsFocusInside && !focusedBefore.inert;
|
|
66187
|
+
const tabindexSetAside = holdsFocusInside ? focusedBefore.getAttribute("tabindex") : null;
|
|
66112
66188
|
if (madeInert) {
|
|
66113
66189
|
focusedBefore.inert = true;
|
|
66114
66190
|
}
|
|
@@ -66126,10 +66202,13 @@ const engageGroup = (groupEl, group, {
|
|
|
66126
66202
|
if (tabindexSetAside !== null) {
|
|
66127
66203
|
focusedBefore.setAttribute("tabindex", tabindexSetAside);
|
|
66128
66204
|
}
|
|
66205
|
+
if (tabindexLent) {
|
|
66206
|
+
groupEl.removeAttribute("tabindex");
|
|
66207
|
+
}
|
|
66129
66208
|
}
|
|
66130
|
-
// Where the engine moved it anyway
|
|
66131
|
-
// tabindex
|
|
66132
|
-
if (
|
|
66209
|
+
// Where the engine moved it anyway: an element focusable without a
|
|
66210
|
+
// tabindex.
|
|
66211
|
+
if (focusMovedInGroup && document.activeElement !== focusedBeforeClose) {
|
|
66133
66212
|
moveFocusTo(focusedBeforeClose, {
|
|
66134
66213
|
focusVisible: ringBeforeClose
|
|
66135
66214
|
});
|
|
@@ -66313,7 +66392,8 @@ const engageGroup = (groupEl, group, {
|
|
|
66313
66392
|
* screen it is synthesized at touchend and hit-tested again, with the group
|
|
66314
66393
|
* still up, so a finger that scrolled the group (pointercancel, no mousedown)
|
|
66315
66394
|
* closes nothing. A pointerdown cancelled downstream (a drag source) brings no
|
|
66316
|
-
* mousedown: its click decides instead
|
|
66395
|
+
* mousedown: its click decides instead, or the start of the drag it turns
|
|
66396
|
+
* into, which brings no click. A popup that captures the outside is
|
|
66317
66397
|
* answered at the pointerdown itself: nothing of that press may reach a peer.
|
|
66318
66398
|
*/
|
|
66319
66399
|
const armPressArbiter = (groupEl, group, engagement) => {
|
|
@@ -66482,7 +66562,28 @@ const armPressArbiter = (groupEl, group, engagement) => {
|
|
|
66482
66562
|
engagement.checkAfterTask();
|
|
66483
66563
|
}
|
|
66484
66564
|
};
|
|
66485
|
-
|
|
66565
|
+
// A press that becomes a drag brings no click: decided at the click, it
|
|
66566
|
+
// would leave the open popup over a card being carried away, its own
|
|
66567
|
+
// trigger maybe. A drag starting outside the open popup closes it, as a
|
|
66568
|
+
// press there does. Nothing is left to hand over to then, so the group goes
|
|
66569
|
+
// back into the page at once — where the drag can land: what is outside the
|
|
66570
|
+
// group is inert while it is engaged.
|
|
66571
|
+
const onDragStart = dragStartEvent => {
|
|
66572
|
+
const openMember = findMemberPressedOutside(dragStartEvent);
|
|
66573
|
+
if (openMember) {
|
|
66574
|
+
const pressOutside = readPressOutside(openMember);
|
|
66575
|
+
if (pressOutside !== "capture") {
|
|
66576
|
+
openMember.openController.requestClose(dragStartEvent, {
|
|
66577
|
+
isCancel: pressOutside === "cancel"
|
|
66578
|
+
});
|
|
66579
|
+
}
|
|
66580
|
+
}
|
|
66581
|
+
if (engagement.pressClosed) {
|
|
66582
|
+
engagement.pressClosed = false;
|
|
66583
|
+
group.disengageIfNothingOpen();
|
|
66584
|
+
}
|
|
66585
|
+
};
|
|
66586
|
+
const listeners = [["pointerdown", onPointerDown], ["mousedown", onMouseDown], ["click", onClick], ["pointerup", onPointerUp], ["pointercancel", onPointerCancel], ["navi_drag_start", onDragStart]];
|
|
66486
66587
|
for (const [type, listener] of listeners) {
|
|
66487
66588
|
document.addEventListener(type, listener, {
|
|
66488
66589
|
capture: true
|
|
@@ -66718,8 +66819,6 @@ const readAncestorClip = (element, elementStyle) => {
|
|
|
66718
66819
|
if (elementStyle.position === "fixed") {
|
|
66719
66820
|
return null;
|
|
66720
66821
|
}
|
|
66721
|
-
// Read on body, an overflow that is the viewport's clips nothing.
|
|
66722
|
-
const bodyClipsNothing = bodyOverflowIsTheViewports();
|
|
66723
66822
|
let left = -Infinity;
|
|
66724
66823
|
let top = -Infinity;
|
|
66725
66824
|
let right = Infinity;
|
|
@@ -66741,9 +66840,10 @@ const readAncestorClip = (element, elementStyle) => {
|
|
|
66741
66840
|
overflowY,
|
|
66742
66841
|
position
|
|
66743
66842
|
} = ancestorStyle;
|
|
66744
|
-
|
|
66745
|
-
const
|
|
66746
|
-
const
|
|
66843
|
+
// Read on a body whose overflow is the viewport's, it clips nothing.
|
|
66844
|
+
const clipsNothing = overflowGoesToViewport(ancestor);
|
|
66845
|
+
const clipsX = !clipsNothing && overflowX !== "visible";
|
|
66846
|
+
const clipsY = !clipsNothing && overflowY !== "visible";
|
|
66747
66847
|
if (clipsX || clipsY) {
|
|
66748
66848
|
clipped = true;
|
|
66749
66849
|
const ancestorRect = ancestor.getBoundingClientRect();
|
|
@@ -66783,12 +66883,6 @@ const readAncestorClip = (element, elementStyle) => {
|
|
|
66783
66883
|
bottom
|
|
66784
66884
|
} : null;
|
|
66785
66885
|
};
|
|
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
66886
|
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
66887
|
// Prefixed alone before Safari 18.
|
|
66794
66888
|
(style.backdropFilter ?? style.webkitBackdropFilter ?? "none") !== "none" || /\b(?:layout|paint|strict|content)\b/.test(style.contain) ||
|
|
@@ -69886,9 +69980,13 @@ const useDialogProps = props => {
|
|
|
69886
69980
|
};
|
|
69887
69981
|
const onKeyDownShortcuts = createOnKeyDownForShortcuts({
|
|
69888
69982
|
escape: e => {
|
|
69889
|
-
//
|
|
69890
|
-
//
|
|
69891
|
-
|
|
69983
|
+
// Answered at the keydown, modal or not: the innermost popup takes the
|
|
69984
|
+
// key and spends it (requestClose prevents it), so one Escape closes
|
|
69985
|
+
// one popup. A modal's native "cancel" comes only once the keydown has
|
|
69986
|
+
// been dispatched, and the key would reach a popup around this one
|
|
69987
|
+
// first — a picker whose drawing holds this dialog. onCancel below is
|
|
69988
|
+
// left the close requests that are no keydown: the back gesture.
|
|
69989
|
+
if (!openController.opened) {
|
|
69892
69990
|
return null;
|
|
69893
69991
|
}
|
|
69894
69992
|
return {
|
|
@@ -70069,9 +70167,9 @@ const useDialogProps = props => {
|
|
|
70069
70167
|
onKeyDownShortcuts(e);
|
|
70070
70168
|
},
|
|
70071
70169
|
"onCancel": e => {
|
|
70072
|
-
// The dialog's own "cancel"
|
|
70073
|
-
//
|
|
70074
|
-
// onKeyDownShortcuts above) fires on the dialog element itself. But a child
|
|
70170
|
+
// The dialog's own "cancel" — a close request that is no keydown (the
|
|
70171
|
+
// back gesture), an Escape being answered at its keydown (see
|
|
70172
|
+
// onKeyDownShortcuts above) — fires on the dialog element itself. But a child
|
|
70075
70173
|
// <input type="file"> also fires a BUBBLING "cancel" when the user
|
|
70076
70174
|
// dismisses the file chooser (per the HTML spec), and it lands here
|
|
70077
70175
|
// too; only the dialog's own cancel means "close".
|
|
@@ -72700,6 +72798,10 @@ const PickerCustom = props => {
|
|
|
72700
72798
|
}
|
|
72701
72799
|
};
|
|
72702
72800
|
});
|
|
72801
|
+
// Whoever hears the Escape — this picker's trigger, its popup, the group
|
|
72802
|
+
// it stands in, its bubble — asks for a cancel; the controller reads it
|
|
72803
|
+
// with this (see escapeEffect in open_controller.js).
|
|
72804
|
+
openController.escapeEffect = escapeEffect;
|
|
72703
72805
|
// scroll <button> of the picker into view when opening it
|
|
72704
72806
|
// -> would be overriden by dialog.jsx or popover.jsx
|
|
72705
72807
|
// so ideally openEffect should be either protective or a pubSub to allow multiple callbacks
|
|
@@ -72925,13 +73027,13 @@ const PickerCustom = props => {
|
|
|
72925
73027
|
if (!openController.opened) {
|
|
72926
73028
|
return null;
|
|
72927
73029
|
}
|
|
72928
|
-
const isCancel = escapeEffect === "cancel";
|
|
72929
73030
|
return {
|
|
72930
|
-
name:
|
|
73031
|
+
name: "escape_to_cancel",
|
|
72931
73032
|
intent: "read",
|
|
72932
73033
|
allowed: () => {
|
|
73034
|
+
// Read with escapeEffect by the controller (see above).
|
|
72933
73035
|
requestClose(e, {
|
|
72934
|
-
isCancel
|
|
73036
|
+
isCancel: true
|
|
72935
73037
|
});
|
|
72936
73038
|
e.preventDefault(); // prevent browser from closing the dialog (if any)
|
|
72937
73039
|
}
|
|
@@ -73082,10 +73184,7 @@ const PickerCustom = props => {
|
|
|
73082
73184
|
}
|
|
73083
73185
|
return renderResolver(PickerContentInsidePopup, {
|
|
73084
73186
|
...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
|
|
73187
|
+
mode
|
|
73089
73188
|
});
|
|
73090
73189
|
};
|
|
73091
73190
|
|
|
@@ -73200,7 +73299,6 @@ const PickerContentInsidePopup = props => {
|
|
|
73200
73299
|
calloutIcon,
|
|
73201
73300
|
calloutCloseButton,
|
|
73202
73301
|
calloutCloseByScroll,
|
|
73203
|
-
escapeEffect,
|
|
73204
73302
|
...rest
|
|
73205
73303
|
} = props;
|
|
73206
73304
|
const isPopover = mode === "popover";
|
|
@@ -73249,7 +73347,6 @@ const PickerContentInsidePopup = props => {
|
|
|
73249
73347
|
,
|
|
73250
73348
|
backdrop: backdrop,
|
|
73251
73349
|
pressOutside: pressOutside,
|
|
73252
|
-
escapeEffect: escapeEffect,
|
|
73253
73350
|
children: jsx(PopupModeContext.Provider, {
|
|
73254
73351
|
value: mode,
|
|
73255
73352
|
children: children
|
|
@@ -73347,7 +73444,6 @@ const PickerCalloutPopup = ({
|
|
|
73347
73444
|
closeByScroll,
|
|
73348
73445
|
backdrop,
|
|
73349
73446
|
pressOutside,
|
|
73350
|
-
escapeEffect,
|
|
73351
73447
|
onnavi_request_open,
|
|
73352
73448
|
onnavi_request_close,
|
|
73353
73449
|
onnavi_request_confirm,
|
|
@@ -73429,14 +73525,14 @@ const PickerCalloutPopup = ({
|
|
|
73429
73525
|
// Not skipped: the callout moves the focus into the picker when it is
|
|
73430
73526
|
// elsewhere, which is what lets Escape find the callout right away.
|
|
73431
73527
|
skipFocus: false,
|
|
73432
|
-
// An Escape the callout answered itself (the focus in the bubble)
|
|
73433
|
-
//
|
|
73528
|
+
// An Escape the callout answered itself (the focus in the bubble) is
|
|
73529
|
+
// the picker's Escape (see escapeEffect in open_controller.js).
|
|
73434
73530
|
onClose: ({
|
|
73435
73531
|
event,
|
|
73436
73532
|
reason
|
|
73437
73533
|
}) => {
|
|
73438
73534
|
openController.close(event, {
|
|
73439
|
-
isCancel: reason === "escape_key"
|
|
73535
|
+
isCancel: reason === "escape_key"
|
|
73440
73536
|
});
|
|
73441
73537
|
}
|
|
73442
73538
|
});
|
|
@@ -79623,8 +79719,14 @@ const UnorderedList = ({
|
|
|
79623
79719
|
value: null,
|
|
79624
79720
|
children: jsx(ListItemColumnsContext.Provider, {
|
|
79625
79721
|
value: columns ? null : itemColumns || null,
|
|
79626
|
-
children: jsx(
|
|
79627
|
-
|
|
79722
|
+
children: jsx(ListSlotContext.Provider, {
|
|
79723
|
+
value: null,
|
|
79724
|
+
children: jsx(ListGroupContext.Provider, {
|
|
79725
|
+
value: undefined,
|
|
79726
|
+
children: jsx(ListDeclaredChildren, {
|
|
79727
|
+
children: children
|
|
79728
|
+
})
|
|
79729
|
+
})
|
|
79628
79730
|
})
|
|
79629
79731
|
})
|
|
79630
79732
|
})
|