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