@weasel-js/labkit 1.4.1 → 1.4.2
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/README.md +8 -0
- package/dist/_dts/{CanvasStackContext-BIRUVVGH.d.ts → CanvasStackContext-kjILVnPj.d.ts} +1 -1
- package/dist/_dts/{frac-X7mWgd7y.d.ts → frac-A4R6v4ld.d.ts} +27 -10
- package/dist/_dts/{index-FEa1MudK.d.ts → index-CsU9WhjM.d.ts} +89 -29
- package/dist/_dts/{types-ChVJJHvk.d.ts → types-BbXpvQa8.d.ts} +20 -1
- package/dist/_dts/{useTrialState-Cdm13fvl.d.ts → useTrialState-DKjqpv20.d.ts} +5 -1
- package/dist/canvas/index.d.ts +5 -5
- package/dist/canvas/index.js +2 -2
- package/dist/chrome/index.d.ts +12 -6
- package/dist/chrome/index.js +5 -5
- package/dist/{chunk-DG2IFMLA.js → chunk-54ZWZ5FQ.js} +127 -100
- package/dist/chunk-54ZWZ5FQ.js.map +1 -0
- package/dist/{chunk-RLOXQZW3.js → chunk-D5KQ5OY6.js} +3 -3
- package/dist/{chunk-RLOXQZW3.js.map → chunk-D5KQ5OY6.js.map} +1 -1
- package/dist/{chunk-PP7ZWAMV.js → chunk-FJG4PHTL.js} +3 -3
- package/dist/{chunk-PP7ZWAMV.js.map → chunk-FJG4PHTL.js.map} +1 -1
- package/dist/chunk-ISSVF5PT.js +7263 -0
- package/dist/chunk-ISSVF5PT.js.map +1 -0
- package/dist/chunk-NSOVI3AZ.js +170 -0
- package/dist/chunk-NSOVI3AZ.js.map +1 -0
- package/dist/{chunk-D7QIPGEB.js → chunk-TN7YSJVU.js} +62 -60
- package/dist/chunk-TN7YSJVU.js.map +1 -0
- package/dist/{chunk-7TRFMIWX.js → chunk-UDXOYZEC.js} +4 -4
- package/dist/{chunk-7TRFMIWX.js.map → chunk-UDXOYZEC.js.map} +1 -1
- package/dist/{chunk-LJSIUFYD.js → chunk-ULDW42CR.js} +23 -2
- package/dist/chunk-ULDW42CR.js.map +1 -0
- package/dist/{chunk-MSR7EVS6.js → chunk-UTOEDPNU.js} +5 -5
- package/dist/{chunk-MSR7EVS6.js.map → chunk-UTOEDPNU.js.map} +1 -1
- package/dist/{chunk-BMQWLKOL.js → chunk-W2FJR5FF.js} +35 -22
- package/dist/chunk-W2FJR5FF.js.map +1 -0
- package/dist/{chunk-OFCBEZMU.js → chunk-WS6ZRV75.js} +3 -3
- package/dist/{chunk-OFCBEZMU.js.map → chunk-WS6ZRV75.js.map} +1 -1
- package/dist/{chunk-53AZ2O2N.js → chunk-XKENZTNE.js} +45 -14
- package/dist/chunk-XKENZTNE.js.map +1 -0
- package/dist/controls/index.d.ts +1 -1
- package/dist/controls/index.js +3 -3
- package/dist/dragdrop/index.d.ts +5 -5
- package/dist/dragdrop/index.js +2 -2
- package/dist/index.d.ts +71 -43
- package/dist/index.js +146 -113
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +23 -8
- package/dist/layers/index.js +3 -3
- package/dist/loupe/index.d.ts +4 -4
- package/dist/loupe/index.js +2 -2
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +84 -3
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/primitives/index.js +4 -4
- package/dist/state/index.d.ts +3 -3
- package/dist/state/index.js +2 -2
- package/dist/styles.css +20 -3
- package/dist/surface/index.js +2 -2
- package/dist/ui/layers/index.d.ts +22 -9
- package/dist/ui/layers/index.js +2 -2
- package/dist/undo/index.d.ts +2 -2
- package/package.json +7 -7
- package/src/annotations/AnnotationTargets.tsx +6 -1
- package/src/annotations/Annotations.overlay.test.tsx +2 -0
- package/src/annotations/types.ts +4 -1
- package/src/canvas/useOrbit.test.ts +7 -3
- package/src/canvas/usePanZoom.test.ts +7 -3
- package/src/chrome/ChromeRegions.stories.tsx +42 -1
- package/src/chrome/builtins.test.ts +4 -0
- package/src/chrome/builtins.tsx +18 -0
- package/src/chrome/index.ts +1 -1
- package/src/chrome/regions/SidebarRegion.tsx +7 -6
- package/src/chrome/regions/TitleBarRegion.test.tsx +64 -0
- package/src/chrome/regions/TitleBarRegion.tsx +16 -7
- package/src/chrome/regions/ToolbarRegion.test.tsx +14 -0
- package/src/chrome/regions/ToolbarRegion.tsx +2 -2
- package/src/chrome/regions/ViewportRegion.tsx +2 -2
- package/src/chrome/regions/regions.test.tsx +45 -1
- package/src/chrome/types.ts +21 -3
- package/src/controls/ControlPanel.test.tsx +38 -0
- package/src/controls/ControlPanel.tsx +51 -6
- package/src/dragdrop/DragDropRuntime.tsx +74 -61
- package/src/dragdrop/Palette.tsx +4 -3
- package/src/dragdrop/dragDrop.test.tsx +35 -12
- package/src/index.ts +2 -1
- package/src/instrument/types.ts +4 -5
- package/src/lab/Lab.test.tsx +7 -0
- package/src/lab/Lab.tsx +2 -2
- package/src/lab/LabContext.ts +5 -1
- package/src/lab/LabShell.less +17 -2
- package/src/layers/AGENTS.md +29 -7
- package/src/layers/LayerList.less +16 -0
- package/src/layers/LayerList.stories.tsx +66 -0
- package/src/layers/LayerList.test.tsx +117 -5
- package/src/layers/LayerList.tsx +192 -53
- package/src/layers/index.ts +1 -1
- package/src/passthrough/weasel-ui.ts +5 -0
- package/src/primitives/FloatingPanel.test.tsx +8 -3
- package/src/state/store.test.ts +78 -0
- package/src/state/store.ts +27 -0
- package/src/state/types.ts +20 -0
- package/src/trial/Trial.less +4 -0
- package/src/trial/Trial.targets.test.tsx +96 -0
- package/src/trial/Trial.test.tsx +171 -14
- package/src/trial/Trial.tsx +4 -1
- package/src/trial/TrialChrome.tsx +22 -2
- package/src/trial/TrialTitleBar.tsx +7 -3
- package/src/trial/index.ts +1 -0
- package/src/trial/trialOps.test.ts +49 -1
- package/src/trial/trialOps.ts +28 -6
- package/dist/chunk-53AZ2O2N.js.map +0 -1
- package/dist/chunk-BMQWLKOL.js.map +0 -1
- package/dist/chunk-D7QIPGEB.js.map +0 -1
- package/dist/chunk-DG2IFMLA.js.map +0 -1
- package/dist/chunk-J6SBFZYT.js +0 -7121
- package/dist/chunk-J6SBFZYT.js.map +0 -1
- package/dist/chunk-LJSIUFYD.js.map +0 -1
- package/dist/chunk-MIT3ISW4.js +0 -100
- package/dist/chunk-MIT3ISW4.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { centerOn, zoomAt } from './chunk-CTRKTLYZ.js';
|
|
2
2
|
import { resolveFrame, screenToWorld, DEFAULT_FRAME } from './chunk-2AYGEN57.js';
|
|
3
3
|
import { CanvasStackContext } from './chunk-CQLQPQ4P.js';
|
|
4
|
-
import { useVisibleRaf } from './chunk-
|
|
4
|
+
import { useVisibleRaf } from './chunk-54ZWZ5FQ.js';
|
|
5
5
|
import { useRef, useEffect, useReducer, useCallback, useContext, useMemo, useState } from 'react';
|
|
6
6
|
import { jsx } from 'react/jsx-runtime';
|
|
7
7
|
|
|
@@ -454,5 +454,5 @@ function resolveLoupe(declared) {
|
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LoupeBubble, TrialLoupe, drawCanvasLens, lensCamera, lensSourceRect, resolveLoupe, sampleStack, useLoupe };
|
|
457
|
-
//# sourceMappingURL=chunk-
|
|
458
|
-
//# sourceMappingURL=chunk-
|
|
457
|
+
//# sourceMappingURL=chunk-D5KQ5OY6.js.map
|
|
458
|
+
//# sourceMappingURL=chunk-D5KQ5OY6.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loupe/canvasLens.ts","../src/loupe/CanvasLoupe.tsx","../src/loupe/DomLoupe.tsx","../src/loupe/LoupeBubble.tsx","../src/loupe/useHostSize.ts","../../loupe/src/geometry.ts","../../loupe/src/model.ts","../src/loupe/useLoupe.ts","../src/loupe/TrialLoupe.tsx","../src/loupe/types.ts"],"names":["jsx","useEffect","useRef"],"mappings":"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","file":"chunk-RLOXQZW3.js","sourcesContent":["import type { LoupePoint } from '@weasel-js/loupe';\nimport { centerOn } from '../canvas/camera';\nimport { screenToWorld } from '../canvas/canvasCoords';\nimport type { CanvasLayerDescriptor } from '../canvas/useLayerScheduler';\nimport { DEFAULT_FRAME, resolveFrame, type WorldFrame, type WorldSpec } from '../canvas/worldSpec';\nimport type { ViewTransform } from '../instrument/types';\n\n/** The camera a round lens of `diameter` shows a magnified region through, and\n * the coordinate system it is read in — the instrument's own `WorldSpec`,\n * resolved against the lens rather than the stack. */\nexport interface LensCamera {\n view: ViewTransform;\n frame: WorldFrame;\n}\n\n/**\n * What a lens aimed at `aim` renders the stack's layers through.\n *\n * The lens is its own viewport, so the world spec resolves against its box: an\n * instrument centred on its viewport is centred in the lens too.\n */\nexport function lensCamera(\n aim: LoupePoint,\n outer: ViewTransform,\n outerFrame: WorldFrame,\n factor: number,\n diameter: number,\n worldSpec?: WorldSpec,\n): LensCamera {\n const size = { width: diameter, height: diameter };\n const frame = resolveFrame(worldSpec, size);\n const world = screenToWorld(aim, outer, outerFrame);\n return { view: centerOn(world, outer.zoom * factor, size, frame), frame };\n}\n\n/** A rectangle in a backing store's own device pixels. */\nexport interface SourceRect {\n sx: number;\n sy: number;\n sw: number;\n sh: number;\n}\n\n/**\n * The region a pixel-mode lens copies out of a presented canvas: the `diameter\n * / factor` CSS px around `aim`, in that canvas' backing-store pixels.\n */\nexport function lensSourceRect(\n aim: LoupePoint,\n factor: number,\n diameter: number,\n dpr: number,\n): SourceRect {\n const span = diameter / factor;\n return {\n sx: (aim.x - span / 2) * dpr,\n sy: (aim.y - span / 2) * dpr,\n sw: span * dpr,\n sh: span * dpr,\n };\n}\n\n/**\n * The colour a stack presents at a point: the topmost visible layer with\n * anything opaque there, or `null` when every layer is transparent — which is\n * the honest answer for a point showing nothing but the workspace behind.\n */\nexport function sampleStack(\n layers: readonly CanvasLayerDescriptor[],\n canvases: Map<string, HTMLCanvasElement>,\n p: LoupePoint,\n dpr: number,\n): string | null {\n for (let i = layers.length - 1; i >= 0; i--) {\n const layer = layers[i];\n if (!layer.visible) continue;\n const canvas = canvases.get(layer.id);\n if (!canvas) continue;\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n if (!ctx) continue;\n const x = Math.floor(p.x * dpr);\n const y = Math.floor(p.y * dpr);\n if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) continue;\n const [r, g, b, a] = ctx.getImageData(x, y, 1, 1).data;\n if (a === 0) continue;\n return `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;\n }\n return null;\n}\n\n/** Redraw the stack into a lens' own canvas. `mode` decides whether the layers\n * are re-run at the lens camera or their presented pixels are enlarged. */\nexport function drawCanvasLens(\n ctx: CanvasRenderingContext2D,\n opts: {\n aim: LoupePoint;\n factor: number;\n diameter: number;\n dpr: number;\n mode: 'vector' | 'pixel';\n outer: ViewTransform;\n outerFrame?: WorldFrame;\n worldSpec?: WorldSpec;\n layers: readonly CanvasLayerDescriptor[];\n canvases: Map<string, HTMLCanvasElement>;\n },\n): void {\n const { aim, factor, diameter, dpr, mode, outer, layers, canvases } = opts;\n ctx.save();\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, diameter, diameter);\n\n if (mode === 'pixel') {\n const { sx, sy, sw, sh } = lensSourceRect(aim, factor, diameter, dpr);\n ctx.imageSmoothingEnabled = false;\n for (const layer of layers) {\n if (!layer.visible) continue;\n const canvas = canvases.get(layer.id);\n if (!canvas || canvas.width === 0 || canvas.height === 0) continue;\n ctx.drawImage(canvas, sx, sy, sw, sh, 0, 0, diameter, diameter);\n }\n ctx.restore();\n return;\n }\n\n const lens = lensCamera(\n aim,\n outer,\n opts.outerFrame ?? DEFAULT_FRAME,\n factor,\n diameter,\n opts.worldSpec,\n );\n for (const layer of layers) {\n if (!layer.visible) continue;\n ctx.save();\n layer.render(ctx, lens.view, lens.frame);\n ctx.restore();\n }\n ctx.restore();\n}\n","import { useVisibleRaf } from '@weasel-js/core';\nimport type { LoupeMode, LoupePoint } from '@weasel-js/loupe';\nimport { useEffect, useRef } from 'react';\nimport type { CanvasStackSurface } from '../canvas/CanvasStackContext';\nimport type { WorldFrame, WorldSpec } from '../canvas/worldSpec';\nimport type { ViewTransform } from '../instrument/types';\nimport { drawCanvasLens } from './canvasLens';\n\n/** Props for `<CanvasLoupe>`. */\nexport interface CanvasLoupeProps {\n aim: LoupePoint;\n factor: number;\n mode: LoupeMode;\n diameter: number;\n /** The stack being magnified: its layers, its pixels, and its measured box. */\n surface: CanvasStackSurface;\n view: ViewTransform;\n frame: WorldFrame;\n worldSpec?: WorldSpec;\n}\n\n/**\n * Paints a lens over a `<CanvasStack>` by re-drawing the stack's layers through\n * a zoomed camera — so the magnified content is as sharp as the original, at\n * any factor. `pixel` mode enlarges what the stack presented instead.\n */\nexport function CanvasLoupe({\n aim,\n factor,\n mode,\n diameter,\n surface,\n view,\n frame,\n worldSpec,\n}: CanvasLoupeProps) {\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const dpr = surface.size.dpr;\n\n // Whatever the stack redraws every frame, the lens has to redraw too — an\n // instrument that animates from its own loop never re-renders this component.\n const args = { aim, factor, mode, diameter, dpr, view, frame, worldSpec, surface };\n const argsRef = useRef(args);\n argsRef.current = args;\n\n const loop = useVisibleRaf(\n () => {\n loop.request();\n const canvas = canvasRef.current;\n const ctx = canvas?.getContext('2d');\n const canvases = argsRef.current.surface.canvases.current;\n if (!ctx || !canvases) return;\n const a = argsRef.current;\n drawCanvasLens(ctx, {\n aim: a.aim,\n factor: a.factor,\n diameter: a.diameter,\n dpr: a.dpr,\n mode: a.mode,\n outer: a.view,\n outerFrame: a.frame,\n worldSpec: a.worldSpec,\n layers: a.surface.layers,\n canvases,\n });\n },\n { target: canvasRef },\n );\n\n useEffect(() => {\n loop.request();\n return () => loop.cancel();\n }, [loop]);\n\n return (\n <canvas\n ref={canvasRef}\n className=\"lk-loupe__canvas\"\n width={Math.max(1, Math.round(diameter * dpr))}\n height={Math.max(1, Math.round(diameter * dpr))}\n />\n );\n}\n","import type { LoupeMode, LoupePoint } from '@weasel-js/loupe';\nimport type { CSSProperties } from 'react';\nimport { zoomAt } from '../canvas/camera';\nimport type { ViewportSize, WorldFrame } from '../canvas/worldSpec';\nimport type { ViewTransform } from '../instrument/types';\nimport type { LoupeRenderArgs } from './types';\n\n/** Props for `<DomLoupe>`. */\nexport interface DomLoupeProps {\n aim: LoupePoint;\n factor: number;\n mode: LoupeMode;\n diameter: number;\n /** The host the instrument draws into, which the stage reproduces. */\n size: ViewportSize;\n /** The trial's own camera. */\n view: ViewTransform;\n frame?: WorldFrame;\n state: unknown;\n config: unknown;\n render: (args: LoupeRenderArgs) => React.ReactNode;\n}\n\n/**\n * Paints a lens over DOM content by asking the instrument to draw itself again\n * at a magnified camera.\n *\n * The stage is a full copy of the host, so the instrument's own layout still\n * holds; the camera is composed about the aimed point, which keeps that point\n * where it already was, and the stage is then slid by `diameter / 2 - aim` to\n * bring it to the middle of the lens.\n */\nexport function DomLoupe({\n aim,\n factor,\n mode,\n diameter,\n size,\n view,\n frame,\n state,\n config,\n render,\n}: DomLoupeProps) {\n const style: CSSProperties = {\n width: `${size.width}px`,\n height: `${size.height}px`,\n transform: `translate(${diameter / 2 - aim.x}px, ${diameter / 2 - aim.y}px)`,\n };\n return (\n <div className=\"lk-loupe__stage\" style={style}>\n {render({\n state,\n config,\n view: zoomAt(view, factor, aim, { frame }),\n factor,\n mode,\n size,\n })}\n </div>\n );\n}\n","import type { LoupePoint } from '@weasel-js/loupe';\nimport type { CSSProperties, ReactNode, RefObject } from 'react';\n\n/** Props for `<LoupeBubble>`. */\nexport interface LoupeBubbleProps {\n /** Where the lens is aimed, in its container's own pixels. */\n aim: LoupePoint;\n diameter: number;\n hostRef?: RefObject<HTMLDivElement | null>;\n children: ReactNode;\n}\n\n/**\n * The lens itself: a circle centred on the aimed point, clipping whatever a\n * painter draws into it.\n *\n * It takes no pointer events, so the pan, the wheel and anything underneath\n * keep working while it is up — and it is `aria-hidden`, since it magnifies\n * content already on the page rather than adding any.\n */\nexport function LoupeBubble({ aim, diameter, hostRef, children }: LoupeBubbleProps) {\n const style: CSSProperties = {\n width: `${diameter}px`,\n height: `${diameter}px`,\n transform: `translate(${aim.x - diameter / 2}px, ${aim.y - diameter / 2}px)`,\n };\n return (\n <div ref={hostRef} className=\"lk-loupe\" style={style} aria-hidden=\"true\">\n {children}\n </div>\n );\n}\n","import { type RefObject, useEffect, useState } from 'react';\nimport type { ViewportSize } from '../canvas/worldSpec';\n\nconst ZERO: ViewportSize = { width: 0, height: 0 };\n\n/** An element's CSS-pixel box, kept current. `{0, 0}` until it is measured, and\n * wherever `ResizeObserver` is absent. */\nexport function useHostSize(ref: RefObject<HTMLElement | null>): ViewportSize {\n const [size, setSize] = useState<ViewportSize>(ZERO);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const update = (): void => {\n const rect = el.getBoundingClientRect();\n setSize((prev) =>\n prev.width === rect.width && prev.height === rect.height\n ? prev\n : { width: rect.width, height: rect.height },\n );\n };\n update();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, [ref]);\n\n return size;\n}\n","import type { View } from '@weasel-js/core';\n\n/** A point on the magnified surface, in its own CSS pixels. */\nexport interface LoupePoint {\n x: number;\n y: number;\n}\n\n/** The lens' rectangle on that surface. */\nexport interface LoupeRect extends LoupePoint {\n w: number;\n h: number;\n}\n\n/**\n * The inner `View` a loupe renders its content through: the outer view's\n * magnification times `factor`, positioned so `target` sits at the center of\n * a viewport the size of `rect`.\n */\nexport function loupeInnerView(\n target: LoupePoint,\n outer: View,\n rect: LoupeRect,\n factor: number,\n): View {\n const scale = { x: outer.scale.x * factor, y: outer.scale.y * factor };\n return {\n x: target.x - rect.w / 2 / scale.x,\n y: target.y - rect.h / 2 / scale.y,\n scale,\n };\n}\n\n/**\n * Where on the outer surface a point inside the lens is looking, in\n * screen-space CSS px.\n *\n * The inverse of {@link loupeInnerView}: the lens centers `aim` in `rect` and\n * magnifies by `factor`, and the outer view's own scale cancels out of the\n * round trip — so this needs no `View`.\n */\nexport function loupeSourcePoint(\n p: LoupePoint,\n rect: LoupeRect,\n aim: LoupePoint,\n factor: number,\n): LoupePoint {\n return {\n x: aim.x + (p.x - (rect.x + rect.w / 2)) / factor,\n y: aim.y + (p.y - (rect.y + rect.h / 2)) / factor,\n };\n}\n","import { type LoupePoint, type LoupeRect, loupeSourcePoint } from './geometry';\n\n/** How a loupe magnifies. `'vector'` re-renders the source through a zoomed-in\n * view, so content stays sharp at any factor; `'pixel'` blows up the actual\n * pixels the surface presented. */\nexport type LoupeMode = 'vector' | 'pixel';\n\n/**\n * What a loupe needs from the surface it magnifies. A painter implements it,\n * and the model asks nothing about how the lens is drawn — only where it is,\n * what colour is under a point, and whether anyone can still see it.\n */\nexport interface LoupeSurface {\n /** The lens' rectangle, or `null` while it has none. */\n lens(): LoupeRect | null;\n /**\n * Does the lens itself cover this point? A stationary lens does over its own\n * frame, which is what the freeze rule below is for; a lens that follows the\n * pointer never covers it and answers `false`.\n */\n covers(p: LoupePoint): boolean;\n /** Hex colour at a surface point, or `null` when the surface cannot say. */\n sample(p: LoupePoint): string | null;\n /** Is the lens off screen? Aims are ignored while it is. */\n hidden(): boolean;\n /** Is the lens gone for good? The model tears itself down when it is. */\n gone(): boolean;\n /** Something the painter must redraw for has changed. */\n changed(): void;\n}\n\n/** Options for {@link createLoupeModel}. */\nexport interface LoupeModelOptions {\n surface: LoupeSurface;\n mode?: LoupeMode;\n /** Magnification. Default 8. */\n factor?: number;\n /** Bounds `setFactor` clamps to. Unset means unclamped. */\n minFactor?: number;\n maxFactor?: number;\n /** Called with the hex colour under the aim point whenever it changes. */\n onColorChange?: (hex: string) => void;\n /** Called when a pick lands — an eyedropper on the magnified surface. Where\n * the colour goes is the consumer's business. */\n onPick?: (hex: string) => void;\n /** Called when the model tears itself down because the surface reported the\n * lens gone, so the painter can drop what it was holding. */\n onDispose?: () => void;\n}\n\n/** Where a loupe is aimed, how far it magnifies, and what colour it is over. */\nexport interface LoupeModel {\n readonly mode: LoupeMode;\n readonly factor: number;\n /** Last aimed point, in the surface's CSS px. */\n readonly aim: LoupePoint;\n /** Hex colour under the aim point. `null` until something answers. */\n readonly color: string | null;\n setMode(mode: LoupeMode): void;\n setFactor(factor: number): void;\n /** Aim at a surface point. Ignored while the lens covers it, is hidden, or\n * the model is disposed. */\n aimAt(p: LoupePoint): void;\n /** Sample what the lens shows at `p`, a point inside it, and report that to\n * `onPick`. Omit `p` to pick at the aim point. Leaves the aim, and `color`,\n * alone. */\n pick(p?: LoupePoint): string | null;\n dispose(): void;\n}\n\n/**\n * Build the loupe's model over a surface. It holds the state a magnifier has —\n * aim, factor, mode, colour — and none of the drawing.\n */\nexport function createLoupeModel(opts: LoupeModelOptions): LoupeModel {\n const { surface } = opts;\n let mode: LoupeMode = opts.mode ?? 'vector';\n let factor = opts.factor ?? 8;\n let aim: LoupePoint = { x: 0, y: 0 };\n let color: string | null = null;\n let disposed = false;\n\n const clamp = (n: number): number =>\n Math.min(opts.maxFactor ?? Number.POSITIVE_INFINITY,\n Math.max(opts.minFactor ?? Number.NEGATIVE_INFINITY, n));\n\n const teardown = () => {\n if (disposed) return;\n disposed = true;\n opts.onDispose?.();\n };\n\n const sampleColor = () => {\n const hex = surface.sample(aim);\n if (hex === null || hex === color) return;\n color = hex;\n opts.onColorChange?.(hex);\n };\n\n return {\n get mode() { return mode; },\n get factor() { return factor; },\n get aim() { return aim; },\n get color() { return color; },\n\n setMode(next) {\n if (disposed || next === mode) return;\n mode = next;\n surface.changed();\n },\n\n setFactor(next) {\n if (disposed) return;\n factor = clamp(next);\n surface.changed();\n },\n\n aimAt(p) {\n if (disposed) return;\n // A lens can be taken away by whatever owns it, without coming back\n // through `dispose`. Follow it down rather than keep sampling for a\n // magnifier nobody can see.\n if (surface.gone()) { teardown(); return; }\n if (surface.hidden()) return;\n if (surface.covers(p)) return;\n aim = p;\n sampleColor();\n surface.changed();\n },\n\n pick(p) {\n if (disposed) return null;\n let at = aim;\n if (p) {\n const rect = surface.lens();\n if (!rect) return null;\n at = loupeSourcePoint(p, rect, aim, factor);\n // The lens is part of the picture the surface presents. A point near\n // its frame maps back underneath it, and sampling there reports the\n // lens' own chrome as artwork.\n if (surface.covers(at)) return null;\n }\n const hex = surface.sample(at);\n if (hex !== null) opts.onPick?.(hex);\n return hex;\n },\n\n dispose: teardown,\n };\n}\n","import {\n createLoupeModel,\n type LoupeMode,\n type LoupeModel,\n type LoupePoint,\n} from '@weasel-js/loupe';\nimport { type RefObject, useCallback, useEffect, useReducer, useRef } from 'react';\nimport type { ResolvedLoupe } from './types';\n\n/** Options for {@link useLoupe}. */\nexport interface UseLoupeOptions {\n capability: ResolvedLoupe;\n /** The element the lens tracks the pointer across. */\n hostRef: RefObject<HTMLElement | null>;\n /** Whether the loupe is turned on. Hold-to-peek shows it regardless. */\n enabled: boolean;\n /** Hex colour at a host point. Omitted, the loupe reports no colour — which\n * is the honest answer for a surface with no pixels to read. */\n sample?: (p: LoupePoint) => string | null;\n}\n\n/** A loupe as a React view reads it. */\nexport interface LoupeState {\n /** Whether the lens should be drawn. */\n visible: boolean;\n /** Where it is aimed, in the host's own pixels. */\n aim: LoupePoint;\n factor: number;\n mode: LoupeMode;\n color: string | null;\n setMode: (mode: LoupeMode) => void;\n setFactor: (factor: number) => void;\n /** Sample what the lens shows at a point inside it. */\n pick: (p?: LoupePoint) => string | null;\n}\n\n/** How fast the wheel walks the magnification. Gentler than the pan-zoom\n * wheel: the lens' whole range is one order of magnitude. */\nconst WHEEL_RATE = 0.002;\n\n/**\n * Binds `@weasel-js/loupe`'s model to a host element and drives it from the\n * pointer, the peek key and the wheel.\n *\n * These are plain listeners rather than gesture bindings because labkit trials\n * do not route input through the dispatcher — see the loupe entry under\n * \"Selection, actions & UI panels\" in `docs/TODO.md`. They are all in this one\n * hook so that when trial input does go through it, this is the only file to\n * rewrite.\n */\nexport function useLoupe({ capability, hostRef, enabled, sample }: UseLoupeOptions): LoupeState {\n const [, bump] = useReducer((n: number) => n + 1, 0);\n\n const overRef = useRef(false);\n const peekingRef = useRef(false);\n const enabledRef = useRef(enabled);\n enabledRef.current = enabled;\n const sampleRef = useRef(sample);\n sampleRef.current = sample;\n const onColorChangeRef = useRef(capability.onColorChange);\n onColorChangeRef.current = capability.onColorChange;\n\n // Reads nothing but refs, so it is stable and an effect may depend on it.\n const shown = useCallback(\n (): boolean => overRef.current && (enabledRef.current || peekingRef.current),\n [],\n );\n\n const modelRef = useRef<LoupeModel | null>(null);\n if (modelRef.current === null) {\n modelRef.current = createLoupeModel({\n mode: capability.mode,\n factor: capability.factor,\n minFactor: capability.minFactor,\n maxFactor: capability.maxFactor,\n onColorChange: (hex) => onColorChangeRef.current?.(hex),\n surface: {\n lens: () => {\n const d = diameterRef.current;\n const { x, y } = modelRef.current?.aim ?? { x: 0, y: 0 };\n return { x: x - d / 2, y: y - d / 2, w: d, h: d };\n },\n // The lens follows the pointer and is painted over the host rather than\n // into it, so it is never part of the picture being magnified.\n covers: () => false,\n sample: (p) => sampleRef.current?.(p) ?? null,\n hidden: () => !shown(),\n gone: () => goneRef.current,\n changed: bump,\n },\n });\n }\n const model = modelRef.current;\n\n const diameterRef = useRef(capability.diameter);\n diameterRef.current = capability.diameter;\n const goneRef = useRef(false);\n\n // The model holds no resources, and `dispose` is one-way — so unmounting only\n // reports the lens gone. Disposing here instead leaves React's mount / unmount\n // / remount in StrictMode with a permanently dead model that silently ignores\n // every aim.\n useEffect(() => {\n goneRef.current = false;\n return () => {\n goneRef.current = true;\n };\n }, []);\n\n // Turning the loupe off with the pointer still inside must put the lens away,\n // and the model only reconsiders on an aim.\n useEffect(() => {\n if (!enabled && !peekingRef.current) bump();\n }, [enabled]);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n\n const onPointerMove = (e: PointerEvent): void => {\n overRef.current = true;\n const rect = host.getBoundingClientRect();\n model.aimAt({ x: e.clientX - rect.left, y: e.clientY - rect.top });\n if (!shown()) return;\n bump();\n };\n const onPointerLeave = (): void => {\n overRef.current = false;\n bump();\n };\n const onWheel = (e: WheelEvent): void => {\n if (!shown()) return;\n // Ahead of the stack's own pan-zoom, which is a React handler on this same\n // element: capture runs first, and stopping propagation keeps the event\n // from reaching React's root listener at all.\n e.preventDefault();\n e.stopPropagation();\n model.setFactor(model.factor * Math.exp(-e.deltaY * WHEEL_RATE));\n };\n\n host.addEventListener('pointermove', onPointerMove);\n host.addEventListener('pointerleave', onPointerLeave);\n host.addEventListener('wheel', onWheel, { capture: true, passive: false });\n return () => {\n host.removeEventListener('pointermove', onPointerMove);\n host.removeEventListener('pointerleave', onPointerLeave);\n host.removeEventListener('wheel', onWheel, { capture: true });\n };\n }, [hostRef, model, shown]);\n\n const peekKey = capability.peekKey;\n useEffect(() => {\n if (peekKey == null) return;\n const setPeeking = (next: boolean): void => {\n if (peekingRef.current === next) return;\n peekingRef.current = next;\n bump();\n };\n const onKeyDown = (e: KeyboardEvent): void => {\n if (e.key === peekKey) setPeeking(true);\n };\n const onKeyUp = (e: KeyboardEvent): void => {\n if (e.key === peekKey) setPeeking(false);\n };\n // A key held while the window loses focus never sends its keyup, so the\n // peek would stay on until the key was pressed and released again.\n const onBlur = (): void => setPeeking(false);\n\n window.addEventListener('keydown', onKeyDown);\n window.addEventListener('keyup', onKeyUp);\n window.addEventListener('blur', onBlur);\n return () => {\n window.removeEventListener('keydown', onKeyDown);\n window.removeEventListener('keyup', onKeyUp);\n window.removeEventListener('blur', onBlur);\n };\n }, [peekKey]);\n\n return {\n visible: shown(),\n aim: model.aim,\n factor: model.factor,\n mode: model.mode,\n color: model.color,\n setMode: model.setMode,\n setFactor: model.setFactor,\n pick: model.pick,\n };\n}\n","import { type RefObject, useContext, useMemo } from 'react';\nimport { CanvasStackContext } from '../canvas/CanvasStackContext';\nimport type { WorldSpec } from '../canvas/worldSpec';\nimport type { ViewTransform } from '../instrument/types';\nimport { CanvasLoupe } from './CanvasLoupe';\nimport { sampleStack } from './canvasLens';\nimport { DomLoupe } from './DomLoupe';\nimport { LoupeBubble } from './LoupeBubble';\nimport type { ResolvedLoupe } from './types';\nimport { useHostSize } from './useHostSize';\nimport { useLoupe } from './useLoupe';\n\n/** Props for `<TrialLoupe>`. */\nexport interface TrialLoupeProps {\n capability: ResolvedLoupe;\n /** Whether the trial's loupe is turned on. Hold-to-peek shows it anyway. */\n enabled: boolean;\n state: unknown;\n config: unknown;\n /** The trial's own camera. */\n view: ViewTransform;\n worldSpec?: WorldSpec;\n /** The element the lens tracks. A loupe mounted inside a `<CanvasStack>`\n * takes the stack's own element instead, and this goes unused. */\n hostRef?: RefObject<HTMLElement | null>;\n}\n\n/** Stable stand-in, so a loupe with nothing to track does not re-bind its\n * listeners on every render. */\nconst NO_HOST: RefObject<HTMLElement | null> = { current: null };\n\n/**\n * A trial's loupe, painted by whichever painter suits its content: the canvas\n * stack it is mounted inside, or the instrument's own `render` at a magnified\n * camera.\n */\nexport function TrialLoupe({\n capability,\n enabled,\n state,\n config,\n view,\n worldSpec,\n hostRef,\n}: TrialLoupeProps) {\n const stack = useContext(CanvasStackContext);\n const surface = capability.render ? undefined : stack?.surface;\n const host = surface?.element ?? hostRef ?? NO_HOST;\n\n const sample = useMemo(() => {\n if (!surface) return undefined;\n return (p: { x: number; y: number }): string | null => {\n const canvases = surface.canvases.current;\n return canvases ? sampleStack(surface.layers, canvases, p, surface.size.dpr) : null;\n };\n }, [surface]);\n\n const loupe = useLoupe({ capability, hostRef: host, enabled, sample });\n const measured = useHostSize(host);\n const size = surface?.size ?? measured;\n\n if (!loupe.visible) return null;\n\n return (\n <LoupeBubble aim={loupe.aim} diameter={capability.diameter}>\n {capability.render ? (\n <DomLoupe\n aim={loupe.aim}\n factor={loupe.factor}\n mode={loupe.mode}\n diameter={capability.diameter}\n size={size}\n view={view}\n frame={stack?.frame}\n state={state}\n config={config}\n render={capability.render}\n />\n ) : surface && stack ? (\n <CanvasLoupe\n aim={loupe.aim}\n factor={loupe.factor}\n mode={loupe.mode}\n diameter={capability.diameter}\n surface={surface}\n view={stack.view}\n frame={stack.frame}\n worldSpec={worldSpec}\n />\n ) : null}\n </LoupeBubble>\n );\n}\n","import type { LoupeMode } from '@weasel-js/loupe';\nimport type { ReactNode } from 'react';\nimport type { ViewportSize } from '../canvas/worldSpec';\nimport type { ViewTransform } from '../instrument/types';\n\n/** What a DOM instrument's loupe `render` is handed: the same data its own\n * `render` gets, and the camera to draw it through. */\nexport interface LoupeRenderArgs<TS = unknown, TC = unknown> {\n state: TS;\n config: TC;\n /** The trial's own camera composed with the magnification, about the aimed\n * point — so drawing the same content through it magnifies in place. */\n view: ViewTransform;\n /** The magnification on its own, for whatever must not scale with the\n * camera. */\n factor: number;\n mode: LoupeMode;\n /** The size of the viewport `view` is written for: the trial's content well,\n * not the lens. The lens shows a circle cut out of it. */\n size: ViewportSize;\n}\n\n/**\n * Declares that an instrument can be magnified.\n *\n * With no `render`, the loupe re-draws the instrument's canvas layers through a\n * zoomed camera, so it stays sharp at any factor. An instrument whose content is\n * DOM supplies `render` instead: given a camera, draw me again.\n */\nexport interface LoupeCapability<TS = unknown, TC = unknown> {\n render?: (args: LoupeRenderArgs<TS, TC>) => ReactNode;\n /** Opening magnification, clamped to the bounds below. Default 6. */\n factor?: number;\n /** What the wheel clamps to. Defaults 2 and 32. */\n minFactor?: number;\n maxFactor?: number;\n /** `'vector'` (default) re-renders the content magnified; `'pixel'` blows up\n * the pixels the instrument presented. A `render` loupe is always vector —\n * DOM has no framebuffer to enlarge. */\n mode?: LoupeMode;\n /** Lens diameter in CSS px. Default 200. */\n diameter?: number;\n /** Held for a momentary peek while the loupe is off. Default `'Alt'`; `null`\n * turns hold-to-peek off. Matched against `KeyboardEvent.key`. */\n peekKey?: string | null;\n /** Called with the colour under the aim, wherever the surface can say. The\n * canvas painter reads it back; a DOM loupe has no pixels to sample. */\n onColorChange?: (hex: string) => void;\n}\n\n/** An instrument's loupe declaration. `true` takes every default. */\nexport type LoupeDeclaration<TS = unknown, TC = unknown> = true | LoupeCapability<TS, TC>;\n\n/** A {@link LoupeCapability} with every default filled in. */\nexport interface ResolvedLoupe<TS = unknown, TC = unknown> {\n render?: (args: LoupeRenderArgs<TS, TC>) => ReactNode;\n onColorChange?: (hex: string) => void;\n factor: number;\n minFactor: number;\n maxFactor: number;\n mode: LoupeMode;\n diameter: number;\n peekKey: string | null;\n}\n\nexport const LOUPE_DEFAULTS = {\n factor: 6,\n minFactor: 2,\n maxFactor: 32,\n mode: 'vector',\n diameter: 200,\n peekKey: 'Alt',\n} as const satisfies Omit<ResolvedLoupe, 'render' | 'onColorChange'>;\n\nexport function resolveLoupe<TS, TC>(declared: LoupeDeclaration<TS, TC>): ResolvedLoupe<TS, TC> {\n const cap = declared === true ? {} : declared;\n const minFactor = cap.minFactor ?? LOUPE_DEFAULTS.minFactor;\n const maxFactor = cap.maxFactor ?? LOUPE_DEFAULTS.maxFactor;\n return {\n render: cap.render,\n onColorChange: cap.onColorChange,\n minFactor,\n maxFactor,\n factor: Math.min(maxFactor, Math.max(minFactor, cap.factor ?? LOUPE_DEFAULTS.factor)),\n diameter: cap.diameter ?? LOUPE_DEFAULTS.diameter,\n peekKey: cap.peekKey === undefined ? LOUPE_DEFAULTS.peekKey : cap.peekKey,\n // A DOM loupe has no framebuffer, so `pixel` would have nothing to enlarge.\n mode: cap.render ? 'vector' : (cap.mode ?? LOUPE_DEFAULTS.mode),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/loupe/canvasLens.ts","../src/loupe/CanvasLoupe.tsx","../src/loupe/DomLoupe.tsx","../src/loupe/LoupeBubble.tsx","../src/loupe/useHostSize.ts","../../loupe/src/geometry.ts","../../loupe/src/model.ts","../src/loupe/useLoupe.ts","../src/loupe/TrialLoupe.tsx","../src/loupe/types.ts"],"names":["jsx","useEffect","useRef"],"mappings":"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","file":"chunk-D5KQ5OY6.js","sourcesContent":["import type { LoupePoint } from '@weasel-js/loupe';\nimport { centerOn } from '../canvas/camera';\nimport { screenToWorld } from '../canvas/canvasCoords';\nimport type { CanvasLayerDescriptor } from '../canvas/useLayerScheduler';\nimport { DEFAULT_FRAME, resolveFrame, type WorldFrame, type WorldSpec } from '../canvas/worldSpec';\nimport type { ViewTransform } from '../instrument/types';\n\n/** The camera a round lens of `diameter` shows a magnified region through, and\n * the coordinate system it is read in — the instrument's own `WorldSpec`,\n * resolved against the lens rather than the stack. */\nexport interface LensCamera {\n view: ViewTransform;\n frame: WorldFrame;\n}\n\n/**\n * What a lens aimed at `aim` renders the stack's layers through.\n *\n * The lens is its own viewport, so the world spec resolves against its box: an\n * instrument centred on its viewport is centred in the lens too.\n */\nexport function lensCamera(\n aim: LoupePoint,\n outer: ViewTransform,\n outerFrame: WorldFrame,\n factor: number,\n diameter: number,\n worldSpec?: WorldSpec,\n): LensCamera {\n const size = { width: diameter, height: diameter };\n const frame = resolveFrame(worldSpec, size);\n const world = screenToWorld(aim, outer, outerFrame);\n return { view: centerOn(world, outer.zoom * factor, size, frame), frame };\n}\n\n/** A rectangle in a backing store's own device pixels. */\nexport interface SourceRect {\n sx: number;\n sy: number;\n sw: number;\n sh: number;\n}\n\n/**\n * The region a pixel-mode lens copies out of a presented canvas: the `diameter\n * / factor` CSS px around `aim`, in that canvas' backing-store pixels.\n */\nexport function lensSourceRect(\n aim: LoupePoint,\n factor: number,\n diameter: number,\n dpr: number,\n): SourceRect {\n const span = diameter / factor;\n return {\n sx: (aim.x - span / 2) * dpr,\n sy: (aim.y - span / 2) * dpr,\n sw: span * dpr,\n sh: span * dpr,\n };\n}\n\n/**\n * The colour a stack presents at a point: the topmost visible layer with\n * anything opaque there, or `null` when every layer is transparent — which is\n * the honest answer for a point showing nothing but the workspace behind.\n */\nexport function sampleStack(\n layers: readonly CanvasLayerDescriptor[],\n canvases: Map<string, HTMLCanvasElement>,\n p: LoupePoint,\n dpr: number,\n): string | null {\n for (let i = layers.length - 1; i >= 0; i--) {\n const layer = layers[i];\n if (!layer.visible) continue;\n const canvas = canvases.get(layer.id);\n if (!canvas) continue;\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n if (!ctx) continue;\n const x = Math.floor(p.x * dpr);\n const y = Math.floor(p.y * dpr);\n if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) continue;\n const [r, g, b, a] = ctx.getImageData(x, y, 1, 1).data;\n if (a === 0) continue;\n return `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;\n }\n return null;\n}\n\n/** Redraw the stack into a lens' own canvas. `mode` decides whether the layers\n * are re-run at the lens camera or their presented pixels are enlarged. */\nexport function drawCanvasLens(\n ctx: CanvasRenderingContext2D,\n opts: {\n aim: LoupePoint;\n factor: number;\n diameter: number;\n dpr: number;\n mode: 'vector' | 'pixel';\n outer: ViewTransform;\n outerFrame?: WorldFrame;\n worldSpec?: WorldSpec;\n layers: readonly CanvasLayerDescriptor[];\n canvases: Map<string, HTMLCanvasElement>;\n },\n): void {\n const { aim, factor, diameter, dpr, mode, outer, layers, canvases } = opts;\n ctx.save();\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, diameter, diameter);\n\n if (mode === 'pixel') {\n const { sx, sy, sw, sh } = lensSourceRect(aim, factor, diameter, dpr);\n ctx.imageSmoothingEnabled = false;\n for (const layer of layers) {\n if (!layer.visible) continue;\n const canvas = canvases.get(layer.id);\n if (!canvas || canvas.width === 0 || canvas.height === 0) continue;\n ctx.drawImage(canvas, sx, sy, sw, sh, 0, 0, diameter, diameter);\n }\n ctx.restore();\n return;\n }\n\n const lens = lensCamera(\n aim,\n outer,\n opts.outerFrame ?? DEFAULT_FRAME,\n factor,\n diameter,\n opts.worldSpec,\n );\n for (const layer of layers) {\n if (!layer.visible) continue;\n ctx.save();\n layer.render(ctx, lens.view, lens.frame);\n ctx.restore();\n }\n ctx.restore();\n}\n","import { useVisibleRaf } from '@weasel-js/core';\nimport type { LoupeMode, LoupePoint } from '@weasel-js/loupe';\nimport { useEffect, useRef } from 'react';\nimport type { CanvasStackSurface } from '../canvas/CanvasStackContext';\nimport type { WorldFrame, WorldSpec } from '../canvas/worldSpec';\nimport type { ViewTransform } from '../instrument/types';\nimport { drawCanvasLens } from './canvasLens';\n\n/** Props for `<CanvasLoupe>`. */\nexport interface CanvasLoupeProps {\n aim: LoupePoint;\n factor: number;\n mode: LoupeMode;\n diameter: number;\n /** The stack being magnified: its layers, its pixels, and its measured box. */\n surface: CanvasStackSurface;\n view: ViewTransform;\n frame: WorldFrame;\n worldSpec?: WorldSpec;\n}\n\n/**\n * Paints a lens over a `<CanvasStack>` by re-drawing the stack's layers through\n * a zoomed camera — so the magnified content is as sharp as the original, at\n * any factor. `pixel` mode enlarges what the stack presented instead.\n */\nexport function CanvasLoupe({\n aim,\n factor,\n mode,\n diameter,\n surface,\n view,\n frame,\n worldSpec,\n}: CanvasLoupeProps) {\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const dpr = surface.size.dpr;\n\n // Whatever the stack redraws every frame, the lens has to redraw too — an\n // instrument that animates from its own loop never re-renders this component.\n const args = { aim, factor, mode, diameter, dpr, view, frame, worldSpec, surface };\n const argsRef = useRef(args);\n argsRef.current = args;\n\n const loop = useVisibleRaf(\n () => {\n loop.request();\n const canvas = canvasRef.current;\n const ctx = canvas?.getContext('2d');\n const canvases = argsRef.current.surface.canvases.current;\n if (!ctx || !canvases) return;\n const a = argsRef.current;\n drawCanvasLens(ctx, {\n aim: a.aim,\n factor: a.factor,\n diameter: a.diameter,\n dpr: a.dpr,\n mode: a.mode,\n outer: a.view,\n outerFrame: a.frame,\n worldSpec: a.worldSpec,\n layers: a.surface.layers,\n canvases,\n });\n },\n { target: canvasRef },\n );\n\n useEffect(() => {\n loop.request();\n return () => loop.cancel();\n }, [loop]);\n\n return (\n <canvas\n ref={canvasRef}\n className=\"lk-loupe__canvas\"\n width={Math.max(1, Math.round(diameter * dpr))}\n height={Math.max(1, Math.round(diameter * dpr))}\n />\n );\n}\n","import type { LoupeMode, LoupePoint } from '@weasel-js/loupe';\nimport type { CSSProperties } from 'react';\nimport { zoomAt } from '../canvas/camera';\nimport type { ViewportSize, WorldFrame } from '../canvas/worldSpec';\nimport type { ViewTransform } from '../instrument/types';\nimport type { LoupeRenderArgs } from './types';\n\n/** Props for `<DomLoupe>`. */\nexport interface DomLoupeProps {\n aim: LoupePoint;\n factor: number;\n mode: LoupeMode;\n diameter: number;\n /** The host the instrument draws into, which the stage reproduces. */\n size: ViewportSize;\n /** The trial's own camera. */\n view: ViewTransform;\n frame?: WorldFrame;\n state: unknown;\n config: unknown;\n render: (args: LoupeRenderArgs) => React.ReactNode;\n}\n\n/**\n * Paints a lens over DOM content by asking the instrument to draw itself again\n * at a magnified camera.\n *\n * The stage is a full copy of the host, so the instrument's own layout still\n * holds; the camera is composed about the aimed point, which keeps that point\n * where it already was, and the stage is then slid by `diameter / 2 - aim` to\n * bring it to the middle of the lens.\n */\nexport function DomLoupe({\n aim,\n factor,\n mode,\n diameter,\n size,\n view,\n frame,\n state,\n config,\n render,\n}: DomLoupeProps) {\n const style: CSSProperties = {\n width: `${size.width}px`,\n height: `${size.height}px`,\n transform: `translate(${diameter / 2 - aim.x}px, ${diameter / 2 - aim.y}px)`,\n };\n return (\n <div className=\"lk-loupe__stage\" style={style}>\n {render({\n state,\n config,\n view: zoomAt(view, factor, aim, { frame }),\n factor,\n mode,\n size,\n })}\n </div>\n );\n}\n","import type { LoupePoint } from '@weasel-js/loupe';\nimport type { CSSProperties, ReactNode, RefObject } from 'react';\n\n/** Props for `<LoupeBubble>`. */\nexport interface LoupeBubbleProps {\n /** Where the lens is aimed, in its container's own pixels. */\n aim: LoupePoint;\n diameter: number;\n hostRef?: RefObject<HTMLDivElement | null>;\n children: ReactNode;\n}\n\n/**\n * The lens itself: a circle centred on the aimed point, clipping whatever a\n * painter draws into it.\n *\n * It takes no pointer events, so the pan, the wheel and anything underneath\n * keep working while it is up — and it is `aria-hidden`, since it magnifies\n * content already on the page rather than adding any.\n */\nexport function LoupeBubble({ aim, diameter, hostRef, children }: LoupeBubbleProps) {\n const style: CSSProperties = {\n width: `${diameter}px`,\n height: `${diameter}px`,\n transform: `translate(${aim.x - diameter / 2}px, ${aim.y - diameter / 2}px)`,\n };\n return (\n <div ref={hostRef} className=\"lk-loupe\" style={style} aria-hidden=\"true\">\n {children}\n </div>\n );\n}\n","import { type RefObject, useEffect, useState } from 'react';\nimport type { ViewportSize } from '../canvas/worldSpec';\n\nconst ZERO: ViewportSize = { width: 0, height: 0 };\n\n/** An element's CSS-pixel box, kept current. `{0, 0}` until it is measured, and\n * wherever `ResizeObserver` is absent. */\nexport function useHostSize(ref: RefObject<HTMLElement | null>): ViewportSize {\n const [size, setSize] = useState<ViewportSize>(ZERO);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const update = (): void => {\n const rect = el.getBoundingClientRect();\n setSize((prev) =>\n prev.width === rect.width && prev.height === rect.height\n ? prev\n : { width: rect.width, height: rect.height },\n );\n };\n update();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, [ref]);\n\n return size;\n}\n","import type { View } from '@weasel-js/core';\n\n/** A point on the magnified surface, in its own CSS pixels. */\nexport interface LoupePoint {\n x: number;\n y: number;\n}\n\n/** The lens' rectangle on that surface. */\nexport interface LoupeRect extends LoupePoint {\n w: number;\n h: number;\n}\n\n/**\n * The inner `View` a loupe renders its content through: the outer view's\n * magnification times `factor`, positioned so `target` sits at the center of\n * a viewport the size of `rect`.\n */\nexport function loupeInnerView(\n target: LoupePoint,\n outer: View,\n rect: LoupeRect,\n factor: number,\n): View {\n const scale = { x: outer.scale.x * factor, y: outer.scale.y * factor };\n return {\n x: target.x - rect.w / 2 / scale.x,\n y: target.y - rect.h / 2 / scale.y,\n scale,\n };\n}\n\n/**\n * Where on the outer surface a point inside the lens is looking, in\n * screen-space CSS px.\n *\n * The inverse of {@link loupeInnerView}: the lens centers `aim` in `rect` and\n * magnifies by `factor`, and the outer view's own scale cancels out of the\n * round trip — so this needs no `View`.\n */\nexport function loupeSourcePoint(\n p: LoupePoint,\n rect: LoupeRect,\n aim: LoupePoint,\n factor: number,\n): LoupePoint {\n return {\n x: aim.x + (p.x - (rect.x + rect.w / 2)) / factor,\n y: aim.y + (p.y - (rect.y + rect.h / 2)) / factor,\n };\n}\n","import { type LoupePoint, type LoupeRect, loupeSourcePoint } from './geometry';\n\n/** How a loupe magnifies. `'vector'` re-renders the source through a zoomed-in\n * view, so content stays sharp at any factor; `'pixel'` blows up the actual\n * pixels the surface presented. */\nexport type LoupeMode = 'vector' | 'pixel';\n\n/**\n * What a loupe needs from the surface it magnifies. A painter implements it,\n * and the model asks nothing about how the lens is drawn — only where it is,\n * what colour is under a point, and whether anyone can still see it.\n */\nexport interface LoupeSurface {\n /** The lens' rectangle, or `null` while it has none. */\n lens(): LoupeRect | null;\n /**\n * Does the lens itself cover this point? A stationary lens does over its own\n * frame, which is what the freeze rule below is for; a lens that follows the\n * pointer never covers it and answers `false`.\n */\n covers(p: LoupePoint): boolean;\n /** Hex colour at a surface point, or `null` when the surface cannot say. */\n sample(p: LoupePoint): string | null;\n /** Is the lens off screen? Aims are ignored while it is. */\n hidden(): boolean;\n /** Is the lens gone for good? The model tears itself down when it is. */\n gone(): boolean;\n /** Something the painter must redraw for has changed. */\n changed(): void;\n}\n\n/** Options for {@link createLoupeModel}. */\nexport interface LoupeModelOptions {\n surface: LoupeSurface;\n mode?: LoupeMode;\n /** Magnification. Default 8. */\n factor?: number;\n /** Bounds `setFactor` clamps to. Unset means unclamped. */\n minFactor?: number;\n maxFactor?: number;\n /** Called with the hex colour under the aim point whenever it changes. */\n onColorChange?: (hex: string) => void;\n /** Called when a pick lands — an eyedropper on the magnified surface. Where\n * the colour goes is the consumer's business. */\n onPick?: (hex: string) => void;\n /** Called when the model tears itself down because the surface reported the\n * lens gone, so the painter can drop what it was holding. */\n onDispose?: () => void;\n}\n\n/** Where a loupe is aimed, how far it magnifies, and what colour it is over. */\nexport interface LoupeModel {\n readonly mode: LoupeMode;\n readonly factor: number;\n /** Last aimed point, in the surface's CSS px. */\n readonly aim: LoupePoint;\n /** Hex colour under the aim point. `null` until something answers. */\n readonly color: string | null;\n setMode(mode: LoupeMode): void;\n setFactor(factor: number): void;\n /** Aim at a surface point. Ignored while the lens covers it, is hidden, or\n * the model is disposed. */\n aimAt(p: LoupePoint): void;\n /** Sample what the lens shows at `p`, a point inside it, and report that to\n * `onPick`. Omit `p` to pick at the aim point. Leaves the aim, and `color`,\n * alone. */\n pick(p?: LoupePoint): string | null;\n dispose(): void;\n}\n\n/**\n * Build the loupe's model over a surface. It holds the state a magnifier has —\n * aim, factor, mode, colour — and none of the drawing.\n */\nexport function createLoupeModel(opts: LoupeModelOptions): LoupeModel {\n const { surface } = opts;\n let mode: LoupeMode = opts.mode ?? 'vector';\n let factor = opts.factor ?? 8;\n let aim: LoupePoint = { x: 0, y: 0 };\n let color: string | null = null;\n let disposed = false;\n\n const clamp = (n: number): number =>\n Math.min(opts.maxFactor ?? Number.POSITIVE_INFINITY,\n Math.max(opts.minFactor ?? Number.NEGATIVE_INFINITY, n));\n\n const teardown = () => {\n if (disposed) return;\n disposed = true;\n opts.onDispose?.();\n };\n\n const sampleColor = () => {\n const hex = surface.sample(aim);\n if (hex === null || hex === color) return;\n color = hex;\n opts.onColorChange?.(hex);\n };\n\n return {\n get mode() { return mode; },\n get factor() { return factor; },\n get aim() { return aim; },\n get color() { return color; },\n\n setMode(next) {\n if (disposed || next === mode) return;\n mode = next;\n surface.changed();\n },\n\n setFactor(next) {\n if (disposed) return;\n factor = clamp(next);\n surface.changed();\n },\n\n aimAt(p) {\n if (disposed) return;\n // A lens can be taken away by whatever owns it, without coming back\n // through `dispose`. Follow it down rather than keep sampling for a\n // magnifier nobody can see.\n if (surface.gone()) { teardown(); return; }\n if (surface.hidden()) return;\n if (surface.covers(p)) return;\n aim = p;\n sampleColor();\n surface.changed();\n },\n\n pick(p) {\n if (disposed) return null;\n let at = aim;\n if (p) {\n const rect = surface.lens();\n if (!rect) return null;\n at = loupeSourcePoint(p, rect, aim, factor);\n // The lens is part of the picture the surface presents. A point near\n // its frame maps back underneath it, and sampling there reports the\n // lens' own chrome as artwork.\n if (surface.covers(at)) return null;\n }\n const hex = surface.sample(at);\n if (hex !== null) opts.onPick?.(hex);\n return hex;\n },\n\n dispose: teardown,\n };\n}\n","import {\n createLoupeModel,\n type LoupeMode,\n type LoupeModel,\n type LoupePoint,\n} from '@weasel-js/loupe';\nimport { type RefObject, useCallback, useEffect, useReducer, useRef } from 'react';\nimport type { ResolvedLoupe } from './types';\n\n/** Options for {@link useLoupe}. */\nexport interface UseLoupeOptions {\n capability: ResolvedLoupe;\n /** The element the lens tracks the pointer across. */\n hostRef: RefObject<HTMLElement | null>;\n /** Whether the loupe is turned on. Hold-to-peek shows it regardless. */\n enabled: boolean;\n /** Hex colour at a host point. Omitted, the loupe reports no colour — which\n * is the honest answer for a surface with no pixels to read. */\n sample?: (p: LoupePoint) => string | null;\n}\n\n/** A loupe as a React view reads it. */\nexport interface LoupeState {\n /** Whether the lens should be drawn. */\n visible: boolean;\n /** Where it is aimed, in the host's own pixels. */\n aim: LoupePoint;\n factor: number;\n mode: LoupeMode;\n color: string | null;\n setMode: (mode: LoupeMode) => void;\n setFactor: (factor: number) => void;\n /** Sample what the lens shows at a point inside it. */\n pick: (p?: LoupePoint) => string | null;\n}\n\n/** How fast the wheel walks the magnification. Gentler than the pan-zoom\n * wheel: the lens' whole range is one order of magnitude. */\nconst WHEEL_RATE = 0.002;\n\n/**\n * Binds `@weasel-js/loupe`'s model to a host element and drives it from the\n * pointer, the peek key and the wheel.\n *\n * These are plain listeners rather than gesture bindings because labkit trials\n * do not route input through the dispatcher — see the loupe entry under\n * \"Selection, actions & UI panels\" in `docs/TODO.md`. They are all in this one\n * hook so that when trial input does go through it, this is the only file to\n * rewrite.\n */\nexport function useLoupe({ capability, hostRef, enabled, sample }: UseLoupeOptions): LoupeState {\n const [, bump] = useReducer((n: number) => n + 1, 0);\n\n const overRef = useRef(false);\n const peekingRef = useRef(false);\n const enabledRef = useRef(enabled);\n enabledRef.current = enabled;\n const sampleRef = useRef(sample);\n sampleRef.current = sample;\n const onColorChangeRef = useRef(capability.onColorChange);\n onColorChangeRef.current = capability.onColorChange;\n\n // Reads nothing but refs, so it is stable and an effect may depend on it.\n const shown = useCallback(\n (): boolean => overRef.current && (enabledRef.current || peekingRef.current),\n [],\n );\n\n const modelRef = useRef<LoupeModel | null>(null);\n if (modelRef.current === null) {\n modelRef.current = createLoupeModel({\n mode: capability.mode,\n factor: capability.factor,\n minFactor: capability.minFactor,\n maxFactor: capability.maxFactor,\n onColorChange: (hex) => onColorChangeRef.current?.(hex),\n surface: {\n lens: () => {\n const d = diameterRef.current;\n const { x, y } = modelRef.current?.aim ?? { x: 0, y: 0 };\n return { x: x - d / 2, y: y - d / 2, w: d, h: d };\n },\n // The lens follows the pointer and is painted over the host rather than\n // into it, so it is never part of the picture being magnified.\n covers: () => false,\n sample: (p) => sampleRef.current?.(p) ?? null,\n hidden: () => !shown(),\n gone: () => goneRef.current,\n changed: bump,\n },\n });\n }\n const model = modelRef.current;\n\n const diameterRef = useRef(capability.diameter);\n diameterRef.current = capability.diameter;\n const goneRef = useRef(false);\n\n // The model holds no resources, and `dispose` is one-way — so unmounting only\n // reports the lens gone. Disposing here instead leaves React's mount / unmount\n // / remount in StrictMode with a permanently dead model that silently ignores\n // every aim.\n useEffect(() => {\n goneRef.current = false;\n return () => {\n goneRef.current = true;\n };\n }, []);\n\n // Turning the loupe off with the pointer still inside must put the lens away,\n // and the model only reconsiders on an aim.\n useEffect(() => {\n if (!enabled && !peekingRef.current) bump();\n }, [enabled]);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n\n const onPointerMove = (e: PointerEvent): void => {\n overRef.current = true;\n const rect = host.getBoundingClientRect();\n model.aimAt({ x: e.clientX - rect.left, y: e.clientY - rect.top });\n if (!shown()) return;\n bump();\n };\n const onPointerLeave = (): void => {\n overRef.current = false;\n bump();\n };\n const onWheel = (e: WheelEvent): void => {\n if (!shown()) return;\n // Ahead of the stack's own pan-zoom, which is a React handler on this same\n // element: capture runs first, and stopping propagation keeps the event\n // from reaching React's root listener at all.\n e.preventDefault();\n e.stopPropagation();\n model.setFactor(model.factor * Math.exp(-e.deltaY * WHEEL_RATE));\n };\n\n host.addEventListener('pointermove', onPointerMove);\n host.addEventListener('pointerleave', onPointerLeave);\n host.addEventListener('wheel', onWheel, { capture: true, passive: false });\n return () => {\n host.removeEventListener('pointermove', onPointerMove);\n host.removeEventListener('pointerleave', onPointerLeave);\n host.removeEventListener('wheel', onWheel, { capture: true });\n };\n }, [hostRef, model, shown]);\n\n const peekKey = capability.peekKey;\n useEffect(() => {\n if (peekKey == null) return;\n const setPeeking = (next: boolean): void => {\n if (peekingRef.current === next) return;\n peekingRef.current = next;\n bump();\n };\n const onKeyDown = (e: KeyboardEvent): void => {\n if (e.key === peekKey) setPeeking(true);\n };\n const onKeyUp = (e: KeyboardEvent): void => {\n if (e.key === peekKey) setPeeking(false);\n };\n // A key held while the window loses focus never sends its keyup, so the\n // peek would stay on until the key was pressed and released again.\n const onBlur = (): void => setPeeking(false);\n\n window.addEventListener('keydown', onKeyDown);\n window.addEventListener('keyup', onKeyUp);\n window.addEventListener('blur', onBlur);\n return () => {\n window.removeEventListener('keydown', onKeyDown);\n window.removeEventListener('keyup', onKeyUp);\n window.removeEventListener('blur', onBlur);\n };\n }, [peekKey]);\n\n return {\n visible: shown(),\n aim: model.aim,\n factor: model.factor,\n mode: model.mode,\n color: model.color,\n setMode: model.setMode,\n setFactor: model.setFactor,\n pick: model.pick,\n };\n}\n","import { type RefObject, useContext, useMemo } from 'react';\nimport { CanvasStackContext } from '../canvas/CanvasStackContext';\nimport type { WorldSpec } from '../canvas/worldSpec';\nimport type { ViewTransform } from '../instrument/types';\nimport { CanvasLoupe } from './CanvasLoupe';\nimport { sampleStack } from './canvasLens';\nimport { DomLoupe } from './DomLoupe';\nimport { LoupeBubble } from './LoupeBubble';\nimport type { ResolvedLoupe } from './types';\nimport { useHostSize } from './useHostSize';\nimport { useLoupe } from './useLoupe';\n\n/** Props for `<TrialLoupe>`. */\nexport interface TrialLoupeProps {\n capability: ResolvedLoupe;\n /** Whether the trial's loupe is turned on. Hold-to-peek shows it anyway. */\n enabled: boolean;\n state: unknown;\n config: unknown;\n /** The trial's own camera. */\n view: ViewTransform;\n worldSpec?: WorldSpec;\n /** The element the lens tracks. A loupe mounted inside a `<CanvasStack>`\n * takes the stack's own element instead, and this goes unused. */\n hostRef?: RefObject<HTMLElement | null>;\n}\n\n/** Stable stand-in, so a loupe with nothing to track does not re-bind its\n * listeners on every render. */\nconst NO_HOST: RefObject<HTMLElement | null> = { current: null };\n\n/**\n * A trial's loupe, painted by whichever painter suits its content: the canvas\n * stack it is mounted inside, or the instrument's own `render` at a magnified\n * camera.\n */\nexport function TrialLoupe({\n capability,\n enabled,\n state,\n config,\n view,\n worldSpec,\n hostRef,\n}: TrialLoupeProps) {\n const stack = useContext(CanvasStackContext);\n const surface = capability.render ? undefined : stack?.surface;\n const host = surface?.element ?? hostRef ?? NO_HOST;\n\n const sample = useMemo(() => {\n if (!surface) return undefined;\n return (p: { x: number; y: number }): string | null => {\n const canvases = surface.canvases.current;\n return canvases ? sampleStack(surface.layers, canvases, p, surface.size.dpr) : null;\n };\n }, [surface]);\n\n const loupe = useLoupe({ capability, hostRef: host, enabled, sample });\n const measured = useHostSize(host);\n const size = surface?.size ?? measured;\n\n if (!loupe.visible) return null;\n\n return (\n <LoupeBubble aim={loupe.aim} diameter={capability.diameter}>\n {capability.render ? (\n <DomLoupe\n aim={loupe.aim}\n factor={loupe.factor}\n mode={loupe.mode}\n diameter={capability.diameter}\n size={size}\n view={view}\n frame={stack?.frame}\n state={state}\n config={config}\n render={capability.render}\n />\n ) : surface && stack ? (\n <CanvasLoupe\n aim={loupe.aim}\n factor={loupe.factor}\n mode={loupe.mode}\n diameter={capability.diameter}\n surface={surface}\n view={stack.view}\n frame={stack.frame}\n worldSpec={worldSpec}\n />\n ) : null}\n </LoupeBubble>\n );\n}\n","import type { LoupeMode } from '@weasel-js/loupe';\nimport type { ReactNode } from 'react';\nimport type { ViewportSize } from '../canvas/worldSpec';\nimport type { ViewTransform } from '../instrument/types';\n\n/** What a DOM instrument's loupe `render` is handed: the same data its own\n * `render` gets, and the camera to draw it through. */\nexport interface LoupeRenderArgs<TS = unknown, TC = unknown> {\n state: TS;\n config: TC;\n /** The trial's own camera composed with the magnification, about the aimed\n * point — so drawing the same content through it magnifies in place. */\n view: ViewTransform;\n /** The magnification on its own, for whatever must not scale with the\n * camera. */\n factor: number;\n mode: LoupeMode;\n /** The size of the viewport `view` is written for: the trial's content well,\n * not the lens. The lens shows a circle cut out of it. */\n size: ViewportSize;\n}\n\n/**\n * Declares that an instrument can be magnified.\n *\n * With no `render`, the loupe re-draws the instrument's canvas layers through a\n * zoomed camera, so it stays sharp at any factor. An instrument whose content is\n * DOM supplies `render` instead: given a camera, draw me again.\n */\nexport interface LoupeCapability<TS = unknown, TC = unknown> {\n render?: (args: LoupeRenderArgs<TS, TC>) => ReactNode;\n /** Opening magnification, clamped to the bounds below. Default 6. */\n factor?: number;\n /** What the wheel clamps to. Defaults 2 and 32. */\n minFactor?: number;\n maxFactor?: number;\n /** `'vector'` (default) re-renders the content magnified; `'pixel'` blows up\n * the pixels the instrument presented. A `render` loupe is always vector —\n * DOM has no framebuffer to enlarge. */\n mode?: LoupeMode;\n /** Lens diameter in CSS px. Default 200. */\n diameter?: number;\n /** Held for a momentary peek while the loupe is off. Default `'Alt'`; `null`\n * turns hold-to-peek off. Matched against `KeyboardEvent.key`. */\n peekKey?: string | null;\n /** Called with the colour under the aim, wherever the surface can say. The\n * canvas painter reads it back; a DOM loupe has no pixels to sample. */\n onColorChange?: (hex: string) => void;\n}\n\n/** An instrument's loupe declaration. `true` takes every default. */\nexport type LoupeDeclaration<TS = unknown, TC = unknown> = true | LoupeCapability<TS, TC>;\n\n/** A {@link LoupeCapability} with every default filled in. */\nexport interface ResolvedLoupe<TS = unknown, TC = unknown> {\n render?: (args: LoupeRenderArgs<TS, TC>) => ReactNode;\n onColorChange?: (hex: string) => void;\n factor: number;\n minFactor: number;\n maxFactor: number;\n mode: LoupeMode;\n diameter: number;\n peekKey: string | null;\n}\n\nexport const LOUPE_DEFAULTS = {\n factor: 6,\n minFactor: 2,\n maxFactor: 32,\n mode: 'vector',\n diameter: 200,\n peekKey: 'Alt',\n} as const satisfies Omit<ResolvedLoupe, 'render' | 'onColorChange'>;\n\nexport function resolveLoupe<TS, TC>(declared: LoupeDeclaration<TS, TC>): ResolvedLoupe<TS, TC> {\n const cap = declared === true ? {} : declared;\n const minFactor = cap.minFactor ?? LOUPE_DEFAULTS.minFactor;\n const maxFactor = cap.maxFactor ?? LOUPE_DEFAULTS.maxFactor;\n return {\n render: cap.render,\n onColorChange: cap.onColorChange,\n minFactor,\n maxFactor,\n factor: Math.min(maxFactor, Math.max(minFactor, cap.factor ?? LOUPE_DEFAULTS.factor)),\n diameter: cap.diameter ?? LOUPE_DEFAULTS.diameter,\n peekKey: cap.peekKey === undefined ? LOUPE_DEFAULTS.peekKey : cap.peekKey,\n // A DOM loupe has no framebuffer, so `pixel` would have nothing to enlarge.\n mode: cap.render ? 'vector' : (cap.mode ?? LOUPE_DEFAULTS.mode),\n };\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { zoomAt } from './chunk-CTRKTLYZ.js';
|
|
2
2
|
import { DEFAULT_FRAME, resolveFrame, screenToWorld } from './chunk-2AYGEN57.js';
|
|
3
3
|
import { CanvasStackContext } from './chunk-CQLQPQ4P.js';
|
|
4
|
-
import { openPointerSession, useVisibleRaf } from './chunk-
|
|
4
|
+
import { openPointerSession, useVisibleRaf } from './chunk-54ZWZ5FQ.js';
|
|
5
5
|
import { useRef, useCallback, useEffect, useState, useMemo } from 'react';
|
|
6
6
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
7
|
|
|
@@ -321,5 +321,5 @@ function useOrbit({
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
export { CanvasStack, PITCH_LIMIT, clampPitch, orbitAfterDrag, orbitAfterWheel, useOrbit, usePanZoom, wrapYaw };
|
|
324
|
-
//# sourceMappingURL=chunk-
|
|
325
|
-
//# sourceMappingURL=chunk-
|
|
324
|
+
//# sourceMappingURL=chunk-FJG4PHTL.js.map
|
|
325
|
+
//# sourceMappingURL=chunk-FJG4PHTL.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/canvas/useLayerScheduler.ts","../src/canvas/usePanZoom.ts","../src/canvas/CanvasStack.tsx","../src/canvas/useOrbit.ts"],"names":["useRef","useEffect","DRAG_THRESHOLD","useCallback"],"mappings":"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","file":"chunk-PP7ZWAMV.js","sourcesContent":["import { useVisibleRaf } from '@weasel-js/core';\nimport { type RefObject, useEffect, useRef } from 'react';\nimport type { ViewTransform } from '../instrument/types';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\n/** One layer of a canvas stack: its id, whether it is currently shown, and how\n * it paints itself. */\nexport interface CanvasLayerDescriptor {\n id: string;\n visible: boolean;\n render: (ctx: CanvasRenderingContext2D, view: ViewTransform, frame: WorldFrame) => void;\n}\n\ninterface SchedulerOptions {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n /** The instrument's coordinate system, resolved against `size`. */\n frame?: WorldFrame;\n canvasRefs: RefObject<Map<string, HTMLCanvasElement>>;\n size: { width: number; height: number; dpr: number };\n /** The element the stack occupies. Given one, the scheduler also stops while\n * the stack sits outside the viewport. */\n host?: RefObject<Element | null>;\n}\n\nexport function useLayerScheduler({\n layers,\n view,\n frame = DEFAULT_FRAME,\n canvasRefs,\n size,\n host,\n}: SchedulerOptions): void {\n const dirty = useRef<Set<string>>(new Set());\n const lastRenderRef = useRef<Map<string, CanvasLayerDescriptor['render']>>(new Map());\n\n useEffect(() => {\n for (const layer of layers) {\n const prev = lastRenderRef.current.get(layer.id);\n if (prev !== layer.render) dirty.current.add(layer.id);\n lastRenderRef.current.set(layer.id, layer.render);\n }\n for (const id of [...lastRenderRef.current.keys()]) {\n if (!layers.find((l) => l.id === id)) lastRenderRef.current.delete(id);\n }\n }, [layers]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-mark dirty when view or size changes\n useEffect(() => {\n for (const layer of layers) dirty.current.add(layer.id);\n }, [view, frame, size, layers]);\n\n // Painting only what is dirty is not the same as doing nothing: a hidden tab\n // still commits React updates, and the effect above marks every layer dirty\n // on any view or size change. The gate is what actually stops the work.\n const frameLoop = useVisibleRaf(\n () => {\n if (dirty.current.size === 0) return;\n const map = canvasRefs.current;\n if (!map) {\n dirty.current.clear();\n return;\n }\n for (const layer of layers) {\n if (!dirty.current.has(layer.id)) continue;\n if (!layer.visible) continue;\n const canvas = map.get(layer.id);\n if (!canvas) continue;\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n ctx.save();\n ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);\n ctx.clearRect(0, 0, size.width, size.height);\n layer.render(ctx, view, frame);\n ctx.restore();\n }\n dirty.current.clear();\n },\n { target: host },\n );\n\n useEffect(() => {\n if (dirty.current.size > 0) frameLoop.request();\n });\n}\n","import { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport {\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useRef,\n type WheelEvent,\n} from 'react';\nimport type { ViewTransform } from '../instrument/types';\nimport { zoomAt } from './camera';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\nexport interface UsePanZoomOptions {\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n /** The opening `view.zoom` always stays reachable, widening these past\n * whatever is passed here if it would otherwise exclude it. */\n minZoom?: number;\n maxZoom?: number;\n /** The instrument's coordinate system, resolved against the viewport. The\n * wheel anchors in this frame; omitting it anchors at the element's\n * top-left, which is what labkit did before a frame could be declared. */\n frame?: WorldFrame;\n /** A primary-button press released without ever crossing the drag\n * threshold. The pointer session owns the release, so the gesture's end is\n * reported here rather than left for a consumer's own `onPointerUp`. */\n onTap?: (e: PointerEvent) => void;\n}\n\nexport interface PanZoomHandlers {\n onWheel: (e: WheelEvent<HTMLElement>) => void;\n onPointerDown: (e: ReactPointerEvent<HTMLElement>) => void;\n isDragging: () => boolean;\n}\n\ninterface DragState {\n startScreenX: number;\n startScreenY: number;\n startPan: { x: number; y: number };\n moved: boolean;\n}\n\nconst DRAG_THRESHOLD = 3;\n\nfunction isPositiveFinite(n: number): boolean {\n return Number.isFinite(n) && n > 0;\n}\n\nexport function usePanZoom({\n view,\n onViewChange,\n minZoom = 0.1,\n maxZoom = 32,\n frame = DEFAULT_FRAME,\n onTap,\n}: UsePanZoomOptions): PanZoomHandlers {\n const dragRef = useRef<DragState | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n const viewRef = useRef(view);\n viewRef.current = view;\n const onViewChangeRef = useRef(onViewChange);\n onViewChangeRef.current = onViewChange;\n const onTapRef = useRef(onTap);\n onTapRef.current = onTap;\n // Captured once, from the view the canvas opened at — not the current view,\n // which would let a prior zoom-out shrink the range and trap the opening\n // view unreachable behind it.\n const initialZoomRef = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);\n\n const onWheel = useCallback(\n (e: WheelEvent<HTMLElement>) => {\n e.preventDefault();\n const rect = e.currentTarget.getBoundingClientRect();\n const initialZoom = initialZoomRef.current;\n onViewChange(\n zoomAt(\n viewRef.current,\n Math.exp(-e.deltaY * 0.001),\n { x: e.clientX - rect.left, y: e.clientY - rect.top },\n {\n frame,\n min: initialZoom == null ? minZoom : Math.min(minZoom, initialZoom),\n max: initialZoom == null ? maxZoom : Math.max(maxZoom, initialZoom),\n },\n ),\n );\n },\n [onViewChange, minZoom, maxZoom, frame],\n );\n\n const clearDrag = useCallback(() => {\n dragRef.current = null;\n sessionRef.current = null;\n }, []);\n\n const onPointerDown = useCallback(\n (e: ReactPointerEvent<HTMLElement>) => {\n if (e.button !== 0) return;\n const drag: DragState = {\n startScreenX: e.clientX,\n startScreenY: e.clientY,\n startPan: { ...viewRef.current.pan },\n moved: false,\n };\n dragRef.current = drag;\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => {\n const dx = ev.clientX - drag.startScreenX;\n const dy = ev.clientY - drag.startScreenY;\n if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;\n drag.moved = true;\n onViewChangeRef.current({\n zoom: viewRef.current.zoom,\n pan: { x: drag.startPan.x + dx, y: drag.startPan.y + dy },\n });\n },\n onEnd: (ev) => {\n const tapped = !drag.moved;\n clearDrag();\n if (tapped) onTapRef.current?.(ev);\n },\n // A cancelled pan keeps whatever it had already applied: every move\n // was committed as it arrived, so there is nothing left to undo.\n onCancel: clearDrag,\n });\n },\n [clearDrag],\n );\n\n useEffect(() => () => sessionRef.current?.cancel(), []);\n\n const isDragging = useCallback(() => dragRef.current?.moved === true, []);\n\n return { onWheel, onPointerDown, isDragging };\n}\n","import { type CSSProperties, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { CanvasStackContext } from './CanvasStackContext';\nimport { screenToWorld } from './canvasCoords';\nimport { type CanvasLayerDescriptor, useLayerScheduler } from './useLayerScheduler';\nimport { usePanZoom } from './usePanZoom';\nimport { resolveFrame, type ViewportSize, type WorldSpec } from './worldSpec';\n\n/** Props for `<CanvasStack>`. */\nexport interface CanvasStackProps {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n /** The instrument's coordinate system. Omitted, world (0,0) sits at the\n * element's top-left with y running down. */\n worldSpec?: WorldSpec;\n minZoom?: number;\n maxZoom?: number;\n width?: number | string;\n height?: number | string;\n className?: string;\n /** Fired whenever the stack is measured, so a consumer can place a view that\n * only makes sense in terms of the viewport. */\n onResize?: (size: ViewportSize) => void;\n onHitTest?: (worldPos: Point) => void;\n children?: ReactNode;\n}\n\n/** Stacks one `<canvas>` per layer and drives them from a shared view, so a\n * layer that changes rarely is not redrawn with one that changes every frame.\n * Handles sizing, device pixel ratio, and pan/zoom. */\nexport function CanvasStack({\n layers,\n view,\n onViewChange,\n worldSpec,\n minZoom,\n maxZoom,\n width = '100%',\n height = '100%',\n className,\n onResize,\n onHitTest,\n children,\n}: CanvasStackProps) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasMap = useRef<Map<string, HTMLCanvasElement>>(new Map());\n const [size, setSize] = useState({ width: 0, height: 0, dpr: 1 });\n\n const setCanvasRef = (id: string, el: HTMLCanvasElement | null): void => {\n if (el) canvasMap.current.set(id, el);\n else canvasMap.current.delete(id);\n };\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const update = () => {\n const rect = el.getBoundingClientRect();\n const dpr = window.devicePixelRatio ?? 1;\n setSize((prev) => {\n if (prev.width === rect.width && prev.height === rect.height && prev.dpr === dpr) {\n return prev;\n }\n return { width: rect.width, height: rect.height, dpr };\n });\n };\n update();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n useEffect(() => {\n if (size.width === 0 && size.height === 0) return;\n onResizeRef.current?.({ width: size.width, height: size.height });\n }, [size.width, size.height]);\n\n const frame = useMemo(() => resolveFrame(worldSpec, size), [worldSpec, size]);\n\n const onTap = (e: PointerEvent) => {\n const el = containerRef.current;\n if (!onHitTest || !el) return;\n const rect = el.getBoundingClientRect();\n onHitTest(screenToWorld({ x: e.clientX - rect.left, y: e.clientY - rect.top }, view, frame));\n };\n\n const handlers = usePanZoom({ view, onViewChange, minZoom, maxZoom, frame, onTap });\n useLayerScheduler({ layers, view, frame, canvasRefs: canvasMap, size, host: containerRef });\n\n const ctxValue = useMemo(\n () => ({\n view,\n frame,\n surface: { element: containerRef, size, canvases: canvasMap, layers },\n }),\n [view, frame, size, layers],\n );\n\n const containerStyle: CSSProperties = { width, height };\n const canvasPx = {\n width: Math.max(0, Math.round(size.width * size.dpr)),\n height: Math.max(0, Math.round(size.height * size.dpr)),\n };\n const canvasCss: CSSProperties = {\n width: size.width,\n height: size.height,\n };\n\n return (\n <CanvasStackContext.Provider value={ctxValue}>\n <div\n ref={containerRef}\n className={className ? `lk-canvas-stack ${className}` : 'lk-canvas-stack'}\n style={containerStyle}\n onWheel={handlers.onWheel}\n onPointerDown={handlers.onPointerDown}\n >\n {layers.map((layer) => (\n <canvas\n key={layer.id}\n ref={(el) => setCanvasRef(layer.id, el)}\n className=\"lk-canvas-stack__canvas\"\n width={canvasPx.width}\n height={canvasPx.height}\n style={{ ...canvasCss, display: layer.visible ? 'block' : 'none' }}\n />\n ))}\n <div className=\"lk-canvas-stack__overlay\">{children}</div>\n </div>\n </CanvasStackContext.Provider>\n );\n}\n","import { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport {\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useRef,\n type WheelEvent,\n} from 'react';\n\n/** A point in the space the instrument works in. */\nexport interface Vec3 {\n x: number;\n y: number;\n z: number;\n}\n\n/** An orbit camera as a trial view: where it looks from, and what it looks at.\n * labkit does not turn this into a matrix — the host's renderer does. */\nexport interface OrbitView {\n yaw: number;\n pitch: number;\n distance: number;\n target: Vec3;\n}\n\n/** Just short of the pole. At exactly ±PI/2 the azimuth is undefined and the\n * camera rolls, which reads as the model jumping rather than as a limit. */\nexport const PITCH_LIMIT = Math.PI / 2 - 0.01;\n\nconst YAW_PER_PX = 0.008;\nconst PITCH_PER_PX = 0.008;\nconst DISTANCE_PER_NOTCH = 0.0015;\nconst DRAG_THRESHOLD = 3;\n\nexport function clampPitch(pitch: number): number {\n return Math.min(PITCH_LIMIT, Math.max(-PITCH_LIMIT, pitch));\n}\n\nexport function wrapYaw(yaw: number): number {\n const wrapped = ((((yaw + Math.PI) % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI)) - Math.PI;\n return wrapped === -Math.PI ? Math.PI : wrapped;\n}\n\n/** The view a drag of (dx, dy) from `start` produces. Absolute against the drag\n * start, so it can be re-applied any number of times without compounding. */\nexport function orbitAfterDrag(start: OrbitView, dx: number, dy: number): OrbitView {\n return {\n ...start,\n yaw: wrapYaw(start.yaw + dx * YAW_PER_PX),\n pitch: clampPitch(start.pitch + dy * PITCH_PER_PX),\n };\n}\n\n/** Multiplicative, so one notch covers the same proportion of the distance\n * whether the camera is near or far. */\nexport function orbitAfterWheel(\n view: OrbitView,\n deltaY: number,\n minDistance: number,\n maxDistance: number,\n): OrbitView {\n const factor = Math.exp(deltaY * DISTANCE_PER_NOTCH);\n return {\n ...view,\n distance: Math.min(maxDistance, Math.max(minDistance, view.distance * factor)),\n };\n}\n\nexport interface UseOrbitOptions {\n view: OrbitView;\n onViewChange: (v: OrbitView) => void;\n /** Restored on double-click. Defaults to the view the hook first saw. */\n home?: OrbitView;\n minDistance?: number;\n maxDistance?: number;\n}\n\nexport interface OrbitHandlers {\n onWheel: (e: WheelEvent<HTMLElement>) => void;\n onPointerDown: (e: ReactPointerEvent<HTMLElement>) => void;\n onDoubleClick: () => void;\n isDragging: () => boolean;\n}\n\ninterface DragState {\n startX: number;\n startY: number;\n startView: OrbitView;\n moved: boolean;\n}\n\n/** Pointer gestures over an orbit view: drag to turn, wheel or pinch to dolly,\n * double-click to go home. The 3D peer of `usePanZoom`. */\nexport function useOrbit({\n view,\n onViewChange,\n home,\n minDistance = 0.1,\n maxDistance = 1000,\n}: UseOrbitOptions): OrbitHandlers {\n const dragRef = useRef<DragState | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n const viewRef = useRef(view);\n viewRef.current = view;\n const onViewChangeRef = useRef(onViewChange);\n onViewChangeRef.current = onViewChange;\n const homeRef = useRef(home ?? view);\n if (home) homeRef.current = home;\n\n const onWheel = useCallback(\n (e: WheelEvent<HTMLElement>) => {\n e.preventDefault();\n onViewChange(orbitAfterWheel(viewRef.current, e.deltaY, minDistance, maxDistance));\n },\n [onViewChange, minDistance, maxDistance],\n );\n\n const clearDrag = useCallback(() => {\n dragRef.current = null;\n sessionRef.current = null;\n }, []);\n\n const onPointerDown = useCallback(\n (e: ReactPointerEvent<HTMLElement>) => {\n if (e.button !== 0) return;\n const drag: DragState = {\n startX: e.clientX,\n startY: e.clientY,\n startView: viewRef.current,\n moved: false,\n };\n dragRef.current = drag;\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => {\n const dx = ev.clientX - drag.startX;\n const dy = ev.clientY - drag.startY;\n if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;\n drag.moved = true;\n onViewChangeRef.current(orbitAfterDrag(drag.startView, dx, dy));\n },\n onEnd: clearDrag,\n // A cancelled turn keeps where it got to: every move was committed as\n // it arrived, so there is nothing left to undo.\n onCancel: clearDrag,\n });\n },\n [clearDrag],\n );\n\n useEffect(() => () => sessionRef.current?.cancel(), []);\n\n const onDoubleClick = useCallback(() => onViewChange(homeRef.current), [onViewChange]);\n const isDragging = useCallback(() => dragRef.current?.moved === true, []);\n\n return { onWheel, onPointerDown, onDoubleClick, isDragging };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/canvas/useLayerScheduler.ts","../src/canvas/usePanZoom.ts","../src/canvas/CanvasStack.tsx","../src/canvas/useOrbit.ts"],"names":["useRef","useEffect","DRAG_THRESHOLD","useCallback"],"mappings":"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","file":"chunk-FJG4PHTL.js","sourcesContent":["import { useVisibleRaf } from '@weasel-js/core';\nimport { type RefObject, useEffect, useRef } from 'react';\nimport type { ViewTransform } from '../instrument/types';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\n/** One layer of a canvas stack: its id, whether it is currently shown, and how\n * it paints itself. */\nexport interface CanvasLayerDescriptor {\n id: string;\n visible: boolean;\n render: (ctx: CanvasRenderingContext2D, view: ViewTransform, frame: WorldFrame) => void;\n}\n\ninterface SchedulerOptions {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n /** The instrument's coordinate system, resolved against `size`. */\n frame?: WorldFrame;\n canvasRefs: RefObject<Map<string, HTMLCanvasElement>>;\n size: { width: number; height: number; dpr: number };\n /** The element the stack occupies. Given one, the scheduler also stops while\n * the stack sits outside the viewport. */\n host?: RefObject<Element | null>;\n}\n\nexport function useLayerScheduler({\n layers,\n view,\n frame = DEFAULT_FRAME,\n canvasRefs,\n size,\n host,\n}: SchedulerOptions): void {\n const dirty = useRef<Set<string>>(new Set());\n const lastRenderRef = useRef<Map<string, CanvasLayerDescriptor['render']>>(new Map());\n\n useEffect(() => {\n for (const layer of layers) {\n const prev = lastRenderRef.current.get(layer.id);\n if (prev !== layer.render) dirty.current.add(layer.id);\n lastRenderRef.current.set(layer.id, layer.render);\n }\n for (const id of [...lastRenderRef.current.keys()]) {\n if (!layers.find((l) => l.id === id)) lastRenderRef.current.delete(id);\n }\n }, [layers]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-mark dirty when view or size changes\n useEffect(() => {\n for (const layer of layers) dirty.current.add(layer.id);\n }, [view, frame, size, layers]);\n\n // Painting only what is dirty is not the same as doing nothing: a hidden tab\n // still commits React updates, and the effect above marks every layer dirty\n // on any view or size change. The gate is what actually stops the work.\n const frameLoop = useVisibleRaf(\n () => {\n if (dirty.current.size === 0) return;\n const map = canvasRefs.current;\n if (!map) {\n dirty.current.clear();\n return;\n }\n for (const layer of layers) {\n if (!dirty.current.has(layer.id)) continue;\n if (!layer.visible) continue;\n const canvas = map.get(layer.id);\n if (!canvas) continue;\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n ctx.save();\n ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);\n ctx.clearRect(0, 0, size.width, size.height);\n layer.render(ctx, view, frame);\n ctx.restore();\n }\n dirty.current.clear();\n },\n { target: host },\n );\n\n useEffect(() => {\n if (dirty.current.size > 0) frameLoop.request();\n });\n}\n","import { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport {\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useRef,\n type WheelEvent,\n} from 'react';\nimport type { ViewTransform } from '../instrument/types';\nimport { zoomAt } from './camera';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\nexport interface UsePanZoomOptions {\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n /** The opening `view.zoom` always stays reachable, widening these past\n * whatever is passed here if it would otherwise exclude it. */\n minZoom?: number;\n maxZoom?: number;\n /** The instrument's coordinate system, resolved against the viewport. The\n * wheel anchors in this frame; omitting it anchors at the element's\n * top-left, which is what labkit did before a frame could be declared. */\n frame?: WorldFrame;\n /** A primary-button press released without ever crossing the drag\n * threshold. The pointer session owns the release, so the gesture's end is\n * reported here rather than left for a consumer's own `onPointerUp`. */\n onTap?: (e: PointerEvent) => void;\n}\n\nexport interface PanZoomHandlers {\n onWheel: (e: WheelEvent<HTMLElement>) => void;\n onPointerDown: (e: ReactPointerEvent<HTMLElement>) => void;\n isDragging: () => boolean;\n}\n\ninterface DragState {\n startScreenX: number;\n startScreenY: number;\n startPan: { x: number; y: number };\n moved: boolean;\n}\n\nconst DRAG_THRESHOLD = 3;\n\nfunction isPositiveFinite(n: number): boolean {\n return Number.isFinite(n) && n > 0;\n}\n\nexport function usePanZoom({\n view,\n onViewChange,\n minZoom = 0.1,\n maxZoom = 32,\n frame = DEFAULT_FRAME,\n onTap,\n}: UsePanZoomOptions): PanZoomHandlers {\n const dragRef = useRef<DragState | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n const viewRef = useRef(view);\n viewRef.current = view;\n const onViewChangeRef = useRef(onViewChange);\n onViewChangeRef.current = onViewChange;\n const onTapRef = useRef(onTap);\n onTapRef.current = onTap;\n // Captured once, from the view the canvas opened at — not the current view,\n // which would let a prior zoom-out shrink the range and trap the opening\n // view unreachable behind it.\n const initialZoomRef = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);\n\n const onWheel = useCallback(\n (e: WheelEvent<HTMLElement>) => {\n e.preventDefault();\n const rect = e.currentTarget.getBoundingClientRect();\n const initialZoom = initialZoomRef.current;\n onViewChange(\n zoomAt(\n viewRef.current,\n Math.exp(-e.deltaY * 0.001),\n { x: e.clientX - rect.left, y: e.clientY - rect.top },\n {\n frame,\n min: initialZoom == null ? minZoom : Math.min(minZoom, initialZoom),\n max: initialZoom == null ? maxZoom : Math.max(maxZoom, initialZoom),\n },\n ),\n );\n },\n [onViewChange, minZoom, maxZoom, frame],\n );\n\n const clearDrag = useCallback(() => {\n dragRef.current = null;\n sessionRef.current = null;\n }, []);\n\n const onPointerDown = useCallback(\n (e: ReactPointerEvent<HTMLElement>) => {\n if (e.button !== 0) return;\n const drag: DragState = {\n startScreenX: e.clientX,\n startScreenY: e.clientY,\n startPan: { ...viewRef.current.pan },\n moved: false,\n };\n dragRef.current = drag;\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => {\n const dx = ev.clientX - drag.startScreenX;\n const dy = ev.clientY - drag.startScreenY;\n if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;\n drag.moved = true;\n onViewChangeRef.current({\n zoom: viewRef.current.zoom,\n pan: { x: drag.startPan.x + dx, y: drag.startPan.y + dy },\n });\n },\n onEnd: (ev) => {\n const tapped = !drag.moved;\n clearDrag();\n if (tapped) onTapRef.current?.(ev);\n },\n // A cancelled pan keeps whatever it had already applied: every move\n // was committed as it arrived, so there is nothing left to undo.\n onCancel: clearDrag,\n });\n },\n [clearDrag],\n );\n\n useEffect(() => () => sessionRef.current?.cancel(), []);\n\n const isDragging = useCallback(() => dragRef.current?.moved === true, []);\n\n return { onWheel, onPointerDown, isDragging };\n}\n","import { type CSSProperties, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { CanvasStackContext } from './CanvasStackContext';\nimport { screenToWorld } from './canvasCoords';\nimport { type CanvasLayerDescriptor, useLayerScheduler } from './useLayerScheduler';\nimport { usePanZoom } from './usePanZoom';\nimport { resolveFrame, type ViewportSize, type WorldSpec } from './worldSpec';\n\n/** Props for `<CanvasStack>`. */\nexport interface CanvasStackProps {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n /** The instrument's coordinate system. Omitted, world (0,0) sits at the\n * element's top-left with y running down. */\n worldSpec?: WorldSpec;\n minZoom?: number;\n maxZoom?: number;\n width?: number | string;\n height?: number | string;\n className?: string;\n /** Fired whenever the stack is measured, so a consumer can place a view that\n * only makes sense in terms of the viewport. */\n onResize?: (size: ViewportSize) => void;\n onHitTest?: (worldPos: Point) => void;\n children?: ReactNode;\n}\n\n/** Stacks one `<canvas>` per layer and drives them from a shared view, so a\n * layer that changes rarely is not redrawn with one that changes every frame.\n * Handles sizing, device pixel ratio, and pan/zoom. */\nexport function CanvasStack({\n layers,\n view,\n onViewChange,\n worldSpec,\n minZoom,\n maxZoom,\n width = '100%',\n height = '100%',\n className,\n onResize,\n onHitTest,\n children,\n}: CanvasStackProps) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasMap = useRef<Map<string, HTMLCanvasElement>>(new Map());\n const [size, setSize] = useState({ width: 0, height: 0, dpr: 1 });\n\n const setCanvasRef = (id: string, el: HTMLCanvasElement | null): void => {\n if (el) canvasMap.current.set(id, el);\n else canvasMap.current.delete(id);\n };\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const update = () => {\n const rect = el.getBoundingClientRect();\n const dpr = window.devicePixelRatio ?? 1;\n setSize((prev) => {\n if (prev.width === rect.width && prev.height === rect.height && prev.dpr === dpr) {\n return prev;\n }\n return { width: rect.width, height: rect.height, dpr };\n });\n };\n update();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n useEffect(() => {\n if (size.width === 0 && size.height === 0) return;\n onResizeRef.current?.({ width: size.width, height: size.height });\n }, [size.width, size.height]);\n\n const frame = useMemo(() => resolveFrame(worldSpec, size), [worldSpec, size]);\n\n const onTap = (e: PointerEvent) => {\n const el = containerRef.current;\n if (!onHitTest || !el) return;\n const rect = el.getBoundingClientRect();\n onHitTest(screenToWorld({ x: e.clientX - rect.left, y: e.clientY - rect.top }, view, frame));\n };\n\n const handlers = usePanZoom({ view, onViewChange, minZoom, maxZoom, frame, onTap });\n useLayerScheduler({ layers, view, frame, canvasRefs: canvasMap, size, host: containerRef });\n\n const ctxValue = useMemo(\n () => ({\n view,\n frame,\n surface: { element: containerRef, size, canvases: canvasMap, layers },\n }),\n [view, frame, size, layers],\n );\n\n const containerStyle: CSSProperties = { width, height };\n const canvasPx = {\n width: Math.max(0, Math.round(size.width * size.dpr)),\n height: Math.max(0, Math.round(size.height * size.dpr)),\n };\n const canvasCss: CSSProperties = {\n width: size.width,\n height: size.height,\n };\n\n return (\n <CanvasStackContext.Provider value={ctxValue}>\n <div\n ref={containerRef}\n className={className ? `lk-canvas-stack ${className}` : 'lk-canvas-stack'}\n style={containerStyle}\n onWheel={handlers.onWheel}\n onPointerDown={handlers.onPointerDown}\n >\n {layers.map((layer) => (\n <canvas\n key={layer.id}\n ref={(el) => setCanvasRef(layer.id, el)}\n className=\"lk-canvas-stack__canvas\"\n width={canvasPx.width}\n height={canvasPx.height}\n style={{ ...canvasCss, display: layer.visible ? 'block' : 'none' }}\n />\n ))}\n <div className=\"lk-canvas-stack__overlay\">{children}</div>\n </div>\n </CanvasStackContext.Provider>\n );\n}\n","import { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport {\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useRef,\n type WheelEvent,\n} from 'react';\n\n/** A point in the space the instrument works in. */\nexport interface Vec3 {\n x: number;\n y: number;\n z: number;\n}\n\n/** An orbit camera as a trial view: where it looks from, and what it looks at.\n * labkit does not turn this into a matrix — the host's renderer does. */\nexport interface OrbitView {\n yaw: number;\n pitch: number;\n distance: number;\n target: Vec3;\n}\n\n/** Just short of the pole. At exactly ±PI/2 the azimuth is undefined and the\n * camera rolls, which reads as the model jumping rather than as a limit. */\nexport const PITCH_LIMIT = Math.PI / 2 - 0.01;\n\nconst YAW_PER_PX = 0.008;\nconst PITCH_PER_PX = 0.008;\nconst DISTANCE_PER_NOTCH = 0.0015;\nconst DRAG_THRESHOLD = 3;\n\nexport function clampPitch(pitch: number): number {\n return Math.min(PITCH_LIMIT, Math.max(-PITCH_LIMIT, pitch));\n}\n\nexport function wrapYaw(yaw: number): number {\n const wrapped = ((((yaw + Math.PI) % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI)) - Math.PI;\n return wrapped === -Math.PI ? Math.PI : wrapped;\n}\n\n/** The view a drag of (dx, dy) from `start` produces. Absolute against the drag\n * start, so it can be re-applied any number of times without compounding. */\nexport function orbitAfterDrag(start: OrbitView, dx: number, dy: number): OrbitView {\n return {\n ...start,\n yaw: wrapYaw(start.yaw + dx * YAW_PER_PX),\n pitch: clampPitch(start.pitch + dy * PITCH_PER_PX),\n };\n}\n\n/** Multiplicative, so one notch covers the same proportion of the distance\n * whether the camera is near or far. */\nexport function orbitAfterWheel(\n view: OrbitView,\n deltaY: number,\n minDistance: number,\n maxDistance: number,\n): OrbitView {\n const factor = Math.exp(deltaY * DISTANCE_PER_NOTCH);\n return {\n ...view,\n distance: Math.min(maxDistance, Math.max(minDistance, view.distance * factor)),\n };\n}\n\nexport interface UseOrbitOptions {\n view: OrbitView;\n onViewChange: (v: OrbitView) => void;\n /** Restored on double-click. Defaults to the view the hook first saw. */\n home?: OrbitView;\n minDistance?: number;\n maxDistance?: number;\n}\n\nexport interface OrbitHandlers {\n onWheel: (e: WheelEvent<HTMLElement>) => void;\n onPointerDown: (e: ReactPointerEvent<HTMLElement>) => void;\n onDoubleClick: () => void;\n isDragging: () => boolean;\n}\n\ninterface DragState {\n startX: number;\n startY: number;\n startView: OrbitView;\n moved: boolean;\n}\n\n/** Pointer gestures over an orbit view: drag to turn, wheel or pinch to dolly,\n * double-click to go home. The 3D peer of `usePanZoom`. */\nexport function useOrbit({\n view,\n onViewChange,\n home,\n minDistance = 0.1,\n maxDistance = 1000,\n}: UseOrbitOptions): OrbitHandlers {\n const dragRef = useRef<DragState | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n const viewRef = useRef(view);\n viewRef.current = view;\n const onViewChangeRef = useRef(onViewChange);\n onViewChangeRef.current = onViewChange;\n const homeRef = useRef(home ?? view);\n if (home) homeRef.current = home;\n\n const onWheel = useCallback(\n (e: WheelEvent<HTMLElement>) => {\n e.preventDefault();\n onViewChange(orbitAfterWheel(viewRef.current, e.deltaY, minDistance, maxDistance));\n },\n [onViewChange, minDistance, maxDistance],\n );\n\n const clearDrag = useCallback(() => {\n dragRef.current = null;\n sessionRef.current = null;\n }, []);\n\n const onPointerDown = useCallback(\n (e: ReactPointerEvent<HTMLElement>) => {\n if (e.button !== 0) return;\n const drag: DragState = {\n startX: e.clientX,\n startY: e.clientY,\n startView: viewRef.current,\n moved: false,\n };\n dragRef.current = drag;\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => {\n const dx = ev.clientX - drag.startX;\n const dy = ev.clientY - drag.startY;\n if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;\n drag.moved = true;\n onViewChangeRef.current(orbitAfterDrag(drag.startView, dx, dy));\n },\n onEnd: clearDrag,\n // A cancelled turn keeps where it got to: every move was committed as\n // it arrived, so there is nothing left to undo.\n onCancel: clearDrag,\n });\n },\n [clearDrag],\n );\n\n useEffect(() => () => sessionRef.current?.cancel(), []);\n\n const onDoubleClick = useCallback(() => onViewChange(homeRef.current), [onViewChange]);\n const isDragging = useCallback(() => dragRef.current?.moved === true, []);\n\n return { onWheel, onPointerDown, onDoubleClick, isDragging };\n}\n"]}
|