@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.
- package/CHANGELOG.md +56 -0
- package/README.md +76 -1
- package/dist/{chunk-C4M62FUT.js → chunk-KVFX5V4Y.js} +226 -2
- package/dist/chunk-KVFX5V4Y.js.map +1 -0
- package/dist/drag.d.ts +20 -4
- package/dist/drag.js +17 -3
- package/dist/drag.js.map +1 -1
- package/dist/gestures.d.ts +245 -1
- package/dist/gestures.js +25 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +25 -1
- package/dist/layout.d.ts +36 -1
- package/dist/layout.js +84 -14
- package/dist/layout.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-C4M62FUT.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
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","../src/gestures/touch-press.ts","../src/gestures/paged-swipe.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","import type { TimerSource } from \"../clock/port\";\nimport type { Point } from \"../shapes\";\nimport { createMachine, type Deadline, type Machine } from \"./machine\";\n\n/**\n * A touch press on something draggable that sits in a scrolling page: is it a drag, or a scroll?\n *\n * ```text\n * idle ──press──► pressed ──move: mostly along the scroll direction, ≥ scrollEscape──► scrolled (never lifts)\n * │ ──move: mostly across it, ≥ lift px────────────────────────────► lifted\n * │ ──tick: held still holdMs─────────────────────────────────────► lifted (where it was pressed)\n * └ ──move: along it short of the escape, or across it below lift px: stays pressed\n * any ──release──► idle\n * ```\n *\n * Before the hold has elapsed, a finger that travels mostly along the page's scroll direction is a scroll: the touch\n * goes back to the page and stays a scroll until it is released; short of the escape it waits, never lifting.\n * Travel past the lift distance mostly across the scroll direction is a deliberate drag. Once the hold has elapsed\n * the press lifts where it is. `scrolled` and `lifted`\n * hold until `release`.\n */\n\n/** Tunables of the touch press. */\nexport interface TouchPressConfig {\n /** px of travel that lifts a press that is not a scroll. Default 6. */\n readonly lift: number;\n /** px of travel along the scroll direction that makes the press a scroll. Default 10. */\n readonly scrollEscape: number;\n /** The page's scroll direction: `\"y\"` (a vertical page, the default) or `\"x\"`. */\n readonly scroll: \"x\" | \"y\";\n /** ms a still finger holds before the press lifts where it is. Default 220. */\n readonly holdMs: number;\n}\n\n/** Defaults of {@link TouchPressConfig}. */\nexport const TOUCH_PRESS_DEFAULTS: TouchPressConfig = { lift: 6, scrollEscape: 10, scroll: \"y\", holdMs: 220 };\n\n/** The touch press's state. `from` is where the finger went down, `at` where it lifted. */\nexport type TouchPressState =\n | { readonly phase: \"idle\" }\n | { readonly phase: \"pressed\"; readonly from: Point; readonly since: number }\n | { readonly phase: \"lifted\"; readonly from: Point; readonly at: Point }\n | { readonly phase: \"scrolled\"; readonly from: Point };\n\n/** Events of the touch press. `t` is the clock time in ms. */\nexport type TouchPressInput =\n | { readonly type: \"press\"; readonly t: number; readonly point: Point }\n | { readonly type: \"move\"; readonly point: Point }\n | { readonly type: \"tick\"; readonly t: number }\n | { readonly type: \"release\" };\n\n/** The idle state (the same object every time). */\nexport const TOUCH_PRESS_IDLE: TouchPressState = { phase: \"idle\" };\n\n/** The touch press's pure transition function. */\nexport function touchPressReducer(state: TouchPressState, event: TouchPressInput, config: TouchPressConfig = TOUCH_PRESS_DEFAULTS): TouchPressState {\n switch (event.type) {\n case \"release\":\n return state.phase === \"idle\" ? state : TOUCH_PRESS_IDLE;\n case \"press\":\n return state.phase === \"idle\" ? { phase: \"pressed\", from: { x: event.point.x, y: event.point.y }, since: event.t } : state;\n case \"tick\":\n if (state.phase !== \"pressed\" || event.t - state.since < config.holdMs) return state;\n return { phase: \"lifted\", from: state.from, at: state.from };\n case \"move\": {\n if (state.phase !== \"pressed\") return state;\n const dx = event.point.x - state.from.x;\n const dy = event.point.y - state.from.y;\n const along = Math.abs(config.scroll === \"y\" ? dy : dx);\n const across = Math.abs(config.scroll === \"y\" ? dx : dy);\n // Mostly along the scroll direction is never a lift: a scroll once it reaches the escape, until then waiting\n // (a finger scroll is reported a few px at a time, and would pass the lift distance first).\n if (along > across) return along >= config.scrollEscape ? { phase: \"scrolled\", from: state.from } : state;\n if (Math.hypot(dx, dy) >= config.lift) return { phase: \"lifted\", from: state.from, at: { x: event.point.x, y: event.point.y } };\n return state;\n }\n default:\n return state;\n }\n}\n\n/** When a pressed state lifts by holding, as a timer event; `null` outside `pressed`. */\nexport function touchPressDeadline(state: TouchPressState, config: TouchPressConfig = TOUCH_PRESS_DEFAULTS): Deadline<TouchPressInput> | null {\n if (state.phase !== \"pressed\") return null;\n return { at: state.since + config.holdMs, event: (t) => ({ type: \"tick\", t }) };\n}\n\n/** Options of {@link createTouchPress}. */\nexport interface TouchPressOptions {\n readonly clock: TimerSource;\n readonly config?: Partial<TouchPressConfig>;\n}\n\n/** A touch press on a clock: the hold timer is armed for you. */\nexport function createTouchPress(options: TouchPressOptions): Machine<TouchPressState, TouchPressInput> {\n const config: TouchPressConfig = { ...TOUCH_PRESS_DEFAULTS, ...options.config };\n return createMachine<TouchPressState, TouchPressInput>({\n reducer: (state, event) => touchPressReducer(state, event, config),\n initial: TOUCH_PRESS_IDLE,\n clock: options.clock,\n deadline: (state) => touchPressDeadline(state, config),\n });\n}\n","import type { TimerSource } from \"../clock/port\";\nimport { createMachine, type Deadline, type Machine } from \"./machine\";\nimport type { ModifierKey, Modifiers } from \"./types\";\n\n/**\n * A paged swipe: a trackpad's two-finger scroll or a finger swipes from one page to the previous or the next, the\n * pages following it, and commits or snaps back when it ends.\n *\n * ```text\n * idle ──wheel / touch-move (arming rules pass)──► armed ──travel ≥ thresholdPx──► tracking\n * ▲ │ │\n * │ rule breach / idle / end gesture end\n * │ ▼ ▼\n * └──────── lockoutMs elapsed ◄── lockout ◄── settled (commit) ◄───────────── settling\n * └──────────────────────────────────────── settled (cancel) ◄───────────────────┤\n * └──────────────────────────────────────── cancel (transition stopped) ◄────────┘\n * ```\n *\n * - **Arming:** nothing else owns the pointer (`blocked`), the movement runs mostly along the swipe (more than twice\n * the cross movement), no Ctrl (a pinch), the configured modifier is held, nothing under the pointer can still\n * scroll that way (`canScrollFurther`), and there is a page on that side (or `wrap`). A wheel run that breaks a\n * rule belongs to scrolling until the wheel has been idle `idleMs`.\n * - **Tracking:** `progress` is the travel over the page size, −1 to 1, held at 0 towards a side with no page.\n * - **Ending:** a touch ends when the finger lifts, a wheel run after `idleMs` without samples. It commits when\n * `|progress| ≥ commitFraction`, or the peak speed towards the target reached `commitVelocity` (the peak starts\n * over when the travel crosses zero, so a fast fling out and a pull back does not commit the way back).\n * - **Settling** lasts as long as the host's page transition; the host sends `settled` when it is done (or `cancel`,\n * which goes straight back to idle, when it stops the transition). After a commit, `lockout` swallows the\n * trackpad's momentum for `lockoutMs`, and the run stays claimed after it.\n *\n * Pure and timer-free: {@link pagedSwipeDeadline} says when the next `tick` is due ({@link createPagedSwipe} arms it).\n * Signs: a wheel delta > 0 along the swipe heads to `next`; a finger moving towards smaller x (or y) heads to `next`.\n */\n\n/** Which side of the current page a swipe heads to. */\nexport type SwipeSide = \"prev\" | \"next\";\n\n/** Tunables of the paged swipe. */\nexport interface PagedSwipeConfig {\n /** px of travel before an armed swipe tracks. Default 24. */\n readonly thresholdPx: number;\n /** The share of the page size that commits at the end. Default 0.35. */\n readonly commitFraction: number;\n /** px/ms: a peak speed towards the target that commits at the end. Default 0.6. */\n readonly commitVelocity: number;\n /** ms without a wheel sample that ends a wheel run. Default 120. */\n readonly idleMs: number;\n /** ms after a commit during which wheel samples are swallowed (momentum). Default 350. */\n readonly lockoutMs: number;\n /** Past the last page comes the first, and back. Default false. */\n readonly wrap: boolean;\n /** The page size in px when no sample or context gives one. Default 800. */\n readonly size: number;\n /** A modifier the wheel needs held to swipe (`null`: none). Touch never needs one. Default null. */\n readonly modifier: ModifierKey | null;\n /** The swipe's direction: `\"x\"` (the default) or `\"y\"`. */\n readonly along: \"x\" | \"y\";\n}\n\n/** Defaults of {@link PagedSwipeConfig}. */\nexport const PAGED_SWIPE_DEFAULTS: PagedSwipeConfig = {\n thresholdPx: 24,\n commitFraction: 0.35,\n commitVelocity: 0.6,\n idleMs: 120,\n lockoutMs: 350,\n wrap: false,\n size: 800,\n modifier: null,\n along: \"x\",\n};\n\n/** What the host knows about the pages now. */\nexport interface PagedSwipeContext {\n readonly hasPrev: boolean;\n readonly hasNext: boolean;\n /** The page size in px, or null (the config's). */\n readonly size: number | null;\n /** Something else owns the pointer (a drag): nothing arms, and a swipe in progress gives way. */\n readonly blocked: boolean;\n}\n\n/** The context before the host has said anything. */\nexport const PAGED_SWIPE_EMPTY_CONTEXT: PagedSwipeContext = { hasPrev: false, hasNext: false, size: null, blocked: false };\n\ntype Input = \"wheel\" | \"touch\";\ninterface Base {\n readonly context: PagedSwipeContext;\n /** −1 to 1; negative towards `prev`. */\n readonly progress: number;\n readonly target: SwipeSide | null;\n}\n\n/** The paged swipe's state. Every phase has a way back to `idle`. */\nexport type PagedSwipeState =\n | (Base & {\n readonly phase: \"idle\";\n /** The last wheel sample of the current run (null: none, or the run went idle). */\n readonly lastWheel: number | null;\n /** This wheel run broke the arming rules (or committed): it cannot arm until the wheel is idle `idleMs`. */\n readonly claimed: boolean;\n })\n | (Base & {\n readonly phase: \"armed\";\n readonly input: Input;\n /** Where the finger went down (touch). */\n readonly origin: { readonly x: number; readonly y: number };\n /** Wheel travel along the swipe and across it so far. */\n readonly travel: number;\n readonly cross: number;\n readonly last: number;\n })\n | (Base & {\n readonly phase: \"tracking\";\n readonly input: Input;\n readonly origin: { readonly x: number; readonly y: number };\n readonly travel: number;\n readonly last: number;\n /** The highest speed (px/ms) towards the current target. */\n readonly peak: number;\n })\n | (Base & { readonly phase: \"settling\"; readonly input: Input; readonly outcome: \"commit\" | \"cancel\"; readonly ended: number; readonly lastWheel: number })\n | (Base & { readonly phase: \"lockout\"; readonly until: number; readonly lastWheel: number });\n\n/** Events of the paged swipe. `t` is the clock time in ms. */\nexport type PagedSwipeInput =\n | {\n readonly type: \"wheel\";\n readonly dx: number;\n readonly dy: number;\n readonly t: number;\n readonly modifiers: Modifiers;\n /** Something under the pointer can still scroll that way: no arming. */\n readonly canScrollFurther?: boolean;\n readonly size?: number;\n }\n | { readonly type: \"touch-start\"; readonly x: number; readonly y: number; readonly t: number; readonly size?: number }\n | { readonly type: \"touch-move\"; readonly x: number; readonly y: number; readonly t: number; readonly canScrollFurther?: boolean; readonly size?: number }\n | { readonly type: \"touch-end\"; readonly t: number }\n | { readonly type: \"tick\"; readonly t: number }\n | { readonly type: \"settled\" }\n | { readonly type: \"cancel\" }\n | { readonly type: \"block\"; readonly on: boolean }\n | { readonly type: \"context\"; readonly hasPrev: boolean; readonly hasNext: boolean; readonly size?: number };\n\n/** The idle state with no context. */\nexport const PAGED_SWIPE_IDLE: PagedSwipeState = { phase: \"idle\", context: PAGED_SWIPE_EMPTY_CONTEXT, progress: 0, target: null, lastWheel: null, claimed: false };\n\nconst idle = (context: PagedSwipeContext, lastWheel: number | null = null, claimed = false): PagedSwipeState => ({ phase: \"idle\", context, progress: 0, target: null, lastWheel, claimed });\nconst sideOf = (travel: number): SwipeSide | null => (travel > 0 ? \"next\" : travel < 0 ? \"prev\" : null);\nconst hasPage = (c: PagedSwipeContext, side: SwipeSide, wrap: boolean): boolean => (wrap ? c.hasPrev || c.hasNext : side === \"next\" ? c.hasNext : c.hasPrev);\nconst withSize = (c: PagedSwipeContext, size: number | undefined): PagedSwipeContext => (size === undefined || size === c.size ? c : { ...c, size });\nconst sizeOf = (c: PagedSwipeContext, size: number | undefined, config: PagedSwipeConfig): number => size ?? c.size ?? config.size;\n\n/** Where the swipe may head for a movement (`main` along the swipe, `cross` across it), or null when it must not arm. */\nfunction arming(c: PagedSwipeContext, main: number, cross: number, config: PagedSwipeConfig, gates: { ctrl: boolean; modifierHeld: boolean; canScrollFurther: boolean }): SwipeSide | null {\n if (c.blocked || gates.ctrl || gates.canScrollFurther || !gates.modifierHeld) return null;\n if (Math.abs(main) <= 2 * Math.abs(cross)) return null;\n const side = sideOf(main);\n return side && hasPage(c, side, config.wrap) ? side : null;\n}\n\n/** Travel to progress: over the page size, −1 to 1, 0 towards a side with no page (unless `wrap`). */\nexport function swipeProgress(travel: number, size: number, c: PagedSwipeContext, wrap: boolean): number {\n const p = Math.max(-1, Math.min(1, travel / Math.max(1, size)));\n if (wrap) return p;\n if ((p < 0 && !c.hasPrev) || (p > 0 && !c.hasNext)) return 0;\n return p;\n}\n\n/** Speed towards `side` for a step of `delta` px over `dt` ms; 0 moving away. */\nconst towards = (delta: number, dt: number, side: SwipeSide | null): number => {\n if (dt <= 0 || side === null) return 0;\n const v = (side === \"next\" ? delta : -delta) / dt;\n return v > 0 ? v : 0;\n};\n\nfunction track(c: PagedSwipeContext, input: Input, origin: { x: number; y: number }, travel: number, t: number, peak: number, size: number, config: PagedSwipeConfig): PagedSwipeState {\n const progress = swipeProgress(travel, size, c, config.wrap);\n return { phase: \"tracking\", context: c, input, origin, travel, last: t, peak, progress, target: sideOf(progress) };\n}\n\nfunction end(s: Extract<PagedSwipeState, { phase: \"tracking\" }>, t: number, config: PagedSwipeConfig): PagedSwipeState {\n const commit = s.target !== null && (Math.abs(s.progress) >= config.commitFraction || s.peak >= config.commitVelocity);\n return { phase: \"settling\", context: s.context, input: s.input, outcome: commit ? \"commit\" : \"cancel\", ended: t, lastWheel: t, progress: s.progress, target: commit ? s.target : null };\n}\n\nconst giveUp = (s: Extract<PagedSwipeState, { phase: \"tracking\" }>, c: PagedSwipeContext): PagedSwipeState => ({\n phase: \"settling\",\n context: c,\n input: s.input,\n outcome: \"cancel\",\n ended: s.last,\n lastWheel: s.last,\n progress: s.progress,\n target: null,\n});\n\n/** The paged swipe's pure transition function. Unmatched pairs return the state unchanged. */\nexport function pagedSwipeReducer(state: PagedSwipeState, event: PagedSwipeInput, config: PagedSwipeConfig = PAGED_SWIPE_DEFAULTS): PagedSwipeState {\n const y = config.along === \"y\";\n if (event.type === \"context\") {\n return { ...state, context: { ...state.context, hasPrev: event.hasPrev, hasNext: event.hasNext, size: event.size ?? state.context.size } };\n }\n if (event.type === \"block\") {\n const c = state.context.blocked === event.on ? state.context : { ...state.context, blocked: event.on };\n if (event.on && state.phase === \"armed\") return idle(c);\n if (event.on && state.phase === \"tracking\") return giveUp(state, c);\n return c === state.context ? state : { ...state, context: c };\n }\n if (event.type === \"cancel\") {\n // Settling too: a host that will never send `settled` (it went away mid-transition) must not leave it stuck.\n if (state.phase === \"armed\" || state.phase === \"lockout\" || state.phase === \"settling\") return idle(state.context);\n if (state.phase === \"tracking\") return giveUp(state, state.context);\n return state;\n }\n switch (state.phase) {\n case \"idle\": {\n if (event.type === \"wheel\") {\n if (state.context.blocked) return state;\n const fresh = state.lastWheel === null || event.t - state.lastWheel >= config.idleMs;\n if (!fresh && state.claimed) return { ...state, lastWheel: event.t };\n const main = y ? event.dy : event.dx;\n const cross = y ? event.dx : event.dy;\n const side = arming(state.context, main, cross, config, wheelGates(event, config));\n const c = withSize(state.context, event.size);\n if (side === null) return idle(c, event.t, true);\n if (Math.abs(main) >= config.thresholdPx) return track(c, \"wheel\", { x: 0, y: 0 }, main, event.t, 0, sizeOf(c, event.size, config), config);\n return { phase: \"armed\", context: c, input: \"wheel\", origin: { x: 0, y: 0 }, travel: main, cross, last: event.t, progress: 0, target: null };\n }\n if (event.type === \"touch-start\") {\n if (state.context.blocked) return state;\n return { phase: \"armed\", context: withSize(state.context, event.size), input: \"touch\", origin: { x: event.x, y: event.y }, travel: 0, cross: 0, last: event.t, progress: 0, target: null };\n }\n return state;\n }\n case \"armed\": {\n if (event.type === \"wheel\") {\n if (state.input !== \"wheel\") return state;\n const main = y ? event.dy : event.dx;\n const cross = y ? event.dx : event.dy;\n // Every sample before the threshold must pass the rules too; a breach gives the run to scrolling.\n if (arming(state.context, main, cross, config, wheelGates(event, config)) === null) return idle(state.context, event.t, true);\n const c = withSize(state.context, event.size);\n const travel = state.travel + main;\n const crossTravel = state.cross + cross;\n // The run as a whole must stay along the swipe.\n if (Math.abs(crossTravel) > Math.abs(travel) / 2) return idle(c, event.t, true);\n if (Math.abs(travel) < config.thresholdPx) return { ...state, context: c, travel, cross: crossTravel, last: event.t };\n return track(c, \"wheel\", state.origin, travel, event.t, towards(main, event.t - state.last, sideOf(travel)), sizeOf(c, event.size, config), config);\n }\n if (event.type === \"touch-move\") {\n if (state.input !== \"touch\") return state;\n const main = y ? state.origin.y - event.y : state.origin.x - event.x;\n const cross = y ? event.x - state.origin.x : event.y - state.origin.y;\n // Finger jitter.\n if (Math.hypot(main, cross) < config.thresholdPx) return { ...state, last: event.t };\n const side = arming(state.context, main, cross, config, { ctrl: false, modifierHeld: true, canScrollFurther: event.canScrollFurther ?? false });\n // Across the swipe, or nowhere to go: the page owns this touch until it ends.\n if (side === null) return idle(state.context);\n const c = withSize(state.context, event.size);\n return track(c, \"touch\", state.origin, main, event.t, towards(main, event.t - state.last, side), sizeOf(c, event.size, config), config);\n }\n if (event.type === \"tick\") return state.input === \"wheel\" && event.t - state.last >= config.idleMs ? idle(state.context) : state;\n if (event.type === \"touch-end\") return state.input === \"touch\" ? idle(state.context) : state;\n if (event.type === \"touch-start\") return { ...state, context: withSize(state.context, event.size), input: \"touch\", origin: { x: event.x, y: event.y }, travel: 0, cross: 0, last: event.t };\n return state;\n }\n case \"tracking\": {\n if (event.type === \"wheel\" || event.type === \"touch-move\") {\n if (state.input !== (event.type === \"wheel\" ? \"wheel\" : \"touch\")) return state;\n const travel =\n event.type === \"wheel\" ? state.travel + (y ? event.dy : event.dx) : y ? state.origin.y - event.y : state.origin.x - event.x;\n const side = sideOf(travel);\n const v = towards(travel - state.travel, event.t - state.last, side);\n const peak = side === sideOf(state.travel) ? Math.max(state.peak, v) : v;\n const c = withSize(state.context, event.size);\n return track(c, state.input, state.origin, travel, event.t, peak, sizeOf(c, event.size, config), config);\n }\n if (event.type === \"tick\") return state.input === \"wheel\" && event.t - state.last >= config.idleMs ? end(state, event.t, config) : state;\n if (event.type === \"touch-end\") return state.input === \"touch\" ? end(state, event.t, config) : state;\n return state;\n }\n case \"settling\": {\n if (event.type === \"settled\") {\n if (state.outcome === \"commit\") return { phase: \"lockout\", context: state.context, until: state.ended + config.lockoutMs, lastWheel: state.lastWheel, progress: 0, target: null };\n return idle(state.context, state.lastWheel, false);\n }\n if (event.type === \"wheel\") return { ...state, lastWheel: event.t };\n return state;\n }\n case \"lockout\": {\n if (event.type === \"wheel\") return event.t >= state.until ? idle(state.context, event.t, true) : { ...state, lastWheel: event.t };\n if (event.type === \"tick\") {\n if (event.t < state.until) return state;\n return event.t - state.lastWheel < config.idleMs ? idle(state.context, state.lastWheel, true) : idle(state.context);\n }\n // A finger is not momentum: it may start a swipe at once.\n if (event.type === \"touch-start\") return pagedSwipeReducer(idle(state.context), event, config);\n return state;\n }\n default:\n return state;\n }\n}\n\nfunction wheelGates(event: Extract<PagedSwipeInput, { type: \"wheel\" }>, config: PagedSwipeConfig): { ctrl: boolean; modifierHeld: boolean; canScrollFurther: boolean } {\n return { ctrl: event.modifiers.ctrl, modifierHeld: config.modifier === null || event.modifiers[config.modifier], canScrollFurther: event.canScrollFurther ?? false };\n}\n\n/** When the next `tick` is due: a wheel run's idle end, or the end of the lockout; `null` when none is. */\nexport function pagedSwipeDeadline(state: PagedSwipeState, config: PagedSwipeConfig = PAGED_SWIPE_DEFAULTS): Deadline<PagedSwipeInput> | null {\n const tick = (t: number): PagedSwipeInput => ({ type: \"tick\", t });\n if ((state.phase === \"armed\" || state.phase === \"tracking\") && state.input === \"wheel\") return { at: state.last + config.idleMs, event: tick };\n if (state.phase === \"lockout\") return { at: state.until, event: tick };\n return null;\n}\n\n/** Options of {@link createPagedSwipe}. */\nexport interface PagedSwipeOptions {\n readonly clock: TimerSource;\n readonly config?: Partial<PagedSwipeConfig>;\n}\n\n/** A paged swipe on a clock: the idle and lockout timers are armed for you. */\nexport function createPagedSwipe(options: PagedSwipeOptions): Machine<PagedSwipeState, PagedSwipeInput> {\n const config: PagedSwipeConfig = { ...PAGED_SWIPE_DEFAULTS, ...options.config };\n return createMachine<PagedSwipeState, PagedSwipeInput>({\n reducer: (state, event) => pagedSwipeReducer(state, event, config),\n initial: PAGED_SWIPE_IDLE,\n clock: options.clock,\n deadline: (state) => pagedSwipeDeadline(state, config),\n });\n}\n"],"mappings":";;;;;AA2BO,IAAM,eAAoC,OAAO,OAAO,EAAE,OAAO,OAAO,KAAK,OAAO,MAAM,OAAO,MAAM,MAAM,CAAC;;;ACpB9G,SAAS,YAAY,GAA8C;AACxE,MAAI,CAAC,EAAG,QAAO,EAAE,GAAG,aAAa;AACjC,SAAO,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC,EAAE,KAAK;AAC1E;AAGO,SAAS,oBAAoB,UAAiC;AACnE,SAAO,aAAa,QAAQ,SAAS;AACvC;AAKO,SAAS,oBAAoB,QAAwC,UAAwC;AAClH,MAAI,WAAW,KAAM,QAAO;AAC5B,SAAO,UAAU,oBAAoB,QAAQ;AAC/C;AAEO,SAAS,OAAO,WAAsB,KAAkC;AAC7E,SAAO,QAAQ,QAAQ,UAAU,GAAG;AACtC;AAQO,SAAS,eAAe,OAAoE,UAAoB,cAA0C;AAC/J,SAAO,aAAa,SAAS,MAAM,SAAS,WAAW,MAAM,WAAW,KAAK,MAAM,UAAU,QAAQ,iBAAiB,SAAS,IAAI,MAAM;AAC3I;AAGO,SAAS,QAAQ,WAAsB,UAA6B;AACzE,SAAO,aAAa,QAAQ,UAAU,OAAO,UAAU;AACzD;;;ACvBO,IAAM,iBAAyC,OAAO,OAAO,EAAE,YAAY,IAAI,YAAY,KAAK,iBAAiB,IAAI,CAAC;AAEtH,SAAS,aAAa,QAAgB,QAAgB,WAAsB,OAAqB,gBAAuB;AAC7H,QAAM,IAAI,cAAc,IAAI,KAAK,aAAa,cAAc,IAAI,KAAK,aAAa;AAClF,SAAO,EAAE,GAAG,OAAO,MAAM,IAAI,GAAG,GAAG,OAAO,MAAM,IAAI,EAAE;AACxD;AASO,SAAS,cAAc,OAAc,WAAsB,OAAqB,gBAA6B;AAClH,MAAI,UAAU,QAAQ,UAAU,MAAM;AACpC,UAAM,SAAS,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,kBAAkB,IAAI,KAAK,kBAAkB,eAAe,gBAAgB;AACxH,WAAO,EAAE,MAAM,QAAQ,QAAQ,OAAO,SAAS,MAAM,KAAK,SAAS,IAAI,SAAS,EAAE;AAAA,EACpF;AACA,MAAI,UAAU,SAAS,MAAM,MAAM,EAAG,QAAO,EAAE,MAAM,UAAU,OAAO,EAAE,GAAG,MAAM,GAAG,GAAG,EAAE,EAAE;AAC3F,SAAO,EAAE,MAAM,UAAU,MAAM;AACjC;AAYO,IAAM,qBAAgD,OAAO,OAAO,EAAE,OAAO,MAAM,WAAW,IAAI,KAAK,GAAG,CAAC;AAO3G,SAAS,aAAa,OAAc,OAAwB,oBAA2B;AAC5F,QAAM,MAAM,CAAC,MAAc;AACzB,UAAM,IAAI,OAAO,CAAC,IAAI,KAAK;AAC3B,UAAM,MAAM,KAAK,IAAI,CAAC;AACtB,QAAI,OAAO,KAAK,UAAW,QAAO;AAClC,WAAO,KAAK,KAAK,CAAC,IAAI,KAAK,IAAI,KAAK,YAAY,KAAK,MAAM,MAAM,KAAK,aAAa,CAAC,GAAG,KAAK,GAAG;AAAA,EACjG;AACA,SAAO,EAAE,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,EAAE;AAC5C;AAEA,SAAS,OAAO,GAAmB;AACjC,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;;;ACXO,IAAM,sBAAsB,OAAO,OAAO;AAAA,EAC/C,YAAY,OAAO,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,EAAE,CAAC;AAAA,EACxD,WAAW,OAAO,OAAO,EAAE,IAAI,KAAK,OAAO,OAAO,OAAO,CAAC,SAAS,KAAK,CAAC,EAA4B,CAAC;AAAA,EACtG,YAAY,OAAO,OAAO,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC;AAAA,EAC5C,OAAO,OAAO,OAAO,EAAE,gBAAgB,MAAM,cAAc,GAAG,CAAC;AACjE,CAAC;AAiDD,IAAM,aAAa;AAEnB,SAAS,OAAO,aAAkC;AAChD,SAAO,gBAAgB,WAAW,gBAAgB,QAAQ,cAAc;AAC1E;AAEA,SAAS,KAAK,GAAU,GAAkB;AACxC,SAAO,KAAK,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AACxC;AAEA,SAAS,IAAI,GAAU,GAAiB;AACtC,SAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE;AAClD;AAEA,SAAS,YAAY,GAAiB;AACpC,SAAO,EAAE,GAAG,OAAO,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,GAAG,OAAO,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE;AAChF;AAEO,SAAS,iBAAiB,SAA4B,MAAmD;AAC9G,QAAM,aAAa,EAAE,GAAG,oBAAoB,YAAY,GAAG,QAAQ,WAAW;AAC9E,QAAM,cAAc,QAAQ,WAAW,MAAM,oBAAoB,UAAU;AAC3E,QAAM,iBAAiB,QAAQ,WAAW,SAAS,oBAAoB,UAAU;AACjF,QAAM,aAAa,EAAE,GAAG,oBAAoB,YAAY,GAAG,QAAQ,WAAW;AAC9E,QAAM,WAAW,EAAE,GAAG,oBAAoB,OAAO,GAAG,QAAQ,MAAM;AAClE,QAAM,WAAyB,EAAE,GAAG,gBAAgB,GAAG,QAAQ,MAAM;AACrE,QAAM,SAAS,QAAQ;AACvB,QAAM,UAAU,QAAQ,WAAW;AACnC,MAAI,eAAmC,QAAQ,gBAAgB;AAC/D,QAAM,WAAqB,QAAQ,YAAY;AAE/C,MAAI,QAAsB;AAC1B,MAAI,QAAsB;AAC1B,QAAM,UAAU,oBAAI,IAAmB;AAEvC,MAAI,cAAc;AAClB,MAAI,YAAmF;AACvF,MAAI,SAAkC;AACtC,MAAI,WAAW;AACf,MAAI,WAAW;AAEf,QAAM,YAAY,CAAC,MAAuC;AACxD,UAAM,SAAS,YAAY,EAAE,MAAM;AACnC,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,QAAQ,SAAS,MAAM;AAAA,MAC/B,MAAM,OAAO,EAAE,WAAW;AAAA,MAC1B,SAAS,EAAE;AAAA,MACX,WAAW,YAAY,EAAE,SAAS;AAAA,MAClC,WAAW,EAAE;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,MAAM,EAAE;AAAA,MACR,GAAI,EAAE,aAAa,SAAY,EAAE,UAAU,EAAE,SAAS,IAAI,CAAC;AAAA,IAC7D;AAAA,EACF;AAEA,QAAM,aAAa,CAAC,MAAa;AAC/B,QAAI,EAAE,UAAU,UAAa,OAAQ,QAAO,WAAW,EAAE,KAAK;AAC9D,MAAE,QAAQ;AAAA,EACZ;AAEA,QAAM,WAAW,MAAoB;AACnC,UAAM,IAAI;AACV,QAAI,EAAG,YAAW,CAAC;AACnB,YAAQ;AACR,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,CAAC,QAAsB,YAA6B;AACtE,UAAM,IAAI,SAAS;AACnB,QAAI,CAAC,EAAG;AACR,SAAK,EAAE,MAAM,UAAU,SAAS,WAAW,EAAE,MAAM,QAAQ,EAAE,QAAQ,QAAQ,SAAS,EAAE,SAAS,CAAC;AAAA,EACpG;AAEA,QAAM,eAAe,CAAC,YAA4B;AAChD,UAAM,IAAI,SAAS;AACnB,QAAI,CAAC,EAAG;AACR,UAAM,QAAQ,CAAC,EAAE,YAAY,CAAC,EAAE;AAChC,QAAI,aAAa;AACjB,QAAI,OAAO;AACT,YAAM,OAAO;AACb,YAAM,SACJ,CAAC,CAAC,QACF,KAAK,WAAW,EAAE,OAAO,UACzB,EAAE,OAAO,OAAO,KAAK,QAAQ,WAAW,MACxC,KAAK,KAAK,QAAQ,EAAE,OAAO,MAAM,KAAK,WAAW;AACnD,mBAAa,SAAS,KAAM,QAAQ,IAAI;AACxC,kBAAY,EAAE,MAAM,QAAQ,MAAM,QAAQ,EAAE,OAAO,QAAQ,QAAQ,EAAE,OAAO,QAAQ,OAAO,WAAW;AAAA,IACxG,OAAO;AACL,kBAAY;AAAA,IACd;AACA,SAAK,EAAE,MAAM,WAAW,SAAS,QAAQ,EAAE,QAAQ,OAAO,YAAY,SAAS,EAAE,SAAS,CAAC;AAAA,EAC7F;AAEA,QAAM,aAAa,MAA2C;AAC5D,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,IAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,CAAC;AAClC,UAAM,IAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,CAAC;AAClC,QAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,WAAO,EAAE,QAAQ,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,KAAK,IAAI,GAAG,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE;AAAA,EACrF;AAEA,QAAM,aAAa,CACjB,OACA,QACA,OACA,KACA,MACA,WACA,QACA,UACkB;AAAA,IAClB,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,EAAE,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,MAAM,EAAE;AAAA,IAC3D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,WAAW,CAAC,OAAyB,MAAc,cAA2C;AAClG,UAAM,IAAI;AACV,YAAQ;AACR,kBAAc,QAAQ,OAAO;AAC7B,QAAI,KAAK,EAAE,KAAM,MAAK,WAAW,OAAO,EAAE,YAAY,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,MAAM,WAAW,SAAS,EAAE,IAAI,CAAC;AAAA,EAC5G;AAMA,QAAM,cAAc,CAAC,IAAY,MAAc,WAAwC,OAAgB,UAAU,UAAU;AACzH,UAAM,IAAI;AACV,UAAM,MAAM,WAAW;AACvB,QAAI,CAAC,KAAK,CAAC,IAAK;AAChB,QAAI,CAAC,EAAE,MAAM;AACX,UAAI,CAAC,SAAS,CAAC,QAAS;AACxB,YAAM,SAAS,EAAE,SAAS,IAAI,EAAE,KAAK,MAAM,QAAQ,IAAI;AACvD,QAAE,SAAS,IAAI,IAAI,KAAK;AACxB,YAAM,QAAQ,EAAE,SAAS,IAAI,EAAE,IAAI,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK;AACvE,YAAM,OAAO,QAAQ,KAAK,UAAU;AACpC,UAAI,CAAC,WAAW,CAAC,QAAQ,KAAK,IAAI,QAAQ,KAAK,IAAI,WAAY;AAC/D,QAAE,SAAS,MAAM;AACjB,YAAM,cAAc,KAAK,IAAI,IAAI,IAAI,EAAE,YAAY,CAAC;AACpD,YAAM,UAAU,KAAK,IAAI,QAAQ,EAAE,WAAW;AAC9C,UAAI,cAAc,SAAS,eAAgB,GAAE,OAAO;AAAA,eAC3C,UAAU,SAAS,aAAc,GAAE,OAAO;AAAA,UAC9C;AAEL,YAAMA,SAAQ,EAAE,SAAS,UAAU,IAAI,IAAI,EAAE,YAAY;AACzD,YAAMC,OAAM,EAAE,GAAG,IAAI,OAAO,IAAI,EAAE,YAAY,GAAG,GAAG,IAAI,OAAO,IAAI,EAAE,YAAY,EAAE;AACnF,QAAE,WAAW,IAAI;AACjB,QAAE,aAAa,IAAI;AACnB,WAAK,WAAW,SAAS,IAAI,QAAQD,QAAOC,MAAK,MAAM,WAAW,SAAS,EAAE,IAAI,CAAC;AAClF;AAAA,IACF;AACA,UAAM,QAAQ,EAAE,SAAS,UAAU,IAAI,IAAI,EAAE,WAAW;AACxD,UAAM,MAAM,EAAE,GAAG,IAAI,OAAO,IAAI,EAAE,WAAW,GAAG,GAAG,IAAI,OAAO,IAAI,EAAE,WAAW,EAAE;AACjF,MAAE,WAAW,IAAI;AACjB,MAAE,aAAa,IAAI;AACnB,QAAI,UAAU,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,EAAG;AAC/C,SAAK,WAAW,UAAU,IAAI,QAAQ,OAAO,KAAK,MAAM,WAAW,SAAS,EAAE,IAAI,CAAC;AAAA,EACrF;AAOA,QAAM,gBAAgB,CAAC,GAAoB,SAAsB;AAC/D,UAAM,IAAI;AACV,QAAI,GAAG;AACL,YAAM,OAAO,EAAE,OAAO,cAAc,EAAE;AACtC,YAAM,QAAQ,SAAS,WAAW,EAAE,OAAO,SAAS;AACpD,UAAI,QAAS,SAAS,EAAE,OAAO,EAAE,KAAK,OAAO,QAAU,aAAY,WAAW;AAAA,IAChF;AACA,QAAI,SAAS,QAAS;AACtB,QAAI,QAAQ,IAAI,EAAE,SAAS,GAAG;AAC5B,UAAI,OAAO,IAAI,SAAS,EAAE,SAAS,EAAG,UAAS,UAAU,EAAE,MAAM,YAAY,EAAE,SAAS,CAAC;AACzF,cAAQ,OAAO,EAAE,SAAS;AAAA,IAC5B;AACA,eAAW,CAAC,IAAI,CAAC,KAAK,SAAS;AAC7B,YAAM,OAAO,OAAO,OAAO,cAAc,MAAM,CAAC,CAAC,OAAO,IAAI,SAAS,EAAE;AACvE,UAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,QAAS,SAAQ,OAAO,EAAE;AAAA,IAC3D;AACA,QAAI,QAAQ,SAAS,EAAG,eAAc;AAAA,EACxC;AAEA,QAAM,SAAS,CAAC,MAAuB;AACrC,UAAM,OAAO,UAAU,CAAC;AAExB,UAAM,UAA0B,EAAE,GAAG,MAAM,QAAQ,eAAe,MAAM,UAAU,YAAY,EAAE;AAChG,kBAAc,GAAG,QAAQ,IAAI;AAC7B,QAAI,QAAQ,SAAS,SAAS;AAC5B,cAAQ,IAAI,EAAE,WAAW,EAAE,QAAQ,QAAQ,QAAQ,MAAM,QAAQ,KAAK,CAAC;AACvE,UAAI,eAAe,MAAO;AAC1B,UAAI,QAAQ,SAAS,GAAG;AACtB,cAAM,MAAM,CAAC,GAAG,QAAQ,KAAK,CAAC;AAE9B,YAAI,SAAS,MAAM,OAAO,SAAS,QAAS,aAAY,YAAY;AAAA,iBAC3D,OAAO;AACd,kBAAQ,OAAO,EAAE,SAAS;AAC1B;AAAA,QACF;AACA,cAAM,IAAI,QAAQ,IAAI,IAAI,CAAC,CAAC;AAC5B,cAAM,IAAI,QAAQ,IAAI,IAAI,CAAC,CAAC;AAC5B,cAAM,IAAI,KAAK,IAAI,GAAG,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC;AAC9C,cAAM,IAAI,IAAI,EAAE,QAAQ,EAAE,MAAM;AAChC,gBAAQ,EAAE,KAAK,WAAW,GAAG,aAAa,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,MAAM,UAAU,oBAAI,IAAI,EAAE;AACzG;AAAA,MACF;AACA,UAAI,QAAQ,OAAO,EAAG;AAAA,IACxB;AACA,QAAI,SAAS,MAAO;AACpB,UAAM,YAAiC,OAAO,QAAQ,WAAW,YAAY,IACzE,QAAQ,WAAW,IACjB,SACA,QAAQ,WAAW,IACjB,WACA,OACJ;AACJ,UAAM,IAAW;AAAA,MACf,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,aAAa;AAAA,MACb;AAAA,MACA,cAAc;AAAA,MACd,OAAO;AAAA,IACT;AACA,YAAQ;AACR,QAAI,UAAU,eAAe,SAAS,QAAQ,IAAI,GAAG;AACnD,QAAE,QAAQ,OAAO,SAAS,MAAM;AAC9B,UAAE,QAAQ;AACV,YAAI,UAAU,KAAK,EAAE,YAAY,SAAU;AAC3C,UAAE,cAAc;AAChB,aAAK,EAAE,MAAM,aAAa,SAAS,EAAE,MAAM,QAAQ,EAAE,OAAO,CAAC;AAAA,MAC/D,GAAG,WAAW;AAAA,IAChB;AACA,SAAK,EAAE,MAAM,SAAS,SAAS,cAAc,UAAU,CAAC;AAAA,EAC1D;AAEA,QAAM,SAAS,CAAC,MAAuB;AACrC,UAAM,OAAO,OAAO,EAAE,WAAW;AACjC,QAAI,SAAS,SAAS;AACpB,YAAM,IAAI,QAAQ,IAAI,EAAE,SAAS;AACjC,YAAM,KAAK,YAAY,EAAE,MAAM;AAC/B,YAAM,QAAQ,CAAC,KAAK,EAAE,OAAO,MAAM,GAAG,KAAK,EAAE,OAAO,MAAM,GAAG;AAC7D,UAAI,GAAG;AACL,UAAE,SAAS;AACX,UAAE,OAAO,EAAE;AAAA,MACb;AACA,UAAI,SAAS,MAAM,IAAI,SAAS,EAAE,SAAS,GAAG;AAC5C,oBAAY,EAAE,WAAW,EAAE,MAAM,YAAY,EAAE,SAAS,GAAG,KAAK;AAChE;AAAA,MACF;AAAA,IACF;AACA,QAAI,SAAS,MAAM,OAAO,cAAc,EAAE,WAAW;AACnD,YAAM,UAAU,UAAU,CAAC;AAE3B,UAAI,QAAQ,SAAS,WAAW,EAAE,YAAY,GAAG;AAC/C,qBAAa,OAAO;AACpB;AAAA,MACF;AACA,YAAM,IAAI;AACV,QAAE,OAAO;AACT,UAAI,CAAC,EAAE,UAAU;AACf,YAAI,KAAK,QAAQ,QAAQ,EAAE,OAAO,MAAM,KAAK,WAAW,EAAE,OAAO,IAAI,EAAG;AACxE,UAAE,WAAW;AACb,mBAAW,CAAC;AAEZ,UAAE,eAAe,EAAE,aAAa,OAAO,QAAQ,WAAW,YAAY,IAAI,EAAE,YAAY;AACxF,aAAK,EAAE,MAAM,aAAa,SAAS,QAAQ,EAAE,QAAQ,cAAc,EAAE,aAAa,CAAC;AACnF;AAAA,MACF;AACA,WAAK,EAAE,MAAM,QAAQ,SAAS,QAAQ,EAAE,OAAO,CAAC;AAChD;AAAA,IACF;AACA,QAAI,CAAC,SAAS,SAAS,WAAW,EAAE,YAAY,EAAG,MAAK,EAAE,MAAM,SAAS,SAAS,UAAU,CAAC,EAAE,CAAC;AAAA,EAClG;AAEA,QAAM,OAAO,CAAC,MAAuB;AACnC,QAAI,OAAO,EAAE,WAAW,MAAM,SAAS;AAErC,YAAM,IAAI,QAAQ,IAAI,EAAE,SAAS;AACjC,UAAI,KAAK,SAAS,CAAC,MAAM,QAAQ,MAAM,IAAI,SAAS,EAAE,SAAS,GAAG;AAChE,cAAM,KAAK,YAAY,EAAE,MAAM;AAC/B,cAAM,QAAQ,EAAE,OAAO,MAAM,GAAG,KAAK,EAAE,OAAO,MAAM,GAAG;AACvD,UAAE,SAAS;AACX,oBAAY,EAAE,WAAW,EAAE,MAAM,YAAY,EAAE,SAAS,GAAG,OAAO,IAAI;AAAA,MACxE;AACA,cAAQ,OAAO,EAAE,SAAS;AAC1B,UAAI,SAAS,MAAM,IAAI,SAAS,EAAE,SAAS,GAAG;AAC5C,iBAAS,OAAO,EAAE,MAAM,YAAY,EAAE,SAAS,CAAC;AAChD;AAAA,MACF;AACA,UAAI,QAAQ,SAAS,EAAG,eAAc;AAAA,IACxC;AACA,QAAI,SAAS,MAAM,OAAO,cAAc,EAAE,UAAW,cAAa,UAAU,CAAC,CAAC;AAAA,EAChF;AAEA,QAAM,WAAW,CAAC,GAAoB,WAAyB;AAC7D,QAAI,OAAO,EAAE,WAAW,MAAM,SAAS;AACrC,YAAM,UAAU,QAAQ,OAAO,EAAE,SAAS;AAC1C,UAAI,SAAS,MAAM,IAAI,SAAS,EAAE,SAAS,GAAG;AAC5C,YAAI,QAAS,UAAS,UAAU,EAAE,MAAM,YAAY,EAAE,SAAS,CAAC;AAChE;AAAA,MACF;AACA,UAAI,QAAQ,SAAS,EAAG,eAAc;AAAA,IACxC;AACA,QAAI,SAAS,MAAM,OAAO,cAAc,EAAE,UAAW,aAAY,QAAQ,UAAU,CAAC,CAAC;AAAA,EACvF;AAEA,QAAM,UAAU,CAAC,MAAqB;AACpC,UAAM,YAAY,YAAY,EAAE,SAAS;AACzC,UAAM,SAAS,YAAY,EAAE,MAAM;AACnC,UAAM,KAAK,EAAE,QAAQ,QAAQ,QAAQ,SAAS,MAAM,EAAE;AACtD,UAAM,SAAS,cAAc,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,QAAQ,GAAG,WAAW,QAAQ;AACzG,QAAI,OAAO,SAAS,QAAQ;AAC1B,UAAI,OAAO,WAAW,EAAG,MAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO,QAAQ,IAAI,WAAW,MAAM,EAAE,KAAK,CAAC;AAClG;AAAA,IACF;AACA,QAAI,OAAO,MAAM,MAAM,KAAK,OAAO,MAAM,MAAM,EAAG;AAClD,SAAK,EAAE,MAAM,UAAU,OAAO,OAAO,OAAO,IAAI,WAAW,MAAM,EAAE,KAAK,CAAC;AAAA,EAC3E;AAEA,QAAM,WAAW,CAAC,MAA6B;AAE7C,QAAI,QAAQ,OAAO,KAAK,MAAO;AAC/B,UAAM,YAAY,YAAY,EAAE,SAAS;AACzC,UAAM,SAAS,YAAY,EAAE,MAAM;AACnC,UAAM,WAAW,OAAO,SAAS,EAAE,KAAK,KAAK,EAAE,QAAQ,IAAI,EAAE,QAAQ;AACrE,UAAM,OAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AAC1B,QAAI,EAAE,SAAS,gBAAgB;AAC7B,eAAS,EAAE,MAAM,SAAS;AAC1B,WAAK,WAAW,SAAS,QAAQ,GAAG,MAAM,EAAE,MAAM,WAAW,YAAY,OAAO,CAAC;AACjF;AAAA,IACF;AACA,QAAI,CAAC,OAAQ;AACb,UAAM,QAAQ,WAAW,OAAO;AAChC,WAAO,OAAO;AACd,QAAI,EAAE,SAAS,iBAAiB;AAC9B,UAAI,UAAU,EAAG,MAAK,WAAW,UAAU,QAAQ,OAAO,MAAM,EAAE,MAAM,WAAW,YAAY,OAAO,CAAC;AACvG;AAAA,IACF;AACA,aAAS;AACT,SAAK,WAAW,OAAO,QAAQ,OAAO,MAAM,EAAE,MAAM,WAAW,YAAY,OAAO,CAAC;AAAA,EACrF;AAEA,QAAM,YAAY,CAAC,WAAyB;AAC1C,gBAAY,MAAM;AAClB,QAAI,MAAO,UAAS,UAAU,UAAU,YAAY,MAAS,CAAC;AAC9D,QAAI,QAAQ;AACV,eAAS;AACT,WAAK,WAAW,UAAU,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,UAAU,YAAY,MAAS,GAAG,YAAY,OAAO,CAAC;AAAA,IACrH;AAAA,EACF;AAEA,SAAO;AAAA,IACL,KAAK,OAAO;AACV,UAAI,SAAU;AACd,UAAI,OAAO,SAAS,MAAM,IAAI,EAAG,YAAW,MAAM;AAClD,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AACH,iBAAO,OAAO,KAAK;AAAA,QACrB,KAAK;AACH,iBAAO,OAAO,KAAK;AAAA,QACrB,KAAK;AACH,iBAAO,KAAK,KAAK;AAAA,QACnB,KAAK;AACH,iBAAO,SAAS,OAAO,eAAe;AAAA,QACxC,KAAK;AAGH,cAAI,MAAM,WAAW,UAAW;AAChC,iBAAO,SAAS,OAAO,aAAa;AAAA,QACtC,KAAK;AACH,iBAAO,QAAQ,KAAK;AAAA,QACtB,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AACH,iBAAO,SAAS,KAAK;AAAA,MACzB;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ;AACN,gBAAU,OAAO;AACjB,cAAQ,MAAM;AACd,oBAAc;AACd,kBAAY;AAAA,IACd;AAAA,IACA,SAAS;AACP,aAAO,QAAQ,EAAE,WAAW,MAAM,OAAO,WAAW,MAAM,MAAM,OAAO,MAAM,UAAU,MAAM,SAAS,IAAI;AAAA,IAC5G;AAAA,IACA,WAAW;AACT,aAAO,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC;AAAA,IAC5B;AAAA,IACA,gBAAgB,KAAK;AACnB,qBAAe;AAAA,IACjB;AAAA,IACA,UAAU;AACR,UAAI,MAAO,YAAW,KAAK;AAC3B,cAAQ;AACR,cAAQ;AACR,eAAS;AACT,cAAQ,MAAM;AACd,iBAAW;AAAA,IACb;AAAA,EACF;AACF;;;AC1eO,SAAS,qBACd,SACA,QACA,UAA+B,CAAC,GACb;AACnB,QAAM,QAAQ,QAAQ;AACtB,MAAI,SAAwB;AAC5B,MAAI;AACJ,MAAI,QAAQ;AAEZ,QAAM,UAAU,MAAY;AAC1B,UAAM,YAAY;AAClB,UAAM,SAAS;AACf,aAAS;AACT,YAAQ;AACR,YAAQ,WAAW,MAAM;AAAA,EAC3B;AAEA,QAAM,aAAa,MAAY;AAC7B,aAAS;AACT,QAAI,QAAQ,EAAG,SAAQ;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,SAAS,MAAM;AACb,eAAS,QAAQ,KAAK,UAAU,SAAY,MAAM,QAAa,IAAI,IAAI;AACvE;AACA,UAAI,WAAW,KAAM,UAAS,OAAO,aAAa,UAAU;AAAA,IAC9D;AAAA,IACA,QAAQ;AACN,UAAI,WAAW,MAAM;AACnB,eAAO,YAAY,MAAM;AACzB,iBAAS;AAAA,MACX;AACA,UAAI,UAAU,EAAG,QAAO;AACxB,cAAQ;AACR,aAAO;AAAA,IACT;AAAA,IACA,SAAS;AACP,UAAI,WAAW,MAAM;AACnB,eAAO,YAAY,MAAM;AACzB,iBAAS;AAAA,MACX;AACA,eAAS;AACT,cAAQ;AAAA,IACV;AAAA,IACA,IAAI,UAAU;AACZ,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AACF;;;ACzCO,SAAS,cAAoB,SAA8C;AAChF,QAAM,EAAE,SAAS,OAAO,SAAS,IAAI;AACrC,QAAM,QAAQ,OAAO,QAAQ,OAAO;AACpC,QAAM,YAAY,oBAAI,IAA8C;AACpE,QAAM,QAAa,CAAC;AACpB,MAAI,cAAc;AAClB,MAAI,WAAW;AACf,MAAI,QAAuB;AAC3B,MAAI,UAAyB;AAC7B,MAAI,aAAwC;AAE5C,QAAM,aAAa,MAAY;AAC7B,QAAI,UAAU,KAAM,OAAM,WAAW,KAAK;AAC1C,YAAQ;AACR,cAAU;AACV,iBAAa;AAAA,EACf;AAEA,QAAM,MAAM,MAAY;AACtB,UAAM,MAAM,aAAa,SAAY,OAAO,SAAS,MAAM,KAAK,CAAC;AACjE,QAAI,QAAQ,MAAM;AAChB,iBAAW;AACX;AAAA,IACF;AACA,iBAAa,IAAI;AACjB,QAAI,UAAU,QAAQ,YAAY,IAAI,GAAI;AAC1C,QAAI,UAAU,KAAM,OAAM,WAAW,KAAK;AAC1C,cAAU,IAAI;AACd,YAAQ,MAAM,SAAS,MAAM;AAC3B,YAAM,OAAO;AACb,cAAQ;AACR,gBAAU;AACV,mBAAa;AACb,UAAI,SAAS,KAAM,SAAQ,KAAK,KAAK,MAAM,IAAI,CAAC,CAAC;AAAA,IACnD,GAAG,KAAK,IAAI,GAAG,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC;AAAA,EACtC;AAEA,QAAM,UAAyB;AAAA,IAC7B,IAAI,QAAQ;AACV,aAAO,MAAM;AAAA,IACf;AAAA,IACA,MAAM,MAAM,MAAM,KAAK;AAAA,IACvB,KAAK,OAAO;AACV,UAAI,SAAU,QAAO,MAAM,KAAK;AAChC,YAAM,KAAK,KAAK;AAChB,UAAI,YAAa,QAAO,MAAM,KAAK;AACnC,oBAAc;AACd,UAAI;AACF,YAAI;AACJ,gBAAQ,OAAO,MAAM,MAAM,OAAO,QAAW;AAC3C,gBAAM,WAAW,MAAM,KAAK;AAC5B,gBAAM,QAAQ,QAAQ,UAAU,IAAI;AACpC,cAAI,UAAU,SAAU;AACxB,gBAAM,QAAQ;AACd,cAAI;AACJ,qBAAW,YAAY,MAAM,KAAK,SAAS,EAAG,UAAS,OAAO,UAAU,IAAI;AAC5E,cAAI,SAAU;AAAA,QAChB;AAAA,MACF,UAAE;AACA,sBAAc;AACd,cAAM,SAAS;AAAA,MACjB;AACA,aAAO,MAAM,KAAK;AAAA,IACpB;AAAA,IACA,UAAU,UAAU;AAClB,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM;AACX,kBAAU,OAAO,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,UAAU;AACR,UAAI,SAAU;AACd,iBAAW;AACX,iBAAW;AACX,gBAAU,MAAM;AAAA,IAClB;AAAA,EACF;AACA,MAAI;AACJ,SAAO;AACT;;;AC/FO,IAAM,iBAA8B,EAAE,SAAS,IAAI;AAqBnD,IAAM,aAAuC,EAAE,OAAO,OAAO;AAG7D,SAAS,aACd,OACA,OACA,SAAsB,gBACJ;AAClB,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,MAAM,UAAU,SAAS,QAAQ;AAAA,IAC1C,KAAK;AACH,UAAI,MAAM,UAAU,QAAS,QAAO;AACpC,UAAI,MAAM,QAAQ,KAAM,QAAO,MAAM,UAAU,SAAS,QAAQ;AAChE,UAAI,MAAM,UAAU,cAAc,OAAO,GAAG,MAAM,KAAK,MAAM,GAAG,EAAG,QAAO;AAC1E,aAAO,EAAE,OAAO,YAAY,KAAK,MAAM,KAAK,OAAO,MAAM,GAAG,SAAS,MAAM,QAAQ;AAAA,IACrF,KAAK;AACH,UAAI,MAAM,UAAU,cAAc,MAAM,IAAI,MAAM,QAAQ,OAAO,QAAS,QAAO;AACjF,aAAO,EAAE,OAAO,SAAS,KAAK,MAAM,KAAK,OAAO,MAAM,OAAO,SAAS,MAAM,GAAG,SAAS,MAAM,QAAQ;AAAA,IACxG;AACE,aAAO;AAAA,EACX;AACF;AAGO,SAAS,cACd,OACA,SAAsB,gBACa;AACnC,MAAI,MAAM,UAAU,WAAY,QAAO;AACvC,SAAO,EAAE,IAAI,MAAM,QAAQ,OAAO,SAAS,OAAO,CAAC,OAAO,EAAE,MAAM,QAAQ,EAAE,GAAG;AACjF;AASO,SAAS,mBACd,SAC6C;AAC7C,QAAM,SAAsB,EAAE,GAAG,gBAAgB,GAAG,QAAQ,OAAO;AACnE,SAAO,cAAkD;AAAA,IACvD,SAAS,CAAC,OAAO,UAAU,aAAa,OAAO,OAAO,MAAM;AAAA,IAC5D,SAAS;AAAA,IACT,OAAO,QAAQ;AAAA,IACf,UAAU,CAAC,UAAU,cAAc,OAAO,MAAM;AAAA,EAClD,CAAC;AACH;;;AChEO,IAAM,uBAAyC,EAAE,MAAM,GAAG,cAAc,IAAI,QAAQ,KAAK,QAAQ,IAAI;AAiBrG,IAAM,mBAAoC,EAAE,OAAO,OAAO;AAG1D,SAAS,kBAAkB,OAAwB,OAAwB,SAA2B,sBAAuC;AAClJ,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,MAAM,UAAU,SAAS,QAAQ;AAAA,IAC1C,KAAK;AACH,aAAO,MAAM,UAAU,SAAS,EAAE,OAAO,WAAW,MAAM,EAAE,GAAG,MAAM,MAAM,GAAG,GAAG,MAAM,MAAM,EAAE,GAAG,OAAO,MAAM,EAAE,IAAI;AAAA,IACvH,KAAK;AACH,UAAI,MAAM,UAAU,aAAa,MAAM,IAAI,MAAM,QAAQ,OAAO,OAAQ,QAAO;AAC/E,aAAO,EAAE,OAAO,UAAU,MAAM,MAAM,MAAM,IAAI,MAAM,KAAK;AAAA,IAC7D,KAAK,QAAQ;AACX,UAAI,MAAM,UAAU,UAAW,QAAO;AACtC,YAAM,KAAK,MAAM,MAAM,IAAI,MAAM,KAAK;AACtC,YAAM,KAAK,MAAM,MAAM,IAAI,MAAM,KAAK;AACtC,YAAM,QAAQ,KAAK,IAAI,OAAO,WAAW,MAAM,KAAK,EAAE;AACtD,YAAM,SAAS,KAAK,IAAI,OAAO,WAAW,MAAM,KAAK,EAAE;AAGvD,UAAI,QAAQ,OAAQ,QAAO,SAAS,OAAO,eAAe,EAAE,OAAO,YAAY,MAAM,MAAM,KAAK,IAAI;AACpG,UAAI,KAAK,MAAM,IAAI,EAAE,KAAK,OAAO,KAAM,QAAO,EAAE,OAAO,UAAU,MAAM,MAAM,MAAM,IAAI,EAAE,GAAG,MAAM,MAAM,GAAG,GAAG,MAAM,MAAM,EAAE,EAAE;AAC9H,aAAO;AAAA,IACT;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AAGO,SAAS,mBAAmB,OAAwB,SAA2B,sBAAwD;AAC5I,MAAI,MAAM,UAAU,UAAW,QAAO;AACtC,SAAO,EAAE,IAAI,MAAM,QAAQ,OAAO,QAAQ,OAAO,CAAC,OAAO,EAAE,MAAM,QAAQ,EAAE,GAAG;AAChF;AASO,SAAS,iBAAiB,SAAuE;AACtG,QAAM,SAA2B,EAAE,GAAG,sBAAsB,GAAG,QAAQ,OAAO;AAC9E,SAAO,cAAgD;AAAA,IACrD,SAAS,CAAC,OAAO,UAAU,kBAAkB,OAAO,OAAO,MAAM;AAAA,IACjE,SAAS;AAAA,IACT,OAAO,QAAQ;AAAA,IACf,UAAU,CAAC,UAAU,mBAAmB,OAAO,MAAM;AAAA,EACvD,CAAC;AACH;;;AC1CO,IAAM,uBAAyC;AAAA,EACpD,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,MAAM;AAAA,EACN,MAAM;AAAA,EACN,UAAU;AAAA,EACV,OAAO;AACT;AAaO,IAAM,4BAA+C,EAAE,SAAS,OAAO,SAAS,OAAO,MAAM,MAAM,SAAS,MAAM;AA+DlH,IAAM,mBAAoC,EAAE,OAAO,QAAQ,SAAS,2BAA2B,UAAU,GAAG,QAAQ,MAAM,WAAW,MAAM,SAAS,MAAM;AAEjK,IAAM,OAAO,CAAC,SAA4B,YAA2B,MAAM,UAAU,WAA4B,EAAE,OAAO,QAAQ,SAAS,UAAU,GAAG,QAAQ,MAAM,WAAW,QAAQ;AACzL,IAAM,SAAS,CAAC,WAAsC,SAAS,IAAI,SAAS,SAAS,IAAI,SAAS;AAClG,IAAM,UAAU,CAAC,GAAsB,MAAiB,SAA4B,OAAO,EAAE,WAAW,EAAE,UAAU,SAAS,SAAS,EAAE,UAAU,EAAE;AACpJ,IAAM,WAAW,CAAC,GAAsB,SAAiD,SAAS,UAAa,SAAS,EAAE,OAAO,IAAI,EAAE,GAAG,GAAG,KAAK;AAClJ,IAAM,SAAS,CAAC,GAAsB,MAA0B,WAAqC,QAAQ,EAAE,QAAQ,OAAO;AAG9H,SAAS,OAAO,GAAsB,MAAc,OAAe,QAA0B,OAA8F;AACzL,MAAI,EAAE,WAAW,MAAM,QAAQ,MAAM,oBAAoB,CAAC,MAAM,aAAc,QAAO;AACrF,MAAI,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,EAAG,QAAO;AAClD,QAAM,OAAO,OAAO,IAAI;AACxB,SAAO,QAAQ,QAAQ,GAAG,MAAM,OAAO,IAAI,IAAI,OAAO;AACxD;AAGO,SAAS,cAAc,QAAgB,MAAc,GAAsB,MAAuB;AACvG,QAAM,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,SAAS,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;AAC9D,MAAI,KAAM,QAAO;AACjB,MAAK,IAAI,KAAK,CAAC,EAAE,WAAa,IAAI,KAAK,CAAC,EAAE,QAAU,QAAO;AAC3D,SAAO;AACT;AAGA,IAAM,UAAU,CAAC,OAAe,IAAY,SAAmC;AAC7E,MAAI,MAAM,KAAK,SAAS,KAAM,QAAO;AACrC,QAAM,KAAK,SAAS,SAAS,QAAQ,CAAC,SAAS;AAC/C,SAAO,IAAI,IAAI,IAAI;AACrB;AAEA,SAAS,MAAM,GAAsB,OAAc,QAAkC,QAAgB,GAAW,MAAc,MAAc,QAA2C;AACrL,QAAM,WAAW,cAAc,QAAQ,MAAM,GAAG,OAAO,IAAI;AAC3D,SAAO,EAAE,OAAO,YAAY,SAAS,GAAG,OAAO,QAAQ,QAAQ,MAAM,GAAG,MAAM,UAAU,QAAQ,OAAO,QAAQ,EAAE;AACnH;AAEA,SAAS,IAAI,GAAoD,GAAW,QAA2C;AACrH,QAAM,SAAS,EAAE,WAAW,SAAS,KAAK,IAAI,EAAE,QAAQ,KAAK,OAAO,kBAAkB,EAAE,QAAQ,OAAO;AACvG,SAAO,EAAE,OAAO,YAAY,SAAS,EAAE,SAAS,OAAO,EAAE,OAAO,SAAS,SAAS,WAAW,UAAU,OAAO,GAAG,WAAW,GAAG,UAAU,EAAE,UAAU,QAAQ,SAAS,EAAE,SAAS,KAAK;AACxL;AAEA,IAAM,SAAS,CAAC,GAAoD,OAA2C;AAAA,EAC7G,OAAO;AAAA,EACP,SAAS;AAAA,EACT,OAAO,EAAE;AAAA,EACT,SAAS;AAAA,EACT,OAAO,EAAE;AAAA,EACT,WAAW,EAAE;AAAA,EACb,UAAU,EAAE;AAAA,EACZ,QAAQ;AACV;AAGO,SAAS,kBAAkB,OAAwB,OAAwB,SAA2B,sBAAuC;AAClJ,QAAM,IAAI,OAAO,UAAU;AAC3B,MAAI,MAAM,SAAS,WAAW;AAC5B,WAAO,EAAE,GAAG,OAAO,SAAS,EAAE,GAAG,MAAM,SAAS,SAAS,MAAM,SAAS,SAAS,MAAM,SAAS,MAAM,MAAM,QAAQ,MAAM,QAAQ,KAAK,EAAE;AAAA,EAC3I;AACA,MAAI,MAAM,SAAS,SAAS;AAC1B,UAAM,IAAI,MAAM,QAAQ,YAAY,MAAM,KAAK,MAAM,UAAU,EAAE,GAAG,MAAM,SAAS,SAAS,MAAM,GAAG;AACrG,QAAI,MAAM,MAAM,MAAM,UAAU,QAAS,QAAO,KAAK,CAAC;AACtD,QAAI,MAAM,MAAM,MAAM,UAAU,WAAY,QAAO,OAAO,OAAO,CAAC;AAClE,WAAO,MAAM,MAAM,UAAU,QAAQ,EAAE,GAAG,OAAO,SAAS,EAAE;AAAA,EAC9D;AACA,MAAI,MAAM,SAAS,UAAU;AAE3B,QAAI,MAAM,UAAU,WAAW,MAAM,UAAU,aAAa,MAAM,UAAU,WAAY,QAAO,KAAK,MAAM,OAAO;AACjH,QAAI,MAAM,UAAU,WAAY,QAAO,OAAO,OAAO,MAAM,OAAO;AAClE,WAAO;AAAA,EACT;AACA,UAAQ,MAAM,OAAO;AAAA,IACnB,KAAK,QAAQ;AACX,UAAI,MAAM,SAAS,SAAS;AAC1B,YAAI,MAAM,QAAQ,QAAS,QAAO;AAClC,cAAM,QAAQ,MAAM,cAAc,QAAQ,MAAM,IAAI,MAAM,aAAa,OAAO;AAC9E,YAAI,CAAC,SAAS,MAAM,QAAS,QAAO,EAAE,GAAG,OAAO,WAAW,MAAM,EAAE;AACnE,cAAM,OAAO,IAAI,MAAM,KAAK,MAAM;AAClC,cAAM,QAAQ,IAAI,MAAM,KAAK,MAAM;AACnC,cAAM,OAAO,OAAO,MAAM,SAAS,MAAM,OAAO,QAAQ,WAAW,OAAO,MAAM,CAAC;AACjF,cAAM,IAAI,SAAS,MAAM,SAAS,MAAM,IAAI;AAC5C,YAAI,SAAS,KAAM,QAAO,KAAK,GAAG,MAAM,GAAG,IAAI;AAC/C,YAAI,KAAK,IAAI,IAAI,KAAK,OAAO,YAAa,QAAO,MAAM,GAAG,SAAS,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,MAAM,MAAM,MAAM,GAAG,MAAM;AAC1I,eAAO,EAAE,OAAO,SAAS,SAAS,GAAG,OAAO,SAAS,QAAQ,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,QAAQ,MAAM,OAAO,MAAM,MAAM,GAAG,UAAU,GAAG,QAAQ,KAAK;AAAA,MAC7I;AACA,UAAI,MAAM,SAAS,eAAe;AAChC,YAAI,MAAM,QAAQ,QAAS,QAAO;AAClC,eAAO,EAAE,OAAO,SAAS,SAAS,SAAS,MAAM,SAAS,MAAM,IAAI,GAAG,OAAO,SAAS,QAAQ,EAAE,GAAG,MAAM,GAAG,GAAG,MAAM,EAAE,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,MAAM,GAAG,UAAU,GAAG,QAAQ,KAAK;AAAA,MAC3L;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK,SAAS;AACZ,UAAI,MAAM,SAAS,SAAS;AAC1B,YAAI,MAAM,UAAU,QAAS,QAAO;AACpC,cAAM,OAAO,IAAI,MAAM,KAAK,MAAM;AAClC,cAAM,QAAQ,IAAI,MAAM,KAAK,MAAM;AAEnC,YAAI,OAAO,MAAM,SAAS,MAAM,OAAO,QAAQ,WAAW,OAAO,MAAM,CAAC,MAAM,KAAM,QAAO,KAAK,MAAM,SAAS,MAAM,GAAG,IAAI;AAC5H,cAAM,IAAI,SAAS,MAAM,SAAS,MAAM,IAAI;AAC5C,cAAM,SAAS,MAAM,SAAS;AAC9B,cAAM,cAAc,MAAM,QAAQ;AAElC,YAAI,KAAK,IAAI,WAAW,IAAI,KAAK,IAAI,MAAM,IAAI,EAAG,QAAO,KAAK,GAAG,MAAM,GAAG,IAAI;AAC9E,YAAI,KAAK,IAAI,MAAM,IAAI,OAAO,YAAa,QAAO,EAAE,GAAG,OAAO,SAAS,GAAG,QAAQ,OAAO,aAAa,MAAM,MAAM,EAAE;AACpH,eAAO,MAAM,GAAG,SAAS,MAAM,QAAQ,QAAQ,MAAM,GAAG,QAAQ,MAAM,MAAM,IAAI,MAAM,MAAM,OAAO,MAAM,CAAC,GAAG,OAAO,GAAG,MAAM,MAAM,MAAM,GAAG,MAAM;AAAA,MACpJ;AACA,UAAI,MAAM,SAAS,cAAc;AAC/B,YAAI,MAAM,UAAU,QAAS,QAAO;AACpC,cAAM,OAAO,IAAI,MAAM,OAAO,IAAI,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM;AACnE,cAAM,QAAQ,IAAI,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM,IAAI,MAAM,OAAO;AAEpE,YAAI,KAAK,MAAM,MAAM,KAAK,IAAI,OAAO,YAAa,QAAO,EAAE,GAAG,OAAO,MAAM,MAAM,EAAE;AACnF,cAAM,OAAO,OAAO,MAAM,SAAS,MAAM,OAAO,QAAQ,EAAE,MAAM,OAAO,cAAc,MAAM,kBAAkB,MAAM,oBAAoB,MAAM,CAAC;AAE9I,YAAI,SAAS,KAAM,QAAO,KAAK,MAAM,OAAO;AAC5C,cAAM,IAAI,SAAS,MAAM,SAAS,MAAM,IAAI;AAC5C,eAAO,MAAM,GAAG,SAAS,MAAM,QAAQ,MAAM,MAAM,GAAG,QAAQ,MAAM,MAAM,IAAI,MAAM,MAAM,IAAI,GAAG,OAAO,GAAG,MAAM,MAAM,MAAM,GAAG,MAAM;AAAA,MACxI;AACA,UAAI,MAAM,SAAS,OAAQ,QAAO,MAAM,UAAU,WAAW,MAAM,IAAI,MAAM,QAAQ,OAAO,SAAS,KAAK,MAAM,OAAO,IAAI;AAC3H,UAAI,MAAM,SAAS,YAAa,QAAO,MAAM,UAAU,UAAU,KAAK,MAAM,OAAO,IAAI;AACvF,UAAI,MAAM,SAAS,cAAe,QAAO,EAAE,GAAG,OAAO,SAAS,SAAS,MAAM,SAAS,MAAM,IAAI,GAAG,OAAO,SAAS,QAAQ,EAAE,GAAG,MAAM,GAAG,GAAG,MAAM,EAAE,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,MAAM,EAAE;AAC1L,aAAO;AAAA,IACT;AAAA,IACA,KAAK,YAAY;AACf,UAAI,MAAM,SAAS,WAAW,MAAM,SAAS,cAAc;AACzD,YAAI,MAAM,WAAW,MAAM,SAAS,UAAU,UAAU,SAAU,QAAO;AACzE,cAAM,SACJ,MAAM,SAAS,UAAU,MAAM,UAAU,IAAI,MAAM,KAAK,MAAM,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM;AAC5H,cAAM,OAAO,OAAO,MAAM;AAC1B,cAAM,IAAI,QAAQ,SAAS,MAAM,QAAQ,MAAM,IAAI,MAAM,MAAM,IAAI;AACnE,cAAM,OAAO,SAAS,OAAO,MAAM,MAAM,IAAI,KAAK,IAAI,MAAM,MAAM,CAAC,IAAI;AACvE,cAAM,IAAI,SAAS,MAAM,SAAS,MAAM,IAAI;AAC5C,eAAO,MAAM,GAAG,MAAM,OAAO,MAAM,QAAQ,QAAQ,MAAM,GAAG,MAAM,OAAO,GAAG,MAAM,MAAM,MAAM,GAAG,MAAM;AAAA,MACzG;AACA,UAAI,MAAM,SAAS,OAAQ,QAAO,MAAM,UAAU,WAAW,MAAM,IAAI,MAAM,QAAQ,OAAO,SAAS,IAAI,OAAO,MAAM,GAAG,MAAM,IAAI;AACnI,UAAI,MAAM,SAAS,YAAa,QAAO,MAAM,UAAU,UAAU,IAAI,OAAO,MAAM,GAAG,MAAM,IAAI;AAC/F,aAAO;AAAA,IACT;AAAA,IACA,KAAK,YAAY;AACf,UAAI,MAAM,SAAS,WAAW;AAC5B,YAAI,MAAM,YAAY,SAAU,QAAO,EAAE,OAAO,WAAW,SAAS,MAAM,SAAS,OAAO,MAAM,QAAQ,OAAO,WAAW,WAAW,MAAM,WAAW,UAAU,GAAG,QAAQ,KAAK;AAChL,eAAO,KAAK,MAAM,SAAS,MAAM,WAAW,KAAK;AAAA,MACnD;AACA,UAAI,MAAM,SAAS,QAAS,QAAO,EAAE,GAAG,OAAO,WAAW,MAAM,EAAE;AAClE,aAAO;AAAA,IACT;AAAA,IACA,KAAK,WAAW;AACd,UAAI,MAAM,SAAS,QAAS,QAAO,MAAM,KAAK,MAAM,QAAQ,KAAK,MAAM,SAAS,MAAM,GAAG,IAAI,IAAI,EAAE,GAAG,OAAO,WAAW,MAAM,EAAE;AAChI,UAAI,MAAM,SAAS,QAAQ;AACzB,YAAI,MAAM,IAAI,MAAM,MAAO,QAAO;AAClC,eAAO,MAAM,IAAI,MAAM,YAAY,OAAO,SAAS,KAAK,MAAM,SAAS,MAAM,WAAW,IAAI,IAAI,KAAK,MAAM,OAAO;AAAA,MACpH;AAEA,UAAI,MAAM,SAAS,cAAe,QAAO,kBAAkB,KAAK,MAAM,OAAO,GAAG,OAAO,MAAM;AAC7F,aAAO;AAAA,IACT;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,WAAW,OAAoD,QAA+F;AACrK,SAAO,EAAE,MAAM,MAAM,UAAU,MAAM,cAAc,OAAO,aAAa,QAAQ,MAAM,UAAU,OAAO,QAAQ,GAAG,kBAAkB,MAAM,oBAAoB,MAAM;AACrK;AAGO,SAAS,mBAAmB,OAAwB,SAA2B,sBAAwD;AAC5I,QAAM,OAAO,CAAC,OAAgC,EAAE,MAAM,QAAQ,EAAE;AAChE,OAAK,MAAM,UAAU,WAAW,MAAM,UAAU,eAAe,MAAM,UAAU,QAAS,QAAO,EAAE,IAAI,MAAM,OAAO,OAAO,QAAQ,OAAO,KAAK;AAC7I,MAAI,MAAM,UAAU,UAAW,QAAO,EAAE,IAAI,MAAM,OAAO,OAAO,KAAK;AACrE,SAAO;AACT;AASO,SAAS,iBAAiB,SAAuE;AACtG,QAAM,SAA2B,EAAE,GAAG,sBAAsB,GAAG,QAAQ,OAAO;AAC9E,SAAO,cAAgD;AAAA,IACrD,SAAS,CAAC,OAAO,UAAU,kBAAkB,OAAO,OAAO,MAAM;AAAA,IACjE,SAAS;AAAA,IACT,OAAO,QAAQ;AAAA,IACf,UAAU,CAAC,UAAU,mBAAmB,OAAO,MAAM;AAAA,EACvD,CAAC;AACH;","names":["scale","pan"]}
|
package/dist/drag.d.ts
CHANGED
|
@@ -104,11 +104,25 @@ interface DragIntentOptions<T extends DragTarget> {
|
|
|
104
104
|
readonly tuning?: Partial<DragTuning>;
|
|
105
105
|
/**
|
|
106
106
|
* The place within a target the pointer is at (a tab index, a stack
|
|
107
|
-
* position), unique across targets; default: the target's id.
|
|
108
|
-
* shown target moves the indicator at once but
|
|
109
|
-
* place has held `roomAfter` ms.
|
|
107
|
+
* position, a pane's edge), unique across targets; default: the target's id.
|
|
108
|
+
* Moving between places of the shown target moves the indicator at once but
|
|
109
|
+
* the room only once the new place has held `roomAfter` ms.
|
|
110
|
+
*
|
|
111
|
+
* - `previous` is the place this target had on the last sample (null if it
|
|
112
|
+
* had none), so a place can hold near its boundary (hysteresis).
|
|
113
|
+
* - A null place is no target: there is no drop at that point of the target.
|
|
114
|
+
* It isn't picked or held, the indicator hides there at once (or shows a
|
|
115
|
+
* target beneath that has a place), no room opens, and a release there
|
|
116
|
+
* lands on a target beneath, else goes home.
|
|
117
|
+
* - It is also asked about the current target while the pointer is just
|
|
118
|
+
* outside its hit zone (within the band, or during the gap grace). Answer
|
|
119
|
+
* the nearest place there: returning null outside the zone drops the band
|
|
120
|
+
* and the grace, so the indicator hides a few px past the edge. Return null
|
|
121
|
+
* only for a part of the target that takes no drop.
|
|
122
|
+
* - It is called on every sample for each target under the pointer and for
|
|
123
|
+
* the current one, so keep it cheap and free of side effects.
|
|
110
124
|
*/
|
|
111
|
-
readonly placeOf?: (target: T, point: Point) => string;
|
|
125
|
+
readonly placeOf?: (target: T, point: Point, previous: string | null) => string | null;
|
|
112
126
|
}
|
|
113
127
|
/** A drag's intent: feed it the pointer on every frame; it says what the indicator shows and what a release lands on. */
|
|
114
128
|
interface DragIntent<T extends DragTarget> {
|
|
@@ -157,6 +171,8 @@ interface DragIntent<T extends DragTarget> {
|
|
|
157
171
|
* kept only by the gap grace can keep the indicator, never show it.
|
|
158
172
|
* - Room is made once the shown target has held `roomAfter` ms, never for a
|
|
159
173
|
* free target, and closes at once when it changes.
|
|
174
|
+
* - Where `placeOf` gives a target no place (null), it is no target: it is
|
|
175
|
+
* neither shown nor landed on there.
|
|
160
176
|
*/
|
|
161
177
|
declare function createDragIntent<T extends DragTarget>(options: DragIntentOptions<T>): DragIntent<T>;
|
|
162
178
|
|
package/dist/drag.js
CHANGED
|
@@ -35,6 +35,7 @@ function liftDecision(press, tuning = {}) {
|
|
|
35
35
|
function createDragIntent(options) {
|
|
36
36
|
const tuning = { ...DRAG_DEFAULTS, ...options.tuning };
|
|
37
37
|
const placeOf = options.placeOf ?? ((t) => t.id);
|
|
38
|
+
let lastPlaces = /* @__PURE__ */ new Map();
|
|
38
39
|
let home = { ...options.home };
|
|
39
40
|
let samples = [];
|
|
40
41
|
let cur = null;
|
|
@@ -70,9 +71,16 @@ function createDragIntent(options) {
|
|
|
70
71
|
}
|
|
71
72
|
if (!inRect(home, p, -tuning.homeInset)) leftHome = true;
|
|
72
73
|
const atHome = inRect(home, p);
|
|
73
|
-
const
|
|
74
|
+
const places = /* @__PURE__ */ new Map();
|
|
75
|
+
const placeAt = (x) => {
|
|
76
|
+
if (!places.has(x.id)) places.set(x.id, placeOf(x, p, lastPlaces.get(x.id) ?? null));
|
|
77
|
+
return places.get(x.id);
|
|
78
|
+
};
|
|
79
|
+
const live = (x) => x !== null && placeAt(x) !== null;
|
|
80
|
+
const raw = atHome ? null : targetAt(targets.filter((x) => inRect(x.hit, p) && live(x)), p);
|
|
74
81
|
cur = follow(cur, targets);
|
|
75
82
|
shown = follow(shown, targets);
|
|
83
|
+
if (!live(cur)) cur = null;
|
|
76
84
|
let next = raw;
|
|
77
85
|
if (cur && !atHome && inRect(cur.hit, p, tuning.band)) {
|
|
78
86
|
const breaks = raw !== null && !same(raw, cur) && raw.priority < cur.priority && inRect(raw.hit, p, -tuning.breakthrough);
|
|
@@ -89,6 +97,7 @@ function createDragIntent(options) {
|
|
|
89
97
|
shown = s;
|
|
90
98
|
};
|
|
91
99
|
const under = !!cur && inRect(cur.hit, p, tuning.band);
|
|
100
|
+
if (shown && !live(shown)) show(null);
|
|
92
101
|
if (!shown) {
|
|
93
102
|
if (cur && under && leftHome && aiming) show(cur);
|
|
94
103
|
} else if (same(cur, shown)) {
|
|
@@ -99,7 +108,7 @@ function createDragIntent(options) {
|
|
|
99
108
|
} else if (cur.priority < shown.priority && inRect(cur.hit, p, -tuning.breakthrough) || under && t - curSince >= tuning.switchHold) {
|
|
100
109
|
show(cur);
|
|
101
110
|
}
|
|
102
|
-
const at = shown ?
|
|
111
|
+
const at = shown ? placeAt(shown) : null;
|
|
103
112
|
if (at !== place) {
|
|
104
113
|
place = at;
|
|
105
114
|
placeSince = t;
|
|
@@ -107,13 +116,17 @@ function createDragIntent(options) {
|
|
|
107
116
|
if (!shown || shown.free) roomPlace = null;
|
|
108
117
|
else if (t - shownSince >= tuning.roomAfter && t - placeSince >= tuning.roomAfter) roomPlace = place;
|
|
109
118
|
else if (shownSince === t) roomPlace = null;
|
|
119
|
+
lastPlaces = places;
|
|
110
120
|
return { shown, room: roomPlace !== null, roomPlace, aiming };
|
|
111
121
|
},
|
|
112
122
|
release(point, targets) {
|
|
113
123
|
if (inRect(home, point)) return null;
|
|
114
124
|
const s = follow(shown, targets);
|
|
115
125
|
if (s) return s;
|
|
116
|
-
return targetAt(
|
|
126
|
+
return targetAt(
|
|
127
|
+
targets.filter((x) => inRect(x.hit, point) && placeOf(x, point, lastPlaces.get(x.id) ?? null) !== null),
|
|
128
|
+
point
|
|
129
|
+
);
|
|
117
130
|
},
|
|
118
131
|
hide(time) {
|
|
119
132
|
shown = null;
|
|
@@ -121,6 +134,7 @@ function createDragIntent(options) {
|
|
|
121
134
|
shownSince = time;
|
|
122
135
|
place = null;
|
|
123
136
|
roomPlace = null;
|
|
137
|
+
lastPlaces = /* @__PURE__ */ new Map();
|
|
124
138
|
samples = [];
|
|
125
139
|
anchor = null;
|
|
126
140
|
restart = true;
|
package/dist/drag.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/drag/intent.ts","../src/drag/preview.ts"],"sourcesContent":["import type { Point, Rect } from \"../shapes\";\n\n/**\n * The drag intent model's tuning. Distances in px, speeds in px/ms, times in\n * ms; every value can be overridden per intent (see\n * {@link DragIntentOptions.tuning}).\n */\nexport interface DragTuning {\n /** A mouse press lifts once the pointer has moved this far. */\n readonly lift: number;\n /** A touch press lifts only after it has been held this long… */\n readonly touchHold: number;\n /** …and then moved this far (moving that far before the hold is over is not a drag). */\n readonly touchLift: number;\n /** The pointer's speed is measured over this window and smoothed. */\n readonly speedWindow: number;\n /** Aiming: the smoothed speed is below this… */\n readonly aimSpeed: number;\n /** …or the pointer has stayed within `still` px of one spot for `dwell` ms. */\n readonly still: number;\n readonly dwell: number;\n /** The current target holds while the pointer is within this band of its zone. */\n readonly band: number;\n /** A higher-priority target takes over at once when the pointer is this far inside it. */\n readonly breakthrough: number;\n /** A gap between zones shorter than this keeps the current target (for a pointer that aims). */\n readonly gapGrace: number;\n /** Aiming, the shown target moves to another one only once that one has held this long. */\n readonly switchHold: number;\n /** Room is made once the shown target, and the place in it, have held this long. */\n readonly roomAfter: number;\n /** The pointer has left the item's home once it is outside the home shrunk by this much on every side. */\n readonly homeInset: number;\n}\n\n/**\n * The defaults: a mouse lifts after 5 px, a touch after a 180 ms hold and\n * 8 px; aiming is slower than 0.45 px/ms over 50 ms, or within 12 px for\n * 90 ms; a 10 px band, a 4 px breakthrough and a 120 ms gap grace; a switch\n * waits 150 ms (the shortest an indicator may show); room after 160 ms.\n */\nexport const DRAG_DEFAULTS: DragTuning = {\n lift: 5,\n touchHold: 180,\n touchLift: 8,\n speedWindow: 50,\n aimSpeed: 0.45,\n still: 12,\n dwell: 90,\n band: 10,\n breakthrough: 4,\n gapGrace: 120,\n switchHold: 150,\n roomAfter: 160,\n homeInset: 6,\n};\n\n/**\n * A drop target: plain data. `hit` is the zone the pointer can be in;\n * `priority` how precise it is (1 beats 2 beats 3; equal priorities go to the\n * smaller zone). `slot` is where the dragged item would land, `label` what to\n * call it; the model reads neither. A `free` target has no slot (the item\n * floats where it is dropped): no room is made for it.\n */\nexport interface DragTarget {\n readonly id: string;\n readonly priority: 1 | 2 | 3;\n readonly hit: Rect;\n readonly slot?: Rect;\n readonly label?: string;\n readonly free?: boolean;\n}\n\n/** Whether `point` lies in `rect` grown by `margin` px on every side (shrunk, for a negative margin). */\nexport function inRect(rect: Rect, point: Point, margin = 0): boolean {\n return point.x >= rect.x - margin && point.x <= rect.x + rect.width + margin && point.y >= rect.y - margin && point.y <= rect.y + rect.height + margin;\n}\n\nconst area = (r: Rect): number => r.width * r.height;\n\n/** The most precise target under `point`: the lowest priority number, then the smallest zone; null when none is. */\nexport function targetAt<T extends DragTarget>(targets: readonly T[], point: Point): T | null {\n let best: T | null = null;\n for (const t of targets) {\n if (!inRect(t.hit, point)) continue;\n if (!best || t.priority < best.priority || (t.priority === best.priority && area(t.hit) < area(best.hit))) best = t;\n }\n return best;\n}\n\n/** What a press does so far: not yet a drag (`wait`), a drag (`lift`), or never one (`refuse`: a touch that moved too soon). */\nexport type LiftDecision = \"wait\" | \"lift\" | \"refuse\";\n\n/**\n * The lift rule for a press that has moved `distance` px in `elapsed` ms. A\n * mouse (or pen) lifts at `lift` px. A touch lifts only after `touchHold` ms,\n * then at `touchLift` px; moving `touchLift` px before the hold is over\n * refuses the drag (the finger is scrolling or swiping).\n */\nexport function liftDecision(press: { readonly touch: boolean; readonly distance: number; readonly elapsed: number }, tuning: Partial<DragTuning> = {}): LiftDecision {\n const t = { ...DRAG_DEFAULTS, ...tuning };\n if (!press.touch) return press.distance >= t.lift ? \"lift\" : \"wait\";\n if (press.elapsed < t.touchHold) return press.distance >= t.touchLift ? \"refuse\" : \"wait\";\n return press.distance >= t.touchLift ? \"lift\" : \"wait\";\n}\n\n/** One update's answer. */\nexport interface DragFrame<T extends DragTarget> {\n /** The target the drop indicator shows, or null. */\n readonly shown: T | null;\n /** The others make room for the shown target: it, and the place in it, have held `roomAfter` ms. */\n readonly room: boolean;\n /**\n * The place the room is made for, or null. Within one target it stays on\n * the old place until a new one has held `roomAfter` ms (see\n * {@link DragIntentOptions.placeOf}).\n */\n readonly roomPlace: string | null;\n /** The pointer is aiming: slow, or nearly still for a moment. */\n readonly aiming: boolean;\n}\n\n/** What a drag intent knows now. */\nexport interface DragIntentState<T extends DragTarget> {\n /** The pointer's smoothed speed, px/ms. */\n readonly speed: number;\n /** The target under the pointer as the hysteresis holds it, or null. */\n readonly current: T | null;\n /** The target the indicator shows, or null. */\n readonly shown: T | null;\n /** The pointer has left the item's home. */\n readonly leftHome: boolean;\n}\n\nexport interface DragIntentOptions<T extends DragTarget> {\n /** Where the item was lifted from: the indicator stays off until the pointer leaves it, and a release there does nothing. */\n readonly home: Rect;\n /** Overrides of {@link DRAG_DEFAULTS}. */\n readonly tuning?: Partial<DragTuning>;\n /**\n * The place within a target the pointer is at (a tab index, a stack\n * position), unique across targets; default: the target's id. Moving between places of the\n * shown target moves the indicator at once but the room only once the new\n * place has held `roomAfter` ms.\n */\n readonly placeOf?: (target: T, point: Point) => string;\n}\n\n/** A drag's intent: feed it the pointer on every frame; it says what the indicator shows and what a release lands on. */\nexport interface DragIntent<T extends DragTarget> {\n /**\n * A pointer sample at `time` (ms), with the targets as they stand (they may\n * be rebuilt between calls: the model follows them by id). Call it once a\n * frame while the item is held, also when the pointer doesn't move: a\n * dwell is time passing.\n */\n update(point: Point, time: number, targets: readonly T[]): DragFrame<T>;\n /**\n * What a release at `point` lands on: the shown target whenever one shows\n * (a drop lands where the indicator was), else the target under the\n * pointer, else null (the item goes home). A release over home is null: it\n * does nothing. Feed the release point to `update` first, so a last jump\n * of the pointer counts.\n */\n release(point: Point, targets: readonly T[]): T | null;\n /**\n * Hide the indicator (the view is scrolling under the pointer, say). The\n * aim starts afresh: the next sample never counts as aiming, and a dwell\n * starts again.\n */\n hide(time: number): void;\n /** The item's home moved with the view (it scrolled) by (dx, dy). */\n moveHome(dx: number, dy: number): void;\n readonly state: DragIntentState<T>;\n readonly tuning: DragTuning;\n}\n\n/**\n * The drag intent model: when the drop indicator shows, which target it\n * shows, when the others make room, and what a release lands on. Pure: fed\n * pointer samples and plain-data targets, no DOM and no clock of its own.\n *\n * - The indicator shows only when the pointer has left the item's home, a\n * target is under it, and it is aiming (slow, or nearly still for a\n * moment). Time spent crossing a big target at speed doesn't count.\n * - The most precise target wins (priority, then the smaller zone).\n * - Hysteresis: the current target holds while the pointer is within `band`\n * px of its zone, unless a more precise one is clearly (`breakthrough` px)\n * under the pointer; for a pointer that aims, a short gap between zones\n * (`gapGrace` ms) keeps it too.\n * - Leaving the shown target: flying, the indicator hides at once; aiming, it\n * moves to the new target once that has held `switchHold` ms (so no\n * indicator shows for less than that), at once for a breakthrough. A target\n * kept only by the gap grace can keep the indicator, never show it.\n * - Room is made once the shown target has held `roomAfter` ms, never for a\n * free target, and closes at once when it changes.\n */\nexport function createDragIntent<T extends DragTarget>(options: DragIntentOptions<T>): DragIntent<T> {\n const tuning: DragTuning = { ...DRAG_DEFAULTS, ...options.tuning };\n const placeOf = options.placeOf ?? ((t: T) => t.id);\n let home: Rect = { ...options.home };\n let samples: { x: number; y: number; t: number }[] = [];\n let cur: T | null = null;\n let curSince = 0;\n let lastInside = 0;\n let shown: T | null = null;\n let shownSince = 0;\n let place: string | null = null;\n let placeSince = 0;\n let roomPlace: string | null = null;\n let leftHome = false;\n let speed = 0;\n let anchor: Point | null = null;\n let stillSince = 0;\n let restart = false;\n const same = (a: T | null, b: T | null): boolean => (a === null || b === null ? a === b : a.id === b.id);\n // Targets may be rebuilt between calls: follow the ones held by id.\n const follow = (t: T | null, targets: readonly T[]): T | null => (t ? (targets.find((x) => x.id === t.id) ?? t) : null);\n\n return {\n tuning,\n get state(): DragIntentState<T> {\n return { speed, current: cur, shown, leftHome };\n },\n update(point, time, targets) {\n const p = point;\n const t = time;\n samples.push({ x: p.x, y: p.y, t });\n while (samples.length > 2 && t - samples[0]!.t > tuning.speedWindow) samples.shift();\n const a = samples[0]!;\n speed = speed * 0.5 + (Math.hypot(p.x - a.x, p.y - a.y) / Math.max(1, t - a.t)) * 0.5;\n if (!anchor || Math.hypot(p.x - anchor.x, p.y - anchor.y) > tuning.still) {\n anchor = { x: p.x, y: p.y };\n stillSince = t;\n }\n if (!inRect(home, p, -tuning.homeInset)) leftHome = true;\n const atHome = inRect(home, p);\n const raw = atHome ? null : targetAt(targets, p);\n cur = follow(cur, targets);\n shown = follow(shown, targets);\n let next = raw;\n if (cur && !atHome && inRect(cur.hit, p, tuning.band)) {\n const breaks = raw !== null && !same(raw, cur) && raw.priority < cur.priority && inRect(raw.hit, p, -tuning.breakthrough);\n next = breaks ? raw : cur;\n }\n if (!next && cur && !atHome && t - lastInside < tuning.gapGrace) next = cur;\n if (next && inRect(next.hit, p)) lastInside = t;\n if (!same(next, cur)) curSince = t;\n cur = next;\n const aiming = !restart && (speed < tuning.aimSpeed || t - stillSince >= tuning.dwell);\n restart = false;\n const show = (s: T | null): void => {\n if (!same(s, shown)) shownSince = t;\n shown = s;\n };\n // Under the pointer (within its band), not just kept by the gap grace.\n const under = !!cur && inRect(cur.hit, p, tuning.band);\n if (!shown) {\n if (cur && under && leftHome && aiming) show(cur);\n } else if (same(cur, shown)) {\n // It stays while its target holds; but a pointer flying out past the band hides it at once.\n if (!aiming && !under) show(null);\n else shown = cur;\n } else if (!cur || !aiming) {\n show(null);\n } else if ((cur.priority < shown.priority && inRect(cur.hit, p, -tuning.breakthrough)) || (under && t - curSince >= tuning.switchHold)) {\n show(cur);\n }\n // The place within the shown target, and the room: after the target and the place have both held.\n const at = shown ? placeOf(shown, p) : null;\n if (at !== place) {\n place = at;\n placeSince = t;\n }\n if (!shown || shown.free) roomPlace = null;\n else if (t - shownSince >= tuning.roomAfter && t - placeSince >= tuning.roomAfter) roomPlace = place;\n else if (shownSince === t) roomPlace = null;\n return { shown, room: roomPlace !== null, roomPlace, aiming };\n },\n release(point, targets) {\n if (inRect(home, point)) return null;\n // Drop = shown: a shown target is kept until it hides (flying out past its band hides it at once).\n const s = follow(shown, targets);\n if (s) return s;\n return targetAt(targets, point);\n },\n hide(time) {\n shown = null;\n cur = null;\n shownSince = time;\n place = null;\n roomPlace = null;\n samples = [];\n anchor = null;\n restart = true;\n },\n moveHome(dx, dy) {\n home = { ...home, x: home.x + dx, y: home.y + dy };\n },\n };\n}\n","import type { DragFrame, DragTarget } from \"./intent\";\n\n/**\n * How a drag previews its drop, from least to most motion while dragging:\n *\n * - `\"indicator\"`: nothing moves; only the indicator (an outline or an\n * insertion marker) shows the target.\n * - `\"gap\"`: once the room is made, the others move aside and open the slot;\n * the item stays under the pointer.\n * - `\"snap\"`: the gap opens and the item itself glides into it (for a target\n * with a slot; one without keeps the indicator).\n *\n * Every mode follows the same intent: what shows, when, and what a release\n * lands on are the drag intent's, so a drop always lands where the preview\n * showed it.\n */\nexport type DragPreview = \"indicator\" | \"gap\" | \"snap\";\n\n/** The preview a drag uses unless the host picks another. */\nexport const DRAG_PREVIEW_DEFAULT: DragPreview = \"snap\";\n\n/** What to draw for one frame of a drag. */\nexport interface PreviewPlan {\n /** Draw the indicator on the shown target. */\n readonly showIndicator: boolean;\n /** Lay the others out around the room (the gap is open). */\n readonly makeRoom: boolean;\n /** Draw the item in its slot instead of under the pointer. */\n readonly snapItem: boolean;\n}\n\n/**\n * The plan for a frame of the drag intent under `preview`. `slotted` says\n * whether the shown target's drop has a slot the item can sit in (default: a\n * target that isn't `free`); only such a target snaps, and in `\"snap\"` it\n * previews with the item rather than the indicator.\n *\n * - `\"indicator\"`: the indicator, never room, never a snap.\n * - `\"gap\"`: the indicator, and room when the intent makes it.\n * - `\"snap\"`: room when the intent makes it, the item snapped into a slotted\n * target's room; the indicator only for a target without a slot.\n */\nexport function previewPlan<T extends DragTarget>(\n frame: Pick<DragFrame<T>, \"shown\" | \"room\">,\n preview: DragPreview,\n slotted: boolean = !!frame.shown && !frame.shown.free,\n): PreviewPlan {\n const shown = !!frame.shown;\n if (preview === \"indicator\") return { showIndicator: shown, makeRoom: false, snapItem: false };\n if (preview === \"gap\") return { showIndicator: shown, makeRoom: frame.room, snapItem: false };\n // By design a slotted target draws nothing until the room opens (`roomAfter`); a release then still lands on it.\n return { showIndicator: shown && !slotted, makeRoom: frame.room, snapItem: frame.room && shown && slotted };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/drag/intent.ts","../src/drag/preview.ts"],"sourcesContent":["import type { Point, Rect } from \"../shapes\";\n\n/**\n * The drag intent model's tuning. Distances in px, speeds in px/ms, times in\n * ms; every value can be overridden per intent (see\n * {@link DragIntentOptions.tuning}).\n */\nexport interface DragTuning {\n /** A mouse press lifts once the pointer has moved this far. */\n readonly lift: number;\n /** A touch press lifts only after it has been held this long… */\n readonly touchHold: number;\n /** …and then moved this far (moving that far before the hold is over is not a drag). */\n readonly touchLift: number;\n /** The pointer's speed is measured over this window and smoothed. */\n readonly speedWindow: number;\n /** Aiming: the smoothed speed is below this… */\n readonly aimSpeed: number;\n /** …or the pointer has stayed within `still` px of one spot for `dwell` ms. */\n readonly still: number;\n readonly dwell: number;\n /** The current target holds while the pointer is within this band of its zone. */\n readonly band: number;\n /** A higher-priority target takes over at once when the pointer is this far inside it. */\n readonly breakthrough: number;\n /** A gap between zones shorter than this keeps the current target (for a pointer that aims). */\n readonly gapGrace: number;\n /** Aiming, the shown target moves to another one only once that one has held this long. */\n readonly switchHold: number;\n /** Room is made once the shown target, and the place in it, have held this long. */\n readonly roomAfter: number;\n /** The pointer has left the item's home once it is outside the home shrunk by this much on every side. */\n readonly homeInset: number;\n}\n\n/**\n * The defaults: a mouse lifts after 5 px, a touch after a 180 ms hold and\n * 8 px; aiming is slower than 0.45 px/ms over 50 ms, or within 12 px for\n * 90 ms; a 10 px band, a 4 px breakthrough and a 120 ms gap grace; a switch\n * waits 150 ms (the shortest an indicator may show); room after 160 ms.\n */\nexport const DRAG_DEFAULTS: DragTuning = {\n lift: 5,\n touchHold: 180,\n touchLift: 8,\n speedWindow: 50,\n aimSpeed: 0.45,\n still: 12,\n dwell: 90,\n band: 10,\n breakthrough: 4,\n gapGrace: 120,\n switchHold: 150,\n roomAfter: 160,\n homeInset: 6,\n};\n\n/**\n * A drop target: plain data. `hit` is the zone the pointer can be in;\n * `priority` how precise it is (1 beats 2 beats 3; equal priorities go to the\n * smaller zone). `slot` is where the dragged item would land, `label` what to\n * call it; the model reads neither. A `free` target has no slot (the item\n * floats where it is dropped): no room is made for it.\n */\nexport interface DragTarget {\n readonly id: string;\n readonly priority: 1 | 2 | 3;\n readonly hit: Rect;\n readonly slot?: Rect;\n readonly label?: string;\n readonly free?: boolean;\n}\n\n/** Whether `point` lies in `rect` grown by `margin` px on every side (shrunk, for a negative margin). */\nexport function inRect(rect: Rect, point: Point, margin = 0): boolean {\n return point.x >= rect.x - margin && point.x <= rect.x + rect.width + margin && point.y >= rect.y - margin && point.y <= rect.y + rect.height + margin;\n}\n\nconst area = (r: Rect): number => r.width * r.height;\n\n/** The most precise target under `point`: the lowest priority number, then the smallest zone; null when none is. */\nexport function targetAt<T extends DragTarget>(targets: readonly T[], point: Point): T | null {\n let best: T | null = null;\n for (const t of targets) {\n if (!inRect(t.hit, point)) continue;\n if (!best || t.priority < best.priority || (t.priority === best.priority && area(t.hit) < area(best.hit))) best = t;\n }\n return best;\n}\n\n/** What a press does so far: not yet a drag (`wait`), a drag (`lift`), or never one (`refuse`: a touch that moved too soon). */\nexport type LiftDecision = \"wait\" | \"lift\" | \"refuse\";\n\n/**\n * The lift rule for a press that has moved `distance` px in `elapsed` ms. A\n * mouse (or pen) lifts at `lift` px. A touch lifts only after `touchHold` ms,\n * then at `touchLift` px; moving `touchLift` px before the hold is over\n * refuses the drag (the finger is scrolling or swiping).\n */\nexport function liftDecision(press: { readonly touch: boolean; readonly distance: number; readonly elapsed: number }, tuning: Partial<DragTuning> = {}): LiftDecision {\n const t = { ...DRAG_DEFAULTS, ...tuning };\n if (!press.touch) return press.distance >= t.lift ? \"lift\" : \"wait\";\n if (press.elapsed < t.touchHold) return press.distance >= t.touchLift ? \"refuse\" : \"wait\";\n return press.distance >= t.touchLift ? \"lift\" : \"wait\";\n}\n\n/** One update's answer. */\nexport interface DragFrame<T extends DragTarget> {\n /** The target the drop indicator shows, or null. */\n readonly shown: T | null;\n /** The others make room for the shown target: it, and the place in it, have held `roomAfter` ms. */\n readonly room: boolean;\n /**\n * The place the room is made for, or null. Within one target it stays on\n * the old place until a new one has held `roomAfter` ms (see\n * {@link DragIntentOptions.placeOf}).\n */\n readonly roomPlace: string | null;\n /** The pointer is aiming: slow, or nearly still for a moment. */\n readonly aiming: boolean;\n}\n\n/** What a drag intent knows now. */\nexport interface DragIntentState<T extends DragTarget> {\n /** The pointer's smoothed speed, px/ms. */\n readonly speed: number;\n /** The target under the pointer as the hysteresis holds it, or null. */\n readonly current: T | null;\n /** The target the indicator shows, or null. */\n readonly shown: T | null;\n /** The pointer has left the item's home. */\n readonly leftHome: boolean;\n}\n\nexport interface DragIntentOptions<T extends DragTarget> {\n /** Where the item was lifted from: the indicator stays off until the pointer leaves it, and a release there does nothing. */\n readonly home: Rect;\n /** Overrides of {@link DRAG_DEFAULTS}. */\n readonly tuning?: Partial<DragTuning>;\n /**\n * The place within a target the pointer is at (a tab index, a stack\n * position, a pane's edge), unique across targets; default: the target's id.\n * Moving between places of the shown target moves the indicator at once but\n * the room only once the new place has held `roomAfter` ms.\n *\n * - `previous` is the place this target had on the last sample (null if it\n * had none), so a place can hold near its boundary (hysteresis).\n * - A null place is no target: there is no drop at that point of the target.\n * It isn't picked or held, the indicator hides there at once (or shows a\n * target beneath that has a place), no room opens, and a release there\n * lands on a target beneath, else goes home.\n * - It is also asked about the current target while the pointer is just\n * outside its hit zone (within the band, or during the gap grace). Answer\n * the nearest place there: returning null outside the zone drops the band\n * and the grace, so the indicator hides a few px past the edge. Return null\n * only for a part of the target that takes no drop.\n * - It is called on every sample for each target under the pointer and for\n * the current one, so keep it cheap and free of side effects.\n */\n readonly placeOf?: (target: T, point: Point, previous: string | null) => string | null;\n}\n\n/** A drag's intent: feed it the pointer on every frame; it says what the indicator shows and what a release lands on. */\nexport interface DragIntent<T extends DragTarget> {\n /**\n * A pointer sample at `time` (ms), with the targets as they stand (they may\n * be rebuilt between calls: the model follows them by id). Call it once a\n * frame while the item is held, also when the pointer doesn't move: a\n * dwell is time passing.\n */\n update(point: Point, time: number, targets: readonly T[]): DragFrame<T>;\n /**\n * What a release at `point` lands on: the shown target whenever one shows\n * (a drop lands where the indicator was), else the target under the\n * pointer, else null (the item goes home). A release over home is null: it\n * does nothing. Feed the release point to `update` first, so a last jump\n * of the pointer counts.\n */\n release(point: Point, targets: readonly T[]): T | null;\n /**\n * Hide the indicator (the view is scrolling under the pointer, say). The\n * aim starts afresh: the next sample never counts as aiming, and a dwell\n * starts again.\n */\n hide(time: number): void;\n /** The item's home moved with the view (it scrolled) by (dx, dy). */\n moveHome(dx: number, dy: number): void;\n readonly state: DragIntentState<T>;\n readonly tuning: DragTuning;\n}\n\n/**\n * The drag intent model: when the drop indicator shows, which target it\n * shows, when the others make room, and what a release lands on. Pure: fed\n * pointer samples and plain-data targets, no DOM and no clock of its own.\n *\n * - The indicator shows only when the pointer has left the item's home, a\n * target is under it, and it is aiming (slow, or nearly still for a\n * moment). Time spent crossing a big target at speed doesn't count.\n * - The most precise target wins (priority, then the smaller zone).\n * - Hysteresis: the current target holds while the pointer is within `band`\n * px of its zone, unless a more precise one is clearly (`breakthrough` px)\n * under the pointer; for a pointer that aims, a short gap between zones\n * (`gapGrace` ms) keeps it too.\n * - Leaving the shown target: flying, the indicator hides at once; aiming, it\n * moves to the new target once that has held `switchHold` ms (so no\n * indicator shows for less than that), at once for a breakthrough. A target\n * kept only by the gap grace can keep the indicator, never show it.\n * - Room is made once the shown target has held `roomAfter` ms, never for a\n * free target, and closes at once when it changes.\n * - Where `placeOf` gives a target no place (null), it is no target: it is\n * neither shown nor landed on there.\n */\nexport function createDragIntent<T extends DragTarget>(options: DragIntentOptions<T>): DragIntent<T> {\n const tuning: DragTuning = { ...DRAG_DEFAULTS, ...options.tuning };\n const placeOf = options.placeOf ?? ((t: T) => t.id);\n // Each target's place on the last sample, by id: its `previous` on the next.\n let lastPlaces = new Map<string, string | null>();\n let home: Rect = { ...options.home };\n let samples: { x: number; y: number; t: number }[] = [];\n let cur: T | null = null;\n let curSince = 0;\n let lastInside = 0;\n let shown: T | null = null;\n let shownSince = 0;\n let place: string | null = null;\n let placeSince = 0;\n let roomPlace: string | null = null;\n let leftHome = false;\n let speed = 0;\n let anchor: Point | null = null;\n let stillSince = 0;\n let restart = false;\n const same = (a: T | null, b: T | null): boolean => (a === null || b === null ? a === b : a.id === b.id);\n // Targets may be rebuilt between calls: follow the ones held by id.\n const follow = (t: T | null, targets: readonly T[]): T | null => (t ? (targets.find((x) => x.id === t.id) ?? t) : null);\n\n return {\n tuning,\n get state(): DragIntentState<T> {\n return { speed, current: cur, shown, leftHome };\n },\n update(point, time, targets) {\n const p = point;\n const t = time;\n samples.push({ x: p.x, y: p.y, t });\n while (samples.length > 2 && t - samples[0]!.t > tuning.speedWindow) samples.shift();\n const a = samples[0]!;\n speed = speed * 0.5 + (Math.hypot(p.x - a.x, p.y - a.y) / Math.max(1, t - a.t)) * 0.5;\n if (!anchor || Math.hypot(p.x - anchor.x, p.y - anchor.y) > tuning.still) {\n anchor = { x: p.x, y: p.y };\n stillSince = t;\n }\n if (!inRect(home, p, -tuning.homeInset)) leftHome = true;\n const atHome = inRect(home, p);\n // Each target's place at this point, worked out once per sample; a null place is no target.\n const places = new Map<string, string | null>();\n const placeAt = (x: T): string | null => {\n if (!places.has(x.id)) places.set(x.id, placeOf(x, p, lastPlaces.get(x.id) ?? null));\n return places.get(x.id)!;\n };\n const live = (x: T | null): x is T => x !== null && placeAt(x) !== null;\n const raw = atHome ? null : targetAt(targets.filter((x) => inRect(x.hit, p) && live(x)), p);\n cur = follow(cur, targets);\n shown = follow(shown, targets);\n if (!live(cur)) cur = null;\n let next = raw;\n if (cur && !atHome && inRect(cur.hit, p, tuning.band)) {\n const breaks = raw !== null && !same(raw, cur) && raw.priority < cur.priority && inRect(raw.hit, p, -tuning.breakthrough);\n next = breaks ? raw : cur;\n }\n if (!next && cur && !atHome && t - lastInside < tuning.gapGrace) next = cur;\n if (next && inRect(next.hit, p)) lastInside = t;\n if (!same(next, cur)) curSince = t;\n cur = next;\n const aiming = !restart && (speed < tuning.aimSpeed || t - stillSince >= tuning.dwell);\n restart = false;\n const show = (s: T | null): void => {\n if (!same(s, shown)) shownSince = t;\n shown = s;\n };\n // Under the pointer (within its band), not just kept by the gap grace.\n const under = !!cur && inRect(cur.hit, p, tuning.band);\n // A shown target with no place here is no drop: the indicator hides at once (the switch hold is for targets).\n if (shown && !live(shown)) show(null);\n if (!shown) {\n if (cur && under && leftHome && aiming) show(cur);\n } else if (same(cur, shown)) {\n // It stays while its target holds; but a pointer flying out past the band hides it at once.\n if (!aiming && !under) show(null);\n else shown = cur;\n } else if (!cur || !aiming) {\n show(null);\n } else if ((cur.priority < shown.priority && inRect(cur.hit, p, -tuning.breakthrough)) || (under && t - curSince >= tuning.switchHold)) {\n show(cur);\n }\n // The place within the shown target, and the room: after the target and the place have both held.\n const at = shown ? placeAt(shown) : null;\n if (at !== place) {\n place = at;\n placeSince = t;\n }\n if (!shown || shown.free) roomPlace = null;\n else if (t - shownSince >= tuning.roomAfter && t - placeSince >= tuning.roomAfter) roomPlace = place;\n else if (shownSince === t) roomPlace = null;\n lastPlaces = places;\n return { shown, room: roomPlace !== null, roomPlace, aiming };\n },\n release(point, targets) {\n if (inRect(home, point)) return null;\n // Drop = shown: a shown target is kept until it hides (flying out past its band hides it at once).\n const s = follow(shown, targets);\n if (s) return s;\n // Else the target under the pointer that has a place there (a null place is no drop).\n return targetAt(\n targets.filter((x) => inRect(x.hit, point) && placeOf(x, point, lastPlaces.get(x.id) ?? null) !== null),\n point,\n );\n },\n hide(time) {\n shown = null;\n cur = null;\n shownSince = time;\n place = null;\n roomPlace = null;\n lastPlaces = new Map();\n samples = [];\n anchor = null;\n restart = true;\n },\n moveHome(dx, dy) {\n home = { ...home, x: home.x + dx, y: home.y + dy };\n },\n };\n}\n","import type { DragFrame, DragTarget } from \"./intent\";\n\n/**\n * How a drag previews its drop, from least to most motion while dragging:\n *\n * - `\"indicator\"`: nothing moves; only the indicator (an outline or an\n * insertion marker) shows the target.\n * - `\"gap\"`: once the room is made, the others move aside and open the slot;\n * the item stays under the pointer.\n * - `\"snap\"`: the gap opens and the item itself glides into it (for a target\n * with a slot; one without keeps the indicator).\n *\n * Every mode follows the same intent: what shows, when, and what a release\n * lands on are the drag intent's, so a drop always lands where the preview\n * showed it.\n */\nexport type DragPreview = \"indicator\" | \"gap\" | \"snap\";\n\n/** The preview a drag uses unless the host picks another. */\nexport const DRAG_PREVIEW_DEFAULT: DragPreview = \"snap\";\n\n/** What to draw for one frame of a drag. */\nexport interface PreviewPlan {\n /** Draw the indicator on the shown target. */\n readonly showIndicator: boolean;\n /** Lay the others out around the room (the gap is open). */\n readonly makeRoom: boolean;\n /** Draw the item in its slot instead of under the pointer. */\n readonly snapItem: boolean;\n}\n\n/**\n * The plan for a frame of the drag intent under `preview`. `slotted` says\n * whether the shown target's drop has a slot the item can sit in (default: a\n * target that isn't `free`); only such a target snaps, and in `\"snap\"` it\n * previews with the item rather than the indicator.\n *\n * - `\"indicator\"`: the indicator, never room, never a snap.\n * - `\"gap\"`: the indicator, and room when the intent makes it.\n * - `\"snap\"`: room when the intent makes it, the item snapped into a slotted\n * target's room; the indicator only for a target without a slot.\n */\nexport function previewPlan<T extends DragTarget>(\n frame: Pick<DragFrame<T>, \"shown\" | \"room\">,\n preview: DragPreview,\n slotted: boolean = !!frame.shown && !frame.shown.free,\n): PreviewPlan {\n const shown = !!frame.shown;\n if (preview === \"indicator\") return { showIndicator: shown, makeRoom: false, snapItem: false };\n if (preview === \"gap\") return { showIndicator: shown, makeRoom: frame.room, snapItem: false };\n // By design a slotted target draws nothing until the room opens (`roomAfter`); a release then still lands on it.\n return { showIndicator: shown && !slotted, makeRoom: frame.room, snapItem: frame.room && shown && slotted };\n}\n"],"mappings":"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","names":[]}
|