@getdevteam/analytics-web 0.1.0 → 0.3.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/feedback/index.ts","../src/feedback/capture.ts","../src/feedback/exportImage.ts","../src/feedback/strokes.ts"],"sourcesContent":["import { uuidV7, type EventContext, type FetchLike } from \"@getdevteam/analytics-core\";\nimport { captureViewport } from \"./capture\";\nimport { drawStrokes, exportAnnotatedImage, type ExportedImage } from \"./exportImage\";\nimport { createStrokeStore, mapToImagePoint, type StrokePoint } from \"./strokes\";\nimport type { FeedbackHandle, WebFeedbackOptions } from \"./types\";\nimport type { Stroke } from \"./strokes\";\n\nexport type { FeedbackHandle, WebFeedbackOptions } from \"./types\";\nexport { createStrokeStore, mapToImagePoint } from \"./strokes\";\nexport type { Stroke, StrokePoint, StrokeStore } from \"./strokes\";\nexport { exportAnnotatedImage } from \"./exportImage\";\nexport type { ExportedImage } from \"./exportImage\";\n\nexport interface FeedbackConfig {\n url: string;\n key: string;\n fetch: FetchLike | undefined;\n getDistinctId(): string;\n getSessionId(): string;\n getContext(): EventContext;\n options: WebFeedbackOptions;\n onError(error: unknown): void;\n}\n\nexport interface FeedbackDeps {\n capture(exclude: HTMLElement, pixelRatio: number): Promise<HTMLCanvasElement>;\n exportAnnotated(\n source: HTMLCanvasElement,\n strokes: readonly Stroke[],\n options: { maxBytes: number },\n ): Promise<ExportedImage | null>;\n}\n\nconst DEFAULT_COLORS = [\"#f44336\", \"#4caf50\", \"#2196f3\", \"#ffeb3b\"];\nconst DEFAULT_ACCENT = \"#5b9cf5\";\nconst DEFAULT_Z_INDEX = 2147483000;\nconst PEN_WIDTH = 5;\nconst MAX_SCREENSHOT_BYTES = 5 * 1024 * 1024;\nconst MAX_TEXT_LENGTH = 10000;\nconst MAX_PAGE_URL_LENGTH = 2048;\nconst SUCCESS_CLOSE_DELAY_MS = 1600;\n\nconst defaultDeps: FeedbackDeps = {\n capture: captureViewport,\n exportAnnotated: exportAnnotatedImage,\n};\n\nexport function mountFeedback(config: FeedbackConfig, deps: FeedbackDeps = defaultDeps): FeedbackHandle {\n const options = config.options;\n const colors = options.colors && options.colors.length > 0 ? options.colors : DEFAULT_COLORS;\n const accent = options.accentColor ?? DEFAULT_ACCENT;\n const pixelRatio =\n options.pixelRatio ??\n Math.min(typeof devicePixelRatio === \"number\" && devicePixelRatio > 0 ? devicePixelRatio : 1, 2);\n\n let destroyed = false;\n let overlayOpen = false;\n // Bumped on every open and close: a submit resolving for an earlier overlay\n // session (sent, then closed and reopened mid-flight) must not paint its\n // outcome into, or auto-close, the new draft.\n let overlaySession = 0;\n let capturing = false;\n let sending = false;\n // One idempotency key per draft (the contract dedupes on it), so retrying a\n // failed send cannot store the same feedback twice; re-minted on each open.\n let draftFeedbackId = uuidV7();\n let sourceCanvas: HTMLCanvasElement | undefined;\n let activeColor = colors[0] ?? DEFAULT_COLORS[0]!;\n let penWidth = PEN_WIDTH;\n let previousBodyOverflow: string | undefined;\n let closeTimer: ReturnType<typeof setTimeout> | undefined;\n let drawContext: CanvasRenderingContext2D | null | undefined;\n const strokes = createStrokeStore();\n\n const report = (error: unknown): void => {\n try {\n config.onError(error);\n } catch {\n // a throwing observer must never break the host app\n }\n };\n const safely = (fn: () => void): void => {\n try {\n fn();\n } catch (error) {\n report(error);\n }\n };\n\n const host = document.createElement(\"div\");\n host.setAttribute(\"data-devteam-feedback\", \"\");\n const shadow = host.attachShadow({ mode: \"open\" });\n\n const style = document.createElement(\"style\");\n style.textContent = buildStyles(options, accent);\n\n const container = element(\"div\", \"container\");\n const fab = element(\"button\", \"fab\");\n fab.type = \"button\";\n fab.textContent = options.buttonLabel ?? \"Feedback\";\n\n const overlay = element(\"div\", \"overlay\");\n overlay.hidden = true;\n const backdrop = element(\"div\", \"backdrop\");\n const panel = element(\"div\", \"panel\");\n panel.setAttribute(\"role\", \"dialog\");\n panel.setAttribute(\"aria-modal\", \"true\");\n\n const header = element(\"div\", \"header\");\n const title = element(\"div\", \"title\");\n title.textContent = options.title ?? \"Send feedback\";\n const closeButton = element(\"button\", \"close\");\n closeButton.type = \"button\";\n closeButton.setAttribute(\"aria-label\", \"Close\");\n closeButton.textContent = \"×\";\n header.append(title, closeButton);\n\n const preview = element(\"div\", \"preview\");\n const drawCanvas = document.createElement(\"canvas\");\n drawCanvas.className = \"draw\";\n preview.appendChild(drawCanvas);\n\n const tools = element(\"div\", \"tools\");\n const swatches = colors.map((color) => {\n const swatch = element(\"button\", \"swatch\");\n swatch.type = \"button\";\n swatch.style.background = color;\n swatch.setAttribute(\"aria-label\", `Pen color ${color}`);\n swatch.addEventListener(\"click\", () => safely(() => selectColor(color)));\n tools.appendChild(swatch);\n return { color, swatch };\n });\n const undoButton = element(\"button\", \"tool undo\");\n undoButton.type = \"button\";\n undoButton.textContent = \"Undo\";\n const clearButton = element(\"button\", \"tool clear\");\n clearButton.type = \"button\";\n clearButton.textContent = \"Clear\";\n tools.append(undoButton, clearButton);\n\n const comment = document.createElement(\"textarea\");\n comment.className = \"comment\";\n comment.placeholder = options.commentPlaceholder ?? \"What went wrong, or what could be better?\";\n comment.maxLength = MAX_TEXT_LENGTH;\n\n const error = element(\"div\", \"error\");\n error.hidden = true;\n const success = element(\"div\", \"success\");\n success.hidden = true;\n success.textContent = options.successMessage ?? \"Thanks for the feedback!\";\n\n const actions = element(\"div\", \"actions\");\n const cancelButton = element(\"button\", \"cancel\");\n cancelButton.type = \"button\";\n cancelButton.textContent = \"Cancel\";\n const submitButton = element(\"button\", \"submit\");\n submitButton.type = \"button\";\n submitButton.textContent = options.submitLabel ?? \"Send\";\n actions.append(cancelButton, submitButton);\n\n panel.append(header, preview, tools, comment, error, success, actions);\n overlay.append(backdrop, panel);\n container.append(fab, overlay);\n shadow.append(style, container);\n\n if (document.body) {\n document.body.appendChild(host);\n } else {\n document.addEventListener(\n \"DOMContentLoaded\",\n () => {\n if (!destroyed) document.body?.appendChild(host);\n },\n { once: true },\n );\n }\n\n function selectColor(color: string): void {\n activeColor = color;\n for (const entry of swatches) {\n entry.swatch.classList.toggle(\"active\", entry.color === color);\n }\n }\n selectColor(activeColor);\n\n function context2d(): CanvasRenderingContext2D | null {\n if (drawContext === undefined) {\n try {\n drawContext = drawCanvas.getContext(\"2d\");\n } catch {\n drawContext = null;\n }\n }\n return drawContext;\n }\n\n function repaint(): void {\n const ctx = context2d();\n if (!ctx) return;\n ctx.clearRect(0, 0, drawCanvas.width, drawCanvas.height);\n drawStrokes(ctx, strokes.list());\n }\n\n function pointFromEvent(event: Event): StrokePoint | undefined {\n const { clientX, clientY } = event as { clientX?: unknown; clientY?: unknown };\n if (typeof clientX !== \"number\" || typeof clientY !== \"number\") return undefined;\n const rect = drawCanvas.getBoundingClientRect();\n return mapToImagePoint(clientX, clientY, rect, drawCanvas.width, drawCanvas.height);\n }\n\n function withPointerCapture(event: Event, action: \"set\" | \"release\"): void {\n const pointerId = (event as { pointerId?: unknown }).pointerId;\n if (typeof pointerId !== \"number\") return;\n const method = action === \"set\" ? drawCanvas.setPointerCapture : drawCanvas.releasePointerCapture;\n if (typeof method !== \"function\") return;\n try {\n method.call(drawCanvas, pointerId);\n } catch {\n // pointer capture is a nicety; drawing works without it\n }\n }\n\n const onPointerDown = (event: Event): void =>\n safely(() => {\n if (!sourceCanvas || sending) return;\n const point = pointFromEvent(event);\n if (!point) return;\n if (typeof event.preventDefault === \"function\") event.preventDefault();\n withPointerCapture(event, \"set\");\n strokes.begin(activeColor, penWidth, point);\n repaint();\n });\n const onPointerMove = (event: Event): void =>\n safely(() => {\n if (!strokes.isDrawing()) return;\n const point = pointFromEvent(event);\n if (!point) return;\n strokes.extend(point);\n repaint();\n });\n const onPointerEnd = (event: Event): void =>\n safely(() => {\n withPointerCapture(event, \"release\");\n strokes.end();\n });\n drawCanvas.addEventListener(\"pointerdown\", onPointerDown);\n drawCanvas.addEventListener(\"pointermove\", onPointerMove);\n drawCanvas.addEventListener(\"pointerup\", onPointerEnd);\n drawCanvas.addEventListener(\"pointercancel\", onPointerEnd);\n // When pointer capture is unavailable a release outside the canvas never\n // reaches it, which would leave the stroke armed (hover keeps extending it\n // and Undo/Clear are mid-drag no-ops). Capture retargets events to the\n // canvas, so these fallbacks only fire on the uncaptured path: leaving the\n // canvas ends the stroke, and the overlay spans the viewport, so any\n // release lands somewhere in the shadow root.\n drawCanvas.addEventListener(\"pointerleave\", onPointerEnd);\n shadow.addEventListener(\"pointerup\", onPointerEnd);\n\n undoButton.addEventListener(\"click\", () =>\n safely(() => {\n strokes.undo();\n repaint();\n }),\n );\n clearButton.addEventListener(\"click\", () =>\n safely(() => {\n strokes.clear();\n repaint();\n }),\n );\n\n const onKeyDown = (event: Event): void => {\n if ((event as { key?: unknown }).key === \"Escape\") safely(close);\n };\n\n function lockScroll(): void {\n if (previousBodyOverflow !== undefined) return;\n previousBodyOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n\n function unlockScroll(): void {\n if (previousBodyOverflow === undefined) return;\n document.body.style.overflow = previousBodyOverflow;\n previousBodyOverflow = undefined;\n }\n\n function setError(message: string | undefined): void {\n error.hidden = message === undefined;\n error.textContent = message ?? \"\";\n }\n\n function setFormVisible(visible: boolean): void {\n const hasPreview = sourceCanvas !== undefined;\n preview.hidden = !visible || !hasPreview;\n tools.hidden = !visible || !hasPreview;\n comment.hidden = !visible;\n actions.hidden = !visible;\n }\n\n function showOverlay(canvas: HTMLCanvasElement | undefined): void {\n overlaySession += 1;\n draftFeedbackId = uuidV7();\n sourceCanvas = canvas;\n strokes.end();\n strokes.clear();\n comment.value = \"\";\n setError(undefined);\n success.hidden = true;\n submitButton.disabled = false;\n if (canvas) {\n canvas.classList.add(\"shot\");\n preview.insertBefore(canvas, drawCanvas);\n drawCanvas.width = canvas.width;\n drawCanvas.height = canvas.height;\n penWidth = PEN_WIDTH * (pixelRatio > 0 ? pixelRatio : 1);\n repaint();\n }\n selectColor(colors[0] ?? DEFAULT_COLORS[0]!);\n setFormVisible(true);\n overlay.hidden = false;\n overlayOpen = true;\n lockScroll();\n document.addEventListener(\"keydown\", onKeyDown);\n }\n\n function close(): void {\n if (!overlayOpen) return;\n overlaySession += 1;\n overlayOpen = false;\n overlay.hidden = true;\n document.removeEventListener(\"keydown\", onKeyDown);\n unlockScroll();\n if (closeTimer !== undefined) {\n clearTimeout(closeTimer);\n closeTimer = undefined;\n }\n sending = false;\n if (sourceCanvas) {\n sourceCanvas.remove();\n sourceCanvas = undefined;\n }\n strokes.end();\n strokes.clear();\n }\n\n function open(): void {\n if (destroyed || overlayOpen || capturing) return;\n capturing = true;\n void Promise.resolve()\n .then(() => deps.capture(host, pixelRatio))\n .then(\n (canvas) => {\n capturing = false;\n if (destroyed) return;\n showOverlay(canvas);\n },\n (captureError: unknown) => {\n capturing = false;\n if (destroyed) return;\n report(captureError);\n showOverlay(undefined);\n },\n );\n }\n\n function buildBody(text: string, screenshot: ExportedImage | null): string {\n const body: Record<string, unknown> = {\n feedback_id: draftFeedbackId,\n text: text.slice(0, MAX_TEXT_LENGTH),\n };\n if (screenshot) {\n body.screenshot = screenshot.base64;\n body.screenshot_content_type = screenshot.contentType;\n }\n const distinctId = config.getDistinctId();\n if (distinctId) body.distinct_id = distinctId;\n const sessionId = config.getSessionId();\n if (sessionId) body.session_id = sessionId;\n if (typeof location !== \"undefined\" && location.href) {\n body.page_url = location.href.slice(0, MAX_PAGE_URL_LENGTH);\n }\n body.context = config.getContext();\n return JSON.stringify(body);\n }\n\n async function submit(): Promise<void> {\n if (sending || !overlayOpen) return;\n const text = comment.value.trim();\n if (text.length === 0) {\n setError(\"Please add a short comment before sending.\");\n return;\n }\n const session = overlaySession;\n const sameSession = (): boolean => !destroyed && overlayOpen && overlaySession === session;\n sending = true;\n submitButton.disabled = true;\n setError(undefined);\n try {\n let screenshot: ExportedImage | null = null;\n if (sourceCanvas) {\n screenshot = await deps.exportAnnotated(sourceCanvas, strokes.list(), {\n maxBytes: MAX_SCREENSHOT_BYTES,\n });\n }\n if (!config.fetch) throw new Error(\"fetch is not available in this environment\");\n const response = await config.fetch(config.url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"X-DevTeam-Key\": config.key },\n body: buildBody(text, screenshot),\n });\n if (!sameSession()) return;\n if (response.ok) {\n setFormVisible(false);\n success.hidden = false;\n closeTimer = setTimeout(() => safely(close), SUCCESS_CLOSE_DELAY_MS);\n } else {\n setError(\"Could not send feedback. Please try again.\");\n }\n } catch (submitError) {\n report(submitError);\n if (sameSession()) setError(\"Could not send feedback. Please try again.\");\n } finally {\n if (overlaySession === session) {\n sending = false;\n submitButton.disabled = false;\n }\n }\n }\n\n fab.addEventListener(\"click\", () => safely(open));\n closeButton.addEventListener(\"click\", () => safely(close));\n cancelButton.addEventListener(\"click\", () => safely(close));\n backdrop.addEventListener(\"click\", () => safely(close));\n submitButton.addEventListener(\"click\", () => safely(() => void submit()));\n\n return {\n open: () => safely(open),\n destroy: () => {\n if (destroyed) return;\n destroyed = true;\n safely(() => {\n close();\n document.removeEventListener(\"keydown\", onKeyDown);\n host.remove();\n });\n },\n };\n}\n\nfunction element<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className: string,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n node.className = className;\n return node;\n}\n\nfunction buildStyles(options: WebFeedbackOptions, accent: string): string {\n const zIndex = options.zIndex ?? DEFAULT_Z_INDEX;\n const side = options.position === \"bottom-left\" ? \"left\" : \"right\";\n return `\n:host { all: initial; }\n* { box-sizing: border-box; }\n[hidden] { display: none !important; }\n.container {\n position: fixed;\n inset: 0;\n z-index: ${zIndex};\n pointer-events: none;\n font-family: system-ui, -apple-system, sans-serif;\n color: #1f2933;\n}\n.fab {\n position: absolute;\n bottom: 20px;\n ${side}: 20px;\n pointer-events: auto;\n border: none;\n border-radius: 999px;\n padding: 10px 18px;\n background: ${accent};\n color: #fff;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);\n}\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.backdrop {\n position: absolute;\n inset: 0;\n background: rgba(15, 23, 42, 0.55);\n}\n.panel {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 10px;\n width: min(560px, calc(100vw - 32px));\n max-height: calc(100vh - 32px);\n overflow: auto;\n background: #fff;\n border-radius: 12px;\n padding: 16px;\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);\n}\n.header { display: flex; align-items: center; justify-content: space-between; }\n.title { font-size: 16px; font-weight: 600; }\n.close {\n border: none;\n background: none;\n font-size: 20px;\n line-height: 1;\n cursor: pointer;\n color: #52606d;\n padding: 4px 8px;\n}\n.preview {\n position: relative;\n border: 1px solid #d3dce6;\n border-radius: 8px;\n overflow: hidden;\n background: #f5f7fa;\n}\n.preview .shot { display: block; width: 100%; height: auto; }\n.preview .draw {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n cursor: crosshair;\n touch-action: none;\n}\n.tools { display: flex; align-items: center; gap: 8px; }\n.swatch {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n border: 2px solid transparent;\n cursor: pointer;\n padding: 0;\n}\n.swatch.active { border-color: #1f2933; }\n.tool {\n border: 1px solid #d3dce6;\n background: #fff;\n border-radius: 6px;\n padding: 4px 10px;\n font-size: 13px;\n cursor: pointer;\n}\n.comment {\n width: 100%;\n min-height: 72px;\n resize: vertical;\n border: 1px solid #d3dce6;\n border-radius: 8px;\n padding: 8px 10px;\n font: inherit;\n}\n.error { color: #b91c1c; font-size: 13px; }\n.success { color: #15803d; font-size: 14px; font-weight: 600; }\n.actions { display: flex; justify-content: flex-end; gap: 8px; }\n.cancel {\n border: 1px solid #d3dce6;\n background: #fff;\n border-radius: 8px;\n padding: 8px 14px;\n font-size: 14px;\n cursor: pointer;\n}\n.submit {\n border: none;\n background: ${accent};\n color: #fff;\n border-radius: 8px;\n padding: 8px 16px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n}\n.submit:disabled { opacity: 0.6; cursor: default; }\n`;\n}\n","import { toCanvas } from \"html-to-image\";\n\n// html-to-image has no clip option, so the visible viewport is captured by\n// sizing the render to the window and translating the cloned document up and\n// left by the current scroll offset.\nexport async function captureViewport(exclude: Node, pixelRatio: number): Promise<HTMLCanvasElement> {\n return toCanvas(document.documentElement, {\n filter: (node) => node !== exclude,\n pixelRatio,\n width: window.innerWidth,\n height: window.innerHeight,\n style: {\n transform: `translate(${-window.scrollX}px, ${-window.scrollY}px)`,\n },\n });\n}\n","import type { Stroke } from \"./strokes\";\n\nexport interface ExportedImage {\n base64: string;\n contentType: \"image/png\" | \"image/jpeg\";\n}\n\nexport interface ExportImageOptions {\n maxBytes: number;\n}\n\nconst JPEG_QUALITY = 0.85;\nconst DOWNSCALE_FACTOR = 0.7;\nconst MIN_EXPORT_DIMENSION = 200;\n\nexport function drawStrokes(ctx: CanvasRenderingContext2D, strokes: readonly Stroke[]): void {\n for (const stroke of strokes) {\n const [first, ...rest] = stroke.points;\n if (!first) continue;\n ctx.strokeStyle = stroke.color;\n ctx.lineWidth = stroke.width;\n ctx.lineCap = \"round\";\n ctx.lineJoin = \"round\";\n ctx.beginPath();\n ctx.moveTo(first.x, first.y);\n if (rest.length === 0) ctx.lineTo(first.x, first.y);\n for (const point of rest) ctx.lineTo(point.x, point.y);\n ctx.stroke();\n }\n}\n\n// Composites the full-resolution screenshot with the strokes, then encodes to\n// PNG; when the PNG is over the byte budget it falls back to JPEG and, if\n// still too large, downscales in 0.7 steps until it fits. Returns null when no\n// encoding fits or the environment has no working 2d canvas.\nexport async function exportAnnotatedImage(\n source: HTMLCanvasElement,\n strokes: readonly Stroke[],\n options: ExportImageOptions,\n): Promise<ExportedImage | null> {\n try {\n const composed = compose(source, strokes);\n if (!composed) return null;\n\n const png = await encode(composed, \"image/png\");\n if (png && png.size <= options.maxBytes) {\n return { base64: await blobToBase64(png), contentType: \"image/png\" };\n }\n\n let canvas = composed;\n let jpeg = await encode(canvas, \"image/jpeg\", JPEG_QUALITY);\n while (\n jpeg &&\n jpeg.size > options.maxBytes &&\n Math.min(canvas.width, canvas.height) * DOWNSCALE_FACTOR >= MIN_EXPORT_DIMENSION\n ) {\n const smaller = downscale(canvas, DOWNSCALE_FACTOR);\n if (!smaller) break;\n canvas = smaller;\n jpeg = await encode(canvas, \"image/jpeg\", JPEG_QUALITY);\n }\n if (jpeg && jpeg.size <= options.maxBytes) {\n return { base64: await blobToBase64(jpeg), contentType: \"image/jpeg\" };\n }\n return null;\n } catch {\n return null;\n }\n}\n\nfunction compose(source: HTMLCanvasElement, strokes: readonly Stroke[]): HTMLCanvasElement | null {\n const canvas = document.createElement(\"canvas\");\n canvas.width = source.width;\n canvas.height = source.height;\n const ctx = get2dContext(canvas);\n if (!ctx || canvas.width === 0 || canvas.height === 0) return null;\n // JPEG has no alpha channel and would composite transparency onto black.\n ctx.fillStyle = \"#ffffff\";\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(source, 0, 0);\n drawStrokes(ctx, strokes);\n return canvas;\n}\n\nfunction downscale(source: HTMLCanvasElement, factor: number): HTMLCanvasElement | null {\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, Math.round(source.width * factor));\n canvas.height = Math.max(1, Math.round(source.height * factor));\n const ctx = get2dContext(canvas);\n if (!ctx) return null;\n ctx.drawImage(source, 0, 0, canvas.width, canvas.height);\n return canvas;\n}\n\nfunction get2dContext(canvas: HTMLCanvasElement): CanvasRenderingContext2D | null {\n try {\n return canvas.getContext(\"2d\");\n } catch {\n return null;\n }\n}\n\nfunction encode(canvas: HTMLCanvasElement, type: string, quality?: number): Promise<Blob | null> {\n return new Promise((resolve) => {\n try {\n canvas.toBlob((blob) => resolve(blob), type, quality);\n } catch {\n resolve(null);\n }\n });\n}\n\nexport async function blobToBase64(blob: Blob): Promise<string> {\n const bytes = new Uint8Array(await blob.arrayBuffer());\n let binary = \"\";\n const chunkSize = 0x8000;\n for (let i = 0; i < bytes.length; i += chunkSize) {\n binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));\n }\n return btoa(binary);\n}\n","export interface StrokePoint {\n x: number;\n y: number;\n}\n\nexport interface Stroke {\n points: StrokePoint[];\n color: string;\n width: number;\n}\n\nexport interface StrokeStore {\n begin(color: string, width: number, point: StrokePoint): void;\n extend(point: StrokePoint): void;\n end(): void;\n undo(): void;\n clear(): void;\n list(): readonly Stroke[];\n isDrawing(): boolean;\n}\n\n// Each stroke snapshots its color and width when it starts, so a later color\n// change never restyles strokes already on the canvas. Undo and clear are\n// no-ops mid-drag to keep the active stroke consistent.\nexport function createStrokeStore(): StrokeStore {\n const strokes: Stroke[] = [];\n let active: Stroke | undefined;\n return {\n begin(color, width, point) {\n if (active) return;\n active = { points: [point], color, width };\n strokes.push(active);\n },\n extend(point) {\n active?.points.push(point);\n },\n end() {\n active = undefined;\n },\n undo() {\n if (active) return;\n strokes.pop();\n },\n clear() {\n if (active) return;\n strokes.length = 0;\n },\n list: () => strokes,\n isDrawing: () => active !== undefined,\n };\n}\n\nexport interface DisplayRect {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\n// Strokes live in the screenshot's own pixel space so they land at the same\n// spot in the full-resolution export as they did on the scaled-down preview.\n// A degenerate rect (jsdom reports zero sizes) maps 1:1 instead of dividing by zero.\nexport function mapToImagePoint(\n clientX: number,\n clientY: number,\n rect: DisplayRect,\n imageWidth: number,\n imageHeight: number,\n): StrokePoint {\n const scaleX = rect.width > 0 && imageWidth > 0 ? imageWidth / rect.width : 1;\n const scaleY = rect.height > 0 && imageHeight > 0 ? imageHeight / rect.height : 1;\n return { x: (clientX - rect.left) * scaleX, y: (clientY - rect.top) * scaleY };\n}\n"],"mappings":"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","names":["error"]}