Gestures - Cancel a captured gesture on lostpointercapture, on a new primary pointer-down, and when its component unmounts, so operations are never left stuck mid-transition. Bindings gain onLostpointercapture. - Warn (console.warn) when navigate() fails or the scene rejects an operation; stay quiet when two recognizers race for one origin. - OriginGesture and OriginGestureSurface props are reactive. Changes apply from the next pointer-down; recognizers are kept per list position so re-renders never interrupt a drag. - Remove the legacy direction/edge/threshold/action gesture form (OriginGestureOptions, legacy OriginGesture props, MaybeOriginAction). Scene - Honour choreography commitVelocity (default 0.3, as before). - Scene errors are OriginSceneError with a stable code. - update() during settling now really catches an operation: the spring stops, and only the latest finish() decides the outcome. - Fix graph corruption found by the new property tests, all present on main: replacements escaping a cancelled push, edges left dangling when their current target differs from the original one, revealed targets keeping operations after their back is cancelled, history pointing at removed entries after replace, visual cycles from back through an undecided replace, exposed nodes reactivated as active, and a cancel cascade that could empty the whole scene. Tests - Add property tests checking structural invariants after every step of random operation sequences and pointer streams (PROPERTY_SEEDS). - Add recognizer tests for stuck gestures, warnings, reactive props, and commitVelocity. Drop the main-comparison suites; keep benchmarks. Also fix the origins demo's two pre-existing type errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Faarh2Ts6StJPcz2mRiHiR
831 lines
30 KiB
Markdown
831 lines
30 KiB
Markdown
# Core v2 API reference
|
|
|
|
`@native-vue-router/core-v2` is a routeless Vue scene compositor. A mounted
|
|
component can create another component, animate both through a local operation
|
|
frame, and retain either side when the operation resolves.
|
|
|
|
This document describes the experimental `0.1.0-experimental.0` API.
|
|
|
|
## Contents
|
|
|
|
- [Gesture start recognition](#gesture-start-recognition)
|
|
- [Minimum setup](#minimum-setup)
|
|
- [Components](#components)
|
|
- [Nested scenes](#nested-scenes)
|
|
- [Scene and view functions](#scene-and-view-functions)
|
|
- [Actions and history](#actions-and-history)
|
|
- [Gestures](#gestures)
|
|
- [Choreographies and effects](#choreographies-and-effects)
|
|
- [Node-scoped controls](#node-scoped-controls)
|
|
- [Scene diagnostics and manual operations](#scene-diagnostics-and-manual-operations)
|
|
- [Type reference](#type-reference)
|
|
- [Errors and constraints](#errors-and-constraints)
|
|
|
|
## Gesture start recognition
|
|
|
|
The builder separates where a gesture begins from the direction it moves:
|
|
|
|
```ts
|
|
const edgeBack = gesture.from
|
|
.left("clamp(24px, 7vw, 48px)")
|
|
.to.right()
|
|
.navigate((context) => (context.canGoBack ? back() : null))
|
|
.animate(slideRight);
|
|
```
|
|
|
|
Edges are measured from the **gesture host element**, not unconditionally from
|
|
the browser viewport. Supported start rules are:
|
|
|
|
```ts
|
|
gesture.from.left(distance);
|
|
gesture.from.right(distance);
|
|
gesture.from.top(distance);
|
|
gesture.from.bottom(distance);
|
|
gesture.from.anywhere();
|
|
gesture.from.when(predicate);
|
|
```
|
|
|
|
`distance` accepts a number in CSS pixels or a CSS length string, including
|
|
percentages, `calc()`, and `clamp()`. It is resolved against the current host
|
|
size at pointer-down.
|
|
|
|
`.from` is optional. A chain beginning at `.to` admits pointer-down anywhere:
|
|
|
|
```ts
|
|
gesture.to.right();
|
|
```
|
|
|
|
This is semantically equivalent to:
|
|
|
|
```ts
|
|
gesture.from.anywhere().to.right();
|
|
```
|
|
|
|
It does not capture on the first positive pixel. The recognizer waits until
|
|
directed movement crosses the intent threshold and dominates the cross-axis.
|
|
|
|
Custom shapes, safe-area rules, and exclusion zones belong in `.from.when()`:
|
|
|
|
```ts
|
|
const dropDialog = gesture.from
|
|
.when(({ point, bounds, event }) => {
|
|
const rail = Math.max(36, bounds.width * 0.08);
|
|
const outsideExcludedBand =
|
|
event.clientY < bounds.top + bounds.height * 0.35 ||
|
|
event.clientY > bounds.top + bounds.height * 0.65;
|
|
return point.localX <= rail && outsideExcludedBand;
|
|
})
|
|
.to.down()
|
|
.navigate(() => above(originView(DialogView)))
|
|
.animate(dropAnimation);
|
|
```
|
|
|
|
Interactive form controls are ignored automatically. Add
|
|
`data-origin-gesture="ignore"` to any other element or ancestor that should not
|
|
begin a gesture.
|
|
|
|
## Minimum setup
|
|
|
|
Import the required compositor stylesheet once:
|
|
|
|
```ts
|
|
import "@native-vue-router/core-v2/style.css";
|
|
```
|
|
|
|
Create a scene:
|
|
|
|
```ts
|
|
import { createOriginScene, originView } from "@native-vue-router/core-v2";
|
|
import HomeView from "./HomeView.vue";
|
|
|
|
export const scene = createOriginScene({
|
|
initial: originView(HomeView, undefined, {
|
|
key: "home",
|
|
name: "Home",
|
|
}),
|
|
});
|
|
```
|
|
|
|
Render it:
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
import { OriginScene } from "@native-vue-router/core-v2";
|
|
import { scene } from "./scene";
|
|
</script>
|
|
|
|
<template>
|
|
<OriginScene :scene="scene" />
|
|
</template>
|
|
```
|
|
|
|
`OriginScene` must have a non-zero width and height through its parent layout.
|
|
|
|
## Components
|
|
|
|
### `OriginScene`
|
|
|
|
Renders every currently mounted scene node as a stable, absolutely positioned
|
|
sibling.
|
|
|
|
| Prop | Type | Required | Description |
|
|
| ------- | ------------- | -------- | -------------------------------------- |
|
|
| `scene` | `OriginScene` | yes | Scene created by `createOriginScene()` |
|
|
|
|
The component provides node ownership to descendants, registers host elements
|
|
for measurement, and applies the scene's composited styles. Application views
|
|
must be rendered through this component before calling `useOrigin()` or
|
|
`useOriginGesture()`.
|
|
|
|
### `OriginGesture`
|
|
|
|
Convenience component that renders one HTML element and binds one gesture
|
|
recognizer to it.
|
|
|
|
| Prop | Type | Default | Description |
|
|
| --------- | ------------------------- | ------- | ---------------------------------- |
|
|
| `as` | `string` | `"div"` | HTML tag used for the gesture host |
|
|
| `gesture` | `OriginGestureDefinition` | — | Builder definition |
|
|
|
|
Attributes, classes, and listeners not consumed as props are forwarded to the
|
|
rendered host.
|
|
|
|
```vue
|
|
<OriginGesture as="main" class="profile" :gesture="openDetailsGesture">
|
|
...
|
|
</OriginGesture>
|
|
```
|
|
|
|
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.
|
|
|
|
### `OriginGestureSurface`
|
|
|
|
Policy-neutral host for multiple completed builder definitions:
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
const gestures = [forwardGesture, backGesture] as const;
|
|
</script>
|
|
|
|
<template>
|
|
<OriginGestureSurface as="main" :gestures="gestures">
|
|
...
|
|
</OriginGestureSurface>
|
|
</template>
|
|
```
|
|
|
|
| Prop | Type | Default | Description |
|
|
| ---------- | ------------------------------------ | -------- | ------------------------------ |
|
|
| `as` | `string` | `"div"` | Shared native gesture host |
|
|
| `gestures` | `readonly OriginGestureDefinition[]` | required | Fully defined page-owned rules |
|
|
|
|
The component adds no recognition or navigation policy. It installs each
|
|
definition with `useOriginGesture()`, forwards every pointer event to every
|
|
binding, and derives the least-permissive shared `touch-action`:
|
|
|
|
- horizontal only: `pan-y`;
|
|
- vertical only: `pan-x`;
|
|
- both axes: `none`.
|
|
|
|
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
|
|
|
|
`OriginScene` is a reusable compositor, not an application-only singleton. A
|
|
component may render another scene inside its own layout:
|
|
|
|
```vue
|
|
<section class="carousel">
|
|
<OriginScene :scene="carouselScene" />
|
|
</section>
|
|
```
|
|
|
|
The child scene gets independent mounted nodes, retained history, measurements,
|
|
operations, and clipping. `useOrigin()` and `useOriginGesture()` resolve the
|
|
nearest scene-node provider, so declarations inside a carousel slide operate
|
|
on carousel components rather than the outer page.
|
|
|
|
Parent/child gesture arbitration is currently pointer-down based. An eligible
|
|
child recognizer stops propagation immediately. If its later `.navigate()`
|
|
factory returns `null`, that same pointer sequence is not offered to the
|
|
parent. Cooperative nested components should reserve a start region with
|
|
`.from.when()` or `.from.left()` that allows the parent handler to receive
|
|
pointer-down.
|
|
|
|
The nested-scenes demo contains both this cooperative policy and an intentional
|
|
greedy-child conflict. A future gesture arena could delay ownership until
|
|
direction and navigation availability are known.
|
|
|
|
## Scene and view functions
|
|
|
|
### `originView(component, props?, options?)`
|
|
|
|
Creates a lightweight recipe for mounting a Vue component.
|
|
|
|
```ts
|
|
const profile = originView(
|
|
ProfileView,
|
|
{ userId: "42" },
|
|
{ key: "profile-42", name: "Profile" },
|
|
);
|
|
```
|
|
|
|
The recipe is not itself a mounted instance. A forward action creates an
|
|
instance from it, then retains that exact instance while its entry remains in
|
|
history. Back does not call the recipe again. Component definitions are marked
|
|
raw so Vue does not proxy them inside reactive scene structures.
|
|
|
|
`OriginViewOptions`:
|
|
|
|
| Field | Type | Description |
|
|
| ------ | -------- | --------------------------------------------- |
|
|
| `key` | `string` | Recipe identity and generated node-key prefix |
|
|
| `name` | `string` | Human-readable diagnostic label |
|
|
|
|
When no key is supplied, one is generated from the component/name and a
|
|
sequence number.
|
|
|
|
### `createOriginScene(options)`
|
|
|
|
Creates one independent scene graph, history context, and compositor.
|
|
|
|
```ts
|
|
const scene = createOriginScene({
|
|
initial: originView(HomeView),
|
|
});
|
|
```
|
|
|
|
`options.initial` accepts one `OriginView` or an array of independent root
|
|
views. Scenes do not share nodes, history, operation IDs, or measurements.
|
|
|
|
## Actions and history
|
|
|
|
### `forward(target, choreography?, options?)`
|
|
|
|
With choreography, creates a complete retained-history push action:
|
|
|
|
```ts
|
|
const openProfile = () =>
|
|
forward(originView(ProfileView, { userId: "42" }), slideLeft);
|
|
```
|
|
|
|
Without choreography, it creates an `OriginNavigationIntent` for a gesture
|
|
builder:
|
|
|
|
```ts
|
|
.navigate(() => forward(originView(ProfileView, { userId: "42" })))
|
|
```
|
|
|
|
When forward commits, the origin remains mounted but becomes parked. Its DOM,
|
|
component-local state, and nested scroll positions remain intact.
|
|
|
|
`OriginNavigationActionOptions.placement` defaults to `"above"`.
|
|
|
|
### `back(choreography?, options?)`
|
|
|
|
Creates a retained-history pop action:
|
|
|
|
```ts
|
|
const goBack = (context: OriginContext) =>
|
|
context.canGoBack ? back(slideRight) : null;
|
|
```
|
|
|
|
Back has no target recipe. When it begins, the scene resolves the origin's
|
|
`previousNodeKey` and reveals that exact mounted instance. A committed back
|
|
unmounts only the current entry. A cancelled back hides the previous entry
|
|
again and leaves the current entry active.
|
|
|
|
`back()` without choreography returns an animation-free navigation intent for
|
|
`.navigate()`. `back(slideRight)` returns a complete programmatic action.
|
|
|
|
`OriginNavigationActionOptions.placement` defaults to `"under"`.
|
|
|
|
### `replace(target, choreography?, options?)`
|
|
|
|
Creates a new target while removing the current history entry:
|
|
|
|
```ts
|
|
const confirmOrder = () =>
|
|
replace(
|
|
originView(OrderConfirmationView, { orderId: "NVO-2048" }),
|
|
slideLeft,
|
|
);
|
|
```
|
|
|
|
The target inherits the origin's `previousNodeKey`, so a later back skips the
|
|
replaced entry. The origin remains mounted while the operation is interactive
|
|
or settling and is unmounted only after commit. Cancelling removes the proposed
|
|
target and restores the origin without changing history.
|
|
|
|
Without choreography, `replace(target)` creates an intent suitable for a
|
|
gesture builder:
|
|
|
|
```ts
|
|
.navigate(() => replace(originView(OrderConfirmationView)))
|
|
.animate(slideLeft)
|
|
```
|
|
|
|
`OriginNavigationActionOptions.placement` defaults to `"above"`.
|
|
|
|
### `originAction(target, choreography, options?)`
|
|
|
|
Constructs a complete low-level `OriginAction`. Prefer `forward()`, `replace()`,
|
|
and `back()` when expressing retained navigation.
|
|
|
|
```ts
|
|
const action = originAction(profile, slideLeft, {
|
|
placement: "above",
|
|
history: "push",
|
|
});
|
|
```
|
|
|
|
`OriginActionOptions`:
|
|
|
|
| Field | Type | Default | Description |
|
|
| ----------- | -------------------- | --------- | ---------------------------- |
|
|
| `placement` | `"above" \| "under"` | `"above"` | Target stacking relationship |
|
|
| `history` | `OriginHistoryMode` | `"push"` | Target history mutation |
|
|
|
|
### `above(target, choreography?, options?)`
|
|
|
|
Shorthand for `originAction()` with `placement: "above"`.
|
|
|
|
```ts
|
|
const openProfile = () => above(originView(ProfileView), slideLeft);
|
|
```
|
|
|
|
Placement controls stacking only. It does not imply a movement direction.
|
|
Omitting choreography returns a navigation intent for a gesture builder.
|
|
|
|
### `under(target, choreography?, options?)`
|
|
|
|
Shorthand for `originAction()` with `placement: "under"`.
|
|
|
|
```ts
|
|
const goBack = (context: OriginContext) =>
|
|
context.previous ? back(slideRight) : null;
|
|
```
|
|
|
|
`under()` does not automatically mean history back. It remains available for
|
|
custom stacking actions; `back()` is the clearer retained-history primitive.
|
|
Omitting choreography returns a navigation intent for a gesture builder.
|
|
|
|
### History modes
|
|
|
|
History is a linked chain of mounted scene nodes.
|
|
|
|
| Mode | Commit behavior |
|
|
| ----------- | -------------------------------------------------------------- |
|
|
| `"push"` | Park and retain the origin; activate the new target |
|
|
| `"replace"` | Create a new target, inherit prior history, unmount the origin |
|
|
| `"back"` | Reuse the retained previous target; pop and unmount the origin |
|
|
|
|
Parked entries are `inert`, `aria-hidden`, invisible, and excluded from pointer
|
|
input. They remain mounted until back pops them or the scene is destroyed.
|
|
|
|
## Gestures
|
|
|
|
### `gesture`
|
|
|
|
Immutable fluent builder for component-owned gesture policy:
|
|
|
|
```ts
|
|
const swipeBack = gesture.from
|
|
.left(32)
|
|
.to.right({ threshold: 10 })
|
|
.complete(({ progress, velocity }) => progress >= 0.4 || velocity >= 0.9)
|
|
.navigate((context) => (context.canGoBack ? back() : null))
|
|
.animate(slideRight);
|
|
```
|
|
|
|
The stages have distinct responsibilities:
|
|
|
|
| Stage | Responsibility |
|
|
| ---------------------- | ---------------------------------------------------------- |
|
|
| `.from.*` | Optional pointer-down eligibility |
|
|
| `.to.*` | Required movement direction and intent-recognition options |
|
|
| `.complete(predicate)` | Optional release commit/cancel decision |
|
|
| `.navigate(factory)` | Required target and retained-history intent |
|
|
| `.onCommit(handler)` | Optional side effect when a release commits |
|
|
| `.animate(routine)` | Required source/target/frame choreography |
|
|
|
|
The builder is persistent and immutable. Reusing an earlier stage cannot
|
|
change a definition already produced from it.
|
|
|
|
`.to.left()`, `.to.right()`, `.to.up()`, and `.to.down()` accept optional
|
|
`OriginGestureDirectionOptions`:
|
|
|
|
| Field | Default | Description |
|
|
| --------------- | ------- | ------------------------------------------------- |
|
|
| `threshold` | `8` | Directed CSS pixels required before capture |
|
|
| `axisDominance` | `1.15` | Directed/cross-axis ratio required before capture |
|
|
|
|
If `.complete()` is omitted, the choreography's `commitThreshold` and
|
|
`commitVelocity` decide release normally.
|
|
|
|
The completion context contains the origin, direction, normalized progress and
|
|
velocity, directed pixel distance, cross-axis distance, duration, pointer-up
|
|
event, host, bounds, and start/current points. Completion predicates are
|
|
synchronous because they select operation intent at release.
|
|
|
|
`.onCommit(handler)` is available at every stage after `.to.*`, returns the
|
|
same stage, and may be called more than once; handlers run in order. A handler
|
|
runs once, synchronously at release, after the operation commits and before
|
|
the settling animation, and receives the same context as `.complete()`. It
|
|
never runs for a cancelled release. A throwing handler is reported as an
|
|
uncaught error without stopping later handlers or the operation. It is the
|
|
place for haptic feedback:
|
|
|
|
```ts
|
|
const openProfile = gesture.to
|
|
.left()
|
|
.navigate(() => forward(originView(ProfileView)))
|
|
.onCommit(() => navigator.vibrate?.(10))
|
|
.animate(slideLeft);
|
|
```
|
|
|
|
`navigator.vibrate` is unavailable in iOS Safari; Capacitor apps can call
|
|
`Haptics.impact()` from `@capacitor/haptics` instead. The method is named
|
|
`onCommit` rather than `then` so builder stages never become promise-like
|
|
"thenables" when awaited or returned from an async function.
|
|
|
|
### `useOriginGesture(definition)`
|
|
|
|
Creates one primary-pointer, single-axis recognizer owned by the component that
|
|
calls it. `definition` may also be a ref or getter; a changed definition
|
|
applies from the next pointer-down.
|
|
|
|
```ts
|
|
const open = useOriginGesture(
|
|
gesture.to
|
|
.left({ threshold: 10 })
|
|
.navigate(() => forward(originView(DetailsView)))
|
|
.animate(slideLeft),
|
|
);
|
|
```
|
|
|
|
The return value contains:
|
|
|
|
```ts
|
|
interface OriginGestureBinding {
|
|
readonly style: Readonly<CSSProperties>;
|
|
readonly onPointerdown: (event: PointerEvent) => void;
|
|
readonly onPointermove: (event: PointerEvent) => void;
|
|
readonly onPointerup: (event: PointerEvent) => void;
|
|
readonly onPointercancel: () => void;
|
|
readonly onLostpointercapture: (event: PointerEvent) => void;
|
|
}
|
|
```
|
|
|
|
Apply all handlers to the same element. The returned style sets dimensions and
|
|
`touch-action` so native scrolling remains available on the cross-axis.
|
|
|
|
Recognition requires:
|
|
|
|
1. A primary, left-button pointer satisfies the optional start policy.
|
|
2. The target is not an ignored interactive element.
|
|
3. Directed movement reaches `threshold`.
|
|
4. Directed movement exceeds cross-axis movement by `axisDominance`.
|
|
5. The navigation factory returns an intent.
|
|
|
|
Progress is directed distance divided by host width or height. Release velocity
|
|
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.
|
|
|
|
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
|
|
|
|
### `defineOriginChoreography(choreography)`
|
|
|
|
Type-safe identity helper for declaring custom visual routines.
|
|
|
|
```ts
|
|
const scaleIn = defineOriginChoreography({
|
|
name: "scale-in",
|
|
commitThreshold: 0.4,
|
|
commitVelocity: 0.8,
|
|
effects: ({ progress, viewport }) => ({
|
|
source: {
|
|
transform: `scale(${1 - progress * 0.08})`,
|
|
opacity: 1 - progress * 0.3,
|
|
},
|
|
target: {
|
|
transform: `translateY(${(1 - progress) * viewport.height}px)`,
|
|
},
|
|
}),
|
|
});
|
|
```
|
|
|
|
The function returns the same object. Its value is type checking and a clear
|
|
construction point.
|
|
|
|
Set `persistAtRest: true` when progress `1` should remain as a connected visual
|
|
relationship after a committed push:
|
|
|
|
```ts
|
|
const openPartialDrawer = defineOriginChoreography({
|
|
name: "partial-drawer-open",
|
|
persistAtRest: true,
|
|
effects: ({ progress }) => ({
|
|
source: {
|
|
transform: `translateX(${progress * 66.6667}%)`,
|
|
},
|
|
target: {
|
|
transform: `translateX(${(progress - 1) * 66.6667}%)`,
|
|
},
|
|
}),
|
|
});
|
|
```
|
|
|
|
This leaves the retained source mounted, visible, and inert instead of parking
|
|
it. The target remains the active history entry. Beginning back suspends the
|
|
resting relationship so a reciprocal close choreography can take over;
|
|
cancelling back restores it exactly.
|
|
|
|
Connected resting effects are supported only by retained-history push actions.
|
|
They are designed for partial drawers, inspectors, and other presentations
|
|
where both mounted views remain visible after commit. They do not appear in
|
|
`scene.operations`, which reports live interactive/settling edges only.
|
|
|
|
`effects()` may return:
|
|
|
|
| Effect | Applied to |
|
|
| -------- | ---------------------------------------------------------- |
|
|
| `frame` | Source, target, and descendants on both sides of this edge |
|
|
| `source` | Component that originated this operation |
|
|
| `target` | Component created by this operation and its descendants |
|
|
|
|
Transforms are concatenated from inherited frames to local frames. Opacity is
|
|
multiplied. Properties inside `style` use local-last precedence, except
|
|
`transform` and numeric `opacity`, which are also composed.
|
|
|
|
Choreography callbacks should be deterministic and free of side effects. They
|
|
can run repeatedly during rendering and animation.
|
|
|
|
### Commit thresholds
|
|
|
|
When `finish()` does not explicitly override the decision, a target commits
|
|
when either:
|
|
|
|
- `progress >= commitThreshold`, default `0.36`; or
|
|
- 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.
|
|
|
|
### Included presets
|
|
|
|
| Export | Behavior |
|
|
| ------------ | ------------------------------------------------------------- |
|
|
| `slideLeft` | Target enters from the right above a slightly receding source |
|
|
| `slideRight` | Source exits right and reveals a target underneath |
|
|
| `fade` | Source fades out as target fades in |
|
|
|
|
These are ordinary `OriginChoreography` objects and can be replaced entirely.
|
|
|
|
### `normalizedEffect(effect, fallbackLayer?)`
|
|
|
|
Internal compositor helper exposed for custom diagnostics or compositors. It
|
|
returns a defined effect and adds `fallbackLayer` to the effect's own layer.
|
|
Applications normally return plain effects and let the scene normalize them.
|
|
|
|
## Node-scoped controls
|
|
|
|
### `useOrigin()`
|
|
|
|
Returns controls scoped to the scene node containing the calling component.
|
|
|
|
```ts
|
|
const origin = useOrigin();
|
|
|
|
await origin.perform(forward(originView(SettingsView), fade));
|
|
```
|
|
|
|
Return value:
|
|
|
|
| Field | Description |
|
|
| ----------------- | --------------------------------------------------- |
|
|
| `nodeKey` | Unique key of this mounted node |
|
|
| `scene` | Containing `OriginScene` |
|
|
| `context` | Reactive node-local `OriginContext` |
|
|
| `view` | Reactive shorthand for the current recipe |
|
|
| `previous` | Recipe belonging to the retained previous instance |
|
|
| `canGoBack` | Whether a retained previous instance exists |
|
|
| `begin(action)` | Create a target and return manual operation control |
|
|
| `perform(action)` | Create and programmatically commit a target |
|
|
|
|
The composable throws when called outside a view mounted by `OriginScene`.
|
|
There is no global `activeView`; the injected node containing the event is the
|
|
origin.
|
|
|
|
## Scene diagnostics and manual operations
|
|
|
|
### `OriginScene` fields
|
|
|
|
| Field | Type | Description |
|
|
| ------------ | ----------------------------------------- | ------------------------------------- |
|
|
| `nodes` | `ComputedRef<readonly OriginSceneNode[]>` | Currently mounted Vue component nodes |
|
|
| `operations` | `ComputedRef<readonly OriginOperation[]>` | Live operation edges |
|
|
| `roots` | `ShallowRef<readonly string[]>` | Visible operation-graph roots |
|
|
|
|
These fields are suitable for inspectors and diagnostics. Do not mutate their
|
|
contents.
|
|
|
|
### `scene.contextFor(nodeKey)`
|
|
|
|
Returns the `OriginContext` for a mounted node. Throws if the key no longer
|
|
exists.
|
|
|
|
### `scene.begin(originKey, action)`
|
|
|
|
For forward or replace, mounts a new target and waits one Vue tick for
|
|
measurement. For back, reveals and measures the retained previous node. It
|
|
then returns an `OriginOperationHandle`.
|
|
|
|
```ts
|
|
const handle = await scene.begin(nodeKey, action);
|
|
handle.update(0.25, 0.4);
|
|
const committed = await handle.finish();
|
|
```
|
|
|
|
Only one outgoing operation may exist for a given origin. Its created target
|
|
can immediately begin its own outgoing operation, enabling X → Y → Z chains.
|
|
|
|
### `OriginOperationHandle`
|
|
|
|
`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 |
|
|
| ----------------------------- | --------------------------------------------------- |
|
|
| `id` | Unique operation ID |
|
|
| `originKey` | Source node key |
|
|
| `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`:
|
|
|
|
| Field | Default | Description |
|
|
| --------- | ------------------ | ---------------------------- |
|
|
| `commit` | threshold decision | Force commit or cancellation |
|
|
| `animate` | `true` | Run the settling spring |
|
|
|
|
### `scene.perform(originKey, action)`
|
|
|
|
Equivalent to beginning an operation and immediately finishing it with
|
|
`commit: true`. The target still uses the settling spring unless reduced motion
|
|
is active.
|
|
|
|
### Renderer integration methods
|
|
|
|
`registerElement()`, `registerContainer()`, `styleForNode()`, and
|
|
`isNodeInteractive()` are public at the TypeScript boundary because the Vue
|
|
renderer components consume them. They are internal integration APIs and may
|
|
change during the experimental series.
|
|
|
|
## Type reference
|
|
|
|
### `OriginView<Props>`
|
|
|
|
A component recipe containing `component`, optional `props`, optional `key`,
|
|
and optional diagnostic `name`.
|
|
|
|
### `OriginAction`
|
|
|
|
A choreography, placement, history mode, and—except for back—target recipe.
|
|
|
|
### `OriginNavigationIntent`
|
|
|
|
An animation-free target, placement, and history mutation returned by
|
|
`forward()`, `replace()`, `back()`, `above()`, or `under()` when choreography
|
|
is omitted. Gesture `.animate()` combines it with choreography to create the
|
|
internal action.
|
|
|
|
### Gesture definition types
|
|
|
|
- `OriginGestureDefinition`: immutable executable result passed to
|
|
`useOriginGesture()` or the `OriginGesture` component.
|
|
- `OriginGestureStart`: anywhere, edge, or predicate start policy.
|
|
- `OriginGestureDistance`: numeric CSS pixels or a CSS length string.
|
|
- `OriginGestureStartContext`: pointer-down event, origin, host, bounds, and
|
|
local/client point.
|
|
- `OriginGestureCompletionContext`: release metrics and origin/DOM context.
|
|
- `OriginGestureDirectionOptions`: `threshold` and `axisDominance`.
|
|
- `OriginGestureBinding`: host style and four pointer handlers.
|
|
- `OriginGestureSurfaceProps`: shared host tag and completed definition list.
|
|
- `MaybeOriginNavigationIntent`: synchronous or asynchronous nullable
|
|
navigation-factory result.
|
|
|
|
### `OriginContext`
|
|
|
|
Node-local action context:
|
|
|
|
- `nodeKey`: mounted origin identity.
|
|
- `view`: origin recipe.
|
|
- `canGoBack`: whether a retained previous instance exists.
|
|
- `previous`: recipe belonging to the mounted previous entry.
|
|
- `history`: recipes belonging to all retained previous entries.
|
|
|
|
### `OriginEffect`
|
|
|
|
| Field | Description |
|
|
| ----------- | ----------------------------------------------- |
|
|
| `transform` | Composable CSS transform contribution |
|
|
| `opacity` | Multiplicative opacity contribution |
|
|
| `layer` | Relative stacking contribution |
|
|
| `style` | Other CSS properties with local-last precedence |
|
|
|
|
`above()` adds a default target layer of `+1`; `under()` adds `-1`.
|
|
|
|
### `OriginChoreographyContext`
|
|
|
|
| Field | Description |
|
|
| ------------ | ------------------------------------------------------ |
|
|
| `progress` | Normalized `0..1` progress |
|
|
| `velocity` | Normalized progress units per second |
|
|
| `phase` | `preparing`, `interactive`, `settling`, or `finished` |
|
|
| `intent` | `undecided`, `commit`, or `cancel` |
|
|
| `originRect` | Origin bounds captured before target mounting |
|
|
| `targetRect` | Target bounds measured after mounting |
|
|
| `viewport` | Scene-container bounds, with browser viewport fallback |
|
|
|
|
Rect values are viewport CSS pixels.
|
|
|
|
### Diagnostic types
|
|
|
|
- `OriginSceneNode`: mounted identity, retained previous key, state, recipe,
|
|
history, and incoming edge.
|
|
- `OriginSceneNodeState`: `active`, `transitioning`, `exposed`, or `parked`.
|
|
- `OriginOperation`: read-only live edge state.
|
|
- `OriginOperationPhase`: operation lifecycle phase.
|
|
- `OriginOperationIntent`: selected operation outcome.
|
|
- `OriginRect`: top, left, width, and height.
|
|
|
|
### Internal types
|
|
|
|
`OriginNodeScope` and `MutableOriginOperation` are renderer/runtime
|
|
implementation types. They are exported by the current barrel but marked
|
|
`@internal` and should not be application dependencies.
|
|
|
|
## 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 `<OriginScene>` |
|
|
|
|
- `useOrigin()` and `useOriginGesture()` must run inside a component mounted by
|
|
`OriginScene`.
|
|
- An origin can own only one outgoing operation at a time.
|
|
- Parked or exposed retained entries are inert and cannot originate operations.
|
|
The active connected target owns interactions until back reveals its source.
|
|
- A target can originate its own operation as soon as its incoming operation's
|
|
intent becomes commit.
|
|
- The included recognizer follows one primary pointer and one axis.
|
|
- Builder edges accept CSS lengths; arbitrary start policy belongs in
|
|
`.from.when()`.
|
|
- Every pushed history entry retains its Vue instance and DOM until a committed
|
|
back operation pops it. There is no eviction policy yet.
|
|
- Parked instances remain mounted, so their ordinary Vue effects and timers
|
|
continue running.
|
|
- A choreography creates one target. Chaining supports any number of
|
|
simultaneously mounted targets.
|
|
- Reduced-motion preference resolves settling immediately.
|