@n-uf/motion 26.11.4 → 26.11.6

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,"sources":["../src/layout/motion.ts","../src/layout/held.ts","../src/layout/flip.ts","../src/layout/dom.ts"],"sourcesContent":["import type { FrameInfo, FrameLoop, Phase } from \"../clock/frame-loop\";\nimport type { Rect } from \"../shapes\";\nimport { popIn, reveal, type PopIn, type RevealFrame } from \"../values/reveal\";\nimport { createSettle, SETTLE_TEMPO, type Settle } from \"../values/settle\";\n\n/**\n * What a {@link Drawable} draws on a frame: where the item is drawn, the box its content is laid out in, how to draw\n * the one at the other without stretching ({@link reveal}), and its appearance ({@link popIn}).\n */\nexport interface DrawFrame {\n /** Where the item is drawn this frame (its frame: background, border, shadow). */\n readonly drawn: Rect;\n /** The box its content is laid out in: where it is heading, or (held) where it was laid out last. */\n readonly box: Rect;\n /** How to draw `box`'s content at `drawn`: a move, the frame at the drawn size, the content clipped. */\n readonly reveal: RevealFrame;\n /** Its uniform appearance: scale about its centre and opacity. */\n readonly pop: PopIn;\n /** How shown it is, 0 (gone) to 1. */\n readonly shown: number;\n /** All the way gone: hidden and at rest (out of sight and out of the way of the pointer). */\n readonly gone: boolean;\n}\n\n/**\n * How an item reaches the screen. The layout motion never measures or touches anything itself: it lays an item's\n * content out once per new box and draws it once per frame through this. {@link domDrawable} writes an element's\n * style; a canvas or a test can draw anything else.\n */\nexport interface Drawable {\n /** Lay the content out in `box` (its position and size): called once per new box, never per frame. */\n layOut(box: Rect): void;\n /** Draw the item for this frame. */\n draw(frame: DrawFrame): void;\n}\n\n/**\n * One item the layout motion moves: give it a new place and it settles there from wherever it is drawn.\n *\n * - {@link LayoutItem.moveTo}: a settle to the new place, retargeted from the drawn rect (no jump, no overshoot);\n * the content is laid out at the new box at once and the item redrawn at once, so no frame shows the old offset\n * from the new box.\n * - A size change is revealed: the content is laid out at the target size and the frame clipped, never scaled.\n * - {@link LayoutItem.show}: appear or disappear with a uniform-scale fade.\n */\nexport interface LayoutItem {\n readonly id: string;\n /** Where it is drawn now. */\n readonly drawn: Rect;\n /** Where it is heading (its settle's target). */\n readonly target: Rect;\n /** The box its content is laid out in. */\n readonly box: Rect;\n /** At rest on its target. */\n readonly settled: boolean;\n /** How shown it is, 0 to 1, and whether it is heading for shown. */\n readonly shown: number;\n readonly showing: boolean;\n /** Its appearance is at rest (fully shown, or gone). */\n readonly shownSettled: boolean;\n /** Settle to `to` from where it is drawn; lays the content out at `to` and redraws at once. */\n moveTo(to: Rect): void;\n /** Be at `to` now, at rest: no glide. */\n jumpTo(to: Rect): void;\n /** Drawn at `from` now, then settle to `to` (a FLIP's invert and play: `from` in the item's own coordinates). */\n glideFrom(from: Rect, to: Rect): void;\n /** Exactly at `r`, at rest (what a pointer holds); the content stays laid out in its box, moved with it. */\n hold(r: Rect): void;\n /** Appear (true) or disappear (false) with a uniform-scale fade; `now`: without the fade. */\n show(on: boolean, options?: { readonly now?: boolean }): void;\n /** Draw it again now (after its drawable's element was laid out anew from outside). */\n redraw(): void;\n}\n\n/** A task the layout motion runs on every frame before it steps the items; true keeps the frames coming. */\nexport type LayoutTask = (frame: FrameInfo) => boolean | void;\n\nexport interface LayoutMotionOptions {\n /** The frame loop every item steps on (one loop for all of them). */\n readonly loop: FrameLoop;\n /** The settle tempo, per second ({@link SETTLE_TEMPO}); default `SETTLE_TEMPO.default`. */\n readonly omega?: number;\n /** The frame phase the items step and draw in; default `\"layout\"`. */\n readonly phase?: Phase;\n /** Where an appearing item grows from, as a share of its size ({@link popIn}'s `from`); default 0.87. */\n readonly popFrom?: number;\n}\n\n/**\n * Layout motion: items with an id and a target rect, each settled to its place on one shared frame loop.\n *\n * Give an item a new place ({@link LayoutItem.moveTo}) and it glides there from wherever it is drawn: the window\n * motion rules of `values/` (settle, reveal, popIn) applied to every item at once. The loop runs only while\n * something moves, or a task asks for frames, and stops by itself.\n *\n * It only animates the rects it is given: which items move where, and when, is the caller's layout.\n */\nexport interface LayoutMotion {\n /** Add an item drawn by `drawable`, at `at`, shown (or gone: `{ shown: false }`). Its content is laid out at once. */\n add(id: string, drawable: Drawable, at: Rect, options?: { readonly shown?: boolean }): LayoutItem;\n get(id: string): LayoutItem | undefined;\n /** Stop moving an item (its drawable is not touched again). */\n remove(id: string): void;\n readonly items: ReadonlyMap<string, LayoutItem>;\n /** Run `task` on every frame, before the items step; returns how to stop it. */\n task(task: LayoutTask): () => void;\n /** Call `listener` on every frame, after the items have stepped and been drawn; returns how to stop it. */\n onFrame(listener: (frame: FrameInfo) => void): () => void;\n /** Call `listener` each time the frames stop (nothing moves); returns how to stop it. */\n onIdle(listener: () => void): () => void;\n /** Make sure at least one more frame runs (a task has something to do). */\n wake(): void;\n /** Frames are running. */\n readonly running: boolean;\n /** Set the tempo for every item (and the ones added later). */\n configure(config: { readonly omega?: number }): void;\n /** Resolves once nothing moves (at once if nothing does). */\n idle(): Promise<void>;\n /** Stop the frames and forget every item. */\n dispose(): void;\n}\n\n/** Two rects the same within a hundredth of a pixel. */\nconst same = (a: Rect, b: Rect): boolean =>\n Math.abs(a.x - b.x) < 0.01 && Math.abs(a.y - b.y) < 0.01 && Math.abs(a.width - b.width) < 0.01 && Math.abs(a.height - b.height) < 0.01;\nconst copy = (r: Rect): Rect => ({ x: r.x, y: r.y, width: r.width, height: r.height });\n\n/** Create the {@link LayoutMotion} for one surface. */\nexport function createLayoutMotion(options: LayoutMotionOptions): LayoutMotion {\n const { loop } = options;\n const phase = options.phase ?? \"layout\";\n const popFrom = options.popFrom ?? 0.87;\n let omega = options.omega ?? SETTLE_TEMPO.default;\n const items = new Map<string, LayoutItem>();\n const steps = new Map<string, (dt: number) => boolean>();\n const tasks = new Set<LayoutTask>();\n const frames = new Set<(frame: FrameInfo) => void>();\n const idles = new Set<() => void>();\n const tempos = new Set<() => void>();\n /** What removing an item undoes besides its entry (its tempo hook). */\n const removers = new Map<string, () => void>();\n let waiters: (() => void)[] = [];\n let stop: (() => void) | null = null;\n let woken = false;\n\n const wake = (): void => {\n woken = true;\n if (stop) return;\n stop = loop.repeat(phase, tick);\n };\n\n function tick(frame: FrameInfo): boolean {\n let moving = woken;\n woken = false;\n for (const task of [...tasks]) if (task(frame)) moving = true;\n for (const step of [...steps.values()]) if (step(frame.delta)) moving = true;\n for (const listener of [...frames]) listener(frame);\n if (moving || woken) return true;\n stop = null;\n const resolve = waiters;\n waiters = [];\n for (const r of resolve) r();\n for (const listener of [...idles]) listener();\n return false;\n }\n\n function add(id: string, drawable: Drawable, at: Rect, opts: { readonly shown?: boolean } = {}): LayoutItem {\n if (items.has(id)) throw new Error(`layout motion: item \"${id}\" is already added (remove it first)`);\n const rect: Settle<Rect> = createSettle<Rect>(copy(at), { omega });\n // A number from 0 to 1: rest within a thousandth, not the default tenth (tuned for pixels).\n const pop: Settle<number> = createSettle(opts.shown === false ? 0 : 1, { omega, restDelta: 0.001 });\n let box = copy(at);\n const frameOf = (): DrawFrame => {\n const drawn = rect.value;\n return { drawn, box, reveal: reveal(drawn, box), pop: popIn(pop.value, popFrom), shown: pop.value, gone: pop.settled && pop.value === 0 };\n };\n const draw = (): void => drawable.draw(frameOf());\n const layOut = (to: Rect): void => {\n box = copy(to);\n drawable.layOut(box);\n };\n const tempo = (): void => {\n rect.configure({ omega });\n pop.configure({ omega });\n };\n const item: LayoutItem = {\n id,\n get drawn() {\n return rect.value;\n },\n get target() {\n return rect.target;\n },\n get box() {\n return box;\n },\n get settled() {\n return rect.settled;\n },\n get shown() {\n return pop.value;\n },\n get showing() {\n return pop.target === 1;\n },\n get shownSettled() {\n return pop.settled;\n },\n moveTo(to) {\n if (same(box, to) && same(rect.target, to)) return;\n layOut(to);\n // Laid out in the new box now: draw it at once at the drawn rect, else until the next frame it would show\n // the old offset from the new box (a dropped item would flash a frame away from where it was let go).\n draw();\n if (same(rect.target, to)) return;\n rect.setTarget(copy(to));\n wake();\n },\n jumpTo(to) {\n layOut(to);\n rect.jumpTo(copy(to));\n draw();\n },\n glideFrom(from, to) {\n rect.jumpTo(copy(from));\n layOut(to);\n draw();\n rect.setTarget(copy(to));\n wake();\n },\n hold(r) {\n rect.hold(copy(r));\n draw();\n },\n show(on, o = {}) {\n const want = on ? 1 : 0;\n if (o.now) pop.jumpTo(want);\n else if (pop.target !== want) {\n pop.setTarget(want);\n wake();\n }\n draw();\n },\n redraw: draw,\n };\n drawable.layOut(box);\n draw();\n items.set(id, item);\n tempos.add(tempo);\n steps.set(id, (dt) => {\n const a = rect.step(dt);\n const b = pop.step(dt);\n draw();\n return a || b;\n });\n removers.set(id, () => tempos.delete(tempo));\n return item;\n }\n\n return {\n add,\n get: (id) => items.get(id),\n remove(id) {\n items.delete(id);\n steps.delete(id);\n removers.get(id)?.();\n removers.delete(id);\n },\n items,\n task(task) {\n tasks.add(task);\n wake();\n return () => tasks.delete(task);\n },\n onFrame(listener) {\n frames.add(listener);\n return () => frames.delete(listener);\n },\n onIdle(listener) {\n idles.add(listener);\n return () => idles.delete(listener);\n },\n wake,\n get running() {\n return stop !== null;\n },\n configure(config) {\n if (config.omega !== undefined) omega = config.omega;\n for (const t of tempos) t();\n },\n idle: () => (stop ? new Promise<void>((r) => waiters.push(r)) : Promise.resolve()),\n dispose() {\n stop?.();\n stop = null;\n items.clear();\n steps.clear();\n tasks.clear();\n frames.clear();\n idles.clear();\n tempos.clear();\n removers.clear();\n const resolve = waiters;\n waiters = [];\n for (const r of resolve) r();\n },\n };\n}\n","import type { Point, Rect } from \"../shapes\";\nimport type { LayoutItem } from \"./motion\";\n\n/**\n * An item held by a pointer: the real item (no copy) follows the pointer 1:1 from where it was grabbed, at the size\n * it was lifted at; its place stays reserved (that is the caller's layout). Then:\n *\n * - {@link HeldDrag.snapTo}: glide into a slot (a snap preview); `null` leaves the slot and glides back to the\n * pointer, following it as it moves ({@link HeldDrag.follow} once a frame), until it is under it again.\n * - {@link HeldDrag.land}: let go into a slot: the landing settles from where it was let go (the release rect), never\n * from its old home. Snapped into that slot already, it simply finishes that glide.\n * - {@link HeldDrag.cancel}: let go with no drop: it glides home from where it was let go.\n * - {@link HeldDrag.preview}: the three drop previews, per frame: indicator and gap keep the item under the pointer\n * (gap with the caller's room open), snap glides it into the slot; leaving glides it back to the pointer.\n * - {@link HeldDrag.resize}: the item takes a new size mid-drag (its content laid out anew by the caller): it settles\n * to it from where it is drawn, the size revealed (never stretched), and is held 1:1 again once there.\n */\nexport interface HeldDrag {\n /** Where the pointer is (null before the first move). */\n readonly pointer: Point | null;\n /** Where the pointer holds the item: the pointer less the grab offset, at the held size (lifted, or resized). */\n readonly held: Rect | null;\n /** The slot it sits in (a snap), or null. */\n readonly snapped: Rect | null;\n /** It left a slot (or took a new size) and is gliding back to the pointer. */\n readonly returning: boolean;\n /** The pointer moved: held 1:1 (unless it sits in a slot or is on its way back). */\n move(pointer: Point): void;\n /**\n * Into `slot` (a glide from where it is drawn; the same slot again does nothing), or out of the slot it sits in\n * (`null`: back to the pointer). Returns what changed: `\"snapped\"`, `\"left\"`, or null.\n */\n snapTo(slot: Rect | null): \"snapped\" | \"left\" | null;\n /** Once a frame: on its way back, retarget to the pointer; true on the frame it is under the pointer again. */\n follow(): boolean;\n /**\n * Once a frame, a drop preview's part for the item (`plan`: `@n-uf/motion/drag`'s `previewPlan`, or any\n * `{ snapItem }`): with `snapItem` it glides into `slot` (snap); else it stays under the pointer, or glides back to\n * it from a slot it left (indicator, gap). The room, if the plan makes any, is the caller's layout. In place of\n * {@link HeldDrag.snapTo} and {@link HeldDrag.follow}; returns what changed.\n */\n preview(plan: { readonly snapItem: boolean }, slot: Rect | null): \"snapped\" | \"left\" | null;\n /**\n * Hold it at `size` from now on (the caller has laid its content out for that size). The grab point keeps its share\n * of the item, so the pointer stays on the same spot of it; the item settles from where it is drawn to its new held\n * rect (its size revealed, never stretched), then is held 1:1 again ({@link HeldDrag.follow} once a frame). In a\n * slot it stays there, taking the new size when it leaves. The same size again does nothing; returns true on a\n * change.\n */\n resize(size: { readonly width: number; readonly height: number }): boolean;\n /** Let go into `slot`: returns the release rect, where the landing starts. */\n land(slot: Rect): Rect;\n /** Let go with no drop: glides `home` from the release rect, which it returns. */\n cancel(home: Rect): Rect;\n}\n\n/** How close (px, every component) the way back has to come to count as under the pointer again. */\nconst CAUGHT = 0.5;\n\n/** Hold `item` from the pointer at `grab` (the pointer's offset from its top-left corner), at its drawn size now. */\nexport function holdItem(item: LayoutItem, grab: Point): HeldDrag {\n let size = { width: item.drawn.width, height: item.drawn.height };\n let at = { x: grab.x, y: grab.y };\n let pointer: Point | null = null;\n let snapped: Rect | null = null;\n let returning = false;\n const heldRect = (): Rect | null => (pointer ? { x: pointer.x - at.x, y: pointer.y - at.y, width: size.width, height: size.height } : null);\n const same = (a: Rect, b: Rect): boolean => Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y), Math.abs(a.width - b.width), Math.abs(a.height - b.height)) < 0.01;\n const snapTo = (slot: Rect | null): \"snapped\" | \"left\" | null => {\n if (slot) {\n if (snapped && same(snapped, slot)) return null;\n snapped = { ...slot };\n returning = false;\n item.moveTo(slot);\n return \"snapped\";\n }\n if (!snapped) return null;\n snapped = null;\n returning = true;\n return \"left\";\n };\n const follow = (): boolean => {\n const goal = heldRect();\n if (!returning || !goal) return false;\n item.moveTo(goal);\n const d = item.drawn;\n if (Math.max(Math.abs(d.x - goal.x), Math.abs(d.y - goal.y), Math.abs(d.width - goal.width), Math.abs(d.height - goal.height)) >= CAUGHT) return false;\n returning = false;\n item.hold(goal);\n return true;\n };\n const letGo = (to: Rect): Rect => {\n const release = { ...item.drawn };\n snapped = null;\n returning = false;\n item.moveTo(to);\n return release;\n };\n return {\n get pointer() {\n return pointer;\n },\n get held() {\n return heldRect();\n },\n get snapped() {\n return snapped;\n },\n get returning() {\n return returning;\n },\n move(p) {\n pointer = { x: p.x, y: p.y };\n if (snapped || returning) return;\n item.hold(heldRect()!);\n },\n snapTo,\n follow,\n preview(plan, slot) {\n const change = snapTo(plan.snapItem && slot ? slot : null);\n follow();\n return change;\n },\n resize(next) {\n if (Math.abs(next.width - size.width) < 0.01 && Math.abs(next.height - size.height) < 0.01) return false;\n at = { x: size.width ? (at.x / size.width) * next.width : 0, y: size.height ? (at.y / size.height) * next.height : 0 };\n size = { width: next.width, height: next.height };\n if (!snapped && pointer) {\n returning = true;\n follow();\n }\n return true;\n },\n land: letGo,\n cancel: letGo,\n };\n}\n","import type { Rect } from \"../shapes\";\nimport type { LayoutItem } from \"./motion\";\n\n/** A rect as a DOM rect reports it (`getBoundingClientRect()`): left, top, width, height. */\nexport interface RectLike {\n readonly left: number;\n readonly top: number;\n readonly width: number;\n readonly height: number;\n}\n\n/** Anything that can say where it is drawn: a DOM node, or a test's stand-in. */\nexport interface Measurable {\n getBoundingClientRect(): RectLike;\n}\n\n/** A {@link RectLike} as a {@link Rect}. */\nexport const rectOf = (r: RectLike): Rect => ({ x: r.left, y: r.top, width: r.width, height: r.height });\n\n/** One member of a {@link flip}: an item, the node it draws, and where it goes once the DOM has changed. */\nexport interface FlipEntry {\n readonly item: LayoutItem;\n readonly node: Measurable;\n /**\n * Once the DOM has changed: the old rect (as measured before, in page coordinates) in the item's own coordinates\n * now, and its new place there.\n */\n readonly place: (before: Rect) => { readonly from: Rect; readonly to: Rect };\n}\n\n/**\n * FLIP for DOM groups, on settle: record where each node is drawn (first), change the DOM (`change`: move nodes to\n * other parents, reorder them), then each item is drawn at its old rect and settles to its new place (last, invert,\n * play). No transform tween: a settle from the rect it was drawn at, so it never jumps or overshoots, and its size\n * changes through reveal.\n */\nexport function flip(entries: readonly FlipEntry[], change: () => void): void {\n const before = entries.map((e) => rectOf(e.node.getBoundingClientRect()));\n change();\n entries.forEach((e, i) => {\n const { from, to } = e.place(before[i]!);\n e.item.glideFrom(from, to);\n });\n}\n","import type { Rect } from \"../shapes\";\nimport type { Drawable, DrawFrame } from \"./motion\";\n\n/**\n * The style properties the layout motion writes, as strings (a DOM node's `style` has them all). Structural: the\n * library needs no DOM types.\n */\nexport interface StyleLike {\n left: string;\n top: string;\n width: string;\n height: string;\n transform: string;\n opacity: string;\n clipPath: string;\n visibility: string;\n}\n\n/** Something with a writable {@link StyleLike} `style`: a DOM node. */\nexport interface Styled {\n readonly style: StyleLike;\n}\n\n/**\n * The nodes of a drawn box: `node` is laid out at the box and moved to the drawn rect; `frame` (background, border,\n * shadow) is drawn at the drawn size; `content` is clipped to it. With no `frame` and `content`, `node` itself is\n * clipped (its content laid out at the box, never stretched).\n */\nexport interface BoxNodes {\n readonly node: Styled;\n readonly frame?: Styled;\n readonly content?: Styled;\n}\n\n/** Lay `node` out at `box`: its position and size, in its offset parent's coordinates. */\nexport function layOutBox(node: Styled, box: Rect): void {\n const s = node.style;\n s.left = `${box.x}px`;\n s.top = `${box.y}px`;\n s.width = `${box.width}px`;\n s.height = `${box.height}px`;\n}\n\n/**\n * Draw one frame of a box laid out by {@link layOutBox}: one transform (the move to the drawn rect, and the uniform\n * appearance about its centre), the frame at the drawn size, the content clipped (reveal); hidden when gone.\n */\nexport function drawBox(nodes: BoxNodes, frame: DrawFrame): void {\n const { reveal: r, pop } = frame;\n const cx = frame.drawn.width / 2;\n const cy = frame.drawn.height / 2;\n const s = nodes.node.style;\n s.transform =\n pop.scale === 1 ? `translate(${r.dx}px, ${r.dy}px)` : `translate(${r.dx}px, ${r.dy}px) translate(${cx}px, ${cy}px) scale(${pop.scale}) translate(${-cx}px, ${-cy}px)`;\n s.opacity = pop.opacity === 1 ? \"\" : String(pop.opacity);\n if (nodes.frame) {\n nodes.frame.style.width = `${r.width}px`;\n nodes.frame.style.height = `${r.height}px`;\n }\n const clipped = nodes.content ?? (nodes.frame ? null : nodes.node);\n if (clipped) clipped.style.clipPath = r.clipRight || r.clipBottom ? `inset(0 ${r.clipRight}px ${r.clipBottom}px 0)` : \"\";\n // All the way gone: out of sight and out of the way of the pointer.\n s.visibility = frame.gone ? \"hidden\" : \"\";\n}\n\n/** A {@link Drawable} for DOM nodes: {@link layOutBox} on a new box, {@link drawBox} on every frame. */\nexport function domDrawable(nodes: BoxNodes): Drawable {\n return {\n layOut: (box) => layOutBox(nodes.node, box),\n draw: (frame) => drawBox(nodes, frame),\n };\n}\n"],"mappings":"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","names":["same"]}
1
+ {"version":3,"sources":["../src/layout/motion.ts","../src/layout/held.ts","../src/layout/flip.ts","../src/layout/dom.ts"],"sourcesContent":["import type { FrameInfo, FrameLoop, Phase } from \"../clock/frame-loop\";\nimport type { Rect } from \"../shapes\";\nimport { popIn, reveal, type PopIn, type RevealFrame } from \"../values/reveal\";\nimport { createSettle, SETTLE_TEMPO, type Settle } from \"../values/settle\";\n\n/**\n * What a {@link Drawable} draws on a frame: where the item is drawn, the box its content is laid out in, how to draw\n * the one at the other without stretching ({@link reveal}), and its appearance ({@link popIn}).\n */\nexport interface DrawFrame {\n /** Where the item is drawn this frame (its frame: background, border, shadow). */\n readonly drawn: Rect;\n /** The box its content is laid out in: where it is heading, or (held) where it was laid out last. */\n readonly box: Rect;\n /** How to draw `box`'s content at `drawn`: a move, the frame at the drawn size, the content clipped. */\n readonly reveal: RevealFrame;\n /** Its uniform appearance: scale about its centre and opacity. */\n readonly pop: PopIn;\n /** How shown it is, 0 (gone) to 1. */\n readonly shown: number;\n /** All the way gone: hidden and at rest (out of sight and out of the way of the pointer). */\n readonly gone: boolean;\n}\n\n/**\n * How an item reaches the screen. The layout motion never measures or touches anything itself: it lays an item's\n * content out once per new box and draws it once per frame through this. {@link domDrawable} writes an element's\n * style; a canvas or a test can draw anything else.\n */\nexport interface Drawable {\n /** Lay the content out in `box` (its position and size): called once per new box, never per frame. */\n layOut(box: Rect): void;\n /** Draw the item for this frame. */\n draw(frame: DrawFrame): void;\n}\n\n/**\n * One item the layout motion moves: give it a new place and it settles there from wherever it is drawn.\n *\n * - {@link LayoutItem.moveTo}: a settle to the new place, retargeted from the drawn rect (no jump, no overshoot);\n * the content is laid out at the new box at once and the item redrawn at once, so no frame shows the old offset\n * from the new box.\n * - A size change is revealed: the content is laid out at the target size and the frame clipped, never scaled.\n * - {@link LayoutItem.show}: appear or disappear with a uniform-scale fade.\n */\nexport interface LayoutItem {\n readonly id: string;\n /** Where it is drawn now. */\n readonly drawn: Rect;\n /** Where it is heading (its settle's target). */\n readonly target: Rect;\n /** The box its content is laid out in. */\n readonly box: Rect;\n /** At rest on its target. */\n readonly settled: boolean;\n /** How shown it is, 0 to 1, and whether it is heading for shown. */\n readonly shown: number;\n readonly showing: boolean;\n /** Its appearance is at rest (fully shown, or gone). */\n readonly shownSettled: boolean;\n /** Settle to `to` from where it is drawn; lays the content out at `to` and redraws at once. */\n moveTo(to: Rect): void;\n /**\n * Settle to `to` from where it is drawn, like {@link LayoutItem.moveTo}, but keep the content laid out in its box\n * while `to` is the box's size: for chasing a moving point (a pointer), which would otherwise lay the content out\n * again on every frame. A new size lays it out at `to`, as `moveTo` does. Optional, so an item from before it (or\n * a test double) still works where it is used: {@link holdItem} falls back to `moveTo`. `createLayoutMotion`'s\n * items always have it.\n */\n retarget?(to: Rect): void;\n /** Be at `to` now, at rest: no glide. */\n jumpTo(to: Rect): void;\n /** Drawn at `from` now, then settle to `to` (a FLIP's invert and play: `from` in the item's own coordinates). */\n glideFrom(from: Rect, to: Rect): void;\n /** Exactly at `r`, at rest (what a pointer holds); the content stays laid out in its box, moved with it. */\n hold(r: Rect): void;\n /** Appear (true) or disappear (false) with a uniform-scale fade; `now`: without the fade. */\n show(on: boolean, options?: { readonly now?: boolean }): void;\n /** Draw it again now (after its drawable's element was laid out anew from outside). */\n redraw(): void;\n}\n\n/** A task the layout motion runs on every frame before it steps the items; true keeps the frames coming. */\nexport type LayoutTask = (frame: FrameInfo) => boolean | void;\n\n/** One item's frame in a batch ({@link LayoutMotionOptions.onDraw}). */\nexport interface DrawEntry {\n readonly id: string;\n readonly frame: DrawFrame;\n}\n\nexport interface LayoutMotionOptions {\n /** The frame loop every item steps on (one loop for all of them). */\n readonly loop: FrameLoop;\n /** The settle tempo, per second ({@link SETTLE_TEMPO}); default `SETTLE_TEMPO.default`. */\n readonly omega?: number;\n /** The frame phase the items step and draw in; default `\"layout\"`. */\n readonly phase?: Phase;\n /** Where an appearing item grows from, as a share of its size ({@link popIn}'s `from`); default 0.87. */\n readonly popFrom?: number;\n /**\n * Reduced motion: read on every new target and every show or hide. While true an item is placed on its new\n * target at once (no glide) and appears or disappears at once (no fade). A boolean, or the host's query (such as\n * the reduced-motion media query). Default false.\n */\n readonly reducedMotion?: boolean | (() => boolean);\n /**\n * Batched drawing: every item that changed is drawn in one pass per frame, after the items step: each one's\n * drawable, then this, once, with all of their frames. Changes made between frames (a new target, a hold) are\n * drawn on the next frame, or now by {@link LayoutMotion.flush}. Without it, each item is drawn as it changes.\n */\n readonly onDraw?: (batch: readonly DrawEntry[]) => void;\n /** Called when frames start running (true) and when they stop (false), once per change. */\n readonly onActiveChange?: (active: boolean) => void;\n}\n\n/**\n * Layout motion: items with an id and a target rect, each settled to its place on one shared frame loop.\n *\n * Give an item a new place ({@link LayoutItem.moveTo}) and it glides there from wherever it is drawn: the window\n * motion rules of `values/` (settle, reveal, popIn) applied to every item at once. The loop runs only while\n * something moves, or a task asks for frames, and stops by itself.\n *\n * It only animates the rects it is given: which items move where, and when, is the caller's layout.\n */\nexport interface LayoutMotion {\n /** Add an item drawn by `drawable`, at `at`, shown (or gone: `{ shown: false }`). Its content is laid out at once. */\n add(id: string, drawable: Drawable, at: Rect, options?: { readonly shown?: boolean }): LayoutItem;\n get(id: string): LayoutItem | undefined;\n /** Stop moving an item (its drawable is not touched again). */\n remove(id: string): void;\n /** Remove every item whose id is not in `ids` (the ones that left the layout). */\n retain(ids: Iterable<string>): void;\n /**\n * With {@link LayoutMotionOptions.onDraw}: draw the items changed since the last frame now, outside the loop. A\n * host calls it right after it commits a new layout, so the next paint shows every item where it is drawn, not a\n * frame at its new box. Without batching each item is already drawn as it changes, and it does nothing.\n */\n flush(): void;\n readonly items: ReadonlyMap<string, LayoutItem>;\n /** Run `task` on every frame, before the items step; returns how to stop it. */\n task(task: LayoutTask): () => void;\n /** Call `listener` on every frame, after the items have stepped and been drawn; returns how to stop it. */\n onFrame(listener: (frame: FrameInfo) => void): () => void;\n /** Call `listener` each time the frames stop (nothing moves); returns how to stop it. */\n onIdle(listener: () => void): () => void;\n /** Make sure at least one more frame runs (a task has something to do). */\n wake(): void;\n /** Frames are running. */\n readonly running: boolean;\n /** Set the tempo for every item (and the ones added later). */\n configure(config: { readonly omega?: number }): void;\n /** Resolves once nothing moves (at once if nothing does). */\n idle(): Promise<void>;\n /** Stop the frames and forget every item. */\n dispose(): void;\n}\n\n/** Two rects the same within a hundredth of a pixel. */\nconst same = (a: Rect, b: Rect): boolean =>\n Math.abs(a.x - b.x) < 0.01 && Math.abs(a.y - b.y) < 0.01 && Math.abs(a.width - b.width) < 0.01 && Math.abs(a.height - b.height) < 0.01;\nconst copy = (r: Rect): Rect => ({ x: r.x, y: r.y, width: r.width, height: r.height });\n\n/** Create the {@link LayoutMotion} for one surface. */\nexport function createLayoutMotion(options: LayoutMotionOptions): LayoutMotion {\n const { loop } = options;\n const phase = options.phase ?? \"layout\";\n const popFrom = options.popFrom ?? 0.87;\n let omega = options.omega ?? SETTLE_TEMPO.default;\n const items = new Map<string, LayoutItem>();\n const steps = new Map<string, (dt: number) => boolean>();\n const tasks = new Set<LayoutTask>();\n const frames = new Set<(frame: FrameInfo) => void>();\n const idles = new Set<() => void>();\n const tempos = new Set<() => void>();\n /** What removing an item undoes besides its entry (its tempo hook). */\n const removers = new Map<string, () => void>();\n let waiters: (() => void)[] = [];\n let stop: (() => void) | null = null;\n let woken = false;\n let ticking = false;\n let active = false;\n const reduced = (): boolean => (typeof options.reducedMotion === \"function\" ? options.reducedMotion() : options.reducedMotion === true);\n const { onDraw, onActiveChange } = options;\n /** Batched drawing: how to draw each item, and the ones that changed since the last batch. */\n const drawers = new Map<string, () => DrawEntry>();\n const dirty = new Set<string>();\n\n const setActive = (on: boolean): void => {\n if (on === active) return;\n active = on;\n onActiveChange?.(on);\n };\n\n const wake = (): void => {\n woken = true;\n if (stop) return;\n stop = loop.repeat(phase, tick);\n setActive(true);\n };\n\n /** Draw every item that changed, then hand the batch over once. */\n function writeBatch(): void {\n if (!onDraw || dirty.size === 0) return;\n const batch: DrawEntry[] = [];\n for (const id of dirty) {\n const entry = drawers.get(id)?.();\n if (entry) batch.push(entry);\n }\n dirty.clear();\n if (batch.length > 0) onDraw(batch);\n }\n\n function tick(frame: FrameInfo): boolean {\n let moving = woken;\n woken = false;\n ticking = true;\n for (const task of [...tasks]) if (task(frame)) moving = true;\n for (const step of [...steps.values()]) if (step(frame.delta)) moving = true;\n writeBatch();\n ticking = false;\n for (const listener of [...frames]) listener(frame);\n if (moving || woken) return true;\n stop = null;\n setActive(false);\n const resolve = waiters;\n waiters = [];\n for (const r of resolve) r();\n for (const listener of [...idles]) listener();\n return false;\n }\n\n function add(id: string, drawable: Drawable, at: Rect, opts: { readonly shown?: boolean } = {}): LayoutItem {\n if (items.has(id)) throw new Error(`layout motion: item \"${id}\" is already added (remove it first)`);\n const rect: Settle<Rect> = createSettle<Rect>(copy(at), { omega });\n // A number from 0 to 1: rest within a thousandth, not the default tenth (tuned for pixels).\n const pop: Settle<number> = createSettle(opts.shown === false ? 0 : 1, { omega, restDelta: 0.001 });\n let box = copy(at);\n const frameOf = (): DrawFrame => {\n const drawn = rect.value;\n return { drawn, box, reveal: reveal(drawn, box), pop: popIn(pop.value, popFrom), shown: pop.value, gone: pop.settled && pop.value === 0 };\n };\n const drawNow = (): DrawEntry => {\n const frame = frameOf();\n drawable.draw(frame);\n return { id, frame };\n };\n // Batched: mark it changed (the batch draws it); a change between frames asks for a frame to draw it in.\n const draw = (): void => {\n if (!onDraw) {\n drawNow();\n return;\n }\n dirty.add(id);\n if (!ticking) wake();\n };\n const layOut = (to: Rect): void => {\n box = copy(to);\n drawable.layOut(box);\n };\n /** Head for `to`: a settle from the drawn rect, or (reduced motion) there at once. */\n const aim = (to: Rect): void => {\n if (reduced()) {\n rect.jumpTo(copy(to));\n draw();\n return;\n }\n rect.setTarget(copy(to));\n wake();\n };\n /** It moved on the last step (so the step that brings it to rest still draws it, batched). */\n let moved = false;\n const tempo = (): void => {\n rect.configure({ omega });\n pop.configure({ omega });\n };\n const item: LayoutItem = {\n id,\n get drawn() {\n return rect.value;\n },\n get target() {\n return rect.target;\n },\n get box() {\n return box;\n },\n get settled() {\n return rect.settled;\n },\n get shown() {\n return pop.value;\n },\n get showing() {\n return pop.target === 1;\n },\n get shownSettled() {\n return pop.settled;\n },\n moveTo(to) {\n if (same(box, to) && same(rect.target, to)) return;\n layOut(to);\n // Laid out in the new box now: draw it at once at the drawn rect, else until the next frame it would show\n // the old offset from the new box (a dropped item would flash a frame away from where it was let go).\n draw();\n if (same(rect.target, to)) return;\n aim(to);\n },\n retarget(to) {\n if (to.width !== box.width || to.height !== box.height) {\n item.moveTo(to);\n return;\n }\n if (same(rect.target, to)) return;\n aim(to);\n },\n jumpTo(to) {\n layOut(to);\n rect.jumpTo(copy(to));\n draw();\n },\n glideFrom(from, to) {\n rect.jumpTo(copy(from));\n layOut(to);\n draw();\n aim(to);\n },\n hold(r) {\n rect.hold(copy(r));\n draw();\n },\n show(on, o = {}) {\n const want = on ? 1 : 0;\n if (o.now || reduced()) pop.jumpTo(want);\n else if (pop.target !== want) {\n pop.setTarget(want);\n wake();\n }\n draw();\n },\n redraw: draw,\n };\n drawers.set(id, drawNow);\n drawable.layOut(box);\n draw();\n items.set(id, item);\n tempos.add(tempo);\n steps.set(id, (dt) => {\n const a = rect.step(dt);\n const b = pop.step(dt);\n // Batched, only an item that moved is drawn; else every item, every frame.\n if (!onDraw || a || b || moved) draw();\n moved = a || b;\n return a || b;\n });\n removers.set(id, () => {\n tempos.delete(tempo);\n drawers.delete(id);\n dirty.delete(id);\n });\n return item;\n }\n\n function remove(id: string): void {\n items.delete(id);\n steps.delete(id);\n removers.get(id)?.();\n removers.delete(id);\n }\n\n return {\n add,\n get: (id) => items.get(id),\n remove,\n retain(ids) {\n const keep = new Set(ids);\n for (const id of [...items.keys()]) if (!keep.has(id)) remove(id);\n },\n flush: writeBatch,\n items,\n task(task) {\n tasks.add(task);\n wake();\n return () => tasks.delete(task);\n },\n onFrame(listener) {\n frames.add(listener);\n return () => frames.delete(listener);\n },\n onIdle(listener) {\n idles.add(listener);\n return () => idles.delete(listener);\n },\n wake,\n get running() {\n return stop !== null;\n },\n configure(config) {\n if (config.omega !== undefined) omega = config.omega;\n for (const t of tempos) t();\n },\n idle: () => (stop ? new Promise<void>((r) => waiters.push(r)) : Promise.resolve()),\n dispose() {\n stop?.();\n stop = null;\n setActive(false);\n items.clear();\n steps.clear();\n drawers.clear();\n dirty.clear();\n tasks.clear();\n frames.clear();\n idles.clear();\n tempos.clear();\n removers.clear();\n const resolve = waiters;\n waiters = [];\n for (const r of resolve) r();\n },\n };\n}\n","import type { Point, Rect } from \"../shapes\";\nimport type { LayoutItem } from \"./motion\";\n\n/**\n * An item held by a pointer: the real item (no copy) follows the pointer 1:1 from where it was grabbed, at the size\n * it was lifted at; its place stays reserved (that is the caller's layout). Then:\n *\n * - {@link HeldDrag.snapTo}: glide into a slot (a snap preview); `null` leaves the slot and glides back to the\n * pointer, following it as it moves ({@link HeldDrag.follow} once a frame), until it is under it again.\n * - {@link HeldDrag.land}: let go into a slot: the landing settles from where it was let go (the release rect), never\n * from its old home. Snapped into that slot already, it simply finishes that glide.\n * - {@link HeldDrag.cancel}: let go with no drop: it glides home from where it was let go.\n * - {@link HeldDrag.preview}: the three drop previews, per frame: indicator and gap keep the item under the pointer\n * (gap with the caller's room open), snap glides it into the slot; leaving glides it back to the pointer.\n * - {@link HeldDrag.resize}: the item takes a new size mid-drag (its content laid out anew by the caller): it settles\n * to it from where it is drawn, the size revealed (never stretched), and is held 1:1 again once there.\n */\nexport interface HeldDrag {\n /** Where the pointer is (null before the first move). */\n readonly pointer: Point | null;\n /** Where the pointer holds the item: the pointer less the grab offset, at the held size (lifted, or resized). */\n readonly held: Rect | null;\n /** The slot it sits in (a snap), or null. */\n readonly snapped: Rect | null;\n /** It left a slot (or took a new size) and is gliding back to the pointer. */\n readonly returning: boolean;\n /** The pointer moved: held 1:1 (unless it sits in a slot or is on its way back). */\n move(pointer: Point): void;\n /**\n * Into `slot` (a glide from where it is drawn; the same slot again does nothing), or out of the slot it sits in\n * (`null`: back to the pointer). Returns what changed: `\"snapped\"`, `\"left\"`, or null.\n */\n snapTo(slot: Rect | null): \"snapped\" | \"left\" | null;\n /** Once a frame: on its way back, retarget to the pointer; true on the frame it is under the pointer again. */\n follow(): boolean;\n /**\n * Once a frame, a drop preview's part for the item (`plan`: `@n-uf/motion/drag`'s `previewPlan`, or any\n * `{ snapItem }`): with `snapItem` it glides into `slot` (snap); else it stays under the pointer, or glides back to\n * it from a slot it left (indicator, gap). The room, if the plan makes any, is the caller's layout. In place of\n * {@link HeldDrag.snapTo} and {@link HeldDrag.follow}; returns what changed.\n */\n preview(plan: { readonly snapItem: boolean }, slot: Rect | null): \"snapped\" | \"left\" | null;\n /**\n * Hold it at `size` from now on (the caller has laid its content out for that size). The grab point keeps its share\n * of the item, so the pointer stays on the same spot of it; the item settles from where it is drawn to its new held\n * rect (its size revealed, never stretched), then is held 1:1 again ({@link HeldDrag.follow} once a frame). In a\n * slot it stays there, taking the new size when it leaves. The same size again does nothing; returns true on a\n * change.\n */\n resize(size: { readonly width: number; readonly height: number }): boolean;\n /** Let go into `slot`: returns the release rect, where the landing starts. */\n land(slot: Rect): Rect;\n /** Let go with no drop: glides `home` from the release rect, which it returns. */\n cancel(home: Rect): Rect;\n}\n\n/** How close (px, every component) the way back has to come to count as under the pointer again. */\nconst CAUGHT = 0.5;\n\n/** Hold `item` from the pointer at `grab` (the pointer's offset from its top-left corner), at its drawn size now. */\nexport function holdItem(item: LayoutItem, grab: Point): HeldDrag {\n let size = { width: item.drawn.width, height: item.drawn.height };\n let at = { x: grab.x, y: grab.y };\n let pointer: Point | null = null;\n let snapped: Rect | null = null;\n let returning = false;\n const heldRect = (): Rect | null => (pointer ? { x: pointer.x - at.x, y: pointer.y - at.y, width: size.width, height: size.height } : null);\n const same = (a: Rect, b: Rect): boolean => Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y), Math.abs(a.width - b.width), Math.abs(a.height - b.height)) < 0.01;\n const snapTo = (slot: Rect | null): \"snapped\" | \"left\" | null => {\n if (slot) {\n if (snapped && same(snapped, slot)) return null;\n snapped = { ...slot };\n returning = false;\n item.moveTo(slot);\n return \"snapped\";\n }\n if (!snapped) return null;\n snapped = null;\n returning = true;\n return \"left\";\n };\n const follow = (): boolean => {\n const goal = heldRect();\n if (!returning || !goal) return false;\n // A chase of the pointer: retargeted every frame, laid out again only when its size changes (an item without\n // retarget, from before it or a test double, moves to it).\n if (item.retarget) item.retarget(goal);\n else item.moveTo(goal);\n const d = item.drawn;\n if (Math.max(Math.abs(d.x - goal.x), Math.abs(d.y - goal.y), Math.abs(d.width - goal.width), Math.abs(d.height - goal.height)) >= CAUGHT) return false;\n returning = false;\n item.hold(goal);\n return true;\n };\n const letGo = (to: Rect): Rect => {\n const release = { ...item.drawn };\n snapped = null;\n returning = false;\n item.moveTo(to);\n return release;\n };\n return {\n get pointer() {\n return pointer;\n },\n get held() {\n return heldRect();\n },\n get snapped() {\n return snapped;\n },\n get returning() {\n return returning;\n },\n move(p) {\n pointer = { x: p.x, y: p.y };\n if (snapped || returning) return;\n item.hold(heldRect()!);\n },\n snapTo,\n follow,\n preview(plan, slot) {\n const change = snapTo(plan.snapItem && slot ? slot : null);\n follow();\n return change;\n },\n resize(next) {\n if (Math.abs(next.width - size.width) < 0.01 && Math.abs(next.height - size.height) < 0.01) return false;\n at = { x: size.width ? (at.x / size.width) * next.width : 0, y: size.height ? (at.y / size.height) * next.height : 0 };\n size = { width: next.width, height: next.height };\n if (!snapped && pointer) {\n returning = true;\n follow();\n }\n return true;\n },\n land: letGo,\n cancel: letGo,\n };\n}\n","import type { Rect } from \"../shapes\";\nimport type { LayoutItem } from \"./motion\";\n\n/** A rect as a DOM rect reports it (`getBoundingClientRect()`): left, top, width, height. */\nexport interface RectLike {\n readonly left: number;\n readonly top: number;\n readonly width: number;\n readonly height: number;\n}\n\n/** Anything that can say where it is drawn: a DOM node, or a test's stand-in. */\nexport interface Measurable {\n getBoundingClientRect(): RectLike;\n}\n\n/** A {@link RectLike} as a {@link Rect}. */\nexport const rectOf = (r: RectLike): Rect => ({ x: r.left, y: r.top, width: r.width, height: r.height });\n\n/** One member of a {@link flip}: an item, the node it draws, and where it goes once the DOM has changed. */\nexport interface FlipEntry {\n readonly item: LayoutItem;\n readonly node: Measurable;\n /**\n * Once the DOM has changed: the old rect (as measured before, in page coordinates) in the item's own coordinates\n * now, and its new place there.\n */\n readonly place: (before: Rect) => { readonly from: Rect; readonly to: Rect };\n}\n\n/**\n * FLIP for DOM groups, on settle: record where each node is drawn (first), change the DOM (`change`: move nodes to\n * other parents, reorder them), then each item is drawn at its old rect and settles to its new place (last, invert,\n * play). No transform tween: a settle from the rect it was drawn at, so it never jumps or overshoots, and its size\n * changes through reveal.\n */\nexport function flip(entries: readonly FlipEntry[], change: () => void): void {\n const before = entries.map((e) => rectOf(e.node.getBoundingClientRect()));\n change();\n entries.forEach((e, i) => {\n const { from, to } = e.place(before[i]!);\n e.item.glideFrom(from, to);\n });\n}\n","import type { Rect } from \"../shapes\";\nimport type { Drawable, DrawFrame } from \"./motion\";\n\n/**\n * The style properties the layout motion writes, as strings (a DOM node's `style` has them all). Structural: the\n * library needs no DOM types.\n */\nexport interface StyleLike {\n left: string;\n top: string;\n width: string;\n height: string;\n transform: string;\n opacity: string;\n clipPath: string;\n visibility: string;\n}\n\n/** Something with a writable {@link StyleLike} `style`: a DOM node. */\nexport interface Styled {\n readonly style: StyleLike;\n}\n\n/**\n * The nodes of a drawn box: `node` is laid out at the box and moved to the drawn rect; `frame` (background, border,\n * shadow) is drawn at the drawn size; `content` is clipped to it. With no `frame` and `content`, `node` itself is\n * clipped (its content laid out at the box, never stretched).\n */\nexport interface BoxNodes {\n readonly node: Styled;\n readonly frame?: Styled;\n readonly content?: Styled;\n}\n\n/** Lay `node` out at `box`: its position and size, in its offset parent's coordinates. */\nexport function layOutBox(node: Styled, box: Rect): void {\n const s = node.style;\n s.left = `${box.x}px`;\n s.top = `${box.y}px`;\n s.width = `${box.width}px`;\n s.height = `${box.height}px`;\n}\n\n/**\n * Draw one frame of a box laid out by {@link layOutBox}: one transform (the move to the drawn rect, and the uniform\n * appearance about its centre), the frame at the drawn size, the content clipped (reveal); hidden when gone.\n */\nexport function drawBox(nodes: BoxNodes, frame: DrawFrame): void {\n const { reveal: r, pop } = frame;\n const cx = frame.drawn.width / 2;\n const cy = frame.drawn.height / 2;\n const s = nodes.node.style;\n s.transform =\n pop.scale === 1 ? `translate(${r.dx}px, ${r.dy}px)` : `translate(${r.dx}px, ${r.dy}px) translate(${cx}px, ${cy}px) scale(${pop.scale}) translate(${-cx}px, ${-cy}px)`;\n s.opacity = pop.opacity === 1 ? \"\" : String(pop.opacity);\n if (nodes.frame) {\n nodes.frame.style.width = `${r.width}px`;\n nodes.frame.style.height = `${r.height}px`;\n }\n const clipped = nodes.content ?? (nodes.frame ? null : nodes.node);\n if (clipped) clipped.style.clipPath = r.clipRight || r.clipBottom ? `inset(0 ${r.clipRight}px ${r.clipBottom}px 0)` : \"\";\n // All the way gone: out of sight and out of the way of the pointer.\n s.visibility = frame.gone ? \"hidden\" : \"\";\n}\n\n/** A {@link Drawable} for DOM nodes: {@link layOutBox} on a new box, {@link drawBox} on every frame. */\nexport function domDrawable(nodes: BoxNodes): Drawable {\n return {\n layOut: (box) => layOutBox(nodes.node, box),\n draw: (frame) => drawBox(nodes, frame),\n };\n}\n"],"mappings":"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","names":["same"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@n-uf/motion",
3
- "version": "26.11.4",
3
+ "version": "26.11.6",
4
4
  "description": "One frame loop with an injected clock, fine-grained signals, springs and tweens, and a DOM-free gesture recogniser. No DOM, no framework.",
5
5
  "keywords": [
6
6
  "animation",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/gestures/types.ts","../src/gestures/modifiers.ts","../src/gestures/wheel.ts","../src/gestures/recognizer.ts","../src/gestures/coalescer.ts","../src/gestures/machine.ts","../src/gestures/dwell.ts"],"sourcesContent":["/**\n * Input types: plain event objects in, one gesture stream out. Nothing here\n * touches the DOM; the host copies the fields it needs out of PointerEvent /\n * WheelEvent / Safari's GestureEvent.\n *\n * Ported from a host app's input layer, whose recogniser this is.\n * Hit testing and routing (what is under a point, who gets a gesture) stay\n * with the host.\n */\n\nimport type { Point } from \"../shapes\";\n\nexport type PointerKind = \"mouse\" | \"pen\" | \"touch\";\n\n/** Pointer modifiers, ctrl included (the drag modifier may be \"ctrl\") */\nexport interface Modifiers {\n shift: boolean;\n alt: boolean;\n meta: boolean;\n ctrl: boolean;\n}\n\nexport type ModifierKey = keyof Modifiers;\n\n/** \"mac\": the drag modifier defaults to Cmd; \"other\": Alt (the OS keeps the Win/Super key) */\nexport type Platform = \"mac\" | \"other\";\n\nexport const NO_MODIFIERS: Readonly<Modifiers> = Object.freeze({ shift: false, alt: false, meta: false, ctrl: false });\n\n/**\n * A pointer as the gesture stream reports it (screen, canvas, kind, buttons,\n * modifiers) plus the fields a recogniser needs: which pointer, which button\n * changed, when.\n */\nexport interface SurfacePointer {\n screen: Point; // CSS px relative to the host's view\n canvas: Point; // the host's world units, through its camera at the time of the event (RecognizerOptions.toCanvas)\n kind: PointerKind;\n buttons: number; // PointerEvent.buttons bit mask\n modifiers: Modifiers;\n pointerId: number;\n /** PointerEvent.button: 0 primary, 1 middle, 2 secondary (a pen's barrel button), 5 eraser; -1 none changed */\n button: number;\n time: number; // event timeStamp, ms\n pressure?: number;\n}\n\n// ----------------------------------------------------------------------------\n// Raw events (copied out of DOM events by the host)\n// ----------------------------------------------------------------------------\n\nexport type RawPointerType = \"pointerdown\" | \"pointermove\" | \"pointerup\" | \"pointercancel\" | \"lostpointercapture\";\n\nexport interface RawPointerEvent {\n type: RawPointerType;\n pointerId: number;\n pointerType: PointerKind | string; // unknown types are treated as mouse\n isPrimary?: boolean;\n button: number;\n buttons: number;\n screen: Point; // clientX/Y minus the view's origin\n modifiers?: Partial<Modifiers>;\n time: number;\n pressure?: number;\n /**\n * lostpointercapture only: whose capture was lost. \"content\" (an element inside the view's content, e.g.\n * right after the host took the pointer for a modifier-drag) leaves the gesture alone; default \"surface\".\n */\n lostBy?: \"surface\" | \"content\";\n}\n\nexport interface RawWheelEvent {\n type: \"wheel\";\n deltaX: number;\n deltaY: number;\n /** 0 pixels, 1 lines, 2 pages */\n deltaMode: 0 | 1 | 2;\n screen: Point;\n modifiers?: Partial<Modifiers>;\n time: number;\n}\n\n/** Safari's trackpad pinch (GestureEvent): scale is relative to the gesture's start */\nexport interface RawSafariGestureEvent {\n type: \"gesturestart\" | \"gesturechange\" | \"gestureend\";\n scale: number;\n screen: Point;\n modifiers?: Partial<Modifiers>;\n time: number;\n}\n\nexport type RawInputEvent = RawPointerEvent | RawWheelEvent | RawSafariGestureEvent;\n\n// ----------------------------------------------------------------------------\n// The gesture stream\n// ----------------------------------------------------------------------------\n\n/** What a modifier-drag would do if the pointer travels with the drag modifier held */\nexport type ModifierDrag = \"move\" | \"resize\";\n\nexport type CancelReason =\n | \"pointercancel\" // the browser or OS took the pointer (scroll, system gesture, palm rejection)\n | \"lostcapture\" // pointer capture lost before the pointer went up\n | \"multitouch\" // a second finger turned the press into a pinch\n | \"missed-up\" // its pointerup never came (the same pointer pressed again, or another went down long after)\n | \"escape\"\n | \"reset\" // the view lost focus, the tool changed, the host was disposed\n | (string & {});\n\ninterface PointerGestureBase {\n pointer: SurfacePointer;\n /** The press that started this gesture */\n origin: SurfacePointer;\n}\n\nexport type InputGesture =\n /** A pointer went down (one per gesture; a second touch becomes a pinch instead) */\n | { type: \"press\"; pointer: SurfacePointer; modifierDrag: ModifierDrag | null }\n /** The pointer travelled past the click threshold; modifierDrag is set when the drag modifier was held then */\n | (PointerGestureBase & { type: \"dragStart\"; modifierDrag: ModifierDrag | null })\n /** A move after dragStart (moves inside the threshold are not reported) */\n | (PointerGestureBase & { type: \"move\" })\n /** Held still past the long-press delay (touch and pen by default); at most once per press */\n | (PointerGestureBase & { type: \"longPress\" })\n /**\n * The pointer went up. click: it never crossed the threshold and no long press fired;\n * clickCount counts quick clicks at the same spot (2 = double click).\n */\n | (PointerGestureBase & { type: \"release\"; click: boolean; clickCount: number; dragged: boolean })\n | (PointerGestureBase & { type: \"cancel\"; reason: CancelReason; dragged: boolean })\n /** A pointer moving with no press (mouse and pen hover): cursor and chrome hover */\n | { type: \"hover\"; pointer: SurfacePointer }\n /**\n * Two fingers (touch) or a trackpad pinch. scale is the change since the last update (multiply it in);\n * pan is the centre's screen-px change since the last update. kind is what was recognised first.\n */\n | {\n type: \"pinch\";\n phase: \"start\" | \"update\" | \"end\" | \"cancel\";\n source: \"touch\" | \"trackpad\";\n kind: \"pinch\" | \"pan\";\n center: { screen: Point; canvas: Point };\n scale: number;\n pan: Point;\n modifiers: Modifiers;\n time: number;\n }\n /** A wheel or trackpad scroll that is not a pinch: delta in screen px (lines and pages converted) */\n | { type: \"scroll\"; delta: Point; at: { screen: Point; canvas: Point }; modifiers: Modifiers; time: number }\n /**\n * Zoom by a factor around a point: Cmd/Ctrl+wheel, a trackpad pinch reported as ctrl+wheel\n * (Chrome, Firefox, Edge). Safari's trackpad pinch comes as \"pinch\" with source \"trackpad\".\n */\n | { type: \"zoom\"; factor: number; at: { screen: Point; canvas: Point }; modifiers: Modifiers; time: number };\n\nexport type InputGestureType = InputGesture[\"type\"];\n","/**\n * Modifier keys: event modifiers to one shape, and the drag modifier\n * resolved per platform (Cmd on macOS, Alt elsewhere; null turns it off).\n */\n\nimport { NO_MODIFIERS, type ModifierKey, type Modifiers, type Platform, type PointerKind } from \"./types\";\n\nexport function toModifiers(m: Partial<Modifiers> | undefined): Modifiers {\n if (!m) return { ...NO_MODIFIERS };\n return { shift: !!m.shift, alt: !!m.alt, meta: !!m.meta, ctrl: !!m.ctrl };\n}\n\n/** The default drag modifier: \"meta\" (Cmd) on macOS, \"alt\" elsewhere */\nexport function defaultDragModifier(platform: Platform): ModifierKey {\n return platform === \"mac\" ? \"meta\" : \"alt\";\n}\n\n/**\n * A configured drag modifier as used: undefined → the platform default, null → off.\n */\nexport function resolveDragModifier(config: ModifierKey | null | undefined, platform: Platform): ModifierKey | null {\n if (config === null) return null;\n return config ?? defaultDragModifier(platform);\n}\n\nexport function isHeld(modifiers: Modifiers, key: ModifierKey | null): boolean {\n return key !== null && modifiers[key];\n}\n\n/**\n * The button a press stands for on this platform. On macOS a Ctrl+click is the secondary click (the system's own\n * rule; the browser's contextmenu follows it), so a primary mouse press with Ctrl held is button 2 there, as a\n * right-click is. Not when Ctrl is the drag modifier (Ctrl+drag then moves). A pen, a finger and the other platforms\n * keep their button.\n */\nexport function platformButton(press: { kind: PointerKind; button: number; modifiers: Modifiers }, platform: Platform, dragModifier: ModifierKey | null): number {\n return platform === \"mac\" && press.kind === \"mouse\" && press.button === 0 && press.modifiers.ctrl && dragModifier !== \"ctrl\" ? 2 : press.button;\n}\n\n/** Platform \"Mod\": Cmd on macOS, Ctrl elsewhere (Mod+wheel zooms, Mod+click is content's multi-select) */\nexport function modHeld(modifiers: Modifiers, platform: Platform): boolean {\n return platform === \"mac\" ? modifiers.meta : modifiers.ctrl;\n}\n","/**\n * Wheel normalisation (Figma / tldraw style): lines and pages to pixels;\n * Cmd/Ctrl+wheel and the trackpad pinch (ctrl+wheel in Chrome, Firefox, Edge)\n * zoom by 2^(−dy / sensitivity); any other wheel scrolls, Shift turns\n * vertical into horizontal.\n */\n\nimport type { Point } from \"../shapes\";\nimport type { Modifiers } from \"./types\";\n\nexport interface WheelOptions {\n /** px per line (deltaMode 1); default 40 */\n lineHeight: number;\n /** px per page (deltaMode 2); default 800 */\n pageHeight: number;\n /** zoom factor = 2^(−deltaY / sensitivity); default 300 */\n zoomSensitivity: number;\n}\n\nexport const WHEEL_DEFAULTS: Readonly<WheelOptions> = Object.freeze({ lineHeight: 40, pageHeight: 800, zoomSensitivity: 300 });\n\nexport function wheelDeltaPx(deltaX: number, deltaY: number, deltaMode: 0 | 1 | 2, opts: WheelOptions = WHEEL_DEFAULTS): Point {\n const k = deltaMode === 1 ? opts.lineHeight : deltaMode === 2 ? opts.pageHeight : 1;\n return { x: finite(deltaX) * k, y: finite(deltaY) * k };\n}\n\nexport type WheelIntent = { kind: \"zoom\"; factor: number } | { kind: \"scroll\"; delta: Point };\n\n/**\n * What a wheel event means. Ctrl or Meta held: zoom (a trackpad pinch arrives as ctrl+wheel).\n * Otherwise scroll; with Shift a vertical-only delta scrolls horizontally (mice without a\n * horizontal wheel).\n */\nexport function classifyWheel(delta: Point, modifiers: Modifiers, opts: WheelOptions = WHEEL_DEFAULTS): WheelIntent {\n if (modifiers.ctrl || modifiers.meta) {\n const factor = Math.pow(2, -delta.y / (opts.zoomSensitivity > 0 ? opts.zoomSensitivity : WHEEL_DEFAULTS.zoomSensitivity));\n return { kind: \"zoom\", factor: Number.isFinite(factor) && factor > 0 ? factor : 1 };\n }\n if (modifiers.shift && delta.x === 0) return { kind: \"scroll\", delta: { x: delta.y, y: 0 } };\n return { kind: \"scroll\", delta };\n}\n\n/** Wheel pan damping for a camera that pans on scroll */\nexport interface WheelPanOptions {\n /** Multiplier on the wheel's px; default 0.65 */\n speed: number;\n /** Linear up to this many px per event; default 30 */\n threshold: number;\n /** Hard cap per event, px; default 60 */\n max: number;\n}\n\nexport const WHEEL_PAN_DEFAULTS: Readonly<WheelPanOptions> = Object.freeze({ speed: 0.65, threshold: 30, max: 60 });\n\n/**\n * The camera pan for one wheel scroll, per axis: speed × delta, linear up to the threshold, then\n * square-root damped (threshold + √(4 × excess)) and capped, so a fast flick of a free-spinning wheel or an\n * accelerated trackpad doesn't throw the canvas away.\n */\nexport function dampWheelPan(delta: Point, opts: WheelPanOptions = WHEEL_PAN_DEFAULTS): Point {\n const one = (d: number) => {\n const v = finite(d) * opts.speed;\n const abs = Math.abs(v);\n if (abs <= opts.threshold) return v;\n return Math.sign(v) * Math.min(opts.threshold + Math.sqrt((abs - opts.threshold) * 4), opts.max);\n };\n return { x: one(delta.x), y: one(delta.y) };\n}\n\nfunction finite(v: number): number {\n return Number.isFinite(v) ? v : 0;\n}\n","/**\n * The gesture recogniser: raw pointer, wheel and Safari gesture events (plain\n * objects) in, one stream of InputGesture out. Mouse, pen and touch share one\n * path; two fingers become a pinch (5 % scale or 10 px pan decides which, read\n * once both fingers have moved, so a pan's leading finger is no pinch); a\n * press becomes a drag only past a per-kind threshold, so a modifier+click\n * (Cmd/Ctrl+click to multi-select) stays a click for the content.\n *\n * One press at a time: a second mouse or pen while one is down is ignored, and\n * a second finger cancels the one-finger press (\"multitouch\") and starts a\n * pinch. After a pinch, the fingers left on the glass start nothing until all\n * are lifted.\n *\n * No DOM, no clock: timestamps come with the events and the long-press timer\n * is injected as the timer half of a ClockPort (without timers there is no\n * long press). The stale-press drop works from event timestamps, so it needs\n * no timer.\n */\n\nimport type { ClockPort } from \"../clock/port\";\nimport type { Point } from \"../shapes\";\nimport { isHeld, platformButton, toModifiers } from \"./modifiers\";\nimport type {\n CancelReason,\n InputGesture,\n ModifierDrag,\n ModifierKey,\n Platform,\n PointerKind,\n RawInputEvent,\n RawPointerEvent,\n RawSafariGestureEvent,\n RawWheelEvent,\n SurfacePointer,\n} from \"./types\";\nimport { classifyWheel, WHEEL_DEFAULTS, wheelDeltaPx, type WheelOptions } from \"./wheel\";\n\n/** The timer half of a ClockPort: any clock (createBrowserClock, createFakeClock) is one. */\nexport type RecognizerTimers = Pick<ClockPort, \"setTimer\" | \"clearTimer\">;\n\nexport interface RecognizerOptions {\n /** Screen px (relative to the view) to canvas units, through the current camera */\n toCanvas: (screen: Point) => Point;\n /** For the long press: a clock; without timers no long press fires */\n timers?: RecognizerTimers;\n /** The drag modifier, resolved (see resolveDragModifier); null = off */\n dragModifier?: ModifierKey | null;\n /** \"mac\": a mouse Ctrl+press is the secondary button, as the system has it (platformButton); default \"other\" */\n platform?: Platform;\n /** Click vs drag: a press becomes a drag after travelling more than this many screen px */\n thresholds?: Partial<Record<PointerKind, number>>;\n longPress?: { ms?: number; kinds?: readonly PointerKind[] };\n /** Two clicks within ms and px of each other count as a double click */\n multiClick?: { ms?: number; px?: number };\n pinch?: { scaleThreshold?: number; panThreshold?: number };\n wheel?: Partial<WheelOptions>;\n /** A mouse or pen press, or a resting finger outside any gesture, untouched this long when another pointer goes down, lost its pointerup; default 1000 ms */\n staleMs?: number;\n}\n\nexport const RECOGNIZER_DEFAULTS = Object.freeze({\n thresholds: Object.freeze({ mouse: 4, pen: 6, touch: 8 }) as Readonly<Record<PointerKind, number>>,\n longPress: Object.freeze({ ms: 500, kinds: Object.freeze([\"touch\", \"pen\"]) as readonly PointerKind[] }),\n multiClick: Object.freeze({ ms: 300, px: 5 }),\n pinch: Object.freeze({ scaleThreshold: 0.05, panThreshold: 10 }),\n});\n\nexport interface Recognizer {\n feed(event: RawInputEvent): void;\n /** Cancel the press and the pinch in flight (Escape, a tool change, the view losing focus) */\n cancel(reason: CancelReason): void;\n /** cancel(\"reset\") and forget every pointer (e.g. after the window lost focus mid-touch) */\n reset(): void;\n /** The press in flight, if any */\n active(): { pointerId: number; kind: PointerKind; dragging: boolean } | null;\n pinching(): boolean;\n /** Change the drag modifier; applies from the next press */\n setDragModifier(key: ModifierKey | null): void;\n dispose(): void;\n}\n\ninterface Press {\n origin: SurfacePointer;\n last: SurfacePointer;\n dragging: boolean;\n longPressed: boolean;\n /** What a modifier-drag would do; decided at the press, confirmed (modifier still held) at dragStart */\n candidate: ModifierDrag | null;\n modifierDrag: ModifierDrag | null;\n timer: number | undefined;\n}\n\ninterface Touch {\n screen: Point;\n time: number;\n}\n\ninterface Pinch {\n ids: [number, number];\n startDist: number;\n startCenter: Point;\n lastDist: number;\n lastCenter: Point;\n kind: \"pinch\" | \"pan\" | null;\n /** While undecided: each finger's moves (position changes) since the last look at the kind */\n reported: Map<number, number>;\n}\n\n/**\n * While two fingers are undecided, the kind is looked at once both have moved as often as each other since the last\n * look: one finger's events arrive before the other's (one or a burst), and a pan's leading finger alone changes the\n * distance like a pinch. A finger at rest sends no moves, so a finger this many moves ahead of the other is looked at\n * on its own (a pinch around an anchored thumb).\n */\nconst SOLO_MOVES = 3;\n\nfunction kindOf(pointerType: string): PointerKind {\n return pointerType === \"touch\" || pointerType === \"pen\" ? pointerType : \"mouse\";\n}\n\nfunction dist(a: Point, b: Point): number {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n\nfunction mid(a: Point, b: Point): Point {\n return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n}\n\nfunction finitePoint(p: Point): Point {\n return { x: Number.isFinite(p.x) ? p.x : 0, y: Number.isFinite(p.y) ? p.y : 0 };\n}\n\nexport function createRecognizer(options: RecognizerOptions, emit: (gesture: InputGesture) => void): Recognizer {\n const thresholds = { ...RECOGNIZER_DEFAULTS.thresholds, ...options.thresholds };\n const longPressMs = options.longPress?.ms ?? RECOGNIZER_DEFAULTS.longPress.ms;\n const longPressKinds = options.longPress?.kinds ?? RECOGNIZER_DEFAULTS.longPress.kinds;\n const multiClick = { ...RECOGNIZER_DEFAULTS.multiClick, ...options.multiClick };\n const pinchCfg = { ...RECOGNIZER_DEFAULTS.pinch, ...options.pinch };\n const wheelCfg: WheelOptions = { ...WHEEL_DEFAULTS, ...options.wheel };\n const timers = options.timers;\n const staleMs = options.staleMs ?? 1000;\n let dragModifier: ModifierKey | null = options.dragModifier ?? null;\n const platform: Platform = options.platform ?? \"other\";\n\n let press: Press | null = null;\n let pinch: Pinch | null = null;\n const touches = new Map<number, Touch>();\n /** After a pinch (or a reset mid-touch), fingers still down start nothing until all are up */\n let touchLocked = false;\n let lastClick: { time: number; screen: Point; button: number; count: number } | null = null;\n let safari: { last: number } | null = null;\n let lastTime = 0;\n let disposed = false;\n\n const pointerOf = (e: RawPointerEvent): SurfacePointer => {\n const screen = finitePoint(e.screen);\n return {\n screen,\n canvas: options.toCanvas(screen),\n kind: kindOf(e.pointerType),\n buttons: e.buttons,\n modifiers: toModifiers(e.modifiers),\n pointerId: e.pointerId,\n button: e.button,\n time: e.time,\n ...(e.pressure !== undefined ? { pressure: e.pressure } : {}),\n };\n };\n\n const clearTimer = (p: Press) => {\n if (p.timer !== undefined && timers) timers.clearTimer(p.timer);\n p.timer = undefined;\n };\n\n const endPress = (): Press | null => {\n const p = press;\n if (p) clearTimer(p);\n press = null;\n return p;\n };\n\n const cancelPress = (reason: CancelReason, pointer?: SurfacePointer) => {\n const p = endPress();\n if (!p) return;\n emit({ type: \"cancel\", pointer: pointer ?? p.last, origin: p.origin, reason, dragged: p.dragging });\n };\n\n const releasePress = (pointer: SurfacePointer) => {\n const p = endPress();\n if (!p) return;\n const click = !p.dragging && !p.longPressed;\n let clickCount = 0;\n if (click) {\n const prev = lastClick;\n const repeat =\n !!prev &&\n prev.button === p.origin.button &&\n p.origin.time - prev.time <= multiClick.ms &&\n dist(prev.screen, p.origin.screen) <= multiClick.px;\n clickCount = repeat ? prev!.count + 1 : 1;\n lastClick = { time: pointer.time, screen: p.origin.screen, button: p.origin.button, count: clickCount };\n } else {\n lastClick = null;\n }\n emit({ type: \"release\", pointer, origin: p.origin, click, clickCount, dragged: p.dragging });\n };\n\n const pinchState = (): { center: Point; d: number } | null => {\n if (!pinch) return null;\n const a = touches.get(pinch.ids[0]);\n const b = touches.get(pinch.ids[1]);\n if (!a || !b) return null;\n return { center: mid(a.screen, b.screen), d: Math.max(1, dist(a.screen, b.screen)) };\n };\n\n const pinchEvent = (\n phase: \"start\" | \"update\" | \"end\" | \"cancel\",\n center: Point,\n scale: number,\n pan: Point,\n time: number,\n modifiers: SurfacePointer[\"modifiers\"],\n source: \"touch\" | \"trackpad\",\n kind: \"pinch\" | \"pan\",\n ): InputGesture => ({\n type: \"pinch\",\n phase,\n source,\n kind,\n center: { screen: center, canvas: options.toCanvas(center) },\n scale,\n pan,\n modifiers,\n time,\n });\n\n const endPinch = (phase: \"end\" | \"cancel\", time: number, modifiers: SurfacePointer[\"modifiers\"]) => {\n const p = pinch;\n pinch = null;\n touchLocked = touches.size > 0;\n if (p && p.kind) emit(pinchEvent(phase, p.lastCenter, 1, { x: 0, y: 0 }, time, modifiers, \"touch\", p.kind));\n };\n\n /**\n * A pinch finger moved (`id`), or lifts (`lifting`: the last look, whatever moved). The kind is decided on both\n * fingers' latest positions (see SOLO_MOVES); the thresholds keep their meaning.\n */\n const updatePinch = (id: number, time: number, modifiers: SurfacePointer[\"modifiers\"], moved: boolean, lifting = false) => {\n const p = pinch;\n const now = pinchState();\n if (!p || !now) return;\n if (!p.kind) {\n if (!moved && !lifting) return; // a move that changed nothing (pressure only) is no move\n const moves = (p.reported.get(id) ?? 0) + (moved ? 1 : 0);\n p.reported.set(id, moves);\n const other = p.reported.get(p.ids[0] === id ? p.ids[1] : p.ids[0]) ?? 0;\n const even = moves > 0 && moves === other;\n if (!lifting && !even && Math.abs(moves - other) < SOLO_MOVES) return;\n p.reported.clear();\n const scaleChange = Math.abs(now.d / p.startDist - 1);\n const panDist = dist(now.center, p.startCenter);\n if (scaleChange > pinchCfg.scaleThreshold) p.kind = \"pinch\";\n else if (panDist > pinchCfg.panThreshold) p.kind = \"pan\";\n else return;\n // The first event carries everything since the second finger landed, so nothing is lost.\n const scale = p.kind === \"pinch\" ? now.d / p.startDist : 1;\n const pan = { x: now.center.x - p.startCenter.x, y: now.center.y - p.startCenter.y };\n p.lastDist = now.d;\n p.lastCenter = now.center;\n emit(pinchEvent(\"start\", now.center, scale, pan, time, modifiers, \"touch\", p.kind));\n return;\n }\n const scale = p.kind === \"pinch\" ? now.d / p.lastDist : 1;\n const pan = { x: now.center.x - p.lastCenter.x, y: now.center.y - p.lastCenter.y };\n p.lastDist = now.d;\n p.lastCenter = now.center;\n if (scale === 1 && pan.x === 0 && pan.y === 0) return;\n emit(pinchEvent(\"update\", now.center, scale, pan, time, modifiers, \"touch\", p.kind));\n };\n\n /**\n * A pointerup that never came (released outside the page, a pen lifted out of range, a lost event):\n * a press of the same pointer, or a mouse / pen press untouched for staleMs, is over. A finger that\n * lands again under its old id, or one resting untouched for staleMs outside any press or pinch, is gone.\n */\n const dropMissedUps = (e: RawPointerEvent, kind: PointerKind) => {\n const p = press;\n if (p) {\n const same = p.origin.pointerId === e.pointerId;\n const mixed = kind !== \"touch\" || p.origin.kind !== \"touch\"; // two fingers make a pinch, never a stale press\n if (same || (mixed && e.time - p.last.time > staleMs)) cancelPress(\"missed-up\");\n }\n if (kind !== \"touch\") return;\n if (touches.has(e.pointerId)) {\n if (pinch?.ids.includes(e.pointerId)) endPinch(\"cancel\", e.time, toModifiers(e.modifiers));\n touches.delete(e.pointerId);\n }\n for (const [id, t] of touches) {\n const busy = press?.origin.pointerId === id || !!pinch?.ids.includes(id);\n if (!busy && e.time - t.time > staleMs) touches.delete(id);\n }\n if (touches.size === 0) touchLocked = false;\n };\n\n const onDown = (e: RawPointerEvent) => {\n const down = pointerOf(e);\n // The press's button as the platform means it (macOS: Ctrl+click is a secondary click)\n const pointer: SurfacePointer = { ...down, button: platformButton(down, platform, dragModifier) };\n dropMissedUps(e, pointer.kind);\n if (pointer.kind === \"touch\") {\n touches.set(e.pointerId, { screen: pointer.screen, time: pointer.time });\n if (touchLocked || pinch) return; // a third finger, or fingers after a pinch\n if (touches.size === 2) {\n const ids = [...touches.keys()] as [number, number];\n // The second finger: whatever the first one started is cancelled, a pinch may begin.\n if (press && press.origin.kind === \"touch\") cancelPress(\"multitouch\");\n else if (press) {\n touches.delete(e.pointerId); // a mouse or pen press owns the input; ignore the finger\n return;\n }\n const a = touches.get(ids[0])!;\n const b = touches.get(ids[1])!;\n const d = Math.max(1, dist(a.screen, b.screen));\n const c = mid(a.screen, b.screen);\n pinch = { ids, startDist: d, startCenter: c, lastDist: d, lastCenter: c, kind: null, reported: new Map() };\n return;\n }\n if (touches.size > 2) return;\n }\n if (press || pinch) return; // one press at a time\n const candidate: ModifierDrag | null = isHeld(pointer.modifiers, dragModifier)\n ? pointer.button === 0\n ? \"move\"\n : pointer.button === 2\n ? \"resize\"\n : null\n : null;\n const p: Press = {\n origin: pointer,\n last: pointer,\n dragging: false,\n longPressed: false,\n candidate,\n modifierDrag: null,\n timer: undefined,\n };\n press = p;\n if (timers && longPressKinds.includes(pointer.kind)) {\n p.timer = timers.setTimer(() => {\n p.timer = undefined;\n if (press !== p || p.dragging || disposed) return;\n p.longPressed = true;\n emit({ type: \"longPress\", pointer: p.last, origin: p.origin });\n }, longPressMs);\n }\n emit({ type: \"press\", pointer, modifierDrag: candidate });\n };\n\n const onMove = (e: RawPointerEvent) => {\n const kind = kindOf(e.pointerType);\n if (kind === \"touch\") {\n const t = touches.get(e.pointerId);\n const to = finitePoint(e.screen);\n const moved = !t || t.screen.x !== to.x || t.screen.y !== to.y;\n if (t) {\n t.screen = to;\n t.time = e.time;\n }\n if (pinch && pinch.ids.includes(e.pointerId)) {\n updatePinch(e.pointerId, e.time, toModifiers(e.modifiers), moved);\n return;\n }\n }\n if (press && press.origin.pointerId === e.pointerId) {\n const pointer = pointerOf(e);\n // A mouse or pen moving with no button down and no pointerup before it: the release was missed.\n if (pointer.kind !== \"touch\" && e.buttons === 0) {\n releasePress(pointer);\n return;\n }\n const p = press;\n p.last = pointer;\n if (!p.dragging) {\n if (dist(pointer.screen, p.origin.screen) <= thresholds[p.origin.kind]) return;\n p.dragging = true;\n clearTimer(p);\n // The modifier must still be held when the drag starts: a released Cmd makes it a plain drag.\n p.modifierDrag = p.candidate && isHeld(pointer.modifiers, dragModifier) ? p.candidate : null;\n emit({ type: \"dragStart\", pointer, origin: p.origin, modifierDrag: p.modifierDrag });\n return;\n }\n emit({ type: \"move\", pointer, origin: p.origin });\n return;\n }\n if (!press && kind !== \"touch\" && e.buttons === 0) emit({ type: \"hover\", pointer: pointerOf(e) });\n };\n\n const onUp = (e: RawPointerEvent) => {\n if (kindOf(e.pointerType) === \"touch\") {\n // A finger lifting from an undecided pinch: its last position is looked at once, so a quick pinch still counts\n const t = touches.get(e.pointerId);\n if (t && pinch && !pinch.kind && pinch.ids.includes(e.pointerId)) {\n const to = finitePoint(e.screen);\n const moved = t.screen.x !== to.x || t.screen.y !== to.y;\n t.screen = to;\n updatePinch(e.pointerId, e.time, toModifiers(e.modifiers), moved, true);\n }\n touches.delete(e.pointerId);\n if (pinch && pinch.ids.includes(e.pointerId)) {\n endPinch(\"end\", e.time, toModifiers(e.modifiers));\n return;\n }\n if (touches.size === 0) touchLocked = false;\n }\n if (press && press.origin.pointerId === e.pointerId) releasePress(pointerOf(e));\n };\n\n const onCancel = (e: RawPointerEvent, reason: CancelReason) => {\n if (kindOf(e.pointerType) === \"touch\") {\n const tracked = touches.delete(e.pointerId);\n if (pinch && pinch.ids.includes(e.pointerId)) {\n if (tracked) endPinch(\"cancel\", e.time, toModifiers(e.modifiers));\n return;\n }\n if (touches.size === 0) touchLocked = false;\n }\n if (press && press.origin.pointerId === e.pointerId) cancelPress(reason, pointerOf(e));\n };\n\n const onWheel = (e: RawWheelEvent) => {\n const modifiers = toModifiers(e.modifiers);\n const screen = finitePoint(e.screen);\n const at = { screen, canvas: options.toCanvas(screen) };\n const intent = classifyWheel(wheelDeltaPx(e.deltaX, e.deltaY, e.deltaMode, wheelCfg), modifiers, wheelCfg);\n if (intent.kind === \"zoom\") {\n if (intent.factor !== 1) emit({ type: \"zoom\", factor: intent.factor, at, modifiers, time: e.time });\n return;\n }\n if (intent.delta.x === 0 && intent.delta.y === 0) return;\n emit({ type: \"scroll\", delta: intent.delta, at, modifiers, time: e.time });\n };\n\n const onSafari = (e: RawSafariGestureEvent) => {\n // iOS Safari fires gesture events for touch pinches too; the pointer path handles those.\n if (touches.size > 0 || pinch) return;\n const modifiers = toModifiers(e.modifiers);\n const screen = finitePoint(e.screen);\n const scaleNow = Number.isFinite(e.scale) && e.scale > 0 ? e.scale : 1;\n const none = { x: 0, y: 0 };\n if (e.type === \"gesturestart\") {\n safari = { last: scaleNow };\n emit(pinchEvent(\"start\", screen, 1, none, e.time, modifiers, \"trackpad\", \"pinch\"));\n return;\n }\n if (!safari) return;\n const scale = scaleNow / safari.last;\n safari.last = scaleNow;\n if (e.type === \"gesturechange\") {\n if (scale !== 1) emit(pinchEvent(\"update\", screen, scale, none, e.time, modifiers, \"trackpad\", \"pinch\"));\n return;\n }\n safari = null;\n emit(pinchEvent(\"end\", screen, scale, none, e.time, modifiers, \"trackpad\", \"pinch\"));\n };\n\n const cancelAll = (reason: CancelReason) => {\n cancelPress(reason);\n if (pinch) endPinch(\"cancel\", lastTime, toModifiers(undefined));\n if (safari) {\n safari = null;\n emit(pinchEvent(\"cancel\", { x: 0, y: 0 }, 1, { x: 0, y: 0 }, lastTime, toModifiers(undefined), \"trackpad\", \"pinch\"));\n }\n };\n\n return {\n feed(event) {\n if (disposed) return;\n if (Number.isFinite(event.time)) lastTime = event.time;\n switch (event.type) {\n case \"pointerdown\":\n return onDown(event);\n case \"pointermove\":\n return onMove(event);\n case \"pointerup\":\n return onUp(event);\n case \"pointercancel\":\n return onCancel(event, \"pointercancel\");\n case \"lostpointercapture\":\n // Also fires right after pointerup; by then the press is gone and this is a no-op. The content\n // losing its capture (the surface just took the pointer) is not the gesture's end.\n if (event.lostBy === \"content\") return;\n return onCancel(event, \"lostcapture\");\n case \"wheel\":\n return onWheel(event);\n case \"gesturestart\":\n case \"gesturechange\":\n case \"gestureend\":\n return onSafari(event);\n }\n },\n cancel: cancelAll,\n reset() {\n cancelAll(\"reset\");\n touches.clear();\n touchLocked = false;\n lastClick = null;\n },\n active() {\n return press ? { pointerId: press.origin.pointerId, kind: press.origin.kind, dragging: press.dragging } : null;\n },\n pinching() {\n return !!pinch?.kind || !!safari;\n },\n setDragModifier(key) {\n dragModifier = key;\n },\n dispose() {\n if (press) clearTimer(press);\n press = null;\n pinch = null;\n safari = null;\n touches.clear();\n disposed = true;\n },\n };\n}\n","import type { FrameSource } from \"../clock/port\";\n\n/** Options of {@link createFrameCoalescer}. */\nexport interface CoalescerOptions<T> {\n /**\n * How a new sample folds into the pending one. Default: keep the latest\n * (pointer positions). Wheel deltas want a sum instead.\n */\n readonly merge?: (pending: T, next: T) => T;\n}\n\n/**\n * Collapses a burst of samples into at most one delivery per frame. Same\n * shape as hypr-tiling's `createFrameCoalescer` (`schedule`, `cancel`),\n * plus `flush` and `pending`.\n */\nexport interface FrameCoalescer<T> {\n /** Record a sample and make sure a frame is requested. */\n schedule(sample: T): void;\n /**\n * Deliver the pending sample now, if there is one (before a pointer-up, so\n * the last move is seen first). Returns whether it delivered.\n */\n flush(): boolean;\n /** Drop the pending sample and the requested frame. Nothing is delivered after this. */\n cancel(): void;\n /** Whether a sample is waiting for its frame. */\n readonly pending: boolean;\n}\n\n/**\n * Deliver samples to `onFrame` once per frame of `frames`: the browser clock,\n * the fake clock, or a frame loop phase (`loop.frames(\"input\")`). `onFrame`\n * also gets how many samples were folded into this one.\n */\nexport function createFrameCoalescer<T>(\n onFrame: (sample: T, count: number) => void,\n frames: FrameSource,\n options: CoalescerOptions<T> = {},\n): FrameCoalescer<T> {\n const merge = options.merge;\n let handle: number | null = null;\n let sample: T | undefined;\n let count = 0;\n\n const deliver = (): void => {\n const delivered = sample as T;\n const folded = count;\n sample = undefined;\n count = 0;\n onFrame(delivered, folded);\n };\n\n const onFrameDue = (): void => {\n handle = null;\n if (count > 0) deliver();\n };\n\n return {\n schedule(next) {\n sample = count > 0 && merge !== undefined ? merge(sample as T, next) : next;\n count++;\n if (handle === null) handle = frames.requestFrame(onFrameDue);\n },\n flush() {\n if (handle !== null) {\n frames.cancelFrame(handle);\n handle = null;\n }\n if (count === 0) return false;\n deliver();\n return true;\n },\n cancel() {\n if (handle !== null) {\n frames.cancelFrame(handle);\n handle = null;\n }\n sample = undefined;\n count = 0;\n },\n get pending() {\n return count > 0;\n },\n };\n}\n","import type { TimerSource } from \"../clock/port\";\nimport { signal } from \"../signals/signals\";\n\n/**\n * A timer event a state asks for: send `event(t)` when the clock reaches\n * `at`. Machines stay pure; this is how a long-press or a dwell turns into\n * an event without the reducer touching a timer.\n */\nexport interface Deadline<E> {\n /** Clock time in ms. */\n readonly at: number;\n /** The event to send, given the clock time it fired at. */\n readonly event: (t: number) => E;\n}\n\n/** Options of {@link createMachine}. */\nexport interface MachineOptions<S, E> {\n /** The pure transition function. Returning the same state means \"no change\". */\n readonly reducer: (state: S, event: E) => S;\n readonly initial: S;\n /** Timers come from here (and `now()` for their events). */\n readonly clock: TimerSource;\n /** The timer event the state needs, or `null`. Checked after every transition. */\n readonly deadline?: (state: S) => Deadline<E> | null;\n}\n\n/** A running machine: the state, a way to send events, and change listeners. */\nexport interface Machine<S, E> {\n /** The current state; reactive (read it in a computed or an effect). */\n readonly state: S;\n /** The current state, untracked. */\n peek(): S;\n /**\n * Run `event` through the reducer and return the new state. An event sent\n * from a listener waits until the current one has been handled.\n */\n send(event: E): S;\n /** Called after each change of state (not for events that changed nothing). */\n subscribe(listener: (next: S, previous: S, event: E) => void): () => void;\n /** Clear the timer and drop listeners. Events sent after this are ignored. */\n dispose(): void;\n}\n\n/** Run a pure machine on a clock: it arms and clears the deadline timer for you. */\nexport function createMachine<S, E>(options: MachineOptions<S, E>): Machine<S, E> {\n const { reducer, clock, deadline } = options;\n const state = signal(options.initial);\n const listeners = new Set<(next: S, previous: S, event: E) => void>();\n const inbox: E[] = [];\n let dispatching = false;\n let disposed = false;\n let timer: number | null = null;\n let timerAt: number | null = null;\n let timerEvent: ((t: number) => E) | null = null;\n\n const clearTimer = (): void => {\n if (timer !== null) clock.clearTimer(timer);\n timer = null;\n timerAt = null;\n timerEvent = null;\n };\n\n const arm = (): void => {\n const due = deadline === undefined ? null : deadline(state.peek());\n if (due === null) {\n clearTimer();\n return;\n }\n timerEvent = due.event;\n if (timer !== null && timerAt === due.at) return;\n if (timer !== null) clock.clearTimer(timer);\n timerAt = due.at;\n timer = clock.setTimer(() => {\n const make = timerEvent;\n timer = null;\n timerAt = null;\n timerEvent = null;\n if (make !== null) machine.send(make(clock.now()));\n }, Math.max(0, due.at - clock.now()));\n };\n\n const machine: Machine<S, E> = {\n get state() {\n return state.value;\n },\n peek: () => state.peek(),\n send(event) {\n if (disposed) return state.peek();\n inbox.push(event);\n if (dispatching) return state.peek();\n dispatching = true;\n try {\n let next: E | undefined;\n while ((next = inbox.shift()) !== undefined) {\n const previous = state.peek();\n const after = reducer(previous, next);\n if (after === previous) continue;\n state.value = after;\n arm();\n for (const listener of Array.from(listeners)) listener(after, previous, next);\n if (disposed) break;\n }\n } finally {\n dispatching = false;\n inbox.length = 0;\n }\n return state.peek();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n clearTimer();\n listeners.clear();\n },\n };\n arm();\n return machine;\n}\n","import type { TimerSource } from \"../clock/port\";\nimport { createMachine, type Deadline, type Machine } from \"./machine\";\n\n/**\n * The dwell machine (spring-load): hold over the same target long enough and\n * it fires, once.\n *\n * ```text\n * idle ──hover(key)──► dwelling ──tick (t − since ≥ dwellMs)──► fired\n * ▲ │ hover(other key) restarts `since` │\n * │ │ hover(null) ─► idle │\n * └────────┴─────────────── reset ◄─────────────────────┘\n * ```\n *\n * `fired` holds until `reset`: neither a late tick nor a hover change can fire\n * it again or clear it while the host acts on it. Shapes come from\n * hypr-tiling's `springLoadReducer`, with its workspace tab generalised to\n * any `key` (the host passes `null` for anything that shouldn't dwell, such as\n * the active tab).\n */\n\n/** Tunables of the dwell machine. */\nexport interface DwellConfig {\n /** How long in ms the pointer must stay over the same key. Default 500. */\n readonly dwellMs: number;\n}\n\n/** Defaults of {@link DwellConfig}. */\nexport const DWELL_DEFAULTS: DwellConfig = { dwellMs: 500 };\n\n/** The dwell machine's state. `K` is the target key, `P` the payload carried into `fired`. */\nexport type DwellState<K = string, P = unknown> =\n | { readonly phase: \"idle\" }\n | { readonly phase: \"dwelling\"; readonly key: K; readonly since: number; readonly payload: P | undefined }\n | {\n readonly phase: \"fired\";\n readonly key: K;\n readonly since: number;\n readonly firedAt: number;\n readonly payload: P | undefined;\n };\n\n/** Events of the dwell machine. `t` is the clock time in ms. */\nexport type DwellInput<K = string, P = unknown> =\n | { readonly type: \"hover\"; readonly t: number; readonly key: K | null; readonly payload?: P }\n | { readonly type: \"tick\"; readonly t: number }\n | { readonly type: \"reset\" };\n\n/** The idle state (the same object every time). */\nexport const DWELL_IDLE: DwellState<never, never> = { phase: \"idle\" };\n\n/** The dwell machine's pure transition function. */\nexport function dwellReducer<K, P>(\n state: DwellState<K, P>,\n event: DwellInput<K, P>,\n config: DwellConfig = DWELL_DEFAULTS,\n): DwellState<K, P> {\n switch (event.type) {\n case \"reset\":\n return state.phase === \"idle\" ? state : DWELL_IDLE;\n case \"hover\":\n if (state.phase === \"fired\") return state;\n if (event.key === null) return state.phase === \"idle\" ? state : DWELL_IDLE;\n if (state.phase === \"dwelling\" && Object.is(state.key, event.key)) return state;\n return { phase: \"dwelling\", key: event.key, since: event.t, payload: event.payload };\n case \"tick\":\n if (state.phase !== \"dwelling\" || event.t - state.since < config.dwellMs) return state;\n return { phase: \"fired\", key: state.key, since: state.since, firedAt: event.t, payload: state.payload };\n default:\n return state;\n }\n}\n\n/** When a dwelling state fires, as a timer event; `null` outside `dwelling`. */\nexport function dwellDeadline<K, P>(\n state: DwellState<K, P>,\n config: DwellConfig = DWELL_DEFAULTS,\n): Deadline<DwellInput<K, P>> | null {\n if (state.phase !== \"dwelling\") return null;\n return { at: state.since + config.dwellMs, event: (t) => ({ type: \"tick\", t }) };\n}\n\n/** Options of {@link createDwellMachine}. */\nexport interface DwellMachineOptions {\n readonly clock: TimerSource;\n readonly config?: Partial<DwellConfig>;\n}\n\n/** A dwell machine on a clock: the dwell timer is armed for you. */\nexport function createDwellMachine<K = string, P = unknown>(\n options: DwellMachineOptions,\n): Machine<DwellState<K, P>, DwellInput<K, P>> {\n const config: DwellConfig = { ...DWELL_DEFAULTS, ...options.config };\n return createMachine<DwellState<K, P>, DwellInput<K, P>>({\n reducer: (state, event) => dwellReducer(state, event, config),\n initial: DWELL_IDLE,\n clock: options.clock,\n deadline: (state) => dwellDeadline(state, config),\n });\n}\n"],"mappings":"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","names":["scale","pan"]}