@mindees/core 0.22.2 → 0.22.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"animation.d.ts","names":[],"sources":["../../src/animation/animation.ts"],"mappings":";;;;KAiBY,WAAA,IAAe,IAA6B,GAAtB,KAAA;;UAGjB,eAAA;EAQA;EANf,IAAA;;WAES,IAAA,EAAM,OAAO;AAAA;;UAIP,aAAA;EAAA;EAOX;EAJJ,GAAA,CAAI,CAAA;EAkCU;EAAA,SAhCL,QAAA;;EAET,IAAA;AAAA;AAyDF;AAAA,iBA3BgB,cAAA,CAAe,GAAuB,EAAlB,WAAW;;iBA2B/B,cAAA,IAAkB,WAAW;AAAA;AAAA,iBA4D7B,OAAA,CAAQ,OAAA,WAAkB,aAAa;;iBAyFvC,MAAA,CACd,EAAA,EAAI,aAAA,EACJ,IAAA;EAAA,SACW,EAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA,GAAS,MAAA;EAAA,SACT,KAAA;EAAA,SACA,UAAA,IAAc,QAAA;AAAA,IAExB,eAAA;;iBA+Ba,MAAA,CACd,EAAA,EAAI,aAAA,EACJ,IAAA;EAAA,SACW,EAAA;EAAA,SACA,SAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,QAAA;EAAA,SACA,SAAA;EAAA,SACA,YAAA;EAAA,SACA,UAAA,IAAc,QAAA;AAAA,IAExB,eAAe;;;;;;iBAiDF,WAAA,CACd,KAAA,gBACA,UAAA,qBACA,WAAA,qBACA,IAAA;EAAA,SAAkB,WAAA;AAAA;;iBAgCJ,cAAA,IAAkB,WAAW;;iBAqB7B,iBAAA;EAAuB,MAAA,EAAQ,WAAW;EAAE,IAAA,GAAO,KAAA;AAAA;;iBAcnD,qBAAA;;iBAKA,eAAA"}
1
+ {"version":3,"file":"animation.d.ts","names":[],"sources":["../../src/animation/animation.ts"],"mappings":";;;;KAiBY,WAAA,IAAe,IAA6B,GAAtB,KAAA;;UAGjB,eAAA;EAQA;EANf,IAAA;;WAES,IAAA,EAAM,OAAO;AAAA;;UAIP,aAAA;EAAA;EAOX;EAJJ,GAAA,CAAI,CAAA;EAkCU;EAAA,SAhCL,QAAA;;EAET,IAAA;AAAA;AAyDF;AAAA,iBA3BgB,cAAA,CAAe,GAAuB,EAAlB,WAAW;;iBA2B/B,cAAA,IAAkB,WAAW;AAAA;AAAA,iBA4D7B,OAAA,CAAQ,OAAA,WAAkB,aAAa;;iBAyFvC,MAAA,CACd,EAAA,EAAI,aAAA,EACJ,IAAA;EAAA,SACW,EAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA,GAAS,MAAA;EAAA,SACT,KAAA;EAAA,SACA,UAAA,IAAc,QAAA;AAAA,IAExB,eAAA;;iBA+Ba,MAAA,CACd,EAAA,EAAI,aAAA,EACJ,IAAA;EAAA,SACW,EAAA;EAAA,SACA,SAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,QAAA;EAAA,SACA,SAAA;EAAA,SACA,YAAA;EAAA,SACA,UAAA,IAAc,QAAA;AAAA,IAExB,eAAe;;;;;;iBAiDF,WAAA,CACd,KAAA,gBACA,UAAA,qBACA,WAAA,qBACA,IAAA;EAAA,SAAkB,WAAA;AAAA;;iBAqCJ,cAAA,IAAkB,WAAW;;iBAqB7B,iBAAA;EAAuB,MAAA,EAAQ,WAAW;EAAE,IAAA,GAAO,KAAA;AAAA;;iBAcnD,qBAAA;;iBAKA,eAAA"}
@@ -247,7 +247,12 @@ function interpolate(value, inputRange, outputRange, opts) {
247
247
  return y0 + (y1 - y0) * (x - x0) / (x1 - x0);
248
248
  };
249
249
  if (x <= inputRange[0]) return extrapolate === "extend" ? lerp(0) : outputRange[0];
250
- if (x >= inputRange[n - 1]) return extrapolate === "extend" ? lerp(n - 2) : outputRange[n - 1];
250
+ if (x >= inputRange[n - 1]) {
251
+ if (extrapolate !== "extend") return outputRange[n - 1];
252
+ let i = n - 2;
253
+ while (i > 0 && inputRange[i] === inputRange[i + 1]) i--;
254
+ return inputRange[i] === inputRange[i + 1] ? outputRange[n - 1] : lerp(i);
255
+ }
251
256
  for (let i = 0; i < n - 1; i++) if (x >= inputRange[i] && x <= inputRange[i + 1]) return lerp(i);
252
257
  return outputRange[n - 1];
253
258
  };
