@jsenv/navi 0.29.188 → 0.29.189

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.
@@ -5,7 +5,7 @@
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
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, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
8
- export { chainEvent, clickIsSuppressed, contrastColor, findEvent, startDragTo } from "@jsenv/dom";
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";
11
11
  import { useErrorBoundary, useLayoutEffect, useContext, useCallback, useRef, useState, useEffect, useMemo, useId } from "preact/hooks";
@@ -5,7 +5,7 @@
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
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, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
8
- export { chainEvent, clickIsSuppressed, contrastColor, findEvent, startDragTo } from "@jsenv/dom";
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";
11
11
  import { useErrorBoundary, useLayoutEffect, useContext, useCallback, useRef, useState, useEffect, useMemo, useId } from "preact/hooks";
@@ -328,6 +328,11 @@ consistency across the app, not from any single call site.
328
328
  NOT stop (a plain `onClick` on an ancestor). It also draws the line with
329
329
  `standalone`: those two answer about the GESTURE, `standalone` about the
330
330
  VALUE, and they are three separate props because they come apart.
331
+ And it says what happens when the gesture's product is a VALUE — an angle, a
332
+ scale, a plan panned under the finger — rather than an element that ends up
333
+ somewhere: no interaction names that, so navi hands over the machinery
334
+ (`createDragGestureController`, `dragAfterIntent`, `markDragSource`, all
335
+ re-exported from navi) with the three traps it takes care of.
331
336
  Read it before reading the pointer by hand — who owns a press between nested
332
337
  boxes, and what a touch may do, are decided before the first pixel moves and
333
338
  cannot be got right from outside navi — and before stopping the propagation of
@@ -781,6 +781,70 @@ container above it does not take the gesture:
781
781
  `docs/drag_to_travel.md`). navi's own swipes do the equivalent with
782
782
  `data-travel-by-drag`.
783
783
 
784
+ ## A gesture whose product is a value
785
+
786
+ `move`, `reorder`, `land` and `toss` answer the same question — where did the
787
+ element end up — because all four carry it. A rotation does not: what comes out of
788
+ it is an angle, and the handle that produced it stays exactly where it is. Same for
789
+ a scale, a zoom, a plan panned under the finger.
790
+
791
+ navi names none of those, and it is not an oversight: an interaction names an
792
+ **outcome** — something that happened once, that can be told, refused, awaited.
793
+ There is no outcome here, only a number read while the hand is still moving, and
794
+ what to do with it (snap it to the neighbouring court, draw it, keep it in a
795
+ signal) is knowledge navi does not have. So it hands over the machinery instead
796
+ and leaves the paint alone:
797
+
798
+ ```js
799
+ import { createDragGestureController, dragAfterIntent } from "@jsenv/navi";
800
+
801
+ const onPointerDown = (pointerDownEvent) => {
802
+ dragAfterIntent(pointerDownEvent, () => {
803
+ const controller = createDragGestureController({
804
+ onDrag: (gestureInfo) => {
805
+ const { xDelta, yDelta } = gestureInfo.layout;
806
+ angleSignal.value = snapToNeighbours(angleFromDelta(xDelta, yDelta));
807
+ },
808
+ onRelease: (gestureInfo) => {
809
+ if (gestureInfo.cancelled) {
810
+ return;
811
+ }
812
+ COURT.PUT.run({ angle: angleSignal.peek() });
813
+ },
814
+ });
815
+ return controller.grabViaPointer(pointerDownEvent, {
816
+ element: pointerDownEvent.currentTarget,
817
+ });
818
+ });
819
+ };
820
+ ```
821
+
822
+ An element that ends up somewhere is NOT this: that one is `move`, and doing it by
823
+ hand gives up the constraint, the commit and the way back when the answer refuses.
824
+
825
+ Three things come with the machinery, and they are the reason not to write it again:
826
+
827
+ - **When the press becomes a drag** is `dragAfterIntent`, and the answer is not one
828
+ policy but three: a `data-drag-handle` exists only to drag, so it takes the
829
+ gesture on contact; a mouse resolves it by distance; a finger resolves it by
830
+ TIME, because travel is exactly what a scroll looks like. One timer for every
831
+ pointer makes the mouse wait for something it never had to prove.
832
+ - **A touch has to be refusable before it is refused.** `touch-action` must be
833
+ non-`auto` when the finger LANDS: after that the touch is on the compositor's
834
+ fast path, every `preventDefault` is an intervention, and on Android the scroll
835
+ runs away with the object. `markDragSource(element, axes)` says it from a
836
+ stylesheet, on the source itself — which is how what surrounds it keeps
837
+ scrolling. A blanket `touch-action: none` on the container buys the same drag by
838
+ taking the pan away, and the pan then has to be written by hand too.
839
+ - **A capture that goes was not necessarily given back.** `lostpointercapture`
840
+ reads the same whether the gesture handed the pointer over or the browser dropped
841
+ it mid-drag, which it does more often than the specification suggests. The loop
842
+ tells the two apart, and `gestureInfo.cancelled` is where it comes out: a release
843
+ nobody asked for must commit nothing.
844
+
845
+ A gesture driven this way is outside the registry, so it says so itself to whatever
846
+ travels above it: `data-no-drag-travel` (see `docs/drag_to_travel.md`).
847
+
784
848
  ## Things worth knowing before guessing
785
849
 
786
850
  - **A drag says its axes to whoever else answers the press.** `data-drag-axis`
@@ -834,6 +898,9 @@ container above it does not take the gesture:
834
898
  `toss` and the `grab` moment.
835
899
  - `src/control/interaction/interaction_keyboard.js`,
836
900
  `interaction_native.js` — the other two detectors.
901
+ - `@jsenv/dom` — `src/interaction/drag/drag_gesture.js` (the loop, its options and
902
+ what `gestureInfo` holds) and `src/interaction/drag/drag_after_intent.js` (when a
903
+ press becomes a drag, and what a source says in the stylesheet).
837
904
  - `src/control/demos/38_interactions_demo.html` — every case above, plus a
838
905
  mailbox, a board whose places are zones and the same board whose places are the
839
906
  pieces, and a custom gesture registered from the page.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.188",
3
+ "version": "0.29.189",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "prepublishOnly": "npm run build"
35
35
  },
36
36
  "dependencies": {
37
- "@jsenv/dom": "0.17.44",
37
+ "@jsenv/dom": "0.17.45",
38
38
  "@jsenv/humanize": "1.7.8",
39
39
  "@jsenv/validity": "0.4.3"
40
40
  },