From 511e58c198650b0025caad8395aa75e28976a2b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 02:49:56 +0000 Subject: [PATCH] Consolidate core-v2 modules and move scene logic onto classes - Merge the small modules into 12 files: shared, choreography, navigation, scene/{types,graph,scene}, gesture/{definition,recognizer}. - SceneGraph now owns the begin/update/commit/cancel history rules as methods, with the step helpers as private methods. - createOriginScene returns a markRaw'd Scene class; operation handles are an OperationHandle class instead of inline object literals. - GestureRecognizer gains static create()/policyFor() factories, absorbing the separate policy module. - Pure helpers used by classes (matchesStart, pointOf, stepSpring, composeNodeStyle, nodeEffectLayers, shouldCommit, ...) are exported consts so they can be tested directly. - Add haptics tests showing where navigator.vibrate can be triggered. Public exports, types, and behavior are unchanged; the differential suites against main still pass. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Faarh2Ts6StJPcz2mRiHiR --- packages/core-v2/README.md | 30 +- packages/core-v2/src/actions/types.ts | 106 ---- packages/core-v2/src/choreography.ts | 296 +++++++++++ packages/core-v2/src/choreography/compose.ts | 80 --- packages/core-v2/src/choreography/define.ts | 27 - packages/core-v2/src/choreography/presets.ts | 55 -- packages/core-v2/src/choreography/types.ts | 133 ----- .../core-v2/src/components/OriginGesture.vue | 4 +- .../src/components/OriginGestureSurface.vue | 5 +- .../core-v2/src/components/OriginNodeHost.vue | 2 +- .../core-v2/src/components/OriginScene.vue | 2 +- packages/core-v2/src/components/types.ts | 83 --- packages/core-v2/src/composables/useOrigin.ts | 92 ---- packages/core-v2/src/gesture/builder.ts | 123 ----- .../src/gesture/{types.ts => definition.ts} | 195 ++++++- packages/core-v2/src/gesture/direction.ts | 51 -- packages/core-v2/src/gesture/policy.ts | 87 --- packages/core-v2/src/gesture/recognizer.ts | 422 ++++++++++++++- packages/core-v2/src/gesture/start.ts | 115 ---- packages/core-v2/src/gesture/tracker.ts | 86 --- .../core-v2/src/gesture/useOriginGesture.ts | 60 --- packages/core-v2/src/haptics.test.ts | 177 +++++++ packages/core-v2/src/index.ts | 64 ++- .../src/{actions/actions.ts => navigation.ts} | 207 +++++++- packages/core-v2/src/scene/compositor.ts | 148 ------ .../core-v2/src/scene/createOriginScene.ts | 174 ------ packages/core-v2/src/scene/graph.ts | 342 +++++++++++- packages/core-v2/src/scene/scene.ts | 496 ++++++++++++++++++ packages/core-v2/src/scene/spring.ts | 90 ---- packages/core-v2/src/scene/transitions.ts | 278 ---------- packages/core-v2/src/scene/types.ts | 22 +- .../src/{shared/geometry.ts => shared.ts} | 14 + packages/core-v2/src/shared/environment.ts | 13 - packages/core-v2/src/view/originView.ts | 44 -- packages/core-v2/src/view/types.ts | 43 -- 35 files changed, 2177 insertions(+), 1989 deletions(-) delete mode 100644 packages/core-v2/src/actions/types.ts create mode 100644 packages/core-v2/src/choreography.ts delete mode 100644 packages/core-v2/src/choreography/compose.ts delete mode 100644 packages/core-v2/src/choreography/define.ts delete mode 100644 packages/core-v2/src/choreography/presets.ts delete mode 100644 packages/core-v2/src/choreography/types.ts delete mode 100644 packages/core-v2/src/components/types.ts delete mode 100644 packages/core-v2/src/composables/useOrigin.ts delete mode 100644 packages/core-v2/src/gesture/builder.ts rename packages/core-v2/src/gesture/{types.ts => definition.ts} (64%) delete mode 100644 packages/core-v2/src/gesture/direction.ts delete mode 100644 packages/core-v2/src/gesture/policy.ts delete mode 100644 packages/core-v2/src/gesture/start.ts delete mode 100644 packages/core-v2/src/gesture/tracker.ts delete mode 100644 packages/core-v2/src/gesture/useOriginGesture.ts create mode 100644 packages/core-v2/src/haptics.test.ts rename packages/core-v2/src/{actions/actions.ts => navigation.ts} (57%) delete mode 100644 packages/core-v2/src/scene/compositor.ts delete mode 100644 packages/core-v2/src/scene/createOriginScene.ts create mode 100644 packages/core-v2/src/scene/scene.ts delete mode 100644 packages/core-v2/src/scene/spring.ts delete mode 100644 packages/core-v2/src/scene/transitions.ts rename packages/core-v2/src/{shared/geometry.ts => shared.ts} (75%) delete mode 100644 packages/core-v2/src/shared/environment.ts delete mode 100644 packages/core-v2/src/view/originView.ts delete mode 100644 packages/core-v2/src/view/types.ts diff --git a/packages/core-v2/README.md b/packages/core-v2/README.md index db77f5d..f546a58 100644 --- a/packages/core-v2/README.md +++ b/packages/core-v2/README.md @@ -294,23 +294,23 @@ component ancestry. ## Source layout -The package is layered; each layer depends only on the ones above it. +Each module depends only on the ones listed above it. -| Directory | Responsibility | -| --------------- | ------------------------------------------------------------------------------------------------------- | -| `shared/` | Geometry and environment helpers | -| `view/` | `originView()` component recipes | -| `choreography/` | Effect types, `defineOriginChoreography()`, presets, and `composeEffects()` style compositing | -| `actions/` | `forward()`, `back()`, `replace()`, `above()`, `under()`, `originAction()` | -| `scene/` | `graph.ts` storage, `transitions.ts` history rules, `compositor.ts` styles, `spring.ts` settling motion | -| `composables/` | `useOrigin()` and the node-scope injection key | -| `gesture/` | Builder, definition→policy normalization, start admission, pointer tracking, and the recognizer | -| `components/` | `OriginScene`, `OriginGesture`, `OriginGestureSurface`, and the internal node host | +| File | Responsibility | +| ----------------------- | ------------------------------------------------------------------------------------------------- | +| `shared.ts` | Geometry and environment helpers | +| `choreography.ts` | Effect types, `defineOriginChoreography()`, presets, and `composeEffects()` style compositing | +| `navigation.ts` | `originView()` recipes and `forward()`, `back()`, `replace()`, `above()`, `under()` actions | +| `scene/types.ts` | Public scene types | +| `scene/graph.ts` | `SceneGraph`: node and edge storage plus the begin/commit/cancel history rules (all synchronous) | +| `scene/scene.ts` | `Scene` and `OperationHandle`: mounting, measuring, settling spring, compositing, and `useOrigin` | +| `gesture/definition.ts` | Gesture types, component props, and the immutable `gesture` builder | +| `gesture/recognizer.ts` | Pointer-down admission, `DirectionalTracker`, `GestureRecognizer`, and `useOriginGesture` | +| `components/` | `OriginScene`, `OriginGesture`, `OriginGestureSurface`, and the internal node host | -`createOriginScene()` wires the scene modules together: the graph holds nodes -and operation edges, transitions rewrite it synchronously when operations -begin, commit, or cancel, and the scene adds the asynchronous parts (waiting -for Vue to mount a target, measuring, and running the settling spring). +Pure helpers used by a class (for example `matchesStart`, `stepSpring`, or +`composeNodeStyle`) are module-level `export const`s that the class calls, so +they can be tested directly without widening the class's interface. ## Current experimental boundaries diff --git a/packages/core-v2/src/actions/types.ts b/packages/core-v2/src/actions/types.ts deleted file mode 100644 index 0c6f7b7..0000000 --- a/packages/core-v2/src/actions/types.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { OriginChoreography } from "../choreography/types"; -import type { OriginView } from "../view/types"; - -/** - * Controls how an operation participates in retained instance history. - * - * - `push`: create a target whose previous entry is the mounted origin. - * - `replace`: create a target that inherits the origin's previous entry, then - * remove the origin on commit. - * - `back`: reuse the mounted previous entry and pop the origin on commit. - */ -export type OriginHistoryMode = "push" | "replace" | "back"; - -/** - * The target's stacking relationship to its origin while an operation exists. - */ -export type OriginPlacement = "above" | "under"; - -/** - * A scene mutation without visual choreography. - * - * Gesture builders keep navigation intent separate from animation so the same - * destination can be paired with different component-local interactions. - * Calling `.animate()` materializes this intent as an {@link OriginAction}. - */ -export interface OriginNavigationIntent { - /** - * Recipe for a newly created push or replacement target. Back navigation - * omits this because the scene resolves the retained previous instance. - */ - readonly target?: OriginView; - /** Target stacking relationship while the gesture operation is visible. */ - readonly placement?: OriginPlacement; - /** Retained-history mutation performed if the gesture commits. */ - readonly history: OriginHistoryMode; -} - -/** A complete request to create and animate a target view from an origin. */ -export interface OriginAction { - /** - * Recipe for a newly created push or replacement target. A back action omits - * this because the scene resolves its already-mounted previous entry. - */ - readonly target?: OriginView; - /** Visual relationship applied to the source, target, and shared frame. */ - readonly choreography: OriginChoreography; - /** - * Target stacking relationship during the operation. - * - * @defaultValue `"above"` - */ - readonly placement?: OriginPlacement; - /** - * History mutation applied to the target recipe. - * - * @defaultValue `"push"` - */ - readonly history?: OriginHistoryMode; -} - -/** - * Values accepted from gesture action factories. - * - * Returning `null` or `undefined` declines the recognized gesture. A promise - * allows lazy target resolution; stale results are discarded after release or - * cancellation. - */ -export type MaybeOriginAction = - OriginAction | null | undefined | Promise; - -/** Options accepted by {@link originAction}. */ -export interface OriginActionOptions { - /** - * Target stacking relationship during the operation. - * - * @defaultValue `"above"` - */ - placement?: OriginPlacement; - /** - * History mutation assigned to the created target node. - * - * @defaultValue `"push"` - */ - history?: OriginHistoryMode; -} - -/** History options shared by the {@link above} and {@link under} helpers. */ -export interface OriginPlacementActionOptions { - /** - * History mutation assigned to the created target node. - * - * @defaultValue `"push"` - */ - history?: OriginHistoryMode; -} - -/** Stacking options accepted by retained-history navigation helpers. */ -export interface OriginNavigationActionOptions { - /** - * Target stacking relationship during the operation. - * - * @defaultValue `"above"` for {@link forward} and {@link replace}, `"under"` - * for {@link back} - */ - placement?: OriginPlacement; -} diff --git a/packages/core-v2/src/choreography.ts b/packages/core-v2/src/choreography.ts new file mode 100644 index 0000000..0c4cb76 --- /dev/null +++ b/packages/core-v2/src/choreography.ts @@ -0,0 +1,296 @@ +import type { CSSProperties } from "vue"; +import { clamp01, type OriginRect } from "./shared"; + +/* + * Choreographies: the visual side of an operation. A choreography computes + * effects from operation progress; `composeEffects` collapses the effects + * applied to one node into its inline style. Nothing here knows about history. + */ + +/** + * The lifecycle phase of an operation edge. + * + * - `preparing`: the target has been added and Vue is mounting it. + * - `interactive`: progress may be controlled by a gesture or application. + * - `settling`: the commit/cancel decision is fixed and the spring is running. + * - `finished`: the graph rewrite or cancellation cleanup has completed. + */ +export type OriginOperationPhase = + "preparing" | "interactive" | "settling" | "finished"; + +/** + * The outcome selected for an operation. + * + * Intent remains `undecided` until `finish()` is called. It becomes final + * before the settling animation completes, allowing the retained target to + * originate its own operation immediately. + */ +export type OriginOperationIntent = "undecided" | "commit" | "cancel"; + +/** + * One composable contribution to a scene node's final visual style. + * + * Transforms are concatenated in origin-to-descendant order and opacity values + * are multiplied. This lets X→Y and Y→Z affect Y simultaneously without either + * routine replacing the other routine's CSS transform. + */ +export interface OriginEffect { + /** + * A CSS transform contribution. Transform strings from inherited and local + * origin frames are concatenated rather than replacing one another. + */ + transform?: string; + /** + * An opacity contribution between `0` and `1`. Contributions from multiple + * frames are multiplied and the final value is clamped. + */ + opacity?: number; + /** + * Relative stacking contribution. `above()` defaults the target to +1 and + * `under()` defaults it to -1. + */ + layer?: number; + /** + * Escape hatch for non-geometric effects such as filter, clipPath, or + * borderRadius. Later/local effects override inherited properties. + */ + style?: CSSProperties; +} + +/** + * Independent visual contributions calculated for one operation edge. + * + * Any omitted contribution is treated as an identity effect. + */ +export interface OriginEffectSet { + /** + * Applied to both source and target. This is useful for moving an entire + * origin-relative coordinate frame. + */ + frame?: OriginEffect; + /** Applied to the component that initiated this operation. */ + source?: OriginEffect; + /** Applied to the component created by this operation and its descendants. */ + target?: OriginEffect; +} + +/** Values supplied whenever a choreography calculates its visual effects. */ +export interface OriginChoreographyContext { + /** Normalized operation progress, clamped to the inclusive range `0..1`. */ + progress: number; + /** + * Normalized progress units per second. Positive velocity moves toward + * commit; negative velocity moves back toward cancellation. + */ + velocity: number; + /** Current lifecycle phase of the operation. */ + phase: OriginOperationPhase; + /** Commit/cancel outcome, if release has already selected one. */ + intent: OriginOperationIntent; + /** Origin host bounds captured immediately before the target mounts. */ + originRect?: OriginRect; + /** Target host bounds measured after Vue mounts the target. */ + targetRect?: OriginRect; + /** Scene-container bounds, or the browser viewport when no container exists. */ + viewport: OriginRect; +} + +/** + * A choreography describes visual relationships only. Component creation, + * history, and cleanup are performed by the scene operation that uses it. + */ +export interface OriginChoreography { + /** Optional diagnostic name surfaced by scene inspectors and devtools. */ + readonly name?: string; + /** + * Calculate source, target, and shared-frame contributions for the current + * operation state. This function should be deterministic and side-effect + * free because it can run many times per animation frame. + */ + effects(context: OriginChoreographyContext): OriginEffectSet; + /** + * Gesture progress required to retain the target after release. + * + * @defaultValue `0.36` + */ + readonly commitThreshold?: number; + /** + * Reserved: normalized positive release velocity intended to commit a + * deliberate flick. + * + * 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. + */ + readonly commitVelocity?: number; + /** + * Keep this choreography's progress-`1` effects connected after a committed + * push instead of collapsing the edge and hiding its source. + * + * This is intended for partial presentations such as drawers and inspectors + * where part of the retained source remains visible beside the target. The + * source remains mounted, visible, and inert. A back operation temporarily + * suspends the resting edge; cancelling back restores it. + * + * Only retained-history `push` actions support connected resting effects. + * + * @defaultValue `false` + */ + readonly persistAtRest?: boolean; +} + +/** + * Preserve type inference while declaring a custom choreography. + * + * The helper performs no runtime transformation. It gives custom routines a + * named, documented construction point and validates their shape in TypeScript. + * + * @param choreography - Side-effect-free visual effect calculator and optional + * release thresholds. + * @returns The same choreography object. + */ +export function defineOriginChoreography( + choreography: OriginChoreography, +): OriginChoreography { + return choreography; +} + +/** Distinguish a choreography from a plain options object. */ +export const isChoreography = (value: unknown): value is OriginChoreography => { + return ( + typeof value === "object" && + value !== null && + "effects" in value && + typeof value.effects === "function" + ); +}; + +/** + * Normalize one contribution before the compositor combines it with effects + * inherited from earlier origin frames. + * + * Most applications should return plain effects from a choreography and let + * the scene call this function. It is exported for custom compositors and + * diagnostics. + * + * @param effect - Optional effect to normalize. + * @param fallbackLayer - Relative layer contribution added to `effect.layer`. + * @returns A defined effect with a numeric layer. + */ +export function normalizedEffect( + effect: OriginEffect | undefined, + fallbackLayer = 0, +): OriginEffect { + return { + ...effect, + layer: (effect?.layer ?? 0) + fallbackLayer, + }; +} + +/** Whether a transform contributes anything to a composed transform list. */ +function isComposable(transform: unknown): transform is string { + return ( + typeof transform === "string" && transform !== "" && transform !== "none" + ); +} + +/** Result of collapsing an ordered list of effects into one style. */ +export interface ComposedEffects { + /** Merged style with the composed `transform` and `opacity` applied. */ + readonly style: CSSProperties; + /** Sum of every effect's relative layer contribution. */ + readonly layer: number; +} + +/** + * Collapse independent effect layers, ordered from the outermost inherited + * frame to the most local one, into a single style. + * + * Transforms are concatenated and opacities multiplied, including `transform` + * and numeric `opacity` supplied through `effect.style`. Every other `style` + * property uses local-last precedence over `base` and earlier effects. + */ +export const composeEffects = ( + effects: readonly OriginEffect[], + base: CSSProperties = {}, +): ComposedEffects => { + const style: CSSProperties = { ...base }; + const transforms: string[] = []; + let opacity = 1; + let layer = 0; + + for (const effect of effects) { + if (isComposable(effect.transform)) transforms.push(effect.transform); + if (effect.opacity !== undefined) opacity *= effect.opacity; + layer += effect.layer ?? 0; + + if (effect.style) { + Object.assign(style, effect.style); + if (isComposable(effect.style.transform)) + transforms.push(effect.style.transform); + if (effect.style.opacity !== undefined) { + const styleOpacity = Number(effect.style.opacity); + if (Number.isFinite(styleOpacity)) opacity *= styleOpacity; + } + } + } + + // Assign the composed properties last so no single effect's `style` can + // replace transform work inherited from other frames. + style.transform = transforms.length ? transforms.join(" ") : "none"; + style.opacity = String(clamp01(opacity)); + return { style, layer }; +}; + +const percent = (value: number) => `${(value * 100).toFixed(4)}%`; + +/** + * Native-style forward motion: the target enters above the source while the + * source recedes slightly. These presets are examples; applications can + * replace them with arbitrary `defineOriginChoreography()` callbacks. + */ +export const slideLeft = defineOriginChoreography({ + name: "slide-left", + commitThreshold: 0.36, + commitVelocity: 0.6, + effects: ({ progress }) => ({ + source: { + transform: `translate3d(${percent(progress * -0.24)}, 0, 0) scale(${1 - progress * 0.025})`, + opacity: 1 - progress * 0.16, + }, + target: { + transform: `translate3d(${percent(1 - progress)}, 0, 0)`, + }, + }), +}); + +/** + * Back motion reveals the target underneath the source. This is merely a + * visual routine; the `back()` action selects retained-history behavior. + */ +export const slideRight = defineOriginChoreography({ + name: "slide-right", + commitThreshold: 0.36, + commitVelocity: 0.6, + effects: ({ progress }) => ({ + source: { + transform: `translate3d(${percent(progress)}, 0, 0)`, + }, + target: { + transform: `translate3d(${percent(-0.24 + progress * 0.24)}, 0, 0) scale(${0.975 + progress * 0.025})`, + opacity: 0.84 + progress * 0.16, + }, + }), +}); + +/** + * Cross-fade preset that fades the source out while fading the target in. + * + * The default release thresholds from the scene are used. + */ +export const fade = defineOriginChoreography({ + name: "fade", + effects: ({ progress }) => ({ + source: { opacity: 1 - progress }, + target: { opacity: progress }, + }), +}); diff --git a/packages/core-v2/src/choreography/compose.ts b/packages/core-v2/src/choreography/compose.ts deleted file mode 100644 index 6df33b4..0000000 --- a/packages/core-v2/src/choreography/compose.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { CSSProperties } from "vue"; -import { clamp01 } from "../shared/geometry"; -import type { OriginEffect } from "./types"; - -/** - * Normalize one contribution before the compositor combines it with effects - * inherited from earlier origin frames. - * - * Most applications should return plain effects from a choreography and let - * the scene call this function. It is exported for custom compositors and - * diagnostics. - * - * @param effect - Optional effect to normalize. - * @param fallbackLayer - Relative layer contribution added to `effect.layer`. - * @returns A defined effect with a numeric layer. - */ -export function normalizedEffect( - effect: OriginEffect | undefined, - fallbackLayer = 0, -): OriginEffect { - return { - ...effect, - layer: (effect?.layer ?? 0) + fallbackLayer, - }; -} - -/** Whether a transform contributes anything to a composed transform list. */ -function isComposable(transform: unknown): transform is string { - return ( - typeof transform === "string" && transform !== "" && transform !== "none" - ); -} - -/** Result of collapsing an ordered list of effects into one style. */ -export interface ComposedEffects { - /** Merged style with the composed `transform` and `opacity` applied. */ - readonly style: CSSProperties; - /** Sum of every effect's relative layer contribution. */ - readonly layer: number; -} - -/** - * Collapse independent effect layers, ordered from the outermost inherited - * frame to the most local one, into a single style. - * - * Transforms are concatenated and opacities multiplied, including `transform` - * and numeric `opacity` supplied through `effect.style`. Every other `style` - * property uses local-last precedence over `base` and earlier effects. - */ -export function composeEffects( - effects: readonly OriginEffect[], - base: CSSProperties = {}, -): ComposedEffects { - const style: CSSProperties = { ...base }; - const transforms: string[] = []; - let opacity = 1; - let layer = 0; - - for (const effect of effects) { - if (isComposable(effect.transform)) transforms.push(effect.transform); - if (effect.opacity !== undefined) opacity *= effect.opacity; - layer += effect.layer ?? 0; - - if (effect.style) { - Object.assign(style, effect.style); - if (isComposable(effect.style.transform)) - transforms.push(effect.style.transform); - if (effect.style.opacity !== undefined) { - const styleOpacity = Number(effect.style.opacity); - if (Number.isFinite(styleOpacity)) opacity *= styleOpacity; - } - } - } - - // Assign the composed properties last so no single effect's `style` can - // replace transform work inherited from other frames. - style.transform = transforms.length ? transforms.join(" ") : "none"; - style.opacity = String(clamp01(opacity)); - return { style, layer }; -} diff --git a/packages/core-v2/src/choreography/define.ts b/packages/core-v2/src/choreography/define.ts deleted file mode 100644 index 2d8c014..0000000 --- a/packages/core-v2/src/choreography/define.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { OriginChoreography } from "./types"; - -/** - * Preserve type inference while declaring a custom choreography. - * - * The helper performs no runtime transformation. It gives custom routines a - * named, documented construction point and validates their shape in TypeScript. - * - * @param choreography - Side-effect-free visual effect calculator and optional - * release thresholds. - * @returns The same choreography object. - */ -export function defineOriginChoreography( - choreography: OriginChoreography, -): OriginChoreography { - return choreography; -} - -/** Distinguish a choreography from a plain options object. */ -export function isChoreography(value: unknown): value is OriginChoreography { - return ( - typeof value === "object" && - value !== null && - "effects" in value && - typeof value.effects === "function" - ); -} diff --git a/packages/core-v2/src/choreography/presets.ts b/packages/core-v2/src/choreography/presets.ts deleted file mode 100644 index 0cab8b4..0000000 --- a/packages/core-v2/src/choreography/presets.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { defineOriginChoreography } from "./define"; - -const percent = (value: number) => `${(value * 100).toFixed(4)}%`; - -/** - * Native-style forward motion: the target enters above the source while the - * source recedes slightly. These presets are examples; applications can - * replace them with arbitrary `defineOriginChoreography()` callbacks. - */ -export const slideLeft = defineOriginChoreography({ - name: "slide-left", - commitThreshold: 0.36, - commitVelocity: 0.6, - effects: ({ progress }) => ({ - source: { - transform: `translate3d(${percent(progress * -0.24)}, 0, 0) scale(${1 - progress * 0.025})`, - opacity: 1 - progress * 0.16, - }, - target: { - transform: `translate3d(${percent(1 - progress)}, 0, 0)`, - }, - }), -}); - -/** - * Back motion reveals the target underneath the source. This is merely a - * visual routine; the `back()` action selects retained-history behavior. - */ -export const slideRight = defineOriginChoreography({ - name: "slide-right", - commitThreshold: 0.36, - commitVelocity: 0.6, - effects: ({ progress }) => ({ - source: { - transform: `translate3d(${percent(progress)}, 0, 0)`, - }, - target: { - transform: `translate3d(${percent(-0.24 + progress * 0.24)}, 0, 0) scale(${0.975 + progress * 0.025})`, - opacity: 0.84 + progress * 0.16, - }, - }), -}); - -/** - * Cross-fade preset that fades the source out while fading the target in. - * - * The default release thresholds from the scene are used. - */ -export const fade = defineOriginChoreography({ - name: "fade", - effects: ({ progress }) => ({ - source: { opacity: 1 - progress }, - target: { opacity: progress }, - }), -}); diff --git a/packages/core-v2/src/choreography/types.ts b/packages/core-v2/src/choreography/types.ts deleted file mode 100644 index 30be007..0000000 --- a/packages/core-v2/src/choreography/types.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type { CSSProperties } from "vue"; -import type { OriginRect } from "../shared/geometry"; - -/** - * The lifecycle phase of an operation edge. - * - * - `preparing`: the target has been added and Vue is mounting it. - * - `interactive`: progress may be controlled by a gesture or application. - * - `settling`: the commit/cancel decision is fixed and the spring is running. - * - `finished`: the graph rewrite or cancellation cleanup has completed. - */ -export type OriginOperationPhase = - "preparing" | "interactive" | "settling" | "finished"; - -/** - * The outcome selected for an operation. - * - * Intent remains `undecided` until `finish()` is called. It becomes final - * before the settling animation completes, allowing the retained target to - * originate its own operation immediately. - */ -export type OriginOperationIntent = "undecided" | "commit" | "cancel"; - -/** - * One composable contribution to a scene node's final visual style. - * - * Transforms are concatenated in origin-to-descendant order and opacity values - * are multiplied. This lets X→Y and Y→Z affect Y simultaneously without either - * routine replacing the other routine's CSS transform. - */ -export interface OriginEffect { - /** - * A CSS transform contribution. Transform strings from inherited and local - * origin frames are concatenated rather than replacing one another. - */ - transform?: string; - /** - * An opacity contribution between `0` and `1`. Contributions from multiple - * frames are multiplied and the final value is clamped. - */ - opacity?: number; - /** - * Relative stacking contribution. `above()` defaults the target to +1 and - * `under()` defaults it to -1. - */ - layer?: number; - /** - * Escape hatch for non-geometric effects such as filter, clipPath, or - * borderRadius. Later/local effects override inherited properties. - */ - style?: CSSProperties; -} - -/** - * Independent visual contributions calculated for one operation edge. - * - * Any omitted contribution is treated as an identity effect. - */ -export interface OriginEffectSet { - /** - * Applied to both source and target. This is useful for moving an entire - * origin-relative coordinate frame. - */ - frame?: OriginEffect; - /** Applied to the component that initiated this operation. */ - source?: OriginEffect; - /** Applied to the component created by this operation and its descendants. */ - target?: OriginEffect; -} - -/** Values supplied whenever a choreography calculates its visual effects. */ -export interface OriginChoreographyContext { - /** Normalized operation progress, clamped to the inclusive range `0..1`. */ - progress: number; - /** - * Normalized progress units per second. Positive velocity moves toward - * commit; negative velocity moves back toward cancellation. - */ - velocity: number; - /** Current lifecycle phase of the operation. */ - phase: OriginOperationPhase; - /** Commit/cancel outcome, if release has already selected one. */ - intent: OriginOperationIntent; - /** Origin host bounds captured immediately before the target mounts. */ - originRect?: OriginRect; - /** Target host bounds measured after Vue mounts the target. */ - targetRect?: OriginRect; - /** Scene-container bounds, or the browser viewport when no container exists. */ - viewport: OriginRect; -} - -/** - * A choreography describes visual relationships only. Component creation, - * history, and cleanup are performed by the scene operation that uses it. - */ -export interface OriginChoreography { - /** Optional diagnostic name surfaced by scene inspectors and devtools. */ - readonly name?: string; - /** - * Calculate source, target, and shared-frame contributions for the current - * operation state. This function should be deterministic and side-effect - * free because it can run many times per animation frame. - */ - effects(context: OriginChoreographyContext): OriginEffectSet; - /** - * Gesture progress required to retain the target after release. - * - * @defaultValue `0.36` - */ - readonly commitThreshold?: number; - /** - * Reserved: normalized positive release velocity intended to commit a - * deliberate flick. - * - * 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. - */ - readonly commitVelocity?: number; - /** - * Keep this choreography's progress-`1` effects connected after a committed - * push instead of collapsing the edge and hiding its source. - * - * This is intended for partial presentations such as drawers and inspectors - * where part of the retained source remains visible beside the target. The - * source remains mounted, visible, and inert. A back operation temporarily - * suspends the resting edge; cancelling back restores it. - * - * Only retained-history `push` actions support connected resting effects. - * - * @defaultValue `false` - */ - readonly persistAtRest?: boolean; -} diff --git a/packages/core-v2/src/components/OriginGesture.vue b/packages/core-v2/src/components/OriginGesture.vue index e716ba6..0689f53 100644 --- a/packages/core-v2/src/components/OriginGesture.vue +++ b/packages/core-v2/src/components/OriginGesture.vue @@ -1,6 +1,6 @@