@@ -1 +1 @@
1
- {"version":3,"file":"animation.js","names":[],"sources":["../../src/animation/animation.ts"],"sourcesContent":["/**\n * The animation engine — RN `Animated`/Reanimated + Flutter `AnimationController` parity, built\n * entirely on the reactive core. An {@link AnimatedValue} **is a signal**, so reading it inside a\n * `style` accessor re-renders only that node (no renderer surface). One injected {@link FrameSource}\n * (mirroring `setReactiveScheduler`) drives a single loop that ticks every active driver inside one\n * `batch()` per frame — so a style reading several animated values recomputes once (glitch-free).\n *\n * With no frame source (SSR / headless / tests until one is wired) animations **jump to their final\n * value** synchronously: deterministic, never a hang, server output shows the end state.\n *\n * @module\n */\n\nimport { batch, getOwner, onCleanup, type Signal, signal, untrack } from '../reactive'\nimport { type Easing, linear } from './easing'\n\n/** A frame source: subscribe with a per-frame `tick(nowMs)`, get back an unsubscribe. (`requestAnimationFrame` on web, vsync on native, a manual ticker in tests.) */\nexport type FrameSource = (tick: (nowMs: number) => void) => () => void\n\n/** A running animation: stop it, or await natural completion. */\nexport interface AnimationHandle {\n /** Stop now (keeps the current value); `done` resolves `false`. Idempotent. */\n stop(): void\n /** Resolves `true` on natural completion, `false` if interrupted/stopped. Settles exactly once. */\n readonly done: Promise<boolean>\n}\n\n/** A reactive, animatable number. Call to read (tracks); `.set` jumps (untracked, stops any driver). */\nexport interface AnimatedValue {\n (): number\n /** Jump to `v` immediately, cancelling any running driver. */\n set(v: number): void\n /** The current per-frame velocity (units/sec) — seeds spring-interrupts-spring. */\n readonly velocity: () => number\n /** Stop any running driver, keeping the current value. */\n stop(): void\n}\n\ninterface Driver {\n readonly av: AnimatedValue\n /** Target value, used to jump-to-final if the frame source detaches mid-flight. */\n readonly target: number\n /** Advance by this frame; return `true` while still running. */\n tick(nowMs: number, dt: number): boolean\n /** Resolve the handle + fire onComplete exactly once. */\n settle(finished: boolean): void\n}\n\ninterface Internal {\n readonly signal: Signal<number>\n readonly vel: { v: number }\n driver: Driver | null\n}\n\nconst MAX_DT = 0.064 // clamp huge gaps (backgrounded tab / GC / breakpoint) so springs can't explode\nconst SPRING_MAX_FRAMES = 600 // ~10s @60fps: a non-converging spring fails safely instead of forever\nconst DEFAULT_DURATION = 250 // matches Atlas tokens.duration.standard (kept literal to avoid an atlas dep)\n\nconst internals = new WeakMap<AnimatedValue, Internal>()\nconst active = new Set<Driver>()\nlet frameSource: FrameSource | null = null\nlet unsubscribe: (() => void) | null = null\nlet lastNow = -1 // -1 = uninitialized (a real frame can arrive at now=0)\n\n/** Inject (or clear) the frame source that drives animations. `null` (default) → jump-to-final. */\nexport function setFrameSource(src: FrameSource | null): void {\n if (src === frameSource) return\n // Detach the current loop FIRST so any change — including a non-null→non-null swap — can never\n // leak the old subscription (which would keep driving) nor block resubscribing under the new one.\n if (unsubscribe) {\n unsubscribe()\n unsubscribe = null\n }\n frameSource = src\n if (src === null) {\n // Detaching entirely: flush every active driver to its final value so nothing is left frozen\n // (symmetric with the start-time SSR fallback).\n batch(() => {\n for (const d of [...active]) {\n writeValue(d.av, d.target)\n active.delete(d)\n const st = internals.get(d.av)\n if (st) st.driver = null\n d.settle(true)\n }\n })\n } else if (active.size > 0) {\n ensureLoop() // resubscribe in-flight animations under the new source\n }\n}\n\n/** The current frame source, or `null`. */\nexport function getFrameSource(): FrameSource | null {\n return frameSource\n}\n\nfunction writeValue(av: AnimatedValue, v: number): void {\n untrack(() => internals.get(av)?.signal.set(v))\n}\n\nfunction maybeSleep(): void {\n if (active.size === 0 && unsubscribe) {\n unsubscribe()\n unsubscribe = null\n }\n}\n\nfunction ensureLoop(): void {\n if (unsubscribe === null && frameSource !== null) {\n lastNow = -1\n unsubscribe = frameSource(onFrame)\n }\n}\n\nfunction onFrame(now: number): void {\n if (lastNow < 0) lastNow = now // first frame establishes the baseline (dt = 0, no jump)\n // Clamp to [0, MAX_DT]: never integrate backward on a non-monotonic timestamp, never explode on a\n // huge gap (backgrounded tab / GC / breakpoint).\n const dt = Math.min(Math.max(0, (now - lastNow) / 1000), MAX_DT)\n // One batch per frame: every driver's write coalesces into a single flush, so a style reading\n // multiple animated values recomputes exactly once (glitch-free).\n batch(() => {\n for (const d of [...active]) {\n // A sibling's onComplete this frame may have stopped this driver — don't tick a removed one\n // (would violate stop()'s \"keeps current value\" contract with an extra write).\n if (!active.has(d)) continue\n let running: boolean\n try {\n running = d.tick(now, dt)\n } catch {\n running = false // isolate a throwing driver (mirrors flushEffects' error isolation)\n }\n if (!running) {\n active.delete(d)\n const st = internals.get(d.av)\n if (st) st.driver = null\n // Isolate a throwing onComplete: settle() invokes the user's onComplete, and this runs on the\n // SHARED frame loop — one bad callback must not propagate out and leave the rAF chain un-rearmed\n // (which would freeze EVERY animation in the app).\n try {\n d.settle(true)\n } catch {\n // swallow: a faulty completion callback can't be allowed to kill the loop\n }\n }\n }\n })\n lastNow = now\n maybeSleep()\n}\n\n/** Create an {@link AnimatedValue} (a reactive number you can drive with {@link timing}/{@link spring}). */\nexport function animate(initial: number): AnimatedValue {\n const s = signal(initial)\n const state: Internal = { signal: s, vel: { v: 0 }, driver: null }\n const av: AnimatedValue = Object.assign(() => s(), {\n set(v: number): void {\n stopDriver(av) // a manual jump cancels any running animation (RN setValue semantics)\n untrack(() => s.set(v))\n },\n velocity: () => state.vel.v,\n stop(): void {\n stopDriver(av)\n },\n })\n internals.set(av, state)\n return av\n}\n\nfunction stopDriver(av: AnimatedValue): void {\n const st = internals.get(av)\n if (st?.driver) {\n const d = st.driver\n active.delete(d)\n st.driver = null\n d.settle(false)\n maybeSleep()\n }\n}\n\n/** Options shared by drivers. */\ninterface DriverOptions {\n readonly to: number\n readonly onComplete?: (finished: boolean) => void\n}\n\n/**\n * Begin a driver on `av`: settle any prior driver (last-write-wins), capture the current value as\n * the start, and either jump-to-final (no frame source) or join the loop. Returns the handle.\n */\nfunction start(\n av: AnimatedValue,\n opts: DriverOptions,\n build: (from: number, settle: (finished: boolean) => void) => Driver['tick'],\n): AnimationHandle {\n const st = internals.get(av)\n if (!st) throw new TypeError('animation driver: value was not created with animate()')\n stopDriver(av) // last-write-wins: at most one driver per value\n\n let settled = false\n let resolveDone!: (finished: boolean) => void\n const done = new Promise<boolean>((r) => {\n resolveDone = r\n })\n const settle = (finished: boolean): void => {\n if (settled) return\n settled = true\n resolveDone(finished)\n opts.onComplete?.(finished)\n }\n\n const from = untrack(av) // start from the CURRENT rendered value (continuous retarget)\n\n // No frame source → jump to the final value synchronously (SSR / headless / not-yet-wired).\n if (frameSource === null) {\n writeValue(av, opts.to)\n settle(true)\n return { stop: () => settle(false), done }\n }\n\n const tick = build(from, settle)\n const driver: Driver = { av, target: opts.to, tick, settle }\n st.driver = driver\n active.add(driver)\n\n // Auto-stop when the owner that started the animation is disposed (unmount), so the loop never\n // writes a dead signal and never leaks a frame subscription.\n if (getOwner() !== null) {\n onCleanup(() => {\n if (st.driver === driver) stopDriver(av)\n })\n }\n\n ensureLoop()\n return {\n stop: () => stopDriver(av),\n done,\n }\n}\n\n/** Animate `av` to `to` over `duration` ms with `easing` (RN `Animated.timing`). */\nexport function timing(\n av: AnimatedValue,\n opts: {\n readonly to: number\n readonly duration?: number\n readonly easing?: Easing\n readonly delay?: number\n readonly onComplete?: (finished: boolean) => void\n },\n): AnimationHandle {\n // Sanitize: `??` only catches null/undefined, so a NaN/Infinity duration would write NaN forever\n // (permanent under Object.is) — fall back to the default for any non-finite duration.\n const duration = Number.isFinite(opts.duration) ? (opts.duration as number) : DEFAULT_DURATION\n const easing = opts.easing ?? linear\n const delay = Number.isFinite(opts.delay) ? (opts.delay as number) : 0\n return start(av, opts, (from, _settle) => {\n let startTime = -1 // -1 = uninitialized (a real frame can arrive at now=0)\n let prev = from\n const settleAt = (): boolean => {\n internals.get(av)!.vel.v = 0 // at rest: velocity is 0 (so a following spring doesn't inherit phantom momentum)\n writeValue(av, opts.to)\n return false\n }\n return (now, dt) => {\n if (startTime < 0) startTime = now\n const elapsed = now - startTime - delay\n if (elapsed < 0) return true // still in the delay window\n const t = duration <= 0 ? 1 : Math.min(elapsed / duration, 1)\n const next = from + (opts.to - from) * easing(t)\n if (!Number.isFinite(next)) return settleAt() // defensive: never write NaN/Infinity\n if (dt > 0) internals.get(av)!.vel.v = (next - prev) / dt\n prev = next\n if (t >= 1) return settleAt()\n writeValue(av, next)\n return true\n }\n })\n}\n\n/** Animate `av` to `to` with spring physics (RN/Reanimated `withSpring`, Flutter `SpringSimulation`). */\nexport function spring(\n av: AnimatedValue,\n opts: {\n readonly to: number\n readonly stiffness?: number\n readonly damping?: number\n readonly mass?: number\n readonly velocity?: number\n readonly restDelta?: number\n readonly restVelocity?: number\n readonly onComplete?: (finished: boolean) => void\n },\n): AnimationHandle {\n const stiffness = Math.max(0, opts.stiffness ?? 170)\n const damping = Math.max(0, opts.damping ?? 26)\n const mass = Math.max(1e-4, opts.mass ?? 1)\n const restDelta = opts.restDelta ?? 0.01\n const restVelocity = opts.restVelocity ?? 0.01\n const omega = Math.sqrt(stiffness / mass) // natural frequency, for substep stability\n return start(av, opts, (from) => {\n let x = from\n let v = opts.velocity ?? internals.get(av)!.vel.v\n let frames = 0\n return (_now, dt) => {\n frames++\n // Semi-implicit (symplectic) Euler is only conditionally stable (omega·dt < ~2). Stiffness is\n // user-controlled, so SUBSTEP the frame's dt to keep each step well inside the stable region —\n // a stiff spring stays finite instead of diverging to Infinity/NaN.\n const steps = Math.max(1, Math.ceil((dt * omega) / 1.5))\n const sub = dt / steps\n for (let i = 0; i < steps; i++) {\n const a = (-stiffness * (x - opts.to) - damping * v) / mass\n v += a * sub\n x += v * sub\n }\n if (!Number.isFinite(x)) {\n // Defensive: never write NaN/Infinity (permanent under Object.is) — snap to the target.\n internals.get(av)!.vel.v = 0\n writeValue(av, opts.to)\n return false\n }\n internals.get(av)!.vel.v = v\n if (\n (Math.abs(x - opts.to) < restDelta && Math.abs(v) < restVelocity) ||\n frames > SPRING_MAX_FRAMES\n ) {\n internals.get(av)!.vel.v = 0\n writeValue(av, opts.to)\n return false\n }\n writeValue(av, x)\n return true\n }\n })\n}\n\n/**\n * Map an accessor through a piecewise-linear range (RN `Animated.interpolate`). Returns a plain\n * accessor, so it tracks `value` and re-reads inside the consuming style each frame — glitch-free\n * for free. `inputRange` must be monotonically increasing and match `outputRange` length (≥2).\n */\nexport function interpolate(\n value: () => number,\n inputRange: readonly number[],\n outputRange: readonly number[],\n opts?: { readonly extrapolate?: 'clamp' | 'extend' },\n): () => number {\n if (inputRange.length !== outputRange.length || inputRange.length < 2) {\n throw new RangeError('interpolate: inputRange and outputRange must be the same length (>= 2)')\n }\n const extrapolate = opts?.extrapolate ?? 'clamp'\n const n = inputRange.length\n return () => {\n const x = value()\n if (Number.isNaN(x)) return outputRange[0] as number // a NaN source maps to the first output, not the last\n const lerp = (i: number): number => {\n const x0 = inputRange[i] as number\n const x1 = inputRange[i + 1] as number\n const y0 = outputRange[i] as number\n const y1 = outputRange[i + 1] as number\n if (x1 === x0) return y0 // zero-width segment → avoid divide-by-zero\n return y0 + ((y1 - y0) * (x - x0)) / (x1 - x0)\n }\n if (x <= (inputRange[0] as number)) {\n return extrapolate === 'extend' ? lerp(0) : (outputRange[0] as number)\n }\n if (x >= (inputRange[n - 1] as number)) {\n return extrapolate === 'extend' ? lerp(n - 2) : (outputRange[n - 1] as number)\n }\n for (let i = 0; i < n - 1; i++) {\n if (x >= (inputRange[i] as number) && x <= (inputRange[i + 1] as number)) return lerp(i)\n }\n return outputRange[n - 1] as number // unreachable for monotonic input\n }\n}\n\n/** A `requestAnimationFrame`-backed {@link FrameSource} for the web (the host wires this at startup). */\nexport function rafFrameSource(): FrameSource {\n return (tick) => {\n const raf = (globalThis as { requestAnimationFrame?: (cb: (t: number) => void) => number })\n .requestAnimationFrame\n const caf = (globalThis as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame\n if (!raf) return () => {} // no rAF (non-browser) → caller already degrades to jump-to-final\n let id = raf(function loop(t: number) {\n // Always re-arm the chain, even if `tick` throws — a single bad frame must never permanently\n // freeze the shared loop. (onFrame already isolates driver ticks + completion callbacks; this is\n // belt-and-suspenders for any other thrown error.)\n try {\n tick(t)\n } finally {\n id = raf(loop)\n }\n })\n return () => caf?.(id)\n }\n}\n\n/** A manually-driven {@link FrameSource} for deterministic tests: call `tick(nowMs)` to advance. */\nexport function manualFrameSource(): { source: FrameSource; tick: (nowMs: number) => void } {\n let cb: ((nowMs: number) => void) | null = null\n return {\n source: (t) => {\n cb = t\n return () => {\n cb = null\n }\n },\n tick: (nowMs) => cb?.(nowMs),\n }\n}\n\n/** @internal Test-only: number of running animations. */\nexport function _activeAnimationCount(): number {\n return active.size\n}\n\n/** @internal Test-only: reset all engine state between tests. */\nexport function _resetAnimation(): void {\n if (unsubscribe) unsubscribe()\n unsubscribe = null\n active.clear()\n frameSource = null\n lastNow = -1\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"animation.js","names":[],"sources":["../../src/animation/animation.ts"],"sourcesContent":["/**\n * The animation engine — RN `Animated`/Reanimated + Flutter `AnimationController` parity, built\n * entirely on the reactive core. An {@link AnimatedValue} **is a signal**, so reading it inside a\n * `style` accessor re-renders only that node (no renderer surface). One injected {@link FrameSource}\n * (mirroring `setReactiveScheduler`) drives a single loop that ticks every active driver inside one\n * `batch()` per frame — so a style reading several animated values recomputes once (glitch-free).\n *\n * With no frame source (SSR / headless / tests until one is wired) animations **jump to their final\n * value** synchronously: deterministic, never a hang, server output shows the end state.\n *\n * @module\n */\n\nimport { batch, getOwner, onCleanup, type Signal, signal, untrack } from '../reactive'\nimport { type Easing, linear } from './easing'\n\n/** A frame source: subscribe with a per-frame `tick(nowMs)`, get back an unsubscribe. (`requestAnimationFrame` on web, vsync on native, a manual ticker in tests.) */\nexport type FrameSource = (tick: (nowMs: number) => void) => () => void\n\n/** A running animation: stop it, or await natural completion. */\nexport interface AnimationHandle {\n /** Stop now (keeps the current value); `done` resolves `false`. Idempotent. */\n stop(): void\n /** Resolves `true` on natural completion, `false` if interrupted/stopped. Settles exactly once. */\n readonly done: Promise<boolean>\n}\n\n/** A reactive, animatable number. Call to read (tracks); `.set` jumps (untracked, stops any driver). */\nexport interface AnimatedValue {\n (): number\n /** Jump to `v` immediately, cancelling any running driver. */\n set(v: number): void\n /** The current per-frame velocity (units/sec) — seeds spring-interrupts-spring. */\n readonly velocity: () => number\n /** Stop any running driver, keeping the current value. */\n stop(): void\n}\n\ninterface Driver {\n readonly av: AnimatedValue\n /** Target value, used to jump-to-final if the frame source detaches mid-flight. */\n readonly target: number\n /** Advance by this frame; return `true` while still running. */\n tick(nowMs: number, dt: number): boolean\n /** Resolve the handle + fire onComplete exactly once. */\n settle(finished: boolean): void\n}\n\ninterface Internal {\n readonly signal: Signal<number>\n readonly vel: { v: number }\n driver: Driver | null\n}\n\nconst MAX_DT = 0.064 // clamp huge gaps (backgrounded tab / GC / breakpoint) so springs can't explode\nconst SPRING_MAX_FRAMES = 600 // ~10s @60fps: a non-converging spring fails safely instead of forever\nconst DEFAULT_DURATION = 250 // matches Atlas tokens.duration.standard (kept literal to avoid an atlas dep)\n\nconst internals = new WeakMap<AnimatedValue, Internal>()\nconst active = new Set<Driver>()\nlet frameSource: FrameSource | null = null\nlet unsubscribe: (() => void) | null = null\nlet lastNow = -1 // -1 = uninitialized (a real frame can arrive at now=0)\n\n/** Inject (or clear) the frame source that drives animations. `null` (default) → jump-to-final. */\nexport function setFrameSource(src: FrameSource | null): void {\n if (src === frameSource) return\n // Detach the current loop FIRST so any change — including a non-null→non-null swap — can never\n // leak the old subscription (which would keep driving) nor block resubscribing under the new one.\n if (unsubscribe) {\n unsubscribe()\n unsubscribe = null\n }\n frameSource = src\n if (src === null) {\n // Detaching entirely: flush every active driver to its final value so nothing is left frozen\n // (symmetric with the start-time SSR fallback).\n batch(() => {\n for (const d of [...active]) {\n writeValue(d.av, d.target)\n active.delete(d)\n const st = internals.get(d.av)\n if (st) st.driver = null\n d.settle(true)\n }\n })\n } else if (active.size > 0) {\n ensureLoop() // resubscribe in-flight animations under the new source\n }\n}\n\n/** The current frame source, or `null`. */\nexport function getFrameSource(): FrameSource | null {\n return frameSource\n}\n\nfunction writeValue(av: AnimatedValue, v: number): void {\n untrack(() => internals.get(av)?.signal.set(v))\n}\n\nfunction maybeSleep(): void {\n if (active.size === 0 && unsubscribe) {\n unsubscribe()\n unsubscribe = null\n }\n}\n\nfunction ensureLoop(): void {\n if (unsubscribe === null && frameSource !== null) {\n lastNow = -1\n unsubscribe = frameSource(onFrame)\n }\n}\n\nfunction onFrame(now: number): void {\n if (lastNow < 0) lastNow = now // first frame establishes the baseline (dt = 0, no jump)\n // Clamp to [0, MAX_DT]: never integrate backward on a non-monotonic timestamp, never explode on a\n // huge gap (backgrounded tab / GC / breakpoint).\n const dt = Math.min(Math.max(0, (now - lastNow) / 1000), MAX_DT)\n // One batch per frame: every driver's write coalesces into a single flush, so a style reading\n // multiple animated values recomputes exactly once (glitch-free).\n batch(() => {\n for (const d of [...active]) {\n // A sibling's onComplete this frame may have stopped this driver — don't tick a removed one\n // (would violate stop()'s \"keeps current value\" contract with an extra write).\n if (!active.has(d)) continue\n let running: boolean\n try {\n running = d.tick(now, dt)\n } catch {\n running = false // isolate a throwing driver (mirrors flushEffects' error isolation)\n }\n if (!running) {\n active.delete(d)\n const st = internals.get(d.av)\n if (st) st.driver = null\n // Isolate a throwing onComplete: settle() invokes the user's onComplete, and this runs on the\n // SHARED frame loop — one bad callback must not propagate out and leave the rAF chain un-rearmed\n // (which would freeze EVERY animation in the app).\n try {\n d.settle(true)\n } catch {\n // swallow: a faulty completion callback can't be allowed to kill the loop\n }\n }\n }\n })\n lastNow = now\n maybeSleep()\n}\n\n/** Create an {@link AnimatedValue} (a reactive number you can drive with {@link timing}/{@link spring}). */\nexport function animate(initial: number): AnimatedValue {\n const s = signal(initial)\n const state: Internal = { signal: s, vel: { v: 0 }, driver: null }\n const av: AnimatedValue = Object.assign(() => s(), {\n set(v: number): void {\n stopDriver(av) // a manual jump cancels any running animation (RN setValue semantics)\n untrack(() => s.set(v))\n },\n velocity: () => state.vel.v,\n stop(): void {\n stopDriver(av)\n },\n })\n internals.set(av, state)\n return av\n}\n\nfunction stopDriver(av: AnimatedValue): void {\n const st = internals.get(av)\n if (st?.driver) {\n const d = st.driver\n active.delete(d)\n st.driver = null\n d.settle(false)\n maybeSleep()\n }\n}\n\n/** Options shared by drivers. */\ninterface DriverOptions {\n readonly to: number\n readonly onComplete?: (finished: boolean) => void\n}\n\n/**\n * Begin a driver on `av`: settle any prior driver (last-write-wins), capture the current value as\n * the start, and either jump-to-final (no frame source) or join the loop. Returns the handle.\n */\nfunction start(\n av: AnimatedValue,\n opts: DriverOptions,\n build: (from: number, settle: (finished: boolean) => void) => Driver['tick'],\n): AnimationHandle {\n const st = internals.get(av)\n if (!st) throw new TypeError('animation driver: value was not created with animate()')\n stopDriver(av) // last-write-wins: at most one driver per value\n\n let settled = false\n let resolveDone!: (finished: boolean) => void\n const done = new Promise<boolean>((r) => {\n resolveDone = r\n })\n const settle = (finished: boolean): void => {\n if (settled) return\n settled = true\n resolveDone(finished)\n opts.onComplete?.(finished)\n }\n\n const from = untrack(av) // start from the CURRENT rendered value (continuous retarget)\n\n // No frame source → jump to the final value synchronously (SSR / headless / not-yet-wired).\n if (frameSource === null) {\n writeValue(av, opts.to)\n settle(true)\n return { stop: () => settle(false), done }\n }\n\n const tick = build(from, settle)\n const driver: Driver = { av, target: opts.to, tick, settle }\n st.driver = driver\n active.add(driver)\n\n // Auto-stop when the owner that started the animation is disposed (unmount), so the loop never\n // writes a dead signal and never leaks a frame subscription.\n if (getOwner() !== null) {\n onCleanup(() => {\n if (st.driver === driver) stopDriver(av)\n })\n }\n\n ensureLoop()\n return {\n stop: () => stopDriver(av),\n done,\n }\n}\n\n/** Animate `av` to `to` over `duration` ms with `easing` (RN `Animated.timing`). */\nexport function timing(\n av: AnimatedValue,\n opts: {\n readonly to: number\n readonly duration?: number\n readonly easing?: Easing\n readonly delay?: number\n readonly onComplete?: (finished: boolean) => void\n },\n): AnimationHandle {\n // Sanitize: `??` only catches null/undefined, so a NaN/Infinity duration would write NaN forever\n // (permanent under Object.is) — fall back to the default for any non-finite duration.\n const duration = Number.isFinite(opts.duration) ? (opts.duration as number) : DEFAULT_DURATION\n const easing = opts.easing ?? linear\n const delay = Number.isFinite(opts.delay) ? (opts.delay as number) : 0\n return start(av, opts, (from, _settle) => {\n let startTime = -1 // -1 = uninitialized (a real frame can arrive at now=0)\n let prev = from\n const settleAt = (): boolean => {\n internals.get(av)!.vel.v = 0 // at rest: velocity is 0 (so a following spring doesn't inherit phantom momentum)\n writeValue(av, opts.to)\n return false\n }\n return (now, dt) => {\n if (startTime < 0) startTime = now\n const elapsed = now - startTime - delay\n if (elapsed < 0) return true // still in the delay window\n const t = duration <= 0 ? 1 : Math.min(elapsed / duration, 1)\n const next = from + (opts.to - from) * easing(t)\n if (!Number.isFinite(next)) return settleAt() // defensive: never write NaN/Infinity\n if (dt > 0) internals.get(av)!.vel.v = (next - prev) / dt\n prev = next\n if (t >= 1) return settleAt()\n writeValue(av, next)\n return true\n }\n })\n}\n\n/** Animate `av` to `to` with spring physics (RN/Reanimated `withSpring`, Flutter `SpringSimulation`). */\nexport function spring(\n av: AnimatedValue,\n opts: {\n readonly to: number\n readonly stiffness?: number\n readonly damping?: number\n readonly mass?: number\n readonly velocity?: number\n readonly restDelta?: number\n readonly restVelocity?: number\n readonly onComplete?: (finished: boolean) => void\n },\n): AnimationHandle {\n const stiffness = Math.max(0, opts.stiffness ?? 170)\n const damping = Math.max(0, opts.damping ?? 26)\n const mass = Math.max(1e-4, opts.mass ?? 1)\n const restDelta = opts.restDelta ?? 0.01\n const restVelocity = opts.restVelocity ?? 0.01\n const omega = Math.sqrt(stiffness / mass) // natural frequency, for substep stability\n return start(av, opts, (from) => {\n let x = from\n let v = opts.velocity ?? internals.get(av)!.vel.v\n let frames = 0\n return (_now, dt) => {\n frames++\n // Semi-implicit (symplectic) Euler is only conditionally stable (omega·dt < ~2). Stiffness is\n // user-controlled, so SUBSTEP the frame's dt to keep each step well inside the stable region —\n // a stiff spring stays finite instead of diverging to Infinity/NaN.\n const steps = Math.max(1, Math.ceil((dt * omega) / 1.5))\n const sub = dt / steps\n for (let i = 0; i < steps; i++) {\n const a = (-stiffness * (x - opts.to) - damping * v) / mass\n v += a * sub\n x += v * sub\n }\n if (!Number.isFinite(x)) {\n // Defensive: never write NaN/Infinity (permanent under Object.is) — snap to the target.\n internals.get(av)!.vel.v = 0\n writeValue(av, opts.to)\n return false\n }\n internals.get(av)!.vel.v = v\n if (\n (Math.abs(x - opts.to) < restDelta && Math.abs(v) < restVelocity) ||\n frames > SPRING_MAX_FRAMES\n ) {\n internals.get(av)!.vel.v = 0\n writeValue(av, opts.to)\n return false\n }\n writeValue(av, x)\n return true\n }\n })\n}\n\n/**\n * Map an accessor through a piecewise-linear range (RN `Animated.interpolate`). Returns a plain\n * accessor, so it tracks `value` and re-reads inside the consuming style each frame — glitch-free\n * for free. `inputRange` must be monotonically increasing and match `outputRange` length (≥2).\n */\nexport function interpolate(\n value: () => number,\n inputRange: readonly number[],\n outputRange: readonly number[],\n opts?: { readonly extrapolate?: 'clamp' | 'extend' },\n): () => number {\n if (inputRange.length !== outputRange.length || inputRange.length < 2) {\n throw new RangeError('interpolate: inputRange and outputRange must be the same length (>= 2)')\n }\n const extrapolate = opts?.extrapolate ?? 'clamp'\n const n = inputRange.length\n return () => {\n const x = value()\n if (Number.isNaN(x)) return outputRange[0] as number // a NaN source maps to the first output, not the last\n const lerp = (i: number): number => {\n const x0 = inputRange[i] as number\n const x1 = inputRange[i + 1] as number\n const y0 = outputRange[i] as number\n const y1 = outputRange[i + 1] as number\n if (x1 === x0) return y0 // zero-width segment → avoid divide-by-zero\n return y0 + ((y1 - y0) * (x - x0)) / (x1 - x0)\n }\n if (x <= (inputRange[0] as number)) {\n return extrapolate === 'extend' ? lerp(0) : (outputRange[0] as number)\n }\n if (x >= (inputRange[n - 1] as number)) {\n if (extrapolate !== 'extend') return outputRange[n - 1] as number\n // Extend along the slope of the last NON-degenerate segment: a zero-width terminal segment makes\n // lerp() return its start value (dropping the real terminal output), so skip degenerate segments.\n let i = n - 2\n while (i > 0 && inputRange[i] === inputRange[i + 1]) i--\n return inputRange[i] === inputRange[i + 1] ? (outputRange[n - 1] as number) : lerp(i)\n }\n for (let i = 0; i < n - 1; i++) {\n if (x >= (inputRange[i] as number) && x <= (inputRange[i + 1] as number)) return lerp(i)\n }\n return outputRange[n - 1] as number // unreachable for monotonic input\n }\n}\n\n/** A `requestAnimationFrame`-backed {@link FrameSource} for the web (the host wires this at startup). */\nexport function rafFrameSource(): FrameSource {\n return (tick) => {\n const raf = (globalThis as { requestAnimationFrame?: (cb: (t: number) => void) => number })\n .requestAnimationFrame\n const caf = (globalThis as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame\n if (!raf) return () => {} // no rAF (non-browser) → caller already degrades to jump-to-final\n let id = raf(function loop(t: number) {\n // Always re-arm the chain, even if `tick` throws — a single bad frame must never permanently\n // freeze the shared loop. (onFrame already isolates driver ticks + completion callbacks; this is\n // belt-and-suspenders for any other thrown error.)\n try {\n tick(t)\n } finally {\n id = raf(loop)\n }\n })\n return () => caf?.(id)\n }\n}\n\n/** A manually-driven {@link FrameSource} for deterministic tests: call `tick(nowMs)` to advance. */\nexport function manualFrameSource(): { source: FrameSource; tick: (nowMs: number) => void } {\n let cb: ((nowMs: number) => void) | null = null\n return {\n source: (t) => {\n cb = t\n return () => {\n cb = null\n }\n },\n tick: (nowMs) => cb?.(nowMs),\n }\n}\n\n/** @internal Test-only: number of running animations. */\nexport function _activeAnimationCount(): number {\n return active.size\n}\n\n/** @internal Test-only: reset all engine state between tests. */\nexport function _resetAnimation(): void {\n if (unsubscribe) unsubscribe()\n unsubscribe = null\n active.clear()\n frameSource = null\n lastNow = -1\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"recognizers.d.ts","names":[],"sources":["../../src/gesture/recognizers.ts"],"mappings":";;AAiBA;;;;;;;;;;AAMsB;AAWtB;;UAjBiB,aAAA;EAAA,SACN,SAAA;EAAA,SACA,CAAA;EAAA,SACA,CAAA;EAgBG;EAAA,SAdH,CAAA;EAAA,SACA,WAAA;AAAA;AAcV;AAAA,iBAHe,gBAAA,CAAiB,IAAA;EAC/B,GAAA;EACA,QAAA,IAAY,EAAA,cAAgB,EAAA;AAAA;AAO6B;AAAA,iBAA3C,gBAAA,CAAiB,CAAA,YAAa,aAAa;;UAgB1C,eAAA;EACf,aAAA,CAAc,CAAA;EACd,aAAA,CAAc,CAAA;EACd,WAAA,CAAY,CAAA;EACZ,eAAA,CAAgB,CAAA;AAAA;;UAID,UAAA,KAAe,MAAA;EAAA,SACrB,QAAA,EAAU,eAAA;EAAA,SACV,KAAA,EAAO,CAAA;EANU;EAQ1B,KAAA;AAAA;;UAoDe,QAAA;EAAA,SACN,YAAA;EAAA,SACA,YAAA;EAAA,SACA,SAAA;EAAA,SACA,SAAA;EAAA,SACA,CAAA;EAAA,SACA,CAAA;AAAA;AAAA,UAGM,QAAA;EAAA,SACN,MAAA;EAAA,SACA,YAAA;EAAA,SACA,YAAA;EAAA,SACA,SAAA;EAAA,SACA,SAAA;EAAA,SACA,CAAA;EAAA,SACA,CAAA;AAAA;;iBAIK,GAAA,CAAI,MAAA;EAClB,OAAA,IAAW,CAAA,EAAG,QAAA;EACd,QAAA,IAAY,CAAA,EAAG,QAAA;EACf,KAAA,IAAS,CAAA,EAAG,QAAA;IAAa,SAAA;EAAA;EACzB,WAAA;EACA,IAAA;AAAA,IACE,UAAA,CAAW,QAAA;AAAA,UAqHE,QAAA;EAAA,SACN,MAAM;AAAA;;iBAID,GAAA,CAAI,MAAA;EAClB,KAAA;EACA,WAAA;EACA,aAAA;AAAA,IACE,UAAU,CAAC,QAAA;;iBAoDC,SAAA,CAAU,MAAA;EACxB,OAAA;EACA,WAAA;EACA,KAAA;EACA,aAAA;EACA,WAAA;AAAA,IACE,UAAU,CAAC,QAAA;AAAA,UA0DE,UAAA;EAAA,SACN,KAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;EAAA,SACA,MAAA;AAAA;AAAA,UAEM,UAAA;EAAA,SACN,MAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA;EAAA,SACA,MAAA;AAAA;;iBAIK,KAAA,CAAM,MAAA;EACpB,OAAA,IAAW,CAAA,EAAG,UAAA;EACd,QAAA,IAAY,CAAA,EAAG,UAAA;EACf,KAAA,IAAS,CAAA,EAAG,UAAA;AAAA,IACV,UAAA,CAAW,UAAA;AAAA,KAuHH,cAAA;AAAA,UACK,UAAA;EAAA,SACN,SAAA,EAAW,cAAc;EAAA,SACzB,SAAA;EAAA,SACA,SAAA;EAAA,SACA,CAAA;EAAA,SACA,CAAA;AAAA;;iBAIK,KAAA,CAAM,MAAA;EACpB,OAAA,IAAW,CAAA,EAAG,UAAA;EACd,SAAA,WAAoB,cAAA;EACpB,WAAA;EACA,WAAA;AAAA,IACE,UAAA,CAAW,QAAA;;;;;;;AA5QQ;iBA2UP,eAAA,CAAgB,WAAA,WAAsB,UAAA,YAAsB,UAAU"}
1
+ {"version":3,"file":"recognizers.d.ts","names":[],"sources":["../../src/gesture/recognizers.ts"],"mappings":";;AAiBA;;;;;;;;;;AAMsB;AAWtB;;UAjBiB,aAAA;EAAA,SACN,SAAA;EAAA,SACA,CAAA;EAAA,SACA,CAAA;EAgBG;EAAA,SAdH,CAAA;EAAA,SACA,WAAA;AAAA;AAcV;AAAA,iBAHe,gBAAA,CAAiB,IAAA;EAC/B,GAAA;EACA,QAAA,IAAY,EAAA,cAAgB,EAAA;AAAA;AAO6B;AAAA,iBAA3C,gBAAA,CAAiB,CAAA,YAAa,aAAa;;UAgB1C,eAAA;EACf,aAAA,CAAc,CAAA;EACd,aAAA,CAAc,CAAA;EACd,WAAA,CAAY,CAAA;EACZ,eAAA,CAAgB,CAAA;AAAA;;UAID,UAAA,KAAe,MAAA;EAAA,SACrB,QAAA,EAAU,eAAA;EAAA,SACV,KAAA,EAAO,CAAA;EANU;EAQ1B,KAAA;AAAA;;UAoDe,QAAA;EAAA,SACN,YAAA;EAAA,SACA,YAAA;EAAA,SACA,SAAA;EAAA,SACA,SAAA;EAAA,SACA,CAAA;EAAA,SACA,CAAA;AAAA;AAAA,UAGM,QAAA;EAAA,SACN,MAAA;EAAA,SACA,YAAA;EAAA,SACA,YAAA;EAAA,SACA,SAAA;EAAA,SACA,SAAA;EAAA,SACA,CAAA;EAAA,SACA,CAAA;AAAA;;iBAIK,GAAA,CAAI,MAAA;EAClB,OAAA,IAAW,CAAA,EAAG,QAAA;EACd,QAAA,IAAY,CAAA,EAAG,QAAA;EACf,KAAA,IAAS,CAAA,EAAG,QAAA;IAAa,SAAA;EAAA;EACzB,WAAA;EACA,IAAA;AAAA,IACE,UAAA,CAAW,QAAA;AAAA,UAiIE,QAAA;EAAA,SACN,MAAM;AAAA;;iBAID,GAAA,CAAI,MAAA;EAClB,KAAA;EACA,WAAA;EACA,aAAA;AAAA,IACE,UAAU,CAAC,QAAA;;iBAoDC,SAAA,CAAU,MAAA;EACxB,OAAA;EACA,WAAA;EACA,KAAA;EACA,aAAA;EACA,WAAA;AAAA,IACE,UAAU,CAAC,QAAA;AAAA,UA0DE,UAAA;EAAA,SACN,KAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;EAAA,SACA,MAAA;AAAA;AAAA,UAEM,UAAA;EAAA,SACN,MAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA;EAAA,SACA,MAAA;AAAA;;iBAIK,KAAA,CAAM,MAAA;EACpB,OAAA,IAAW,CAAA,EAAG,UAAA;EACd,QAAA,IAAY,CAAA,EAAG,UAAA;EACf,KAAA,IAAS,CAAA,EAAG,UAAA;AAAA,IACV,UAAA,CAAW,UAAA;AAAA,KAuHH,cAAA;AAAA,UACK,UAAA;EAAA,SACN,SAAA,EAAW,cAAc;EAAA,SACzB,SAAA;EAAA,SACA,SAAA;EAAA,SACA,CAAA;EAAA,SACA,CAAA;AAAA;;iBAIK,KAAA,CAAM,MAAA;EACpB,OAAA,IAAW,CAAA,EAAG,UAAA;EACd,SAAA,WAAoB,cAAA;EACpB,WAAA;EACA,WAAA;AAAA,IACE,UAAA,CAAW,QAAA;;;;;;;AA5QQ;iBA2UP,eAAA,CAAgB,WAAA,WAAsB,UAAA,YAAsB,UAAU"}
@@ -101,18 +101,23 @@ function pan(config) {
101
101
  x$.set(e.x);
102
102
  y$.set(e.y);
103
103
  };
104
- const reset = () => {
105
- pointers.clear();
106
- id = null;
107
- active = false;
104
+ const restState = () => {
108
105
  batch(() => {
109
106
  active$.set(false);
110
107
  tx$.set(0);
111
108
  ty$.set(0);
112
109
  vx$.set(0);
113
110
  vy$.set(0);
111
+ x$.set(0);
112
+ y$.set(0);
114
113
  });
115
114
  };
115
+ const reset = () => {
116
+ pointers.clear();
117
+ id = null;
118
+ active = false;
119
+ restState();
120
+ };
116
121
  return {
117
122
  state: {
118
123
  active: () => active$(),
@@ -162,7 +167,8 @@ function pan(config) {
162
167
  if (s.pointerId === id) {
163
168
  id = pointers.keys().next().value ?? null;
164
169
  active = false;
165
- active$.set(false);
170
+ if (id === null) restState();
171
+ else active$.set(false);
166
172
  }
167
173
  },
168
174
  onPointerCancel(e) {
@@ -176,7 +182,8 @@ function pan(config) {
176
182
  if (s.pointerId === id) {
177
183
  id = pointers.keys().next().value ?? null;
178
184
  active = false;
179
- active$.set(false);
185
+ if (id === null) restState();
186
+ else active$.set(false);
180
187
  }
181
188
  }
182
189
  }
@@ -1 +1 @@
1
- {"version":3,"file":"recognizers.js","names":[],"sources":["../../src/gesture/recognizers.ts"],"sourcesContent":["/**\n * Gesture recognizers — RN Gesture Handler / Flutter GestureDetector parity, built on the reactive\n * core. Each factory returns a {@link Recognizer}: a bag of pointer-event handlers to spread onto a\n * host element, plus REACTIVE state (signals) you read in a `style` accessor or feed to the\n * animation engine. The only platform-aware code is {@link normalizePointer}; everything else is\n * pure payload → signal, so it runs on web (Pointer Events), native (the command-backend payload),\n * and tests (synthetic events), and SSRs safely (no `document` access).\n *\n * Attach two recognizers to ONE element via {@link composeGestures} — the renderer binds a single\n * listener per event name, so spreading two `onPointerMove`s would drop one; compose merges them.\n *\n * @module\n */\n\nimport { batch, signal } from '../reactive'\n\n/** A normalized pointer sample (platform differences absorbed by {@link normalizePointer}). */\nexport interface PointerSample {\n readonly pointerId: number\n readonly x: number\n readonly y: number\n /** Timestamp in ms. */\n readonly t: number\n readonly pointerType?: string\n}\n\n// --- injectable clock (deterministic longPress tests; mirrors the animation FrameSource pattern) ---\nlet nowMs = (): number => Date.now()\nlet scheduleTimer = (fn: () => void, ms: number): (() => void) => {\n const id = setTimeout(fn, ms)\n return () => clearTimeout(id)\n}\n\n/** @internal Test-only: inject a deterministic clock + timer. */\nexport function _setGestureClock(opts: {\n now?: () => number\n schedule?: (fn: () => void, ms: number) => () => void\n}): void {\n if (opts.now) nowMs = opts.now\n if (opts.schedule) scheduleTimer = opts.schedule\n}\n\n/** Normalize a host pointer event to a {@link PointerSample}. Web `PointerEvent` or a native payload. */\nexport function normalizePointer(e: unknown): PointerSample {\n const ev = (e ?? {}) as Record<string, unknown>\n const web = 'clientX' in ev // distinguishes a DOM PointerEvent from a native JSON payload\n const num = (v: unknown, d = 0): number => (typeof v === 'number' && Number.isFinite(v) ? v : d)\n return {\n pointerId: num(ev.pointerId, 1),\n x: web ? num(ev.clientX) : num(ev.x),\n y: web ? num(ev.clientY) : num(ev.y),\n t: num(web ? ev.timeStamp : ev.timestamp, nowMs()),\n ...(typeof (web ? ev.pointerType : ev.type) === 'string'\n ? { pointerType: (web ? ev.pointerType : ev.type) as string }\n : {}),\n }\n}\n\n/** Handlers to spread onto a host element. */\nexport interface GestureHandlers {\n onPointerDown(e: unknown): void\n onPointerMove(e: unknown): void\n onPointerUp(e: unknown): void\n onPointerCancel(e: unknown): void\n}\n\n/** A recognizer: handlers to attach, reactive `state`, and a `reset()` for cleanup. */\nexport interface Recognizer<S = Record<string, () => number | boolean>> {\n readonly handlers: GestureHandlers\n readonly state: S\n /** Clear pointers, timers, and reset state to rest (call from `onCleanup`). */\n reset(): void\n}\n\ninterface Tracked {\n startX: number\n startY: number\n startT: number\n x: number\n y: number\n lastX: number\n lastY: number\n lastT: number\n vx: number // EWMA velocity, px/ms\n vy: number\n}\n\nconst VEL_ALPHA = 0.6\nconst dist = (ax: number, ay: number, bx: number, by: number): number =>\n Math.hypot(ax - bx, ay - by)\n\n/** Update a tracked pointer + its EWMA velocity from a new sample. */\nfunction track(p: Tracked, s: PointerSample): void {\n const dtMs = s.t - p.lastT\n if (dtMs > 0) {\n p.vx = VEL_ALPHA * ((s.x - p.lastX) / dtMs) + (1 - VEL_ALPHA) * p.vx\n p.vy = VEL_ALPHA * ((s.y - p.lastY) / dtMs) + (1 - VEL_ALPHA) * p.vy\n }\n p.lastX = s.x\n p.lastY = s.y\n p.lastT = s.t\n p.x = s.x\n p.y = s.y\n}\n\nfunction newTracked(s: PointerSample): Tracked {\n return {\n startX: s.x,\n startY: s.y,\n startT: s.t,\n x: s.x,\n y: s.y,\n lastX: s.x,\n lastY: s.y,\n lastT: s.t,\n vx: 0,\n vy: 0,\n }\n}\n\n// --- Pan (drag) -----------------------------------------------------------------------------------\n\n/** A continuous pan/drag update (translations relative to gesture start; velocity in px/ms). */\nexport interface PanEvent {\n readonly translationX: number\n readonly translationY: number\n readonly velocityX: number\n readonly velocityY: number\n readonly x: number\n readonly y: number\n}\n\nexport interface PanState {\n readonly active: () => boolean\n readonly translationX: () => number\n readonly translationY: () => number\n readonly velocityX: () => number\n readonly velocityY: () => number\n readonly x: () => number\n readonly y: () => number\n}\n\n/** Recognize a drag. Becomes active once the pointer moves past `minDistance` (slop). */\nexport function pan(config: {\n onBegin?: (e: PanEvent) => void\n onUpdate?: (e: PanEvent) => void\n onEnd?: (e: PanEvent & { completed: boolean }) => void\n minDistance?: number\n axis?: 'both' | 'x' | 'y'\n}): Recognizer<PanState> {\n const minDistance = config.minDistance ?? 10\n const axis = config.axis ?? 'both'\n const pointers = new Map<number, Tracked>()\n let id: number | null = null // the claimed pointer\n let active = false\n const active$ = signal(false)\n const tx$ = signal(0)\n const ty$ = signal(0)\n const vx$ = signal(0)\n const vy$ = signal(0)\n const x$ = signal(0)\n const y$ = signal(0)\n\n const filt = (dx: number, dy: number): [number, number] =>\n axis === 'x' ? [dx, 0] : axis === 'y' ? [0, dy] : [dx, dy]\n\n const eventFor = (p: Tracked): PanEvent => {\n const [tx, ty] = filt(p.x - p.startX, p.y - p.startY)\n const [vx, vy] = filt(p.vx, p.vy)\n return { translationX: tx, translationY: ty, velocityX: vx, velocityY: vy, x: p.x, y: p.y }\n }\n const writeFrom = (p: Tracked): void => {\n const e = eventFor(p)\n tx$.set(e.translationX)\n ty$.set(e.translationY)\n vx$.set(e.velocityX)\n vy$.set(e.velocityY)\n x$.set(e.x)\n y$.set(e.y)\n }\n\n const reset = (): void => {\n pointers.clear()\n id = null\n active = false\n batch(() => {\n active$.set(false)\n tx$.set(0)\n ty$.set(0)\n vx$.set(0)\n vy$.set(0)\n })\n }\n\n return {\n state: {\n active: () => active$(),\n translationX: () => tx$(),\n translationY: () => ty$(),\n velocityX: () => vx$(),\n velocityY: () => vy$(),\n x: () => x$(),\n y: () => y$(),\n },\n reset,\n handlers: {\n onPointerDown(e): void {\n const s = normalizePointer(e)\n pointers.set(s.pointerId, newTracked(s))\n if (id === null) id = s.pointerId\n },\n onPointerMove(e): void {\n const s = normalizePointer(e)\n const p = pointers.get(s.pointerId)\n if (!p) return\n track(p, s)\n if (s.pointerId !== id) return\n // Slop on the AXIS-FILTERED delta: an `axis: 'y'` pan must not activate on horizontal travel.\n const [sx, sy] = filt(p.x - p.startX, p.y - p.startY)\n const slop = Math.hypot(sx, sy)\n batch(() => {\n if (!active && slop >= minDistance) {\n active = true\n active$.set(true)\n writeFrom(p)\n config.onBegin?.(eventFor(p))\n } else if (active) {\n writeFrom(p)\n config.onUpdate?.(eventFor(p))\n }\n })\n },\n onPointerUp(e): void {\n const s = normalizePointer(e)\n const p = pointers.get(s.pointerId)\n if (p) track(p, s)\n if (s.pointerId === id && active && p) {\n config.onEnd?.({ ...eventFor(p), completed: true })\n }\n pointers.delete(s.pointerId)\n if (s.pointerId === id) {\n id = pointers.keys().next().value ?? null\n active = false\n active$.set(false)\n }\n },\n onPointerCancel(e): void {\n const s = normalizePointer(e)\n const p = pointers.get(s.pointerId)\n if (s.pointerId === id && active && p) {\n config.onEnd?.({ ...eventFor(p), completed: false })\n }\n pointers.delete(s.pointerId)\n if (s.pointerId === id) {\n // Hand off to another still-down pointer (mirror onPointerUp) so it can re-claim the pan.\n id = pointers.keys().next().value ?? null\n active = false\n active$.set(false)\n }\n },\n },\n }\n}\n\n// --- Tap ------------------------------------------------------------------------------------------\n\nexport interface TapState {\n readonly active: () => boolean\n}\n\n/** Recognize a tap: down + up within `maxDistance` and `maxDurationMs`, no extra pointer. */\nexport function tap(config: {\n onTap?: () => void\n maxDistance?: number\n maxDurationMs?: number\n}): Recognizer<TapState> {\n const maxDistance = config.maxDistance ?? 10\n const maxDurationMs = config.maxDurationMs ?? 500\n let down: PointerSample | null = null\n let failed = false\n const active$ = signal(false)\n const reset = (): void => {\n down = null\n failed = false\n active$.set(false)\n }\n return {\n state: { active: () => active$() },\n reset,\n handlers: {\n onPointerDown(e): void {\n if (down !== null) {\n failed = true // a second pointer fails a tap\n return\n }\n down = normalizePointer(e)\n failed = false\n active$.set(true)\n },\n onPointerMove(e): void {\n if (!down || failed) return\n const s = normalizePointer(e)\n if (s.pointerId === down.pointerId && dist(s.x, s.y, down.x, down.y) > maxDistance) {\n failed = true\n active$.set(false)\n }\n },\n onPointerUp(e): void {\n const s = normalizePointer(e)\n if (down && !failed && s.pointerId === down.pointerId) {\n const within =\n dist(s.x, s.y, down.x, down.y) <= maxDistance && s.t - down.t <= maxDurationMs\n if (within) config.onTap?.()\n }\n if (!down || s.pointerId === down.pointerId) reset() // ignore a foreign pointer's cancel/up\n },\n onPointerCancel(e): void {\n const s = normalizePointer(e)\n if (!down || s.pointerId === down.pointerId) reset() // a foreign pointer must not kill our tap\n },\n },\n }\n}\n\n// --- Long press -----------------------------------------------------------------------------------\n\n/** Recognize a long press: pointer held past `minDurationMs` without moving past `maxDistance`. */\nexport function longPress(config: {\n onBegin?: () => void\n onLongPress?: () => void\n onEnd?: () => void\n minDurationMs?: number\n maxDistance?: number\n}): Recognizer<TapState> {\n const minDurationMs = config.minDurationMs ?? 500\n const maxDistance = config.maxDistance ?? 10\n let down: PointerSample | null = null\n let cancelTimer: (() => void) | null = null\n let fired = false\n const active$ = signal(false)\n const clear = (): void => {\n cancelTimer?.()\n cancelTimer = null\n }\n const reset = (): void => {\n clear()\n down = null\n fired = false\n active$.set(false)\n }\n /** End the press, firing onEnd iff the long-press had fired, then reset. */\n const finish = (): void => {\n if (fired) config.onEnd?.()\n reset()\n }\n return {\n state: { active: () => active$() },\n reset,\n handlers: {\n onPointerDown(e): void {\n if (down !== null) return\n down = normalizePointer(e)\n fired = false\n active$.set(true)\n config.onBegin?.()\n cancelTimer = scheduleTimer(() => {\n fired = true\n config.onLongPress?.()\n }, minDurationMs)\n },\n onPointerMove(e): void {\n if (!down) return\n const s = normalizePointer(e)\n if (s.pointerId === down.pointerId && dist(s.x, s.y, down.x, down.y) > maxDistance) {\n finish() // slop exceeded → end (onEnd iff it had fired), then fail\n }\n },\n onPointerUp(e): void {\n const s = normalizePointer(e)\n if (down && s.pointerId === down.pointerId) finish()\n },\n onPointerCancel(e): void {\n const s = normalizePointer(e)\n if (!down || s.pointerId === down.pointerId) finish()\n },\n },\n }\n}\n\n// --- Pinch (scale) --------------------------------------------------------------------------------\n\nexport interface PinchEvent {\n readonly scale: number\n readonly velocity: number\n readonly focalX: number\n readonly focalY: number\n}\nexport interface PinchState {\n readonly active: () => boolean\n readonly scale: () => number\n readonly focalX: () => number\n readonly focalY: () => number\n}\n\n/** Recognize a two-finger pinch. `scale` is current distance / start distance between the pinned pair. */\nexport function pinch(config: {\n onBegin?: (e: PinchEvent) => void\n onUpdate?: (e: PinchEvent) => void\n onEnd?: (e: PinchEvent) => void\n}): Recognizer<PinchState> {\n const pts = new Map<number, PointerSample>()\n let pair: [number, number] | null = null\n let startDist = 0\n let lastScale = 1\n let lastT = 0\n const active$ = signal(false)\n const scale$ = signal(1)\n const fx$ = signal(0)\n const fy$ = signal(0)\n\n const pairPts = (): [PointerSample, PointerSample] | null => {\n if (!pair) return null\n const a = pts.get(pair[0])\n const b = pts.get(pair[1])\n return a && b ? [a, b] : null\n }\n const eventNow = (a: PointerSample, b: PointerSample, t: number): PinchEvent => {\n const d = dist(a.x, a.y, b.x, b.y)\n const scale = startDist > 0 ? d / startDist : 1\n const dt = t - lastT\n const velocity = dt > 0 ? (scale - lastScale) / dt : 0\n lastScale = scale\n lastT = t\n return { scale, velocity, focalX: (a.x + b.x) / 2, focalY: (a.y + b.y) / 2 }\n }\n const reset = (): void => {\n pts.clear()\n pair = null\n startDist = 0\n lastScale = 1\n batch(() => {\n active$.set(false)\n scale$.set(1)\n fx$.set(0)\n fy$.set(0)\n })\n }\n const begin = (): void => {\n const pp = pairPts()\n if (!pp) return\n startDist = dist(pp[0].x, pp[0].y, pp[1].x, pp[1].y)\n lastScale = 1\n lastT = Math.max(pp[0].t, pp[1].t)\n const focalX = (pp[0].x + pp[1].x) / 2\n const focalY = (pp[0].y + pp[1].y) / 2\n batch(() => {\n active$.set(true)\n scale$.set(1)\n fx$.set(focalX) // seed focal so a style reading focalX/Y is correct from the first frame\n fy$.set(focalY)\n })\n config.onBegin?.({ scale: 1, velocity: 0, focalX, focalY })\n }\n /** A pointer left: if it was a pinned finger but ≥2 remain, re-pin survivors continuously; else end. */\n const onLift = (s: PointerSample): void => {\n pts.delete(s.pointerId)\n if (!active$() || !pair) return\n const wasPinned = s.pointerId === pair[0] || s.pointerId === pair[1]\n if (!wasPinned) return // a non-pinned finger lifted — pinch continues unaffected\n if (pts.size >= 2) {\n const ids = [...pts.keys()]\n pair = [ids[0] as number, ids[1] as number]\n const pp = pairPts()\n if (pp) {\n // Re-base so dist/startDist keeps equalling the CURRENT scale (no jump on the next move).\n const d = dist(pp[0].x, pp[0].y, pp[1].x, pp[1].y)\n startDist = d / Math.max(scale$(), 1e-4)\n lastScale = scale$()\n }\n return\n }\n config.onEnd?.({ scale: scale$(), velocity: 0, focalX: fx$(), focalY: fy$() })\n reset()\n }\n return {\n state: {\n active: () => active$(),\n scale: () => scale$(),\n focalX: () => fx$(),\n focalY: () => fy$(),\n },\n reset,\n handlers: {\n onPointerDown(e): void {\n const s = normalizePointer(e)\n pts.set(s.pointerId, s)\n if (!pair && pts.size >= 2) {\n const ids = [...pts.keys()]\n pair = [ids[0] as number, ids[1] as number]\n begin()\n }\n },\n onPointerMove(e): void {\n const s = normalizePointer(e)\n if (!pts.has(s.pointerId)) return\n pts.set(s.pointerId, s)\n const pp = pairPts()\n if (!active$() || !pp) return\n const ev = eventNow(pp[0], pp[1], s.t)\n batch(() => {\n scale$.set(ev.scale)\n fx$.set(ev.focalX)\n fy$.set(ev.focalY)\n })\n config.onUpdate?.(ev)\n },\n onPointerUp(e): void {\n onLift(normalizePointer(e))\n },\n onPointerCancel(e): void {\n onLift(normalizePointer(e))\n },\n },\n }\n}\n\n// --- Swipe ----------------------------------------------------------------------------------------\n\nexport type SwipeDirection = 'left' | 'right' | 'up' | 'down'\nexport interface SwipeEvent {\n readonly direction: SwipeDirection\n readonly velocityX: number\n readonly velocityY: number\n readonly x: number\n readonly y: number\n}\n\n/** Recognize a fast flick on pointer-up (velocity ≥ `minVelocity` px/ms over `minDistance`). */\nexport function swipe(config: {\n onSwipe?: (e: SwipeEvent) => void\n direction?: 'any' | SwipeDirection\n minVelocity?: number\n minDistance?: number\n}): Recognizer<TapState> {\n const want = config.direction ?? 'any'\n const minVelocity = config.minVelocity ?? 0.3\n const minDistance = config.minDistance ?? 30\n const pointers = new Map<number, Tracked>()\n const active$ = signal(false)\n const reset = (): void => {\n pointers.clear()\n active$.set(false)\n }\n const dominant = (p: Tracked): SwipeDirection => {\n const dx = p.x - p.startX\n const dy = p.y - p.startY\n if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? 'right' : 'left'\n return dy >= 0 ? 'down' : 'up'\n }\n return {\n state: { active: () => active$() },\n reset,\n handlers: {\n onPointerDown(e): void {\n const s = normalizePointer(e)\n pointers.set(s.pointerId, newTracked(s))\n active$.set(true)\n },\n onPointerMove(e): void {\n const s = normalizePointer(e)\n const p = pointers.get(s.pointerId)\n if (p) track(p, s)\n },\n onPointerUp(e): void {\n const s = normalizePointer(e)\n const p = pointers.get(s.pointerId)\n if (p) {\n track(p, s)\n const speed = Math.hypot(p.vx, p.vy)\n const moved = dist(p.x, p.y, p.startX, p.startY)\n const dir = dominant(p)\n if (speed >= minVelocity && moved >= minDistance && (want === 'any' || want === dir)) {\n config.onSwipe?.({ direction: dir, velocityX: p.vx, velocityY: p.vy, x: p.x, y: p.y })\n }\n }\n pointers.delete(s.pointerId) // per-pointer: another finger can still produce its own swipe\n if (pointers.size === 0) active$.set(false)\n },\n onPointerCancel(e): void {\n const s = normalizePointer(e)\n pointers.delete(s.pointerId)\n if (pointers.size === 0) active$.set(false)\n },\n },\n }\n}\n\n// --- Composition ----------------------------------------------------------------------------------\n\n/**\n * Merge several recognizers into ONE so they can attach to a single element — required because the\n * renderer binds a single listener per event name (spreading two `onPointerMove`s would drop one).\n * `simultaneous` (the default): every recognizer sees every event independently (e.g. pan + pinch\n * together). Per-recognizer slop already disambiguates tap-vs-pan; an explicit exclusive arena is a\n * follow-up.\n */\nexport function composeGestures(recognizers: readonly Recognizer<never>[]): Recognizer<never> {\n const fan =\n (key: keyof GestureHandlers) =>\n (e: unknown): void => {\n let firstError: unknown\n batch(() => {\n // Isolate each recognizer: one throwing must not skip the rest (e.g. leaving a sibling's\n // long-press timer armed). Collect the first error and rethrow after all have run.\n for (const r of recognizers) {\n try {\n r.handlers[key](e)\n } catch (err) {\n if (firstError === undefined) firstError = err\n }\n }\n })\n if (firstError !== undefined) throw firstError\n }\n return {\n state: {} as never,\n reset: () => {\n for (const r of recognizers) r.reset()\n },\n handlers: {\n onPointerDown: fan('onPointerDown'),\n onPointerMove: fan('onPointerMove'),\n onPointerUp: fan('onPointerUp'),\n onPointerCancel: fan('onPointerCancel'),\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"recognizers.js","names":[],"sources":["../../src/gesture/recognizers.ts"],"sourcesContent":["/**\n * Gesture recognizers — RN Gesture Handler / Flutter GestureDetector parity, built on the reactive\n * core. Each factory returns a {@link Recognizer}: a bag of pointer-event handlers to spread onto a\n * host element, plus REACTIVE state (signals) you read in a `style` accessor or feed to the\n * animation engine. The only platform-aware code is {@link normalizePointer}; everything else is\n * pure payload → signal, so it runs on web (Pointer Events), native (the command-backend payload),\n * and tests (synthetic events), and SSRs safely (no `document` access).\n *\n * Attach two recognizers to ONE element via {@link composeGestures} — the renderer binds a single\n * listener per event name, so spreading two `onPointerMove`s would drop one; compose merges them.\n *\n * @module\n */\n\nimport { batch, signal } from '../reactive'\n\n/** A normalized pointer sample (platform differences absorbed by {@link normalizePointer}). */\nexport interface PointerSample {\n readonly pointerId: number\n readonly x: number\n readonly y: number\n /** Timestamp in ms. */\n readonly t: number\n readonly pointerType?: string\n}\n\n// --- injectable clock (deterministic longPress tests; mirrors the animation FrameSource pattern) ---\nlet nowMs = (): number => Date.now()\nlet scheduleTimer = (fn: () => void, ms: number): (() => void) => {\n const id = setTimeout(fn, ms)\n return () => clearTimeout(id)\n}\n\n/** @internal Test-only: inject a deterministic clock + timer. */\nexport function _setGestureClock(opts: {\n now?: () => number\n schedule?: (fn: () => void, ms: number) => () => void\n}): void {\n if (opts.now) nowMs = opts.now\n if (opts.schedule) scheduleTimer = opts.schedule\n}\n\n/** Normalize a host pointer event to a {@link PointerSample}. Web `PointerEvent` or a native payload. */\nexport function normalizePointer(e: unknown): PointerSample {\n const ev = (e ?? {}) as Record<string, unknown>\n const web = 'clientX' in ev // distinguishes a DOM PointerEvent from a native JSON payload\n const num = (v: unknown, d = 0): number => (typeof v === 'number' && Number.isFinite(v) ? v : d)\n return {\n pointerId: num(ev.pointerId, 1),\n x: web ? num(ev.clientX) : num(ev.x),\n y: web ? num(ev.clientY) : num(ev.y),\n t: num(web ? ev.timeStamp : ev.timestamp, nowMs()),\n ...(typeof (web ? ev.pointerType : ev.type) === 'string'\n ? { pointerType: (web ? ev.pointerType : ev.type) as string }\n : {}),\n }\n}\n\n/** Handlers to spread onto a host element. */\nexport interface GestureHandlers {\n onPointerDown(e: unknown): void\n onPointerMove(e: unknown): void\n onPointerUp(e: unknown): void\n onPointerCancel(e: unknown): void\n}\n\n/** A recognizer: handlers to attach, reactive `state`, and a `reset()` for cleanup. */\nexport interface Recognizer<S = Record<string, () => number | boolean>> {\n readonly handlers: GestureHandlers\n readonly state: S\n /** Clear pointers, timers, and reset state to rest (call from `onCleanup`). */\n reset(): void\n}\n\ninterface Tracked {\n startX: number\n startY: number\n startT: number\n x: number\n y: number\n lastX: number\n lastY: number\n lastT: number\n vx: number // EWMA velocity, px/ms\n vy: number\n}\n\nconst VEL_ALPHA = 0.6\nconst dist = (ax: number, ay: number, bx: number, by: number): number =>\n Math.hypot(ax - bx, ay - by)\n\n/** Update a tracked pointer + its EWMA velocity from a new sample. */\nfunction track(p: Tracked, s: PointerSample): void {\n const dtMs = s.t - p.lastT\n if (dtMs > 0) {\n p.vx = VEL_ALPHA * ((s.x - p.lastX) / dtMs) + (1 - VEL_ALPHA) * p.vx\n p.vy = VEL_ALPHA * ((s.y - p.lastY) / dtMs) + (1 - VEL_ALPHA) * p.vy\n }\n p.lastX = s.x\n p.lastY = s.y\n p.lastT = s.t\n p.x = s.x\n p.y = s.y\n}\n\nfunction newTracked(s: PointerSample): Tracked {\n return {\n startX: s.x,\n startY: s.y,\n startT: s.t,\n x: s.x,\n y: s.y,\n lastX: s.x,\n lastY: s.y,\n lastT: s.t,\n vx: 0,\n vy: 0,\n }\n}\n\n// --- Pan (drag) -----------------------------------------------------------------------------------\n\n/** A continuous pan/drag update (translations relative to gesture start; velocity in px/ms). */\nexport interface PanEvent {\n readonly translationX: number\n readonly translationY: number\n readonly velocityX: number\n readonly velocityY: number\n readonly x: number\n readonly y: number\n}\n\nexport interface PanState {\n readonly active: () => boolean\n readonly translationX: () => number\n readonly translationY: () => number\n readonly velocityX: () => number\n readonly velocityY: () => number\n readonly x: () => number\n readonly y: () => number\n}\n\n/** Recognize a drag. Becomes active once the pointer moves past `minDistance` (slop). */\nexport function pan(config: {\n onBegin?: (e: PanEvent) => void\n onUpdate?: (e: PanEvent) => void\n onEnd?: (e: PanEvent & { completed: boolean }) => void\n minDistance?: number\n axis?: 'both' | 'x' | 'y'\n}): Recognizer<PanState> {\n const minDistance = config.minDistance ?? 10\n const axis = config.axis ?? 'both'\n const pointers = new Map<number, Tracked>()\n let id: number | null = null // the claimed pointer\n let active = false\n const active$ = signal(false)\n const tx$ = signal(0)\n const ty$ = signal(0)\n const vx$ = signal(0)\n const vy$ = signal(0)\n const x$ = signal(0)\n const y$ = signal(0)\n\n const filt = (dx: number, dy: number): [number, number] =>\n axis === 'x' ? [dx, 0] : axis === 'y' ? [0, dy] : [dx, dy]\n\n const eventFor = (p: Tracked): PanEvent => {\n const [tx, ty] = filt(p.x - p.startX, p.y - p.startY)\n const [vx, vy] = filt(p.vx, p.vy)\n return { translationX: tx, translationY: ty, velocityX: vx, velocityY: vy, x: p.x, y: p.y }\n }\n const writeFrom = (p: Tracked): void => {\n const e = eventFor(p)\n tx$.set(e.translationX)\n ty$.set(e.translationY)\n vx$.set(e.velocityX)\n vy$.set(e.velocityY)\n x$.set(e.x)\n y$.set(e.y)\n }\n\n // Snap the rich reactive state back to rest (active + translation + velocity + position). Used by\n // reset() and on the final pointer-up/cancel — so `state.translationX()` etc. don't stay stuck at the\n // last drag offset after release (consistent with tap/longPress, which reset on up).\n const restState = (): void => {\n batch(() => {\n active$.set(false)\n tx$.set(0)\n ty$.set(0)\n vx$.set(0)\n vy$.set(0)\n x$.set(0)\n y$.set(0)\n })\n }\n\n const reset = (): void => {\n pointers.clear()\n id = null\n active = false\n restState()\n }\n\n return {\n state: {\n active: () => active$(),\n translationX: () => tx$(),\n translationY: () => ty$(),\n velocityX: () => vx$(),\n velocityY: () => vy$(),\n x: () => x$(),\n y: () => y$(),\n },\n reset,\n handlers: {\n onPointerDown(e): void {\n const s = normalizePointer(e)\n pointers.set(s.pointerId, newTracked(s))\n if (id === null) id = s.pointerId\n },\n onPointerMove(e): void {\n const s = normalizePointer(e)\n const p = pointers.get(s.pointerId)\n if (!p) return\n track(p, s)\n if (s.pointerId !== id) return\n // Slop on the AXIS-FILTERED delta: an `axis: 'y'` pan must not activate on horizontal travel.\n const [sx, sy] = filt(p.x - p.startX, p.y - p.startY)\n const slop = Math.hypot(sx, sy)\n batch(() => {\n if (!active && slop >= minDistance) {\n active = true\n active$.set(true)\n writeFrom(p)\n config.onBegin?.(eventFor(p))\n } else if (active) {\n writeFrom(p)\n config.onUpdate?.(eventFor(p))\n }\n })\n },\n onPointerUp(e): void {\n const s = normalizePointer(e)\n const p = pointers.get(s.pointerId)\n if (p) track(p, s)\n if (s.pointerId === id && active && p) {\n config.onEnd?.({ ...eventFor(p), completed: true })\n }\n pointers.delete(s.pointerId)\n if (s.pointerId === id) {\n id = pointers.keys().next().value ?? null\n active = false\n // Last pointer up → snap state to rest; otherwise hand off to the remaining pointer.\n if (id === null) restState()\n else active$.set(false)\n }\n },\n onPointerCancel(e): void {\n const s = normalizePointer(e)\n const p = pointers.get(s.pointerId)\n if (s.pointerId === id && active && p) {\n config.onEnd?.({ ...eventFor(p), completed: false })\n }\n pointers.delete(s.pointerId)\n if (s.pointerId === id) {\n // Hand off to another still-down pointer (mirror onPointerUp) so it can re-claim the pan.\n id = pointers.keys().next().value ?? null\n active = false\n if (id === null) restState()\n else active$.set(false)\n }\n },\n },\n }\n}\n\n// --- Tap ------------------------------------------------------------------------------------------\n\nexport interface TapState {\n readonly active: () => boolean\n}\n\n/** Recognize a tap: down + up within `maxDistance` and `maxDurationMs`, no extra pointer. */\nexport function tap(config: {\n onTap?: () => void\n maxDistance?: number\n maxDurationMs?: number\n}): Recognizer<TapState> {\n const maxDistance = config.maxDistance ?? 10\n const maxDurationMs = config.maxDurationMs ?? 500\n let down: PointerSample | null = null\n let failed = false\n const active$ = signal(false)\n const reset = (): void => {\n down = null\n failed = false\n active$.set(false)\n }\n return {\n state: { active: () => active$() },\n reset,\n handlers: {\n onPointerDown(e): void {\n if (down !== null) {\n failed = true // a second pointer fails a tap\n return\n }\n down = normalizePointer(e)\n failed = false\n active$.set(true)\n },\n onPointerMove(e): void {\n if (!down || failed) return\n const s = normalizePointer(e)\n if (s.pointerId === down.pointerId && dist(s.x, s.y, down.x, down.y) > maxDistance) {\n failed = true\n active$.set(false)\n }\n },\n onPointerUp(e): void {\n const s = normalizePointer(e)\n if (down && !failed && s.pointerId === down.pointerId) {\n const within =\n dist(s.x, s.y, down.x, down.y) <= maxDistance && s.t - down.t <= maxDurationMs\n if (within) config.onTap?.()\n }\n if (!down || s.pointerId === down.pointerId) reset() // ignore a foreign pointer's cancel/up\n },\n onPointerCancel(e): void {\n const s = normalizePointer(e)\n if (!down || s.pointerId === down.pointerId) reset() // a foreign pointer must not kill our tap\n },\n },\n }\n}\n\n// --- Long press -----------------------------------------------------------------------------------\n\n/** Recognize a long press: pointer held past `minDurationMs` without moving past `maxDistance`. */\nexport function longPress(config: {\n onBegin?: () => void\n onLongPress?: () => void\n onEnd?: () => void\n minDurationMs?: number\n maxDistance?: number\n}): Recognizer<TapState> {\n const minDurationMs = config.minDurationMs ?? 500\n const maxDistance = config.maxDistance ?? 10\n let down: PointerSample | null = null\n let cancelTimer: (() => void) | null = null\n let fired = false\n const active$ = signal(false)\n const clear = (): void => {\n cancelTimer?.()\n cancelTimer = null\n }\n const reset = (): void => {\n clear()\n down = null\n fired = false\n active$.set(false)\n }\n /** End the press, firing onEnd iff the long-press had fired, then reset. */\n const finish = (): void => {\n if (fired) config.onEnd?.()\n reset()\n }\n return {\n state: { active: () => active$() },\n reset,\n handlers: {\n onPointerDown(e): void {\n if (down !== null) return\n down = normalizePointer(e)\n fired = false\n active$.set(true)\n config.onBegin?.()\n cancelTimer = scheduleTimer(() => {\n fired = true\n config.onLongPress?.()\n }, minDurationMs)\n },\n onPointerMove(e): void {\n if (!down) return\n const s = normalizePointer(e)\n if (s.pointerId === down.pointerId && dist(s.x, s.y, down.x, down.y) > maxDistance) {\n finish() // slop exceeded → end (onEnd iff it had fired), then fail\n }\n },\n onPointerUp(e): void {\n const s = normalizePointer(e)\n if (down && s.pointerId === down.pointerId) finish()\n },\n onPointerCancel(e): void {\n const s = normalizePointer(e)\n if (!down || s.pointerId === down.pointerId) finish()\n },\n },\n }\n}\n\n// --- Pinch (scale) --------------------------------------------------------------------------------\n\nexport interface PinchEvent {\n readonly scale: number\n readonly velocity: number\n readonly focalX: number\n readonly focalY: number\n}\nexport interface PinchState {\n readonly active: () => boolean\n readonly scale: () => number\n readonly focalX: () => number\n readonly focalY: () => number\n}\n\n/** Recognize a two-finger pinch. `scale` is current distance / start distance between the pinned pair. */\nexport function pinch(config: {\n onBegin?: (e: PinchEvent) => void\n onUpdate?: (e: PinchEvent) => void\n onEnd?: (e: PinchEvent) => void\n}): Recognizer<PinchState> {\n const pts = new Map<number, PointerSample>()\n let pair: [number, number] | null = null\n let startDist = 0\n let lastScale = 1\n let lastT = 0\n const active$ = signal(false)\n const scale$ = signal(1)\n const fx$ = signal(0)\n const fy$ = signal(0)\n\n const pairPts = (): [PointerSample, PointerSample] | null => {\n if (!pair) return null\n const a = pts.get(pair[0])\n const b = pts.get(pair[1])\n return a && b ? [a, b] : null\n }\n const eventNow = (a: PointerSample, b: PointerSample, t: number): PinchEvent => {\n const d = dist(a.x, a.y, b.x, b.y)\n const scale = startDist > 0 ? d / startDist : 1\n const dt = t - lastT\n const velocity = dt > 0 ? (scale - lastScale) / dt : 0\n lastScale = scale\n lastT = t\n return { scale, velocity, focalX: (a.x + b.x) / 2, focalY: (a.y + b.y) / 2 }\n }\n const reset = (): void => {\n pts.clear()\n pair = null\n startDist = 0\n lastScale = 1\n batch(() => {\n active$.set(false)\n scale$.set(1)\n fx$.set(0)\n fy$.set(0)\n })\n }\n const begin = (): void => {\n const pp = pairPts()\n if (!pp) return\n startDist = dist(pp[0].x, pp[0].y, pp[1].x, pp[1].y)\n lastScale = 1\n lastT = Math.max(pp[0].t, pp[1].t)\n const focalX = (pp[0].x + pp[1].x) / 2\n const focalY = (pp[0].y + pp[1].y) / 2\n batch(() => {\n active$.set(true)\n scale$.set(1)\n fx$.set(focalX) // seed focal so a style reading focalX/Y is correct from the first frame\n fy$.set(focalY)\n })\n config.onBegin?.({ scale: 1, velocity: 0, focalX, focalY })\n }\n /** A pointer left: if it was a pinned finger but ≥2 remain, re-pin survivors continuously; else end. */\n const onLift = (s: PointerSample): void => {\n pts.delete(s.pointerId)\n if (!active$() || !pair) return\n const wasPinned = s.pointerId === pair[0] || s.pointerId === pair[1]\n if (!wasPinned) return // a non-pinned finger lifted — pinch continues unaffected\n if (pts.size >= 2) {\n const ids = [...pts.keys()]\n pair = [ids[0] as number, ids[1] as number]\n const pp = pairPts()\n if (pp) {\n // Re-base so dist/startDist keeps equalling the CURRENT scale (no jump on the next move).\n const d = dist(pp[0].x, pp[0].y, pp[1].x, pp[1].y)\n startDist = d / Math.max(scale$(), 1e-4)\n lastScale = scale$()\n }\n return\n }\n config.onEnd?.({ scale: scale$(), velocity: 0, focalX: fx$(), focalY: fy$() })\n reset()\n }\n return {\n state: {\n active: () => active$(),\n scale: () => scale$(),\n focalX: () => fx$(),\n focalY: () => fy$(),\n },\n reset,\n handlers: {\n onPointerDown(e): void {\n const s = normalizePointer(e)\n pts.set(s.pointerId, s)\n if (!pair && pts.size >= 2) {\n const ids = [...pts.keys()]\n pair = [ids[0] as number, ids[1] as number]\n begin()\n }\n },\n onPointerMove(e): void {\n const s = normalizePointer(e)\n if (!pts.has(s.pointerId)) return\n pts.set(s.pointerId, s)\n const pp = pairPts()\n if (!active$() || !pp) return\n const ev = eventNow(pp[0], pp[1], s.t)\n batch(() => {\n scale$.set(ev.scale)\n fx$.set(ev.focalX)\n fy$.set(ev.focalY)\n })\n config.onUpdate?.(ev)\n },\n onPointerUp(e): void {\n onLift(normalizePointer(e))\n },\n onPointerCancel(e): void {\n onLift(normalizePointer(e))\n },\n },\n }\n}\n\n// --- Swipe ----------------------------------------------------------------------------------------\n\nexport type SwipeDirection = 'left' | 'right' | 'up' | 'down'\nexport interface SwipeEvent {\n readonly direction: SwipeDirection\n readonly velocityX: number\n readonly velocityY: number\n readonly x: number\n readonly y: number\n}\n\n/** Recognize a fast flick on pointer-up (velocity ≥ `minVelocity` px/ms over `minDistance`). */\nexport function swipe(config: {\n onSwipe?: (e: SwipeEvent) => void\n direction?: 'any' | SwipeDirection\n minVelocity?: number\n minDistance?: number\n}): Recognizer<TapState> {\n const want = config.direction ?? 'any'\n const minVelocity = config.minVelocity ?? 0.3\n const minDistance = config.minDistance ?? 30\n const pointers = new Map<number, Tracked>()\n const active$ = signal(false)\n const reset = (): void => {\n pointers.clear()\n active$.set(false)\n }\n const dominant = (p: Tracked): SwipeDirection => {\n const dx = p.x - p.startX\n const dy = p.y - p.startY\n if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? 'right' : 'left'\n return dy >= 0 ? 'down' : 'up'\n }\n return {\n state: { active: () => active$() },\n reset,\n handlers: {\n onPointerDown(e): void {\n const s = normalizePointer(e)\n pointers.set(s.pointerId, newTracked(s))\n active$.set(true)\n },\n onPointerMove(e): void {\n const s = normalizePointer(e)\n const p = pointers.get(s.pointerId)\n if (p) track(p, s)\n },\n onPointerUp(e): void {\n const s = normalizePointer(e)\n const p = pointers.get(s.pointerId)\n if (p) {\n track(p, s)\n const speed = Math.hypot(p.vx, p.vy)\n const moved = dist(p.x, p.y, p.startX, p.startY)\n const dir = dominant(p)\n if (speed >= minVelocity && moved >= minDistance && (want === 'any' || want === dir)) {\n config.onSwipe?.({ direction: dir, velocityX: p.vx, velocityY: p.vy, x: p.x, y: p.y })\n }\n }\n pointers.delete(s.pointerId) // per-pointer: another finger can still produce its own swipe\n if (pointers.size === 0) active$.set(false)\n },\n onPointerCancel(e): void {\n const s = normalizePointer(e)\n pointers.delete(s.pointerId)\n if (pointers.size === 0) active$.set(false)\n },\n },\n }\n}\n\n// --- Composition ----------------------------------------------------------------------------------\n\n/**\n * Merge several recognizers into ONE so they can attach to a single element — required because the\n * renderer binds a single listener per event name (spreading two `onPointerMove`s would drop one).\n * `simultaneous` (the default): every recognizer sees every event independently (e.g. pan + pinch\n * together). Per-recognizer slop already disambiguates tap-vs-pan; an explicit exclusive arena is a\n * follow-up.\n */\nexport function composeGestures(recognizers: readonly Recognizer<never>[]): Recognizer<never> {\n const fan =\n (key: keyof GestureHandlers) =>\n (e: unknown): void => {\n let firstError: unknown\n batch(() => {\n // Isolate each recognizer: one throwing must not skip the rest (e.g. leaving a sibling's\n // long-press timer armed). Collect the first error and rethrow after all have run.\n for (const r of recognizers) {\n try {\n r.handlers[key](e)\n } catch (err) {\n if (firstError === undefined) firstError = err\n }\n }\n })\n if (firstError !== undefined) throw firstError\n }\n return {\n state: {} as never,\n reset: () => {\n for (const r of recognizers) r.reset()\n },\n handlers: {\n onPointerDown: fan('onPointerDown'),\n onPointerMove: fan('onPointerMove'),\n onPointerUp: fan('onPointerUp'),\n onPointerCancel: fan('onPointerCancel'),\n },\n }\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ import { ThreadPool, WorkerLike, WorkerPoolOptions, createInlineThreadPool, crea
14
14
  /** The npm package name. */
15
15
  declare const name = "@mindees/core";
16
16
  /** The package version. All `@mindees/*` packages share one locked version line. */
17
- declare const VERSION = "0.22.2";
17
+ declare const VERSION = "0.22.4";
18
18
  /**
19
19
  * Current maturity of this package. See the repository `STATUS.md`.
20
20
  *
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import { createInlineThreadPool, createNativeThreadPool, createWorkerPool } from
12
12
  /** The npm package name. */
13
13
  const name = "@mindees/core";
14
14
  /** The package version. All `@mindees/*` packages share one locked version line. */
15
- const VERSION = "0.22.2";
15
+ const VERSION = "0.22.4";
16
16
  /**
17
17
  * Current maturity of this package. See the repository `STATUS.md`.
18
18
  *
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from './types'\n\n/**\n * Animation engine: reactive animated values + timing/spring drivers + interpolate, driven by an\n * injected frame source (RN Animated/Reanimated + Flutter AnimationController parity).\n */\nexport {\n _activeAnimationCount,\n _resetAnimation,\n type AnimatedValue,\n type AnimationHandle,\n animate,\n cubicBezier,\n type Easing,\n easeInOutQuad,\n easeInQuad,\n easeOutCubic,\n easeOutQuad,\n type FrameSource,\n getFrameSource,\n interpolate,\n linear,\n manualFrameSource,\n rafFrameSource,\n setFrameSource,\n spring,\n timing,\n} from './animation'\n/**\n * Component model: a renderer-agnostic element tree plus selector-based,\n * re-render-isolated context. (Phase 2)\n */\nexport {\n type Component,\n type Context,\n type ContextProvider,\n createContext,\n createElement,\n createProvider,\n ELEMENT_TYPE,\n type ElementType,\n Fragment,\n hasOwner,\n isElement,\n isKeyedRegion,\n isPortal,\n KEYED_REGION,\n type KeyedRegion,\n type KeyedRegionOptions,\n keyedRegion,\n type MindeesElement,\n type MindeesNode,\n PORTAL,\n type PortalRegion,\n portal,\n renderComponent,\n type SelectorEquals,\n} from './component'\nexport { NotImplementedError } from './errors'\n/**\n * Gesture recognizers: tap/longPress/pan/pinch/swipe → reactive state that drives styles and the\n * animation engine (RN Gesture Handler / Flutter GestureDetector parity).\n */\nexport {\n _setGestureClock,\n composeGestures,\n type GestureHandlers,\n longPress,\n normalizePointer,\n type PanEvent,\n type PanState,\n type PinchEvent,\n type PinchState,\n type PointerSample,\n pan,\n panAnimated,\n pinch,\n type Recognizer,\n type SwipeDirection,\n type SwipeEvent,\n swipe,\n type TapState,\n tap,\n} from './gesture'\nexport { notImplemented } from './not-implemented'\n/**\n * Fine-grained reactivity: signals, computed values, effects, batching, and\n * disposal scopes. This is the reactive core of MindeesNative.\n */\nexport {\n type Accessor,\n batch,\n type ComputedOptions,\n computed,\n createRoot,\n deferred,\n type EffectOptions,\n type EqualsFn,\n effect,\n getOwner,\n type Memo,\n memo,\n type Owner,\n on,\n onCleanup,\n runWithOwner,\n type Signal,\n type SignalOptions,\n setReactiveScheduler,\n signal,\n startTransition,\n untrack,\n} from './reactive'\n/**\n * Priority scheduler: two-lane (sync/normal), microtask-batched, with\n * cancellable and dedupable tasks. (Phase 2)\n */\nexport {\n createScheduler,\n type Priority,\n type ScheduledTask,\n type ScheduleOptions,\n Scheduler,\n type SchedulerOptions,\n type Task,\n} from './scheduler'\n/**\n * Threading abstraction: a {@link ThreadPool} contract with a working Web Worker\n * backend and an inline fallback. Native multi-threading is a research track. (Phase 2)\n */\nexport {\n createInlineThreadPool,\n createNativeThreadPool,\n createWorkerPool,\n type ThreadPool,\n type WorkerLike,\n type WorkerPoolOptions,\n} from './threading'\nexport type { Maturity, PackageInfo } from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/core'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.22.2'\n\n/**\n * Current maturity of this package. See the repository `STATUS.md`.\n *\n * The reactivity layer (signals/computed/effect/batch), the component model with\n * selector-isolated context, the priority scheduler, and the thread-pool\n * abstraction (Web Worker + inline) are all implemented and tested. Native\n * multi-threading remains a research track (throws `NotImplementedError`).\n */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n"],"mappings":";;;;;;;;;;;;AA6IA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;;;AAUvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from './types'\n\n/**\n * Animation engine: reactive animated values + timing/spring drivers + interpolate, driven by an\n * injected frame source (RN Animated/Reanimated + Flutter AnimationController parity).\n */\nexport {\n _activeAnimationCount,\n _resetAnimation,\n type AnimatedValue,\n type AnimationHandle,\n animate,\n cubicBezier,\n type Easing,\n easeInOutQuad,\n easeInQuad,\n easeOutCubic,\n easeOutQuad,\n type FrameSource,\n getFrameSource,\n interpolate,\n linear,\n manualFrameSource,\n rafFrameSource,\n setFrameSource,\n spring,\n timing,\n} from './animation'\n/**\n * Component model: a renderer-agnostic element tree plus selector-based,\n * re-render-isolated context. (Phase 2)\n */\nexport {\n type Component,\n type Context,\n type ContextProvider,\n createContext,\n createElement,\n createProvider,\n ELEMENT_TYPE,\n type ElementType,\n Fragment,\n hasOwner,\n isElement,\n isKeyedRegion,\n isPortal,\n KEYED_REGION,\n type KeyedRegion,\n type KeyedRegionOptions,\n keyedRegion,\n type MindeesElement,\n type MindeesNode,\n PORTAL,\n type PortalRegion,\n portal,\n renderComponent,\n type SelectorEquals,\n} from './component'\nexport { NotImplementedError } from './errors'\n/**\n * Gesture recognizers: tap/longPress/pan/pinch/swipe → reactive state that drives styles and the\n * animation engine (RN Gesture Handler / Flutter GestureDetector parity).\n */\nexport {\n _setGestureClock,\n composeGestures,\n type GestureHandlers,\n longPress,\n normalizePointer,\n type PanEvent,\n type PanState,\n type PinchEvent,\n type PinchState,\n type PointerSample,\n pan,\n panAnimated,\n pinch,\n type Recognizer,\n type SwipeDirection,\n type SwipeEvent,\n swipe,\n type TapState,\n tap,\n} from './gesture'\nexport { notImplemented } from './not-implemented'\n/**\n * Fine-grained reactivity: signals, computed values, effects, batching, and\n * disposal scopes. This is the reactive core of MindeesNative.\n */\nexport {\n type Accessor,\n batch,\n type ComputedOptions,\n computed,\n createRoot,\n deferred,\n type EffectOptions,\n type EqualsFn,\n effect,\n getOwner,\n type Memo,\n memo,\n type Owner,\n on,\n onCleanup,\n runWithOwner,\n type Signal,\n type SignalOptions,\n setReactiveScheduler,\n signal,\n startTransition,\n untrack,\n} from './reactive'\n/**\n * Priority scheduler: two-lane (sync/normal), microtask-batched, with\n * cancellable and dedupable tasks. (Phase 2)\n */\nexport {\n createScheduler,\n type Priority,\n type ScheduledTask,\n type ScheduleOptions,\n Scheduler,\n type SchedulerOptions,\n type Task,\n} from './scheduler'\n/**\n * Threading abstraction: a {@link ThreadPool} contract with a working Web Worker\n * backend and an inline fallback. Native multi-threading is a research track. (Phase 2)\n */\nexport {\n createInlineThreadPool,\n createNativeThreadPool,\n createWorkerPool,\n type ThreadPool,\n type WorkerLike,\n type WorkerPoolOptions,\n} from './threading'\nexport type { Maturity, PackageInfo } from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/core'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.22.4'\n\n/**\n * Current maturity of this package. See the repository `STATUS.md`.\n *\n * The reactivity layer (signals/computed/effect/batch), the component model with\n * selector-isolated context, the priority scheduler, and the thread-pool\n * abstraction (Web Worker + inline) are all implemented and tested. Native\n * multi-threading remains a research track (throws `NotImplementedError`).\n */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n"],"mappings":";;;;;;;;;;;;AA6IA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;;;AAUvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindees/core",
3
- "version": "0.22.2",
3
+ "version": "0.22.4",
4
4
  "description": "MindeesNative core — fine-grained reactivity (signals/computed/effect/batch), component model with selector-isolated context, priority scheduler, and a thread-pool abstraction (Web Worker + inline; native is a research track).",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",