@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.
@@ -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, true);
18109
+ document.addEventListener("keydown", onDocumentKeydown);
18084
18110
  addTeardown(() => {
18085
- document.removeEventListener("keydown", onDocumentKeydown, true);
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 clickEvent.constructor(clickEvent.type, clickEvent),
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 || node === document.body;
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
- const scrollContainer = getScrollContainer(element, {
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 placeScroller = getScrollContainer(groupEl);
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: it focuses the first element
65999
- // marked autofocus, or else the first focusable the group holds — and
66000
- // scrolls the group's own scroller to it. Pointed at what already has the
66001
- // focus, or at the group itself, with any other mark set aside for the
66002
- // call, so nothing moves. A native autofocus is otherwise never used (see
66003
- // the focus skill): this one exists for the length of the call, to steer
66004
- // the browser's own pick, not to transfer anything.
66005
- const autofocusTarget = focusWasInGroup ? focusedBefore : groupEl;
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 = focusWasInGroup ? [...groupEl.querySelectorAll("[autofocus]")].filter(element => element !== autofocusTarget) : [];
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): closing now would hand the focus to what held it
66086
- // at showModal() time, often leaving in this same commit. A park
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 focusMovedSinceShowModal = focusedBeforeClose && focusedBeforeClose !== document.body && focusedBeforeClose !== groupEl && focusedBeforeClose !== focusLeftByShowModal && focusedBeforeClose.isConnected;
66108
- const keepsFocusWhereItIs = focusMovedSinceShowModal && focusedBefore && focusedBefore !== focusedBeforeClose && focusedBefore !== document.body && focusedBefore.isConnected;
66109
- const holdsFocusInside = keepsFocusWhereItIs && focusedBefore.contains(focusedBeforeClose);
66110
- const madeInert = keepsFocusWhereItIs && !holdsFocusInside && !focusedBefore.inert;
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 (an element focusable without a
66131
- // tabindex).
66132
- if (focusMovedSinceShowModal && document.activeElement !== focusedBeforeClose) {
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. A popup that captures the outside is
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
- const listeners = [["pointerdown", onPointerDown], ["mousedown", onMouseDown], ["click", onClick], ["pointerup", onPointerUp], ["pointercancel", onPointerCancel]];
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
- const overflowGoesToViewport = ancestor === document.body && bodyClipsNothing;
66745
- const clipsX = !overflowGoesToViewport && overflowX !== "visible";
66746
- const clipsY = !overflowGoesToViewport && overflowY !== "visible";
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
- // Only a modal <dialog> fires "cancel" on Escape natively (handled via
69890
- // onCancel below); neither a .show()n nor a showPopover()n one does.
69891
- if (isModal || !openController.opened) {
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" (Escape on a modal showModal() dialog —
70073
- // the Escape handling for the other two shows lives in
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: isCancel ? "escape_to_cancel" : "escape_to_close",
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) means
73433
- // what the picker's own Escape means.
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" && escapeEffect === "cancel"
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(ListDeclaredChildren, {
79627
- children: children
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
  })