@jsenv/navi 0.29.194 → 0.29.195

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, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, scrollIntoViewScoped, initFocusGroup, isTouchDrivenEvent, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, getVirtualKeyboardOverlayHeight, 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, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, installPanZoom, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, scrollIntoViewScoped, initFocusGroup, isTouchDrivenEvent, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, getVirtualKeyboardOverlayHeight, 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, Fragment as Fragment$1 } from "preact";
@@ -10500,26 +10500,31 @@ const swipeTypeOf = (axis, pulled) => {
10500
10500
  };
10501
10501
 
10502
10502
  /**
10503
- * `move`, `reorder`, `land`, `toss` — one grab, and what letting go of it means.
10503
+ * `move`, `reorder`, `land`, `toss`, `leave` — one grab, and what letting go of
10504
+ * it means.
10504
10505
  *
10505
- * All four are the same gesture: the element is picked up and carried. What
10506
+ * All five are the same gesture: the element is picked up and carried. What
10506
10507
  * differs is the answer at the release, so one detector reads them all — it is one
10507
10508
  * press, and something has to arbitrate it.
10508
10509
  *
10509
10510
  * interactions={{ reorder: moveBefore, toss: remove }}
10510
10511
  * interactions={{ land: swapPlaces }}
10511
- * interactions={{ move: remember }}
10512
+ * interactions={{ move: remember, leave: remove }}
10512
10513
  *
10513
- * `toss` combines with `reorder` and with `land`: a task dragged onto another
10514
- * changes places, the same task thrown far and fast is gotten rid of. The three
10515
- * others do not combine with each other — an element either goes where it is put,
10516
- * takes a place in a list, or comes down on a place, and no two of those answers
10517
- * can both be true of one release.
10514
+ * `toss` and `leave` each combine with `reorder` and with `land`: a task dragged
10515
+ * onto another changes places, the same task thrown far and fast is gotten rid
10516
+ * of, a marker let go of off its plan is removed. `leave` combines with `move` as
10517
+ * well. `move`, `reorder` and `land` do not combine with each other — an element
10518
+ * either goes where it is put, takes a place in a list, or comes down on a place,
10519
+ * and no two of those answers can both be true of one release.
10518
10520
  *
10519
10521
  * `move` carries the element ITSELF and leaves it where it was put; the others
10520
10522
  * carry a copy and put the original back. That is the same difference said in
10521
10523
  * layout terms: something moved has a new place of its own, something reordered
10522
- * had its place taken by the list.
10524
+ * had its place taken by the list. Where that place is KEPT is the answer's:
10525
+ * an application that redraws the element from state while answering (a new
10526
+ * `left`/`top`) owns the position from then on, and the drag's own translate
10527
+ * goes; one that draws nothing leaves it to the element, where it is baked in.
10523
10528
  *
10524
10529
  * `reorder` VS `land`: both come down on an item, and what separates them is what
10525
10530
  * a place IS. A row of a list is a place BETWEEN two others — free by construction,
@@ -10535,9 +10540,8 @@ const swipeTypeOf = (axis, pulled) => {
10535
10540
  * }}}
10536
10541
  *
10537
10542
  * `toId` is an element and never null: a copy over nothing is a release that meant
10538
- * nothing, and the interaction does not happen at all — unless the element says
10539
- * `data-toss-by="release-outside"` (see below), which gives that release a
10540
- * meaning of its own.
10543
+ * nothing, and the interaction does not happen at all — unless the element
10544
+ * declares `leave` (see below), which gives that release a meaning of its own.
10541
10545
  *
10542
10546
  * WHERE ON IT, in `x`, `y`, `width`, `height`: the box the copy came down in,
10543
10547
  * measured inside the place — its border and its scroll taken out. A place that is
@@ -10574,15 +10578,30 @@ const swipeTypeOf = (axis, pulled) => {
10574
10578
  * <Plan id="plan" data-droppable>…</Plan>
10575
10579
  * </div>
10576
10580
  *
10577
- * HOW A TOSS IS MADE: `data-toss-by`, beside `toss`. A THROW (`"throw"`, the
10578
- * default) is far AND fast — the flick that gets rid of something, a list's
10579
- * gesture, and it is judged before any landing. A RELEASE OUTSIDE
10580
- * (`"release-outside"`) has no place under it — dragging a marker off a plan and
10581
- * letting go, deliberate and never a flick, a surface's gesture. Each is named on
10582
- * its own because each is wrong where the other is right: on a plan a fast drag
10583
- * that ends ON it has not asked for the thing to go, and on a list a row let go of
10584
- * beside it is a row put back. Both at once is
10585
- * `data-toss-by="throw release-outside"`.
10581
+ * LET GO OF AWAY FROM EVERY PLACE: `leave`. A throw is a GESTURE — far and fast,
10582
+ * the flick that gets rid of a row, judged before any landing. A release outside
10583
+ * is a PLACE — the hand let go with nothing under the thing, judged after a
10584
+ * landing was looked for. They share the outcome an application usually attaches
10585
+ * to them and nothing else, so each has its name: `toss` is the throw, `leave` is
10586
+ * the release outside, and neither reads the other's rules — there is no speed to
10587
+ * a release, and on a plan a fast drag that ends ON it has not asked for the
10588
+ * thing to go.
10589
+ *
10590
+ * interactions={{
10591
+ * move: (event) => remember(event.detail.x, event.detail.y),
10592
+ * leave: () => remove(id),
10593
+ * }}
10594
+ *
10595
+ * Beside `land` or `reorder`, "outside" is away from every place. Beside `move`
10596
+ * — the element itself travels, and nothing is a place — it is outside the
10597
+ * surface the element stands in: the nearest `data-droppable` ancestor, or,
10598
+ * without one, what can be seen of the scroll container. Either way it is judged
10599
+ * on the element's box no longer overlapping it, not on the pointer, which is
10600
+ * still well inside the frame when a 30px marker has just left it. The detail is
10601
+ * `toss`'s, `{ id, x, y }`, the distance travelled being what an exit is animated
10602
+ * with. A refused `leave` travels back, as a refused `move` does; picked up and
10603
+ * put straight back down stays a cancel — it has to have gone somewhere to be
10604
+ * away from anything.
10586
10605
  *
10587
10606
  * Nothing of the gesture is decided here. `startDragTo` owns all of it — the
10588
10607
  * copy carried above the page while the original keeps its place in the layout, the
@@ -10630,7 +10649,7 @@ const swipeTypeOf = (axis, pulled) => {
10630
10649
  *
10631
10650
  * interactions={{ toss: remove, grab: () => navigator.vibrate?.(10) }}
10632
10651
  *
10633
- * The four above all answer the RELEASE, and between the press and the release
10652
+ * The five above all answer the RELEASE, and between the press and the release
10634
10653
  * there is one instant that counts for the hand making the gesture: the one where
10635
10654
  * the object stops being pressed and starts being held. `grab` is that instant,
10636
10655
  * and it is the same one whichever way the drag was entered — a finger held still,
@@ -10644,7 +10663,7 @@ const swipeTypeOf = (axis, pulled) => {
10644
10663
  *
10645
10664
  * It is told, not asked: `grab` reports, so what it returns is not waited on and
10646
10665
  * preventing its event does not call the gesture off. And it is not an interaction
10647
- * on its own — declared without one of the four above there is no gesture for it
10666
+ * on its own — declared without one of the five above there is no gesture for it
10648
10667
  * to be the beginning of.
10649
10668
  *
10650
10669
  * A `longpress` needs nothing of this: it already happens at the moment the hold
@@ -10664,6 +10683,8 @@ const REORDER = "reorder";
10664
10683
  // for a file being dropped on the page would get this one and read it as such.
10665
10684
  const LAND = "land";
10666
10685
  const TOSS = "toss";
10686
+ // Let go of away from every place — not a throw, which is judged on its speed.
10687
+ const LEAVE = "leave";
10667
10688
  // The moment the press stops being a press and becomes a hold on the object.
10668
10689
  const GRAB = "grab";
10669
10690
 
@@ -10680,14 +10701,9 @@ const DROP_CONTAINER_ATTRIBUTE = "data-drop-container";
10680
10701
  const AXIS_ATTRIBUTE = "data-drag-axis";
10681
10702
  const DELAY_ATTRIBUTE = "data-drag-delay";
10682
10703
  const SLOP_ATTRIBUTE = "data-drag-slop";
10683
- const THRESHOLD_ATTRIBUTE = "data-drag-threshold";
10704
+ const THRESHOLD_ATTRIBUTE$1 = "data-drag-threshold";
10684
10705
  const TOSS_DISTANCE_ATTRIBUTE = "data-toss-distance";
10685
10706
  const TOSS_SPEED_ATTRIBUTE = "data-toss-speed";
10686
- // How a toss is made here: "throw" (far and fast), "release-outside" (let go of
10687
- // away from every place), or both.
10688
- const TOSS_BY_ATTRIBUTE = "data-toss-by";
10689
- const TOSS_BY_DEFAULT = "throw";
10690
- const TOSS_WAYS = ["throw", "release-outside"];
10691
10707
 
10692
10708
  defineInteractionDetector({
10693
10709
  name: "drag",
@@ -10696,6 +10712,7 @@ defineInteractionDetector({
10696
10712
  type === REORDER ||
10697
10713
  type === LAND ||
10698
10714
  type === TOSS ||
10715
+ type === LEAVE ||
10699
10716
  type === GRAB,
10700
10717
  // The press is the beginning of the gesture, not an answer: nothing may read
10701
10718
  // it until it is known whether the hand is dragging or just pressing.
@@ -10705,12 +10722,13 @@ defineInteractionDetector({
10705
10722
  const canReorder = types.includes(REORDER);
10706
10723
  const canLand = types.includes(LAND);
10707
10724
  const canToss = types.includes(TOSS);
10708
- if (!canMove && !canReorder && !canLand && !canToss) {
10725
+ const canLeave = types.includes(LEAVE);
10726
+ if (!canMove && !canReorder && !canLand && !canToss && !canLeave) {
10709
10727
  // Only "grab": there is no gesture to be taken by, so there is no moment to
10710
10728
  // be told about either.
10711
10729
  {
10712
10730
  console.warn(
10713
- `interactions: "${GRAB}" says when a drag takes hold, so it needs a drag to say it about. Declare it beside "${MOVE}", "${REORDER}", "${LAND}" or "${TOSS}".`,
10731
+ `interactions: "${GRAB}" says when a drag takes hold, so it needs a drag to say it about. Declare it beside "${MOVE}", "${REORDER}", "${LAND}", "${TOSS}" or "${LEAVE}".`,
10714
10732
  );
10715
10733
  }
10716
10734
  return undefined;
@@ -10726,7 +10744,7 @@ defineInteractionDetector({
10726
10744
  const other = canLand ? LAND : canReorder ? REORDER : TOSS;
10727
10745
  const instead =
10728
10746
  other === TOSS
10729
- ? ` Something that can be put down anywhere AND thrown away declares "${LAND}" rather than "${MOVE}": its detail says where the copy came down, which is what a surface answers with.`
10747
+ ? ` Something that moves in place AND goes away when dragged off its surface declares "${LEAVE}" beside "${MOVE}": a release outside the surface is what "${LEAVE}" answers, and it is not a throw.`
10730
10748
  : "";
10731
10749
  console.warn(
10732
10750
  `interactions: "${MOVE}" and "${other}" cannot both answer one release — "${MOVE}" leaves the element where the hand put it, "${other}" carries a copy and puts the original back. "${other}" wins here, so the element never travels.${instead}`,
@@ -10744,34 +10762,23 @@ defineInteractionDetector({
10744
10762
  axisHolder?.getAttribute(AXIS_ATTRIBUTE) ||
10745
10763
  // A list runs one way, and reordering walks it. Anything else goes wherever
10746
10764
  // the hand takes it: a board has places all around, a thing put somewhere has
10747
- // two axes to be put along, and a throw goes where it was thrown.
10748
- (canReorder && !canLand && !canToss ? "y" : "xy");
10749
- // Where the places are, and what a release away from all of them means —
10750
- // both read at setup for the same reason as the axes: they are what the
10751
- // gesture is about, and it is what holds the two sides of it that says them.
10765
+ // two axes to be put along, a throw goes where it was thrown and a thing
10766
+ // that leaves goes out by whichever edge.
10767
+ (canReorder && !canLand && !canToss && !canLeave ? "y" : "xy");
10768
+ // Where the places are, read at setup for the same reason as the axes: they
10769
+ // are what the gesture is about, and it is what holds the two sides of it
10770
+ // that says them.
10752
10771
  const dropContainer = element.closest(`[${DROP_CONTAINER_ATTRIBUTE}]`);
10753
- const tossByHolder = element.closest(`[${TOSS_BY_ATTRIBUTE}]`);
10754
- const tossBy = (
10755
- tossByHolder?.getAttribute(TOSS_BY_ATTRIBUTE) || TOSS_BY_DEFAULT
10756
- )
10757
- .trim()
10758
- .split(/\s+/);
10759
- {
10760
- for (const way of tossBy) {
10761
- if (!TOSS_WAYS.includes(way)) {
10762
- console.warn(
10763
- `interactions: "${way}" is not a way a toss is made. ${TOSS_BY_ATTRIBUTE} takes "${TOSS_WAYS.join('", "')}", or both.`,
10764
- );
10765
- }
10766
- }
10767
- if (
10768
- tossBy.includes("release-outside") &&
10769
- (!canToss || (!canReorder && !canLand))
10770
- ) {
10771
- console.warn(
10772
- `interactions: ${TOSS_BY_ATTRIBUTE}="release-outside" makes a release away from every place mean the thing is gotten rid of, so it needs both "${TOSS}" and places to be away from. Declare "${TOSS}" beside "${REORDER}" or "${LAND}".`,
10773
- );
10774
- }
10772
+ // What a `leave` is outside of when nothing is a place: the surface the
10773
+ // element stands in. Looked for above the parent — the element itself may be
10774
+ // a place of its own (a piece that receives), which is not what it is inside.
10775
+ const outsideOf =
10776
+ element.parentElement.closest(`[${DROPPABLE_ATTRIBUTE}]`) || undefined;
10777
+ if (canLeave && !canLand && !canReorder && !outsideOf) {
10778
+ console.warn(
10779
+ `interactions: "${LEAVE}" is judged against the surface this element stands in, and nothing above it is marked ${DROPPABLE_ATTRIBUTE} — so it is judged against what can be seen of the scroll container, which on a page is the window. Mark the surface it leaves.`,
10780
+ element,
10781
+ );
10775
10782
  }
10776
10783
 
10777
10784
  if (canReorder) {
@@ -10795,7 +10802,7 @@ defineInteractionDetector({
10795
10802
  const onPointerDown = (pointerDownEvent) => {
10796
10803
  if (canLand && !placesLookedFor) {
10797
10804
  placesLookedFor = true;
10798
- warnWhenNothingToLandOn(element, dropContainer, tossBy);
10805
+ warnWhenNothingToLandOn(element, dropContainer, canLeave);
10799
10806
  }
10800
10807
  // What this element says a release can mean. The gesture then runs only what
10801
10808
  // those need — no copy for a move, no drop hint for something that can only
@@ -10809,6 +10816,7 @@ defineInteractionDetector({
10809
10816
  ? `[${REORDERABLE_ATTRIBUTE}]`
10810
10817
  : undefined,
10811
10818
  containerElement: dropContainer || undefined,
10819
+ outsideOf,
10812
10820
  getItemId: (itemElement) => itemElement.id,
10813
10821
  direction: { x: axes.includes("x"), y: axes.includes("y") },
10814
10822
  // Where it may go, said in the DOM. A thing that is put somewhere stays
@@ -10816,21 +10824,20 @@ defineInteractionDetector({
10816
10824
  // the scrollable area can be far larger than the box, and "inside the box"
10817
10825
  // is what a hand expects). `data-drag-free` lifts that, and so does a
10818
10826
  // named container of places: they are somewhere else by construction, and
10819
- // a copy kept in its own scroll area could never reach them. Left alone
10820
- // for a throw, which frees the area on its own — it has to be able to
10821
- // leave.
10827
+ // a copy kept in its own scroll area could never reach them. So does
10828
+ // "leave": a thing let go of outside has to be able to get there. Left
10829
+ // alone for a throw, which frees the area on its own.
10822
10830
  areaConstraint:
10823
- dropContainer || element.closest(`[data-drag-free]`)
10831
+ dropContainer || element.closest(`[data-drag-free]`) || canLeave
10824
10832
  ? "none"
10825
10833
  : canMove
10826
10834
  ? "scrollport"
10827
10835
  : undefined,
10828
- threshold: readConfig(THRESHOLD_ATTRIBUTE, undefined),
10836
+ threshold: readConfig(THRESHOLD_ATTRIBUTE$1, undefined),
10829
10837
  longPressDelay: readConfig(DELAY_ATTRIBUTE, undefined),
10830
10838
  longPressSlop: readConfig(SLOP_ATTRIBUTE, undefined),
10831
10839
  tossDistance: readConfig(TOSS_DISTANCE_ATTRIBUTE, undefined),
10832
10840
  tossSpeed: readConfig(TOSS_SPEED_ATTRIBUTE, undefined),
10833
- tossBy,
10834
10841
  // The one moment the gesture has that is not a release. Said here rather
10835
10842
  // than from the press that led to it, because the press is only one of the
10836
10843
  // two ways in: a finger holds still, a mouse travels a few pixels, and it
@@ -10844,7 +10851,7 @@ defineInteractionDetector({
10844
10851
  });
10845
10852
  }
10846
10853
  : undefined,
10847
- // Handed straight back in all three cases: what `trigger` returns is a
10854
+ // Handed straight back in every case: what `trigger` returns is a
10848
10855
  // promise while the answer is still going, which is what the gesture waits
10849
10856
  // on before it lets go of what it carries.
10850
10857
  onReorder: (fromId, toId, syncCloneWithDropTarget) =>
@@ -10861,6 +10868,8 @@ defineInteractionDetector({
10861
10868
  x: gestureInfo.layout.xDelta,
10862
10869
  y: gestureInfo.layout.yDelta,
10863
10870
  }),
10871
+ onLeave: ({ x, y }) =>
10872
+ trigger(LEAVE, pointerDownEvent, { id: element.id, x, y }),
10864
10873
  onMove: ({ x, y }) => trigger(MOVE, pointerDownEvent, { x, y }),
10865
10874
  });
10866
10875
  };
@@ -10876,10 +10885,9 @@ defineInteractionDetector({
10876
10885
 
10877
10886
  // A place is looked for INSIDE the container and nowhere else, so the surface a
10878
10887
  // piece already stands on is not one of them — a search never finds what it starts
10879
- // from. Nothing then happens at all, and where `release-outside` is on it is worse
10880
- // than nothing: every release is away from every place, so every release gets rid
10881
- // of the thing.
10882
- const warnWhenNothingToLandOn = (element, dropContainer, tossBy) => {
10888
+ // from. Nothing then happens at all, and beside `leave` it is worse than nothing:
10889
+ // every release is away from every place, so every release is a leave.
10890
+ const warnWhenNothingToLandOn = (element, dropContainer, canLeave) => {
10883
10891
  const container = dropContainer || element.parentElement;
10884
10892
  if (!container) {
10885
10893
  return;
@@ -10891,8 +10899,8 @@ const warnWhenNothingToLandOn = (element, dropContainer, tossBy) => {
10891
10899
  return;
10892
10900
  }
10893
10901
  }
10894
- const consequence = tossBy.includes("release-outside")
10895
- ? ` Every release is then away from every place, which ${TOSS_BY_ATTRIBUTE}="release-outside" reads as a toss.`
10902
+ const consequence = canLeave
10903
+ ? ` Every release is then away from every place, which "${LEAVE}" answers.`
10896
10904
  : " Nothing answers a release.";
10897
10905
  const droppableAround = element.parentElement?.closest(
10898
10906
  `[${DROPPABLE_ATTRIBUTE}]`,
@@ -10910,6 +10918,71 @@ const warnWhenNothingToLandOn = (element, dropContainer, tossBy) => {
10910
10918
  );
10911
10919
  };
10912
10920
 
10921
+ /**
10922
+ * `pan`, `zoom` — a surface under the hand.
10923
+ *
10924
+ * <Box
10925
+ * interactions={{
10926
+ * pan: (event) => moveCenterBy(event.detail), // { x, y } since the last one
10927
+ * zoom: (event) => zoomBy(event.detail), // { factor, x, y }
10928
+ * }}
10929
+ * />
10930
+ *
10931
+ * Neither is an outcome: both are a stream, reported on every frame while the
10932
+ * hand is still moving, and navi otherwise names only what happens once (see
10933
+ * "A gesture whose product is a value" in docs/interactions.md). They are named
10934
+ * here because of what has to be settled BEFORE the press, which is what
10935
+ * `interactions` exists for: `touch-action` on the surface, said from a
10936
+ * stylesheet since a browser decides what a touch may do when it lands; the pan
10937
+ * stepping back for what is carried ACROSS the surface (a marker declaring
10938
+ * `move`, a handle) and for what answers the pointer on its own; the pinch not
10939
+ * beginning as a pan under its first finger; the wheel and the pinch writing
10940
+ * one `zoom`. A handle turned, a sun dragged around a plan have none of that to
10941
+ * arbitrate — the handle is the whole surface of the gesture — and stay
10942
+ * unnamed.
10943
+ *
10944
+ * `pan`'s detail is the movement since the previous `pan`, in px. `zoom`'s is
10945
+ * the factor (above 1 is in) and the point of the surface it is around,
10946
+ * measured inside its border — the point between the fingers, or under the
10947
+ * wheel. What a pixel of pan means in the application's coordinates, and
10948
+ * whether the zoom is continuous or stepped, is the application's.
10949
+ *
10950
+ * Declared alone, `zoom` leaves one pointer to whatever else reads it and takes
10951
+ * only two fingers and the wheel; `pan` alone leaves the wheel to the page.
10952
+ *
10953
+ * Nothing of the gesture is decided here — `installPanZoom` in @jsenv/dom owns
10954
+ * the pointers, the capture, the wheel burst and the click left behind. This
10955
+ * says which names the element answers with, and hands the numbers over.
10956
+ */
10957
+
10958
+
10959
+ const PAN = "pan";
10960
+ const ZOOM = "zoom";
10961
+ // The same attribute a carried element reads: how far a pointer travels before
10962
+ // it is a gesture rather than a press.
10963
+ const THRESHOLD_ATTRIBUTE = "data-drag-threshold";
10964
+
10965
+ defineInteractionDetector({
10966
+ name: "surface",
10967
+ claims: (type) => type === PAN || type === ZOOM,
10968
+ // A press on the surface may be a tap, a hold or the beginning of a pan, and
10969
+ // nothing may read it as the first until the pointer has said which.
10970
+ disputesPress: true,
10971
+ setup: (element, trigger, { types, readConfig }) => {
10972
+ const canPan = types.includes(PAN);
10973
+ const canZoom = types.includes(ZOOM);
10974
+ return installPanZoom(element, {
10975
+ threshold: readConfig(THRESHOLD_ATTRIBUTE, undefined),
10976
+ onPan: canPan
10977
+ ? ({ event, x, y }) => trigger(PAN, event, { x, y })
10978
+ : undefined,
10979
+ onZoom: canZoom
10980
+ ? ({ event, factor, x, y }) => trigger(ZOOM, event, { factor, x, y })
10981
+ : undefined,
10982
+ });
10983
+ },
10984
+ });
10985
+
10913
10986
  // used by form elements such as <input>, <select>, <textarea> to have their own action bound to a single parameter
