diff --git a/apps/origins-demo/src/views/NestedScenesView.vue b/apps/origins-demo/src/views/NestedScenesView.vue index f36a81b..7687ec3 100644 --- a/apps/origins-demo/src/views/NestedScenesView.vue +++ b/apps/origins-demo/src/views/NestedScenesView.vue @@ -7,6 +7,7 @@ import { gesture, originView, useOrigin, + type OriginView, } from "@native-vue-router/core-v2"; import { useDemoInstance } from "../lab-state"; import { unfocusPortal } from "../motions"; @@ -86,7 +87,7 @@ const slideSets: Record = { ], }; -function nestedView(kind: NestedSceneKind, index: number) { +function nestedView(kind: NestedSceneKind, index: number): OriginView { const slides = slideSets[kind]; const copy = slides[index]!; return originView( @@ -98,7 +99,7 @@ function nestedView(kind: NestedSceneKind, index: number) { ...copy, next: index + 1 < slides.length - ? () => nestedView(kind, index + 1) + ? (): OriginView => nestedView(kind, index + 1) : undefined, }, { diff --git a/packages/core-v2/API.md b/packages/core-v2/API.md index c91414e..1bfed0e 100644 --- a/packages/core-v2/API.md +++ b/packages/core-v2/API.md @@ -145,7 +145,7 @@ recognizer to it. | Prop | Type | Default | Description | | --------- | ------------------------- | ------- | ---------------------------------- | | `as` | `string` | `"div"` | HTML tag used for the gesture host | -| `gesture` | `OriginGestureDefinition` | — | Preferred builder definition | +| `gesture` | `OriginGestureDefinition` | — | Builder definition | Attributes, classes, and listeners not consumed as props are forwarded to the rendered host. @@ -156,8 +156,9 @@ rendered host. ``` -For compatibility, the component also accepts the legacy mutually exclusive -set of `direction`, `edge`, `threshold`, and `action` props. +The `gesture` prop is reactive. A new definition applies from the next +pointer-down; a gesture already in progress keeps the definition it started +with, so a parent re-render never interrupts a drag. Use `useOriginGesture()` instead when an extra wrapper is undesirable. @@ -190,9 +191,11 @@ binding, and derives the least-permissive shared `touch-action`: - vertical only: `pan-x`; - both axes: `none`. -Definitions should be immutable and stable for the lifetime of the rendered -surface. The owning page remains the visible declaration point for every -`.from`, `.to`, `.complete`, `.navigate`, and `.animate` choice. +The `gestures` list is reactive, including its length and the derived +`touch-action`. Recognizers are kept per list position, so a re-render with new +definition objects never interrupts a gesture in progress; each change applies +from the next pointer-down. The owning page remains the visible declaration +point for every `.from`, `.to`, `.complete`, `.navigate`, and `.animate` choice. ## Nested scenes @@ -457,7 +460,8 @@ const openProfile = gesture.to ### `useOriginGesture(definition)` Creates one primary-pointer, single-axis recognizer owned by the component that -calls it. +calls it. `definition` may also be a ref or getter; a changed definition +applies from the next pointer-down. ```ts const open = useOriginGesture( @@ -477,6 +481,7 @@ interface OriginGestureBinding { readonly onPointermove: (event: PointerEvent) => void; readonly onPointerup: (event: PointerEvent) => void; readonly onPointercancel: () => void; + readonly onLostpointercapture: (event: PointerEvent) => void; } ``` @@ -497,8 +502,14 @@ is normalized by the same dimension. An asynchronous navigation factory is supported. If it resolves after the pointer was released or cancelled, the stale result is discarded. -The legacy `OriginGestureOptions` object remains accepted. Its `edge` is a -number inferred from the side opposite `direction`, matching the previous API. +A captured gesture is cancelled, never left mid-transition, when its pointer +ends without a pointer-up: on `pointercancel`, on `lostpointercapture`, when a +new primary pointer goes down, or when the component unmounts. + +Failures are reported with `console.warn` and the gesture is abandoned: a +`navigate()` factory that throws or rejects, or a scene that rejects the +operation (see `OriginSceneError`). Returning `null` declines silently, as +does losing a race to another gesture on the same origin. ## Choreographies and effects @@ -575,10 +586,9 @@ When `finish()` does not explicitly override the decision, a target commits when either: - `progress >= commitThreshold`, default `0.36`; or -- release `velocity >= 0.3` progress units per second, at any progress. - -`commitVelocity` is accepted on choreographies but is not currently consulted; -the flick threshold is the fixed `0.3` above. +- release `velocity >= commitVelocity`, default `0.3` progress units per + second, at any progress. Velocity is normalized by the gesture host's size, + so `1` is one full width (or height) per second. The operation's intent becomes final before its spring settles. @@ -662,8 +672,13 @@ can immediately begin its own outgoing operation, enabling X → Y → Z chains. ### `OriginOperationHandle` -`intent` reports the selected outcome (`undecided`, `commit`, or `cancel`). It -changes synchronously when `finish()` or `cancel()` is called. +`intent` changes synchronously when `finish()` or `cancel()` is called, before +the settling spring runs. + +Calling `update()` while an operation settles catches it: the spring stops and +progress is under control again, for example to grab a transition that is +still animating. A later `finish()` decides the outcome, and any earlier +`finish()` promise resolves with that final outcome. | Member | Description | | ----------------------------- | --------------------------------------------------- | @@ -672,6 +687,7 @@ changes synchronously when `finish()` or `cancel()` is called. | `targetKey` | Created or retained target node key | | `update(progress, velocity?)` | Update normalized interactive state | | `finish(options?)` | Decide, settle, and return whether target committed | +| `intent` | Selected outcome: `undecided`, `commit`, `cancel` | | `cancel(options?)` | Force cancellation and remove the target branch | `OriginFinishOptions`: @@ -737,12 +753,6 @@ Node-local action context: - `previous`: recipe belonging to the mounted previous entry. - `history`: recipes belonging to all retained previous entries. -### `MaybeOriginAction` - -```ts -OriginAction | null | undefined | Promise; -``` - ### `OriginEffect` | Field | Description | @@ -786,6 +796,21 @@ implementation types. They are exported by the current barrel but marked ## Errors and constraints +Scene operations reject with `OriginSceneError`, whose `code` identifies the +cause: + +| Code | Cause | +| ------------------- | --------------------------------------------------- | +| `missing-node` | The node no longer exists | +| `missing-origin` | The origin node does not exist | +| `inert-origin` | The origin is parked or exposed | +| `origin-busy` | The origin already has a live outgoing operation | +| `invalid-action` | The action cannot be executed as written | +| `no-previous-entry` | Back was requested without a retained entry | +| `undecided-forward` | Back was requested through an unreleased forward | +| `target-busy` | The back target is transitioning or still presented | +| `outside-scene` | `useOrigin()` was called outside an `` | + - `useOrigin()` and `useOriginGesture()` must run inside a component mounted by `OriginScene`. - An origin can own only one outgoing operation at a time. diff --git a/packages/core-v2/src/choreography.ts b/packages/core-v2/src/choreography.ts index 0c4cb76..fc756a7 100644 --- a/packages/core-v2/src/choreography.ts +++ b/packages/core-v2/src/choreography.ts @@ -115,11 +115,11 @@ export interface OriginChoreography { */ readonly commitThreshold?: number; /** - * Reserved: normalized positive release velocity intended to commit a - * deliberate flick. + * Release velocity, in progress units per second, that commits a flick + * regardless of progress. Velocity is normalized by the gesture host's + * size, so `1` means one full host width (or height) per second. * - * The scene currently commits any release whose velocity is at least `0.3`, - * regardless of this value. See `FLICK_COMMIT_VELOCITY` in the scene module. + * @defaultValue `0.3` */ readonly commitVelocity?: number; /** diff --git a/packages/core-v2/src/components/OriginGesture.vue b/packages/core-v2/src/components/OriginGesture.vue index 0689f53..62f09ad 100644 --- a/packages/core-v2/src/components/OriginGesture.vue +++ b/packages/core-v2/src/components/OriginGesture.vue @@ -1,6 +1,6 @@