@jsenv/navi 0.29.324 → 0.29.325

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.
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, coarsePointerSignal, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
6
6
  export { disableVirtualKeyboardOverlay } from "./jsenv_navi_side_effects.js";
7
- import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, keepTouchRefusable, isPressDrivenClick, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, isTouchDrivenEvent, scrollIntoViewScoped, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
7
+ import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, keepTouchRefusable, isPressDrivenClick, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, createInternalCustomEvent, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, isTouchDrivenEvent, scrollIntoViewScoped, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
8
8
  export { chainEvent, clickIsSuppressed, contrastColor, createDragGestureController, dragAfterIntent, findEvent, markDragSource, startDragTo } from "@jsenv/dom";
9
9
  import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
10
10
  import { isValidElement, createContext, render, h, toChildArray, options, cloneElement, createElement, Fragment as Fragment$1 } from "preact";
@@ -12616,6 +12616,12 @@ const useExecuteAction = (
12616
12616
  // whose command sent — is stored as the callout display target, so the
12617
12617
  // message appears on whoever asked rather than on the whole control.
12618
12618
  const element = elementRef.current;
12619
+ if (!element) {
12620
+ // The control left the page while its run was out (its own answer can
12621
+ // unmount it, see the outcome events below): there is no box to hang
12622
+ // the refusal on. onActionError is still told, by the run's side effect.
12623
+ return;
12624
+ }
12619
12625
  let target = requester;
12620
12626
  // A requester that is no longer on the page is not a place to show
12621
12627
  // anything: the clear cross leaves with the value it optimistically
@@ -12718,12 +12724,20 @@ const useExecuteAction = (
12718
12724
  // Either three callbacks, one per outcome, or a single one for "however
12719
12725
  // this ends" — it receives `{ aborted, reason }`, `{ error }` or
12720
12726
  // `{ data }`, so a caller that only needs to know the action settled
12721
- // (and whether it worked) does not have to register three.
12727
+ // (and whether it worked) does not have to register three. Each also
12728
+ // receives the outcome event (navi_action_abort/error/end) as its
12729
+ // second argument.
12722
12730
  const addSideEffect = (sideEffect) => {
12723
12731
  if (typeof sideEffect === "function") {
12724
- addAbortCallback((reason) => sideEffect({ aborted: true, reason }));
12725
- addErrorCallback((error) => sideEffect({ error }));
12726
- addCompleteCallback((data) => sideEffect({ data }));
12732
+ addAbortCallback((reason, outcomeEvent) =>
12733
+ sideEffect({ aborted: true, reason }, outcomeEvent),
12734
+ );
12735
+ addErrorCallback((error, outcomeEvent) =>
12736
+ sideEffect({ error }, outcomeEvent),
12737
+ );
12738
+ addCompleteCallback((data, outcomeEvent) =>
12739
+ sideEffect({ data }, outcomeEvent),
12740
+ );
12727
12741
  return;
12728
12742
  }
12729
12743
  const { abort, error, complete } = sideEffect;
@@ -12731,56 +12745,6 @@ const useExecuteAction = (
12731
12745
  addErrorCallback(error);
12732
12746
  addCompleteCallback(complete);
12733
12747
  };
12734
- addSideEffect({
12735
- abort: (reason) => {
12736
- const element = elementRef.current;
12737
- if (
12738
- // at this stage the action side effect might have removed the <element> from the DOM
12739
- // (in theory no because action side effect are batched to happen after)
12740
- // but other side effects might do this
12741
- element
12742
- ) {
12743
- dispatchInternalCustomEvent(element, "navi_action_abort", {
12744
- ...sharedActionEventDetail,
12745
- reason,
12746
- });
12747
- }
12748
- },
12749
- error: (error) => {
12750
- if (errorEffect === "show_validation_message") {
12751
- addErrorMessage(error, { requester });
12752
- } else if (errorEffect === "throw") {
12753
- setError(error);
12754
- }
12755
-
12756
- const element = elementRef.current;
12757
- if (
12758
- // at this stage the action side effect might have removed the <element> from the DOM
12759
- // (in theory no because action side effect are batched to happen after)
12760
- // but other side effects might do this
12761
- element
12762
- ) {
12763
- dispatchInternalCustomEvent(element, "navi_action_error", {
12764
- ...sharedActionEventDetail,
12765
- error,
12766
- });
12767
- }
12768
- },
12769
- complete: (data) => {
12770
- const element = elementRef.current;
12771
- if (
12772
- // at this stage the action side effect might have removed the <element> from the DOM
12773
- // (in theory no because action side effect are batched to happen after)
12774
- // but other side effects might do this
12775
- element
12776
- ) {
12777
- dispatchInternalCustomEvent(element, "navi_action_end", {
12778
- ...sharedActionEventDetail,
12779
- data,
12780
- });
12781
- }
12782
- },
12783
- });
12784
12748
 
12785
12749
  const actionStartEventDetail = {
12786
12750
  ...sharedActionEventDetail,
@@ -12792,13 +12756,50 @@ const useExecuteAction = (
12792
12756
  actionStartEventDetail,
12793
12757
  );
12794
12758
 
12759
+ // The outcome is told twice: to the element, when there still is one,
12760
+ // and to the side effects registered at navi_action_start, always. A
12761
+ // run can outlive its control — the run's own answer, written to a
12762
+ // store the parent reads to decide what it draws, unmounts the control
12763
+ // one microtask before the run settles — and the element is then no
12764
+ // place to dispatch to: Preact drops a detached element's listeners.
12765
+ // The side effects are the run's, not the element's, so the event
12766
+ // reaches them either way.
12767
+ const tellOutcome = (outcomeEventName, outcomeEventDetail, trigger) => {
12768
+ const outcomeEvent = createInternalCustomEvent(outcomeEventName, {
12769
+ ...sharedActionEventDetail,
12770
+ ...outcomeEventDetail,
12771
+ });
12772
+ const element = elementRef.current;
12773
+ if (element) {
12774
+ element.dispatchEvent(outcomeEvent);
12775
+ }
12776
+ trigger(outcomeEvent);
12777
+ };
12778
+
12795
12779
  const runAction = () => {
12796
12780
  return action[method]({
12797
12781
  event: actionEvent,
12798
12782
  reason: `"${event.type}" event on ${getElementSignature(event.target)}`,
12799
- onAbort: triggerAbort,
12800
- onError: triggerError,
12801
- onComplete: triggerComplete,
12783
+ onAbort: (reason) => {
12784
+ tellOutcome("navi_action_abort", { reason }, (outcomeEvent) => {
12785
+ triggerAbort(reason, outcomeEvent);
12786
+ });
12787
+ },
12788
+ onError: (error) => {
12789
+ if (errorEffect === "show_validation_message") {
12790
+ addErrorMessage(error, { requester });
12791
+ } else if (errorEffect === "throw") {
12792
+ setError(error);
12793
+ }
12794
+ tellOutcome("navi_action_error", { error }, (outcomeEvent) => {
12795
+ triggerError(error, outcomeEvent);
12796
+ });
12797
+ },
12798
+ onComplete: (data) => {
12799
+ tellOutcome("navi_action_end", { data }, (outcomeEvent) => {
12800
+ triggerComplete(data, outcomeEvent);
12801
+ });
12802
+ },
12802
12803
  });
12803
12804
  };
12804
12805
 
@@ -39868,10 +39869,24 @@ const useInteractiveProps = (props, {
39868
39869
  // aborted run, whose promise is awaited to completion (see
39869
39870
  // performRun in actions.js) — which is exactly what "the server is
39870
39871
  // done with it" means, and therefore when the queued request may go.
39871
- e.detail.addSideEffect(outcome => {
39872
+ e.detail.addSideEffect((outcome, outcomeEvent) => {
39872
39873
  uiStateController.actionInFlight = false;
39873
39874
  uiStateController.runningActionSignal.value = null;
39874
39875
  uiStateController.parallelGuard?.release(uiStateController);
39876
+ // The outcome callbacks are told from here, the run's side effect,
39877
+ // and not from the onnavi_action_* handlers below: the run can
39878
+ // outlive this control (its own answer, written to a store the
39879
+ // parent reads, unmounts it one microtask before it settles), and
39880
+ // whoever wears the run — a parent's `loading` following these,
39881
+ // see 12_picker_card_demo.html — must hear it end all the same.
39882
+ // The handlers below keep what needs the element.
39883
+ if (outcome.aborted) {
39884
+ onActionAborted?.(outcomeEvent);
39885
+ } else if ("error" in outcome) {
39886
+ onActionError?.(outcome.error, outcomeEvent);
39887
+ } else {
39888
+ onActionEnd?.(outcome.data, outcomeEvent);
39889
+ }
39875
39890
  const queuedEvent = uiStateController.queuedActionAllowedEvent;
39876
39891
  if (!queuedEvent) {
39877
39892
  syncOptimisticAttribute(false);
@@ -39911,7 +39926,6 @@ const useInteractiveProps = (props, {
39911
39926
  if (resetOnAbort && !superseded) {
39912
39927
  dispatchRequestResetUIState(e.currentTarget, e);
39913
39928
  }
39914
- onActionAborted?.(e);
39915
39929
  },
39916
39930
  onnavi_action_error: e => {
39917
39931
  const {
@@ -39920,7 +39934,6 @@ const useInteractiveProps = (props, {
39920
39934
  if (resetOnError) {
39921
39935
  dispatchRequestResetUIState(e.currentTarget, e);
39922
39936
  }
39923
- onActionError?.(error, e);
39924
39937
  uiStateController.onActionError(e);
39925
39938
  },
39926
39939
  onnavi_action_end: e => {
@@ -39928,7 +39941,6 @@ const useInteractiveProps = (props, {
39928
39941
  data
39929
39942
  } = e.detail;
39930
39943
  debugAction(e, `action end with data: ${JSON.stringify(data)}`);
39931
- onActionEnd?.(data, e);
39932
39944
  controlRootProps.onnavi_action_end?.(e);
39933
39945
  uiStateController.onActionEnd(e);
39934
39946
 
@@ -59316,34 +59328,44 @@ const css$E = /* css */`
59316
59328
  * `showModal()`'d (native focus trap, `Escape`-to-cancel, hardware
59317
59329
  * back-button dismissal, rest-of-document made inert), without one it is
59318
59330
  * shown through the Popover API and the page behind stays live.
59319
- * @param {boolean} [props.dockedOnSmallTouchScreen] - Turns the dialog into a
59320
- * bottom sheet (docked flush to the bottom edge, full width) on a small touch
59321
- * screen, and leaves it alone otherwise. For a dialog meant to be interacted
59322
- * with rather than merely read: on a phone the keyboard owns the bottom of
59323
- * the screen and a centered box ends up both cramped and out of thumb reach,
59324
- * while under a mouse the centered box is already the right shape. Both
59325
- * halves of the name matter (`smallTouchScreenSignal`): touch alone would
59326
- * dock a big touch screen — a tablet, a kiosk panel — a whole screen away
59327
- * from where the finger just tapped, and size alone would dock a narrow
59328
- * desktop window, which is still a mouse. It supplies defaults for
59329
- * `positionArea`, `marginWithContainer`, `expandX` and `scrollCapture`, so
59330
- * any of them can still be pinned explicitly — including `expandX={false}`,
59331
+ * @param {boolean|"top"|"bottom"} [props.dockedOnSmallTouchScreen] - Turns
59332
+ * the dialog into a sheet (docked flush to one edge, full width) on a small
59333
+ * touch screen, and leaves it alone otherwise: under a mouse the centered box
59334
+ * is already the right shape, while on a phone a centered box ends up cramped
59335
+ * between the keyboard and the top of the screen. The value says which edge
59336
+ * the sheet rests on. `true` (or `"top"`) rests it on the top edge, out of
59337
+ * the virtual keyboard's way: a field tapped in the sheet raises the keyboard
59338
+ * below it, and the question and the field stay where the finger found them.
59339
+ * `"bottom"` rests it on the bottom edge, where the thumbs are — for a sheet
59340
+ * that is read and tapped (a list, a couple of buttons, a confirmation) and
59341
+ * holds nothing that raises a keyboard; one that does is reflowed into the
59342
+ * strip left above the keyboard at every keystroke. The edge is said here
59343
+ * rather than through `positionArea`, which places both shapes:
59344
+ * `positionArea="bottom"` would also pin the centered box to the bottom of a
59345
+ * desktop window. Both halves of the name matter (`smallTouchScreenSignal`):
59346
+ * touch alone would dock a big touch screen — a tablet, a kiosk panel — a
59347
+ * whole screen away from where the finger just tapped, and size alone would
59348
+ * dock a narrow desktop window, which is still a mouse. It supplies defaults
59349
+ * for `positionArea`, `marginWithContainer`, `expandX` and `scrollCapture`,
59350
+ * so any of them can still be pinned explicitly — including `expandX={false}`,
59331
59351
  * which opts the docked dialog out of the full-width stretch and leaves it a
59332
- * floating box at the bottom. It also withdraws `maxWidth` while docked: a
59352
+ * floating box against its edge. It also withdraws `maxWidth` while docked: a
59333
59353
  * sheet is container-wide by definition, and a `maxWidth` is an answer about
59334
59354
  * the *centered* shape, so the two can be stated together (`maxWidth="16rem"
59335
59355
  * dockedOnSmallTouchScreen`) and each applies where it means something.
59336
59356
  * `minWidth` needs no such rule — its floor is below the full width — and
59337
59357
  * `maxHeight`/`minHeight` keep applying, a sheet being content-tall.
59338
- * Ignored entirely when `expandY`
59339
- * (or `expand`) is set: a dialog already filling the height is on the bottom
59340
- * edge docking would bring it to, so docking could only take away the shape
59341
- * the caller asked for. Re-resolves live as the pointer
59342
- * type or the window size changes. A sheet resting on the bottom edge is also
59343
- * pushed back down to close it, held by its header (a `Box` with the `header`
59344
- * prop) and by anything else carrying `data-swipe-grip`. The rest of the sheet
59345
- * is left to what it holds, so a board something is dragged across keeps its
59346
- * own gestures. See `swipe_to_close.js`.
59358
+ * Ignored entirely when `expandY` (or `expand`) is set: a dialog already
59359
+ * filling the height touches both edges, so docking could only take away the
59360
+ * shape the caller asked for. Re-resolves live as the pointer type or the
59361
+ * window size changes. A sheet is also pushed back through the edge it rests
59362
+ * on to close it (down for a bottom sheet, up for a top one), held by its
59363
+ * header (a `Box` with the `header` prop) and by anything else carrying
59364
+ * `data-swipe-grip`. The rest of the sheet is left to what it holds, so a
59365
+ * board something is dragged across keeps its own gestures. See
59366
+ * `swipe_to_close.js`. In dev, a `"bottom"` sheet found holding a field that
59367
+ * raises the keyboard is warned about at open, on any screen — the mistake
59368
+ * itself only shows on a phone.
59347
59369
  * @param {string} [props.positionArea="center"] - Where to dock the dialog
59348
59370
  * within its container (the viewport for `layer="top"`, the positioned
59349
59371
  * ancestor for `layer="local"`) — Dialog is never anchored to a real
@@ -59709,28 +59731,28 @@ const DialogLocal = props => {
59709
59731
  * own to render: a modal's is the native `::backdrop`, and `backdrop={false}`
59710
59732
  * has none at all.
59711
59733
  */
59712
- // What a dialog turns into on a small touch screen. "bottom" is not a taste:
59713
- // it puts the dialog in the zone a phone is actually operated from — where the
59714
- // thumbs rest and where the virtual keyboard comes up — instead of the middle
59715
- // of the screen, which is the farthest point from both.
59734
+ // What a dialog turns into on a small touch screen: a sheet flush against one
59735
+ // edge of the screen. Which edge is the caller's word (the prop's value, see
59736
+ // its JSDoc), the top one unless told "bottom": the cheap mistake must be the
59737
+ // default one — a sheet docked top by mistake is a little farther from the
59738
+ // thumb, a sheet docked bottom by mistake fights the virtual keyboard, which
59739
+ // rises from that very edge, on every keystroke.
59716
59740
  // Only defaults: an explicitly passed prop still wins, so the docked shape can
59717
59741
  // be adjusted one axis at a time instead of being all-or-nothing.
59718
59742
  const DOCKED = {
59719
- positionArea: "bottom",
59720
59743
  marginWithContainer: 0,
59721
59744
  expandX: true,
59722
- // A sheet resting on the bottom edge is dragged with a thumb, and a drag that
59723
- // runs past its own edge must not land on the page behind it: the same
59724
- // reasoning as "bottom" above, applied to the gesture instead of the shape.
59745
+ // A sheet is dragged with a thumb, and a drag that runs past its own edge
59746
+ // must not land on the page behind it.
59725
59747
  scrollCapture: true
59726
59748
  };
59727
59749
 
59728
- // Where a bottom sheet is held to push it back down: the strip a Box declares
59729
- // with `header`, plus anything the application marked as one more. Everything
59730
- // else in the sheet is content the finger came to operate — a board a piece is
59731
- // dragged across, a list, a map — and a press there belongs to it. A sheet with
59732
- // no header and nothing marked is not pushed down at all; it is closed by its
59733
- // own controls, by the backdrop and by Escape.
59750
+ // Where a sheet is held to push it back through its edge: the strip a Box
59751
+ // declares with `header`, plus anything the application marked as one more.
59752
+ // Everything else in the sheet is content the finger came to operate — a board
59753
+ // a piece is dragged across, a list, a map — and a press there belongs to it. A
59754
+ // sheet with no header and nothing marked is not pushed back at all; it is
59755
+ // closed by its own controls, by the backdrop and by Escape.
59734
59756
  const DOCKED_SWIPE_GRIP = "[data-header],[data-swipe-grip]";
59735
59757
  const useDialogProps = props => {
59736
59758
  const backdropProps = {};
@@ -59836,12 +59858,12 @@ const useDialogProps = props => {
59836
59858
  // Only a small touch screen changes anything: on a mouse — and on a touch
59837
59859
  // screen too big to reach the bottom edge of — a dialog already wants to be
59838
59860
  // the centered box it is by default, so there is nothing to resolve.
59839
- // expandY cancels the docking outright: docking exists to bring the dialog
59840
- // down to the edge the thumb is on, and a dialog already filling the height
59841
- // is on that edge — all docking could still do is take away the shape the
59842
- // caller asked for (and arm a swipe-down on something that never rose).
59861
+ // expandY cancels the docking outright: a dialog already filling the height
59862
+ // touches both edges — all docking could still do is take away the shape
59863
+ // the caller asked for (and arm a swipe on something that never rose).
59843
59864
  const isDocked = dockedOnSmallTouchScreen && smallTouchScreenSignal.value && !expandY;
59844
- const positionArea = positionAreaProp ?? (isDocked ? DOCKED.positionArea : "center");
59865
+ const dockedEdge = dockedOnSmallTouchScreen === "bottom" ? "bottom" : "top";
59866
+ const positionArea = positionAreaProp ?? (isDocked ? dockedEdge : "center");
59845
59867
  const marginWithContainer = marginWithContainerProp ?? (isDocked ? DOCKED.marginWithContainer :
59846
59868
  // A share of whatever holds the dialog: the app's own screen for a
59847
59869
  // top-layer one — where appw is exactly "3% of the container", the
@@ -59903,12 +59925,12 @@ const useDialogProps = props => {
59903
59925
  y: "center",
59904
59926
  x: "center"
59905
59927
  };
59906
- // Pushing the sheet back down closes it — a bottom sheet is reached with a
59907
- // thumb, and the thumb is already on the edge it would push. Only a sheet
59908
- // actually resting on the bottom edge: anywhere else the gesture would send
59909
- // the dialog somewhere it never came from.
59910
- const swipeToCloseDown = isDocked && parsedPositionArea.y === "bottom";
59911
- const onSwipePointerDown = swipeToCloseDown ? createSwipeToClose("bottom", {
59928
+ // Pushing the sheet back through the edge it rests on closes it — the way
59929
+ // it came in, and the way the thumb on that edge already pushes. Only a sheet
59930
+ // actually resting on an edge: anywhere else the gesture would send the
59931
+ // dialog somewhere it never came from.
59932
+ const swipeToCloseSide = isDocked && (parsedPositionArea.y === "top" || parsedPositionArea.y === "bottom") ? parsedPositionArea.y : null;
59933
+ const onSwipePointerDown = swipeToCloseSide ? createSwipeToClose(swipeToCloseSide, {
59912
59934
  grip: DOCKED_SWIPE_GRIP
59913
59935
  }) : null;
59914
59936
  // A corner sitting exactly on the container's own corner must not be
@@ -60540,12 +60562,12 @@ const useDialogProps = props => {
60540
60562
  "data-flush-right": flushEdges.right ? "" : undefined,
60541
60563
  "data-flush-bottom": flushEdges.bottom ? "" : undefined,
60542
60564
  "data-flush-left": flushEdges.left ? "" : undefined,
60543
- "data-swipe-to-close": swipeToCloseDown ? "" : undefined,
60565
+ "data-swipe-to-close": swipeToCloseSide ?? undefined,
60544
60566
  // The axis the sheet travels on when it is pushed back, said to the shared
60545
60567
  // gesture layer: it is what a box travelling inside the sheet reads to know
60546
60568
  // this axis is already walked (see @jsenv/dom's drag_to_travel).
60547
- "data-drag-travel": swipeToCloseDown ? SWIPE_AXIS_BY_SIDE.bottom : undefined,
60548
- "data-travel-by-drag": swipeToCloseDown ? SWIPE_AXIS_BY_SIDE.bottom : undefined,
60569
+ "data-drag-travel": swipeToCloseSide ? SWIPE_AXIS_BY_SIDE[swipeToCloseSide] : undefined,
60570
+ "data-travel-by-drag": swipeToCloseSide ? SWIPE_AXIS_BY_SIDE[swipeToCloseSide] : undefined,
60549
60571
  "onPointerDown": e => {
60550
60572
  rest.onPointerDown?.(e);
60551
60573
  onSwipePointerDown?.(e);
@@ -62339,8 +62361,8 @@ const css$C = /* css */`@layer navi {
62339
62361
  * @param {string} [props.positionAreaFixed] - **Popover-only**, same guard.
62340
62362
  * @param {string} [props.positionAreaWhenAnchorIsInvalid] - **Popover-only**,
62341
62363
  * same guard.
62342
- * @param {boolean} [props.dockedOnSmallTouchScreen] - **Dialog-only** (a
62343
- * popover is never a bottom sheet), destructured out for the same reason
62364
+ * @param {boolean|"top"|"bottom"} [props.dockedOnSmallTouchScreen] -
62365
+ * **Dialog-only** (a popover is never a sheet), destructured out for the same reason
62344
62366
  * turned around: it must not land on the popover element as a stray DOM
62345
62367
  * attribute when the screen-size resolution picks `mode="popover"`. Where
62346
62368
  * that resolution lands is what decides whether it applies at all, so a
@@ -62368,7 +62390,7 @@ const css$C = /* css */`@layer navi {
62368
62390
  * honoured in either mode — a wall-less `Dialog` is shown through the
62369
62391
  * Popover API rather than `showModal()` — so which mode the screen-size
62370
62392
  * resolution picks says nothing about whether the page behind stays live.
62371
- * It is also what makes a sheet docked to the bottom of a phone's screen
62393
+ * It is also what makes a sheet docked to an edge of a phone's screen
62372
62394
  * (`dockedOnSmallTouchScreen`) non-modal.
62373
62395
  * @param {"auto"|"discrete"|"invisible"} [props.backdropVariant] - Forwarded
62374
62396
  * as-is to whichever component renders (both understand it identically):
@@ -74973,7 +74995,7 @@ const css$n = /* css */`.navi_split_button {
74973
74995
  * dialogExpand?: boolean,
74974
74996
  * dialogExpandX?: boolean,
74975
74997
  * dialogExpandY?: boolean,
74976
- * dockedOnSmallTouchScreen?: boolean,
74998
+ * dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
74977
74999
  * marginWithContainer?: number | string,
74978
75000
  * backdrop?: boolean,
74979
75001
  * backdropVariant?: "auto" | "discrete" | "invisible",
@@ -84401,6 +84423,9 @@ const createSlot = (SlotRenderer = Box) => {
84401
84423
  slotPropsSignal.value = props;
84402
84424
  useLayoutEffect(() => {
84403
84425
  return () => {
84426
+ // Within one diff preact mounts the newcomer before unmounting the
84427
+ // leaver, so the leaver may no longer be the filler by the time it
84428
+ // is cleaned up.
84404
84429
  if (filler !== fillerRef) {
84405
84430
  return;
84406
84431
  }