10914
10987
  // when inside a <form> the form params are updated when the form element single param is updated
10915
10988
  const useActionBoundToOneParam = (action, paramsSignal) => {
@@ -20206,9 +20279,14 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
20206
20279
  * preventInitialTransition?: boolean,
20207
20280
  * separator?: import("ignore:preact").ComponentChildren | ((index: number) => import("ignore:preact").ComponentChildren),
20208
20281
  * selfInteractions?: string,
20282
+ * interactions?: { [type: string]: "request_action" | "request_ui_action" | ((event: Event) => void) | false | null | undefined },
20209
20283
  * children?: import("ignore:preact").ComponentChildren,
20210
20284
  * [key: string]: any,
20211
20285
  * }>}
20286
+ * @param {object} [interactions] What this box answers, by interaction name
20287
+ * (see docs/interactions.md). A plain box has no wiring of its own: it does
20288
+ * nothing with `action` (that is a control's prop), so a click on it is
20289
+ * declared here like any other interaction: `interactions={{ click: fn }}`.
20212
20290
  */
20213
20291
  const Box = props => {
20214
20292
  const {
@@ -20348,6 +20426,14 @@ const computeBox = (props, parentBoxFlow) => {
20348
20426
  rest[SELF_INTERACTIONS_ATTRIBUTE] = selfInteractionsAttributeValue(selfInteractions);
20349
20427
  }
20350
20428
  let as = asProp;
20429
+ if (Object.hasOwn(rest, "action")) {
20430
+ // A control (Button, Link, a field) takes `action` off its props before
20431
+ // reaching here, so an `action` still present is one nothing will run. A
20432
+ // string is a form's own attribute and stays.
20433
+ if (typeof rest.action !== "string") {
20434
+ console.warn(`Box: "action" is a control's prop and a plain box does nothing with it. To answer a click, declare it as an interaction: interactions={{ click: fn }}.`);
20435
+ }
20436
+ }
20351
20437
 
20352
20438
  // A box that scrolls is what gives header/footer/body their meaning, and
20353
20439
  // saying overflow="auto" is already saying it — no second prop for the same