@tremolo-ui/dom 0.7.0 → 0.8.0
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/dist/index.cjs +1298 -235
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +767 -84
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +767 -84
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1263 -237
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/file/accept.ts +17 -0
- package/src/file/drop-zone.ts +8 -10
- package/src/index.ts +76 -1
- package/src/input/check-steps.ts +144 -0
- package/src/input/defaults.ts +32 -0
- package/src/input/direction.ts +107 -0
- package/src/knob/geometry.ts +100 -0
- package/src/number-input/stepper-drag.ts +139 -0
- package/src/number-input/text.ts +100 -0
- package/src/number-input/value.ts +129 -0
- package/src/options/replace.ts +22 -0
- package/src/piano/index.ts +128 -2
- package/src/piano/layout.ts +19 -0
- package/src/piano/shortcuts.ts +86 -0
- package/src/pointer/long-press.ts +103 -0
- package/src/points-editor/index.ts +361 -0
- package/src/position.ts +20 -0
- package/src/slider/decimal-digits.ts +23 -0
- package/src/slider/marks.ts +56 -0
- package/src/style.ts +41 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["names","pointerEvent"],"sources":["../src/canvas/context.ts","../src/canvas/animation.ts","../src/file/accept.ts","../src/file/drop-zone.ts","../src/input/modifiers.ts","../src/input/apply-delta.ts","../src/midi/access.ts","../src/midi/message.ts","../src/midi/input.ts","../src/piano/layout.ts","../src/pointer/drag.ts","../src/piano/index.ts","../src/xy.ts","../src/pointer/drag-value.ts","../src/pointer/wheel.ts","../src/selection/box.ts"],"sourcesContent":["/**\n * The assignable parts of a 2D context's drawing state. They have to be\n * carried across a resize by hand because setting `canvas.width` resets the\n * context to its defaults. The transform and line dash are handled separately\n * by {@link DrawingContext} because they are exposed through methods.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save\n */\nexport const drawingState = [\n 'strokeStyle',\n 'fillStyle',\n 'globalAlpha',\n 'lineWidth',\n 'lineCap',\n 'lineJoin',\n 'miterLimit',\n 'lineDashOffset',\n 'shadowOffsetX',\n 'shadowOffsetY',\n 'shadowBlur',\n 'shadowColor',\n 'globalCompositeOperation',\n 'filter',\n 'font',\n 'fontKerning',\n 'fontStretch',\n 'fontVariantCaps',\n 'textAlign',\n 'textBaseline',\n 'direction',\n 'letterSpacing',\n 'textRendering',\n 'wordSpacing',\n 'imageSmoothingEnabled',\n 'imageSmoothingQuality',\n] as const\n\nexport type DrawingState = (typeof drawingState)[number]\nexport type DrawingStateValue = CanvasRenderingContext2D[DrawingState]\nexport type DrawingContext = Pick<CanvasRenderingContext2D, DrawingState> & {\n /** The current line dash sequence. */\n lineDash: number[]\n /** The current transformation matrix. */\n transform: DOMMatrix\n}\n\nexport function isDrawingState(value: unknown): value is DrawingState {\n const names: readonly string[] = drawingState\n return typeof value === 'string' && names.includes(value)\n}\n\n/** Copy the drawing state off a context, to put back after a resize. */\nexport function readDrawingState(\n context: CanvasRenderingContext2D,\n): DrawingContext {\n const state = {} as DrawingContext\n for (const property of drawingState) {\n ;(state[property] as DrawingStateValue) = context[property]\n }\n state.lineDash = context.getLineDash()\n state.transform = context.getTransform()\n return state\n}\n\n/** Put a state read by {@link readDrawingState} back onto a context. */\nexport function writeDrawingState(\n context: CanvasRenderingContext2D,\n state: DrawingContext,\n transformScale = 1,\n) {\n for (const property of drawingState) {\n ;(context[property] as DrawingStateValue) = state[property]\n }\n context.setLineDash(state.lineDash)\n const { a, b, c, d, e, f } = state.transform\n context.setTransform(\n a * transformScale,\n b * transformScale,\n c * transformScale,\n d * transformScale,\n e * transformScale,\n f * transformScale,\n )\n}\n\n/**\n * Give the canvas a backing store of `width * dpr` by `height * dpr` device\n * pixels while keeping it `width` by `height` in CSS pixels, and scale the\n * context so that drawing code can work in CSS pixels throughout.\n *\n * Assigning to `canvas.width` clears the canvas and resets the context, so\n * this both resizes and re-establishes the transform.\n */\nexport function applyDevicePixelRatio(\n canvas: HTMLCanvasElement,\n context: CanvasRenderingContext2D,\n width: number,\n height: number,\n dpr: number,\n) {\n canvas.width = width * dpr\n canvas.height = height * dpr\n // Reset the current transformation matrix to the identity matrix\n context.setTransform(1, 0, 0, 1, 0, 0)\n context.scale(dpr, dpr)\n canvas.style.width = `${width}px`\n canvas.style.height = `${height}px`\n}\n","import {\n applyDevicePixelRatio,\n readDrawingState,\n writeDrawingState,\n type DrawingContext,\n} from './context'\n\n/** What the canvas looked like when a frame was drawn. */\nexport interface AnimationFrame {\n /** Width of the canvas in CSS pixels. */\n width: number\n /** Height of the canvas in CSS pixels. */\n height: number\n /** Frames drawn so far, starting at 0. */\n count: number\n /** Milliseconds since the previous frame. */\n deltaTime: number\n /** Milliseconds since the instance was created. */\n elapsedTime: number\n /** Frames per second implied by `deltaTime`, or 0 when no time elapsed. */\n fps: number\n}\n\nexport type CanvasDrawFunction = (\n context: CanvasRenderingContext2D,\n frame: AnimationFrame,\n) => void\n\nexport type CanvasInitFunction = (\n context: CanvasRenderingContext2D,\n size: { width: number; height: number },\n) => void\n\nexport interface AnimationCanvasOptions {\n /** Called for every frame. */\n draw: CanvasDrawFunction\n\n /** Called once, after the first size is known and before the first frame. */\n init?: CanvasInitFunction\n\n /**\n * Keep drawing on every animation frame. When off, a frame is drawn only on\n * a resize or an explicit {@link AnimationCanvasInstance.redraw}.\n *\n * @default true\n */\n animate?: boolean\n\n /**\n * Size in CSS pixels. Ignored when `resizable` is on.\n *\n * @default { width: 100, height: 100 }\n */\n size?: { width: number; height: number }\n\n /**\n * Follow the size of the canvas's parent element instead of `size`.\n *\n * Fixed for the lifetime of the instance: it decides whether a\n * `ResizeObserver` is attached.\n *\n * @default false\n */\n resizable?: boolean\n\n /**\n * Carry the drawing across a resize, so that the canvas does not blank for a\n * frame while the new size is drawn.\n *\n * @default true\n */\n reduceFlickering?: boolean\n\n /**\n * Passed to `getContext('2d', …)`. Read once when the context is created, so\n * it is fixed for the lifetime of the instance.\n *\n * @see https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement/getContext#contextattributes\n */\n contextAttributes?: CanvasRenderingContext2DSettings\n}\n\nexport interface AnimationCanvasInstance {\n /**\n * Replace the given options. Lets a wrapper feed a fresh `draw` in on every\n * render without restarting the animation, so the frame count and the\n * elapsed time keep running.\n *\n * `resizable` and `contextAttributes` are fixed for the lifetime of the\n * instance and are ignored here.\n *\n * While `animate` is off this also draws a frame, since nothing else would.\n */\n update: (options: Partial<AnimationCanvasOptions>) => void\n\n /** Draw one frame now, whether or not `animate` is on. */\n redraw: () => void\n\n destroy: () => void\n}\n\n/**\n * Drive a canvas from `requestAnimationFrame`, keeping its backing store in\n * step with the device pixel ratio and, optionally, with the size of its\n * parent.\n *\n * Drawing code works in CSS pixels: the context is scaled by the device pixel\n * ratio, and `width` / `height` of each {@link AnimationFrame} are CSS pixels.\n */\nexport function createAnimationCanvas(\n canvas: HTMLCanvasElement,\n options: AnimationCanvasOptions,\n): AnimationCanvasInstance {\n let opts = options\n\n const context2d = canvas.getContext('2d', opts.contextAttributes)\n if (!context2d) {\n throw new Error('createAnimationCanvas: cannot get a 2d context')\n }\n const context: CanvasRenderingContext2D = context2d\n\n const resizable = opts.resizable ?? false\n\n /** Off-document canvas holding the drawing while the real one is resized. */\n let memo: HTMLCanvasElement | null = null\n let memoContext: CanvasRenderingContext2D | null = null\n\n let width = 0\n let height = 0\n let appliedDpr = 0\n /** Whether a size has been applied, so a frame can be drawn. */\n let sized = false\n let initialized = false\n\n let frameId: number | null = null\n let count = -1\n const startTime = performance.now()\n let previousTime = startTime\n\n function devicePixelRatio() {\n return globalThis.devicePixelRatio || 1\n }\n\n /**\n * Copy the canvas onto the memo canvas at its full device resolution, 1:1.\n *\n * Assigning to `memo.width` resets the memo's transform to the identity, so\n * the copy neither scales nor resamples.\n */\n function takeSnapshot() {\n if (!(opts.reduceFlickering ?? true)) return false\n if (!memo) {\n memo = globalThis.document?.createElement('canvas') ?? null\n memoContext = memo?.getContext('2d', opts.contextAttributes) ?? null\n }\n if (!memo || !memoContext) return false\n\n memo.width = canvas.width\n memo.height = canvas.height\n if (canvas.width > 0 && canvas.height > 0) {\n memoContext.drawImage(canvas, 0, 0)\n }\n return true\n }\n\n /**\n * Put the snapshot back, given the size in CSS pixels it was taken at.\n *\n * The context is scaled to CSS pixels, so drawing the snapshot at its old\n * CSS size leaves it exactly where it was. While the device pixel ratio\n * stays put that is a 1:1 copy of device pixels, and when it changes the\n * snapshot is rescaled once, from its full resolution.\n */\n function restoreSnapshot(\n hasSnapshot: boolean,\n previousWidth: number,\n previousHeight: number,\n ) {\n if (!hasSnapshot || !memo) return\n if (memo.width <= 0 || memo.height <= 0) return\n context.drawImage(memo, 0, 0, previousWidth, previousHeight)\n }\n\n function applySize(w: number, h: number) {\n const dpr = devicePixelRatio()\n const previousWidth = width\n const previousHeight = height\n const previousDpr = appliedDpr\n const state: DrawingContext | null = sized\n ? readDrawingState(context)\n : null\n const hasSnapshot = sized && takeSnapshot()\n applyDevicePixelRatio(canvas, context, w, h, dpr)\n width = w\n height = h\n appliedDpr = dpr\n sized = true\n // Restore pixels while the reset context still has neutral alpha,\n // compositing, filter, and shadow settings.\n restoreSnapshot(hasSnapshot, previousWidth, previousHeight)\n if (state) {\n writeDrawingState(context, state, dpr / previousDpr)\n }\n }\n\n function applyDevicePixelRatioIfNeeded() {\n if (sized && devicePixelRatio() !== appliedDpr) {\n applySize(width, height)\n }\n }\n\n function drawFrame(timestamp = performance.now()) {\n if (!sized) return\n applyDevicePixelRatioIfNeeded()\n if (!initialized) {\n initialized = true\n opts.init?.(context, { width, height })\n }\n const deltaTime = timestamp - previousTime\n previousTime = timestamp\n count += 1\n opts.draw(context, {\n width,\n height,\n count,\n deltaTime,\n elapsedTime: timestamp - startTime,\n fps: deltaTime > 0 ? 1000 / deltaTime : 0,\n })\n }\n\n function tick(timestamp: DOMHighResTimeStamp) {\n // Scheduled before drawing so that a slow frame does not delay the next\n // request, matching how requestAnimationFrame loops are usually written.\n frameId = requestAnimationFrame(tick)\n drawFrame(timestamp)\n }\n\n function startLoop() {\n if (frameId !== null) return\n previousTime = performance.now()\n frameId = requestAnimationFrame(tick)\n }\n\n function stopLoop() {\n if (frameId === null) return\n cancelAnimationFrame(frameId)\n frameId = null\n }\n\n let observer: ResizeObserver | null = null\n\n if (resizable) {\n const parent = canvas.parentElement\n if (!parent) {\n throw new Error(\n 'createAnimationCanvas: resizable needs the canvas to have a parent element',\n )\n }\n // The observer reports the current size as soon as it starts, so it is the\n // single source of the size — including the first one. Reading the parent\n // here instead would mix the padding box with the content box the observer\n // reports.\n observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width: w, height: h } = entry.contentRect\n if (w !== width || h !== height || devicePixelRatio() !== appliedDpr) {\n applySize(w, h)\n }\n // With the loop running the next frame covers the new size already.\n if (!(opts.animate ?? true)) drawFrame()\n }\n })\n observer.observe(parent)\n } else {\n const { width: w = 100, height: h = 100 } = opts.size ?? {}\n applySize(w, h)\n }\n\n if (opts.animate ?? true) {\n startLoop()\n } else if (sized) {\n drawFrame()\n }\n\n return {\n update: (next) => {\n const wasAnimating = opts.animate ?? true\n opts = {\n ...opts,\n ...next,\n resizable,\n contextAttributes: opts.contextAttributes,\n }\n\n if (!resizable) {\n const { width: w = 100, height: h = 100 } = opts.size ?? {}\n if (w !== width || h !== height) applySize(w, h)\n }\n applyDevicePixelRatioIfNeeded()\n\n const animating = opts.animate ?? true\n if (animating && !wasAnimating) startLoop()\n if (!animating && wasAnimating) stopLoop()\n\n // Without a loop running, a resize and this call are the only things\n // that can put anything new on the canvas — so a new `draw` has to be\n // painted here. That is what makes a canvas driven by state rather than\n // by time work: the wrapper re-renders, pushes the new handler in, and\n // the frame it draws reflects it.\n if (!animating) drawFrame()\n },\n redraw: drawFrame,\n destroy: () => {\n stopLoop()\n observer?.disconnect()\n observer = null\n memo = null\n memoContext = null\n },\n }\n}\n","/**\n * What an `accept` rule is matched against.\n *\n * `File` satisfies it, and so does `DataTransferItem` — which matters while a\n * drag is still in the air, since the browser reports the type of what is\n * being dragged but withholds the name.\n */\nexport interface AcceptCandidate {\n /** The file name, when it is known. */\n name?: string\n /** The MIME type, or `''` when the browser has no type for it. */\n type: string\n}\n\n/**\n * Does a file satisfy an `accept` attribute?\n *\n * `accept` is written the way the HTML attribute is: a comma separated list of\n * extensions (`.wav`), MIME types (`audio/wav`) and type groups (`audio/*`).\n * Anything that matches one entry is accepted, and an empty or missing\n * `accept` takes everything.\n *\n * **The browser's own `accept` is only a hint to the file picker.** A person\n * can switch it to \"All Files\", drag a file in, or pick one the picker was\n * never asked about, so what arrives still has to be checked.\n *\n * A rule that cannot be decided is not treated as a rejection: with no `name`,\n * an extension rule says nothing either way, and `accept=\".wav\"` reports a\n * match rather than refusing a file it has not seen the name of. The name is\n * there by the time the file is dropped, which is when the answer counts.\n */\nexport function matchesAccept(\n candidate: AcceptCandidate,\n accept?: string,\n): boolean {\n const rules = (accept ?? '')\n .split(',')\n .map((rule) => rule.trim().toLowerCase())\n .filter(Boolean)\n if (rules.length === 0) return true\n\n const name = candidate.name?.toLowerCase()\n const type = candidate.type.toLowerCase()\n let undecided = false\n\n for (const rule of rules) {\n if (rule.startsWith('.')) {\n if (name === undefined) undecided = true\n else if (name.endsWith(rule)) return true\n } else if (rule.endsWith('/*')) {\n if (type.startsWith(rule.slice(0, -1))) return true\n } else if (type === rule) {\n return true\n }\n }\n\n return undecided\n}\n","import { matchesAccept } from './accept'\n\n/** What is in the air over the element. */\nexport interface DropZoneState {\n /** Files are being dragged over the element. */\n over: boolean\n /**\n * None of what is being dragged matches `accept`, as far as can be told\n * before the drop.\n *\n * The browser reports the type of what is being dragged but withholds the\n * name, so a rule written as an extension cannot be decided yet and is not\n * counted against the drag. It is decided on the drop, where the name is.\n */\n invalid: boolean\n}\n\nexport interface DropZoneOptions {\n /**\n * Which files to take, written the way the `accept` attribute of a file\n * input is: a comma separated list of extensions (`.wav`), MIME types\n * (`audio/wav`) and type groups (`audio/*`).\n */\n accept?: string\n /**\n * Take more than one file from a single drop. With it off, only the first\n * accepted file is reported, as a file input without `multiple` does.\n *\n * @default false\n */\n multiple?: boolean\n /**\n * Refuse the drop. The drag is still swallowed rather than let through: an\n * unhandled drop makes the browser leave the page and open the file.\n *\n * @default false\n */\n disabled?: boolean\n\n /** Called with the dropped files that match `accept`. */\n onDrop?: (files: File[], event: DragEvent) => void\n /**\n * Called with the dropped files that do not match `accept`, so that the\n * reason can be shown.\n */\n onReject?: (files: File[], event: DragEvent) => void\n /** Called whenever {@link DropZoneInstance.state} would change. */\n onStateChange?: (state: DropZoneState) => void\n}\n\nexport interface DropZoneInstance {\n /** What is in the air over the element, right now. */\n readonly state: DropZoneState\n /** Replace the given options, keeping the listeners in place. */\n update: (options: DropZoneOptions) => void\n destroy: () => void\n}\n\n/**\n * Take files dropped onto an element.\n *\n * ```ts\n * const zone = createDropZone(element, {\n * accept: 'audio/*',\n * onDrop: (files) => load(files[0]),\n * })\n * ```\n *\n * The element needs no attribute of its own: a drop target is made by\n * cancelling `dragover`, which this does.\n */\nexport function createDropZone(\n element: Element,\n options: DropZoneOptions = {},\n): DropZoneInstance {\n let opts = options\n let state: DropZoneState = { over: false, invalid: false }\n\n /**\n * `dragenter` and `dragleave` fire for descendants too, so moving between\n * two children of the zone leaves before it enters. Counting the pairs is\n * what keeps the state from flickering off in the middle of the element.\n */\n let depth = 0\n\n function setState(next: DropZoneState) {\n if (next.over === state.over && next.invalid === state.invalid) return\n state = next\n opts.onStateChange?.(state)\n }\n\n function carriesFiles(transfer: DataTransfer | null) {\n // `types` is the only thing that can be trusted during a drag; `files` is\n // empty until the drop.\n return !!transfer?.types.includes('Files')\n }\n\n /** Whether anything being dragged could still be accepted. */\n function anyAcceptable(transfer: DataTransfer | null) {\n const items = Array.from(transfer?.items ?? []).filter(\n (item) => item.kind === 'file',\n )\n // Some browsers hand over no items at all, only the `Files` type. Nothing\n // is known, so nothing is refused.\n if (items.length === 0) return true\n return items.some((item) => matchesAccept({ type: item.type }, opts.accept))\n }\n\n function onDragEnter(event: DragEvent) {\n if (!carriesFiles(event.dataTransfer)) return\n // Cancelled as well as `dragover`: a target that only cancels one of the\n // two is not a drop target in every browser.\n event.preventDefault()\n depth += 1\n setState({ over: true, invalid: !anyAcceptable(event.dataTransfer) })\n }\n\n function onDragOver(event: DragEvent) {\n if (!carriesFiles(event.dataTransfer)) return\n event.preventDefault()\n if (event.dataTransfer) {\n // Decides the cursor the pointer shows, and whether a drop is offered.\n event.dataTransfer.dropEffect =\n opts.disabled || state.invalid ? 'none' : 'copy'\n }\n // A drag that began outside the document can arrive without a `dragenter`\n // the listener saw, so the state is settled here too.\n if (!state.over) {\n depth = Math.max(depth, 1)\n setState({ over: true, invalid: !anyAcceptable(event.dataTransfer) })\n }\n }\n\n function onDragLeave(event: DragEvent) {\n if (!carriesFiles(event.dataTransfer)) return\n depth = Math.max(0, depth - 1)\n if (depth === 0) setState({ over: false, invalid: false })\n }\n\n function onDrop(event: DragEvent) {\n if (!carriesFiles(event.dataTransfer)) return\n // Always cancelled, even while disabled: an unhandled drop makes the\n // browser leave the page and open the file.\n event.preventDefault()\n depth = 0\n setState({ over: false, invalid: false })\n if (opts.disabled) return\n\n const dropped = Array.from(event.dataTransfer?.files ?? [])\n const accepted: File[] = []\n const rejected: File[] = []\n for (const file of dropped) {\n if (matchesAccept(file, opts.accept)) accepted.push(file)\n else rejected.push(file)\n }\n\n if (rejected.length > 0) opts.onReject?.(rejected, event)\n const taken = opts.multiple ? accepted : accepted.slice(0, 1)\n if (taken.length > 0) opts.onDrop?.(taken, event)\n }\n\n /**\n * A drag that ends anywhere else — dropped on another element, cancelled\n * with Esc, taken out of the window — sends no `dragleave` here, and the\n * element would stay marked as a drop target for good.\n */\n function onDragEndAnywhere() {\n depth = 0\n setState({ over: false, invalid: false })\n }\n\n const handlers = {\n dragenter: onDragEnter,\n dragover: onDragOver,\n dragleave: onDragLeave,\n drop: onDrop,\n } as const\n\n for (const [type, handler] of Object.entries(handlers)) {\n element.addEventListener(type, handler as EventListener)\n }\n\n const doc = element.ownerDocument\n doc?.addEventListener('dragend', onDragEndAnywhere)\n doc?.addEventListener('drop', onDragEndAnywhere)\n\n return {\n get state() {\n return state\n },\n update: (next) => {\n opts = { ...opts, ...next }\n },\n destroy: () => {\n for (const [type, handler] of Object.entries(handlers)) {\n element.removeEventListener(type, handler as EventListener)\n }\n doc?.removeEventListener('dragend', onDragEndAnywhere)\n doc?.removeEventListener('drop', onDragEndAnywhere)\n },\n }\n}\n","/**\n * Options for setting the amount of keyboard and mouse wheel changes.\n */\nexport type InputEventOption = readonly ['normalized' | 'raw', number]\n\n/**\n * A modifier key that can carry an amount of its own.\n *\n * `ctrl` and `meta` are kept apart rather than folded into one \"command\" key:\n * a plugin UI that mirrors a desktop host usually wants the same physical key\n * on every platform, not the platform's own convention.\n */\nexport type Modifier = 'shift' | 'alt' | 'ctrl' | 'meta'\n\n/** The modifier flags of a `WheelEvent` or a `KeyboardEvent`. */\nexport interface ModifierState {\n shiftKey: boolean\n altKey: boolean\n ctrlKey: boolean\n metaKey: boolean\n}\n\n/** One setting per modifier key, with `default` for none of them. */\ntype ModifierSetting = number | InputEventOption\n\nexport type ModifierMap<T extends ModifierSetting> = { default: T } & Partial<\n Record<Modifier, T>\n>\n\n/**\n * A single setting, or one per modifier key.\n *\n * @example\n * ['raw', 1]\n * { default: ['raw', 1], shift: ['raw', 0.1] }\n */\nexport type ModifierValue<T extends ModifierSetting> = T | ModifierMap<T>\n\n/**\n * Checked in this order, and the first one that is both held and configured\n * wins. Fixing an order is what keeps two modifiers held at once from\n * behaving differently between browsers.\n */\nconst MODIFIER_ORDER = ['meta', 'ctrl', 'alt', 'shift'] as const\n\nconst MODIFIER_FLAG = {\n meta: 'metaKey',\n ctrl: 'ctrlKey',\n alt: 'altKey',\n shift: 'shiftKey',\n} as const satisfies Record<Modifier, keyof ModifierState>\n\n/**\n * A map is the only form with a `default` key, which is what tells it apart\n * from a bare setting. Tuples are arrays, so they never match.\n */\nfunction isModifierMap<T extends ModifierSetting>(\n value: ModifierValue<T>,\n): value is ModifierMap<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n 'default' in value\n )\n}\n\n/**\n * Pick the setting that applies, given the modifier keys being held.\n *\n * @example\n * selectModifier({ default: 1, shift: 0.1 }, event)\n */\nexport function selectModifier<T extends ModifierSetting>(\n options: ModifierValue<T>,\n modifiers?: ModifierState,\n): { value: T; modifier: Modifier | null } {\n if (!isModifierMap(options)) {\n // TypeScript cannot subtract the map from `ModifierValue<T>` while `T` is\n // still a type parameter, so the other half has to be spelled out.\n return { value: options as T, modifier: null }\n }\n if (modifiers) {\n for (const modifier of MODIFIER_ORDER) {\n const value = options[modifier]\n // Compared against undefined rather than checked for truthiness: 0 is a\n // legitimate setting.\n if (value !== undefined && modifiers[MODIFIER_FLAG[modifier]]) {\n return { value, modifier }\n }\n }\n }\n return { value: options.default, modifier: null }\n}\n\n/**\n * Turn every entry of a setting into another kind of setting, keeping which\n * modifier each belongs to.\n *\n * A drag sensitivity is a number and a keyboard amount is a tuple, but the two\n * describe the same thing from the caller's side. This carries one over to the\n * other so that a component can hand a sensitivity to {@link applyDelta}\n * without unpicking the modifier map itself — which matters, since naming a\n * modifier is also what takes `step` out of the pipeline.\n *\n * @example\n * mapModifier({ default: 1, shift: 0.1 }, (f) => ['raw', step * f])\n * // { default: ['raw', 1], shift: ['raw', 0.1] }\n */\nexport function mapModifier<\n T extends ModifierSetting,\n U extends ModifierSetting,\n>(options: ModifierValue<T>, fn: (value: T) => U): ModifierValue<U> {\n if (!isModifierMap(options)) return fn(options as T)\n const mapped = { default: fn(options.default) } as ModifierMap<U>\n for (const modifier of MODIFIER_ORDER) {\n const value = options[modifier]\n if (value !== undefined) mapped[modifier] = fn(value)\n }\n return mapped\n}\n","import {\n clamp,\n linearScale,\n stepValue,\n toPrecision,\n type ValueRange,\n} from '@tremolo-ui/functions'\n\nimport {\n selectModifier,\n type InputEventOption,\n type ModifierState,\n type ModifierValue,\n} from './modifiers'\n\n/**\n * Move a value by an amount of input, as reported by a wheel or an arrow key.\n *\n * The pipeline matches {@link createDragValue}: scale, then step, then clamp.\n * Which key or which sign of `deltaY` counts as which direction is left to the\n * caller, since it differs per component.\n *\n * @param direction which way, and how many times, to apply the option. The\n * size of one step is `option[1]`, so this is normally `1` or `-1`.\n *\n * @param modifiers the event, for `options` that name a modifier key. See\n * {@link selectModifier}.\n *\n * @example\n * // ArrowDown on a slider whose keyboard option is ['raw', 1]\n * applyDelta(value, -1, keyboard, { min, max, step, scale })\n *\n * @example\n * // Shift+ArrowDown, where `keyboard` is { default: …, shift: ['raw', 0.1] }\n * applyDelta(value, -1, keyboard, range, event)\n */\nexport function applyDelta(\n value: number,\n direction: number,\n options: ModifierValue<InputEventOption>,\n { min, max, step, scale = linearScale }: ValueRange,\n modifiers?: ModifierState,\n): number {\n if (min >= max) throw new RangeError('requirements: min < max')\n if (step !== undefined && (!Number.isFinite(step) || step <= 0)) {\n throw new RangeError(\n 'applyDelta step: requirements: finite and greater than 0',\n )\n }\n\n const {\n value: [mode, amount],\n modifier,\n } = selectModifier(options, modifiers)\n\n const x = direction * amount\n const next =\n mode === 'normalized'\n ? scale.denormalize(scale.normalize(value, min, max) + x, min, max)\n : value + x\n\n // Naming a modifier is a deliberate request to move off the grid, so `step`\n // does not apply to it. Without this a finer amount would round straight\n // back to where it started: `stepValue(3 + 0.1, 1)` is 3.\n const quantum = modifier === null ? step : undefined\n const stepped = quantum !== undefined ? stepValue(next, quantum) : next\n\n // Rounded before the clamp, so that `min` and `max` still have the last\n // word and the value can land on them exactly. Without this the artefact\n // accumulates: with no `step` to round it back, twelve presses of a 0.1\n // modifier amount reach 5.699999999999998 rather than 5.7.\n return clamp(toPrecision(stepped), min, max)\n}\n","/** @private */\nexport const PERMISSION_DENIED = 'PERMISSION_DENIED'\n/** @private */\nexport const NOT_SUPPORTED = 'NOT_SUPPORTED'\n/** @private */\nexport const UNAVAILABLE = 'UNAVAILABLE'\n\n/** @private */\nexport type MIDIAccessError =\n | typeof PERMISSION_DENIED\n | typeof NOT_SUPPORTED\n | typeof UNAVAILABLE\n\nexport type MIDIAccessOptions = {\n /**\n * Ask for system exclusive messages as well.\n *\n * Browsers treat this as a separate, more sensitive permission, so leave it\n * off unless the app actually reads or sends sysex.\n *\n * @default false\n */\n sysex?: boolean\n}\n\nexport type MIDIAccessState = {\n readonly midiAccess: MIDIAccess | null\n readonly error: MIDIAccessError | null\n /**\n * The inputs currently connected, in the order MIDIAccess lists them.\n *\n * Kept up to date as devices are plugged in and unplugged, so a UI listing\n * the devices does not have to watch `statechange` itself.\n */\n readonly inputs: readonly MIDIInput[]\n}\n\nconst INITIAL_STATE: MIDIAccessState = {\n midiAccess: null,\n error: null,\n inputs: [],\n}\n\nexport interface MIDIAccessInstance {\n /** Request MIDI access. Safe to call more than once. */\n request: (options?: MIDIAccessOptions) => void\n getState: () => MIDIAccessState\n /** Snapshot for server side rendering. Always the initial state. */\n getServerState: () => MIDIAccessState\n /** @returns unsubscribe function */\n subscribe: (listener: () => void) => () => void\n destroy: () => void\n}\n\n/**\n * Which of our errors a rejected `requestMIDIAccess()` amounts to.\n *\n * The spec names the reasons, and they need telling apart: a user who said no\n * can say yes on a second ask, while a browser without the API never will.\n */\nfunction toError(reason: unknown): MIDIAccessError {\n const name =\n typeof reason === 'object' && reason !== null && 'name' in reason\n ? String((reason as { name: unknown }).name)\n : ''\n\n if (name === 'SecurityError' || name === 'NotAllowedError') {\n return PERMISSION_DENIED\n }\n if (name === 'NotSupportedError' || name === 'TypeError') {\n return NOT_SUPPORTED\n }\n // AbortError, InvalidStateError, and anything a browser makes up.\n return UNAVAILABLE\n}\n\n/**\n * Request MIDI access in the browser.\n *\n * The returned instance holds the state and notifies subscribers when it changes,\n * so it can be consumed from any framework.\n */\nexport function createMIDIAccess(): MIDIAccessInstance {\n let state = INITIAL_STATE\n let destroyed = false\n let access: MIDIAccess | null = null\n let requestGeneration = 0\n const listeners = new Set<() => void>()\n\n function setState(next: MIDIAccessState) {\n state = next\n for (const listener of listeners) listener()\n }\n\n /**\n * A new array every time, so that `useSyncExternalStore` and its equivalents\n * see the change. The identity of the state object is what they compare.\n */\n function readInputs() {\n return access ? [...access.inputs.values()] : []\n }\n\n function handleStateChange(event?: Event) {\n if (destroyed || !access) return\n if (event && (event as MIDIConnectionEvent).port?.type === 'output') return\n setState({ ...state, inputs: readInputs() })\n }\n\n function request(options: MIDIAccessOptions = {}) {\n if (destroyed) return\n const generation = ++requestGeneration\n if (typeof navigator === 'undefined' || !navigator.requestMIDIAccess) {\n setState({ ...state, error: NOT_SUPPORTED })\n return\n }\n navigator.requestMIDIAccess({ sysex: options.sysex ?? false }).then(\n (granted) => {\n if (destroyed || generation !== requestGeneration) return\n access?.removeEventListener('statechange', handleStateChange)\n access = granted\n granted.addEventListener('statechange', handleStateChange)\n setState({ midiAccess: granted, error: null, inputs: readInputs() })\n },\n (reason: unknown) => {\n if (destroyed || generation !== requestGeneration) return\n setState({ ...state, error: toError(reason) })\n },\n )\n }\n\n return {\n request,\n getState: () => state,\n getServerState: () => INITIAL_STATE,\n subscribe: (listener) => {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n destroy: () => {\n destroyed = true\n requestGeneration += 1\n access?.removeEventListener('statechange', handleStateChange)\n access = null\n listeners.clear()\n },\n }\n}\n","export interface MIDIMessageInstance {\n /** Replace the handler, keeping the listeners in place. */\n update: (onMIDIMessage: (event: MIDIMessageEvent) => void) => void\n destroy: () => void\n}\n\n/**\n * Listen to raw `midimessage` events on every input of a MIDIAccess.\n *\n * The set of inputs is followed rather than sampled: MIDIAccess fires\n * `statechange` when a device is plugged in or unplugged, and the listeners\n * move with it. A keyboard connected after access was granted works without\n * the caller having to rebuild anything.\n *\n * Use this when you need more detail than {@link createMIDIInput} provides.\n */\nexport function createMIDIMessage(\n midiAccess: MIDIAccess | null,\n onMIDIMessage: (event: MIDIMessageEvent) => void,\n): MIDIMessageInstance {\n let handler = onMIDIMessage\n\n /** Inputs this instance currently listens on. */\n const attached = new Set<MIDIInput>()\n\n const listener = (event: Event) => handler(event as MIDIMessageEvent)\n\n function sync() {\n if (!midiAccess) return\n const connected = new Set(midiAccess.inputs.values())\n\n for (const input of connected) {\n if (attached.has(input)) continue\n input.addEventListener('midimessage', listener)\n attached.add(input)\n }\n for (const input of [...attached]) {\n if (connected.has(input)) continue\n input.removeEventListener('midimessage', listener)\n attached.delete(input)\n }\n }\n\n sync()\n midiAccess?.addEventListener('statechange', sync)\n\n return {\n update: (next) => {\n handler = next\n },\n destroy: () => {\n midiAccess?.removeEventListener('statechange', sync)\n for (const input of attached) {\n input.removeEventListener('midimessage', listener)\n }\n attached.clear()\n },\n }\n}\n","import { createMIDIMessage } from './message'\n\n/** Status byte of each channel message, with the channel nibble cleared. */\nconst STATUS = {\n NOTE_OFF: 0x80,\n NOTE_ON: 0x90,\n AFTERTOUCH: 0xa0,\n CONTROL_CHANGE: 0xb0,\n PROGRAM_CHANGE: 0xc0,\n CHANNEL_PRESSURE: 0xd0,\n PITCH_BEND: 0xe0,\n}\n\n/**\n * Centre of the 14-bit pitch bend range: no bend.\n *\n * The range is not symmetric — 0 is 8192 below centre and 16383 is 8191 above\n * — so a wheel at rest reports exactly this rather than half of the maximum.\n */\nexport const PITCH_BEND_CENTER = 8192\n\n/**\n * Every handler is given the channel last, as 0-15. MIDI channels are written\n * 1-16 on hardware, so add one before showing it to anyone.\n */\nexport type MIDIInputHandlers = {\n onNoteOnEvent?: (note: number, velocity: number, channel: number) => void\n onNoteOffEvent?: (note: number, channel: number) => void\n /**\n * The 14-bit bend, 0-16383, centred at {@link PITCH_BEND_CENTER}.\n *\n * The two data bytes are little-endian — the first carries the low 7 bits —\n * which is the other way round from every other message.\n */\n onPitchBendEvent?: (value: number, channel: number) => void\n /** `controller` is the CC number, `value` is 0-127. */\n onControlChangeEvent?: (\n controller: number,\n value: number,\n channel: number,\n ) => void\n onProgramChangeEvent?: (program: number, channel: number) => void\n /** Pressure for one held note (polyphonic aftertouch). */\n onAftertouchEvent?: (note: number, pressure: number, channel: number) => void\n /** Pressure for the whole channel, sent by keyboards with one sensor. */\n onChannelPressureEvent?: (pressure: number, channel: number) => void\n}\n\nexport interface MIDIInputInstance {\n /** Replace the handlers, keeping the listeners in place. */\n update: (handlers: MIDIInputHandlers) => void\n destroy: () => void\n}\n\n/**\n * Handle the channel voice messages of every connected input. To be used with\n * {@link createMIDIAccess}. Internally uses {@link createMIDIMessage}, so\n * devices plugged in later are picked up.\n *\n * System messages (clock, sysex, and the rest of `0xf0`-`0xff`) are not\n * decoded here; reach for {@link createMIDIMessage} for those.\n */\nexport function createMIDIInput(\n midiAccess: MIDIAccess | null,\n handlers: MIDIInputHandlers,\n): MIDIInputInstance {\n let current = handlers\n\n const message = createMIDIMessage(midiAccess, (event) => {\n if (!event.data || event.data.length === 0) return\n\n const status = event.data[0]\n // 0xf0 and above are system messages, which carry no channel.\n if (status >= 0xf0) return\n\n const kind = status & 0xf0\n const channel = status & 0x0f\n const first = event.data[1] ?? 0\n const second = event.data[2] ?? 0\n\n switch (kind) {\n case STATUS.NOTE_OFF:\n current.onNoteOffEvent?.(first, channel)\n break\n case STATUS.NOTE_ON:\n // A note on with zero velocity is how most devices say note off.\n if (second === 0) current.onNoteOffEvent?.(first, channel)\n else current.onNoteOnEvent?.(first, second, channel)\n break\n case STATUS.AFTERTOUCH:\n current.onAftertouchEvent?.(first, second, channel)\n break\n case STATUS.CONTROL_CHANGE:\n current.onControlChangeEvent?.(first, second, channel)\n break\n case STATUS.PROGRAM_CHANGE:\n current.onProgramChangeEvent?.(first, channel)\n break\n case STATUS.CHANNEL_PRESSURE:\n current.onChannelPressureEvent?.(first, channel)\n break\n case STATUS.PITCH_BEND:\n current.onPitchBendEvent?.((second << 7) | first, channel)\n break\n }\n })\n\n return {\n update: (next) => {\n current = next\n },\n destroy: message.destroy,\n }\n}\n","import {\n isBlackKey,\n isWhiteKey,\n noteKey,\n noteKeys,\n type NoteKey,\n} from '@tremolo-ui/functions'\n\nexport type NoteRange = {\n first: number\n last: number\n}\n\n/**\n * `[noteRange.first, noteRange.first + 1, ..., noteRange.last]`\n */\nexport function getNoteRangeArray(noteRange: NoteRange): number[] {\n return Array.from(\n { length: noteRange.last - noteRange.first + 1 },\n (_, i) => i + noteRange.first,\n )\n}\n\n/**\n * The geometry of a drawn keyboard.\n *\n * One description is shared by the drawing and the hit testing, so a key cannot\n * be drawn somewhere other than where it responds.\n */\nexport interface PianoLayout {\n noteRange: NoteRange\n\n /** Width of a white key, excluding {@link PianoLayout.keyGap}. */\n whiteKeyWidth: number\n\n /**\n * Space between two white keys. Part of the slot a white key occupies, so it\n * still belongs to one of the keys for the purpose of hit testing.\n *\n * @default 1\n */\n keyGap?: number\n\n /**\n * Width of a black key, as a fraction of {@link PianoLayout.whiteKeyWidth}.\n *\n * @default 0.65\n */\n blackKeyWidthRatio?: number\n\n /**\n * Height of a black key, as a fraction of the height of the keyboard.\n *\n * @default 0.6\n */\n blackKeyHeightRatio?: number\n}\n\nconst DEFAULT_KEY_GAP = 1\nconst DEFAULT_BLACK_KEY_WIDTH_RATIO = 0.65\nconst DEFAULT_BLACK_KEY_HEIGHT_RATIO = 0.6\n\n/**\n * How many white keys sit at or before each pitch class, counting from C.\n *\n * A black key shares the number of the white key to its left plus one, which\n * puts it on the boundary between the two; {@link notePosition} then shifts it\n * back by half its width to centre it there.\n */\nconst whiteKeysBefore: Record<NoteKey, number> = {\n C: 0,\n 'C#': 1,\n D: 1,\n 'D#': 2,\n E: 2,\n F: 3,\n 'F#': 4,\n G: 4,\n 'G#': 5,\n A: 5,\n 'A#': 6,\n B: 6,\n}\n\n/** Width of a black key in pixels. */\nexport function blackKeyWidth(layout: PianoLayout): number {\n return (\n layout.whiteKeyWidth *\n (layout.blackKeyWidthRatio ?? DEFAULT_BLACK_KEY_WIDTH_RATIO)\n )\n}\n\nfunction rawNotePosition(note: number, layout: PianoLayout): number {\n const slot = layout.whiteKeyWidth + (layout.keyGap ?? DEFAULT_KEY_GAP)\n const target = noteKey(note)\n const first = noteKey(layout.noteRange.first)\n\n const octave = Math.floor((note - layout.noteRange.first) / 12)\n const octaveOffset =\n noteKeys.indexOf(first) > noteKeys.indexOf(target) ? 1 : 0\n const whiteKeysIn =\n whiteKeysBefore[target] -\n whiteKeysBefore[first] +\n (octave + octaveOffset) * 7\n\n return isBlackKey(note)\n ? whiteKeysIn * slot - blackKeyWidth(layout) / 2\n : whiteKeysIn * slot\n}\n\nfunction pianoBounds(layout: PianoLayout) {\n const notes = getNoteRangeArray(layout.noteRange)\n if (notes.length === 0) return { left: 0, right: 0 }\n\n let left = Infinity\n let right = -Infinity\n const whiteWidth = layout.whiteKeyWidth + (layout.keyGap ?? DEFAULT_KEY_GAP)\n for (const note of notes) {\n const noteLeft = rawNotePosition(note, layout)\n const width = isBlackKey(note) ? blackKeyWidth(layout) : whiteWidth\n left = Math.min(left, noteLeft)\n right = Math.max(right, noteLeft + width)\n }\n return { left, right }\n}\n\n/** Width of the whole keyboard in pixels. */\nexport function pianoWidth(layout: PianoLayout): number {\n const { left, right } = pianoBounds(layout)\n return right - left\n}\n\n/**\n * Offset of the left edge of a key from the left edge of the keyboard, in\n * pixels.\n *\n * Notes outside `noteRange` are placed too, so the value is negative below\n * `noteRange.first`.\n */\nexport function notePosition(note: number, layout: PianoLayout): number {\n return rawNotePosition(note, layout) - pianoBounds(layout).left\n}\n\n/**\n * The note drawn at a point, or null where there is none.\n *\n * Black keys are tested first, so they win where they overlap a white one. A\n * white key covers its gap as well as its width, so the whole width of the\n * keyboard belongs to some key and a click cannot fall between two.\n *\n * @param x offset from the left edge of the keyboard, in pixels\n * @param y offset from its top edge, in pixels\n * @param height height of the keyboard, in pixels\n */\nexport function noteAt(\n x: number,\n y: number,\n height: number,\n layout: PianoLayout,\n): number | null {\n if (x < 0 || x >= pianoWidth(layout) || y < 0 || y >= height) return null\n\n const notes = getNoteRangeArray(layout.noteRange)\n const blackHeight =\n height * (layout.blackKeyHeightRatio ?? DEFAULT_BLACK_KEY_HEIGHT_RATIO)\n\n if (y < blackHeight) {\n for (const note of notes) {\n if (isWhiteKey(note)) continue\n const left = notePosition(note, layout)\n if (left <= x && x < left + blackKeyWidth(layout)) return note\n }\n }\n\n const slot = layout.whiteKeyWidth + (layout.keyGap ?? DEFAULT_KEY_GAP)\n for (const note of notes) {\n if (isBlackKey(note)) continue\n const left = notePosition(note, layout)\n if (left <= x && x < left + slot) return note\n }\n\n return null\n}\n","export type DragState = {\n /** Total movement from the drag start, in screen coordinates. */\n x: number\n y: number\n /** Movement since the previous event, in screen coordinates. */\n deltaX: number\n deltaY: number\n /** Pointer position in viewport coordinates. */\n clientX: number\n clientY: number\n /**\n * Which pointer this is. Always the same value for one drag; with\n * {@link DragOptions.multiPointer} it tells concurrent drags apart.\n */\n pointerId: number\n event: PointerEvent\n}\n\nexport type DragOptions = {\n /**\n * Minimum movement in pixels before `onDrag` fires. Movement below it is\n * carried over to the next event rather than discarded, so a slow drag still\n * reports once it adds up.\n *\n * Prevents `onDrag` from firing on, for example, a double click.\n *\n * @default 0\n */\n threshold?: number\n\n /**\n * CSS cursor to show while dragging. Applied to the element itself: pointer\n * capture keeps it in effect even once the pointer leaves the element, so\n * there is no need to touch the document.\n *\n * With {@link DragOptions.multiPointer} it is set for the first pointer and\n * restored once the last one is up.\n */\n cursor?: string\n\n /**\n * Decide whether a pointerdown starts a drag at all.\n *\n * Checked before anything else — **before the pointer is captured** — so\n * declining here leaves the whole gesture to whatever else is listening.\n * Deciding later would be too late: the capture has already been taken from\n * the element that was going to handle it.\n *\n * The use for it is a drag on a container that also holds draggable things\n * of its own, such as a rubber-band selection that must not begin on top of\n * one of the objects it would select.\n */\n shouldStart?: (event: PointerEvent) => boolean\n\n /**\n * Hide the pointer and read its movement directly, instead of following it\n * around the screen.\n *\n * A relative drag — a knob, a stepper — does not care where the pointer is,\n * only how far it moved, and letting it wander has two costs: the cursor\n * ends up far from what it is holding, and **the drag stops at the edge of\n * the screen**, where the operating system pins the pointer and the\n * coordinates stop changing. A fine drag reaches that edge quickly.\n *\n * **Not for a drag whose value is the position pointed at** — anything on\n * `elementMapping`. `clientX` / `clientY` freeze while the pointer is\n * locked, so there is no position left to read.\n *\n * The request needs a user gesture, which a pointerdown is, but it can still\n * be refused; the drag then carries on as an ordinary one. Read on\n * pointerdown, so `update()` reaches the next drag rather than the current.\n *\n * @default false\n */\n pointerLock?: boolean\n\n /**\n * Track every pointer that goes down on the element, rather than only the\n * first. Each one gets its own `onDragStart` / `onDrag` / `onDragEnd` and\n * carries its own totals; {@link DragState.pointerId} says which is which.\n *\n * Fixed for the lifetime of the instance: switching part way through a drag\n * has no meaning, so `update()` ignores it.\n *\n * @default false\n */\n multiPointer?: boolean\n\n onDragStart?: (state: DragState) => void\n onDrag?: (state: DragState) => void\n /**\n * Called exactly once for every drag that starts, whether tracking ends by\n * pointer release, cancellation, capture or lock loss, or destruction.\n */\n onDragEnd?: (state: DragState) => void\n}\n\nexport interface DragInstance {\n /**\n * Replace the given options. Lets a wrapper feed fresh handlers in without\n * tearing down the listeners, which would abort a drag in progress.\n *\n * `multiPointer` is fixed for the lifetime of the instance and is ignored\n * here.\n */\n update: (options: DragOptions) => void\n /** End any active drags before removing the instance. */\n destroy: () => void\n}\n\n/**\n * Applied to the element for the lifetime of the instance.\n *\n * `touch-action` stops touch dragging from scrolling the page. That alone makes\n * the browser treat a long press as the start of a text selection, so selection\n * and the iOS callout are suppressed here too.\n */\nconst MANAGED_STYLES = [\n ['touch-action', 'none'],\n ['user-select', 'none'],\n ['-webkit-user-select', 'none'],\n ['-webkit-touch-callout', 'none'],\n] as const\n\ntype ManagedStyleState = {\n count: number\n previous: Map<string, { value: string; priority: string }>\n}\n\nconst managedStyleStates = new WeakMap<Element, ManagedStyleState>()\n\ntype CaptureTarget = {\n requestPointerLock?: () => unknown\n setPointerCapture?: (pointerId: number) => void\n releasePointerCapture?: (pointerId: number) => void\n hasPointerCapture?: (pointerId: number) => boolean\n}\n\n/** What one pointer needs to report its own movement. */\ntype PointerState = {\n /** Where the listeners for this pointer live. */\n moveTarget: EventTarget\n startX: number\n startY: number\n lastX: number\n lastY: number\n /**\n * Where the travel stood when the pointer lock took effect, and how far it\n * has moved since. Screen coordinates stop changing under the lock, so from\n * that point the movement of each event is added up instead.\n *\n * The base is taken when the lock engages rather than on pointerdown: the\n * request is asynchronous, and whatever movement happened while it was in\n * flight was measured the ordinary way.\n */\n lockBaseX?: number\n lockBaseY?: number\n lockMoveX: number\n lockMoveY: number\n /** The most recent event, to end the drag with when the lock is lost. */\n lastEvent: PointerEvent\n}\n\n/**\n * Track a pointer drag on an element.\n *\n * Uses Pointer Events only, and pointer capture so that the drag keeps working\n * once the pointer leaves the element. For the lifetime of the instance the\n * element gets `touch-action: none` so that touch dragging does not scroll the\n * page, plus `user-select: none` so that a long press does not start a text\n * selection instead.\n *\n * One pointer at a time by default; see {@link DragOptions.multiPointer}.\n */\nexport function createDrag(\n element: Element,\n options: DragOptions = {},\n): DragInstance {\n let opts = options\n const multiPointer = options.multiPointer ?? false\n const capture = element as CaptureTarget\n const style = (element as Partial<HTMLElement>).style\n\n let managedStyles = managedStyleStates.get(element)\n if (style) {\n if (managedStyles) {\n managedStyles.count += 1\n } else {\n const previous = new Map<string, { value: string; priority: string }>()\n for (const [property, value] of MANAGED_STYLES) {\n previous.set(property, {\n value: style.getPropertyValue(property),\n priority: style.getPropertyPriority(property),\n })\n style.setProperty(property, value)\n }\n managedStyles = { count: 1, previous }\n managedStyleStates.set(element, managedStyles)\n }\n }\n\n const pointers = new Map<number, PointerState>()\n /**\n * How many pointers each target carries. Adding the same listener twice is a\n * no-op and removing it once removes it for good, so a target is subscribed\n * to while at least one pointer is on it and no longer.\n */\n const targets = new Map<EventTarget, number>()\n let previousCursor: string | undefined\n /** The pointer that asked for the lock, while it is still down. */\n let lockedPointerId: number | null = null\n let lockRequest = 0\n let destroyed = false\n\n function state(\n event: PointerEvent,\n pointer: PointerState,\n deltaX: number,\n deltaY: number,\n ): DragState {\n return {\n x:\n pointer.lockBaseX !== undefined\n ? pointer.lockBaseX + pointer.lockMoveX\n : event.screenX - pointer.startX,\n y:\n pointer.lockBaseY !== undefined\n ? pointer.lockBaseY + pointer.lockMoveY\n : event.screenY - pointer.startY,\n deltaX,\n deltaY,\n clientX: event.clientX,\n clientY: event.clientY,\n pointerId: event.pointerId,\n event,\n }\n }\n\n /**\n * `user-select: none` only makes the element's own text unselectable; the\n * browser still starts a selection on long press and grabs whatever text it\n * finds nearby. Cancelling the selection outright is what actually stops it.\n */\n function preventSelectStart(event: Event) {\n event.preventDefault()\n }\n\n function retainTarget(target: EventTarget) {\n const count = targets.get(target) ?? 0\n if (count === 0) {\n target.addEventListener('pointermove', handlePointerMove)\n target.addEventListener('pointerup', handlePointerUp)\n target.addEventListener('pointercancel', handlePointerUp)\n if (target === element) {\n target.addEventListener('lostpointercapture', handleLostPointerCapture)\n }\n }\n targets.set(target, count + 1)\n }\n\n function releaseTarget(target: EventTarget) {\n const count = targets.get(target) ?? 0\n if (count > 1) {\n targets.set(target, count - 1)\n return\n }\n target.removeEventListener('pointermove', handlePointerMove)\n target.removeEventListener('pointerup', handlePointerUp)\n target.removeEventListener('pointercancel', handlePointerUp)\n if (target === element) {\n target.removeEventListener('lostpointercapture', handleLostPointerCapture)\n }\n targets.delete(target)\n }\n\n function handlePointerDown(event: Event) {\n const pointerEvent = event as PointerEvent\n if (pointerEvent.button !== 0) return\n const pointerId = pointerEvent.pointerId\n // Without multiPointer only one pointer drives the drag; ignore the rest.\n if (pointers.has(pointerId)) return\n if (!multiPointer && pointers.size > 0) return\n\n if (opts.shouldStart && !opts.shouldStart(pointerEvent)) return\n\n const isFirst = pointers.size === 0\n if (isFirst && opts.cursor && style) {\n previousCursor = style.cursor\n style.cursor = opts.cursor\n }\n\n capture.setPointerCapture?.(pointerId)\n // With pointer capture the element receives the rest of the gesture.\n // Without it (older engines, jsdom) fall back to the window.\n const moveTarget =\n capture.hasPointerCapture?.(pointerId) === true\n ? element\n : (globalThis.window ?? element)\n\n const pointer: PointerState = {\n moveTarget,\n startX: pointerEvent.screenX,\n startY: pointerEvent.screenY,\n lastX: pointerEvent.screenX,\n lastY: pointerEvent.screenY,\n lockMoveX: 0,\n lockMoveY: 0,\n lastEvent: pointerEvent,\n }\n pointers.set(pointerId, pointer)\n\n retainTarget(moveTarget)\n if (isFirst) {\n globalThis.document?.addEventListener('selectstart', preventSelectStart)\n }\n\n // One pointer can be locked, so the first one takes it.\n if (isFirst && opts.pointerLock) {\n const requestId = ++lockRequest\n lockedPointerId = pointerId\n globalThis.document?.addEventListener(\n 'pointerlockchange',\n handleLockChange,\n )\n try {\n // Newer engines return a promise that rejects; older ones fire\n // `pointerlockerror` instead. Either way a refusal only means the drag\n // stays an ordinary one, so nothing here has to act on it.\n const request = capture.requestPointerLock?.() as\n | Promise<void>\n | undefined\n request?.then?.(\n () => {\n if (\n requestId === lockRequest &&\n (!pointers.has(pointerId) || destroyed) &&\n globalThis.document?.pointerLockElement === element\n ) {\n globalThis.document.exitPointerLock?.()\n }\n },\n () => {},\n )\n } catch {\n // requestPointerLock threw synchronously; same story.\n }\n }\n\n opts.onDragStart?.(state(pointerEvent, pointer, 0, 0))\n }\n\n function handlePointerMove(event: Event) {\n const pointerEvent = event as PointerEvent\n const pointer = pointers.get(pointerEvent.pointerId)\n if (!pointer) return\n\n // Under the lock the screen position no longer moves, so the movement the\n // event reports is the only thing left to read.\n const locked = pointer.lockBaseX !== undefined\n const deltaX = locked\n ? (pointerEvent.movementX ?? 0)\n : pointerEvent.screenX - pointer.lastX\n const deltaY = locked\n ? (pointerEvent.movementY ?? 0)\n : pointerEvent.screenY - pointer.lastY\n\n // Movement below the threshold accumulates until it crosses it. Dropping it\n // instead would swallow a slow drag entirely: pointer coordinates are\n // fractional, so each event can move less than a pixel and never report.\n const threshold = opts.threshold ?? 0\n if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return\n\n pointer.lastX = pointerEvent.screenX\n pointer.lastY = pointerEvent.screenY\n pointer.lastEvent = pointerEvent\n if (locked) {\n pointer.lockMoveX += deltaX\n pointer.lockMoveY += deltaY\n }\n\n opts.onDrag?.(state(pointerEvent, pointer, deltaX, deltaY))\n }\n\n function handleLockChange() {\n if (lockedPointerId === null) return\n const pointer = pointers.get(lockedPointerId)\n if (!pointer) return\n\n if (globalThis.document?.pointerLockElement === element) {\n // Engaged. Whatever moved while the request was in flight was measured\n // the ordinary way, so the travel so far becomes the base and the\n // per-event movement is added to it from here.\n pointer.lockBaseX = pointer.lastX - pointer.startX\n pointer.lockBaseY = pointer.lastY - pointer.startY\n pointer.lockMoveX = 0\n pointer.lockMoveY = 0\n return\n }\n\n // Lost without the pointer coming up: Esc, a tab switch, leaving\n // fullscreen. No pointerup is coming, so the drag ends here rather than\n // hanging on with a pointer nobody can see.\n if (pointer.lockBaseX === undefined) return\n finishDrag(lockedPointerId)\n }\n\n function handlePointerUp(event: Event) {\n const pointerEvent = event as PointerEvent\n finishDrag(pointerEvent.pointerId, pointerEvent)\n }\n\n function handleLostPointerCapture(event: Event) {\n const pointerEvent = event as PointerEvent\n finishDrag(pointerEvent.pointerId)\n }\n\n function finishDrag(pointerId: number, event?: PointerEvent) {\n const pointer = pointers.get(pointerId)\n if (!pointer) return\n\n const finalState = event\n ? state(\n event,\n pointer,\n event.screenX - pointer.lastX,\n event.screenY - pointer.lastY,\n )\n : state(pointer.lastEvent, pointer, 0, 0)\n\n stopTracking(pointerId)\n opts.onDragEnd?.(finalState)\n }\n\n function stopTracking(pointerId: number) {\n const pointer = pointers.get(pointerId)\n if (!pointer) return\n\n pointers.delete(pointerId)\n releaseTarget(pointer.moveTarget)\n try {\n if (capture.hasPointerCapture?.(pointerId) === true) {\n capture.releasePointerCapture?.(pointerId)\n }\n } catch {\n // The capture may disappear between checking and releasing it. Tracking\n // is already cleared, so the drag still ends normally.\n }\n\n if (lockedPointerId === pointerId) {\n lockedPointerId = null\n const document = globalThis.document\n document?.removeEventListener('pointerlockchange', handleLockChange)\n // Already gone when the lock is what ended the drag.\n if (document?.pointerLockElement === element) document.exitPointerLock?.()\n }\n\n if (pointers.size > 0) return\n\n globalThis.document?.removeEventListener('selectstart', preventSelectStart)\n if (previousCursor !== undefined && style) {\n style.cursor = previousCursor\n previousCursor = undefined\n }\n }\n\n element.addEventListener('pointerdown', handlePointerDown)\n\n return {\n update: (next) => {\n opts = { ...opts, ...next, multiPointer }\n },\n destroy: () => {\n if (destroyed) return\n destroyed = true\n element.removeEventListener('pointerdown', handlePointerDown)\n for (const pointerId of [...pointers.keys()]) finishDrag(pointerId)\n if (style && managedStyles && --managedStyles.count === 0) {\n for (const [property] of MANAGED_STYLES) {\n const previous = managedStyles.previous.get(property)\n if (previous?.value) {\n style.setProperty(property, previous.value, previous.priority)\n } else {\n style.removeProperty(property)\n }\n }\n managedStyleStates.delete(element)\n }\n },\n }\n}\n","import { createDrag } from '../pointer/drag'\n\nimport { noteAt, type PianoLayout } from './layout'\n\n/**\n * What asked for a note to sound.\n *\n * A note stops only once everything that asked for it has let go, so two\n * fingers on one key, or a key held by both the mouse and a MIDI keyboard,\n * behave the way they look.\n *\n * Pointers use `pointer:<pointerId>`; everything else names its own source.\n */\nexport type NoteSource = string\n\nexport interface PianoInputOptions {\n /** Geometry of the drawn keyboard, used to find the note under a pointer. */\n layout: PianoLayout\n\n /**\n * Let a pointer slide from one key to the next while it is down. With it off,\n * the key that was pressed keeps sounding until the pointer is released.\n *\n * @default true\n */\n glissando?: boolean\n\n /**\n * Highest note that can sound. Above it a note is refused, whichever source\n * asks for it.\n *\n * @default 127\n */\n midiMax?: number\n\n /** Called when a note starts sounding, not for each source that asks. */\n onPlayNote?: (note: number, velocity?: number) => void\n /** Called once the last source holding a note has let go. */\n onStopNote?: (note: number) => void\n /** Called whenever {@link PianoInputInstance.activeNotes} would change. */\n onActiveNotesChange?: (notes: number[]) => void\n}\n\nexport interface PianoInputInstance {\n /**\n * Replace the given options. Lets a wrapper feed a fresh layout in without\n * tearing down the listeners, which would abort a drag in progress.\n */\n update: (options: Partial<PianoInputOptions>) => void\n\n /**\n * Start a note from something other than a pointer: a keyboard shortcut, a\n * MIDI message, an imperative call.\n */\n noteOn: (\n note: number,\n options?: { source?: NoteSource; velocity?: number },\n ) => void\n /** Release a note held by `source`. */\n noteOff: (note: number, options?: { source?: NoteSource }) => void\n\n /** The notes currently sounding, ascending. */\n activeNotes: () => number[]\n\n destroy: () => void\n}\n\nconst DEFAULT_SOURCE: NoteSource = 'api'\n\n/**\n * Drive a piano keyboard with pointers, and own what is sounding.\n *\n * Every way of playing a note goes through the one instance — pointers here,\n * keyboard shortcuts and MIDI through {@link PianoInputInstance.noteOn} — so\n * there is a single answer to what is currently held, and drawing the keys is\n * left entirely to the wrapper.\n *\n * Tracks every pointer at once, so a chord can be played with several fingers.\n */\nexport function createPianoInput(\n element: Element,\n options: PianoInputOptions,\n): PianoInputInstance {\n let opts = options\n\n /** Which sources are holding each sounding note. */\n const held = new Map<number, Set<NoteSource>>()\n /** The note each pointer is currently on. */\n const pointerNotes = new Map<number, number>()\n\n function activeNotes(): number[] {\n return [...held.keys()].sort((a, b) => a - b)\n }\n\n function noteOn(\n note: number,\n {\n source = DEFAULT_SOURCE,\n velocity,\n }: { source?: NoteSource; velocity?: number } = {},\n ) {\n if (!Number.isInteger(note) || note < 0 || note > 127) {\n throw new RangeError('note: requirements: an integer from 0 to 127')\n }\n if (note > (opts.midiMax ?? 127)) return\n\n const sources = held.get(note)\n if (sources) {\n // Already sounding: remember the extra holder and leave it alone.\n sources.add(source)\n return\n }\n\n held.set(note, new Set([source]))\n opts.onPlayNote?.(note, velocity)\n opts.onActiveNotesChange?.(activeNotes())\n }\n\n function noteOff(\n note: number,\n { source = DEFAULT_SOURCE }: { source?: NoteSource } = {},\n ) {\n if (!Number.isInteger(note) || note < 0 || note > 127) {\n throw new RangeError('note: requirements: an integer from 0 to 127')\n }\n const sources = held.get(note)\n if (!sources) return\n\n sources.delete(source)\n if (sources.size > 0) return\n\n held.delete(note)\n opts.onStopNote?.(note)\n opts.onActiveNotesChange?.(activeNotes())\n }\n\n /** The note under a pointer, or null where the pointer is off the keys. */\n function noteUnder(clientX: number, clientY: number): number | null {\n const { left, top, height } = element.getBoundingClientRect()\n return noteAt(clientX - left, clientY - top, height, opts.layout)\n }\n\n /** Move a pointer onto a note, releasing whatever it held before. */\n function movePointer(pointerId: number, note: number | null) {\n const source = `pointer:${pointerId}`\n const previous = pointerNotes.get(pointerId)\n if (previous === note) return\n\n if (previous !== undefined) noteOff(previous, { source })\n\n if (note === null || note > (opts.midiMax ?? 127)) {\n pointerNotes.delete(pointerId)\n } else {\n pointerNotes.set(pointerId, note)\n noteOn(note, { source })\n }\n }\n\n const drag = createDrag(element, {\n multiPointer: true,\n onDragStart: (state) =>\n movePointer(state.pointerId, noteUnder(state.clientX, state.clientY)),\n onDrag: (state) => {\n // Without glissando the pressed key holds until the pointer is released,\n // so where the pointer wanders to does not matter.\n if (opts.glissando === false) return\n movePointer(state.pointerId, noteUnder(state.clientX, state.clientY))\n },\n onDragEnd: (state) => movePointer(state.pointerId, null),\n })\n\n return {\n update: (next) => {\n opts = { ...opts, ...next }\n\n const midiMax = opts.midiMax ?? 127\n const stoppedNotes = activeNotes().filter((note) => note > midiMax)\n if (stoppedNotes.length === 0) return\n\n for (const note of stoppedNotes) {\n held.delete(note)\n opts.onStopNote?.(note)\n }\n for (const [pointerId, note] of pointerNotes) {\n if (note > midiMax) pointerNotes.delete(pointerId)\n }\n opts.onActiveNotesChange?.(activeNotes())\n },\n noteOn,\n noteOff,\n activeNotes,\n destroy: () => {\n drag.destroy()\n // Anything still held is released, so a caller that mirrors these\n // callbacks into a synth is not left with a stuck note.\n const notes = activeNotes()\n for (const note of notes) {\n held.delete(note)\n opts.onStopNote?.(note)\n }\n pointerNotes.clear()\n if (notes.length > 0) opts.onActiveNotesChange?.([])\n },\n }\n}\n","/**\n * A pair of per-axis values. The tuple elements are labelled, so editors show\n * `[x: number, y: number]` rather than a bare pair.\n */\nexport type XY<T> = [x: T, y: T]\n\n/**\n * A setting that may be given once for both axes, or per axis.\n *\n * A single value is told from a pair with `Array.isArray`, so the single form\n * is only offered while `T` cannot itself be an array. Where it can, the pair\n * is the only way to write it, since a lone array would be read as a pair.\n */\nexport type XYInput<T> = [T] extends [readonly unknown[]]\n ? readonly [x: T, y: T]\n : T | readonly [x: T, y: T]\n\n// `Array.isArray` narrows a mutable tuple on its own, but not a readonly one,\n// hence the explicit predicate.\nfunction isPair<T>(\n value: T | readonly [x: T, y: T],\n): value is readonly [x: T, y: T] {\n return Array.isArray(value)\n}\n\n/**\n * Spread a setting that may have been given as a single value.\n *\n * The parameter is written out rather than taken as `XYInput<T>`: a\n * conditional type cannot be narrowed, so the constraint stays where it is\n * declared and this takes both forms.\n *\n * The pair is copied rather than passed along, so that the result is a tuple\n * the caller owns even when a readonly one was given.\n */\nexport function toXY<T>(value: T | readonly [x: T, y: T]): XY<T> {\n return isPair(value) ? [value[0], value[1]] : [value, value]\n}\n","import {\n clamp,\n linearScale,\n normalizeValue,\n stepValue,\n toPrecision,\n type ValueRange,\n} from '@tremolo-ui/functions'\n\nimport { toXY, type XY, type XYInput } from '../xy'\n\nimport { createDrag, type DragState } from './drag'\n\n/**\n * How the 0-1 travel of one axis maps onto a value.\n *\n * Extends `ValueRange` so that a drag and an `applyDelta` nudge from a wheel\n * or an arrow key share one description of the scaling.\n */\nexport interface AxisOptions extends ValueRange {\n /**\n * Flip the axis so that its far end is `min`.\n *\n * Positions follow the screen: x grows to the right, y downwards. A vertical\n * slider whose maximum is at the top therefore reverses its y axis.\n *\n * @default false\n */\n reverse?: boolean\n}\n\n/** Where the value of each axis currently sits, as a position (see {@link DragValueMapping}). */\nexport interface MappingContext {\n position: () => XY<number>\n}\n\n/**\n * Turns pointer movement into a position on each axis: 0 is the `min` end of\n * the travel and 1 the `max` end, before `reverse` and the scaling of\n * {@link AxisOptions} are applied.\n *\n * A mapping holds the state of the drag in progress, so an instance belongs to\n * a single {@link createDragValue} instance.\n */\nexport interface DragValueMapping {\n /**\n * @returns the position, or null when it cannot be determined and the event\n * should be ignored.\n */\n start: (state: DragState, context: MappingContext) => XY<number> | null\n move: (state: DragState, context: MappingContext) => XY<number> | null\n}\n\n/**\n * Map the pointer onto the bounding rect of an element: the value *is* the\n * position pointed at, so the middle of the element is 0.5.\n *\n * The element is read on every event, so it may be mounted after the drag is\n * set up and may change size while a drag is in progress.\n */\nexport function elementMapping(\n getElement: () => Element | null | undefined,\n {\n sensitivity,\n }: {\n /**\n * How much the movement counts, read on every move. `1` is the pointer\n * position itself; `0.1` makes the same movement cover a tenth of the\n * travel, which is what a fine-adjustment modifier wants.\n *\n * Anything but `1` turns the mapping relative: the value stops being the\n * position pointed at and starts being where it stood when the sensitivity\n * changed, plus the movement since. **The pointer and the value stay apart\n * for the rest of the drag** rather than snapping back together when the\n * key is released, since snapping would move the value nobody asked to\n * move.\n */\n sensitivity?: (state: DragState) => number\n } = {},\n): DragValueMapping {\n /** The reported position when the sensitivity last changed. */\n let origin: XY<number> = [0, 0]\n /** The raw position at that same moment. */\n let anchor: XY<number> = [0, 0]\n /** The previous event, so a sensitivity change can be dated back to it. */\n let previous: XY<number> = [0, 0]\n let factor = 1\n\n function positionIn(state: DragState): XY<number> | null {\n const element = getElement()\n if (!element) return null\n const { left, top, right, bottom } = element.getBoundingClientRect()\n // A collapsed element has no travel to normalize against, and\n // normalizeValue rejects an empty range.\n return [\n right > left ? normalizeValue(state.clientX, left, right) : 0,\n bottom > top ? normalizeValue(state.clientY, top, bottom) : 0,\n ]\n }\n\n const reported = (raw: XY<number>, at: number): XY<number> => [\n origin[0] + (raw[0] - anchor[0]) * at,\n origin[1] + (raw[1] - anchor[1]) * at,\n ]\n\n return {\n start: (state) => {\n const raw = positionIn(state)\n if (!raw) return null\n // origin and anchor together, so the value is the position pointed at:\n // a plain click still lands where it was aimed.\n origin = raw\n anchor = raw\n previous = raw\n // Read here too, so a modifier already held when the pointer went down\n // applies from the first pixel rather than from the first change.\n factor = sensitivity ? sensitivity(state) : 1\n return raw\n },\n move: (state) => {\n const raw = positionIn(state)\n if (!raw) return null\n\n const next = sensitivity ? sensitivity(state) : 1\n if (next !== factor) {\n // Fold the travel so far into the origin, or the new sensitivity would\n // apply to the whole drag and the value would jump.\n //\n // Dated to the previous event rather than this one: a key produces no\n // pointer event of its own, so the change is only seen on the next\n // move, and that move's own distance belongs to the new sensitivity.\n origin = reported(previous, factor)\n anchor = previous\n factor = next\n }\n previous = raw\n\n // With no sensitivity given, origin and anchor never move apart and this\n // is the raw position, exactly as before.\n return reported(raw, factor)\n },\n }\n}\n\n/**\n * Move the value away from where it stood when the drag started, by the\n * distance dragged. The pointer position itself carries no meaning, so the\n * value can be adjusted from anywhere on the screen.\n */\nexport function relativeMapping({\n pixelRange = 100,\n sensitivity,\n}: {\n /**\n * Pixels of movement that span the whole range.\n * @default 100\n */\n pixelRange?: XYInput<number>\n /**\n * How much the movement counts, read on every move. `1` is `pixelRange` as\n * given; `0.1` makes the same movement cover a tenth of the range, which is\n * what a fine-adjustment modifier wants.\n *\n * Changing it mid-drag does not disturb the value: the travel so far is\n * folded into the origin and measuring starts again from there.\n */\n sensitivity?: (state: DragState) => number\n} = {}): DragValueMapping {\n const [baseX, baseY] = toXY(pixelRange)\n let origin: XY<number> = [0, 0]\n /** Where the current sensitivity took over, in drag coordinates. */\n let anchor: XY<number> = [0, 0]\n /** The previous event, so a sensitivity change can be dated back to it. */\n let previous: XY<number> = [0, 0]\n let factor = 1\n\n const travelled = (to: XY<number>, at: number): XY<number> => [\n baseX === 0 ? origin[0] : origin[0] + ((to[0] - anchor[0]) * at) / baseX,\n baseY === 0 ? origin[1] : origin[1] + ((to[1] - anchor[1]) * at) / baseY,\n ]\n\n return {\n start: (state, context) => {\n origin = context.position()\n anchor = [0, 0]\n previous = [0, 0]\n // Read here too, so a modifier already held when the pointer went down\n // applies from the first pixel rather than from the first change.\n factor = sensitivity ? sensitivity(state) : 1\n return origin\n },\n // Measured from the anchor rather than accumulated per event, so the\n // position picks up no rounding error of its own. The anchor only moves\n // when the sensitivity does, which is a handful of times per drag at most.\n move: (state) => {\n const next = sensitivity ? sensitivity(state) : 1\n if (next !== factor) {\n // Fold the travel so far into the origin, or the new sensitivity would\n // apply to the whole drag and the value would jump.\n //\n // Dated to the previous event rather than this one: a key produces no\n // pointer event of its own, so the change is only seen on the next\n // move, and that move's own distance belongs to the new sensitivity.\n origin = travelled(previous, factor)\n anchor = previous\n factor = next\n }\n previous = [state.x, state.y]\n return travelled([state.x, state.y], factor)\n },\n }\n}\n\nexport interface DragValueOptions {\n /** Scaling of each axis; a single value applies to both. */\n axis: XYInput<AxisOptions>\n\n /** How pointer movement becomes a position. */\n mapping: DragValueMapping\n\n /**\n * The current value of each axis. Read when a drag starts, by mappings that\n * move the value relative to it, such as {@link relativeMapping}.\n */\n getValue?: () => XY<number>\n\n /**\n * Report the value on pointer down, before any movement.\n *\n * Enable it where the pointer position *is* the value, so that a plain click\n * jumps to it. Leave it off where the element being dragged is an object in\n * its own right, so that grabbing its edge does not shift it under the\n * pointer.\n *\n * @default false\n */\n updateOnPointerDown?: boolean\n\n /** @see DragOptions.threshold */\n threshold?: number\n /** @see DragOptions.cursor */\n cursor?: string\n /**\n * @see DragOptions.pointerLock\n *\n * Only for a mapping that moves the value relative to where it stood, such\n * as {@link relativeMapping}. {@link elementMapping} reads the pointer\n * position, and there is none while it is locked.\n */\n pointerLock?: boolean\n /** @see DragOptions.shouldStart */\n shouldStart?: (event: PointerEvent) => boolean\n\n onChange?: (value: XY<number>, state: DragState) => void\n onDragStart?: (value: XY<number>, state: DragState) => void\n onDragEnd?: (value: XY<number>, state: DragState) => void\n}\n\nexport interface DragValueInstance {\n /**\n * Replace the given options. Lets a wrapper feed fresh values in without\n * tearing down the listeners, which would abort a drag in progress.\n *\n * `mapping` is fixed for the lifetime of the instance and is ignored here.\n */\n update: (options: Partial<DragValueOptions>) => void\n destroy: () => void\n}\n\n/**\n * Drive a value with a pointer drag.\n *\n * Combines {@link createDrag} with the scaling of `@tremolo-ui/functions`: the\n * mapping decides where the pointer sits on the 0-1 travel of each axis, and\n * the axis options turn that into a value.\n */\nexport function createDragValue(\n element: Element,\n options: DragValueOptions,\n): DragValueInstance {\n let opts = options\n let lastValue: XY<number> = [0, 0]\n let active = false\n\n const axes = () => toXY(opts.axis)\n\n function valueOf(position: XY<number>): XY<number> {\n return axes().map((axis, i) => {\n const p = axis.reverse ? 1 - position[i] : position[i]\n // A scale clamps the position, so a mapping may report outside 0-1.\n const scale = axis.scale ?? linearScale\n const value = scale.denormalize(p, axis.min, axis.max)\n const stepped = axis.step ? stepValue(value, axis.step) : value\n // Rounding to the step can leave the range, and so can dropping the\n // binary artefact, so the clamp comes last.\n return clamp(toPrecision(stepped), axis.min, axis.max)\n }) as XY<number>\n }\n\n const context: MappingContext = {\n position: () => {\n const getValue = opts.getValue\n if (!getValue) {\n throw new Error(\n 'createDragValue: getValue is required by the given mapping',\n )\n }\n const value = getValue()\n return axes().map((axis, i) => {\n const scale = axis.scale ?? linearScale\n const n = scale.normalize(value[i], axis.min, axis.max)\n return axis.reverse ? 1 - n : n\n }) as XY<number>\n },\n }\n\n const drag = createDrag(element, {\n threshold: opts.threshold,\n cursor: opts.cursor,\n pointerLock: opts.pointerLock,\n shouldStart: (event) => opts.shouldStart?.(event) ?? true,\n onDragStart: (state) => {\n const position = opts.mapping.start(state, context)\n if (!position) return\n active = true\n lastValue = valueOf(position)\n if (opts.updateOnPointerDown) opts.onChange?.(lastValue, state)\n opts.onDragStart?.(lastValue, state)\n },\n onDrag: (state) => {\n if (!active) return\n const position = opts.mapping.move(state, context)\n if (!position) return\n lastValue = valueOf(position)\n opts.onChange?.(lastValue, state)\n },\n onDragEnd: (state) => {\n if (!active) return\n if (\n state.event.type === 'pointerup' &&\n (state.deltaX !== 0 || state.deltaY !== 0)\n ) {\n const position = opts.mapping.move(state, context)\n if (position) {\n lastValue = valueOf(position)\n opts.onChange?.(lastValue, state)\n }\n }\n active = false\n opts.onDragEnd?.(lastValue, state)\n },\n })\n\n return {\n update: (next) => {\n opts = { ...opts, ...next, mapping: opts.mapping }\n drag.update({\n threshold: opts.threshold,\n cursor: opts.cursor,\n pointerLock: opts.pointerLock,\n })\n },\n destroy: () => drag.destroy(),\n }\n}\n","export interface WheelOptions {\n /** Replace the callback through {@link WheelInstance.update}. */\n onWheel?: (event: WheelEvent) => void\n /**\n * Only report events while the focus is inside the element.\n *\n * A control that reacts to the wheel on hover alone takes the scroll away\n * from the page, so passing over one in a long form silently changes its\n * value. Requiring focus makes that an explicit act.\n *\n * The check is `contains`, not an identity test: the element that actually\n * takes focus is usually a descendant, such as a thumb or an `<input>`, and\n * a caller may have replaced it with markup of their own.\n *\n * @default false\n */\n requireFocus?: boolean\n}\n\nexport interface WheelInstance {\n /** Replace the given options, keeping the listener in place. */\n update: (options: WheelOptions) => void\n destroy: () => void\n}\n\n/**\n * Listen to wheel events on an element.\n *\n * The listener is registered with `passive: false` so that the handler can call\n * `preventDefault()` to stop the page from scrolling.\n */\nexport function createWheel(\n element: Element,\n onWheel: (event: WheelEvent) => void,\n options: WheelOptions = {},\n): WheelInstance {\n let opts = { ...options, onWheel }\n\n function hasFocus() {\n const active = element.ownerDocument?.activeElement\n return !!active && element.contains(active)\n }\n\n const handler = (event: Event) => {\n if (opts.requireFocus && !hasFocus()) return\n opts.onWheel?.(event as WheelEvent)\n }\n\n element.addEventListener('wheel', handler, { passive: false })\n\n return {\n update: (next) => {\n opts = { ...opts, ...next }\n },\n destroy: () => {\n // Only `capture` matters when removing, and it is false here.\n element.removeEventListener('wheel', handler)\n },\n }\n}\n","import type { XY } from '../xy'\n\n/**\n * A rectangle in the 0..1 space of whatever the box is drawn over, with `y`\n * growing downwards — the same space the items are given in.\n */\nexport interface SelectionBoxRect {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport interface SelectionBoxOptions<Id> {\n /**\n * Everything the box can pick up, read on every move rather than taken as a\n * snapshot: what is under the box changes while it is dragged, and an item\n * may have moved since the last one.\n */\n items: () => Iterable<readonly [Id, XY<number>]>\n /** The box as it is dragged, and `null` once it is gone. */\n onBoxChange?: (box: SelectionBoxRect | null) => void\n /** What the box covers, added to whatever it started from. */\n onSelectionChange?: (ids: Id[]) => void\n}\n\nexport interface SelectionBoxBeginOptions<Id> {\n /**\n * Add to `selection` rather than replacing it. Ctrl / meta rather than\n * shift, for the controls where shift is the fine-adjustment key.\n */\n additive?: boolean\n /** What was selected before the box started. Only read when `additive`. */\n selection?: readonly Id[]\n}\n\nexport interface SelectionBoxInstance<Id> {\n /** Replace the given options, without interrupting a box in progress. */\n update: (options: Partial<SelectionBoxOptions<Id>>) => void\n\n /** Start a box at `at`, which is one of its corners. */\n begin: (at: XY<number>, options?: SelectionBoxBeginOptions<Id>) => void\n /** Drag the opposite corner to `to`. */\n move: (to: XY<number>) => void\n /**\n * Finish the box. The selection stays as it is.\n *\n * @returns whether a box was running, so that a caller can tell a drag from\n * a press that selected nothing.\n */\n end: () => boolean\n\n /** The box being dragged, or `null` when there is none. */\n box: () => SelectionBoxRect | null\n\n destroy: () => void\n}\n\nfunction rectOf(from: XY<number>, to: XY<number>): SelectionBoxRect {\n return {\n x: Math.min(from[0], to[0]),\n y: Math.min(from[1], to[1]),\n width: Math.abs(to[0] - from[0]),\n height: Math.abs(to[1] - from[1]),\n }\n}\n\n/** Whether the box covers the point, edges included. */\nexport function selectionBoxCovers(\n box: SelectionBoxRect,\n [x, y]: XY<number>,\n): boolean {\n return (\n x >= box.x &&\n x <= box.x + box.width &&\n y >= box.y &&\n y <= box.y + box.height\n )\n}\n\n/**\n * Selecting by dragging a box over a set of items.\n *\n * The drag itself is not here: the caller owns the pointer, and reports where\n * it went in the same 0..1 space the items are given in.\n */\nexport function createSelectionBox<Id>(\n options: SelectionBoxOptions<Id>,\n): SelectionBoxInstance<Id> {\n let opts = options\n let drag: { from: XY<number>; base: readonly Id[] } | null = null\n let box: SelectionBoxRect | null = null\n\n function setBox(next: SelectionBoxRect | null) {\n box = next\n opts.onBoxChange?.(next)\n }\n\n function apply(rect: SelectionBoxRect, base: readonly Id[]) {\n const inside: Id[] = []\n for (const [id, at] of opts.items()) {\n if (selectionBoxCovers(rect, at) && !base.includes(id)) inside.push(id)\n }\n opts.onSelectionChange?.([...base, ...inside])\n }\n\n return {\n update: (next) => {\n opts = { ...opts, ...next }\n },\n begin: (at, { additive = false, selection = [] } = {}) => {\n drag = { from: at, base: additive ? [...selection] : [] }\n setBox(rectOf(at, at))\n // A plain press is a deselect of its own, so that a drag that picks\n // nothing up leaves nothing selected.\n if (!additive) opts.onSelectionChange?.([])\n },\n move: (to) => {\n if (!drag) return\n const rect = rectOf(drag.from, to)\n setBox(rect)\n apply(rect, drag.base)\n },\n end: () => {\n const running = drag !== null\n drag = null\n if (box !== null) setBox(null)\n return running\n },\n box: () => box,\n destroy: () => {\n drag = null\n box = null\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["names","pointerEvent"],"sources":["../src/canvas/context.ts","../src/canvas/animation.ts","../src/file/accept.ts","../src/file/drop-zone.ts","../src/input/modifiers.ts","../src/input/apply-delta.ts","../src/input/check-steps.ts","../src/input/defaults.ts","../src/input/direction.ts","../src/knob/geometry.ts","../src/midi/access.ts","../src/midi/message.ts","../src/midi/input.ts","../src/pointer/drag.ts","../src/number-input/stepper-drag.ts","../src/number-input/value.ts","../src/number-input/text.ts","../src/options/replace.ts","../src/piano/layout.ts","../src/piano/shortcuts.ts","../src/piano/index.ts","../src/selection/box.ts","../src/points-editor/index.ts","../src/xy.ts","../src/pointer/drag-value.ts","../src/pointer/long-press.ts","../src/pointer/wheel.ts","../src/position.ts","../src/slider/decimal-digits.ts","../src/slider/marks.ts","../src/style.ts"],"sourcesContent":["/**\n * The assignable parts of a 2D context's drawing state. They have to be\n * carried across a resize by hand because setting `canvas.width` resets the\n * context to its defaults. The transform and line dash are handled separately\n * by {@link DrawingContext} because they are exposed through methods.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save\n */\nexport const drawingState = [\n 'strokeStyle',\n 'fillStyle',\n 'globalAlpha',\n 'lineWidth',\n 'lineCap',\n 'lineJoin',\n 'miterLimit',\n 'lineDashOffset',\n 'shadowOffsetX',\n 'shadowOffsetY',\n 'shadowBlur',\n 'shadowColor',\n 'globalCompositeOperation',\n 'filter',\n 'font',\n 'fontKerning',\n 'fontStretch',\n 'fontVariantCaps',\n 'textAlign',\n 'textBaseline',\n 'direction',\n 'letterSpacing',\n 'textRendering',\n 'wordSpacing',\n 'imageSmoothingEnabled',\n 'imageSmoothingQuality',\n] as const\n\nexport type DrawingState = (typeof drawingState)[number]\nexport type DrawingStateValue = CanvasRenderingContext2D[DrawingState]\nexport type DrawingContext = Pick<CanvasRenderingContext2D, DrawingState> & {\n /** The current line dash sequence. */\n lineDash: number[]\n /** The current transformation matrix. */\n transform: DOMMatrix\n}\n\nexport function isDrawingState(value: unknown): value is DrawingState {\n const names: readonly string[] = drawingState\n return typeof value === 'string' && names.includes(value)\n}\n\n/** Copy the drawing state off a context, to put back after a resize. */\nexport function readDrawingState(\n context: CanvasRenderingContext2D,\n): DrawingContext {\n const state = {} as DrawingContext\n for (const property of drawingState) {\n ;(state[property] as DrawingStateValue) = context[property]\n }\n state.lineDash = context.getLineDash()\n state.transform = context.getTransform()\n return state\n}\n\n/** Put a state read by {@link readDrawingState} back onto a context. */\nexport function writeDrawingState(\n context: CanvasRenderingContext2D,\n state: DrawingContext,\n transformScale = 1,\n) {\n for (const property of drawingState) {\n ;(context[property] as DrawingStateValue) = state[property]\n }\n context.setLineDash(state.lineDash)\n const { a, b, c, d, e, f } = state.transform\n context.setTransform(\n a * transformScale,\n b * transformScale,\n c * transformScale,\n d * transformScale,\n e * transformScale,\n f * transformScale,\n )\n}\n\n/**\n * Give the canvas a backing store of `width * dpr` by `height * dpr` device\n * pixels while keeping it `width` by `height` in CSS pixels, and scale the\n * context so that drawing code can work in CSS pixels throughout.\n *\n * Assigning to `canvas.width` clears the canvas and resets the context, so\n * this both resizes and re-establishes the transform.\n */\nexport function applyDevicePixelRatio(\n canvas: HTMLCanvasElement,\n context: CanvasRenderingContext2D,\n width: number,\n height: number,\n dpr: number,\n) {\n canvas.width = width * dpr\n canvas.height = height * dpr\n // Reset the current transformation matrix to the identity matrix\n context.setTransform(1, 0, 0, 1, 0, 0)\n context.scale(dpr, dpr)\n canvas.style.width = `${width}px`\n canvas.style.height = `${height}px`\n}\n","import {\n applyDevicePixelRatio,\n readDrawingState,\n writeDrawingState,\n type DrawingContext,\n} from './context'\n\n/** What the canvas looked like when a frame was drawn. */\nexport interface AnimationFrame {\n /** Width of the canvas in CSS pixels. */\n width: number\n /** Height of the canvas in CSS pixels. */\n height: number\n /** Frames drawn so far, starting at 0. */\n count: number\n /** Milliseconds since the previous frame. */\n deltaTime: number\n /** Milliseconds since the instance was created. */\n elapsedTime: number\n /** Frames per second implied by `deltaTime`, or 0 when no time elapsed. */\n fps: number\n}\n\nexport type CanvasDrawFunction = (\n context: CanvasRenderingContext2D,\n frame: AnimationFrame,\n) => void\n\nexport type CanvasInitFunction = (\n context: CanvasRenderingContext2D,\n size: { width: number; height: number },\n) => void\n\nexport interface AnimationCanvasOptions {\n /** Called for every frame. */\n draw: CanvasDrawFunction\n\n /** Called once, after the first size is known and before the first frame. */\n init?: CanvasInitFunction\n\n /**\n * Keep drawing on every animation frame. When off, a frame is drawn only on\n * a resize or an explicit {@link AnimationCanvasInstance.redraw}.\n *\n * @default true\n */\n animate?: boolean\n\n /**\n * Size in CSS pixels. Ignored when `resizable` is on.\n *\n * @default { width: 100, height: 100 }\n */\n size?: { width: number; height: number }\n\n /**\n * Follow the size of the canvas's parent element instead of `size`.\n *\n * Fixed for the lifetime of the instance: it decides whether a\n * `ResizeObserver` is attached.\n *\n * @default false\n */\n resizable?: boolean\n\n /**\n * Carry the drawing across a resize, so that the canvas does not blank for a\n * frame while the new size is drawn.\n *\n * @default true\n */\n reduceFlickering?: boolean\n\n /**\n * Passed to `getContext('2d', …)`. Read once when the context is created, so\n * it is fixed for the lifetime of the instance.\n *\n * @see https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement/getContext#contextattributes\n */\n contextAttributes?: CanvasRenderingContext2DSettings\n}\n\nexport interface AnimationCanvasInstance {\n /**\n * Replace the given options. Lets a wrapper feed a fresh `draw` in on every\n * render without restarting the animation, so the frame count and the\n * elapsed time keep running.\n *\n * `resizable` and `contextAttributes` are fixed for the lifetime of the\n * instance and are ignored here.\n *\n * While `animate` is off this also draws a frame, since nothing else would.\n */\n update: (options: Partial<AnimationCanvasOptions>) => void\n\n /** Draw one frame now, whether or not `animate` is on. */\n redraw: () => void\n\n destroy: () => void\n}\n\n/**\n * Drive a canvas from `requestAnimationFrame`, keeping its backing store in\n * step with the device pixel ratio and, optionally, with the size of its\n * parent.\n *\n * Drawing code works in CSS pixels: the context is scaled by the device pixel\n * ratio, and `width` / `height` of each {@link AnimationFrame} are CSS pixels.\n */\nexport function createAnimationCanvas(\n canvas: HTMLCanvasElement,\n options: AnimationCanvasOptions,\n): AnimationCanvasInstance {\n let opts = options\n\n const context2d = canvas.getContext('2d', opts.contextAttributes)\n if (!context2d) {\n throw new Error('createAnimationCanvas: cannot get a 2d context')\n }\n const context: CanvasRenderingContext2D = context2d\n\n const resizable = opts.resizable ?? false\n\n /** Off-document canvas holding the drawing while the real one is resized. */\n let memo: HTMLCanvasElement | null = null\n let memoContext: CanvasRenderingContext2D | null = null\n\n let width = 0\n let height = 0\n let appliedDpr = 0\n /** Whether a size has been applied, so a frame can be drawn. */\n let sized = false\n let initialized = false\n\n let frameId: number | null = null\n let count = -1\n const startTime = performance.now()\n let previousTime = startTime\n\n function devicePixelRatio() {\n return globalThis.devicePixelRatio || 1\n }\n\n /**\n * Copy the canvas onto the memo canvas at its full device resolution, 1:1.\n *\n * Assigning to `memo.width` resets the memo's transform to the identity, so\n * the copy neither scales nor resamples.\n */\n function takeSnapshot() {\n if (!(opts.reduceFlickering ?? true)) return false\n if (!memo) {\n memo = globalThis.document?.createElement('canvas') ?? null\n memoContext = memo?.getContext('2d', opts.contextAttributes) ?? null\n }\n if (!memo || !memoContext) return false\n\n memo.width = canvas.width\n memo.height = canvas.height\n if (canvas.width > 0 && canvas.height > 0) {\n memoContext.drawImage(canvas, 0, 0)\n }\n return true\n }\n\n /**\n * Put the snapshot back, given the size in CSS pixels it was taken at.\n *\n * The context is scaled to CSS pixels, so drawing the snapshot at its old\n * CSS size leaves it exactly where it was. While the device pixel ratio\n * stays put that is a 1:1 copy of device pixels, and when it changes the\n * snapshot is rescaled once, from its full resolution.\n */\n function restoreSnapshot(\n hasSnapshot: boolean,\n previousWidth: number,\n previousHeight: number,\n ) {\n if (!hasSnapshot || !memo) return\n if (memo.width <= 0 || memo.height <= 0) return\n context.drawImage(memo, 0, 0, previousWidth, previousHeight)\n }\n\n function applySize(w: number, h: number) {\n const dpr = devicePixelRatio()\n const previousWidth = width\n const previousHeight = height\n const previousDpr = appliedDpr\n const state: DrawingContext | null = sized\n ? readDrawingState(context)\n : null\n const hasSnapshot = sized && takeSnapshot()\n applyDevicePixelRatio(canvas, context, w, h, dpr)\n width = w\n height = h\n appliedDpr = dpr\n sized = true\n // Restore pixels while the reset context still has neutral alpha,\n // compositing, filter, and shadow settings.\n restoreSnapshot(hasSnapshot, previousWidth, previousHeight)\n if (state) {\n writeDrawingState(context, state, dpr / previousDpr)\n }\n }\n\n function applyDevicePixelRatioIfNeeded() {\n if (sized && devicePixelRatio() !== appliedDpr) {\n applySize(width, height)\n }\n }\n\n function drawFrame(timestamp = performance.now()) {\n if (!sized) return\n applyDevicePixelRatioIfNeeded()\n if (!initialized) {\n initialized = true\n opts.init?.(context, { width, height })\n }\n const deltaTime = timestamp - previousTime\n previousTime = timestamp\n count += 1\n opts.draw(context, {\n width,\n height,\n count,\n deltaTime,\n elapsedTime: timestamp - startTime,\n fps: deltaTime > 0 ? 1000 / deltaTime : 0,\n })\n }\n\n function tick(timestamp: DOMHighResTimeStamp) {\n // Scheduled before drawing so that a slow frame does not delay the next\n // request, matching how requestAnimationFrame loops are usually written.\n frameId = requestAnimationFrame(tick)\n drawFrame(timestamp)\n }\n\n function startLoop() {\n if (frameId !== null) return\n previousTime = performance.now()\n frameId = requestAnimationFrame(tick)\n }\n\n function stopLoop() {\n if (frameId === null) return\n cancelAnimationFrame(frameId)\n frameId = null\n }\n\n let observer: ResizeObserver | null = null\n\n if (resizable) {\n const parent = canvas.parentElement\n if (!parent) {\n throw new Error(\n 'createAnimationCanvas: resizable needs the canvas to have a parent element',\n )\n }\n // The observer reports the current size as soon as it starts, so it is the\n // single source of the size — including the first one. Reading the parent\n // here instead would mix the padding box with the content box the observer\n // reports.\n observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width: w, height: h } = entry.contentRect\n if (w !== width || h !== height || devicePixelRatio() !== appliedDpr) {\n applySize(w, h)\n }\n // With the loop running the next frame covers the new size already.\n if (!(opts.animate ?? true)) drawFrame()\n }\n })\n observer.observe(parent)\n } else {\n const { width: w = 100, height: h = 100 } = opts.size ?? {}\n applySize(w, h)\n }\n\n if (opts.animate ?? true) {\n startLoop()\n } else if (sized) {\n drawFrame()\n }\n\n return {\n update: (next) => {\n const wasAnimating = opts.animate ?? true\n opts = {\n ...opts,\n ...next,\n resizable,\n contextAttributes: opts.contextAttributes,\n }\n\n if (!resizable) {\n const { width: w = 100, height: h = 100 } = opts.size ?? {}\n if (w !== width || h !== height) applySize(w, h)\n }\n applyDevicePixelRatioIfNeeded()\n\n const animating = opts.animate ?? true\n if (animating && !wasAnimating) startLoop()\n if (!animating && wasAnimating) stopLoop()\n\n // Without a loop running, a resize and this call are the only things\n // that can put anything new on the canvas — so a new `draw` has to be\n // painted here. That is what makes a canvas driven by state rather than\n // by time work: the wrapper re-renders, pushes the new handler in, and\n // the frame it draws reflects it.\n if (!animating) drawFrame()\n },\n redraw: drawFrame,\n destroy: () => {\n stopLoop()\n observer?.disconnect()\n observer = null\n memo = null\n memoContext = null\n },\n }\n}\n","/**\n * What an `accept` rule is matched against.\n *\n * `File` satisfies it, and so does `DataTransferItem` — which matters while a\n * drag is still in the air, since the browser reports the type of what is\n * being dragged but withholds the name.\n */\nexport interface AcceptCandidate {\n /** The file name, when it is known. */\n name?: string\n /** The MIME type, or `''` when the browser has no type for it. */\n type: string\n}\n\n/**\n * Does a file satisfy an `accept` attribute?\n *\n * `accept` is written the way the HTML attribute is: a comma separated list of\n * extensions (`.wav`), MIME types (`audio/wav`) and type groups (`audio/*`).\n * Anything that matches one entry is accepted, and an empty or missing\n * `accept` takes everything.\n *\n * **The browser's own `accept` is only a hint to the file picker.** A person\n * can switch it to \"All Files\", drag a file in, or pick one the picker was\n * never asked about, so what arrives still has to be checked.\n *\n * A rule that cannot be decided is not treated as a rejection: with no `name`,\n * an extension rule says nothing either way, and `accept=\".wav\"` reports a\n * match rather than refusing a file it has not seen the name of. The name is\n * there by the time the file is dropped, which is when the answer counts.\n */\nexport function matchesAccept(\n candidate: AcceptCandidate,\n accept?: string,\n): boolean {\n const rules = (accept ?? '')\n .split(',')\n .map((rule) => rule.trim().toLowerCase())\n .filter(Boolean)\n if (rules.length === 0) return true\n\n const name = candidate.name?.toLowerCase()\n const type = candidate.type.toLowerCase()\n let undecided = false\n\n for (const rule of rules) {\n if (rule.startsWith('.')) {\n if (name === undefined) undecided = true\n else if (name.endsWith(rule)) return true\n } else if (rule.endsWith('/*')) {\n if (type.startsWith(rule.slice(0, -1))) return true\n } else if (type === rule) {\n return true\n }\n }\n\n return undecided\n}\n\n/**\n * Split files into those that satisfy `accept` and those that do not, keeping\n * their order. See {@link matchesAccept}.\n */\nexport function partitionByAccept(\n files: Iterable<File>,\n accept?: string,\n): { accepted: File[]; rejected: File[] } {\n const accepted: File[] = []\n const rejected: File[] = []\n for (const file of files) {\n if (matchesAccept(file, accept)) accepted.push(file)\n else rejected.push(file)\n }\n return { accepted, rejected }\n}\n","import { matchesAccept, partitionByAccept } from './accept'\n\n/** What is in the air over the element. */\nexport interface DropZoneState {\n /** Files are being dragged over the element. */\n over: boolean\n /**\n * None of what is being dragged matches `accept`, as far as can be told\n * before the drop.\n *\n * The browser reports the type of what is being dragged but withholds the\n * name, so a rule written as an extension cannot be decided yet and is not\n * counted against the drag. It is decided on the drop, where the name is.\n */\n invalid: boolean\n}\n\nexport interface DropZoneOptions {\n /**\n * Which files to take, written the way the `accept` attribute of a file\n * input is: a comma separated list of extensions (`.wav`), MIME types\n * (`audio/wav`) and type groups (`audio/*`).\n */\n accept?: string\n /**\n * Take more than one file from a single drop. With it off, only the first\n * accepted file is reported, as a file input without `multiple` does.\n *\n * @default false\n */\n multiple?: boolean\n /**\n * Refuse the drop. The drag is still swallowed rather than let through: an\n * unhandled drop makes the browser leave the page and open the file.\n *\n * @default false\n */\n disabled?: boolean\n\n /** Called with the dropped files that match `accept`. */\n onDrop?: (files: File[], event: DragEvent) => void\n /**\n * Called with the dropped files that do not match `accept`, so that the\n * reason can be shown. It comes after `onDrop` for the same drop, so a list\n * of rejected files can be cleared in `onDrop` and filled here.\n */\n onReject?: (files: File[], event: DragEvent) => void\n /** Called whenever {@link DropZoneInstance.state} would change. */\n onStateChange?: (state: DropZoneState) => void\n}\n\nexport interface DropZoneInstance {\n /** What is in the air over the element, right now. */\n readonly state: DropZoneState\n /** Replace the given options, keeping the listeners in place. */\n update: (options: DropZoneOptions) => void\n destroy: () => void\n}\n\n/**\n * Take files dropped onto an element.\n *\n * ```ts\n * const zone = createDropZone(element, {\n * accept: 'audio/*',\n * onDrop: (files) => load(files[0]),\n * })\n * ```\n *\n * The element needs no attribute of its own: a drop target is made by\n * cancelling `dragover`, which this does.\n */\nexport function createDropZone(\n element: Element,\n options: DropZoneOptions = {},\n): DropZoneInstance {\n let opts = options\n let state: DropZoneState = { over: false, invalid: false }\n\n /**\n * `dragenter` and `dragleave` fire for descendants too, so moving between\n * two children of the zone leaves before it enters. Counting the pairs is\n * what keeps the state from flickering off in the middle of the element.\n */\n let depth = 0\n\n function setState(next: DropZoneState) {\n if (next.over === state.over && next.invalid === state.invalid) return\n state = next\n opts.onStateChange?.(state)\n }\n\n function carriesFiles(transfer: DataTransfer | null) {\n // `types` is the only thing that can be trusted during a drag; `files` is\n // empty until the drop.\n return !!transfer?.types.includes('Files')\n }\n\n /** Whether anything being dragged could still be accepted. */\n function anyAcceptable(transfer: DataTransfer | null) {\n const items = Array.from(transfer?.items ?? []).filter(\n (item) => item.kind === 'file',\n )\n // Some browsers hand over no items at all, only the `Files` type. Nothing\n // is known, so nothing is refused.\n if (items.length === 0) return true\n return items.some((item) => matchesAccept({ type: item.type }, opts.accept))\n }\n\n function onDragEnter(event: DragEvent) {\n if (!carriesFiles(event.dataTransfer)) return\n // Cancelled as well as `dragover`: a target that only cancels one of the\n // two is not a drop target in every browser.\n event.preventDefault()\n depth += 1\n setState({ over: true, invalid: !anyAcceptable(event.dataTransfer) })\n }\n\n function onDragOver(event: DragEvent) {\n if (!carriesFiles(event.dataTransfer)) return\n event.preventDefault()\n if (event.dataTransfer) {\n // Decides the cursor the pointer shows, and whether a drop is offered.\n event.dataTransfer.dropEffect =\n opts.disabled || state.invalid ? 'none' : 'copy'\n }\n // A drag that began outside the document can arrive without a `dragenter`\n // the listener saw, so the state is settled here too.\n if (!state.over) {\n depth = Math.max(depth, 1)\n setState({ over: true, invalid: !anyAcceptable(event.dataTransfer) })\n }\n }\n\n function onDragLeave(event: DragEvent) {\n if (!carriesFiles(event.dataTransfer)) return\n depth = Math.max(0, depth - 1)\n if (depth === 0) setState({ over: false, invalid: false })\n }\n\n function onDrop(event: DragEvent) {\n if (!carriesFiles(event.dataTransfer)) return\n // Always cancelled, even while disabled: an unhandled drop makes the\n // browser leave the page and open the file.\n event.preventDefault()\n depth = 0\n setState({ over: false, invalid: false })\n if (opts.disabled) return\n\n const { accepted, rejected } = partitionByAccept(\n event.dataTransfer?.files ?? [],\n opts.accept,\n )\n\n const taken = opts.multiple ? accepted : accepted.slice(0, 1)\n if (taken.length > 0) opts.onDrop?.(taken, event)\n if (rejected.length > 0) opts.onReject?.(rejected, event)\n }\n\n /**\n * A drag that ends anywhere else — dropped on another element, cancelled\n * with Esc, taken out of the window — sends no `dragleave` here, and the\n * element would stay marked as a drop target for good.\n */\n function onDragEndAnywhere() {\n depth = 0\n setState({ over: false, invalid: false })\n }\n\n const handlers = {\n dragenter: onDragEnter,\n dragover: onDragOver,\n dragleave: onDragLeave,\n drop: onDrop,\n } as const\n\n for (const [type, handler] of Object.entries(handlers)) {\n element.addEventListener(type, handler as EventListener)\n }\n\n const doc = element.ownerDocument\n doc?.addEventListener('dragend', onDragEndAnywhere)\n doc?.addEventListener('drop', onDragEndAnywhere)\n\n return {\n get state() {\n return state\n },\n update: (next) => {\n opts = { ...opts, ...next }\n },\n destroy: () => {\n for (const [type, handler] of Object.entries(handlers)) {\n element.removeEventListener(type, handler as EventListener)\n }\n doc?.removeEventListener('dragend', onDragEndAnywhere)\n doc?.removeEventListener('drop', onDragEndAnywhere)\n },\n }\n}\n","/**\n * Options for setting the amount of keyboard and mouse wheel changes.\n */\nexport type InputEventOption = readonly ['normalized' | 'raw', number]\n\n/**\n * A modifier key that can carry an amount of its own.\n *\n * `ctrl` and `meta` are kept apart rather than folded into one \"command\" key:\n * a plugin UI that mirrors a desktop host usually wants the same physical key\n * on every platform, not the platform's own convention.\n */\nexport type Modifier = 'shift' | 'alt' | 'ctrl' | 'meta'\n\n/** The modifier flags of a `WheelEvent` or a `KeyboardEvent`. */\nexport interface ModifierState {\n shiftKey: boolean\n altKey: boolean\n ctrlKey: boolean\n metaKey: boolean\n}\n\n/** One setting per modifier key, with `default` for none of them. */\ntype ModifierSetting = number | InputEventOption\n\nexport type ModifierMap<T extends ModifierSetting> = { default: T } & Partial<\n Record<Modifier, T>\n>\n\n/**\n * A single setting, or one per modifier key.\n *\n * @example\n * ['raw', 1]\n * { default: ['raw', 1], shift: ['raw', 0.1] }\n */\nexport type ModifierValue<T extends ModifierSetting> = T | ModifierMap<T>\n\n/**\n * Checked in this order, and the first one that is both held and configured\n * wins. Fixing an order is what keeps two modifiers held at once from\n * behaving differently between browsers.\n */\nconst MODIFIER_ORDER = ['meta', 'ctrl', 'alt', 'shift'] as const\n\nconst MODIFIER_FLAG = {\n meta: 'metaKey',\n ctrl: 'ctrlKey',\n alt: 'altKey',\n shift: 'shiftKey',\n} as const satisfies Record<Modifier, keyof ModifierState>\n\n/**\n * A map is the only form with a `default` key, which is what tells it apart\n * from a bare setting. Tuples are arrays, so they never match.\n */\nfunction isModifierMap<T extends ModifierSetting>(\n value: ModifierValue<T>,\n): value is ModifierMap<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n 'default' in value\n )\n}\n\n/**\n * Pick the setting that applies, given the modifier keys being held.\n *\n * @example\n * selectModifier({ default: 1, shift: 0.1 }, event)\n */\nexport function selectModifier<T extends ModifierSetting>(\n options: ModifierValue<T>,\n modifiers?: ModifierState,\n): { value: T; modifier: Modifier | null } {\n if (!isModifierMap(options)) {\n // TypeScript cannot subtract the map from `ModifierValue<T>` while `T` is\n // still a type parameter, so the other half has to be spelled out.\n return { value: options as T, modifier: null }\n }\n if (modifiers) {\n for (const modifier of MODIFIER_ORDER) {\n const value = options[modifier]\n // Compared against undefined rather than checked for truthiness: 0 is a\n // legitimate setting.\n if (value !== undefined && modifiers[MODIFIER_FLAG[modifier]]) {\n return { value, modifier }\n }\n }\n }\n return { value: options.default, modifier: null }\n}\n\n/**\n * Turn every entry of a setting into another kind of setting, keeping which\n * modifier each belongs to.\n *\n * A drag sensitivity is a number and a keyboard amount is a tuple, but the two\n * describe the same thing from the caller's side. This carries one over to the\n * other so that a component can hand a sensitivity to {@link applyDelta}\n * without unpicking the modifier map itself — which matters, since naming a\n * modifier is also what takes `step` out of the pipeline.\n *\n * @example\n * mapModifier({ default: 1, shift: 0.1 }, (f) => ['raw', step * f])\n * // { default: ['raw', 1], shift: ['raw', 0.1] }\n */\nexport function mapModifier<\n T extends ModifierSetting,\n U extends ModifierSetting,\n>(options: ModifierValue<T>, fn: (value: T) => U): ModifierValue<U> {\n if (!isModifierMap(options)) return fn(options as T)\n const mapped = { default: fn(options.default) } as ModifierMap<U>\n for (const modifier of MODIFIER_ORDER) {\n const value = options[modifier]\n if (value !== undefined) mapped[modifier] = fn(value)\n }\n return mapped\n}\n","import {\n clamp,\n linearScale,\n stepValue,\n toPrecision,\n type ValueRange,\n} from '@tremolo-ui/functions'\n\nimport {\n selectModifier,\n type InputEventOption,\n type ModifierState,\n type ModifierValue,\n} from './modifiers'\n\n/**\n * Move a value by an amount of input, as reported by a wheel or an arrow key.\n *\n * The pipeline matches {@link createDragValue}: scale, then step, then clamp.\n * Which key or which sign of `deltaY` counts as which direction is left to the\n * caller, since it differs per component.\n *\n * @param direction which way, and how many times, to apply the option. The\n * size of one step is `option[1]`, so this is normally `1` or `-1`.\n *\n * @param modifiers the event, for `options` that name a modifier key. See\n * {@link selectModifier}.\n *\n * @example\n * // ArrowDown on a slider whose keyboard option is ['raw', 1]\n * applyDelta(value, -1, keyboard, { min, max, step, scale })\n *\n * @example\n * // Shift+ArrowDown, where `keyboard` is { default: …, shift: ['raw', 0.1] }\n * applyDelta(value, -1, keyboard, range, event)\n */\nexport function applyDelta(\n value: number,\n direction: number,\n options: ModifierValue<InputEventOption>,\n { min, max, step, scale = linearScale }: ValueRange,\n modifiers?: ModifierState,\n): number {\n if (min >= max) throw new RangeError('requirements: min < max')\n if (step !== undefined && (!Number.isFinite(step) || step <= 0)) {\n throw new RangeError(\n 'applyDelta step: requirements: finite and greater than 0',\n )\n }\n\n const {\n value: [mode, amount],\n modifier,\n } = selectModifier(options, modifiers)\n\n const x = direction * amount\n const next =\n mode === 'normalized'\n ? scale.denormalize(scale.normalize(value, min, max) + x, min, max)\n : value + x\n\n // Naming a modifier is a deliberate request to move off the grid, so `step`\n // does not apply to it. Without this a finer amount would round straight\n // back to where it started: `stepValue(3 + 0.1, 1)` is 3.\n const quantum = modifier === null ? step : undefined\n const stepped = quantum !== undefined ? stepValue(next, quantum) : next\n\n // Rounded before the clamp, so that `min` and `max` still have the last\n // word and the value can land on them exactly. Without this the artefact\n // accumulates: with no `step` to round it back, twelve presses of a 0.1\n // modifier amount reach 5.699999999999998 rather than 5.7.\n return clamp(toPrecision(stepped), min, max)\n}\n","import { linearScale, type ValueRange } from '@tremolo-ui/functions'\n\nimport { applyDelta } from './apply-delta'\nimport {\n type InputEventOption,\n type ModifierState,\n type ModifierValue,\n} from './modifiers'\n\n/** Positions probed across the travel. The ends are left out so that the\n * clamp at `min` and `max` cannot be mistaken for a press that does nothing. */\nconst PROBES = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]\n\nconst NONE: ModifierState = {\n shiftKey: false,\n altKey: false,\n ctrlKey: false,\n metaKey: false,\n}\n\nconst MODIFIER_STATE: Record<string, ModifierState> = {\n shift: { ...NONE, shiftKey: true },\n alt: { ...NONE, altKey: true },\n ctrl: { ...NONE, ctrlKey: true },\n meta: { ...NONE, metaKey: true },\n}\n\n/** The held keys that select each entry of a modifier-aware input option. */\nfunction entries(options: ModifierValue<InputEventOption>): ModifierState[] {\n if (Array.isArray(options)) return [NONE]\n return Object.keys(options).map((key) => MODIFIER_STATE[key] ?? NONE)\n}\n\ninterface Outcome {\n /** The press changed the value at least once across the travel. */\n moved: boolean\n /** The change reached the displayed text at least once. */\n visible: boolean\n}\n\n/**\n * Press every entry of `options` at nine points along the travel and report\n * whether anything came of it.\n *\n * Run against `applyDelta` itself rather than against a reading of `step`:\n * the whole point is that the amount, the step and the scale interact, and\n * the pipeline is the only thing that knows how.\n */\nfunction probe(\n options: ModifierValue<InputEventOption>,\n range: ValueRange,\n format?: (value: number) => string,\n): Outcome {\n const { min, max, scale = linearScale } = range\n const outcome: Outcome = { moved: false, visible: false }\n for (const modifiers of entries(options)) {\n for (const position of PROBES) {\n const value = scale.denormalize(position, min, max)\n const up = applyDelta(value, 1, options, range, modifiers)\n const down = applyDelta(value, -1, options, range, modifiers)\n if (up !== value || down !== value) outcome.moved = true\n if (!format) continue\n const shown = format(value)\n if (format(up) !== shown || format(down) !== shown) outcome.visible = true\n }\n }\n return outcome\n}\n\nexport interface CheckStepsOptions {\n /** The component, for the message. */\n component: string\n /** The axis, for a component that has more than one. */\n axis?: string\n /**\n * The range to probe, or `null` to check nothing. An unbounded input has no\n * travel to sample, so `NumberInput` passes `null` when `min` and `max` are\n * not both there.\n */\n range: ValueRange | null\n keyboard?: ModifierValue<InputEventOption> | null\n wheel?: ModifierValue<InputEventOption> | null\n /**\n * How the value is displayed, where the component shows one. Called with\n * probe values only.\n */\n format?: (value: number) => string\n}\n\n/**\n * The warnings to show when a key press or a wheel notch cannot produce a\n * change the user can see. Empty when there is nothing to say.\n *\n * Two settings that are each fine on their own can cancel out, and nothing\n * fails when they do — the control simply sits there:\n *\n * - **`step` coarser than the amount.** `keyboard={['raw', 0.1]}` with\n * `step={1}` rounds every press straight back to where it started\n * - **the display coarser than the amount.** A `format` showing two decimals\n * of a kHz value cannot show a press worth 1 Hz\n *\n * The second is only reported when the press is invisible at *every* point\n * along the travel. A display that rounds is a deliberate choice and is\n * normally right — it is being too coarse everywhere that makes it a mistake.\n *\n * **Meant for development builds only.** It probes the whole travel, so call\n * it behind an inline `process.env.NODE_ENV` check: the bundler then drops\n * the call, and this function and its messages with it.\n */\nexport function checkSteps({\n component,\n axis,\n range,\n keyboard,\n wheel,\n format,\n}: CheckStepsOptions): string[] {\n if (!range || !(range.min < range.max)) return []\n const where = axis ? `${component} (${axis})` : component\n const warnings: string[] = []\n for (const [name, options] of [\n ['keyboard', keyboard],\n ['wheel', wheel],\n ] as const) {\n if (!options) continue\n const { moved, visible } = probe(options, range, format)\n if (!moved) {\n warnings.push(\n `[tremolo-ui] ${where}: \\`${name}\\` cannot move the value.` +\n (range.step !== undefined\n ? ` Each press is smaller than \\`step\\` (${range.step}), so it rounds`\n : ' Each press rounds') +\n ' straight back to where it started.',\n )\n } else if (format && !visible) {\n warnings.push(\n `[tremolo-ui] ${where}: \\`${name}\\` moves the value, but \\`format\\`` +\n ' shows the same text before and after, everywhere in the range.' +\n ' The display is too coarse for it to be seen.',\n )\n }\n }\n return warnings\n}\n","import { type InputEventOption, type ModifierValue } from './modifiers'\n\n/**\n * The keyboard amount used by Knob, NumberInput, Slider, and XYPad by default:\n * 1 per press in the units of the value, and 0.1 with shift held. That is one\n * `step` only while `step` is 1; a coarser `step` rounds 1 straight back, which\n * `checkSteps` warns about.\n *\n * A modifier entry is not snapped to `step`, which is what lets the finer\n * amount move at all — see `applyDelta`.\n */\nexport const DEFAULT_KEYBOARD_OPTIONS: ModifierValue<InputEventOption> = {\n default: ['raw', 1],\n shift: ['raw', 0.1],\n}\n\n/**\n * The wheel amount used by Knob, NumberInput, Slider, and XYPad by default.\n *\n * Browsers turn shift+wheel into horizontal scrolling, which empties `deltaY`\n * and fills `deltaX`, so no modifier is bound here.\n */\nexport const DEFAULT_WHEEL_OPTIONS: ModifierValue<InputEventOption> = ['raw', 1]\n\n/**\n * The drag sensitivity used by value controls by default. Shift makes the\n * same movement cover a tenth of the range, matching the arrow keys.\n */\nexport const DEFAULT_DRAG_SENSITIVITY: ModifierValue<number> = {\n default: 1,\n shift: 0.1,\n}\n","/**\n * Which way an input moves a value, before any amount is applied.\n *\n * `applyDelta` takes the direction as given, since which key or which sign of\n * `deltaY` counts as \"up\" differs per control. The answers are collected here\n * so that every wrapper gives the same one: a knob that turns the other way\n * in one framework would be a bug nobody could see from the code.\n *\n * Two families, by what the control looks like:\n *\n * - **one value** (`Knob`, `Slider`, `NumberInput`): right and up raise it\n * - **a position on screen** (`XYPad`, `PointsEditor`): in screen coordinates,\n * x growing rightwards and y growing downwards, plus which axis moves\n *\n * A control that runs the other way (`reverse`) flips the result itself.\n */\n\n/** One of the four arrow keys, as `KeyboardEvent.key` names it. */\nexport type ArrowKey = 'ArrowRight' | 'ArrowLeft' | 'ArrowUp' | 'ArrowDown'\n\n/** Is `key` one of the four arrow keys? */\nexport function isArrowKey(key: string): key is ArrowKey {\n return (\n key === 'ArrowRight' ||\n key === 'ArrowLeft' ||\n key === 'ArrowUp' ||\n key === 'ArrowDown'\n )\n}\n\n/** A move along one of the two axes of a position on screen. */\nexport interface AxisMove {\n /** 0 = x, 1 = y. */\n axis: 0 | 1\n /** `1` towards the right or the bottom, `-1` towards the left or the top. */\n direction: 1 | -1\n}\n\n/**\n * The direction an arrow key moves a single value: right and up raise it.\n * `null` for any other key.\n */\nexport function arrowKeyDirection(key: string): 1 | -1 | null {\n if (!isArrowKey(key)) return null\n return key === 'ArrowRight' || key === 'ArrowUp' ? 1 : -1\n}\n\n/**\n * The axis and direction an arrow key moves a position on screen. `null` for\n * any other key.\n *\n * The key picks the axis, whichever element inside the control holds the\n * focus: a two-dimensional control is one control to the person moving it.\n */\nexport function arrowKeyMove(key: string): AxisMove | null {\n if (!isArrowKey(key)) return null\n return {\n axis: key === 'ArrowRight' || key === 'ArrowLeft' ? 0 : 1,\n direction: key === 'ArrowLeft' || key === 'ArrowUp' ? -1 : 1,\n }\n}\n\nexport interface WheelDirectionOptions {\n /**\n * Read horizontal scrolling as well, for a control laid out horizontally:\n * scrolling right raises the value. Vertical scrolling still counts when\n * there is no horizontal movement.\n *\n * @default false\n */\n horizontal?: boolean\n}\n\n/**\n * The direction one wheel event moves a single value: scrolling up raises it.\n * `null` when the event carries no movement the control reads.\n */\nexport function wheelDirection(\n event: Pick<WheelEvent, 'deltaX' | 'deltaY'>,\n { horizontal = false }: WheelDirectionOptions = {},\n): 1 | -1 | null {\n if (horizontal && event.deltaX !== 0) return event.deltaX > 0 ? 1 : -1\n if (event.deltaY === 0) return null\n return event.deltaY > 0 ? -1 : 1\n}\n\n/**\n * The axis and direction one wheel event moves a position on screen. `null`\n * when the event carries no movement.\n *\n * Scrolling moves y, and shift switches to x. Browsers turn shift+wheel into\n * horizontal scrolling: `deltaY` comes out empty and `deltaX` carries the\n * movement. Reading whichever axis moved keeps shift working as the x-axis\n * modifier — and picks up a trackpad's own horizontal gesture, which never\n * had a modifier.\n */\nexport function wheelMove(\n event: Pick<WheelEvent, 'deltaX' | 'deltaY' | 'shiftKey'>,\n): AxisMove | null {\n const horizontal = event.deltaX !== 0\n const delta = horizontal ? event.deltaX : event.deltaY\n if (delta === 0) return null\n return {\n axis: horizontal || event.shiftKey ? 0 : 1,\n direction: delta < 0 ? -1 : 1,\n }\n}\n","import { radian, type Scale } from '@tremolo-ui/functions'\n\n/**\n * Width and height of the viewBox a knob is drawn in. The arcs and the thumb\n * are laid out in these units, and the SVG scales them to the knob's size.\n */\nexport const KNOB_VIEWBOX_SIZE = 100\n\nconst center = KNOB_VIEWBOX_SIZE / 2\n\nexport interface KnobAngleOptions {\n value: number\n min: number\n max: number\n scale: Scale\n /** Where the active arc starts from, so that it can grow from the middle. */\n startValue: number\n /** How far the knob turns from `min` to `max`, in degrees. */\n angleRange: number\n}\n\n/**\n * The angles a knob is drawn with, in degrees clockwise from the top.\n *\n * The travel is centred on the top, so `angleRange` of 270 runs from -135 to\n * 135. Derived from the value alone, so a wrapper can call it while rendering.\n */\nexport interface KnobAngles {\n /** The value, normalized to 0..1 along the scale. */\n p: number\n /** Where the travel starts. */\n r1: number\n /** Where the active arc starts: the lower of the value and `startValue`. */\n r2: number\n /** Where the active arc ends: the higher of the value and `startValue`. */\n r3: number\n /** Where the travel ends. */\n r4: number\n}\n\nexport function knobAngles({\n value,\n min,\n max,\n scale,\n startValue,\n angleRange,\n}: KnobAngleOptions): KnobAngles {\n const p = scale.normalize(value, min, max)\n const s = scale.normalize(startValue, min, max)\n const r1 = -angleRange / 2\n const r2 = r1 + Math.min(p, s) * angleRange\n const r3 = r1 + Math.max(p, s) * angleRange\n const r4 = angleRange / 2\n return { p, r1, r2, r3, r4 }\n}\n\n/**\n * The point at `angle` on a circle of `radius` around the centre of the\n * viewBox.\n *\n * The radius depends on the stroke of the line being drawn, which each arc\n * has its own of, so the point is found per arc rather than once for the knob.\n */\nexport function knobArcPoint(angle: number, radius: number) {\n return {\n x: center + radius * Math.cos(radian(angle - 90)),\n y: center + radius * Math.sin(radian(angle - 90)),\n }\n}\n\n/**\n * The radius that keeps a stroke of `strokeWidth` inside the viewBox: half of\n * the stroke falls outside the path it is drawn along.\n */\nexport function knobArcRadius(strokeWidth: number | string | undefined) {\n const width =\n typeof strokeWidth === 'number'\n ? strokeWidth\n : Number.parseFloat(String(strokeWidth))\n return Number.isFinite(width) ? center - width / 2 : center\n}\n\n/** Build an SVG path for an arc, splitting full turns into drawable segments. */\nexport function knobArcPath(\n startAngle: number,\n endAngle: number,\n radius: number,\n) {\n const start = knobArcPoint(startAngle, radius)\n const sweep = endAngle - startAngle\n const segmentCount = Math.max(1, Math.ceil(Math.abs(sweep) / 180))\n const segmentSweep = sweep / segmentCount\n let path = `M ${start.x} ${start.y}`\n for (let i = 1; i <= segmentCount; i += 1) {\n const end = knobArcPoint(startAngle + segmentSweep * i, radius)\n path += ` A ${radius} ${radius} 0 0 ${segmentSweep >= 0 ? 1 : 0} ${end.x} ${end.y}`\n }\n return path\n}\n","/** @private */\nexport const PERMISSION_DENIED = 'PERMISSION_DENIED'\n/** @private */\nexport const NOT_SUPPORTED = 'NOT_SUPPORTED'\n/** @private */\nexport const UNAVAILABLE = 'UNAVAILABLE'\n\n/** @private */\nexport type MIDIAccessError =\n | typeof PERMISSION_DENIED\n | typeof NOT_SUPPORTED\n | typeof UNAVAILABLE\n\nexport type MIDIAccessOptions = {\n /**\n * Ask for system exclusive messages as well.\n *\n * Browsers treat this as a separate, more sensitive permission, so leave it\n * off unless the app actually reads or sends sysex.\n *\n * @default false\n */\n sysex?: boolean\n}\n\nexport type MIDIAccessState = {\n readonly midiAccess: MIDIAccess | null\n readonly error: MIDIAccessError | null\n /**\n * The inputs currently connected, in the order MIDIAccess lists them.\n *\n * Kept up to date as devices are plugged in and unplugged, so a UI listing\n * the devices does not have to watch `statechange` itself.\n */\n readonly inputs: readonly MIDIInput[]\n}\n\nconst INITIAL_STATE: MIDIAccessState = {\n midiAccess: null,\n error: null,\n inputs: [],\n}\n\nexport interface MIDIAccessInstance {\n /** Request MIDI access. Safe to call more than once. */\n request: (options?: MIDIAccessOptions) => void\n getState: () => MIDIAccessState\n /** Snapshot for server side rendering. Always the initial state. */\n getServerState: () => MIDIAccessState\n /** @returns unsubscribe function */\n subscribe: (listener: () => void) => () => void\n destroy: () => void\n}\n\n/**\n * Which of our errors a rejected `requestMIDIAccess()` amounts to.\n *\n * The spec names the reasons, and they need telling apart: a user who said no\n * can say yes on a second ask, while a browser without the API never will.\n */\nfunction toError(reason: unknown): MIDIAccessError {\n const name =\n typeof reason === 'object' && reason !== null && 'name' in reason\n ? String((reason as { name: unknown }).name)\n : ''\n\n if (name === 'SecurityError' || name === 'NotAllowedError') {\n return PERMISSION_DENIED\n }\n if (name === 'NotSupportedError' || name === 'TypeError') {\n return NOT_SUPPORTED\n }\n // AbortError, InvalidStateError, and anything a browser makes up.\n return UNAVAILABLE\n}\n\n/**\n * Request MIDI access in the browser.\n *\n * The returned instance holds the state and notifies subscribers when it changes,\n * so it can be consumed from any framework.\n */\nexport function createMIDIAccess(): MIDIAccessInstance {\n let state = INITIAL_STATE\n let destroyed = false\n let access: MIDIAccess | null = null\n let requestGeneration = 0\n const listeners = new Set<() => void>()\n\n function setState(next: MIDIAccessState) {\n state = next\n for (const listener of listeners) listener()\n }\n\n /**\n * A new array every time, so that `useSyncExternalStore` and its equivalents\n * see the change. The identity of the state object is what they compare.\n */\n function readInputs() {\n return access ? [...access.inputs.values()] : []\n }\n\n function handleStateChange(event?: Event) {\n if (destroyed || !access) return\n if (event && (event as MIDIConnectionEvent).port?.type === 'output') return\n setState({ ...state, inputs: readInputs() })\n }\n\n function request(options: MIDIAccessOptions = {}) {\n if (destroyed) return\n const generation = ++requestGeneration\n if (typeof navigator === 'undefined' || !navigator.requestMIDIAccess) {\n setState({ ...state, error: NOT_SUPPORTED })\n return\n }\n navigator.requestMIDIAccess({ sysex: options.sysex ?? false }).then(\n (granted) => {\n if (destroyed || generation !== requestGeneration) return\n access?.removeEventListener('statechange', handleStateChange)\n access = granted\n granted.addEventListener('statechange', handleStateChange)\n setState({ midiAccess: granted, error: null, inputs: readInputs() })\n },\n (reason: unknown) => {\n if (destroyed || generation !== requestGeneration) return\n setState({ ...state, error: toError(reason) })\n },\n )\n }\n\n return {\n request,\n getState: () => state,\n getServerState: () => INITIAL_STATE,\n subscribe: (listener) => {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n destroy: () => {\n destroyed = true\n requestGeneration += 1\n access?.removeEventListener('statechange', handleStateChange)\n access = null\n listeners.clear()\n },\n }\n}\n","export interface MIDIMessageInstance {\n /** Replace the handler, keeping the listeners in place. */\n update: (onMIDIMessage: (event: MIDIMessageEvent) => void) => void\n destroy: () => void\n}\n\n/**\n * Listen to raw `midimessage` events on every input of a MIDIAccess.\n *\n * The set of inputs is followed rather than sampled: MIDIAccess fires\n * `statechange` when a device is plugged in or unplugged, and the listeners\n * move with it. A keyboard connected after access was granted works without\n * the caller having to rebuild anything.\n *\n * Use this when you need more detail than {@link createMIDIInput} provides.\n */\nexport function createMIDIMessage(\n midiAccess: MIDIAccess | null,\n onMIDIMessage: (event: MIDIMessageEvent) => void,\n): MIDIMessageInstance {\n let handler = onMIDIMessage\n\n /** Inputs this instance currently listens on. */\n const attached = new Set<MIDIInput>()\n\n const listener = (event: Event) => handler(event as MIDIMessageEvent)\n\n function sync() {\n if (!midiAccess) return\n const connected = new Set(midiAccess.inputs.values())\n\n for (const input of connected) {\n if (attached.has(input)) continue\n input.addEventListener('midimessage', listener)\n attached.add(input)\n }\n for (const input of [...attached]) {\n if (connected.has(input)) continue\n input.removeEventListener('midimessage', listener)\n attached.delete(input)\n }\n }\n\n sync()\n midiAccess?.addEventListener('statechange', sync)\n\n return {\n update: (next) => {\n handler = next\n },\n destroy: () => {\n midiAccess?.removeEventListener('statechange', sync)\n for (const input of attached) {\n input.removeEventListener('midimessage', listener)\n }\n attached.clear()\n },\n }\n}\n","import { createMIDIMessage } from './message'\n\n/** Status byte of each channel message, with the channel nibble cleared. */\nconst STATUS = {\n NOTE_OFF: 0x80,\n NOTE_ON: 0x90,\n AFTERTOUCH: 0xa0,\n CONTROL_CHANGE: 0xb0,\n PROGRAM_CHANGE: 0xc0,\n CHANNEL_PRESSURE: 0xd0,\n PITCH_BEND: 0xe0,\n}\n\n/**\n * Centre of the 14-bit pitch bend range: no bend.\n *\n * The range is not symmetric — 0 is 8192 below centre and 16383 is 8191 above\n * — so a wheel at rest reports exactly this rather than half of the maximum.\n */\nexport const PITCH_BEND_CENTER = 8192\n\n/**\n * Every handler is given the channel last, as 0-15. MIDI channels are written\n * 1-16 on hardware, so add one before showing it to anyone.\n */\nexport type MIDIInputHandlers = {\n onNoteOnEvent?: (note: number, velocity: number, channel: number) => void\n onNoteOffEvent?: (note: number, channel: number) => void\n /**\n * The 14-bit bend, 0-16383, centred at {@link PITCH_BEND_CENTER}.\n *\n * The two data bytes are little-endian — the first carries the low 7 bits —\n * which is the other way round from every other message.\n */\n onPitchBendEvent?: (value: number, channel: number) => void\n /** `controller` is the CC number, `value` is 0-127. */\n onControlChangeEvent?: (\n controller: number,\n value: number,\n channel: number,\n ) => void\n onProgramChangeEvent?: (program: number, channel: number) => void\n /** Pressure for one held note (polyphonic aftertouch). */\n onAftertouchEvent?: (note: number, pressure: number, channel: number) => void\n /** Pressure for the whole channel, sent by keyboards with one sensor. */\n onChannelPressureEvent?: (pressure: number, channel: number) => void\n}\n\nexport interface MIDIInputInstance {\n /** Replace the handlers, keeping the listeners in place. */\n update: (handlers: MIDIInputHandlers) => void\n destroy: () => void\n}\n\n/**\n * Handle the channel voice messages of every connected input. To be used with\n * {@link createMIDIAccess}. Internally uses {@link createMIDIMessage}, so\n * devices plugged in later are picked up.\n *\n * System messages (clock, sysex, and the rest of `0xf0`-`0xff`) are not\n * decoded here; reach for {@link createMIDIMessage} for those.\n */\nexport function createMIDIInput(\n midiAccess: MIDIAccess | null,\n handlers: MIDIInputHandlers,\n): MIDIInputInstance {\n let current = handlers\n\n const message = createMIDIMessage(midiAccess, (event) => {\n if (!event.data || event.data.length === 0) return\n\n const status = event.data[0]\n // 0xf0 and above are system messages, which carry no channel.\n if (status >= 0xf0) return\n\n const kind = status & 0xf0\n const channel = status & 0x0f\n const first = event.data[1] ?? 0\n const second = event.data[2] ?? 0\n\n switch (kind) {\n case STATUS.NOTE_OFF:\n current.onNoteOffEvent?.(first, channel)\n break\n case STATUS.NOTE_ON:\n // A note on with zero velocity is how most devices say note off.\n if (second === 0) current.onNoteOffEvent?.(first, channel)\n else current.onNoteOnEvent?.(first, second, channel)\n break\n case STATUS.AFTERTOUCH:\n current.onAftertouchEvent?.(first, second, channel)\n break\n case STATUS.CONTROL_CHANGE:\n current.onControlChangeEvent?.(first, second, channel)\n break\n case STATUS.PROGRAM_CHANGE:\n current.onProgramChangeEvent?.(first, channel)\n break\n case STATUS.CHANNEL_PRESSURE:\n current.onChannelPressureEvent?.(first, channel)\n break\n case STATUS.PITCH_BEND:\n current.onPitchBendEvent?.((second << 7) | first, channel)\n break\n }\n })\n\n return {\n update: (next) => {\n current = next\n },\n destroy: message.destroy,\n }\n}\n","export type DragState = {\n /** Total movement from the drag start, in screen coordinates. */\n x: number\n y: number\n /** Movement since the previous event, in screen coordinates. */\n deltaX: number\n deltaY: number\n /** Pointer position in viewport coordinates. */\n clientX: number\n clientY: number\n /**\n * Which pointer this is. Always the same value for one drag; with\n * {@link DragOptions.multiPointer} it tells concurrent drags apart.\n */\n pointerId: number\n event: PointerEvent\n}\n\nexport type DragOptions = {\n /**\n * Minimum movement in pixels before `onDrag` fires. Movement below it is\n * carried over to the next event rather than discarded, so a slow drag still\n * reports once it adds up.\n *\n * Prevents `onDrag` from firing on, for example, a double click.\n *\n * @default 0\n */\n threshold?: number\n\n /**\n * CSS cursor to show while dragging. Applied to the element itself: pointer\n * capture keeps it in effect even once the pointer leaves the element, so\n * there is no need to touch the document.\n *\n * With {@link DragOptions.multiPointer} it is set for the first pointer and\n * restored once the last one is up.\n */\n cursor?: string\n\n /**\n * Decide whether a pointerdown starts a drag at all.\n *\n * Checked before anything else — **before the pointer is captured** — so\n * declining here leaves the whole gesture to whatever else is listening.\n * Deciding later would be too late: the capture has already been taken from\n * the element that was going to handle it.\n *\n * The use for it is a drag on a container that also holds draggable things\n * of its own, such as a rubber-band selection that must not begin on top of\n * one of the objects it would select.\n */\n shouldStart?: (event: PointerEvent) => boolean\n\n /**\n * Hide the pointer and read its movement directly, instead of following it\n * around the screen.\n *\n * A relative drag — a knob, a stepper — does not care where the pointer is,\n * only how far it moved, and letting it wander has two costs: the cursor\n * ends up far from what it is holding, and **the drag stops at the edge of\n * the screen**, where the operating system pins the pointer and the\n * coordinates stop changing. A fine drag reaches that edge quickly.\n *\n * **Not for a drag whose value is the position pointed at** — anything on\n * `elementMapping`. `clientX` / `clientY` freeze while the pointer is\n * locked, so there is no position left to read.\n *\n * The request needs a user gesture, which a pointerdown is, but it can still\n * be refused; the drag then carries on as an ordinary one. Read on\n * pointerdown, so `update()` reaches the next drag rather than the current.\n *\n * @default false\n */\n pointerLock?: boolean\n\n /**\n * Track every pointer that goes down on the element, rather than only the\n * first. Each one gets its own `onDragStart` / `onDrag` / `onDragEnd` and\n * carries its own totals; {@link DragState.pointerId} says which is which.\n *\n * Fixed for the lifetime of the instance: switching part way through a drag\n * has no meaning, so `update()` ignores it.\n *\n * @default false\n */\n multiPointer?: boolean\n\n onDragStart?: (state: DragState) => void\n onDrag?: (state: DragState) => void\n /**\n * Called exactly once for every drag that starts, whether tracking ends by\n * pointer release, cancellation, capture or lock loss, or destruction.\n */\n onDragEnd?: (state: DragState) => void\n}\n\nexport interface DragInstance {\n /**\n * Replace the given options. Lets a wrapper feed fresh handlers in without\n * tearing down the listeners, which would abort a drag in progress.\n *\n * `multiPointer` is fixed for the lifetime of the instance and is ignored\n * here.\n */\n update: (options: DragOptions) => void\n /** End any active drags before removing the instance. */\n destroy: () => void\n}\n\n/**\n * Applied to the element for the lifetime of the instance.\n *\n * `touch-action` stops touch dragging from scrolling the page. That alone makes\n * the browser treat a long press as the start of a text selection, so selection\n * and the iOS callout are suppressed here too.\n */\nconst MANAGED_STYLES = [\n ['touch-action', 'none'],\n ['user-select', 'none'],\n ['-webkit-user-select', 'none'],\n ['-webkit-touch-callout', 'none'],\n] as const\n\ntype ManagedStyleState = {\n count: number\n previous: Map<string, { value: string; priority: string }>\n}\n\nconst managedStyleStates = new WeakMap<Element, ManagedStyleState>()\n\ntype CaptureTarget = {\n requestPointerLock?: () => unknown\n setPointerCapture?: (pointerId: number) => void\n releasePointerCapture?: (pointerId: number) => void\n hasPointerCapture?: (pointerId: number) => boolean\n}\n\n/** What one pointer needs to report its own movement. */\ntype PointerState = {\n /** Where the listeners for this pointer live. */\n moveTarget: EventTarget\n startX: number\n startY: number\n lastX: number\n lastY: number\n /**\n * Where the travel stood when the pointer lock took effect, and how far it\n * has moved since. Screen coordinates stop changing under the lock, so from\n * that point the movement of each event is added up instead.\n *\n * The base is taken when the lock engages rather than on pointerdown: the\n * request is asynchronous, and whatever movement happened while it was in\n * flight was measured the ordinary way.\n */\n lockBaseX?: number\n lockBaseY?: number\n lockMoveX: number\n lockMoveY: number\n /** The most recent event, to end the drag with when the lock is lost. */\n lastEvent: PointerEvent\n}\n\n/**\n * Track a pointer drag on an element.\n *\n * Uses Pointer Events only, and pointer capture so that the drag keeps working\n * once the pointer leaves the element. For the lifetime of the instance the\n * element gets `touch-action: none` so that touch dragging does not scroll the\n * page, plus `user-select: none` so that a long press does not start a text\n * selection instead.\n *\n * One pointer at a time by default; see {@link DragOptions.multiPointer}.\n */\nexport function createDrag(\n element: Element,\n options: DragOptions = {},\n): DragInstance {\n let opts = options\n const multiPointer = options.multiPointer ?? false\n const capture = element as CaptureTarget\n const style = (element as Partial<HTMLElement>).style\n\n let managedStyles = managedStyleStates.get(element)\n if (style) {\n if (managedStyles) {\n managedStyles.count += 1\n } else {\n const previous = new Map<string, { value: string; priority: string }>()\n for (const [property, value] of MANAGED_STYLES) {\n previous.set(property, {\n value: style.getPropertyValue(property),\n priority: style.getPropertyPriority(property),\n })\n style.setProperty(property, value)\n }\n managedStyles = { count: 1, previous }\n managedStyleStates.set(element, managedStyles)\n }\n }\n\n const pointers = new Map<number, PointerState>()\n /**\n * How many pointers each target carries. Adding the same listener twice is a\n * no-op and removing it once removes it for good, so a target is subscribed\n * to while at least one pointer is on it and no longer.\n */\n const targets = new Map<EventTarget, number>()\n let previousCursor: string | undefined\n /** The pointer that asked for the lock, while it is still down. */\n let lockedPointerId: number | null = null\n let lockRequest = 0\n let destroyed = false\n\n function state(\n event: PointerEvent,\n pointer: PointerState,\n deltaX: number,\n deltaY: number,\n ): DragState {\n return {\n x:\n pointer.lockBaseX !== undefined\n ? pointer.lockBaseX + pointer.lockMoveX\n : event.screenX - pointer.startX,\n y:\n pointer.lockBaseY !== undefined\n ? pointer.lockBaseY + pointer.lockMoveY\n : event.screenY - pointer.startY,\n deltaX,\n deltaY,\n clientX: event.clientX,\n clientY: event.clientY,\n pointerId: event.pointerId,\n event,\n }\n }\n\n /**\n * `user-select: none` only makes the element's own text unselectable; the\n * browser still starts a selection on long press and grabs whatever text it\n * finds nearby. Cancelling the selection outright is what actually stops it.\n */\n function preventSelectStart(event: Event) {\n event.preventDefault()\n }\n\n function retainTarget(target: EventTarget) {\n const count = targets.get(target) ?? 0\n if (count === 0) {\n target.addEventListener('pointermove', handlePointerMove)\n target.addEventListener('pointerup', handlePointerUp)\n target.addEventListener('pointercancel', handlePointerUp)\n if (target === element) {\n target.addEventListener('lostpointercapture', handleLostPointerCapture)\n }\n }\n targets.set(target, count + 1)\n }\n\n function releaseTarget(target: EventTarget) {\n const count = targets.get(target) ?? 0\n if (count > 1) {\n targets.set(target, count - 1)\n return\n }\n target.removeEventListener('pointermove', handlePointerMove)\n target.removeEventListener('pointerup', handlePointerUp)\n target.removeEventListener('pointercancel', handlePointerUp)\n if (target === element) {\n target.removeEventListener('lostpointercapture', handleLostPointerCapture)\n }\n targets.delete(target)\n }\n\n function handlePointerDown(event: Event) {\n const pointerEvent = event as PointerEvent\n if (pointerEvent.button !== 0) return\n const pointerId = pointerEvent.pointerId\n // Without multiPointer only one pointer drives the drag; ignore the rest.\n if (pointers.has(pointerId)) return\n if (!multiPointer && pointers.size > 0) return\n\n if (opts.shouldStart && !opts.shouldStart(pointerEvent)) return\n\n const isFirst = pointers.size === 0\n if (isFirst && opts.cursor && style) {\n previousCursor = style.cursor\n style.cursor = opts.cursor\n }\n\n capture.setPointerCapture?.(pointerId)\n // With pointer capture the element receives the rest of the gesture.\n // Without it (older engines, jsdom) fall back to the window.\n const moveTarget =\n capture.hasPointerCapture?.(pointerId) === true\n ? element\n : (globalThis.window ?? element)\n\n const pointer: PointerState = {\n moveTarget,\n startX: pointerEvent.screenX,\n startY: pointerEvent.screenY,\n lastX: pointerEvent.screenX,\n lastY: pointerEvent.screenY,\n lockMoveX: 0,\n lockMoveY: 0,\n lastEvent: pointerEvent,\n }\n pointers.set(pointerId, pointer)\n\n retainTarget(moveTarget)\n if (isFirst) {\n globalThis.document?.addEventListener('selectstart', preventSelectStart)\n }\n\n // One pointer can be locked, so the first one takes it.\n if (isFirst && opts.pointerLock) {\n const requestId = ++lockRequest\n lockedPointerId = pointerId\n globalThis.document?.addEventListener(\n 'pointerlockchange',\n handleLockChange,\n )\n try {\n // Newer engines return a promise that rejects; older ones fire\n // `pointerlockerror` instead. Either way a refusal only means the drag\n // stays an ordinary one, so nothing here has to act on it.\n const request = capture.requestPointerLock?.() as\n | Promise<void>\n | undefined\n request?.then?.(\n () => {\n if (\n requestId === lockRequest &&\n (!pointers.has(pointerId) || destroyed) &&\n globalThis.document?.pointerLockElement === element\n ) {\n globalThis.document.exitPointerLock?.()\n }\n },\n () => {},\n )\n } catch {\n // requestPointerLock threw synchronously; same story.\n }\n }\n\n opts.onDragStart?.(state(pointerEvent, pointer, 0, 0))\n }\n\n function handlePointerMove(event: Event) {\n const pointerEvent = event as PointerEvent\n const pointer = pointers.get(pointerEvent.pointerId)\n if (!pointer) return\n\n // Under the lock the screen position no longer moves, so the movement the\n // event reports is the only thing left to read.\n const locked = pointer.lockBaseX !== undefined\n const deltaX = locked\n ? (pointerEvent.movementX ?? 0)\n : pointerEvent.screenX - pointer.lastX\n const deltaY = locked\n ? (pointerEvent.movementY ?? 0)\n : pointerEvent.screenY - pointer.lastY\n\n // Movement below the threshold accumulates until it crosses it. Dropping it\n // instead would swallow a slow drag entirely: pointer coordinates are\n // fractional, so each event can move less than a pixel and never report.\n const threshold = opts.threshold ?? 0\n if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) return\n\n pointer.lastX = pointerEvent.screenX\n pointer.lastY = pointerEvent.screenY\n pointer.lastEvent = pointerEvent\n if (locked) {\n pointer.lockMoveX += deltaX\n pointer.lockMoveY += deltaY\n }\n\n opts.onDrag?.(state(pointerEvent, pointer, deltaX, deltaY))\n }\n\n function handleLockChange() {\n if (lockedPointerId === null) return\n const pointer = pointers.get(lockedPointerId)\n if (!pointer) return\n\n if (globalThis.document?.pointerLockElement === element) {\n // Engaged. Whatever moved while the request was in flight was measured\n // the ordinary way, so the travel so far becomes the base and the\n // per-event movement is added to it from here.\n pointer.lockBaseX = pointer.lastX - pointer.startX\n pointer.lockBaseY = pointer.lastY - pointer.startY\n pointer.lockMoveX = 0\n pointer.lockMoveY = 0\n return\n }\n\n // Lost without the pointer coming up: Esc, a tab switch, leaving\n // fullscreen. No pointerup is coming, so the drag ends here rather than\n // hanging on with a pointer nobody can see.\n if (pointer.lockBaseX === undefined) return\n finishDrag(lockedPointerId)\n }\n\n function handlePointerUp(event: Event) {\n const pointerEvent = event as PointerEvent\n finishDrag(pointerEvent.pointerId, pointerEvent)\n }\n\n function handleLostPointerCapture(event: Event) {\n const pointerEvent = event as PointerEvent\n finishDrag(pointerEvent.pointerId)\n }\n\n function finishDrag(pointerId: number, event?: PointerEvent) {\n const pointer = pointers.get(pointerId)\n if (!pointer) return\n\n const finalState = event\n ? state(\n event,\n pointer,\n event.screenX - pointer.lastX,\n event.screenY - pointer.lastY,\n )\n : state(pointer.lastEvent, pointer, 0, 0)\n\n stopTracking(pointerId)\n opts.onDragEnd?.(finalState)\n }\n\n function stopTracking(pointerId: number) {\n const pointer = pointers.get(pointerId)\n if (!pointer) return\n\n pointers.delete(pointerId)\n releaseTarget(pointer.moveTarget)\n try {\n if (capture.hasPointerCapture?.(pointerId) === true) {\n capture.releasePointerCapture?.(pointerId)\n }\n } catch {\n // The capture may disappear between checking and releasing it. Tracking\n // is already cleared, so the drag still ends normally.\n }\n\n if (lockedPointerId === pointerId) {\n lockedPointerId = null\n const document = globalThis.document\n document?.removeEventListener('pointerlockchange', handleLockChange)\n // Already gone when the lock is what ended the drag.\n if (document?.pointerLockElement === element) document.exitPointerLock?.()\n }\n\n if (pointers.size > 0) return\n\n globalThis.document?.removeEventListener('selectstart', preventSelectStart)\n if (previousCursor !== undefined && style) {\n style.cursor = previousCursor\n previousCursor = undefined\n }\n }\n\n element.addEventListener('pointerdown', handlePointerDown)\n\n return {\n update: (next) => {\n opts = { ...opts, ...next, multiPointer }\n },\n destroy: () => {\n if (destroyed) return\n destroyed = true\n element.removeEventListener('pointerdown', handlePointerDown)\n for (const pointerId of [...pointers.keys()]) finishDrag(pointerId)\n if (style && managedStyles && --managedStyles.count === 0) {\n for (const [property] of MANAGED_STYLES) {\n const previous = managedStyles.previous.get(property)\n if (previous?.value) {\n style.setProperty(property, previous.value, previous.priority)\n } else {\n style.removeProperty(property)\n }\n }\n managedStyleStates.delete(element)\n }\n },\n }\n}\n","import { type ValueRange } from '@tremolo-ui/functions'\n\nimport { applyDelta } from '../input/apply-delta'\nimport { DEFAULT_DRAG_SENSITIVITY } from '../input/defaults'\nimport {\n mapModifier,\n selectModifier,\n type InputEventOption,\n type ModifierValue,\n} from '../input/modifiers'\nimport { createDrag } from '../pointer/drag'\n\nexport interface StepperDragOptions {\n /** The value now, read when the drag starts moving it. */\n getValue: () => number\n /**\n * The range the value moves across. Its `step` is what one step of the drag\n * is worth; see `numberInputRanges` for the one a number input uses.\n */\n range: ValueRange\n /**\n * How many pixels of vertical movement make one step.\n *\n * @default 1\n */\n pixels?: number\n /**\n * How much a step is worth, per modifier key: `0.1` makes the same movement\n * count a tenth as much. Pressing or releasing the key mid-drag does not\n * move the value.\n *\n * @default { default: 1, shift: 0.1 }\n */\n sensitivity?: ModifierValue<number>\n /** Hide the pointer and keep it from hitting the edge of the screen. */\n pointerLock?: boolean\n /** Called with the new value whenever the drag moves it. */\n onChange: (value: number) => void\n}\n\nexport interface StepperDragInstance {\n /** Replace the given options. `pointerLock` reaches the next drag. */\n update: (options: Partial<StepperDragOptions>) => void\n /**\n * Whether the drag in progress has moved the value. A stepper button's\n * press-and-hold repeat stands down once it has, so the value is not moved\n * twice.\n */\n moved: () => boolean\n destroy: () => void\n}\n\n/**\n * Drag up and down on the steppers of a number input to move its value, one\n * `step` every `pixels` — up raises it, as on a knob.\n *\n * Counted from where the drag started moving rather than added up per\n * event, so rounding cannot accumulate. The start is taken on the first\n * move, not on pointerdown: a stepper button acts on pointerdown, so by then\n * the value may already have been nudged once, and the drag carries on from\n * there.\n */\nexport function createStepperDrag(\n element: Element,\n options: StepperDragOptions,\n): StepperDragInstance {\n let opts = options\n let origin: { y: number; value: number } | null = null\n let moved = false\n /**\n * Which sensitivity the drag is counting at, and where the previous event\n * was — a key produces no pointer event of its own, so a change is only\n * seen on the next move and has to be dated back to the one before it.\n */\n let factor = 1\n let previousY = 0\n\n const drag = createDrag(element, {\n threshold: 1,\n cursor: 'ns-resize',\n pointerLock: opts.pointerLock,\n onDragStart: (state) => {\n origin = null\n moved = false\n factor = selectModifier(\n opts.sensitivity ?? DEFAULT_DRAG_SENSITIVITY,\n state.event,\n ).value\n },\n onDrag: (state) => {\n const { y } = state\n if (!origin) {\n origin = { y, value: opts.getValue() }\n previousY = y\n return\n }\n\n const sensitivity = opts.sensitivity ?? DEFAULT_DRAG_SENSITIVITY\n // Pressing or releasing the key mid-drag must not move the value, so the\n // travel so far is folded into the origin and measuring starts again\n // from the previous event: that event's own distance belongs to the new\n // sensitivity.\n const next = selectModifier(sensitivity, state.event).value\n if (next !== factor) {\n origin = { y: previousY, value: opts.getValue() }\n factor = next\n }\n previousY = y\n\n const steps = Math.round(-(y - origin.y) / (opts.pixels ?? 1))\n if (steps === 0) return\n moved = true\n\n // The sensitivity as an amount per step, carried over as a modifier map\n // rather than resolved here: that keeps `step` out of the pipeline for\n // a modifier entry, since naming one is a request to move off the grid.\n const step = opts.range.step ?? 1\n const amounts = mapModifier(sensitivity, (f): InputEventOption => [\n 'raw',\n step * f,\n ])\n opts.onChange(\n applyDelta(origin.value, steps, amounts, opts.range, state.event),\n )\n },\n onDragEnd: () => {\n moved = false\n },\n })\n\n return {\n update: (next) => {\n opts = { ...opts, ...next }\n if ('pointerLock' in next) drag.update({ pointerLock: opts.pointerLock })\n },\n moved: () => moved,\n destroy: () => drag.destroy(),\n }\n}\n","import { type Scale, type ValueRange } from '@tremolo-ui/functions'\n\nimport { applyDelta } from '../input/apply-delta'\nimport {\n selectModifier,\n type InputEventOption,\n type ModifierState,\n type ModifierValue,\n} from '../input/modifiers'\n\n/**\n * The value a number input edits, and how far it may go.\n *\n * Unlike a slider, either end may be left open, and `clampValue: false` lets\n * the value past the ends that are set.\n */\nexport interface NumberInputValueOptions {\n min?: number\n max?: number\n step?: number\n scale?: Scale\n /**\n * Keep the value between `min` and `max`.\n *\n * @default true\n */\n clampValue?: boolean\n}\n\n/** The ranges {@link nudgeNumberInput} moves a value across. */\nexport interface NumberInputRanges {\n /** For a `normalized` amount, which needs a finite span to take a share of. */\n normalized: ValueRange\n /** For a `raw` amount, which does not. */\n raw: ValueRange\n}\n\n/**\n * The ranges a number input moves its value across, with the open ends\n * filled in.\n *\n * A normalized amount needs a finite span even when an end is unbounded or\n * clamping is off. Safe integers provide one without overflowing the span a\n * scale calculates. A raw amount needs no span, so its open ends can cover\n * every finite number instead of stopping at the safe-integer range.\n */\nexport function numberInputRanges({\n min,\n max,\n step,\n scale,\n clampValue = true,\n}: NumberInputValueOptions): NumberInputRanges {\n const lo = clampValue ? min : undefined\n const hi = clampValue ? max : undefined\n return {\n normalized: {\n min: lo ?? Number.MIN_SAFE_INTEGER,\n max: hi ?? Number.MAX_SAFE_INTEGER,\n step,\n scale,\n },\n raw: {\n min: lo ?? -Number.MAX_VALUE,\n max: hi ?? Number.MAX_VALUE,\n step,\n scale,\n },\n }\n}\n\n/**\n * Move a number input's value by one press of a key, a wheel notch or a\n * stepper, picking the range that suits the kind of amount. See `applyDelta`.\n */\nexport function nudgeNumberInput(\n value: number,\n direction: number,\n options: ModifierValue<InputEventOption>,\n ranges: NumberInputRanges,\n modifiers?: ModifierState,\n): number {\n const [mode] = selectModifier(options, modifiers).value\n return applyDelta(\n value,\n direction,\n options,\n mode === 'raw' ? ranges.raw : ranges.normalized,\n modifiers,\n )\n}\n\n/**\n * Where the value stands against the ends: whether it can go no further\n * down or up, and whether it lies outside them — which only an unclamped\n * input, or a value set from outside, can do.\n */\nexport function numberInputBounds(\n value: number,\n { min, max, clampValue = true }: NumberInputValueOptions,\n) {\n return {\n atMin: clampValue && min !== undefined && value <= min,\n atMax: clampValue && max !== undefined && value >= max,\n outOfRange:\n (min !== undefined && value < min) || (max !== undefined && value > max),\n }\n}\n\n/**\n * The value typed text commits to, or `null` when there is no number in it.\n *\n * Text with no number is not a value: the input should go back to what it\n * was showing rather than commit a zero the user never typed. What is read\n * is clamped here and not while typing, since clamping as the user types\n * would make \"1500\" impossible to enter into an input whose max is 100.\n */\nexport function commitNumberInputText(\n text: string,\n parse: (text: string) => number,\n { min, max, clampValue = true }: NumberInputValueOptions,\n): number | null {\n const parsed = parse(text)\n if (!Number.isFinite(parsed)) return null\n let committed = parsed\n if (clampValue && min !== undefined) committed = Math.max(committed, min)\n if (clampValue && max !== undefined) committed = Math.min(committed, max)\n return committed\n}\n","/**\n * Reading a number out of the text of a number input, and keeping the caret\n * in place while the number under it changes.\n *\n * The text is whatever `format` made of the value — `\"440 Hz\"`, `\"-6.0 dB\"` —\n * or a half-typed entry, so none of this assumes the text is a number alone.\n */\n\n/** The signs a number can carry. U+2212 is what some `Intl.NumberFormat` locales write. */\nconst SIGNS = '+-\\u2212'\n\n/** Where the number is in the text; the rest, on either side, is the unit. */\nexport interface NumberSpan {\n start: number\n end: number\n}\n\n/**\n * Where the number is in the text: from its first digit to its last, with the\n * sign and the decimal point in front of it. Whatever is left on either side\n * is taken for the unit, so it does not matter whether a space separates them,\n * or what the number looks like — `+6.0`, `1e+21`, `1,000` and `1:30` are each\n * one number. `null` when the text has no digit at all.\n */\nexport function numberSpan(text: string): NumberSpan | null {\n const first = text.search(/\\d/)\n if (first === -1) return null\n let start = first\n if (text[start - 1] === '.') start -= 1\n if (start > 0 && SIGNS.includes(text[start - 1])) start -= 1\n return { start, end: text.search(/\\d\\D*$/) + 1 }\n}\n\n/** A plain number, and nothing else: no grouping, no other separators. */\nconst PLAIN_NUMBER =\n /^[+\\-\\u2212]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+\\-\\u2212]?\\d+)?$/\n\n/**\n * The number in the text, with the unit after it ignored: the default `parse`\n * of a number input.\n *\n * `NaN`, which leaves the value alone, whenever the number cannot be read\n * safely, rather than a part of it: text with no number, a number that is not\n * a plain one (`1,000` would otherwise read as 1, and `1:30` as 1), and text\n * with something in front of the number, which can change what it means (the\n * `L` of a pan reading `L 30`). A `format` that writes any of those needs its\n * own `parse`.\n */\nexport function parseNumberText(text: string): number {\n const span = numberSpan(text)\n if (!span || text.slice(0, span.start).trim() !== '') return NaN\n const number = text.slice(span.start, span.end)\n return PLAIN_NUMBER.test(number)\n ? Number(number.replace(/\\u2212/g, '-'))\n : NaN\n}\n\n/**\n * The index the caret is measured against: the decimal point, or where one\n * would go if the number has none — in front of an exponent, if there is one.\n *\n * Measuring from an end instead would slide the caret across a digit whenever\n * the number changed length — `9.9` to `10.0` gains a character in front, `10`\n * to `9` loses one, and so does `9e+9` to `1e+10` behind — which is exactly\n * what stepping does.\n */\nfunction decimalAnchor(text: string, span: NumberSpan) {\n const number = text.slice(span.start, span.end)\n const exponent = number.search(/[eE][+\\-\\u2212]?\\d/)\n const mantissa = exponent === -1 ? number : number.slice(0, exponent)\n const dot = mantissa.indexOf('.')\n return span.start + (dot === -1 ? mantissa.length : dot)\n}\n\nconst NO_NUMBER: NumberSpan = { start: 0, end: 0 }\n\n/**\n * Where the caret is, relative to the decimal point, so that it can be put\n * back at the same digit once the value has changed. See\n * {@link caretAtDecimalOffset}.\n */\nexport function caretDecimalOffset(text: string, caret: number): number {\n return caret - decimalAnchor(text, numberSpan(text) ?? NO_NUMBER)\n}\n\n/**\n * The caret position `offset` characters from the decimal point of the new\n * text, kept within the number.\n *\n * @example\n * // The caret sits in front of the point of \"9.9\" when ArrowUp turns it\n * // into \"10.0\"\n * const offset = caretDecimalOffset('9.9', 1) // 0\n * caretAtDecimalOffset('10.0', offset) // 2: still in front of the point\n */\nexport function caretAtDecimalOffset(text: string, offset: number): number {\n const span = numberSpan(text) ?? NO_NUMBER\n const place = decimalAnchor(text, span) + offset\n return Math.max(span.start, Math.min(place, span.end))\n}\n","/**\n * The `update()` argument that leaves an instance with exactly `next` as its\n * options.\n *\n * `update()` merges into what the instance has, which suits a wrapper that\n * pushes one setting at a time. A wrapper handed the whole new set instead —\n * a Svelte action, a Vue composable — has to clear what the new set no longer\n * carries, or a handler taken away, or an option left to its default, keeps\n * working.\n *\n * @example\n * instance.update(replaceOptions(previous, next))\n */\nexport function replaceOptions<T extends object>(\n previous: T | undefined,\n next: T | undefined,\n): Partial<T> {\n const cleared = Object.fromEntries(\n Object.keys(previous ?? {}).map((key) => [key, undefined]),\n ) as Partial<T>\n return { ...cleared, ...next }\n}\n","import {\n isBlackKey,\n isWhiteKey,\n noteKey,\n noteKeys,\n type NoteKey,\n} from '@tremolo-ui/functions'\n\nexport type NoteRange = {\n first: number\n last: number\n}\n\n/**\n * `[noteRange.first, noteRange.first + 1, ..., noteRange.last]`\n */\nexport function getNoteRangeArray(noteRange: NoteRange): number[] {\n return Array.from(\n { length: noteRange.last - noteRange.first + 1 },\n (_, i) => i + noteRange.first,\n )\n}\n\n/**\n * The geometry of a drawn keyboard.\n *\n * One description is shared by the drawing and the hit testing, so a key cannot\n * be drawn somewhere other than where it responds.\n */\nexport interface PianoLayout {\n noteRange: NoteRange\n\n /** Width of a white key, excluding {@link PianoLayout.keyGap}. */\n whiteKeyWidth: number\n\n /**\n * Space between two white keys. Part of the slot a white key occupies, so it\n * still belongs to one of the keys for the purpose of hit testing.\n *\n * @default 1\n */\n keyGap?: number\n\n /**\n * Width of a black key, as a fraction of {@link PianoLayout.whiteKeyWidth}.\n *\n * @default 0.65\n */\n blackKeyWidthRatio?: number\n\n /**\n * Height of a black key, as a fraction of the height of the keyboard.\n *\n * @default 0.6\n */\n blackKeyHeightRatio?: number\n}\n\nconst DEFAULT_KEY_GAP = 1\nconst DEFAULT_BLACK_KEY_WIDTH_RATIO = 0.65\nconst DEFAULT_BLACK_KEY_HEIGHT_RATIO = 0.6\n\n/**\n * How many white keys sit at or before each pitch class, counting from C.\n *\n * A black key shares the number of the white key to its left plus one, which\n * puts it on the boundary between the two; {@link notePosition} then shifts it\n * back by half its width to centre it there.\n */\nconst whiteKeysBefore: Record<NoteKey, number> = {\n C: 0,\n 'C#': 1,\n D: 1,\n 'D#': 2,\n E: 2,\n F: 3,\n 'F#': 4,\n G: 4,\n 'G#': 5,\n A: 5,\n 'A#': 6,\n B: 6,\n}\n\n/** Width of a black key in pixels. */\nexport function blackKeyWidth(layout: PianoLayout): number {\n return (\n layout.whiteKeyWidth *\n (layout.blackKeyWidthRatio ?? DEFAULT_BLACK_KEY_WIDTH_RATIO)\n )\n}\n\nfunction rawNotePosition(note: number, layout: PianoLayout): number {\n const slot = layout.whiteKeyWidth + (layout.keyGap ?? DEFAULT_KEY_GAP)\n const target = noteKey(note)\n const first = noteKey(layout.noteRange.first)\n\n const octave = Math.floor((note - layout.noteRange.first) / 12)\n const octaveOffset =\n noteKeys.indexOf(first) > noteKeys.indexOf(target) ? 1 : 0\n const whiteKeysIn =\n whiteKeysBefore[target] -\n whiteKeysBefore[first] +\n (octave + octaveOffset) * 7\n\n return isBlackKey(note)\n ? whiteKeysIn * slot - blackKeyWidth(layout) / 2\n : whiteKeysIn * slot\n}\n\nfunction pianoBounds(layout: PianoLayout) {\n const notes = getNoteRangeArray(layout.noteRange)\n if (notes.length === 0) return { left: 0, right: 0 }\n\n let left = Infinity\n let right = -Infinity\n const whiteWidth = layout.whiteKeyWidth + (layout.keyGap ?? DEFAULT_KEY_GAP)\n for (const note of notes) {\n const noteLeft = rawNotePosition(note, layout)\n const width = isBlackKey(note) ? blackKeyWidth(layout) : whiteWidth\n left = Math.min(left, noteLeft)\n right = Math.max(right, noteLeft + width)\n }\n return { left, right }\n}\n\n/** Width of the whole keyboard in pixels. */\nexport function pianoWidth(layout: PianoLayout): number {\n const { left, right } = pianoBounds(layout)\n return right - left\n}\n\n/**\n * Offset of the left edge of a key from the left edge of the keyboard, in\n * pixels.\n *\n * Notes outside `noteRange` are placed too, so the value is negative below\n * `noteRange.first`.\n */\nexport function notePosition(note: number, layout: PianoLayout): number {\n return rawNotePosition(note, layout) - pianoBounds(layout).left\n}\n\n/**\n * The note drawn at a point, or null where there is none.\n *\n * Black keys are tested first, so they win where they overlap a white one. A\n * white key covers its gap as well as its width, so the whole width of the\n * keyboard belongs to some key and a click cannot fall between two.\n *\n * @param x offset from the left edge of the keyboard, in pixels\n * @param y offset from its top edge, in pixels\n * @param height height of the keyboard, in pixels\n */\nexport function noteAt(\n x: number,\n y: number,\n height: number,\n layout: PianoLayout,\n): number | null {\n if (x < 0 || x >= pianoWidth(layout) || y < 0 || y >= height) return null\n\n const notes = getNoteRangeArray(layout.noteRange)\n const blackHeight =\n height * (layout.blackKeyHeightRatio ?? DEFAULT_BLACK_KEY_HEIGHT_RATIO)\n\n if (y < blackHeight) {\n for (const note of notes) {\n if (isWhiteKey(note)) continue\n const left = notePosition(note, layout)\n if (left <= x && x < left + blackKeyWidth(layout)) return note\n }\n }\n\n const slot = layout.whiteKeyWidth + (layout.keyGap ?? DEFAULT_KEY_GAP)\n for (const note of notes) {\n if (isBlackKey(note)) continue\n const left = notePosition(note, layout)\n if (left <= x && x < left + slot) return note\n }\n\n return null\n}\n\n/**\n * The white key width that makes the keyboard exactly `width` wide, for a\n * keyboard that follows the size of its container.\n *\n * Solved from {@link pianoWidth} rather than by dividing among the white\n * keys, so that a range that starts or ends on a black key — which sticks out\n * by a fraction of a white key — still fills the container. The width grows\n * linearly with the white key width, so two samples pin it down.\n */\nexport function fitWhiteKeyWidth(\n width: number,\n layout: Omit<PianoLayout, 'whiteKeyWidth'>,\n): number {\n const at = (whiteKeyWidth: number) => pianoWidth({ ...layout, whiteKeyWidth })\n const slope = at(2) - at(1)\n if (slope <= 0) return 0\n return (width - (at(1) - slope)) / slope\n}\n","export type KeyboardShortcuts = {\n /**\n * Keys laid out from `noteRange.first`, one entry per semitone.\n *\n * An empty string leaves that note without a shortcut: `KeyboardEvent.key` is\n * never empty, so the entry can never match. Use it to skip the black keys\n * (see {@link SHORTCUTS.HOME_ROW_NATURAL}) and keep the remaining entries\n * lined up with the notes.\n */\n keys: string[]\n}\n\n/**\n * Ready-made keyboard layouts. Both assume `noteRange.first` is a C.\n */\nexport const SHORTCUTS = {\n /** Every semitone from C, over the two rows of a QWERTY keyboard. */\n HOME_ROW: {\n keys: [\n 'a',\n 'w',\n 's',\n 'e',\n 'd',\n 'f',\n 't',\n 'g',\n 'y',\n 'h',\n 'u',\n 'j',\n 'k',\n 'o',\n 'l',\n 'p',\n ';',\n ],\n },\n /** The white keys only, on the home row. Black keys have no shortcut. */\n HOME_ROW_NATURAL: {\n keys: [\n 'a',\n '',\n 's',\n '',\n 'd',\n 'f',\n '',\n 'g',\n '',\n 'h',\n '',\n 'j',\n 'k',\n '',\n 'l',\n '',\n ';',\n ],\n },\n}\n\n/**\n * Where keyboard shortcuts listen. `root` handles keys only while the piano\n * or one of its descendants has focus; `window` handles them anywhere on the\n * page except in editable elements.\n */\nexport type KeyboardShortcutsScope = 'root' | 'window'\n\n/**\n * Whether a key press was typed into something: shortcuts must not play a\n * note for a letter that is going into a text field.\n */\nexport function isEditableTarget(target: EventTarget | null) {\n if (!(target instanceof HTMLElement)) return false\n if (target.matches('input, textarea, select')) return true\n\n for (let element: HTMLElement | null = target; element;) {\n const contentEditable = element.getAttribute('contenteditable')\n if (contentEditable !== null)\n return contentEditable.toLowerCase() !== 'false'\n element = element.parentElement\n }\n\n return false\n}\n","import { createDrag } from '../pointer/drag'\n\nimport { noteAt, type PianoLayout } from './layout'\nimport {\n isEditableTarget,\n type KeyboardShortcuts,\n type KeyboardShortcutsScope,\n} from './shortcuts'\n\n/**\n * What asked for a note to sound.\n *\n * A note stops only once everything that asked for it has let go, so two\n * fingers on one key, or a key held by both the mouse and a MIDI keyboard,\n * behave the way they look.\n *\n * Pointers use `pointer:<pointerId>`; everything else names its own source.\n */\nexport type NoteSource = string\n\nexport interface PianoInputOptions {\n /** Geometry of the drawn keyboard, used to find the note under a pointer. */\n layout: PianoLayout\n\n /**\n * Let a pointer slide from one key to the next while it is down. With it off,\n * the key that was pressed keeps sounding until the pointer is released.\n *\n * @default true\n */\n glissando?: boolean\n\n /**\n * Highest note that can sound. Above it a note is refused, whichever source\n * asks for it.\n *\n * @default 127\n */\n midiMax?: number\n\n /**\n * Play notes from the computer keyboard: `keys[i]` plays\n * `layout.noteRange.first + i`. `SHORTCUTS` has ready-made layouts.\n *\n * A held key keeps its note until it is released, the focus leaves (with\n * `root`), or the window loses focus. Changing the keys, the scope or the\n * note range releases every note a key is holding, since the key would\n * mean something else on release.\n */\n keyboardShortcuts?: KeyboardShortcuts\n\n /**\n * Where keyboard shortcuts listen: on the element (`root`), which has to\n * be focusable, or anywhere on the page (`window`). Keys typed into an\n * editable element are ignored either way.\n *\n * @default 'root'\n */\n keyboardShortcutsScope?: KeyboardShortcutsScope\n\n /** Called when a note starts sounding, not for each source that asks. */\n onPlayNote?: (note: number, velocity?: number) => void\n /** Called once the last source holding a note has let go. */\n onStopNote?: (note: number) => void\n /** Called whenever {@link PianoInputInstance.activeNotes} would change. */\n onActiveNotesChange?: (notes: number[]) => void\n}\n\nexport interface PianoInputInstance {\n /**\n * Replace the given options. Lets a wrapper feed a fresh layout in without\n * tearing down the listeners, which would abort a drag in progress.\n */\n update: (options: Partial<PianoInputOptions>) => void\n\n /**\n * Start a note from something other than a pointer or a shortcut: a MIDI\n * message, an imperative call.\n */\n noteOn: (\n note: number,\n options?: { source?: NoteSource; velocity?: number },\n ) => void\n /** Release a note held by `source`. */\n noteOff: (note: number, options?: { source?: NoteSource }) => void\n\n /** The notes currently sounding, ascending. */\n activeNotes: () => number[]\n\n destroy: () => void\n}\n\nconst DEFAULT_SOURCE: NoteSource = 'api'\n\n/**\n * Drive a piano keyboard with pointers, and own what is sounding.\n *\n * Every way of playing a note goes through the one instance — pointers here,\n * keyboard shortcuts and MIDI through {@link PianoInputInstance.noteOn} — so\n * there is a single answer to what is currently held, and drawing the keys is\n * left entirely to the wrapper.\n *\n * Tracks every pointer at once, so a chord can be played with several fingers.\n */\nexport function createPianoInput(\n element: Element,\n options: PianoInputOptions,\n): PianoInputInstance {\n let opts = options\n\n /** Which sources are holding each sounding note. */\n const held = new Map<number, Set<NoteSource>>()\n /** The note each pointer is currently on. */\n const pointerNotes = new Map<number, number>()\n /**\n * The note each held shortcut key is playing, by `code` — the physical key,\n * so that releasing it with a different modifier or layout still matches.\n */\n const shortcutNotes = new Map<string, number>()\n\n function activeNotes(): number[] {\n return [...held.keys()].sort((a, b) => a - b)\n }\n\n function noteOn(\n note: number,\n {\n source = DEFAULT_SOURCE,\n velocity,\n }: { source?: NoteSource; velocity?: number } = {},\n ) {\n if (!Number.isInteger(note) || note < 0 || note > 127) {\n throw new RangeError('note: requirements: an integer from 0 to 127')\n }\n if (note > (opts.midiMax ?? 127)) return\n\n const sources = held.get(note)\n if (sources) {\n // Already sounding: remember the extra holder and leave it alone.\n sources.add(source)\n return\n }\n\n held.set(note, new Set([source]))\n opts.onPlayNote?.(note, velocity)\n opts.onActiveNotesChange?.(activeNotes())\n }\n\n function noteOff(\n note: number,\n { source = DEFAULT_SOURCE }: { source?: NoteSource } = {},\n ) {\n if (!Number.isInteger(note) || note < 0 || note > 127) {\n throw new RangeError('note: requirements: an integer from 0 to 127')\n }\n const sources = held.get(note)\n if (!sources) return\n\n sources.delete(source)\n if (sources.size > 0) return\n\n held.delete(note)\n opts.onStopNote?.(note)\n opts.onActiveNotesChange?.(activeNotes())\n }\n\n /** The note under a pointer, or null where the pointer is off the keys. */\n function noteUnder(clientX: number, clientY: number): number | null {\n const { left, top, height } = element.getBoundingClientRect()\n return noteAt(clientX - left, clientY - top, height, opts.layout)\n }\n\n /** Move a pointer onto a note, releasing whatever it held before. */\n function movePointer(pointerId: number, note: number | null) {\n const source = `pointer:${pointerId}`\n const previous = pointerNotes.get(pointerId)\n if (previous === note) return\n\n if (previous !== undefined) noteOff(previous, { source })\n\n if (note === null || note > (opts.midiMax ?? 127)) {\n pointerNotes.delete(pointerId)\n } else {\n pointerNotes.set(pointerId, note)\n noteOn(note, { source })\n }\n }\n\n /** The note a shortcut key plays, or null when it has none. */\n function shortcutNote(key: string) {\n const keys = opts.keyboardShortcuts?.keys\n if (!keys || key === '') return null\n const { first, last } = opts.layout.noteRange\n const index = keys.indexOf(key)\n const note = first + index\n return index === -1 || note > last ? null : note\n }\n\n function onKeyDown(event: KeyboardEvent) {\n const id = event.code || event.key\n if (event.repeat || shortcutNotes.has(id)) return\n if (isEditableTarget(event.target)) return\n const note = shortcutNote(event.key)\n if (note === null) return\n shortcutNotes.set(id, note)\n noteOn(note, { source: `keyboard:${id}` })\n }\n\n function onKeyUp(event: KeyboardEvent) {\n const id = event.code || event.key\n const note = shortcutNotes.get(id)\n if (note === undefined) return\n shortcutNotes.delete(id)\n noteOff(note, { source: `keyboard:${id}` })\n }\n\n function releaseShortcuts() {\n for (const [id, note] of shortcutNotes) {\n noteOff(note, { source: `keyboard:${id}` })\n }\n shortcutNotes.clear()\n }\n\n function onFocusOut(event: Event) {\n const next = (event as FocusEvent).relatedTarget\n // Moving between the keys and whatever else is inside keeps them held.\n if (next instanceof Node && element.contains(next)) return\n releaseShortcuts()\n }\n\n /** The scope the listeners are bound for, or null when none are. */\n let boundScope: KeyboardShortcutsScope | null = null\n\n function bindShortcuts() {\n const scope = opts.keyboardShortcuts\n ? (opts.keyboardShortcutsScope ?? 'root')\n : null\n if (scope === boundScope) return\n unbindShortcuts()\n const win = globalThis.window\n if (!scope || !win) return\n const target: EventTarget = scope === 'window' ? win : element\n target.addEventListener('keydown', onKeyDown as EventListener)\n target.addEventListener('keyup', onKeyUp as EventListener)\n if (scope === 'root') element.addEventListener('focusout', onFocusOut)\n // A key released while the window is in the background never sends its\n // keyup, so everything is let go when the focus leaves the page.\n win.addEventListener('blur', releaseShortcuts)\n boundScope = scope\n }\n\n function unbindShortcuts() {\n const win = globalThis.window\n if (!boundScope || !win) return\n const target: EventTarget = boundScope === 'window' ? win : element\n target.removeEventListener('keydown', onKeyDown as EventListener)\n target.removeEventListener('keyup', onKeyUp as EventListener)\n element.removeEventListener('focusout', onFocusOut)\n win.removeEventListener('blur', releaseShortcuts)\n boundScope = null\n }\n\n /**\n * What the held keys were started against. Compared by value: a caller\n * who writes the keys inline hands over a new array on every update, and\n * releasing on that would stop a note as soon as anything re-renders.\n */\n function shortcutMapping() {\n const { first, last } = opts.layout.noteRange\n return [\n opts.keyboardShortcuts?.keys.join('\\u0000') ?? '',\n opts.keyboardShortcutsScope ?? 'root',\n first,\n last,\n ].join('\\u0001')\n }\n\n bindShortcuts()\n\n const drag = createDrag(element, {\n multiPointer: true,\n onDragStart: (state) =>\n movePointer(state.pointerId, noteUnder(state.clientX, state.clientY)),\n onDrag: (state) => {\n // Without glissando the pressed key holds until the pointer is released,\n // so where the pointer wanders to does not matter.\n if (opts.glissando === false) return\n movePointer(state.pointerId, noteUnder(state.clientX, state.clientY))\n },\n onDragEnd: (state) => movePointer(state.pointerId, null),\n })\n\n return {\n update: (next) => {\n const mapping = shortcutMapping()\n opts = { ...opts, ...next }\n if (shortcutMapping() !== mapping) releaseShortcuts()\n bindShortcuts()\n\n const midiMax = opts.midiMax ?? 127\n const stoppedNotes = activeNotes().filter((note) => note > midiMax)\n if (stoppedNotes.length === 0) return\n\n for (const note of stoppedNotes) {\n held.delete(note)\n opts.onStopNote?.(note)\n }\n for (const [pointerId, note] of pointerNotes) {\n if (note > midiMax) pointerNotes.delete(pointerId)\n }\n opts.onActiveNotesChange?.(activeNotes())\n },\n noteOn,\n noteOff,\n activeNotes,\n destroy: () => {\n drag.destroy()\n unbindShortcuts()\n shortcutNotes.clear()\n // Anything still held is released, so a caller that mirrors these\n // callbacks into a synth is not left with a stuck note.\n const notes = activeNotes()\n for (const note of notes) {\n held.delete(note)\n opts.onStopNote?.(note)\n }\n pointerNotes.clear()\n if (notes.length > 0) opts.onActiveNotesChange?.([])\n },\n }\n}\n","import type { XY } from '../xy'\n\n/**\n * A rectangle in the 0..1 space of whatever the box is drawn over, with `y`\n * growing downwards — the same space the items are given in.\n */\nexport interface SelectionBoxRect {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport interface SelectionBoxOptions<Id> {\n /**\n * Everything the box can pick up, read on every move rather than taken as a\n * snapshot: what is under the box changes while it is dragged, and an item\n * may have moved since the last one.\n */\n items: () => Iterable<readonly [Id, XY<number>]>\n /** The box as it is dragged, and `null` once it is gone. */\n onBoxChange?: (box: SelectionBoxRect | null) => void\n /** What the box covers, added to whatever it started from. */\n onSelectionChange?: (ids: Id[]) => void\n}\n\nexport interface SelectionBoxBeginOptions<Id> {\n /**\n * Add to `selection` rather than replacing it. Ctrl / meta rather than\n * shift, for the controls where shift is the fine-adjustment key.\n */\n additive?: boolean\n /** What was selected before the box started. Only read when `additive`. */\n selection?: readonly Id[]\n}\n\nexport interface SelectionBoxInstance<Id> {\n /** Replace the given options, without interrupting a box in progress. */\n update: (options: Partial<SelectionBoxOptions<Id>>) => void\n\n /** Start a box at `at`, which is one of its corners. */\n begin: (at: XY<number>, options?: SelectionBoxBeginOptions<Id>) => void\n /** Drag the opposite corner to `to`. */\n move: (to: XY<number>) => void\n /**\n * Finish the box. The selection stays as it is.\n *\n * @returns whether a box was running, so that a caller can tell a drag from\n * a press that selected nothing.\n */\n end: () => boolean\n\n /** The box being dragged, or `null` when there is none. */\n box: () => SelectionBoxRect | null\n\n destroy: () => void\n}\n\nfunction rectOf(from: XY<number>, to: XY<number>): SelectionBoxRect {\n return {\n x: Math.min(from[0], to[0]),\n y: Math.min(from[1], to[1]),\n width: Math.abs(to[0] - from[0]),\n height: Math.abs(to[1] - from[1]),\n }\n}\n\n/** Whether the box covers the point, edges included. */\nexport function selectionBoxCovers(\n box: SelectionBoxRect,\n [x, y]: XY<number>,\n): boolean {\n return (\n x >= box.x &&\n x <= box.x + box.width &&\n y >= box.y &&\n y <= box.y + box.height\n )\n}\n\n/**\n * Selecting by dragging a box over a set of items.\n *\n * The drag itself is not here: the caller owns the pointer, and reports where\n * it went in the same 0..1 space the items are given in.\n */\nexport function createSelectionBox<Id>(\n options: SelectionBoxOptions<Id>,\n): SelectionBoxInstance<Id> {\n let opts = options\n let drag: { from: XY<number>; base: readonly Id[] } | null = null\n let box: SelectionBoxRect | null = null\n\n function setBox(next: SelectionBoxRect | null) {\n box = next\n opts.onBoxChange?.(next)\n }\n\n function apply(rect: SelectionBoxRect, base: readonly Id[]) {\n const inside: Id[] = []\n for (const [id, at] of opts.items()) {\n if (selectionBoxCovers(rect, at) && !base.includes(id)) inside.push(id)\n }\n opts.onSelectionChange?.([...base, ...inside])\n }\n\n return {\n update: (next) => {\n opts = { ...opts, ...next }\n },\n begin: (at, { additive = false, selection = [] } = {}) => {\n drag = { from: at, base: additive ? [...selection] : [] }\n setBox(rectOf(at, at))\n // A plain press is a deselect of its own, so that a drag that picks\n // nothing up leaves nothing selected.\n if (!additive) opts.onSelectionChange?.([])\n },\n move: (to) => {\n if (!drag) return\n const rect = rectOf(drag.from, to)\n setBox(rect)\n apply(rect, drag.base)\n },\n end: () => {\n const running = drag !== null\n drag = null\n if (box !== null) setBox(null)\n return running\n },\n box: () => box,\n destroy: () => {\n drag = null\n box = null\n },\n }\n}\n","import { clamp, toPrecision } from '@tremolo-ui/functions'\n\nimport { applyDelta } from '../input/apply-delta'\nimport {\n type InputEventOption,\n type ModifierState,\n type ModifierValue,\n} from '../input/modifiers'\nimport { createSelectionBox, type SelectionBoxRect } from '../selection/box'\nimport { type XY } from '../xy'\n\n/** Where a point is: 0..1 on each axis, with y growing downwards. */\nexport interface PointPosition {\n x: number\n y: number\n}\n\n/**\n * A point's value is its position in the editor, so its range is the editor:\n * no scaling, and no rounding to a step.\n */\nexport const POINT_AXIS = { min: 0, max: 1 }\n\n/**\n * How much one wheel notch moves a point by default: a hundredth of the\n * editor, whatever its pixel size.\n */\nexport const POINTS_EDITOR_DEFAULT_WHEEL: ModifierValue<InputEventOption> = [\n 'normalized',\n 0.01,\n]\n\n/**\n * How much one arrow key press moves a point by default. Shift is the\n * fine-adjustment key everywhere else, so it is bound here too — but only on\n * the keyboard. On the wheel it already means the x axis, and browsers hand\n * shift+wheel over as horizontal scrolling anyway.\n */\nexport const POINTS_EDITOR_DEFAULT_KEYBOARD: ModifierValue<InputEventOption> = {\n default: ['normalized', 0.01],\n shift: ['normalized', 0.001],\n}\n\n/** Keep a point within its range. An axis left out runs from 0 to 1. */\nexport function clampPoint(\n point: PointPosition,\n min?: Partial<PointPosition>,\n max?: Partial<PointPosition>,\n): PointPosition {\n return {\n x: clamp(point.x, min?.x ?? 0, max?.x ?? 1),\n y: clamp(point.y, min?.y ?? 0, max?.y ?? 1),\n }\n}\n\n/**\n * What a point tells the editor about itself, so that a selection can be\n * moved without the editor knowing how the points are stored.\n */\nexport interface PointsEditorPoint {\n value: PointPosition\n min?: Partial<PointPosition>\n max?: Partial<PointPosition>\n /** A point that cannot move stays put while the rest of a selection moves. */\n readonly?: boolean\n onChange?: (value: PointPosition) => void\n /** The point's own element, to match the focus and a press against. */\n element?: Element | null\n /** The wheel option the point resolved, `null` for no wheel. */\n wheel?: ModifierValue<InputEventOption> | null\n}\n\nexport interface PointsEditorOptions {\n /**\n * Let points be selected, and a selection be moved as one. While it is off\n * nothing is selected, and a drag moves only the point it started on.\n *\n * @default false\n */\n selectable?: boolean\n /**\n * The ids of the selected points. Whoever holds the selection pushes it\n * back with `update()` after {@link PointsEditorOptions.onSelectionChange}.\n */\n selection?: readonly string[]\n /** Called whenever a press or a selection box changes the selection. */\n onSelectionChange?: (selection: string[]) => void\n /** Called whenever the selection box changes, with `null` once it is gone. */\n onSelectionBoxChange?: (rect: SelectionBoxRect | null) => void\n}\n\nexport interface PointsEditorInstance {\n /** Replace the given options. */\n update: (options: Partial<PointsEditorOptions>) => void\n /**\n * Register a point under `id`. `read` is called whenever the editor needs\n * the point, so it can return what the point is now rather than what it\n * was when it registered. Returns the function that unregisters it.\n */\n registerPoint: (id: string, read: () => PointsEditorPoint) => () => void\n /** Whether the element is a point, or inside one. */\n isPointElement: (element: Element | null | undefined) => boolean\n /**\n * A pointer went down on a point: work out the new selection and remember\n * where everything the drag picked up started.\n *\n * Ctrl / meta add the point to the selection or take it out: shift is the\n * fine-adjustment key on every control here, and it cannot be both. A\n * press on a point already selected keeps the selection, so the group can\n * be dragged.\n */\n beginPointDrag: (id: string, modifiers: ModifierState) => void\n /**\n * Move everything the drag picked up by `delta` from where it started,\n * stopping the whole group together at the edge.\n */\n movePointDrag: (delta: PointPosition) => void\n /**\n * Move `id` — and the selection, when it is part of one — by `delta` from\n * where the points are now: an input that is not a drag has no earlier\n * position to measure against.\n */\n nudgeSelection: (id: string, delta: PointPosition) => void\n /**\n * Move `id` by one press of `option` along one axis, taking the selection\n * along as {@link PointsEditorInstance.nudgeSelection} does.\n */\n nudgePoint: (\n id: string,\n axis: 'x' | 'y',\n direction: number,\n option: ModifierValue<InputEventOption>,\n modifiers?: ModifierState,\n ) => void\n /**\n * Move the point holding the focus by one wheel notch, with its own wheel\n * option. The wheel is listened to once for the whole editor, since a wheel\n * event only reaches what the cursor is over. Returns whether a point took\n * it, so the caller knows whether to consume the event.\n */\n nudgeFocusedPoint: (\n axis: 'x' | 'y',\n direction: number,\n modifiers: ModifierState,\n ) => boolean\n /** A drag on empty space started at `at`: start a selection box there. */\n beginSelectionBox: (at: PointPosition, modifiers: ModifierState) => void\n moveSelectionBox: (to: PointPosition) => void\n /**\n * End the selection box. The press that started it left the focus on\n * nothing, and the arrow keys and the wheel reach a point only through the\n * focus, so it is handed to one of the points the box selected.\n */\n endSelectionBox: () => void\n destroy: () => void\n}\n\nconst EMPTY: readonly string[] = []\n\n/**\n * How far a group may move before something in it leaves its range.\n *\n * Clamping each point on its own would break the shape of the selection: the\n * one that reached the edge would stop while the rest carried on. One amount\n * for all of them means the whole selection stops together.\n */\nfunction allowedDelta(\n delta: PointPosition,\n entries: { start: PointPosition; point: PointsEditorPoint }[],\n): PointPosition {\n let loX = -Infinity\n let hiX = Infinity\n let loY = -Infinity\n let hiY = Infinity\n for (const { start, point } of entries) {\n // A point that cannot move stays where it is, so its range says nothing\n // about how far the rest may go.\n if (point.readonly) continue\n loX = Math.max(loX, (point.min?.x ?? 0) - start.x)\n hiX = Math.min(hiX, (point.max?.x ?? 1) - start.x)\n loY = Math.max(loY, (point.min?.y ?? 0) - start.y)\n hiY = Math.min(hiY, (point.max?.y ?? 1) - start.y)\n }\n // A point that started outside its own range leaves nothing to move within.\n return {\n x: hiX < loX ? 0 : clamp(delta.x, loX, hiX),\n y: hiY < loY ? 0 : clamp(delta.y, loY, hiY),\n }\n}\n\n/**\n * The selection and the moves of a points editor: which points a press or a\n * box selects, and how a selection moves as one.\n *\n * The points stay with the wrapper, which registers each one; the editor only\n * reads them when it needs to, so a point's value can change on every frame\n * of a drag without anything being re-registered.\n */\nexport function createPointsEditor(\n options: PointsEditorOptions = {},\n): PointsEditorInstance {\n let opts = options\n const points = new Map<string, () => PointsEditorPoint>()\n /** What the current drag picked up, and where those points started. */\n let dragged: { id: string; start: PointPosition }[] = []\n\n const selection = () => (opts.selectable ? (opts.selection ?? EMPTY) : EMPTY)\n\n function changeSelection(next: string[]) {\n // Read back at once by the drag that follows, before the owner has had a\n // chance to push it with update().\n opts = { ...opts, selection: next }\n opts.onSelectionChange?.(next)\n }\n\n function snapshot(ids: readonly string[]) {\n return ids.flatMap((id) => {\n const point = points.get(id)?.()\n return point ? [{ id, start: { ...point.value } }] : []\n })\n }\n\n function moveFrom(\n entries: { id: string; start: PointPosition }[],\n delta: PointPosition,\n ) {\n const withPoint = entries.flatMap((entry) => {\n const point = points.get(entry.id)?.()\n return point ? [{ ...entry, point }] : []\n })\n const allowed = allowedDelta(delta, withPoint)\n for (const { start, point } of withPoint) {\n if (point.readonly) continue\n // Rounded here as well as in the pipeline: a move is a subtraction and\n // an addition of its own, and that is enough to put the binary artefact\n // back (0.2 + 0.1 lands on 0.30000000000000004).\n point.onChange?.({\n x: toPrecision(start.x + allowed.x),\n y: toPrecision(start.y + allowed.y),\n })\n }\n }\n\n function nudgeSelection(id: string, delta: PointPosition) {\n const current = selection()\n moveFrom(snapshot(current.includes(id) ? current : [id]), delta)\n }\n\n function nudgePoint(\n id: string,\n axis: 'x' | 'y',\n direction: number,\n option: ModifierValue<InputEventOption>,\n modifiers?: ModifierState,\n ) {\n const point = points.get(id)?.()\n if (!point) return\n const { value } = point\n const next = applyDelta(\n value[axis],\n direction,\n option,\n POINT_AXIS,\n modifiers,\n )\n // As a move, so that the rest of the selection comes along and the whole\n // group stops together at the edge.\n nudgeSelection(id, {\n x: axis === 'x' ? next - value.x : 0,\n y: axis === 'y' ? next - value.y : 0,\n })\n }\n\n const box = createSelectionBox<string>({\n *items(): Generator<readonly [string, XY<number>]> {\n for (const [id, read] of points) {\n const { x, y } = read().value\n yield [id, [x, y]]\n }\n },\n onBoxChange: (rect) => opts.onSelectionBoxChange?.(rect),\n onSelectionChange: changeSelection,\n })\n\n return {\n update: (next) => {\n opts = { ...opts, ...next }\n },\n registerPoint: (id, read) => {\n points.set(id, read)\n return () => {\n if (points.get(id) === read) points.delete(id)\n }\n },\n isPointElement: (element) => {\n if (!element) return false\n for (const read of points.values()) {\n if (read().element?.contains(element)) return true\n }\n return false\n },\n beginPointDrag: (id, modifiers) => {\n if (!opts.selectable) {\n dragged = snapshot([id])\n return\n }\n const current = selection()\n const additive = modifiers.ctrlKey || modifiers.metaKey\n let next: readonly string[]\n if (additive) {\n next = current.includes(id)\n ? current.filter((x) => x !== id)\n : [...current, id]\n } else if (current.includes(id)) {\n next = current\n } else {\n next = [id]\n }\n changeSelection([...next])\n // A press that took the point out of the selection was a deselect, not\n // the start of a move, so there is nothing to drag.\n dragged = next.includes(id) ? snapshot(next) : []\n },\n movePointDrag: (delta) => moveFrom(dragged, delta),\n nudgeSelection,\n nudgePoint,\n nudgeFocusedPoint: (axis, direction, modifiers) => {\n for (const [id, read] of points) {\n const { element, wheel, readonly, onChange } = read()\n // The document the point is in, which need not be the one this script\n // runs in — an editor rendered into an iframe has its own focus.\n const active = element?.ownerDocument.activeElement\n // A point answers only for the focus inside its own inputs, so both\n // axes stay part of the same interaction.\n if (!active || !element?.contains(active)) continue\n if (!wheel || readonly || !onChange) return false\n nudgePoint(id, axis, direction, wheel, modifiers)\n return true\n }\n return false\n },\n beginSelectionBox: (at, modifiers) => {\n if (!opts.selectable) return\n box.begin([at.x, at.y], {\n additive: modifiers.ctrlKey || modifiers.metaKey,\n selection: selection(),\n })\n },\n moveSelectionBox: (to) => box.move([to.x, to.y]),\n endSelectionBox: () => {\n if (!box.end()) return\n const [first] = selection()\n const element = first ? points.get(first)?.().element : null\n if (element && 'focus' in element) (element as HTMLElement).focus()\n },\n destroy: () => {\n box.destroy()\n dragged = []\n },\n }\n}\n","/**\n * A pair of per-axis values. The tuple elements are labelled, so editors show\n * `[x: number, y: number]` rather than a bare pair.\n */\nexport type XY<T> = [x: T, y: T]\n\n/**\n * A setting that may be given once for both axes, or per axis.\n *\n * A single value is told from a pair with `Array.isArray`, so the single form\n * is only offered while `T` cannot itself be an array. Where it can, the pair\n * is the only way to write it, since a lone array would be read as a pair.\n */\nexport type XYInput<T> = [T] extends [readonly unknown[]]\n ? readonly [x: T, y: T]\n : T | readonly [x: T, y: T]\n\n// `Array.isArray` narrows a mutable tuple on its own, but not a readonly one,\n// hence the explicit predicate.\nfunction isPair<T>(\n value: T | readonly [x: T, y: T],\n): value is readonly [x: T, y: T] {\n return Array.isArray(value)\n}\n\n/**\n * Spread a setting that may have been given as a single value.\n *\n * The parameter is written out rather than taken as `XYInput<T>`: a\n * conditional type cannot be narrowed, so the constraint stays where it is\n * declared and this takes both forms.\n *\n * The pair is copied rather than passed along, so that the result is a tuple\n * the caller owns even when a readonly one was given.\n */\nexport function toXY<T>(value: T | readonly [x: T, y: T]): XY<T> {\n return isPair(value) ? [value[0], value[1]] : [value, value]\n}\n","import {\n clamp,\n linearScale,\n normalizeValue,\n stepValue,\n toPrecision,\n type ValueRange,\n} from '@tremolo-ui/functions'\n\nimport { toXY, type XY, type XYInput } from '../xy'\n\nimport { createDrag, type DragState } from './drag'\n\n/**\n * How the 0-1 travel of one axis maps onto a value.\n *\n * Extends `ValueRange` so that a drag and an `applyDelta` nudge from a wheel\n * or an arrow key share one description of the scaling.\n */\nexport interface AxisOptions extends ValueRange {\n /**\n * Flip the axis so that its far end is `min`.\n *\n * Positions follow the screen: x grows to the right, y downwards. A vertical\n * slider whose maximum is at the top therefore reverses its y axis.\n *\n * @default false\n */\n reverse?: boolean\n}\n\n/** Where the value of each axis currently sits, as a position (see {@link DragValueMapping}). */\nexport interface MappingContext {\n position: () => XY<number>\n}\n\n/**\n * Turns pointer movement into a position on each axis: 0 is the `min` end of\n * the travel and 1 the `max` end, before `reverse` and the scaling of\n * {@link AxisOptions} are applied.\n *\n * A mapping holds the state of the drag in progress, so an instance belongs to\n * a single {@link createDragValue} instance.\n */\nexport interface DragValueMapping {\n /**\n * @returns the position, or null when it cannot be determined and the event\n * should be ignored.\n */\n start: (state: DragState, context: MappingContext) => XY<number> | null\n move: (state: DragState, context: MappingContext) => XY<number> | null\n}\n\n/**\n * Map the pointer onto the bounding rect of an element: the value *is* the\n * position pointed at, so the middle of the element is 0.5.\n *\n * The element is read on every event, so it may be mounted after the drag is\n * set up and may change size while a drag is in progress.\n */\nexport function elementMapping(\n getElement: () => Element | null | undefined,\n {\n sensitivity,\n }: {\n /**\n * How much the movement counts, read on every move. `1` is the pointer\n * position itself; `0.1` makes the same movement cover a tenth of the\n * travel, which is what a fine-adjustment modifier wants.\n *\n * Anything but `1` turns the mapping relative: the value stops being the\n * position pointed at and starts being where it stood when the sensitivity\n * changed, plus the movement since. **The pointer and the value stay apart\n * for the rest of the drag** rather than snapping back together when the\n * key is released, since snapping would move the value nobody asked to\n * move.\n */\n sensitivity?: (state: DragState) => number\n } = {},\n): DragValueMapping {\n /** The reported position when the sensitivity last changed. */\n let origin: XY<number> = [0, 0]\n /** The raw position at that same moment. */\n let anchor: XY<number> = [0, 0]\n /** The previous event, so a sensitivity change can be dated back to it. */\n let previous: XY<number> = [0, 0]\n let factor = 1\n\n function positionIn(state: DragState): XY<number> | null {\n const element = getElement()\n if (!element) return null\n const { left, top, right, bottom } = element.getBoundingClientRect()\n // A collapsed element has no travel to normalize against, and\n // normalizeValue rejects an empty range.\n return [\n right > left ? normalizeValue(state.clientX, left, right) : 0,\n bottom > top ? normalizeValue(state.clientY, top, bottom) : 0,\n ]\n }\n\n const reported = (raw: XY<number>, at: number): XY<number> => [\n origin[0] + (raw[0] - anchor[0]) * at,\n origin[1] + (raw[1] - anchor[1]) * at,\n ]\n\n return {\n start: (state) => {\n const raw = positionIn(state)\n if (!raw) return null\n // origin and anchor together, so the value is the position pointed at:\n // a plain click still lands where it was aimed.\n origin = raw\n anchor = raw\n previous = raw\n // Read here too, so a modifier already held when the pointer went down\n // applies from the first pixel rather than from the first change.\n factor = sensitivity ? sensitivity(state) : 1\n return raw\n },\n move: (state) => {\n const raw = positionIn(state)\n if (!raw) return null\n\n const next = sensitivity ? sensitivity(state) : 1\n if (next !== factor) {\n // Fold the travel so far into the origin, or the new sensitivity would\n // apply to the whole drag and the value would jump.\n //\n // Dated to the previous event rather than this one: a key produces no\n // pointer event of its own, so the change is only seen on the next\n // move, and that move's own distance belongs to the new sensitivity.\n origin = reported(previous, factor)\n anchor = previous\n factor = next\n }\n previous = raw\n\n // With no sensitivity given, origin and anchor never move apart and this\n // is the raw position, exactly as before.\n return reported(raw, factor)\n },\n }\n}\n\n/**\n * Move the value away from where it stood when the drag started, by the\n * distance dragged. The pointer position itself carries no meaning, so the\n * value can be adjusted from anywhere on the screen.\n */\nexport function relativeMapping({\n pixelRange = 100,\n sensitivity,\n}: {\n /**\n * Pixels of movement that span the whole range.\n * @default 100\n */\n pixelRange?: XYInput<number>\n /**\n * How much the movement counts, read on every move. `1` is `pixelRange` as\n * given; `0.1` makes the same movement cover a tenth of the range, which is\n * what a fine-adjustment modifier wants.\n *\n * Changing it mid-drag does not disturb the value: the travel so far is\n * folded into the origin and measuring starts again from there.\n */\n sensitivity?: (state: DragState) => number\n} = {}): DragValueMapping {\n const [baseX, baseY] = toXY(pixelRange)\n let origin: XY<number> = [0, 0]\n /** Where the current sensitivity took over, in drag coordinates. */\n let anchor: XY<number> = [0, 0]\n /** The previous event, so a sensitivity change can be dated back to it. */\n let previous: XY<number> = [0, 0]\n let factor = 1\n\n const travelled = (to: XY<number>, at: number): XY<number> => [\n baseX === 0 ? origin[0] : origin[0] + ((to[0] - anchor[0]) * at) / baseX,\n baseY === 0 ? origin[1] : origin[1] + ((to[1] - anchor[1]) * at) / baseY,\n ]\n\n return {\n start: (state, context) => {\n origin = context.position()\n anchor = [0, 0]\n previous = [0, 0]\n // Read here too, so a modifier already held when the pointer went down\n // applies from the first pixel rather than from the first change.\n factor = sensitivity ? sensitivity(state) : 1\n return origin\n },\n // Measured from the anchor rather than accumulated per event, so the\n // position picks up no rounding error of its own. The anchor only moves\n // when the sensitivity does, which is a handful of times per drag at most.\n move: (state) => {\n const next = sensitivity ? sensitivity(state) : 1\n if (next !== factor) {\n // Fold the travel so far into the origin, or the new sensitivity would\n // apply to the whole drag and the value would jump.\n //\n // Dated to the previous event rather than this one: a key produces no\n // pointer event of its own, so the change is only seen on the next\n // move, and that move's own distance belongs to the new sensitivity.\n origin = travelled(previous, factor)\n anchor = previous\n factor = next\n }\n previous = [state.x, state.y]\n return travelled([state.x, state.y], factor)\n },\n }\n}\n\nexport interface DragValueOptions {\n /** Scaling of each axis; a single value applies to both. */\n axis: XYInput<AxisOptions>\n\n /** How pointer movement becomes a position. */\n mapping: DragValueMapping\n\n /**\n * The current value of each axis. Read when a drag starts, by mappings that\n * move the value relative to it, such as {@link relativeMapping}.\n */\n getValue?: () => XY<number>\n\n /**\n * Report the value on pointer down, before any movement.\n *\n * Enable it where the pointer position *is* the value, so that a plain click\n * jumps to it. Leave it off where the element being dragged is an object in\n * its own right, so that grabbing its edge does not shift it under the\n * pointer.\n *\n * @default false\n */\n updateOnPointerDown?: boolean\n\n /** @see DragOptions.threshold */\n threshold?: number\n /** @see DragOptions.cursor */\n cursor?: string\n /**\n * @see DragOptions.pointerLock\n *\n * Only for a mapping that moves the value relative to where it stood, such\n * as {@link relativeMapping}. {@link elementMapping} reads the pointer\n * position, and there is none while it is locked.\n */\n pointerLock?: boolean\n /** @see DragOptions.shouldStart */\n shouldStart?: (event: PointerEvent) => boolean\n\n onChange?: (value: XY<number>, state: DragState) => void\n onDragStart?: (value: XY<number>, state: DragState) => void\n onDragEnd?: (value: XY<number>, state: DragState) => void\n}\n\nexport interface DragValueInstance {\n /**\n * Replace the given options. Lets a wrapper feed fresh values in without\n * tearing down the listeners, which would abort a drag in progress.\n *\n * `mapping` is fixed for the lifetime of the instance and is ignored here.\n */\n update: (options: Partial<DragValueOptions>) => void\n destroy: () => void\n}\n\n/**\n * Drive a value with a pointer drag.\n *\n * Combines {@link createDrag} with the scaling of `@tremolo-ui/functions`: the\n * mapping decides where the pointer sits on the 0-1 travel of each axis, and\n * the axis options turn that into a value.\n */\nexport function createDragValue(\n element: Element,\n options: DragValueOptions,\n): DragValueInstance {\n let opts = options\n let lastValue: XY<number> = [0, 0]\n let active = false\n\n const axes = () => toXY(opts.axis)\n\n function valueOf(position: XY<number>): XY<number> {\n return axes().map((axis, i) => {\n const p = axis.reverse ? 1 - position[i] : position[i]\n // A scale clamps the position, so a mapping may report outside 0-1.\n const scale = axis.scale ?? linearScale\n const value = scale.denormalize(p, axis.min, axis.max)\n const stepped = axis.step ? stepValue(value, axis.step) : value\n // Rounding to the step can leave the range, and so can dropping the\n // binary artefact, so the clamp comes last.\n return clamp(toPrecision(stepped), axis.min, axis.max)\n }) as XY<number>\n }\n\n const context: MappingContext = {\n position: () => {\n const getValue = opts.getValue\n if (!getValue) {\n throw new Error(\n 'createDragValue: getValue is required by the given mapping',\n )\n }\n const value = getValue()\n return axes().map((axis, i) => {\n const scale = axis.scale ?? linearScale\n const n = scale.normalize(value[i], axis.min, axis.max)\n return axis.reverse ? 1 - n : n\n }) as XY<number>\n },\n }\n\n const drag = createDrag(element, {\n threshold: opts.threshold,\n cursor: opts.cursor,\n pointerLock: opts.pointerLock,\n shouldStart: (event) => opts.shouldStart?.(event) ?? true,\n onDragStart: (state) => {\n const position = opts.mapping.start(state, context)\n if (!position) return\n active = true\n lastValue = valueOf(position)\n if (opts.updateOnPointerDown) opts.onChange?.(lastValue, state)\n opts.onDragStart?.(lastValue, state)\n },\n onDrag: (state) => {\n if (!active) return\n const position = opts.mapping.move(state, context)\n if (!position) return\n lastValue = valueOf(position)\n opts.onChange?.(lastValue, state)\n },\n onDragEnd: (state) => {\n if (!active) return\n if (\n state.event.type === 'pointerup' &&\n (state.deltaX !== 0 || state.deltaY !== 0)\n ) {\n const position = opts.mapping.move(state, context)\n if (position) {\n lastValue = valueOf(position)\n opts.onChange?.(lastValue, state)\n }\n }\n active = false\n opts.onDragEnd?.(lastValue, state)\n },\n })\n\n return {\n update: (next) => {\n opts = { ...opts, ...next, mapping: opts.mapping }\n drag.update({\n threshold: opts.threshold,\n cursor: opts.cursor,\n pointerLock: opts.pointerLock,\n })\n },\n destroy: () => drag.destroy(),\n }\n}\n","export interface LongPressOptions {\n /** Called once on the press, then again every `interval` after `delay`. */\n onPress: () => void\n /**\n * How long the press has to be held before it starts repeating, in\n * milliseconds.\n *\n * @default 500\n */\n delay?: number\n /**\n * How often it repeats once it has started, in milliseconds.\n *\n * @default 40\n */\n interval?: number\n}\n\nexport interface LongPressInstance {\n /**\n * Start a press, from a `pointerdown` or with no event at all. Only the\n * primary button starts one, and a press already in progress is left alone.\n */\n start: (event?: Pick<PointerEvent, 'button' | 'pointerId'>) => void\n /** End the press, as releasing the pointer would. */\n stop: () => void\n /** Replace the given options. The press in progress picks them up. */\n update: (options: Partial<LongPressOptions>) => void\n /** Whether a press is in progress. */\n pressed: () => boolean\n destroy: () => void\n}\n\n/**\n * Repeat an action while a pointer is held down: once on the press, then\n * again every `interval` after `delay` — the way a stepper button or a\n * key held on a keyboard behaves.\n *\n * The release is listened for on the window, not on the element: the\n * pointer may be let go anywhere. Only the pointer that started the press\n * ends it, so a second finger lifting elsewhere does not. The window losing\n * focus ends it too, since the release would never arrive.\n */\nexport function createLongPress(options: LongPressOptions): LongPressInstance {\n let opts = options\n let pointerId: number | null = null\n let active = false\n let timer: ReturnType<typeof setTimeout> | null = null\n\n function clearTimer() {\n if (timer !== null) clearTimeout(timer)\n timer = null\n }\n\n /** Wait `wait`, fire, and keep going at `interval` from then on. */\n function schedule(wait: number) {\n timer = setTimeout(() => {\n opts.onPress()\n if (active) schedule(opts.interval ?? 40)\n }, wait)\n }\n\n function onPointerEnd(event: PointerEvent) {\n // Pointer events carry an id; a press started without one ends on any.\n if (pointerId !== null && event.pointerId !== pointerId) return\n stop()\n }\n\n function listen(add: boolean) {\n const target = globalThis.window\n if (!target) return\n const method = add ? 'addEventListener' : 'removeEventListener'\n target[method]('pointerup', onPointerEnd as EventListener)\n target[method]('pointercancel', onPointerEnd as EventListener)\n target[method]('blur', stop)\n }\n\n function stop() {\n if (!active) return\n active = false\n pointerId = null\n clearTimer()\n listen(false)\n }\n\n return {\n start: (event) => {\n if (active || (event && event.button !== 0)) return\n active = true\n pointerId = event?.pointerId ?? null\n listen(true)\n opts.onPress()\n // The action may have stopped the press itself.\n if (active) schedule(opts.delay ?? 500)\n },\n stop,\n update: (next) => {\n opts = { ...opts, ...next }\n },\n pressed: () => active,\n destroy: stop,\n }\n}\n","export interface WheelOptions {\n /** Replace the callback through {@link WheelInstance.update}. */\n onWheel?: (event: WheelEvent) => void\n /**\n * Only report events while the focus is inside the element.\n *\n * A control that reacts to the wheel on hover alone takes the scroll away\n * from the page, so passing over one in a long form silently changes its\n * value. Requiring focus makes that an explicit act.\n *\n * The check is `contains`, not an identity test: the element that actually\n * takes focus is usually a descendant, such as a thumb or an `<input>`, and\n * a caller may have replaced it with markup of their own.\n *\n * @default false\n */\n requireFocus?: boolean\n}\n\nexport interface WheelInstance {\n /** Replace the given options, keeping the listener in place. */\n update: (options: WheelOptions) => void\n destroy: () => void\n}\n\n/**\n * Listen to wheel events on an element.\n *\n * The listener is registered with `passive: false` so that the handler can call\n * `preventDefault()` to stop the page from scrolling.\n */\nexport function createWheel(\n element: Element,\n onWheel: (event: WheelEvent) => void,\n options: WheelOptions = {},\n): WheelInstance {\n let opts = { ...options, onWheel }\n\n function hasFocus() {\n const active = element.ownerDocument?.activeElement\n return !!active && element.contains(active)\n }\n\n const handler = (event: Event) => {\n if (opts.requireFocus && !hasFocus()) return\n opts.onWheel?.(event as WheelEvent)\n }\n\n element.addEventListener('wheel', handler, { passive: false })\n\n return {\n update: (next) => {\n opts = { ...opts, ...next }\n },\n destroy: () => {\n // Only `capture` matters when removing, and it is false here.\n element.removeEventListener('wheel', handler)\n },\n }\n}\n","import { linearScale, toFixed, type ValueRange } from '@tremolo-ui/functions'\n\n/**\n * Where a value sits along a track, as a whole percentage from its start.\n *\n * Normalized along the scale, so a thumb, the fill behind it and the marks\n * all sit on the curve the drag follows. `reversed` measures from the other\n * end — for a track that grows upwards or leftwards on screen, since CSS\n * places things from the top and the left.\n */\nexport function valuePercent(\n value: number,\n { min, max, scale = linearScale }: Pick<ValueRange, 'min' | 'max' | 'scale'>,\n reversed = false,\n): number {\n // Reversed before rounding, so that a value halfway between two whole\n // percentages lands on the same one whichever end it is measured from.\n const normalized = scale.normalize(value, min, max)\n return toFixed((reversed ? 1 - normalized : normalized) * 100)\n}\n","/**\n * How many digits a number carries after the decimal point.\n *\n * `Slider.Marks` builds its labels by multiplying an interval, which leaves\n * binary debris in the last digits (`0.1 * 3` is `0.30000000000000004`). The\n * count is what tells `toFixed` how far to round that back.\n *\n * The exponent form has to be handled separately: `String(1e-7)` is `'1e-7'`,\n * which has no decimal point at all, so reading the text after the point\n * reports no digits and the interval rounds away to whole numbers.\n */\nexport function decimalDigits(x: number): number {\n if (!Number.isFinite(x)) return 0\n\n const text = String(x)\n const e = text.indexOf('e')\n if (e === -1) return text.split('.')[1]?.length ?? 0\n\n const fraction = text.slice(0, e).split('.')[1]?.length ?? 0\n // A negative exponent pushes the point further right, a positive one pulls\n // it back past the digits that are there.\n return Math.max(0, fraction - Number(text.slice(e + 1)))\n}\n","import { toFixed } from '@tremolo-ui/functions'\n\nimport { decimalDigits } from './decimal-digits'\n\n/**\n * How `Slider.Marks` fills itself in when it is given no children: one option\n * every `per`, or every `step` of the slider. The object form turns off the\n * mark or the label for the whole set; a single option is customized by\n * writing `Slider.MarksOption` out instead.\n */\nexport type MarksOptions =\n | 'step'\n | number\n | {\n per: 'step' | number\n mark?: boolean\n label?: boolean\n }\n\n/** One mark along a slider, as {@link sliderMarks} lays them out. */\nexport interface SliderMark {\n value: number\n mark: boolean\n label: boolean\n}\n\n/**\n * The marks `options` asks for between `min` and `max`, in ascending order.\n *\n * Each value is a whole multiple of the interval, rounded to the digits the\n * interval has: stepping by 0.1 would otherwise put binary debris in the\n * labels (`0.1 * 3` is `0.30000000000000004`).\n */\nexport function sliderMarks(\n options: MarksOptions,\n min: number,\n max: number,\n step: number,\n): SliderMark[] {\n const {\n per,\n mark = true,\n label = true,\n } = typeof options === 'object' ? options : { per: options }\n const optionsList: SliderMark[] = []\n const interval = per === 'step' ? step : per\n const count = Math.floor(max / interval) - Math.ceil(min / interval) + 1\n for (let i = 0; i < count; i++) {\n const value = toFixed(\n interval * (Math.ceil(min / interval) + i),\n decimalDigits(interval),\n )\n optionsList.push({ value: value, mark: mark, label: label })\n }\n return optionsList\n}\n","/**\n * A number as a CSS length, for a custom property.\n *\n * A custom property takes whatever text it is given, so `--size: 50` comes out\n * as the invalid `50` rather than `50px`. React appends `px` to a bare number\n * only for the properties it knows take a length, and a custom property is\n * never one of them; Vue and Svelte append nothing at all. Anything already a\n * string is passed through, so `'3rem'` and `'100%'` still work.\n */\nexport function cssLength(\n value: number | string | undefined,\n): string | undefined {\n return typeof value === 'number' ? `${value}px` : value\n}\n\n/**\n * Take an element out of sight while leaving it in the accessibility tree and\n * in the tab order.\n *\n * `display: none` and `visibility: hidden` would remove it from both, and the\n * native control underneath a headless component is what carries the ARIA and\n * the keyboard behaviour. `pointer-events: none` is safe because nothing is\n * ever clicked here directly: a `<label>` forwards its click, and a drag is\n * handled by the part that is visible.\n *\n * Every value is a string with its unit, so the object can be handed to any\n * framework's `style` binding as it is.\n */\nexport const visuallyHiddenStyle = {\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: '0',\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n clipPath: 'inset(50%)',\n whiteSpace: 'nowrap',\n border: '0',\n pointerEvents: 'none',\n} as const\n"],"mappings":";;;;;;;;;;AAQA,MAAa,eAAe;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAWA,SAAgB,eAAe,OAAuC;CAEpE,OAAO,OAAO,UAAU,YAAYA,aAAM,SAAS,KAAK;AAC1D;;AAGA,SAAgB,iBACd,SACgB;CAChB,MAAM,QAAQ,CAAC;CACf,KAAK,MAAM,YAAY,cACpB,MAAO,YAAkC,QAAQ;CAEpD,MAAM,WAAW,QAAQ,YAAY;CACrC,MAAM,YAAY,QAAQ,aAAa;CACvC,OAAO;AACT;;AAGA,SAAgB,kBACd,SACA,OACA,iBAAiB,GACjB;CACA,KAAK,MAAM,YAAY,cACpB,QAAS,YAAkC,MAAM;CAEpD,QAAQ,YAAY,MAAM,QAAQ;CAClC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,MAAM,MAAM;CACnC,QAAQ,aACN,IAAI,gBACJ,IAAI,gBACJ,IAAI,gBACJ,IAAI,gBACJ,IAAI,gBACJ,IAAI,cACN;AACF;;;;;;;;;AAUA,SAAgB,sBACd,QACA,SACA,OACA,QACA,KACA;CACA,OAAO,QAAQ,QAAQ;CACvB,OAAO,SAAS,SAAS;CAEzB,QAAQ,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;CACrC,QAAQ,MAAM,KAAK,GAAG;CACtB,OAAO,MAAM,QAAQ,GAAG,MAAM;CAC9B,OAAO,MAAM,SAAS,GAAG,OAAO;AAClC;;;;;;;;;;;ACEA,SAAgB,sBACd,QACA,SACyB;CACzB,IAAI,OAAO;CAEX,MAAM,YAAY,OAAO,WAAW,MAAM,KAAK,iBAAiB;CAChE,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,gDAAgD;CAElE,MAAM,UAAoC;CAE1C,MAAM,YAAY,KAAK,aAAa;;CAGpC,IAAI,OAAiC;CACrC,IAAI,cAA+C;CAEnD,IAAI,QAAQ;CACZ,IAAI,SAAS;CACb,IAAI,aAAa;;CAEjB,IAAI,QAAQ;CACZ,IAAI,cAAc;CAElB,IAAI,UAAyB;CAC7B,IAAI,QAAQ;CACZ,MAAM,YAAY,YAAY,IAAI;CAClC,IAAI,eAAe;CAEnB,SAAS,mBAAmB;EAC1B,OAAO,WAAW,oBAAoB;CACxC;;;;;;;CAQA,SAAS,eAAe;EACtB,IAAI,EAAE,KAAK,oBAAoB,OAAO,OAAO;EAC7C,IAAI,CAAC,MAAM;GACT,OAAO,WAAW,UAAU,cAAc,QAAQ,KAAK;GACvD,cAAc,MAAM,WAAW,MAAM,KAAK,iBAAiB,KAAK;EAClE;EACA,IAAI,CAAC,QAAQ,CAAC,aAAa,OAAO;EAElC,KAAK,QAAQ,OAAO;EACpB,KAAK,SAAS,OAAO;EACrB,IAAI,OAAO,QAAQ,KAAK,OAAO,SAAS,GACtC,YAAY,UAAU,QAAQ,GAAG,CAAC;EAEpC,OAAO;CACT;;;;;;;;;CAUA,SAAS,gBACP,aACA,eACA,gBACA;EACA,IAAI,CAAC,eAAe,CAAC,MAAM;EAC3B,IAAI,KAAK,SAAS,KAAK,KAAK,UAAU,GAAG;EACzC,QAAQ,UAAU,MAAM,GAAG,GAAG,eAAe,cAAc;CAC7D;CAEA,SAAS,UAAU,GAAW,GAAW;EACvC,MAAM,MAAM,iBAAiB;EAC7B,MAAM,gBAAgB;EACtB,MAAM,iBAAiB;EACvB,MAAM,cAAc;EACpB,MAAM,QAA+B,QACjC,iBAAiB,OAAO,IACxB;EACJ,MAAM,cAAc,SAAS,aAAa;EAC1C,sBAAsB,QAAQ,SAAS,GAAG,GAAG,GAAG;EAChD,QAAQ;EACR,SAAS;EACT,aAAa;EACb,QAAQ;EAGR,gBAAgB,aAAa,eAAe,cAAc;EAC1D,IAAI,OACF,kBAAkB,SAAS,OAAO,MAAM,WAAW;CAEvD;CAEA,SAAS,gCAAgC;EACvC,IAAI,SAAS,iBAAiB,MAAM,YAClC,UAAU,OAAO,MAAM;CAE3B;CAEA,SAAS,UAAU,YAAY,YAAY,IAAI,GAAG;EAChD,IAAI,CAAC,OAAO;EACZ,8BAA8B;EAC9B,IAAI,CAAC,aAAa;GAChB,cAAc;GACd,KAAK,OAAO,SAAS;IAAE;IAAO;GAAO,CAAC;EACxC;EACA,MAAM,YAAY,YAAY;EAC9B,eAAe;EACf,SAAS;EACT,KAAK,KAAK,SAAS;GACjB;GACA;GACA;GACA;GACA,aAAa,YAAY;GACzB,KAAK,YAAY,IAAI,MAAO,YAAY;EAC1C,CAAC;CACH;CAEA,SAAS,KAAK,WAAgC;EAG5C,UAAU,sBAAsB,IAAI;EACpC,UAAU,SAAS;CACrB;CAEA,SAAS,YAAY;EACnB,IAAI,YAAY,MAAM;EACtB,eAAe,YAAY,IAAI;EAC/B,UAAU,sBAAsB,IAAI;CACtC;CAEA,SAAS,WAAW;EAClB,IAAI,YAAY,MAAM;EACtB,qBAAqB,OAAO;EAC5B,UAAU;CACZ;CAEA,IAAI,WAAkC;CAEtC,IAAI,WAAW;EACb,MAAM,SAAS,OAAO;EACtB,IAAI,CAAC,QACH,MAAM,IAAI,MACR,4EACF;EAMF,WAAW,IAAI,gBAAgB,YAAY;GACzC,KAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,EAAE,OAAO,GAAG,QAAQ,MAAM,MAAM;IACtC,IAAI,MAAM,SAAS,MAAM,UAAU,iBAAiB,MAAM,YACxD,UAAU,GAAG,CAAC;IAGhB,IAAI,EAAE,KAAK,WAAW,OAAO,UAAU;GACzC;EACF,CAAC;EACD,SAAS,QAAQ,MAAM;CACzB,OAAO;EACL,MAAM,EAAE,OAAO,IAAI,KAAK,QAAQ,IAAI,QAAQ,KAAK,QAAQ,CAAC;EAC1D,UAAU,GAAG,CAAC;CAChB;CAEA,IAAI,KAAK,WAAW,MAClB,UAAU;MACL,IAAI,OACT,UAAU;CAGZ,OAAO;EACL,SAAS,SAAS;GAChB,MAAM,eAAe,KAAK,WAAW;GACrC,OAAO;IACL,GAAG;IACH,GAAG;IACH;IACA,mBAAmB,KAAK;GAC1B;GAEA,IAAI,CAAC,WAAW;IACd,MAAM,EAAE,OAAO,IAAI,KAAK,QAAQ,IAAI,QAAQ,KAAK,QAAQ,CAAC;IAC1D,IAAI,MAAM,SAAS,MAAM,QAAQ,UAAU,GAAG,CAAC;GACjD;GACA,8BAA8B;GAE9B,MAAM,YAAY,KAAK,WAAW;GAClC,IAAI,aAAa,CAAC,cAAc,UAAU;GAC1C,IAAI,CAAC,aAAa,cAAc,SAAS;GAOzC,IAAI,CAAC,WAAW,UAAU;EAC5B;EACA,QAAQ;EACR,eAAe;GACb,SAAS;GACT,UAAU,WAAW;GACrB,WAAW;GACX,OAAO;GACP,cAAc;EAChB;CACF;AACF;;;;;;;;;;;;;;;;;;;;AClSA,SAAgB,cACd,WACA,QACS;CACT,MAAM,SAAS,UAAU,GAAA,CACtB,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CACxC,OAAO,OAAO;CACjB,IAAI,MAAM,WAAW,GAAG,OAAO;CAE/B,MAAM,OAAO,UAAU,MAAM,YAAY;CACzC,MAAM,OAAO,UAAU,KAAK,YAAY;CACxC,IAAI,YAAY;CAEhB,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,WAAW,GAAG,GAAG;EACxB,IAAI,SAAS,KAAA,GAAW,YAAY;OAC/B,IAAI,KAAK,SAAS,IAAI,GAAG,OAAO;CACvC,OAAO,IAAI,KAAK,SAAS,IAAI,GACvB;MAAA,KAAK,WAAW,KAAK,MAAM,GAAG,EAAE,CAAC,GAAG,OAAO;CAAA,OAC1C,IAAI,SAAS,MAClB,OAAO;CAIX,OAAO;AACT;;;;;AAMA,SAAgB,kBACd,OACA,QACwC;CACxC,MAAM,WAAmB,CAAC;CAC1B,MAAM,WAAmB,CAAC;CAC1B,KAAK,MAAM,QAAQ,OACjB,IAAI,cAAc,MAAM,MAAM,GAAG,SAAS,KAAK,IAAI;MAC9C,SAAS,KAAK,IAAI;CAEzB,OAAO;EAAE;EAAU;CAAS;AAC9B;;;;;;;;;;;;;;;;ACFA,SAAgB,eACd,SACA,UAA2B,CAAC,GACV;CAClB,IAAI,OAAO;CACX,IAAI,QAAuB;EAAE,MAAM;EAAO,SAAS;CAAM;;;;;;CAOzD,IAAI,QAAQ;CAEZ,SAAS,SAAS,MAAqB;EACrC,IAAI,KAAK,SAAS,MAAM,QAAQ,KAAK,YAAY,MAAM,SAAS;EAChE,QAAQ;EACR,KAAK,gBAAgB,KAAK;CAC5B;CAEA,SAAS,aAAa,UAA+B;EAGnD,OAAO,CAAC,CAAC,UAAU,MAAM,SAAS,OAAO;CAC3C;;CAGA,SAAS,cAAc,UAA+B;EACpD,MAAM,QAAQ,MAAM,KAAK,UAAU,SAAS,CAAC,CAAC,CAAC,CAAC,QAC7C,SAAS,KAAK,SAAS,MAC1B;EAGA,IAAI,MAAM,WAAW,GAAG,OAAO;EAC/B,OAAO,MAAM,MAAM,SAAS,cAAc,EAAE,MAAM,KAAK,KAAK,GAAG,KAAK,MAAM,CAAC;CAC7E;CAEA,SAAS,YAAY,OAAkB;EACrC,IAAI,CAAC,aAAa,MAAM,YAAY,GAAG;EAGvC,MAAM,eAAe;EACrB,SAAS;EACT,SAAS;GAAE,MAAM;GAAM,SAAS,CAAC,cAAc,MAAM,YAAY;EAAE,CAAC;CACtE;CAEA,SAAS,WAAW,OAAkB;EACpC,IAAI,CAAC,aAAa,MAAM,YAAY,GAAG;EACvC,MAAM,eAAe;EACrB,IAAI,MAAM,cAER,MAAM,aAAa,aACjB,KAAK,YAAY,MAAM,UAAU,SAAS;EAI9C,IAAI,CAAC,MAAM,MAAM;GACf,QAAQ,KAAK,IAAI,OAAO,CAAC;GACzB,SAAS;IAAE,MAAM;IAAM,SAAS,CAAC,cAAc,MAAM,YAAY;GAAE,CAAC;EACtE;CACF;CAEA,SAAS,YAAY,OAAkB;EACrC,IAAI,CAAC,aAAa,MAAM,YAAY,GAAG;EACvC,QAAQ,KAAK,IAAI,GAAG,QAAQ,CAAC;EAC7B,IAAI,UAAU,GAAG,SAAS;GAAE,MAAM;GAAO,SAAS;EAAM,CAAC;CAC3D;CAEA,SAAS,OAAO,OAAkB;EAChC,IAAI,CAAC,aAAa,MAAM,YAAY,GAAG;EAGvC,MAAM,eAAe;EACrB,QAAQ;EACR,SAAS;GAAE,MAAM;GAAO,SAAS;EAAM,CAAC;EACxC,IAAI,KAAK,UAAU;EAEnB,MAAM,EAAE,UAAU,aAAa,kBAC7B,MAAM,cAAc,SAAS,CAAC,GAC9B,KAAK,MACP;EAEA,MAAM,QAAQ,KAAK,WAAW,WAAW,SAAS,MAAM,GAAG,CAAC;EAC5D,IAAI,MAAM,SAAS,GAAG,KAAK,SAAS,OAAO,KAAK;EAChD,IAAI,SAAS,SAAS,GAAG,KAAK,WAAW,UAAU,KAAK;CAC1D;;;;;;CAOA,SAAS,oBAAoB;EAC3B,QAAQ;EACR,SAAS;GAAE,MAAM;GAAO,SAAS;EAAM,CAAC;CAC1C;CAEA,MAAM,WAAW;EACf,WAAW;EACX,UAAU;EACV,WAAW;EACX,MAAM;CACR;CAEA,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,QAAQ,GACnD,QAAQ,iBAAiB,MAAM,OAAwB;CAGzD,MAAM,MAAM,QAAQ;CACpB,KAAK,iBAAiB,WAAW,iBAAiB;CAClD,KAAK,iBAAiB,QAAQ,iBAAiB;CAE/C,OAAO;EACL,IAAI,QAAQ;GACV,OAAO;EACT;EACA,SAAS,SAAS;GAChB,OAAO;IAAE,GAAG;IAAM,GAAG;GAAK;EAC5B;EACA,eAAe;GACb,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,QAAQ,GACnD,QAAQ,oBAAoB,MAAM,OAAwB;GAE5D,KAAK,oBAAoB,WAAW,iBAAiB;GACrD,KAAK,oBAAoB,QAAQ,iBAAiB;EACpD;CACF;AACF;;;;;;;;AC5JA,MAAM,iBAAiB;CAAC;CAAQ;CAAQ;CAAO;AAAO;AAEtD,MAAM,gBAAgB;CACpB,MAAM;CACN,MAAM;CACN,KAAK;CACL,OAAO;AACT;;;;;AAMA,SAAS,cACP,OACyB;CACzB,OACE,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,KAAK,KACpB,aAAa;AAEjB;;;;;;;AAQA,SAAgB,eACd,SACA,WACyC;CACzC,IAAI,CAAC,cAAc,OAAO,GAGxB,OAAO;EAAE,OAAO;EAAc,UAAU;CAAK;CAE/C,IAAI,WACF,KAAK,MAAM,YAAY,gBAAgB;EACrC,MAAM,QAAQ,QAAQ;EAGtB,IAAI,UAAU,KAAA,KAAa,UAAU,cAAc,YACjD,OAAO;GAAE;GAAO;EAAS;CAE7B;CAEF,OAAO;EAAE,OAAO,QAAQ;EAAS,UAAU;CAAK;AAClD;;;;;;;;;;;;;;;AAgBA,SAAgB,YAGd,SAA2B,IAAuC;CAClE,IAAI,CAAC,cAAc,OAAO,GAAG,OAAO,GAAG,OAAY;CACnD,MAAM,SAAS,EAAE,SAAS,GAAG,QAAQ,OAAO,EAAE;CAC9C,KAAK,MAAM,YAAY,gBAAgB;EACrC,MAAM,QAAQ,QAAQ;EACtB,IAAI,UAAU,KAAA,GAAW,OAAO,YAAY,GAAG,KAAK;CACtD;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;ACpFA,SAAgB,WACd,OACA,WACA,SACA,EAAE,KAAK,KAAK,MAAM,QAAQ,eAC1B,WACQ;CACR,IAAI,OAAO,KAAK,MAAM,IAAI,WAAW,yBAAyB;CAC9D,IAAI,SAAS,KAAA,MAAc,CAAC,OAAO,SAAS,IAAI,KAAK,QAAQ,IAC3D,MAAM,IAAI,WACR,0DACF;CAGF,MAAM,EACJ,OAAO,CAAC,MAAM,SACd,aACE,eAAe,SAAS,SAAS;CAErC,MAAM,IAAI,YAAY;CACtB,MAAM,OACJ,SAAS,eACL,MAAM,YAAY,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAChE,QAAQ;CAKd,MAAM,UAAU,aAAa,OAAO,OAAO,KAAA;CAC3C,MAAM,UAAU,YAAY,KAAA,IAAY,UAAU,MAAM,OAAO,IAAI;CAMnE,OAAO,MAAM,YAAY,OAAO,GAAG,KAAK,GAAG;AAC7C;;;;;AC7DA,MAAM,SAAS;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG;AAE3D,MAAM,OAAsB;CAC1B,UAAU;CACV,QAAQ;CACR,SAAS;CACT,SAAS;AACX;AAEA,MAAM,iBAAgD;CACpD,OAAO;EAAE,GAAG;EAAM,UAAU;CAAK;CACjC,KAAK;EAAE,GAAG;EAAM,QAAQ;CAAK;CAC7B,MAAM;EAAE,GAAG;EAAM,SAAS;CAAK;CAC/B,MAAM;EAAE,GAAG;EAAM,SAAS;CAAK;AACjC;;AAGA,SAAS,QAAQ,SAA2D;CAC1E,IAAI,MAAM,QAAQ,OAAO,GAAG,OAAO,CAAC,IAAI;CACxC,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,QAAQ,eAAe,QAAQ,IAAI;AACtE;;;;;;;;;AAiBA,SAAS,MACP,SACA,OACA,QACS;CACT,MAAM,EAAE,KAAK,KAAK,QAAQ,gBAAgB;CAC1C,MAAM,UAAmB;EAAE,OAAO;EAAO,SAAS;CAAM;CACxD,KAAK,MAAM,aAAa,QAAQ,OAAO,GACrC,KAAK,MAAM,YAAY,QAAQ;EAC7B,MAAM,QAAQ,MAAM,YAAY,UAAU,KAAK,GAAG;EAClD,MAAM,KAAK,WAAW,OAAO,GAAG,SAAS,OAAO,SAAS;EACzD,MAAM,OAAO,WAAW,OAAO,IAAI,SAAS,OAAO,SAAS;EAC5D,IAAI,OAAO,SAAS,SAAS,OAAO,QAAQ,QAAQ;EACpD,IAAI,CAAC,QAAQ;EACb,MAAM,QAAQ,OAAO,KAAK;EAC1B,IAAI,OAAO,EAAE,MAAM,SAAS,OAAO,IAAI,MAAM,OAAO,QAAQ,UAAU;CACxE;CAEF,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAgB,WAAW,EACzB,WACA,MACA,OACA,UACA,OACA,UAC8B;CAC9B,IAAI,CAAC,SAAS,EAAE,MAAM,MAAM,MAAM,MAAM,OAAO,CAAC;CAChD,MAAM,QAAQ,OAAO,GAAG,UAAU,IAAI,KAAK,KAAK;CAChD,MAAM,WAAqB,CAAC;CAC5B,KAAK,MAAM,CAAC,MAAM,YAAY,CAC5B,CAAC,YAAY,QAAQ,GACrB,CAAC,SAAS,KAAK,CACjB,GAAY;EACV,IAAI,CAAC,SAAS;EACd,MAAM,EAAE,OAAO,YAAY,MAAM,SAAS,OAAO,MAAM;EACvD,IAAI,CAAC,OACH,SAAS,KACP,gBAAgB,MAAM,MAAM,KAAK,8BAC9B,MAAM,SAAS,KAAA,IACZ,yCAAyC,MAAM,KAAK,mBACpD,wBACJ,qCACJ;OACK,IAAI,UAAU,CAAC,SACpB,SAAS,KACP,gBAAgB,MAAM,MAAM,KAAK,+IAGnC;CAEJ;CACA,OAAO;AACT;;;;;;;;;;;;ACpIA,MAAa,2BAA4D;CACvE,SAAS,CAAC,OAAO,CAAC;CAClB,OAAO,CAAC,OAAO,EAAG;AACpB;;;;;;;AAQA,MAAa,wBAAyD,CAAC,OAAO,CAAC;;;;;AAM/E,MAAa,2BAAkD;CAC7D,SAAS;CACT,OAAO;AACT;;;;ACVA,SAAgB,WAAW,KAA8B;CACvD,OACE,QAAQ,gBACR,QAAQ,eACR,QAAQ,aACR,QAAQ;AAEZ;;;;;AAcA,SAAgB,kBAAkB,KAA4B;CAC5D,IAAI,CAAC,WAAW,GAAG,GAAG,OAAO;CAC7B,OAAO,QAAQ,gBAAgB,QAAQ,YAAY,IAAI;AACzD;;;;;;;;AASA,SAAgB,aAAa,KAA8B;CACzD,IAAI,CAAC,WAAW,GAAG,GAAG,OAAO;CAC7B,OAAO;EACL,MAAM,QAAQ,gBAAgB,QAAQ,cAAc,IAAI;EACxD,WAAW,QAAQ,eAAe,QAAQ,YAAY,KAAK;CAC7D;AACF;;;;;AAiBA,SAAgB,eACd,OACA,EAAE,aAAa,UAAiC,CAAC,GAClC;CACf,IAAI,cAAc,MAAM,WAAW,GAAG,OAAO,MAAM,SAAS,IAAI,IAAI;CACpE,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,OAAO,MAAM,SAAS,IAAI,KAAK;AACjC;;;;;;;;;;;AAYA,SAAgB,UACd,OACiB;CACjB,MAAM,aAAa,MAAM,WAAW;CACpC,MAAM,QAAQ,aAAa,MAAM,SAAS,MAAM;CAChD,IAAI,UAAU,GAAG,OAAO;CACxB,OAAO;EACL,MAAM,cAAc,MAAM,WAAW,IAAI;EACzC,WAAW,QAAQ,IAAI,KAAK;CAC9B;AACF;;;;;;;ACpGA,MAAa,oBAAoB;AAEjC,MAAM,SAAA;AAgCN,SAAgB,WAAW,EACzB,OACA,KACA,KACA,OACA,YACA,cAC+B;CAC/B,MAAM,IAAI,MAAM,UAAU,OAAO,KAAK,GAAG;CACzC,MAAM,IAAI,MAAM,UAAU,YAAY,KAAK,GAAG;CAC9C,MAAM,KAAK,CAAC,aAAa;CAIzB,OAAO;EAAE;EAAG;EAAI,IAHL,KAAK,KAAK,IAAI,GAAG,CAAC,IAAI;EAGb,IAFT,KAAK,KAAK,IAAI,GAAG,CAAC,IAAI;EAET,IADb,aAAa;CACG;AAC7B;;;;;;;;AASA,SAAgB,aAAa,OAAe,QAAgB;CAC1D,OAAO;EACL,GAAG,SAAS,SAAS,KAAK,IAAI,OAAO,QAAQ,EAAE,CAAC;EAChD,GAAG,SAAS,SAAS,KAAK,IAAI,OAAO,QAAQ,EAAE,CAAC;CAClD;AACF;;;;;AAMA,SAAgB,cAAc,aAA0C;CACtE,MAAM,QACJ,OAAO,gBAAgB,WACnB,cACA,OAAO,WAAW,OAAO,WAAW,CAAC;CAC3C,OAAO,OAAO,SAAS,KAAK,IAAI,SAAS,QAAQ,IAAI;AACvD;;AAGA,SAAgB,YACd,YACA,UACA,QACA;CACA,MAAM,QAAQ,aAAa,YAAY,MAAM;CAC7C,MAAM,QAAQ,WAAW;CACzB,MAAM,eAAe,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC;CACjE,MAAM,eAAe,QAAQ;CAC7B,IAAI,OAAO,KAAK,MAAM,EAAE,GAAG,MAAM;CACjC,KAAK,IAAI,IAAI,GAAG,KAAK,cAAc,KAAK,GAAG;EACzC,MAAM,MAAM,aAAa,aAAa,eAAe,GAAG,MAAM;EAC9D,QAAQ,MAAM,OAAO,GAAG,OAAO,OAAO,gBAAgB,IAAI,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI;CAClF;CACA,OAAO;AACT;;;;AClGA,MAAa,oBAAoB;;AAEjC,MAAa,gBAAgB;;AAE7B,MAAa,cAAc;AAgC3B,MAAM,gBAAiC;CACrC,YAAY;CACZ,OAAO;CACP,QAAQ,CAAC;AACX;;;;;;;AAmBA,SAAS,QAAQ,QAAkC;CACjD,MAAM,OACJ,OAAO,WAAW,YAAY,WAAW,QAAQ,UAAU,SACvD,OAAQ,OAA6B,IAAI,IACzC;CAEN,IAAI,SAAS,mBAAmB,SAAS,mBACvC,OAAO;CAET,IAAI,SAAS,uBAAuB,SAAS,aAC3C,OAAO;CAGT,OAAO;AACT;;;;;;;AAQA,SAAgB,mBAAuC;CACrD,IAAI,QAAQ;CACZ,IAAI,YAAY;CAChB,IAAI,SAA4B;CAChC,IAAI,oBAAoB;CACxB,MAAM,4BAAY,IAAI,IAAgB;CAEtC,SAAS,SAAS,MAAuB;EACvC,QAAQ;EACR,KAAK,MAAM,YAAY,WAAW,SAAS;CAC7C;;;;;CAMA,SAAS,aAAa;EACpB,OAAO,SAAS,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,IAAI,CAAC;CACjD;CAEA,SAAS,kBAAkB,OAAe;EACxC,IAAI,aAAa,CAAC,QAAQ;EAC1B,IAAI,SAAU,MAA8B,MAAM,SAAS,UAAU;EACrE,SAAS;GAAE,GAAG;GAAO,QAAQ,WAAW;EAAE,CAAC;CAC7C;CAEA,SAAS,QAAQ,UAA6B,CAAC,GAAG;EAChD,IAAI,WAAW;EACf,MAAM,aAAa,EAAE;EACrB,IAAI,OAAO,cAAc,eAAe,CAAC,UAAU,mBAAmB;GACpE,SAAS;IAAE,GAAG;IAAO,OAAO;GAAc,CAAC;GAC3C;EACF;EACA,UAAU,kBAAkB,EAAE,OAAO,QAAQ,SAAS,MAAM,CAAC,CAAC,CAAC,MAC5D,YAAY;GACX,IAAI,aAAa,eAAe,mBAAmB;GACnD,QAAQ,oBAAoB,eAAe,iBAAiB;GAC5D,SAAS;GACT,QAAQ,iBAAiB,eAAe,iBAAiB;GACzD,SAAS;IAAE,YAAY;IAAS,OAAO;IAAM,QAAQ,WAAW;GAAE,CAAC;EACrE,IACC,WAAoB;GACnB,IAAI,aAAa,eAAe,mBAAmB;GACnD,SAAS;IAAE,GAAG;IAAO,OAAO,QAAQ,MAAM;GAAE,CAAC;EAC/C,CACF;CACF;CAEA,OAAO;EACL;EACA,gBAAgB;EAChB,sBAAsB;EACtB,YAAY,aAAa;GACvB,UAAU,IAAI,QAAQ;GACtB,aAAa;IACX,UAAU,OAAO,QAAQ;GAC3B;EACF;EACA,eAAe;GACb,YAAY;GACZ,qBAAqB;GACrB,QAAQ,oBAAoB,eAAe,iBAAiB;GAC5D,SAAS;GACT,UAAU,MAAM;EAClB;CACF;AACF;;;;;;;;;;;;;ACpIA,SAAgB,kBACd,YACA,eACqB;CACrB,IAAI,UAAU;;CAGd,MAAM,2BAAW,IAAI,IAAe;CAEpC,MAAM,YAAY,UAAiB,QAAQ,KAAyB;CAEpE,SAAS,OAAO;EACd,IAAI,CAAC,YAAY;EACjB,MAAM,YAAY,IAAI,IAAI,WAAW,OAAO,OAAO,CAAC;EAEpD,KAAK,MAAM,SAAS,WAAW;GAC7B,IAAI,SAAS,IAAI,KAAK,GAAG;GACzB,MAAM,iBAAiB,eAAe,QAAQ;GAC9C,SAAS,IAAI,KAAK;EACpB;EACA,KAAK,MAAM,SAAS,CAAC,GAAG,QAAQ,GAAG;GACjC,IAAI,UAAU,IAAI,KAAK,GAAG;GAC1B,MAAM,oBAAoB,eAAe,QAAQ;GACjD,SAAS,OAAO,KAAK;EACvB;CACF;CAEA,KAAK;CACL,YAAY,iBAAiB,eAAe,IAAI;CAEhD,OAAO;EACL,SAAS,SAAS;GAChB,UAAU;EACZ;EACA,eAAe;GACb,YAAY,oBAAoB,eAAe,IAAI;GACnD,KAAK,MAAM,SAAS,UAClB,MAAM,oBAAoB,eAAe,QAAQ;GAEnD,SAAS,MAAM;EACjB;CACF;AACF;;;;ACvDA,MAAM,SAAS;CACb,UAAU;CACV,SAAS;CACT,YAAY;CACZ,gBAAgB;CAChB,gBAAgB;CAChB,kBAAkB;CAClB,YAAY;AACd;;;;;;;AAQA,MAAa,oBAAoB;;;;;;;;;AA2CjC,SAAgB,gBACd,YACA,UACmB;CACnB,IAAI,UAAU;CAyCd,OAAO;EACL,SAAS,SAAS;GAChB,UAAU;EACZ;EACA,SA3Cc,kBAAkB,aAAa,UAAU;GACvD,IAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,WAAW,GAAG;GAE5C,MAAM,SAAS,MAAM,KAAK;GAE1B,IAAI,UAAU,KAAM;GAEpB,MAAM,OAAO,SAAS;GACtB,MAAM,UAAU,SAAS;GACzB,MAAM,QAAQ,MAAM,KAAK,MAAM;GAC/B,MAAM,SAAS,MAAM,KAAK,MAAM;GAEhC,QAAQ,MAAR;IACE,KAAK,OAAO;KACV,QAAQ,iBAAiB,OAAO,OAAO;KACvC;IACF,KAAK,OAAO;KAEV,IAAI,WAAW,GAAG,QAAQ,iBAAiB,OAAO,OAAO;UACpD,QAAQ,gBAAgB,OAAO,QAAQ,OAAO;KACnD;IACF,KAAK,OAAO;KACV,QAAQ,oBAAoB,OAAO,QAAQ,OAAO;KAClD;IACF,KAAK,OAAO;KACV,QAAQ,uBAAuB,OAAO,QAAQ,OAAO;KACrD;IACF,KAAK,OAAO;KACV,QAAQ,uBAAuB,OAAO,OAAO;KAC7C;IACF,KAAK,OAAO;KACV,QAAQ,yBAAyB,OAAO,OAAO;KAC/C;IACF,KAAK,OAAO,YACV,QAAQ,mBAAoB,UAAU,IAAK,OAAO,OAAO;GAE7D;EACF,CAMiB,CAAC,CAAC;CACnB;AACF;;;;;;;;;;ACIA,MAAM,iBAAiB;CACrB,CAAC,gBAAgB,MAAM;CACvB,CAAC,eAAe,MAAM;CACtB,CAAC,uBAAuB,MAAM;CAC9B,CAAC,yBAAyB,MAAM;AAClC;AAOA,MAAM,qCAAqB,IAAI,QAAoC;;;;;;;;;;;;AA6CnE,SAAgB,WACd,SACA,UAAuB,CAAC,GACV;CACd,IAAI,OAAO;CACX,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,UAAU;CAChB,MAAM,QAAS,QAAiC;CAEhD,IAAI,gBAAgB,mBAAmB,IAAI,OAAO;CAClD,IAAI,OAAO;EACT,IAAI,eACF,cAAc,SAAS;OAClB;GACL,MAAM,2BAAW,IAAI,IAAiD;GACtE,KAAK,MAAM,CAAC,UAAU,UAAU,gBAAgB;IAC9C,SAAS,IAAI,UAAU;KACrB,OAAO,MAAM,iBAAiB,QAAQ;KACtC,UAAU,MAAM,oBAAoB,QAAQ;IAC9C,CAAC;IACD,MAAM,YAAY,UAAU,KAAK;GACnC;GACA,gBAAgB;IAAE,OAAO;IAAG;GAAS;GACrC,mBAAmB,IAAI,SAAS,aAAa;EAC/C;CACF;CAEA,MAAM,2BAAW,IAAI,IAA0B;;;;;;CAM/C,MAAM,0BAAU,IAAI,IAAyB;CAC7C,IAAI;;CAEJ,IAAI,kBAAiC;CACrC,IAAI,cAAc;CAClB,IAAI,YAAY;CAEhB,SAAS,MACP,OACA,SACA,QACA,QACW;EACX,OAAO;GACL,GACE,QAAQ,cAAc,KAAA,IAClB,QAAQ,YAAY,QAAQ,YAC5B,MAAM,UAAU,QAAQ;GAC9B,GACE,QAAQ,cAAc,KAAA,IAClB,QAAQ,YAAY,QAAQ,YAC5B,MAAM,UAAU,QAAQ;GAC9B;GACA;GACA,SAAS,MAAM;GACf,SAAS,MAAM;GACf,WAAW,MAAM;GACjB;EACF;CACF;;;;;;CAOA,SAAS,mBAAmB,OAAc;EACxC,MAAM,eAAe;CACvB;CAEA,SAAS,aAAa,QAAqB;EACzC,MAAM,QAAQ,QAAQ,IAAI,MAAM,KAAK;EACrC,IAAI,UAAU,GAAG;GACf,OAAO,iBAAiB,eAAe,iBAAiB;GACxD,OAAO,iBAAiB,aAAa,eAAe;GACpD,OAAO,iBAAiB,iBAAiB,eAAe;GACxD,IAAI,WAAW,SACb,OAAO,iBAAiB,sBAAsB,wBAAwB;EAE1E;EACA,QAAQ,IAAI,QAAQ,QAAQ,CAAC;CAC/B;CAEA,SAAS,cAAc,QAAqB;EAC1C,MAAM,QAAQ,QAAQ,IAAI,MAAM,KAAK;EACrC,IAAI,QAAQ,GAAG;GACb,QAAQ,IAAI,QAAQ,QAAQ,CAAC;GAC7B;EACF;EACA,OAAO,oBAAoB,eAAe,iBAAiB;EAC3D,OAAO,oBAAoB,aAAa,eAAe;EACvD,OAAO,oBAAoB,iBAAiB,eAAe;EAC3D,IAAI,WAAW,SACb,OAAO,oBAAoB,sBAAsB,wBAAwB;EAE3E,QAAQ,OAAO,MAAM;CACvB;CAEA,SAAS,kBAAkB,OAAc;EACvC,MAAM,eAAe;EACrB,IAAI,aAAa,WAAW,GAAG;EAC/B,MAAM,YAAY,aAAa;EAE/B,IAAI,SAAS,IAAI,SAAS,GAAG;EAC7B,IAAI,CAAC,gBAAgB,SAAS,OAAO,GAAG;EAExC,IAAI,KAAK,eAAe,CAAC,KAAK,YAAY,YAAY,GAAG;EAEzD,MAAM,UAAU,SAAS,SAAS;EAClC,IAAI,WAAW,KAAK,UAAU,OAAO;GACnC,iBAAiB,MAAM;GACvB,MAAM,SAAS,KAAK;EACtB;EAEA,QAAQ,oBAAoB,SAAS;EAGrC,MAAM,aACJ,QAAQ,oBAAoB,SAAS,MAAM,OACvC,UACC,WAAW,UAAU;EAE5B,MAAM,UAAwB;GAC5B;GACA,QAAQ,aAAa;GACrB,QAAQ,aAAa;GACrB,OAAO,aAAa;GACpB,OAAO,aAAa;GACpB,WAAW;GACX,WAAW;GACX,WAAW;EACb;EACA,SAAS,IAAI,WAAW,OAAO;EAE/B,aAAa,UAAU;EACvB,IAAI,SACF,WAAW,UAAU,iBAAiB,eAAe,kBAAkB;EAIzE,IAAI,WAAW,KAAK,aAAa;GAC/B,MAAM,YAAY,EAAE;GACpB,kBAAkB;GAClB,WAAW,UAAU,iBACnB,qBACA,gBACF;GACA,IAAI;IAOF,CAHgB,QAAQ,qBAAqB,EAAA,EAGpC,aACD;KACJ,IACE,cAAc,gBACb,CAAC,SAAS,IAAI,SAAS,KAAK,cAC7B,WAAW,UAAU,uBAAuB,SAE5C,WAAW,SAAS,kBAAkB;IAE1C,SACM,CAAC,CACT;GACF,QAAQ,CAER;EACF;EAEA,KAAK,cAAc,MAAM,cAAc,SAAS,GAAG,CAAC,CAAC;CACvD;CAEA,SAAS,kBAAkB,OAAc;EACvC,MAAM,eAAe;EACrB,MAAM,UAAU,SAAS,IAAI,aAAa,SAAS;EACnD,IAAI,CAAC,SAAS;EAId,MAAM,SAAS,QAAQ,cAAc,KAAA;EACrC,MAAM,SAAS,SACV,aAAa,aAAa,IAC3B,aAAa,UAAU,QAAQ;EACnC,MAAM,SAAS,SACV,aAAa,aAAa,IAC3B,aAAa,UAAU,QAAQ;EAKnC,MAAM,YAAY,KAAK,aAAa;EACpC,IAAI,KAAK,IAAI,MAAM,IAAI,aAAa,KAAK,IAAI,MAAM,IAAI,WAAW;EAElE,QAAQ,QAAQ,aAAa;EAC7B,QAAQ,QAAQ,aAAa;EAC7B,QAAQ,YAAY;EACpB,IAAI,QAAQ;GACV,QAAQ,aAAa;GACrB,QAAQ,aAAa;EACvB;EAEA,KAAK,SAAS,MAAM,cAAc,SAAS,QAAQ,MAAM,CAAC;CAC5D;CAEA,SAAS,mBAAmB;EAC1B,IAAI,oBAAoB,MAAM;EAC9B,MAAM,UAAU,SAAS,IAAI,eAAe;EAC5C,IAAI,CAAC,SAAS;EAEd,IAAI,WAAW,UAAU,uBAAuB,SAAS;GAIvD,QAAQ,YAAY,QAAQ,QAAQ,QAAQ;GAC5C,QAAQ,YAAY,QAAQ,QAAQ,QAAQ;GAC5C,QAAQ,YAAY;GACpB,QAAQ,YAAY;GACpB;EACF;EAKA,IAAI,QAAQ,cAAc,KAAA,GAAW;EACrC,WAAW,eAAe;CAC5B;CAEA,SAAS,gBAAgB,OAAc;EACrC,MAAM,eAAe;EACrB,WAAW,aAAa,WAAW,YAAY;CACjD;CAEA,SAAS,yBAAyB,OAAc;EAE9C,WAAWC,MAAa,SAAS;CACnC;CAEA,SAAS,WAAW,WAAmB,OAAsB;EAC3D,MAAM,UAAU,SAAS,IAAI,SAAS;EACtC,IAAI,CAAC,SAAS;EAEd,MAAM,aAAa,QACf,MACE,OACA,SACA,MAAM,UAAU,QAAQ,OACxB,MAAM,UAAU,QAAQ,KAC1B,IACA,MAAM,QAAQ,WAAW,SAAS,GAAG,CAAC;EAE1C,aAAa,SAAS;EACtB,KAAK,YAAY,UAAU;CAC7B;CAEA,SAAS,aAAa,WAAmB;EACvC,MAAM,UAAU,SAAS,IAAI,SAAS;EACtC,IAAI,CAAC,SAAS;EAEd,SAAS,OAAO,SAAS;EACzB,cAAc,QAAQ,UAAU;EAChC,IAAI;GACF,IAAI,QAAQ,oBAAoB,SAAS,MAAM,MAC7C,QAAQ,wBAAwB,SAAS;EAE7C,QAAQ,CAGR;EAEA,IAAI,oBAAoB,WAAW;GACjC,kBAAkB;GAClB,MAAM,WAAW,WAAW;GAC5B,UAAU,oBAAoB,qBAAqB,gBAAgB;GAEnE,IAAI,UAAU,uBAAuB,SAAS,SAAS,kBAAkB;EAC3E;EAEA,IAAI,SAAS,OAAO,GAAG;EAEvB,WAAW,UAAU,oBAAoB,eAAe,kBAAkB;EAC1E,IAAI,mBAAmB,KAAA,KAAa,OAAO;GACzC,MAAM,SAAS;GACf,iBAAiB,KAAA;EACnB;CACF;CAEA,QAAQ,iBAAiB,eAAe,iBAAiB;CAEzD,OAAO;EACL,SAAS,SAAS;GAChB,OAAO;IAAE,GAAG;IAAM,GAAG;IAAM;GAAa;EAC1C;EACA,eAAe;GACb,IAAI,WAAW;GACf,YAAY;GACZ,QAAQ,oBAAoB,eAAe,iBAAiB;GAC5D,KAAK,MAAM,aAAa,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,WAAW,SAAS;GAClE,IAAI,SAAS,iBAAiB,EAAE,cAAc,UAAU,GAAG;IACzD,KAAK,MAAM,CAAC,aAAa,gBAAgB;KACvC,MAAM,WAAW,cAAc,SAAS,IAAI,QAAQ;KACpD,IAAI,UAAU,OACZ,MAAM,YAAY,UAAU,SAAS,OAAO,SAAS,QAAQ;UAE7D,MAAM,eAAe,QAAQ;IAEjC;IACA,mBAAmB,OAAO,OAAO;GACnC;EACF;CACF;AACF;;;;;;;;;;;;;AC3aA,SAAgB,kBACd,SACA,SACqB;CACrB,IAAI,OAAO;CACX,IAAI,SAA8C;CAClD,IAAI,QAAQ;;;;;;CAMZ,IAAI,SAAS;CACb,IAAI,YAAY;CAEhB,MAAM,OAAO,WAAW,SAAS;EAC/B,WAAW;EACX,QAAQ;EACR,aAAa,KAAK;EAClB,cAAc,UAAU;GACtB,SAAS;GACT,QAAQ;GACR,SAAS,eACP,KAAK,eAAe,0BACpB,MAAM,KACR,CAAC,CAAC;EACJ;EACA,SAAS,UAAU;GACjB,MAAM,EAAE,MAAM;GACd,IAAI,CAAC,QAAQ;IACX,SAAS;KAAE;KAAG,OAAO,KAAK,SAAS;IAAE;IACrC,YAAY;IACZ;GACF;GAEA,MAAM,cAAc,KAAK,eAAe;GAKxC,MAAM,OAAO,eAAe,aAAa,MAAM,KAAK,CAAC,CAAC;GACtD,IAAI,SAAS,QAAQ;IACnB,SAAS;KAAE,GAAG;KAAW,OAAO,KAAK,SAAS;IAAE;IAChD,SAAS;GACX;GACA,YAAY;GAEZ,MAAM,QAAQ,KAAK,MAAM,EAAE,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE;GAC7D,IAAI,UAAU,GAAG;GACjB,QAAQ;GAKR,MAAM,OAAO,KAAK,MAAM,QAAQ;GAChC,MAAM,UAAU,YAAY,cAAc,MAAwB,CAChE,OACA,OAAO,CACT,CAAC;GACD,KAAK,SACH,WAAW,OAAO,OAAO,OAAO,SAAS,KAAK,OAAO,MAAM,KAAK,CAClE;EACF;EACA,iBAAiB;GACf,QAAQ;EACV;CACF,CAAC;CAED,OAAO;EACL,SAAS,SAAS;GAChB,OAAO;IAAE,GAAG;IAAM,GAAG;GAAK;GAC1B,IAAI,iBAAiB,MAAM,KAAK,OAAO,EAAE,aAAa,KAAK,YAAY,CAAC;EAC1E;EACA,aAAa;EACb,eAAe,KAAK,QAAQ;CAC9B;AACF;;;;;;;;;;;;AC5FA,SAAgB,kBAAkB,EAChC,KACA,KACA,MACA,OACA,aAAa,QACgC;CAC7C,MAAM,KAAK,aAAa,MAAM,KAAA;CAC9B,MAAM,KAAK,aAAa,MAAM,KAAA;CAC9B,OAAO;EACL,YAAY;GACV,KAAK,MAAM,OAAO;GAClB,KAAK,MAAM,OAAO;GAClB;GACA;EACF;EACA,KAAK;GACH,KAAK,MAAM,CAAC,OAAO;GACnB,KAAK,MAAM,OAAO;GAClB;GACA;EACF;CACF;AACF;;;;;AAMA,SAAgB,iBACd,OACA,WACA,SACA,QACA,WACQ;CACR,MAAM,CAAC,QAAQ,eAAe,SAAS,SAAS,CAAC,CAAC;CAClD,OAAO,WACL,OACA,WACA,SACA,SAAS,QAAQ,OAAO,MAAM,OAAO,YACrC,SACF;AACF;;;;;;AAOA,SAAgB,kBACd,OACA,EAAE,KAAK,KAAK,aAAa,QACzB;CACA,OAAO;EACL,OAAO,cAAc,QAAQ,KAAA,KAAa,SAAS;EACnD,OAAO,cAAc,QAAQ,KAAA,KAAa,SAAS;EACnD,YACG,QAAQ,KAAA,KAAa,QAAQ,OAAS,QAAQ,KAAA,KAAa,QAAQ;CACxE;AACF;;;;;;;;;AAUA,SAAgB,sBACd,MACA,OACA,EAAE,KAAK,KAAK,aAAa,QACV;CACf,MAAM,SAAS,MAAM,IAAI;CACzB,IAAI,CAAC,OAAO,SAAS,MAAM,GAAG,OAAO;CACrC,IAAI,YAAY;CAChB,IAAI,cAAc,QAAQ,KAAA,GAAW,YAAY,KAAK,IAAI,WAAW,GAAG;CACxE,IAAI,cAAc,QAAQ,KAAA,GAAW,YAAY,KAAK,IAAI,WAAW,GAAG;CACxE,OAAO;AACT;;;;;;;;;;;ACvHA,MAAM,QAAQ;;;;;;;;AAed,SAAgB,WAAW,MAAiC;CAC1D,MAAM,QAAQ,KAAK,OAAO,IAAI;CAC9B,IAAI,UAAU,IAAI,OAAO;CACzB,IAAI,QAAQ;CACZ,IAAI,KAAK,QAAQ,OAAO,KAAK,SAAS;CACtC,IAAI,QAAQ,KAAK,MAAM,SAAS,KAAK,QAAQ,EAAE,GAAG,SAAS;CAC3D,OAAO;EAAE;EAAO,KAAK,KAAK,OAAO,QAAQ,IAAI;CAAE;AACjD;;AAGA,MAAM,eACJ;;;;;;;;;;;;AAaF,SAAgB,gBAAgB,MAAsB;CACpD,MAAM,OAAO,WAAW,IAAI;CAC5B,IAAI,CAAC,QAAQ,KAAK,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC,KAAK,MAAM,IAAI,OAAO;CAC7D,MAAM,SAAS,KAAK,MAAM,KAAK,OAAO,KAAK,GAAG;CAC9C,OAAO,aAAa,KAAK,MAAM,IAC3B,OAAO,OAAO,QAAQ,WAAW,GAAG,CAAC,IACrC;AACN;;;;;;;;;;AAWA,SAAS,cAAc,MAAc,MAAkB;CACrD,MAAM,SAAS,KAAK,MAAM,KAAK,OAAO,KAAK,GAAG;CAC9C,MAAM,WAAW,OAAO,OAAO,oBAAoB;CACnD,MAAM,WAAW,aAAa,KAAK,SAAS,OAAO,MAAM,GAAG,QAAQ;CACpE,MAAM,MAAM,SAAS,QAAQ,GAAG;CAChC,OAAO,KAAK,SAAS,QAAQ,KAAK,SAAS,SAAS;AACtD;AAEA,MAAM,YAAwB;CAAE,OAAO;CAAG,KAAK;AAAE;;;;;;AAOjD,SAAgB,mBAAmB,MAAc,OAAuB;CACtE,OAAO,QAAQ,cAAc,MAAM,WAAW,IAAI,KAAK,SAAS;AAClE;;;;;;;;;;;AAYA,SAAgB,qBAAqB,MAAc,QAAwB;CACzE,MAAM,OAAO,WAAW,IAAI,KAAK;CACjC,MAAM,QAAQ,cAAc,MAAM,IAAI,IAAI;CAC1C,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,IAAI,OAAO,KAAK,GAAG,CAAC;AACvD;;;;;;;;;;;;;;;;ACtFA,SAAgB,eACd,UACA,MACY;CAIZ,OAAO;EAAE,GAHO,OAAO,YACrB,OAAO,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,KAAK,KAAA,CAAS,CAAC,CAEzC;EAAG,GAAG;CAAK;AAC/B;;;;;;ACLA,SAAgB,kBAAkB,WAAgC;CAChE,OAAO,MAAM,KACX,EAAE,QAAQ,UAAU,OAAO,UAAU,QAAQ,EAAE,IAC9C,GAAG,MAAM,IAAI,UAAU,KAC1B;AACF;AAqCA,MAAM,kBAAkB;AACxB,MAAM,gCAAgC;AACtC,MAAM,iCAAiC;;;;;;;;AASvC,MAAM,kBAA2C;CAC/C,GAAG;CACH,MAAM;CACN,GAAG;CACH,MAAM;CACN,GAAG;CACH,GAAG;CACH,MAAM;CACN,GAAG;CACH,MAAM;CACN,GAAG;CACH,MAAM;CACN,GAAG;AACL;;AAGA,SAAgB,cAAc,QAA6B;CACzD,OACE,OAAO,iBACN,OAAO,sBAAsB;AAElC;AAEA,SAAS,gBAAgB,MAAc,QAA6B;CAClE,MAAM,OAAO,OAAO,iBAAiB,OAAO,UAAU;CACtD,MAAM,SAAS,QAAQ,IAAI;CAC3B,MAAM,QAAQ,QAAQ,OAAO,UAAU,KAAK;CAE5C,MAAM,SAAS,KAAK,OAAO,OAAO,OAAO,UAAU,SAAS,EAAE;CAC9D,MAAM,eACJ,SAAS,QAAQ,KAAK,IAAI,SAAS,QAAQ,MAAM,IAAI,IAAI;CAC3D,MAAM,cACJ,gBAAgB,UAChB,gBAAgB,UACf,SAAS,gBAAgB;CAE5B,OAAO,WAAW,IAAI,IAClB,cAAc,OAAO,cAAc,MAAM,IAAI,IAC7C,cAAc;AACpB;AAEA,SAAS,YAAY,QAAqB;CACxC,MAAM,QAAQ,kBAAkB,OAAO,SAAS;CAChD,IAAI,MAAM,WAAW,GAAG,OAAO;EAAE,MAAM;EAAG,OAAO;CAAE;CAEnD,IAAI,OAAO;CACX,IAAI,QAAQ;CACZ,MAAM,aAAa,OAAO,iBAAiB,OAAO,UAAU;CAC5D,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,WAAW,gBAAgB,MAAM,MAAM;EAC7C,MAAM,QAAQ,WAAW,IAAI,IAAI,cAAc,MAAM,IAAI;EACzD,OAAO,KAAK,IAAI,MAAM,QAAQ;EAC9B,QAAQ,KAAK,IAAI,OAAO,WAAW,KAAK;CAC1C;CACA,OAAO;EAAE;EAAM;CAAM;AACvB;;AAGA,SAAgB,WAAW,QAA6B;CACtD,MAAM,EAAE,MAAM,UAAU,YAAY,MAAM;CAC1C,OAAO,QAAQ;AACjB;;;;;;;;AASA,SAAgB,aAAa,MAAc,QAA6B;CACtE,OAAO,gBAAgB,MAAM,MAAM,IAAI,YAAY,MAAM,CAAC,CAAC;AAC7D;;;;;;;;;;;;AAaA,SAAgB,OACd,GACA,GACA,QACA,QACe;CACf,IAAI,IAAI,KAAK,KAAK,WAAW,MAAM,KAAK,IAAI,KAAK,KAAK,QAAQ,OAAO;CAErE,MAAM,QAAQ,kBAAkB,OAAO,SAAS;CAIhD,IAAI,IAFF,UAAU,OAAO,uBAAuB,iCAGxC,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,WAAW,IAAI,GAAG;EACtB,MAAM,OAAO,aAAa,MAAM,MAAM;EACtC,IAAI,QAAQ,KAAK,IAAI,OAAO,cAAc,MAAM,GAAG,OAAO;CAC5D;CAGF,MAAM,OAAO,OAAO,iBAAiB,OAAO,UAAU;CACtD,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,WAAW,IAAI,GAAG;EACtB,MAAM,OAAO,aAAa,MAAM,MAAM;EACtC,IAAI,QAAQ,KAAK,IAAI,OAAO,MAAM,OAAO;CAC3C;CAEA,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,iBACd,OACA,QACQ;CACR,MAAM,MAAM,kBAA0B,WAAW;EAAE,GAAG;EAAQ;CAAc,CAAC;CAC7E,MAAM,QAAQ,GAAG,CAAC,IAAI,GAAG,CAAC;CAC1B,IAAI,SAAS,GAAG,OAAO;CACvB,QAAQ,SAAS,GAAG,CAAC,IAAI,UAAU;AACrC;;;;;;AC1LA,MAAa,YAAY;;CAEvB,UAAU,EACR,MAAM;EACJ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,EACF;;CAEA,kBAAkB,EAChB,MAAM;EACJ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,EACF;AACF;;;;;AAaA,SAAgB,iBAAiB,QAA4B;CAC3D,IAAI,EAAE,kBAAkB,cAAc,OAAO;CAC7C,IAAI,OAAO,QAAQ,yBAAyB,GAAG,OAAO;CAEtD,KAAK,IAAI,UAA8B,QAAQ,UAAU;EACvD,MAAM,kBAAkB,QAAQ,aAAa,iBAAiB;EAC9D,IAAI,oBAAoB,MACtB,OAAO,gBAAgB,YAAY,MAAM;EAC3C,UAAU,QAAQ;CACpB;CAEA,OAAO;AACT;;;ACOA,MAAM,iBAA6B;;;;;;;;;;;AAYnC,SAAgB,iBACd,SACA,SACoB;CACpB,IAAI,OAAO;;CAGX,MAAM,uBAAO,IAAI,IAA6B;;CAE9C,MAAM,+BAAe,IAAI,IAAoB;;;;;CAK7C,MAAM,gCAAgB,IAAI,IAAoB;CAE9C,SAAS,cAAwB;EAC/B,OAAO,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAC9C;CAEA,SAAS,OACP,MACA,EACE,SAAS,gBACT,aAC8C,CAAC,GACjD;EACA,IAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,KAChD,MAAM,IAAI,WAAW,8CAA8C;EAErE,IAAI,QAAQ,KAAK,WAAW,MAAM;EAElC,MAAM,UAAU,KAAK,IAAI,IAAI;EAC7B,IAAI,SAAS;GAEX,QAAQ,IAAI,MAAM;GAClB;EACF;EAEA,KAAK,IAAI,sBAAM,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC;EAChC,KAAK,aAAa,MAAM,QAAQ;EAChC,KAAK,sBAAsB,YAAY,CAAC;CAC1C;CAEA,SAAS,QACP,MACA,EAAE,SAAS,mBAA4C,CAAC,GACxD;EACA,IAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,KAChD,MAAM,IAAI,WAAW,8CAA8C;EAErE,MAAM,UAAU,KAAK,IAAI,IAAI;EAC7B,IAAI,CAAC,SAAS;EAEd,QAAQ,OAAO,MAAM;EACrB,IAAI,QAAQ,OAAO,GAAG;EAEtB,KAAK,OAAO,IAAI;EAChB,KAAK,aAAa,IAAI;EACtB,KAAK,sBAAsB,YAAY,CAAC;CAC1C;;CAGA,SAAS,UAAU,SAAiB,SAAgC;EAClE,MAAM,EAAE,MAAM,KAAK,WAAW,QAAQ,sBAAsB;EAC5D,OAAO,OAAO,UAAU,MAAM,UAAU,KAAK,QAAQ,KAAK,MAAM;CAClE;;CAGA,SAAS,YAAY,WAAmB,MAAqB;EAC3D,MAAM,SAAS,WAAW;EAC1B,MAAM,WAAW,aAAa,IAAI,SAAS;EAC3C,IAAI,aAAa,MAAM;EAEvB,IAAI,aAAa,KAAA,GAAW,QAAQ,UAAU,EAAE,OAAO,CAAC;EAExD,IAAI,SAAS,QAAQ,QAAQ,KAAK,WAAW,MAC3C,aAAa,OAAO,SAAS;OACxB;GACL,aAAa,IAAI,WAAW,IAAI;GAChC,OAAO,MAAM,EAAE,OAAO,CAAC;EACzB;CACF;;CAGA,SAAS,aAAa,KAAa;EACjC,MAAM,OAAO,KAAK,mBAAmB;EACrC,IAAI,CAAC,QAAQ,QAAQ,IAAI,OAAO;EAChC,MAAM,EAAE,OAAO,SAAS,KAAK,OAAO;EACpC,MAAM,QAAQ,KAAK,QAAQ,GAAG;EAC9B,MAAM,OAAO,QAAQ;EACrB,OAAO,UAAU,MAAM,OAAO,OAAO,OAAO;CAC9C;CAEA,SAAS,UAAU,OAAsB;EACvC,MAAM,KAAK,MAAM,QAAQ,MAAM;EAC/B,IAAI,MAAM,UAAU,cAAc,IAAI,EAAE,GAAG;EAC3C,IAAI,iBAAiB,MAAM,MAAM,GAAG;EACpC,MAAM,OAAO,aAAa,MAAM,GAAG;EACnC,IAAI,SAAS,MAAM;EACnB,cAAc,IAAI,IAAI,IAAI;EAC1B,OAAO,MAAM,EAAE,QAAQ,YAAY,KAAK,CAAC;CAC3C;CAEA,SAAS,QAAQ,OAAsB;EACrC,MAAM,KAAK,MAAM,QAAQ,MAAM;EAC/B,MAAM,OAAO,cAAc,IAAI,EAAE;EACjC,IAAI,SAAS,KAAA,GAAW;EACxB,cAAc,OAAO,EAAE;EACvB,QAAQ,MAAM,EAAE,QAAQ,YAAY,KAAK,CAAC;CAC5C;CAEA,SAAS,mBAAmB;EAC1B,KAAK,MAAM,CAAC,IAAI,SAAS,eACvB,QAAQ,MAAM,EAAE,QAAQ,YAAY,KAAK,CAAC;EAE5C,cAAc,MAAM;CACtB;CAEA,SAAS,WAAW,OAAc;EAChC,MAAM,OAAQ,MAAqB;EAEnC,IAAI,gBAAgB,QAAQ,QAAQ,SAAS,IAAI,GAAG;EACpD,iBAAiB;CACnB;;CAGA,IAAI,aAA4C;CAEhD,SAAS,gBAAgB;EACvB,MAAM,QAAQ,KAAK,oBACd,KAAK,0BAA0B,SAChC;EACJ,IAAI,UAAU,YAAY;EAC1B,gBAAgB;EAChB,MAAM,MAAM,WAAW;EACvB,IAAI,CAAC,SAAS,CAAC,KAAK;EACpB,MAAM,SAAsB,UAAU,WAAW,MAAM;EACvD,OAAO,iBAAiB,WAAW,SAA0B;EAC7D,OAAO,iBAAiB,SAAS,OAAwB;EACzD,IAAI,UAAU,QAAQ,QAAQ,iBAAiB,YAAY,UAAU;EAGrE,IAAI,iBAAiB,QAAQ,gBAAgB;EAC7C,aAAa;CACf;CAEA,SAAS,kBAAkB;EACzB,MAAM,MAAM,WAAW;EACvB,IAAI,CAAC,cAAc,CAAC,KAAK;EACzB,MAAM,SAAsB,eAAe,WAAW,MAAM;EAC5D,OAAO,oBAAoB,WAAW,SAA0B;EAChE,OAAO,oBAAoB,SAAS,OAAwB;EAC5D,QAAQ,oBAAoB,YAAY,UAAU;EAClD,IAAI,oBAAoB,QAAQ,gBAAgB;EAChD,aAAa;CACf;;;;;;CAOA,SAAS,kBAAkB;EACzB,MAAM,EAAE,OAAO,SAAS,KAAK,OAAO;EACpC,OAAO;GACL,KAAK,mBAAmB,KAAK,KAAK,IAAQ,KAAK;GAC/C,KAAK,0BAA0B;GAC/B;GACA;EACF,CAAC,CAAC,KAAK,GAAQ;CACjB;CAEA,cAAc;CAEd,MAAM,OAAO,WAAW,SAAS;EAC/B,cAAc;EACd,cAAc,UACZ,YAAY,MAAM,WAAW,UAAU,MAAM,SAAS,MAAM,OAAO,CAAC;EACtE,SAAS,UAAU;GAGjB,IAAI,KAAK,cAAc,OAAO;GAC9B,YAAY,MAAM,WAAW,UAAU,MAAM,SAAS,MAAM,OAAO,CAAC;EACtE;EACA,YAAY,UAAU,YAAY,MAAM,WAAW,IAAI;CACzD,CAAC;CAED,OAAO;EACL,SAAS,SAAS;GAChB,MAAM,UAAU,gBAAgB;GAChC,OAAO;IAAE,GAAG;IAAM,GAAG;GAAK;GAC1B,IAAI,gBAAgB,MAAM,SAAS,iBAAiB;GACpD,cAAc;GAEd,MAAM,UAAU,KAAK,WAAW;GAChC,MAAM,eAAe,YAAY,CAAC,CAAC,QAAQ,SAAS,OAAO,OAAO;GAClE,IAAI,aAAa,WAAW,GAAG;GAE/B,KAAK,MAAM,QAAQ,cAAc;IAC/B,KAAK,OAAO,IAAI;IAChB,KAAK,aAAa,IAAI;GACxB;GACA,KAAK,MAAM,CAAC,WAAW,SAAS,cAC9B,IAAI,OAAO,SAAS,aAAa,OAAO,SAAS;GAEnD,KAAK,sBAAsB,YAAY,CAAC;EAC1C;EACA;EACA;EACA;EACA,eAAe;GACb,KAAK,QAAQ;GACb,gBAAgB;GAChB,cAAc,MAAM;GAGpB,MAAM,QAAQ,YAAY;GAC1B,KAAK,MAAM,QAAQ,OAAO;IACxB,KAAK,OAAO,IAAI;IAChB,KAAK,aAAa,IAAI;GACxB;GACA,aAAa,MAAM;GACnB,IAAI,MAAM,SAAS,GAAG,KAAK,sBAAsB,CAAC,CAAC;EACrD;CACF;AACF;;;AChRA,SAAS,OAAO,MAAkB,IAAkC;CAClE,OAAO;EACL,GAAG,KAAK,IAAI,KAAK,IAAI,GAAG,EAAE;EAC1B,GAAG,KAAK,IAAI,KAAK,IAAI,GAAG,EAAE;EAC1B,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,EAAE;EAC/B,QAAQ,KAAK,IAAI,GAAG,KAAK,KAAK,EAAE;CAClC;AACF;;AAGA,SAAgB,mBACd,KACA,CAAC,GAAG,IACK;CACT,OACE,KAAK,IAAI,KACT,KAAK,IAAI,IAAI,IAAI,SACjB,KAAK,IAAI,KACT,KAAK,IAAI,IAAI,IAAI;AAErB;;;;;;;AAQA,SAAgB,mBACd,SAC0B;CAC1B,IAAI,OAAO;CACX,IAAI,OAAyD;CAC7D,IAAI,MAA+B;CAEnC,SAAS,OAAO,MAA+B;EAC7C,MAAM;EACN,KAAK,cAAc,IAAI;CACzB;CAEA,SAAS,MAAM,MAAwB,MAAqB;EAC1D,MAAM,SAAe,CAAC;EACtB,KAAK,MAAM,CAAC,IAAI,OAAO,KAAK,MAAM,GAChC,IAAI,mBAAmB,MAAM,EAAE,KAAK,CAAC,KAAK,SAAS,EAAE,GAAG,OAAO,KAAK,EAAE;EAExE,KAAK,oBAAoB,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC;CAC/C;CAEA,OAAO;EACL,SAAS,SAAS;GAChB,OAAO;IAAE,GAAG;IAAM,GAAG;GAAK;EAC5B;EACA,QAAQ,IAAI,EAAE,WAAW,OAAO,YAAY,CAAC,MAAM,CAAC,MAAM;GACxD,OAAO;IAAE,MAAM;IAAI,MAAM,WAAW,CAAC,GAAG,SAAS,IAAI,CAAC;GAAE;GACxD,OAAO,OAAO,IAAI,EAAE,CAAC;GAGrB,IAAI,CAAC,UAAU,KAAK,oBAAoB,CAAC,CAAC;EAC5C;EACA,OAAO,OAAO;GACZ,IAAI,CAAC,MAAM;GACX,MAAM,OAAO,OAAO,KAAK,MAAM,EAAE;GACjC,OAAO,IAAI;GACX,MAAM,MAAM,KAAK,IAAI;EACvB;EACA,WAAW;GACT,MAAM,UAAU,SAAS;GACzB,OAAO;GACP,IAAI,QAAQ,MAAM,OAAO,IAAI;GAC7B,OAAO;EACT;EACA,WAAW;EACX,eAAe;GACb,OAAO;GACP,MAAM;EACR;CACF;AACF;;;;;;;AClHA,MAAa,aAAa;CAAE,KAAK;CAAG,KAAK;AAAE;;;;;AAM3C,MAAa,8BAA+D,CAC1E,cACA,GACF;;;;;;;AAQA,MAAa,iCAAkE;CAC7E,SAAS,CAAC,cAAc,GAAI;CAC5B,OAAO,CAAC,cAAc,IAAK;AAC7B;;AAGA,SAAgB,WACd,OACA,KACA,KACe;CACf,OAAO;EACL,GAAG,MAAM,MAAM,GAAG,KAAK,KAAK,GAAG,KAAK,KAAK,CAAC;EAC1C,GAAG,MAAM,MAAM,GAAG,KAAK,KAAK,GAAG,KAAK,KAAK,CAAC;CAC5C;AACF;AAwGA,MAAM,QAA2B,CAAC;;;;;;;;AASlC,SAAS,aACP,OACA,SACe;CACf,IAAI,MAAM;CACV,IAAI,MAAM;CACV,IAAI,MAAM;CACV,IAAI,MAAM;CACV,KAAK,MAAM,EAAE,OAAO,WAAW,SAAS;EAGtC,IAAI,MAAM,UAAU;EACpB,MAAM,KAAK,IAAI,MAAM,MAAM,KAAK,KAAK,KAAK,MAAM,CAAC;EACjD,MAAM,KAAK,IAAI,MAAM,MAAM,KAAK,KAAK,KAAK,MAAM,CAAC;EACjD,MAAM,KAAK,IAAI,MAAM,MAAM,KAAK,KAAK,KAAK,MAAM,CAAC;EACjD,MAAM,KAAK,IAAI,MAAM,MAAM,KAAK,KAAK,KAAK,MAAM,CAAC;CACnD;CAEA,OAAO;EACL,GAAG,MAAM,MAAM,IAAI,MAAM,MAAM,GAAG,KAAK,GAAG;EAC1C,GAAG,MAAM,MAAM,IAAI,MAAM,MAAM,GAAG,KAAK,GAAG;CAC5C;AACF;;;;;;;;;AAUA,SAAgB,mBACd,UAA+B,CAAC,GACV;CACtB,IAAI,OAAO;CACX,MAAM,yBAAS,IAAI,IAAqC;;CAExD,IAAI,UAAkD,CAAC;CAEvD,MAAM,kBAAmB,KAAK,aAAc,KAAK,aAAa,QAAS;CAEvE,SAAS,gBAAgB,MAAgB;EAGvC,OAAO;GAAE,GAAG;GAAM,WAAW;EAAK;EAClC,KAAK,oBAAoB,IAAI;CAC/B;CAEA,SAAS,SAAS,KAAwB;EACxC,OAAO,IAAI,SAAS,OAAO;GACzB,MAAM,QAAQ,OAAO,IAAI,EAAE,CAAC,GAAG;GAC/B,OAAO,QAAQ,CAAC;IAAE;IAAI,OAAO,EAAE,GAAG,MAAM,MAAM;GAAE,CAAC,IAAI,CAAC;EACxD,CAAC;CACH;CAEA,SAAS,SACP,SACA,OACA;EACA,MAAM,YAAY,QAAQ,SAAS,UAAU;GAC3C,MAAM,QAAQ,OAAO,IAAI,MAAM,EAAE,CAAC,GAAG;GACrC,OAAO,QAAQ,CAAC;IAAE,GAAG;IAAO;GAAM,CAAC,IAAI,CAAC;EAC1C,CAAC;EACD,MAAM,UAAU,aAAa,OAAO,SAAS;EAC7C,KAAK,MAAM,EAAE,OAAO,WAAW,WAAW;GACxC,IAAI,MAAM,UAAU;GAIpB,MAAM,WAAW;IACf,GAAG,YAAY,MAAM,IAAI,QAAQ,CAAC;IAClC,GAAG,YAAY,MAAM,IAAI,QAAQ,CAAC;GACpC,CAAC;EACH;CACF;CAEA,SAAS,eAAe,IAAY,OAAsB;EACxD,MAAM,UAAU,UAAU;EAC1B,SAAS,SAAS,QAAQ,SAAS,EAAE,IAAI,UAAU,CAAC,EAAE,CAAC,GAAG,KAAK;CACjE;CAEA,SAAS,WACP,IACA,MACA,WACA,QACA,WACA;EACA,MAAM,QAAQ,OAAO,IAAI,EAAE,CAAC,GAAG;EAC/B,IAAI,CAAC,OAAO;EACZ,MAAM,EAAE,UAAU;EAClB,MAAM,OAAO,WACX,MAAM,OACN,WACA,QACA,YACA,SACF;EAGA,eAAe,IAAI;GACjB,GAAG,SAAS,MAAM,OAAO,MAAM,IAAI;GACnC,GAAG,SAAS,MAAM,OAAO,MAAM,IAAI;EACrC,CAAC;CACH;CAEA,MAAM,MAAM,mBAA2B;EACrC,CAAC,QAAkD;GACjD,KAAK,MAAM,CAAC,IAAI,SAAS,QAAQ;IAC/B,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,CAAC;IACxB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;GACnB;EACF;EACA,cAAc,SAAS,KAAK,uBAAuB,IAAI;EACvD,mBAAmB;CACrB,CAAC;CAED,OAAO;EACL,SAAS,SAAS;GAChB,OAAO;IAAE,GAAG;IAAM,GAAG;GAAK;EAC5B;EACA,gBAAgB,IAAI,SAAS;GAC3B,OAAO,IAAI,IAAI,IAAI;GACnB,aAAa;IACX,IAAI,OAAO,IAAI,EAAE,MAAM,MAAM,OAAO,OAAO,EAAE;GAC/C;EACF;EACA,iBAAiB,YAAY;GAC3B,IAAI,CAAC,SAAS,OAAO;GACrB,KAAK,MAAM,QAAQ,OAAO,OAAO,GAC/B,IAAI,KAAK,CAAC,CAAC,SAAS,SAAS,OAAO,GAAG,OAAO;GAEhD,OAAO;EACT;EACA,iBAAiB,IAAI,cAAc;GACjC,IAAI,CAAC,KAAK,YAAY;IACpB,UAAU,SAAS,CAAC,EAAE,CAAC;IACvB;GACF;GACA,MAAM,UAAU,UAAU;GAC1B,MAAM,WAAW,UAAU,WAAW,UAAU;GAChD,IAAI;GACJ,IAAI,UACF,OAAO,QAAQ,SAAS,EAAE,IACtB,QAAQ,QAAQ,MAAM,MAAM,EAAE,IAC9B,CAAC,GAAG,SAAS,EAAE;QACd,IAAI,QAAQ,SAAS,EAAE,GAC5B,OAAO;QAEP,OAAO,CAAC,EAAE;GAEZ,gBAAgB,CAAC,GAAG,IAAI,CAAC;GAGzB,UAAU,KAAK,SAAS,EAAE,IAAI,SAAS,IAAI,IAAI,CAAC;EAClD;EACA,gBAAgB,UAAU,SAAS,SAAS,KAAK;EACjD;EACA;EACA,oBAAoB,MAAM,WAAW,cAAc;GACjD,KAAK,MAAM,CAAC,IAAI,SAAS,QAAQ;IAC/B,MAAM,EAAE,SAAS,OAAO,UAAU,aAAa,KAAK;IAGpD,MAAM,SAAS,SAAS,cAAc;IAGtC,IAAI,CAAC,UAAU,CAAC,SAAS,SAAS,MAAM,GAAG;IAC3C,IAAI,CAAC,SAAS,YAAY,CAAC,UAAU,OAAO;IAC5C,WAAW,IAAI,MAAM,WAAW,OAAO,SAAS;IAChD,OAAO;GACT;GACA,OAAO;EACT;EACA,oBAAoB,IAAI,cAAc;GACpC,IAAI,CAAC,KAAK,YAAY;GACtB,IAAI,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG;IACtB,UAAU,UAAU,WAAW,UAAU;IACzC,WAAW,UAAU;GACvB,CAAC;EACH;EACA,mBAAmB,OAAO,IAAI,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;EAC/C,uBAAuB;GACrB,IAAI,CAAC,IAAI,IAAI,GAAG;GAChB,MAAM,CAAC,SAAS,UAAU;GAC1B,MAAM,UAAU,QAAQ,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,UAAU;GACxD,IAAI,WAAW,WAAW,SAAS,QAAyB,MAAM;EACpE;EACA,eAAe;GACb,IAAI,QAAQ;GACZ,UAAU,CAAC;EACb;CACF;AACF;;;ACrVA,SAAS,OACP,OACgC;CAChC,OAAO,MAAM,QAAQ,KAAK;AAC5B;;;;;;;;;;;AAYA,SAAgB,KAAQ,OAAyC;CAC/D,OAAO,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,MAAM,EAAE,IAAI,CAAC,OAAO,KAAK;AAC7D;;;;;;;;;;ACuBA,SAAgB,eACd,YACA,EACE,gBAeE,CAAC,GACa;;CAElB,IAAI,SAAqB,CAAC,GAAG,CAAC;;CAE9B,IAAI,SAAqB,CAAC,GAAG,CAAC;;CAE9B,IAAI,WAAuB,CAAC,GAAG,CAAC;CAChC,IAAI,SAAS;CAEb,SAAS,WAAW,OAAqC;EACvD,MAAM,UAAU,WAAW;EAC3B,IAAI,CAAC,SAAS,OAAO;EACrB,MAAM,EAAE,MAAM,KAAK,OAAO,WAAW,QAAQ,sBAAsB;EAGnE,OAAO,CACL,QAAQ,OAAO,eAAe,MAAM,SAAS,MAAM,KAAK,IAAI,GAC5D,SAAS,MAAM,eAAe,MAAM,SAAS,KAAK,MAAM,IAAI,CAC9D;CACF;CAEA,MAAM,YAAY,KAAiB,OAA2B,CAC5D,OAAO,MAAM,IAAI,KAAK,OAAO,MAAM,IACnC,OAAO,MAAM,IAAI,KAAK,OAAO,MAAM,EACrC;CAEA,OAAO;EACL,QAAQ,UAAU;GAChB,MAAM,MAAM,WAAW,KAAK;GAC5B,IAAI,CAAC,KAAK,OAAO;GAGjB,SAAS;GACT,SAAS;GACT,WAAW;GAGX,SAAS,cAAc,YAAY,KAAK,IAAI;GAC5C,OAAO;EACT;EACA,OAAO,UAAU;GACf,MAAM,MAAM,WAAW,KAAK;GAC5B,IAAI,CAAC,KAAK,OAAO;GAEjB,MAAM,OAAO,cAAc,YAAY,KAAK,IAAI;GAChD,IAAI,SAAS,QAAQ;IAOnB,SAAS,SAAS,UAAU,MAAM;IAClC,SAAS;IACT,SAAS;GACX;GACA,WAAW;GAIX,OAAO,SAAS,KAAK,MAAM;EAC7B;CACF;AACF;;;;;;AAOA,SAAgB,gBAAgB,EAC9B,aAAa,KACb,gBAgBE,CAAC,GAAqB;CACxB,MAAM,CAAC,OAAO,SAAS,KAAK,UAAU;CACtC,IAAI,SAAqB,CAAC,GAAG,CAAC;;CAE9B,IAAI,SAAqB,CAAC,GAAG,CAAC;;CAE9B,IAAI,WAAuB,CAAC,GAAG,CAAC;CAChC,IAAI,SAAS;CAEb,MAAM,aAAa,IAAgB,OAA2B,CAC5D,UAAU,IAAI,OAAO,KAAK,OAAO,MAAO,GAAG,KAAK,OAAO,MAAM,KAAM,OACnE,UAAU,IAAI,OAAO,KAAK,OAAO,MAAO,GAAG,KAAK,OAAO,MAAM,KAAM,KACrE;CAEA,OAAO;EACL,QAAQ,OAAO,YAAY;GACzB,SAAS,QAAQ,SAAS;GAC1B,SAAS,CAAC,GAAG,CAAC;GACd,WAAW,CAAC,GAAG,CAAC;GAGhB,SAAS,cAAc,YAAY,KAAK,IAAI;GAC5C,OAAO;EACT;EAIA,OAAO,UAAU;GACf,MAAM,OAAO,cAAc,YAAY,KAAK,IAAI;GAChD,IAAI,SAAS,QAAQ;IAOnB,SAAS,UAAU,UAAU,MAAM;IACnC,SAAS;IACT,SAAS;GACX;GACA,WAAW,CAAC,MAAM,GAAG,MAAM,CAAC;GAC5B,OAAO,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM;EAC7C;CACF;AACF;;;;;;;;AAiEA,SAAgB,gBACd,SACA,SACmB;CACnB,IAAI,OAAO;CACX,IAAI,YAAwB,CAAC,GAAG,CAAC;CACjC,IAAI,SAAS;CAEb,MAAM,aAAa,KAAK,KAAK,IAAI;CAEjC,SAAS,QAAQ,UAAkC;EACjD,OAAO,KAAK,CAAC,CAAC,KAAK,MAAM,MAAM;GAC7B,MAAM,IAAI,KAAK,UAAU,IAAI,SAAS,KAAK,SAAS;GAGpD,MAAM,SADQ,KAAK,SAAS,YAAA,CACR,YAAY,GAAG,KAAK,KAAK,KAAK,GAAG;GACrD,MAAM,UAAU,KAAK,OAAO,UAAU,OAAO,KAAK,IAAI,IAAI;GAG1D,OAAO,MAAM,YAAY,OAAO,GAAG,KAAK,KAAK,KAAK,GAAG;EACvD,CAAC;CACH;CAEA,MAAM,UAA0B,EAC9B,gBAAgB;EACd,MAAM,WAAW,KAAK;EACtB,IAAI,CAAC,UACH,MAAM,IAAI,MACR,4DACF;EAEF,MAAM,QAAQ,SAAS;EACvB,OAAO,KAAK,CAAC,CAAC,KAAK,MAAM,MAAM;GAE7B,MAAM,KADQ,KAAK,SAAS,YAAA,CACZ,UAAU,MAAM,IAAI,KAAK,KAAK,KAAK,GAAG;GACtD,OAAO,KAAK,UAAU,IAAI,IAAI;EAChC,CAAC;CACH,EACF;CAEA,MAAM,OAAO,WAAW,SAAS;EAC/B,WAAW,KAAK;EAChB,QAAQ,KAAK;EACb,aAAa,KAAK;EAClB,cAAc,UAAU,KAAK,cAAc,KAAK,KAAK;EACrD,cAAc,UAAU;GACtB,MAAM,WAAW,KAAK,QAAQ,MAAM,OAAO,OAAO;GAClD,IAAI,CAAC,UAAU;GACf,SAAS;GACT,YAAY,QAAQ,QAAQ;GAC5B,IAAI,KAAK,qBAAqB,KAAK,WAAW,WAAW,KAAK;GAC9D,KAAK,cAAc,WAAW,KAAK;EACrC;EACA,SAAS,UAAU;GACjB,IAAI,CAAC,QAAQ;GACb,MAAM,WAAW,KAAK,QAAQ,KAAK,OAAO,OAAO;GACjD,IAAI,CAAC,UAAU;GACf,YAAY,QAAQ,QAAQ;GAC5B,KAAK,WAAW,WAAW,KAAK;EAClC;EACA,YAAY,UAAU;GACpB,IAAI,CAAC,QAAQ;GACb,IACE,MAAM,MAAM,SAAS,gBACpB,MAAM,WAAW,KAAK,MAAM,WAAW,IACxC;IACA,MAAM,WAAW,KAAK,QAAQ,KAAK,OAAO,OAAO;IACjD,IAAI,UAAU;KACZ,YAAY,QAAQ,QAAQ;KAC5B,KAAK,WAAW,WAAW,KAAK;IAClC;GACF;GACA,SAAS;GACT,KAAK,YAAY,WAAW,KAAK;EACnC;CACF,CAAC;CAED,OAAO;EACL,SAAS,SAAS;GAChB,OAAO;IAAE,GAAG;IAAM,GAAG;IAAM,SAAS,KAAK;GAAQ;GACjD,KAAK,OAAO;IACV,WAAW,KAAK;IAChB,QAAQ,KAAK;IACb,aAAa,KAAK;GACpB,CAAC;EACH;EACA,eAAe,KAAK,QAAQ;CAC9B;AACF;;;;;;;;;;;;;ACjUA,SAAgB,gBAAgB,SAA8C;CAC5E,IAAI,OAAO;CACX,IAAI,YAA2B;CAC/B,IAAI,SAAS;CACb,IAAI,QAA8C;CAElD,SAAS,aAAa;EACpB,IAAI,UAAU,MAAM,aAAa,KAAK;EACtC,QAAQ;CACV;;CAGA,SAAS,SAAS,MAAc;EAC9B,QAAQ,iBAAiB;GACvB,KAAK,QAAQ;GACb,IAAI,QAAQ,SAAS,KAAK,YAAY,EAAE;EAC1C,GAAG,IAAI;CACT;CAEA,SAAS,aAAa,OAAqB;EAEzC,IAAI,cAAc,QAAQ,MAAM,cAAc,WAAW;EACzD,KAAK;CACP;CAEA,SAAS,OAAO,KAAc;EAC5B,MAAM,SAAS,WAAW;EAC1B,IAAI,CAAC,QAAQ;EACb,MAAM,SAAS,MAAM,qBAAqB;EAC1C,OAAO,OAAO,CAAC,aAAa,YAA6B;EACzD,OAAO,OAAO,CAAC,iBAAiB,YAA6B;EAC7D,OAAO,OAAO,CAAC,QAAQ,IAAI;CAC7B;CAEA,SAAS,OAAO;EACd,IAAI,CAAC,QAAQ;EACb,SAAS;EACT,YAAY;EACZ,WAAW;EACX,OAAO,KAAK;CACd;CAEA,OAAO;EACL,QAAQ,UAAU;GAChB,IAAI,UAAW,SAAS,MAAM,WAAW,GAAI;GAC7C,SAAS;GACT,YAAY,OAAO,aAAa;GAChC,OAAO,IAAI;GACX,KAAK,QAAQ;GAEb,IAAI,QAAQ,SAAS,KAAK,SAAS,GAAG;EACxC;EACA;EACA,SAAS,SAAS;GAChB,OAAO;IAAE,GAAG;IAAM,GAAG;GAAK;EAC5B;EACA,eAAe;EACf,SAAS;CACX;AACF;;;;;;;;;ACvEA,SAAgB,YACd,SACA,SACA,UAAwB,CAAC,GACV;CACf,IAAI,OAAO;EAAE,GAAG;EAAS;CAAQ;CAEjC,SAAS,WAAW;EAClB,MAAM,SAAS,QAAQ,eAAe;EACtC,OAAO,CAAC,CAAC,UAAU,QAAQ,SAAS,MAAM;CAC5C;CAEA,MAAM,WAAW,UAAiB;EAChC,IAAI,KAAK,gBAAgB,CAAC,SAAS,GAAG;EACtC,KAAK,UAAU,KAAmB;CACpC;CAEA,QAAQ,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;CAE7D,OAAO;EACL,SAAS,SAAS;GAChB,OAAO;IAAE,GAAG;IAAM,GAAG;GAAK;EAC5B;EACA,eAAe;GAEb,QAAQ,oBAAoB,SAAS,OAAO;EAC9C;CACF;AACF;;;;;;;;;;;ACjDA,SAAgB,aACd,OACA,EAAE,KAAK,KAAK,QAAQ,eACpB,WAAW,OACH;CAGR,MAAM,aAAa,MAAM,UAAU,OAAO,KAAK,GAAG;CAClD,OAAO,SAAS,WAAW,IAAI,aAAa,cAAc,GAAG;AAC/D;;;;;;;;;;;;;;ACRA,SAAgB,cAAc,GAAmB;CAC/C,IAAI,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO;CAEhC,MAAM,OAAO,OAAO,CAAC;CACrB,MAAM,IAAI,KAAK,QAAQ,GAAG;CAC1B,IAAI,MAAM,IAAI,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,UAAU;CAEnD,MAAM,WAAW,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,UAAU;CAG3D,OAAO,KAAK,IAAI,GAAG,WAAW,OAAO,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC;AACzD;;;;;;;;;;ACWA,SAAgB,YACd,SACA,KACA,KACA,MACc;CACd,MAAM,EACJ,KACA,OAAO,MACP,QAAQ,SACN,OAAO,YAAY,WAAW,UAAU,EAAE,KAAK,QAAQ;CAC3D,MAAM,cAA4B,CAAC;CACnC,MAAM,WAAW,QAAQ,SAAS,OAAO;CACzC,MAAM,QAAQ,KAAK,MAAM,MAAM,QAAQ,IAAI,KAAK,KAAK,MAAM,QAAQ,IAAI;CACvE,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;EAC9B,MAAM,QAAQ,QACZ,YAAY,KAAK,KAAK,MAAM,QAAQ,IAAI,IACxC,cAAc,QAAQ,CACxB;EACA,YAAY,KAAK;GAAS;GAAa;GAAa;EAAM,CAAC;CAC7D;CACA,OAAO;AACT;;;;;;;;;;;;AC9CA,SAAgB,UACd,OACoB;CACpB,OAAO,OAAO,UAAU,WAAW,GAAG,MAAM,MAAM;AACpD;;;;;;;;;;;;;;AAeA,MAAa,sBAAsB;CACjC,UAAU;CACV,OAAO;CACP,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,UAAU;CACV,MAAM;CACN,UAAU;CACV,YAAY;CACZ,QAAQ;CACR,eAAe;AACjB"}
|