diff --git a/README.md b/README.md index df26843..024c392 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,19 @@ npm install --save cereb import { pinch } from "cereb"; import { zoom } from "cereb/operators"; +let scale = 1.0; +const MIN_SCALE = 0.5, MAX_SCALE = 3.0; + // pipe creates a pipeline where signals flow through operators // Each operator extends the signal (signals are immutable) pinch(element) - // Operator: Determine scale value. - .pipe(zoom({ minScale: 0.5, maxScale: 3.0 })).on((signal) => { - // The scale property is extended from the value. - // - pinch emits distance → zoom calculates scale - // - zoom also works with other inputs (keyboard, wheel, etc.) - element.style.transform = `scale(${signal.value.scale})`; + // Operator: Convert ratio to scale delta + .pipe(zoom()) + .on((signal) => { + // zoom outputs frame-by-frame delta, accumulate and clamp + scale += signal.value.scale; + scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale)); + element.style.transform = `scale(${scale})`; }); ``` diff --git a/docs/public/llms-full.txt b/docs/public/llms-full.txt index 9587574..6569cce 100644 --- a/docs/public/llms-full.txt +++ b/docs/public/llms-full.txt @@ -501,21 +501,78 @@ singlePointer(canvas) ### zoom -Calculate bounded scale from ratio input. +Convert ratio input to frame-by-frame scale delta. Consumer accumulates and clamps. ```typescript -function zoom>(options?: { - minScale?: number; - maxScale?: number; - baseScale?: number | (() => number); - mode?: "multiply" | "add"; -}): Operator +function zoom>( + options?: ZoomOptions +): Operator ``` +**Output:** +- `scale`: Frame-by-frame scale delta (not absolute scale) +- `deltaScale`: Same as scale (deprecated) + ```typescript +let scale = 1.0; +const MIN_SCALE = 0.5, MAX_SCALE = 3.0; + pinch(element) - .pipe(zoom({ minScale: 0.5, maxScale: 3.0 })) - .on((s) => element.style.transform = `scale(${s.value.scale})`); + .pipe(zoom()) + .on((s) => { + scale += s.value.scale; + scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale)); + element.style.transform = `scale(${scale})`; + }); +``` + +### rotate3d + +Convert 2D pan to 3D rotation delta. Horizontal pan → Y-axis rotation, vertical pan → X-axis rotation. + +```typescript +function rotate3d>(options?: { + sensitivityX?: number; // default 1.0 + sensitivityY?: number; // default 1.0 + invertX?: boolean; // default false + invertY?: boolean; // default false +}): Operator +``` + +**Output:** +- `rotation`: Frame-by-frame rotation delta in radians `[rx, ry, rz]` + +```typescript +let rotation = [0, 0, 0]; + +pan(element) + .pipe(rotate3d({ sensitivityX: 0.5, sensitivityY: 0.5 })) + .on((s) => { + const [drx, dry, drz] = s.value.rotation; + rotation[0] += drx; + rotation[1] += dry; + element.style.transform = `rotateX(${rotation[0]}rad) rotateY(${rotation[1]}rad)`; + }); +``` + +### translate + +Convert pan delta to 2D translation coordinates. + +```typescript +function translate>(options?: { + baseTranslate?: [number, number] | (() => [number, number]); // default [0, 0] + sensitivity?: number; // default 1.0 +}): Operator +``` + +```typescript +pan(element) + .pipe(translate()) + .on((s) => { + const [x, y] = s.value.translate; + element.style.transform = `translate(${x}px, ${y}px)`; + }); ``` ### when @@ -650,38 +707,41 @@ singlePointer(element) import { wheel, keyheld, keydown, pinch } from "cereb"; import { when, extend, spy, zoom } from "cereb/operators"; -let currentScale = 1; -const zoomOp = () => zoom({ minScale: 0.5, maxScale: 3.0, baseScale: () => currentScale }); +let scale = 1; +const MIN_SCALE = 0.5, MAX_SCALE = 3.0; +const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); const zoomMode$ = keyheld(window, { code: "KeyZ" }) .pipe(extend((s) => ({ opened: s.value.held }))); -// Pinch zoom -pinch(element).pipe(zoomOp()).on(apply); +// Pinch zoom - uses delta-based zoom operator +pinch(element) + .pipe(zoom()) + .on((s) => { + scale = clamp(scale + s.value.scale, MIN_SCALE, MAX_SCALE); + element.style.transform = `scale(${scale})`; + }); -// Z + wheel zoom +// Z + wheel zoom - compute scale directly wheel(element, { passive: false }) .pipe( when(zoomMode$), - spy((s) => s.value.originalEvent.preventDefault()), - extend((s) => ({ ratio: Math.exp(-s.value.deltaY * 0.005) })), - zoomOp() + spy((s) => s.value.originalEvent.preventDefault()) ) - .on(apply); + .on((s) => { + const multiplier = Math.exp(-s.value.deltaY * 0.005); + scale = clamp(scale * multiplier, MIN_SCALE, MAX_SCALE); + element.style.transform = `scale(${scale})`; + }); -// Z + +/- keyboard zoom +// Z + +/- keyboard zoom - compute scale directly keydown(window, { code: ["Equal", "Minus"] }) - .pipe( - when(zoomMode$), - extend((s) => ({ ratio: s.value.code === "Equal" ? 1.2 : 1 / 1.2 })), - zoomOp() - ) - .on(apply); - -function apply(signal) { - currentScale = signal.value.scale; - element.style.transform = `scale(${currentScale})`; -} + .pipe(when(zoomMode$)) + .on((s) => { + const multiplier = s.value.code === "Equal" ? 1.2 : 1 / 1.2; + scale = clamp(scale * multiplier, MIN_SCALE, MAX_SCALE); + element.style.transform = `scale(${scale})`; + }); ``` ### Drawing Application diff --git a/docs/public/llms.txt b/docs/public/llms.txt index 2193f9f..201fdca 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -37,7 +37,9 @@ - [extend](https://cereb.dev/operator-api/extend): Add properties to signal value - [session](https://cereb.dev/operator-api/session): Group start-to-end as session - [offset](https://cereb.dev/operator-api/offset): Element-relative coordinates -- [zoom](https://cereb.dev/operator-api/zoom): Bounded scale calculation +- [zoom](https://cereb.dev/operator-api/zoom): Convert ratio to scale delta +- [rotate3d](https://cereb.dev/operator-api/rotate3d): Convert pan to 3D rotation delta +- [translate](https://cereb.dev/operator-api/translate): Convert pan to 2D translation - [when](https://cereb.dev/operator-api/when): Gate by another stream's state - [throttle](https://cereb.dev/operator-api/throttle): Rate limiting - [debounce](https://cereb.dev/operator-api/debounce): Wait for silence diff --git a/docs/src/components/examples/space-adventure.astro b/docs/src/components/examples/space-adventure.astro index 68d2b01..28cabf4 100644 --- a/docs/src/components/examples/space-adventure.astro +++ b/docs/src/components/examples/space-adventure.astro @@ -8,11 +8,13 @@ import "./space-adventure.css";
- +
+ +
Zoom Mode
Tip. - Pinch, double tap, or change slider to zoom.
+ Drag to rotate. Pinch or double tap to zoom.
In Desktop, use 'z' + '+/-' or 'wheel' to zoom.
@@ -32,6 +34,10 @@ import "./space-adventure.css"; SCALE 1.00
+
+ ROTATION + 0, 0 +
INPUT - @@ -41,8 +47,8 @@ import "./space-adventure.css";