@weasel-js/cursor 1.7.0 → 1.7.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/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@ interface CursorGlyph {
17
17
  readonly radius?: number;
18
18
  readonly paths: readonly CursorPath[];
19
19
  }
20
+ /** One path of a {@link CursorGlyph}, tagged with the role that decides its paint. */
20
21
  type CursorPath =
21
22
  /** A filled part of the silhouette. */
22
23
  {
@@ -47,7 +48,9 @@ type CursorPath =
47
48
  * artwork alike precisely because it does not track the theme.
48
49
  */
49
50
  declare const CURSOR_INK = "#141418";
51
+ /** The outline stroked around every silhouette member. */
50
52
  declare const CURSOR_HALO = "#ffffff";
53
+ /** Halo stroke width in glyph units; a stroke path's halo adds it to the path's own width. */
51
54
  declare const CURSOR_HALO_WIDTH = 2.6;
52
55
  /**
53
56
  * Rotation steps a baked angle is quantized to. Below 16 the cursor visibly
@@ -80,6 +83,7 @@ declare function haloFitsInBox(glyph: CursorGlyph): boolean;
80
83
  */
81
84
  declare function rotationFitsInBox(glyph: CursorGlyph): boolean;
82
85
 
86
+ /** How {@link bakeCursor} renders a glyph. */
83
87
  interface BakeOptions {
84
88
  /** Rendered size in CSS px. Default 24. */
85
89
  readonly size?: number;
@@ -106,6 +110,7 @@ declare function quantizeCursorAngle(angle: number): number;
106
110
  */
107
111
  declare function bakeCursor(glyph: CursorGlyph, opts?: BakeOptions): string;
108
112
 
113
+ /** Every built-in cursor glyph, by name. */
109
114
  declare const GLYPHS: {
110
115
  readonly pencil: {
111
116
  readonly box: 24;
@@ -404,6 +409,7 @@ interface CursorPaintOp {
404
409
  readonly width: number;
405
410
  };
406
411
  }
412
+ /** Options for {@link cursorPaintOps}. */
407
413
  interface PaintOptions {
408
414
  /**
409
415
  * Multiplies every stroke width, leaving geometry alone. Default 1.
@@ -418,6 +424,7 @@ interface PaintOptions {
418
424
  declare function cursorPaintOps(glyph: CursorGlyph, opts?: PaintOptions): CursorPaintOp[];
419
425
  /** An affine `[a, b, c, d, e, f]`: `x' = ax + cy + e`, `y' = bx + dy + f`. */
420
426
  type CursorMatrix = readonly [number, number, number, number, number, number];
427
+ /** Where and how large {@link cursorPaintMatrix} places a glyph. */
421
428
  interface PaintPlacement {
422
429
  /** Rendered size in CSS px, as `bakeCursor` means it. */
423
430
  readonly size: number;
@@ -476,6 +483,7 @@ declare function chromeLineWidthScale(glyph: CursorGlyph, size: number): number;
476
483
  type PaintedCursor = Extract<ResolvedCursor, {
477
484
  kind: 'painted';
478
485
  }>;
486
+ /** One frame's worth of painted cursor: which glyph, and where. */
479
487
  interface PaintedCursorFrame {
480
488
  readonly cursor: PaintedCursor;
481
489
  /** Pointer position in the layer's own space: canvas-local CSS px. */
@@ -484,8 +492,11 @@ interface PaintedCursorFrame {
484
492
  readonly y: number;
485
493
  };
486
494
  }
495
+ /** A surface's painted cursor, in two slots: a base and an override that wins while set. */
487
496
  interface PaintedCursorState {
497
+ /** The active tool's cursor. */
488
498
  setBase(cursor: PaintedCursor | null): void;
499
+ /** An affordance's or action's cursor; wins over the base while set. */
489
500
  setOverride(cursor: PaintedCursor | null): void;
490
501
  /** Canvas-local CSS px. */
491
502
  setPointer(x: number, y: number): void;
@@ -504,6 +515,7 @@ interface PaintedCursorState {
504
515
  /** Run `fn` whenever the answer to `current()` may have changed. */
505
516
  subscribe(fn: () => void): () => void;
506
517
  }
518
+ /** An empty store: no cursor set, pointer outside the surface. */
507
519
  declare function createPaintedCursorState(): PaintedCursorState;
508
520
 
509
521
  export { type BakeOptions, CURSOR_ANGLE_STEPS, CURSOR_HALO, CURSOR_HALO_WIDTH, CURSOR_INK, CURSOR_MAX_CSS_PX, type CursorGlyph, type CursorGlyphName, type CursorGlyphSpec, type CursorMatrix, type CursorPaintOp, type CursorPath, type CursorSpec, GLYPHS, type PaintOptions, type PaintPlacement, type PaintedCursor, type PaintedCursorFrame, type PaintedCursorState, type ResolvedCursor, bakeCursor, chromeLineWidthScale, createPaintedCursorState, cursorFor, cursorPaintMatrix, cursorPaintOps, cursorWorldSize, haloFitsInBox, quantizeCursorAngle, resolveCursor, resolveCursorTier, rotationFitsInBox };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/paint.ts","../src/bake.ts","../src/glyphs.ts","../src/registry.ts","../src/resolve.ts","../src/paintedCursorState.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * A cursor glyph: SVG path `d` strings tagged with a paint role, plus the\n * hotspot in glyph units. The same record feeds both the data-URI baker and\n * the Path2D painter, so `d` is the one geometry form neither has to translate.\n */\nexport interface CursorGlyph {\n /** Side of the square viewBox the paths are authored in. */\n readonly box: number;\n /** Hotspot in glyph units, scaled to integer CSS px at bake time. */\n readonly hotspot: readonly [number, number];\n /**\n * The glyph-unit radius a `worldRadius` spec means, for glyphs that measure\n * something — a brush ring reads as a lie about the brush size unless the\n * painter knows which circle in the drawing is the one being sized. Defaults\n * to the box's inscribed radius.\n */\n readonly radius?: number;\n readonly paths: readonly CursorPath[];\n}\n\nexport type CursorPath =\n /** A filled part of the silhouette. */\n | { readonly role: 'ink'; readonly d: string }\n /** An unfilled part of the silhouette — a handle, a wire, an arc. */\n | { readonly role: 'stroke'; readonly d: string; readonly width: number }\n /** A division inside the silhouette, drawn in the halo color. */\n | { readonly role: 'detail'; readonly d: string; readonly width: number }\n /** A literal color, for glyphs that carry a swatch. */\n | { readonly role: 'accent'; readonly d: string; readonly fill: string };\n\n/**\n * Ink and halo are constants of the register rather than parameters: a\n * self-contrasting glyph reads on white paper, dark chrome and mid-tone\n * artwork alike precisely because it does not track the theme.\n */\nexport const CURSOR_INK = '#141418';\nexport const CURSOR_HALO = '#ffffff';\nexport const CURSOR_HALO_WIDTH = 2.6;\n\n/**\n * Rotation steps a baked angle is quantized to. Below 16 the cursor visibly\n * snaps against a smoothly rotating selection; above it the cache grows for no\n * perceptible gain.\n */\nexport const CURSOR_ANGLE_STEPS = 16;\n\n/**\n * Chrome silently drops a cursor image above this size and falls back to the\n * keyword after the comma, with no error anywhere. Measured on Chrome 152 /\n * macOS 26.5; see the spec's \"Measured browser behavior\".\n */\nexport const CURSOR_MAX_CSS_PX = 128;\n\n/**\n * Visits the points that bound an authored `d` string, for the fit guards below.\n *\n * Command-aware on purpose. Scraping every number out of the string instead\n * reads an arc's radii and its three flags as coordinates, which reports a\n * bogus `0` for every `A` ever written.\n *\n * An arc is bounded by the circle it lies on: its centre is recovered from the\n * endpoints and radius, then the whole circle is admitted. Expanding the arc's\n * *endpoint* by the radii instead over-reports whenever an endpoint sits at an\n * extreme of the circle, which is exactly where the half-circle idiom these\n * glyphs use puts it.\n *\n * The authored dialect is absolute `M`/`L`/`A`/`Z` with circular arcs. A\n * relative command would be measured as if absolute and silently under-report,\n * so it throws.\n */\nfunction walk(d: string, see: (x: number, y: number) => void): void {\n let cx = 0;\n let cy = 0;\n for (const [, cmd, args] of d.matchAll(/([A-Za-z])([^A-Za-z]*)/g)) {\n if (cmd !== cmd.toUpperCase()) {\n throw new Error(`cursor glyph path uses a relative command '${cmd}': ${d}`);\n }\n const nums = (args.match(/-?\\d+(?:\\.\\d+)?/g) ?? []).map(Number);\n if (cmd === 'Z') continue;\n if (cmd === 'A') {\n for (let i = 0; i + 7 <= nums.length; i += 7) {\n const [rx, ry, , largeArc, sweep, x, y] = nums.slice(i, i + 7);\n const r = Math.max(rx, ry);\n const dx = x - cx;\n const dy = y - cy;\n const half = Math.hypot(dx, dy) / 2;\n // Centre sits off the chord midpoint by this much, perpendicular to it.\n const off = Math.sqrt(Math.max(0, r * r - half * half));\n const sign = largeArc === sweep ? -1 : 1;\n const ux = half > 0 ? -dy / (half * 2) : 0;\n const uy = half > 0 ? dx / (half * 2) : 0;\n const ox = (cx + x) / 2 + sign * off * ux;\n const oy = (cy + y) / 2 + sign * off * uy;\n see(ox - r, oy - r);\n see(ox + r, oy - r);\n see(ox - r, oy + r);\n see(ox + r, oy + r);\n cx = x;\n cy = y;\n }\n continue;\n }\n // M and L: plain coordinate pairs.\n for (let i = 0; i + 2 <= nums.length; i += 2) {\n cx = nums[i];\n cy = nums[i + 1];\n see(cx, cy);\n }\n }\n}\n\n/**\n * True when every authored path sits at least half a halo stroke inside the\n * viewBox. A clipped halo is invisible at proof size and flattens the glyph's\n * outline at cursor size, so it is worth failing loudly at authoring time\n * rather than discovering it on a dark background.\n */\nexport function haloFitsInBox(glyph: CursorGlyph): boolean {\n const margin = CURSOR_HALO_WIDTH / 2;\n let ok = true;\n for (const p of glyph.paths) {\n walk(p.d, (x, y) => {\n for (const v of [x, y]) {\n if (v < margin || v > glyph.box - margin) ok = false;\n }\n });\n }\n return ok;\n}\n\n/**\n * True when the glyph still fits its viewBox at every rotation — i.e. every\n * authored point is inside the box's inscribed circle, halo included.\n *\n * Bake rotates about the box centre in a viewBox that does not grow, so a\n * glyph that merely satisfies {@link haloFitsInBox} can have its corners\n * sheared off partway round. The loss is a few pixels at cursor size and\n * invisible until someone looks at exactly the wrong angle, which is why it\n * is worth failing at authoring time.\n */\nexport function rotationFitsInBox(glyph: CursorGlyph): boolean {\n const c = glyph.box / 2;\n const limit = c - CURSOR_HALO_WIDTH / 2;\n let ok = true;\n for (const p of glyph.paths) {\n walk(p.d, (x, y) => {\n if (Math.hypot(x - c, y - c) > limit) ok = false;\n });\n }\n return ok;\n}\n","import { CURSOR_HALO, CURSOR_HALO_WIDTH, CURSOR_INK } from './types';\nimport type { CursorGlyph, CursorPath } from './types';\n\n/**\n * One path of a glyph, with the paint the register gives it.\n *\n * Deliberately not a renderer's own type: `@weasel-js/cursor` decides *what*\n * to paint and in what order, and leaves *how* to whoever is drawing — the\n * baker turns each op into an SVG element, the painted-tier layer turns it\n * into a draw command. Both get the same list, which is what makes the two\n * tiers show the same glyph rather than two drawings that resemble each other.\n *\n * Stroked ops are round-capped and round-joined; nothing in the register\n * wants a mitre.\n */\nexport interface CursorPaintOp {\n readonly d: string;\n readonly fill?: string;\n readonly stroke?: { readonly color: string; readonly width: number };\n}\n\nexport interface PaintOptions {\n /**\n * Multiplies every stroke width, leaving geometry alone. Default 1.\n *\n * A world-sized glyph passes the reciprocal of its scale here so line weight\n * stays chrome-weight in CSS px: a brush ring at a 400px radius otherwise\n * carries a 40px-thick stroke and a halo to match.\n */\n readonly lineWidthScale?: number;\n}\n\n/**\n * The halo pass — every silhouette member drawn once, wide, in halo colour\n * before any ink lands.\n *\n * One pass under everything rather than a per-path `paint-order`: with each\n * path haloing itself, a later path's halo cuts a white trench through an\n * earlier path's fill wherever the two overlap.\n */\nfunction halo(p: CursorPath, k: number): CursorPaintOp | null {\n switch (p.role) {\n case 'ink':\n case 'accent':\n return { d: p.d, fill: CURSOR_HALO, stroke: { color: CURSOR_HALO, width: CURSOR_HALO_WIDTH * k } };\n case 'stroke':\n return { d: p.d, stroke: { color: CURSOR_HALO, width: (p.width + CURSOR_HALO_WIDTH) * k } };\n // A detail IS halo-coloured and sits on top of the ink; it has no halo.\n case 'detail':\n return null;\n }\n}\n\nfunction ink(p: CursorPath, k: number): CursorPaintOp {\n switch (p.role) {\n case 'ink':\n return { d: p.d, fill: CURSOR_INK };\n case 'stroke':\n return { d: p.d, stroke: { color: CURSOR_INK, width: p.width * k } };\n case 'detail':\n return { d: p.d, stroke: { color: CURSOR_HALO, width: p.width * k } };\n case 'accent':\n return { d: p.d, fill: p.fill };\n }\n}\n\n/** Every paint a glyph needs, in draw order: all halos, then all ink. */\nexport function cursorPaintOps(glyph: CursorGlyph, opts: PaintOptions = {}): CursorPaintOp[] {\n const k = opts.lineWidthScale ?? 1;\n const out: CursorPaintOp[] = [];\n for (const p of glyph.paths) {\n const h = halo(p, k);\n if (h) out.push(h);\n }\n for (const p of glyph.paths) out.push(ink(p, k));\n return out;\n}\n\n/** An affine `[a, b, c, d, e, f]`: `x' = ax + cy + e`, `y' = bx + dy + f`. */\nexport type CursorMatrix = readonly [number, number, number, number, number, number];\n\nexport interface PaintPlacement {\n /** Rendered size in CSS px, as `bakeCursor` means it. */\n readonly size: number;\n /** Clockwise rotation in radians. Default 0. */\n readonly angle?: number;\n /** Where the hotspot goes — the pointer, in the layer's own coords. */\n readonly at: { readonly x: number; readonly y: number };\n}\n\n/**\n * Where a glyph's own units land, to place it under the pointer at a size and\n * an angle.\n *\n * Rotation is about the hotspot, not the box centre. That is the same\n * placement the baker produces — it turns the glyph about the centre and then\n * carries the hotspot around with it, and the centre cancels — but it is worth\n * stating, because a painter that rotates about the centre and forgets to move\n * the hotspot swings the glyph around a point the user never sees.\n *\n * Unquantized on purpose: the 22.5° step exists to bound the bake cache, and\n * the painter has no cache to bound. A painted cursor turns smoothly.\n */\nexport function cursorPaintMatrix(glyph: CursorGlyph, placement: PaintPlacement): CursorMatrix {\n const s = placement.size / glyph.box;\n const theta = placement.angle ?? 0;\n // y is down, so a positive angle carries +x toward +y — clockwise on screen.\n const a = s * Math.cos(theta);\n const b = s * Math.sin(theta);\n const [hx, hy] = glyph.hotspot;\n return [a, b, -b, a, placement.at.x - (a * hx - b * hy), placement.at.y - (b * hx + a * hy)];\n}\n\n/**\n * The CSS-px size a world-sized glyph is drawn at, so the circle its `radius`\n * names measures `worldRadius` world units on screen.\n *\n * A glyph with no `radius` is measured by its inscribed circle. Getting this\n * wrong scales every brush cursor by a constant factor, which reads as a\n * slightly-off cursor rather than as a bug.\n */\nexport function cursorWorldSize(\n glyph: CursorGlyph,\n worldRadius: number,\n scale: number,\n): number {\n const r = glyph.radius ?? glyph.box / 2;\n return (worldRadius * scale * glyph.box) / r;\n}\n\n/**\n * The `lineWidthScale` that keeps a glyph's line weight at chrome weight while\n * its geometry scales — what a world-sized glyph wants, and what a fixed-size\n * one must not have (there, weight scaling is what makes the painted tier\n * match the baked one).\n */\nexport function chromeLineWidthScale(glyph: CursorGlyph, size: number): number {\n return glyph.box / size;\n}\n","import { cursorPaintOps } from './paint';\nimport type { CursorPaintOp } from './paint';\nimport { CURSOR_ANGLE_STEPS, CURSOR_MAX_CSS_PX } from './types';\nimport type { CursorGlyph } from './types';\n\nexport interface BakeOptions {\n /** Rendered size in CSS px. Default 24. */\n readonly size?: number;\n /** Clockwise rotation in radians about the box centre, quantized to\n * {@link CURSOR_ANGLE_STEPS} steps. Default 0. */\n readonly angle?: number;\n /** Keyword drawn if the browser rejects the image. Default 'default'. */\n readonly fallback?: string;\n}\n\n/**\n * Index of the quantization step an angle falls in, in `[0, CURSOR_ANGLE_STEPS)`.\n *\n * Exported because the bake cache keys on this rather than on the raw angle:\n * the hover pump feeds a continuously varying selection rotation, and two\n * angles in the same step must be one cache entry, not two.\n */\nexport function quantizeCursorAngle(angle: number): number {\n const i = Math.round((angle / (Math.PI * 2)) * CURSOR_ANGLE_STEPS);\n return ((i % CURSOR_ANGLE_STEPS) + CURSOR_ANGLE_STEPS) % CURSOR_ANGLE_STEPS;\n}\n\n/** One paint op as an SVG element. */\nfunction renderOp(op: CursorPaintOp): string {\n const fill = op.fill === undefined ? 'none' : op.fill;\n const stroke = op.stroke\n ? ` stroke=\"${op.stroke.color}\" stroke-width=\"${op.stroke.width}\"` +\n ` stroke-linecap=\"round\" stroke-linejoin=\"round\"`\n : '';\n return `<path d=\"${op.d}\" fill=\"${fill}\"${stroke}/>`;\n}\n\n/**\n * Render a glyph to a CSS cursor value.\n *\n * Ships SVG rather than a bitmap because Chrome rasterizes an SVG data-URI\n * cursor at device scale — it is already crisp on a retina display, so there\n * is no PNG pipeline and no `image-set()` here. See the spec.\n */\nexport function bakeCursor(glyph: CursorGlyph, opts: BakeOptions = {}): string {\n const size = opts.size ?? 24;\n if (size > CURSOR_MAX_CSS_PX) {\n throw new RangeError(\n `cursor size ${size} exceeds the ${CURSOR_MAX_CSS_PX}px cap: the browser ` +\n `would drop the image and silently fall back. Use the painted tier.`,\n );\n }\n // Halos first, then ink; within each pass, source order is z-order. The\n // pass structure is `paint.ts`'s, so the painted tier draws the same glyph\n // rather than a second drawing that resembles it.\n const paths = cursorPaintOps(glyph).map(renderOp).join('');\n\n const c = glyph.box / 2;\n const step = quantizeCursorAngle(opts.angle ?? 0);\n const theta = (step / CURSOR_ANGLE_STEPS) * Math.PI * 2;\n // A whole-glyph transform rather than rotated path data: the same record\n // has to reach `paint.ts` unrotated, and a `d` string is not re-authorable.\n const body = step === 0\n ? paths\n : `<g transform=\"rotate(${(step * 360) / CURSOR_ANGLE_STEPS} ${c} ${c})\">${paths}</g>`;\n const svg =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\"` +\n ` viewBox=\"0 0 ${glyph.box} ${glyph.box}\">${body}</svg>`;\n // The hotspot travels with the glyph. Left behind, a rotated arrow would\n // point from its tail.\n const dx = glyph.hotspot[0] - c;\n const dy = glyph.hotspot[1] - c;\n const cos = Math.cos(theta);\n const sin = Math.sin(theta);\n const hx = Math.round(((c + cos * dx - sin * dy) / glyph.box) * size);\n const hy = Math.round(((c + sin * dx + cos * dy) / glyph.box) * size);\n const uri = `data:image/svg+xml,${encodeURIComponent(svg)}`;\n return `url(\"${uri}\") ${hx} ${hy}, ${opts.fallback ?? 'default'}`;\n}\n","// GENERATED by scripts/gen-cursors.mjs — do not edit by hand.\n// Filled silhouettes on a 24-unit box; the halo is applied at bake time.\n\nimport type { CursorGlyph } from './types';\n\nexport const GLYPHS = {\n \"pencil\": {\n \"box\": 24,\n \"hotspot\": [\n 5,\n 19\n ],\n \"paths\": [\n {\n \"role\": \"ink\",\n \"d\": \"M 5 19 L 7.5 16.5 L 16 8 L 19 11 L 10.5 19.5 L 8 19 Z\"\n },\n {\n \"role\": \"detail\",\n \"d\": \"M 14 6 L 19 11\",\n \"width\": 1.2\n }\n ]\n },\n \"pen\": {\n \"box\": 24,\n \"hotspot\": [\n 5,\n 19\n ],\n \"paths\": [\n {\n \"role\": \"ink\",\n \"d\": \"M 5 19 L 8.5 9.5 L 13 5 L 18 10 L 13.5 14.5 Z\"\n },\n {\n \"role\": \"detail\",\n \"d\": \"M 8.4 13.9 L 12.2 10.1\",\n \"width\": 0.9\n }\n ]\n },\n \"penPlus\": {\n \"box\": 24,\n \"hotspot\": [\n 3,\n 17\n ],\n \"paths\": [\n {\n \"role\": \"ink\",\n \"d\": \"M 3 17 L 6.5 7.5 L 11 3 L 16 8 L 11.5 12.5 Z\"\n },\n {\n \"role\": \"detail\",\n \"d\": \"M 6.4 11.9 L 10.2 8.1\",\n \"width\": 0.9\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 16.1 13.5 L 18.9 13.5 L 18.9 16.1 L 21.5 16.1 L 21.5 18.9 L 18.9 18.9 L 18.9 21.5 L 16.1 21.5 L 16.1 18.9 L 13.5 18.9 L 13.5 16.1 L 16.1 16.1 Z\"\n }\n ]\n },\n \"eyedropper\": {\n \"box\": 24,\n \"hotspot\": [\n 5,\n 19\n ],\n \"paths\": [\n {\n \"role\": \"ink\",\n \"d\": \"M 5 19 L 6.8 14.8 L 14.4 7.2 L 16.8 9.6 L 9.2 17.2 Z\"\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 18.2 2.6 A 3.2 3.2 0 1 0 18.2 9 A 3.2 3.2 0 1 0 18.2 2.6 Z\"\n }\n ]\n },\n \"brush\": {\n \"box\": 24,\n \"hotspot\": [\n 12,\n 12\n ],\n \"radius\": 9.9,\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 12 2.1 A 9.9 9.9 0 1 0 12 21.9 A 9.9 9.9 0 1 0 12 2.1 Z\",\n \"width\": 1.6\n }\n ]\n },\n \"crosshairRect\": {\n \"box\": 24,\n \"hotspot\": [\n 9,\n 9\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 1.6 9 L 6.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 11.6 9 L 16.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 1.6 L 9 6.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 11.6 L 9 16.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 14.2 14.2 L 21 14.2 L 21 21 L 14.2 21 Z\"\n }\n ]\n },\n \"crosshairEllipse\": {\n \"box\": 24,\n \"hotspot\": [\n 9,\n 9\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 1.6 9 L 6.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 11.6 9 L 16.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 1.6 L 9 6.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 11.6 L 9 16.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 17.6 13.9 A 3.7 3.7 0 1 0 17.6 21.3 A 3.7 3.7 0 1 0 17.6 13.9 Z\"\n }\n ]\n },\n \"crosshairLine\": {\n \"box\": 24,\n \"hotspot\": [\n 9,\n 9\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 1.6 9 L 6.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 11.6 9 L 16.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 1.6 L 9 6.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 11.6 L 9 16.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 14.2 21 L 21 14.2\",\n \"width\": 2.2\n }\n ]\n },\n \"crosshairStar\": {\n \"box\": 24,\n \"hotspot\": [\n 9,\n 9\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 1.6 9 L 6.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 11.6 9 L 16.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 1.6 L 9 6.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 11.6 L 9 16.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 17.6 13.4 L 18.72 16.06 L 21.59 16.3 L 19.41 18.19 L 20.07 21 L 17.6 19.5 L 15.13 21 L 15.79 18.19 L 13.61 16.3 L 16.48 16.06 Z\"\n }\n ]\n },\n \"crosshairPolygon\": {\n \"box\": 24,\n \"hotspot\": [\n 9,\n 9\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 1.6 9 L 6.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 11.6 9 L 16.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 1.6 L 9 6.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 11.6 L 9 16.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 17.6 13.5 L 21.5 16.33 L 20.01 20.92 L 15.19 20.92 L 13.7 16.33 Z\"\n }\n ]\n },\n \"resize\": {\n \"box\": 24,\n \"hotspot\": [\n 12,\n 12\n ],\n \"paths\": [\n {\n \"role\": \"ink\",\n \"d\": \"M 2.5 12 L 7 8.8 L 7 10.7 L 17 10.7 L 17 8.8 L 21.5 12 L 17 15.2 L 17 13.3 L 7 13.3 L 7 15.2 Z\"\n }\n ]\n },\n \"rotate\": {\n \"box\": 24,\n \"hotspot\": [\n 12,\n 12\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 5.8 9.74 A 6.6 6.6 0 0 1 18.2 9.74\",\n \"width\": 2.8\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 4.22 14.07 L 2.98 8.72 L 8.62 10.77 Z\"\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 19.78 14.07 L 15.38 10.77 L 21.02 8.72 Z\"\n }\n ]\n }\n} as const satisfies Record<string, CursorGlyph>;\n\n/** Every glyph name in the set. */\nexport type CursorGlyphName = keyof typeof GLYPHS;\n","import { bakeCursor, quantizeCursorAngle } from './bake';\nimport type { BakeOptions } from './bake';\nimport { GLYPHS } from './glyphs';\nimport type { CursorGlyphName } from './glyphs';\nimport type { CursorGlyph } from './types';\n\nconst cache = new Map<string, string>();\n\n/**\n * The baked cursor string for a named glyph, memoized.\n *\n * The key space is bounded by the glyph set times 16 angles times the handful\n * of sizes and fallbacks in use, so the cache needs no eviction.\n */\nexport function cursorFor(name: CursorGlyphName, opts: BakeOptions = {}): string {\n const size = opts.size ?? 24;\n const fallback = opts.fallback ?? 'default';\n // The step index, not the raw angle: the hover pump feeds a continuously\n // varying selection rotation, so a raw-angle key would never hit.\n const step = quantizeCursorAngle(opts.angle ?? 0);\n const key = `${name}|${size}|${step}|${fallback}`;\n const hit = cache.get(key);\n if (hit !== undefined) return hit;\n\n // Widened deliberately: the key type says this is always defined, but the\n // guard is what makes a bad name from untyped JS throw instead of baking\n // `undefined` into a cursor string that silently does nothing.\n const glyph = (GLYPHS as Record<string, CursorGlyph | undefined>)[name];\n if (glyph === undefined) {\n throw new Error(`unknown cursor glyph: ${String(name)}`);\n }\n const css = bakeCursor(glyph, { size, fallback, angle: opts.angle ?? 0 });\n cache.set(key, css);\n return css;\n}\n","import { cursorFor } from './registry';\nimport type { CursorGlyphName } from './glyphs';\nimport { CURSOR_MAX_CSS_PX } from './types';\n\n/**\n * What a tool, action or affordance declares as its cursor.\n *\n * A bare string is a CSS cursor value and passes through untouched, so every\n * declaration written before this type existed keeps working.\n */\nexport type CursorSpec = string | CursorGlyphSpec;\n\n/** The glyph form of a {@link CursorSpec}. */\nexport interface CursorGlyphSpec {\n readonly glyph: CursorGlyphName;\n /** Rendered size in CSS px. Default 24. */\n readonly size?: number;\n /**\n * Size in world units instead of `size`, so the glyph tracks zoom — a brush\n * radius ring. Forces the painted tier at every zoom level, because a baked\n * image would have to be rebuilt on every wheel tick.\n */\n readonly worldRadius?: number;\n /** Clockwise rotation in radians, quantized to 22.5° at bake. */\n readonly angle?: number;\n /** Keyword drawn if the browser rejects the image. Default 'default'. */\n readonly fallback?: string;\n}\n\n/**\n * Which tier a spec reaches the screen through.\n *\n * Tools never choose this — they declare what they want and the resolver\n * picks, so a brush cursor stays a brush cursor across the radius where it\n * stops being expressible as a CSS cursor at all.\n */\nexport type ResolvedCursor =\n | { readonly kind: 'css'; readonly css: string }\n | {\n readonly kind: 'painted';\n readonly glyph: CursorGlyphName;\n readonly angle: number;\n /** CSS px, when the spec fixed one. */\n readonly size?: number;\n /** World units; the painter scales this by the live view. */\n readonly worldRadius?: number;\n };\n\n/**\n * Resolve a spec to the tier that can actually draw it.\n *\n * Escalates to painted when the glyph is sized in world units, or when its\n * fixed size is past {@link CURSOR_MAX_CSS_PX} — above which the browser drops\n * the image and silently falls back to the keyword.\n */\nexport function resolveCursorTier(spec: CursorSpec): ResolvedCursor {\n if (typeof spec === 'string') return { kind: 'css', css: spec };\n const { glyph, size, worldRadius, angle = 0, fallback } = spec;\n if (worldRadius !== undefined) {\n return { kind: 'painted', glyph, angle, worldRadius };\n }\n if (size !== undefined && size > CURSOR_MAX_CSS_PX) {\n return { kind: 'painted', glyph, angle, size };\n }\n return {\n kind: 'css',\n css: cursorFor(glyph, {\n ...(size !== undefined ? { size } : {}),\n ...(fallback !== undefined ? { fallback } : {}),\n angle,\n }),\n };\n}\n\n/**\n * A {@link CursorSpec} as a CSS cursor value.\n *\n * Every consumer of a widened cursor field calls this, so a declaration never\n * has to know which form it holds. A painted cursor answers `'none'` — the\n * native cursor gets out of the way and the painted-tier layer draws it. A\n * caller that writes that string without the layer installed leaves the user\n * with no cursor, so reach for {@link resolveCursorTier} if you need to know.\n */\nexport function resolveCursor(spec: CursorSpec | undefined): string | undefined {\n if (spec === undefined) return undefined;\n const r = resolveCursorTier(spec);\n return r.kind === 'css' ? r.css : 'none';\n}\n","/**\n * The painted cursor a surface is currently showing, shared between whoever\n * decides it and the layer that draws it.\n *\n * Two slots rather than one, mirroring the precedence the cursor pipeline\n * already has: `<Canvas>` writes the active tool's cursor to `base` from\n * render, and the hover pump writes an affordance's or an action's cursor to\n * `override` imperatively. The override wins while it is set, and clearing it\n * restores the base without a React commit — the same reason the pump uses an\n * inline style for the CSS tier.\n */\n\nimport type { ResolvedCursor } from './resolve';\n\n/** The painted arm of `ResolvedCursor` — what this module stores. */\nexport type PaintedCursor = Extract<ResolvedCursor, { kind: 'painted' }>;\n\nexport interface PaintedCursorFrame {\n readonly cursor: PaintedCursor;\n /** Pointer position in the layer's own space: canvas-local CSS px. */\n readonly at: { readonly x: number; readonly y: number };\n}\n\nexport interface PaintedCursorState {\n setBase(cursor: PaintedCursor | null): void;\n setOverride(cursor: PaintedCursor | null): void;\n /** Canvas-local CSS px. */\n setPointer(x: number, y: number): void;\n /** Pointer left the surface: nothing to draw until it returns. */\n clearPointer(): void;\n /**\n * Whether a painted cursor is set at all, regardless of where the pointer is.\n *\n * Lets the pointer path skip measuring the canvas rect on every move in the\n * overwhelmingly common case where the compositor is drawing the cursor and\n * nothing here needs its position.\n */\n active(): boolean;\n /** What the layer should draw this frame, or null for none. */\n current(): PaintedCursorFrame | null;\n /** Run `fn` whenever the answer to `current()` may have changed. */\n subscribe(fn: () => void): () => void;\n}\n\n/**\n * Structural, not `Object.is`. Both writers rebuild their cursor object from a\n * spec every time they run — the pump on every pointermove, `<Canvas>` on\n * every render — so identity comparison would report a change constantly and\n * drive a repaint per pointer event.\n */\nfunction same(a: PaintedCursor | null, b: PaintedCursor | null): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n return (\n a.glyph === b.glyph &&\n a.angle === b.angle &&\n a.size === b.size &&\n a.worldRadius === b.worldRadius\n );\n}\n\nexport function createPaintedCursorState(): PaintedCursorState {\n let base: PaintedCursor | null = null;\n let override: PaintedCursor | null = null;\n let at: { x: number; y: number } | null = null;\n const subs = new Set<() => void>();\n\n const notify = (): void => {\n for (const fn of subs) fn();\n };\n\n return {\n setBase(cursor) {\n if (same(base, cursor)) return;\n base = cursor;\n notify();\n },\n setOverride(cursor) {\n if (same(override, cursor)) return;\n override = cursor;\n notify();\n },\n setPointer(x, y) {\n // A moved pointer only matters while something is being painted; a\n // notify per pointermove otherwise would request a redraw per event\n // for a cursor the compositor is drawing itself.\n if (at && at.x === x && at.y === y) return;\n at = { x, y };\n if (override ?? base) notify();\n },\n clearPointer() {\n if (at === null) return;\n at = null;\n notify();\n },\n active() {\n return (override ?? base) !== null;\n },\n current() {\n const cursor = override ?? base;\n if (!cursor || !at) return null;\n return { cursor, at };\n },\n subscribe(fn) {\n subs.add(fn);\n return () => { subs.delete(fn); };\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/paint.ts","../src/bake.ts","../src/glyphs.ts","../src/registry.ts","../src/resolve.ts","../src/paintedCursorState.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * A cursor glyph: SVG path `d` strings tagged with a paint role, plus the\n * hotspot in glyph units. The same record feeds both the data-URI baker and\n * the Path2D painter, so `d` is the one geometry form neither has to translate.\n */\nexport interface CursorGlyph {\n /** Side of the square viewBox the paths are authored in. */\n readonly box: number;\n /** Hotspot in glyph units, scaled to integer CSS px at bake time. */\n readonly hotspot: readonly [number, number];\n /**\n * The glyph-unit radius a `worldRadius` spec means, for glyphs that measure\n * something — a brush ring reads as a lie about the brush size unless the\n * painter knows which circle in the drawing is the one being sized. Defaults\n * to the box's inscribed radius.\n */\n readonly radius?: number;\n readonly paths: readonly CursorPath[];\n}\n\n/** One path of a {@link CursorGlyph}, tagged with the role that decides its paint. */\nexport type CursorPath =\n /** A filled part of the silhouette. */\n | { readonly role: 'ink'; readonly d: string }\n /** An unfilled part of the silhouette — a handle, a wire, an arc. */\n | { readonly role: 'stroke'; readonly d: string; readonly width: number }\n /** A division inside the silhouette, drawn in the halo color. */\n | { readonly role: 'detail'; readonly d: string; readonly width: number }\n /** A literal color, for glyphs that carry a swatch. */\n | { readonly role: 'accent'; readonly d: string; readonly fill: string };\n\n/**\n * Ink and halo are constants of the register rather than parameters: a\n * self-contrasting glyph reads on white paper, dark chrome and mid-tone\n * artwork alike precisely because it does not track the theme.\n */\nexport const CURSOR_INK = '#141418';\n/** The outline stroked around every silhouette member. */\nexport const CURSOR_HALO = '#ffffff';\n/** Halo stroke width in glyph units; a stroke path's halo adds it to the path's own width. */\nexport const CURSOR_HALO_WIDTH = 2.6;\n\n/**\n * Rotation steps a baked angle is quantized to. Below 16 the cursor visibly\n * snaps against a smoothly rotating selection; above it the cache grows for no\n * perceptible gain.\n */\nexport const CURSOR_ANGLE_STEPS = 16;\n\n/**\n * Chrome silently drops a cursor image above this size and falls back to the\n * keyword after the comma, with no error anywhere. Measured on Chrome 152 /\n * macOS 26.5; see the spec's \"Measured browser behavior\".\n */\nexport const CURSOR_MAX_CSS_PX = 128;\n\n/**\n * Visits the points that bound an authored `d` string, for the fit guards below.\n *\n * Command-aware on purpose. Scraping every number out of the string instead\n * reads an arc's radii and its three flags as coordinates, which reports a\n * bogus `0` for every `A` ever written.\n *\n * An arc is bounded by the circle it lies on: its centre is recovered from the\n * endpoints and radius, then the whole circle is admitted. Expanding the arc's\n * *endpoint* by the radii instead over-reports whenever an endpoint sits at an\n * extreme of the circle, which is exactly where the half-circle idiom these\n * glyphs use puts it.\n *\n * The authored dialect is absolute `M`/`L`/`A`/`Z` with circular arcs. A\n * relative command would be measured as if absolute and silently under-report,\n * so it throws.\n */\nfunction walk(d: string, see: (x: number, y: number) => void): void {\n let cx = 0;\n let cy = 0;\n for (const [, cmd, args] of d.matchAll(/([A-Za-z])([^A-Za-z]*)/g)) {\n if (cmd !== cmd.toUpperCase()) {\n throw new Error(`cursor glyph path uses a relative command '${cmd}': ${d}`);\n }\n const nums = (args.match(/-?\\d+(?:\\.\\d+)?/g) ?? []).map(Number);\n if (cmd === 'Z') continue;\n if (cmd === 'A') {\n for (let i = 0; i + 7 <= nums.length; i += 7) {\n const [rx, ry, , largeArc, sweep, x, y] = nums.slice(i, i + 7);\n const r = Math.max(rx, ry);\n const dx = x - cx;\n const dy = y - cy;\n const half = Math.hypot(dx, dy) / 2;\n // Centre sits off the chord midpoint by this much, perpendicular to it.\n const off = Math.sqrt(Math.max(0, r * r - half * half));\n const sign = largeArc === sweep ? -1 : 1;\n const ux = half > 0 ? -dy / (half * 2) : 0;\n const uy = half > 0 ? dx / (half * 2) : 0;\n const ox = (cx + x) / 2 + sign * off * ux;\n const oy = (cy + y) / 2 + sign * off * uy;\n see(ox - r, oy - r);\n see(ox + r, oy - r);\n see(ox - r, oy + r);\n see(ox + r, oy + r);\n cx = x;\n cy = y;\n }\n continue;\n }\n // M and L: plain coordinate pairs.\n for (let i = 0; i + 2 <= nums.length; i += 2) {\n cx = nums[i];\n cy = nums[i + 1];\n see(cx, cy);\n }\n }\n}\n\n/**\n * True when every authored path sits at least half a halo stroke inside the\n * viewBox. A clipped halo is invisible at proof size and flattens the glyph's\n * outline at cursor size, so it is worth failing loudly at authoring time\n * rather than discovering it on a dark background.\n */\nexport function haloFitsInBox(glyph: CursorGlyph): boolean {\n const margin = CURSOR_HALO_WIDTH / 2;\n let ok = true;\n for (const p of glyph.paths) {\n walk(p.d, (x, y) => {\n for (const v of [x, y]) {\n if (v < margin || v > glyph.box - margin) ok = false;\n }\n });\n }\n return ok;\n}\n\n/**\n * True when the glyph still fits its viewBox at every rotation — i.e. every\n * authored point is inside the box's inscribed circle, halo included.\n *\n * Bake rotates about the box centre in a viewBox that does not grow, so a\n * glyph that merely satisfies {@link haloFitsInBox} can have its corners\n * sheared off partway round. The loss is a few pixels at cursor size and\n * invisible until someone looks at exactly the wrong angle, which is why it\n * is worth failing at authoring time.\n */\nexport function rotationFitsInBox(glyph: CursorGlyph): boolean {\n const c = glyph.box / 2;\n const limit = c - CURSOR_HALO_WIDTH / 2;\n let ok = true;\n for (const p of glyph.paths) {\n walk(p.d, (x, y) => {\n if (Math.hypot(x - c, y - c) > limit) ok = false;\n });\n }\n return ok;\n}\n","import { CURSOR_HALO, CURSOR_HALO_WIDTH, CURSOR_INK } from './types';\nimport type { CursorGlyph, CursorPath } from './types';\n\n/**\n * One path of a glyph, with the paint the register gives it.\n *\n * Deliberately not a renderer's own type: `@weasel-js/cursor` decides *what*\n * to paint and in what order, and leaves *how* to whoever is drawing — the\n * baker turns each op into an SVG element, the painted-tier layer turns it\n * into a draw command. Both get the same list, which is what makes the two\n * tiers show the same glyph rather than two drawings that resemble each other.\n *\n * Stroked ops are round-capped and round-joined; nothing in the register\n * wants a mitre.\n */\nexport interface CursorPaintOp {\n readonly d: string;\n readonly fill?: string;\n readonly stroke?: { readonly color: string; readonly width: number };\n}\n\n/** Options for {@link cursorPaintOps}. */\nexport interface PaintOptions {\n /**\n * Multiplies every stroke width, leaving geometry alone. Default 1.\n *\n * A world-sized glyph passes the reciprocal of its scale here so line weight\n * stays chrome-weight in CSS px: a brush ring at a 400px radius otherwise\n * carries a 40px-thick stroke and a halo to match.\n */\n readonly lineWidthScale?: number;\n}\n\n/**\n * The halo pass — every silhouette member drawn once, wide, in halo colour\n * before any ink lands.\n *\n * One pass under everything rather than a per-path `paint-order`: with each\n * path haloing itself, a later path's halo cuts a white trench through an\n * earlier path's fill wherever the two overlap.\n */\nfunction halo(p: CursorPath, k: number): CursorPaintOp | null {\n switch (p.role) {\n case 'ink':\n case 'accent':\n return { d: p.d, fill: CURSOR_HALO, stroke: { color: CURSOR_HALO, width: CURSOR_HALO_WIDTH * k } };\n case 'stroke':\n return { d: p.d, stroke: { color: CURSOR_HALO, width: (p.width + CURSOR_HALO_WIDTH) * k } };\n // A detail IS halo-coloured and sits on top of the ink; it has no halo.\n case 'detail':\n return null;\n }\n}\n\nfunction ink(p: CursorPath, k: number): CursorPaintOp {\n switch (p.role) {\n case 'ink':\n return { d: p.d, fill: CURSOR_INK };\n case 'stroke':\n return { d: p.d, stroke: { color: CURSOR_INK, width: p.width * k } };\n case 'detail':\n return { d: p.d, stroke: { color: CURSOR_HALO, width: p.width * k } };\n case 'accent':\n return { d: p.d, fill: p.fill };\n }\n}\n\n/** Every paint a glyph needs, in draw order: all halos, then all ink. */\nexport function cursorPaintOps(glyph: CursorGlyph, opts: PaintOptions = {}): CursorPaintOp[] {\n const k = opts.lineWidthScale ?? 1;\n const out: CursorPaintOp[] = [];\n for (const p of glyph.paths) {\n const h = halo(p, k);\n if (h) out.push(h);\n }\n for (const p of glyph.paths) out.push(ink(p, k));\n return out;\n}\n\n/** An affine `[a, b, c, d, e, f]`: `x' = ax + cy + e`, `y' = bx + dy + f`. */\nexport type CursorMatrix = readonly [number, number, number, number, number, number];\n\n/** Where and how large {@link cursorPaintMatrix} places a glyph. */\nexport interface PaintPlacement {\n /** Rendered size in CSS px, as `bakeCursor` means it. */\n readonly size: number;\n /** Clockwise rotation in radians. Default 0. */\n readonly angle?: number;\n /** Where the hotspot goes — the pointer, in the layer's own coords. */\n readonly at: { readonly x: number; readonly y: number };\n}\n\n/**\n * Where a glyph's own units land, to place it under the pointer at a size and\n * an angle.\n *\n * Rotation is about the hotspot, not the box centre. That is the same\n * placement the baker produces — it turns the glyph about the centre and then\n * carries the hotspot around with it, and the centre cancels — but it is worth\n * stating, because a painter that rotates about the centre and forgets to move\n * the hotspot swings the glyph around a point the user never sees.\n *\n * Unquantized on purpose: the 22.5° step exists to bound the bake cache, and\n * the painter has no cache to bound. A painted cursor turns smoothly.\n */\nexport function cursorPaintMatrix(glyph: CursorGlyph, placement: PaintPlacement): CursorMatrix {\n const s = placement.size / glyph.box;\n const theta = placement.angle ?? 0;\n // y is down, so a positive angle carries +x toward +y — clockwise on screen.\n const a = s * Math.cos(theta);\n const b = s * Math.sin(theta);\n const [hx, hy] = glyph.hotspot;\n return [a, b, -b, a, placement.at.x - (a * hx - b * hy), placement.at.y - (b * hx + a * hy)];\n}\n\n/**\n * The CSS-px size a world-sized glyph is drawn at, so the circle its `radius`\n * names measures `worldRadius` world units on screen.\n *\n * A glyph with no `radius` is measured by its inscribed circle. Getting this\n * wrong scales every brush cursor by a constant factor, which reads as a\n * slightly-off cursor rather than as a bug.\n */\nexport function cursorWorldSize(\n glyph: CursorGlyph,\n worldRadius: number,\n scale: number,\n): number {\n const r = glyph.radius ?? glyph.box / 2;\n return (worldRadius * scale * glyph.box) / r;\n}\n\n/**\n * The `lineWidthScale` that keeps a glyph's line weight at chrome weight while\n * its geometry scales — what a world-sized glyph wants, and what a fixed-size\n * one must not have (there, weight scaling is what makes the painted tier\n * match the baked one).\n */\nexport function chromeLineWidthScale(glyph: CursorGlyph, size: number): number {\n return glyph.box / size;\n}\n","import { cursorPaintOps } from './paint';\nimport type { CursorPaintOp } from './paint';\nimport { CURSOR_ANGLE_STEPS, CURSOR_MAX_CSS_PX } from './types';\nimport type { CursorGlyph } from './types';\n\n/** How {@link bakeCursor} renders a glyph. */\nexport interface BakeOptions {\n /** Rendered size in CSS px. Default 24. */\n readonly size?: number;\n /** Clockwise rotation in radians about the box centre, quantized to\n * {@link CURSOR_ANGLE_STEPS} steps. Default 0. */\n readonly angle?: number;\n /** Keyword drawn if the browser rejects the image. Default 'default'. */\n readonly fallback?: string;\n}\n\n/**\n * Index of the quantization step an angle falls in, in `[0, CURSOR_ANGLE_STEPS)`.\n *\n * Exported because the bake cache keys on this rather than on the raw angle:\n * the hover pump feeds a continuously varying selection rotation, and two\n * angles in the same step must be one cache entry, not two.\n */\nexport function quantizeCursorAngle(angle: number): number {\n const i = Math.round((angle / (Math.PI * 2)) * CURSOR_ANGLE_STEPS);\n return ((i % CURSOR_ANGLE_STEPS) + CURSOR_ANGLE_STEPS) % CURSOR_ANGLE_STEPS;\n}\n\n/** One paint op as an SVG element. */\nfunction renderOp(op: CursorPaintOp): string {\n const fill = op.fill === undefined ? 'none' : op.fill;\n const stroke = op.stroke\n ? ` stroke=\"${op.stroke.color}\" stroke-width=\"${op.stroke.width}\"` +\n ` stroke-linecap=\"round\" stroke-linejoin=\"round\"`\n : '';\n return `<path d=\"${op.d}\" fill=\"${fill}\"${stroke}/>`;\n}\n\n/**\n * Render a glyph to a CSS cursor value.\n *\n * Ships SVG rather than a bitmap because Chrome rasterizes an SVG data-URI\n * cursor at device scale — it is already crisp on a retina display, so there\n * is no PNG pipeline and no `image-set()` here. See the spec.\n */\nexport function bakeCursor(glyph: CursorGlyph, opts: BakeOptions = {}): string {\n const size = opts.size ?? 24;\n if (size > CURSOR_MAX_CSS_PX) {\n throw new RangeError(\n `cursor size ${size} exceeds the ${CURSOR_MAX_CSS_PX}px cap: the browser ` +\n `would drop the image and silently fall back. Use the painted tier.`,\n );\n }\n // Halos first, then ink; within each pass, source order is z-order. The\n // pass structure is `paint.ts`'s, so the painted tier draws the same glyph\n // rather than a second drawing that resembles it.\n const paths = cursorPaintOps(glyph).map(renderOp).join('');\n\n const c = glyph.box / 2;\n const step = quantizeCursorAngle(opts.angle ?? 0);\n const theta = (step / CURSOR_ANGLE_STEPS) * Math.PI * 2;\n // A whole-glyph transform rather than rotated path data: the same record\n // has to reach `paint.ts` unrotated, and a `d` string is not re-authorable.\n const body = step === 0\n ? paths\n : `<g transform=\"rotate(${(step * 360) / CURSOR_ANGLE_STEPS} ${c} ${c})\">${paths}</g>`;\n const svg =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\"` +\n ` viewBox=\"0 0 ${glyph.box} ${glyph.box}\">${body}</svg>`;\n // The hotspot travels with the glyph. Left behind, a rotated arrow would\n // point from its tail.\n const dx = glyph.hotspot[0] - c;\n const dy = glyph.hotspot[1] - c;\n const cos = Math.cos(theta);\n const sin = Math.sin(theta);\n const hx = Math.round(((c + cos * dx - sin * dy) / glyph.box) * size);\n const hy = Math.round(((c + sin * dx + cos * dy) / glyph.box) * size);\n const uri = `data:image/svg+xml,${encodeURIComponent(svg)}`;\n return `url(\"${uri}\") ${hx} ${hy}, ${opts.fallback ?? 'default'}`;\n}\n","// GENERATED by scripts/gen-cursors.mjs — do not edit by hand.\n// Filled silhouettes on a 24-unit box; the halo is applied at bake time.\n\nimport type { CursorGlyph } from './types';\n\n/** Every built-in cursor glyph, by name. */\nexport const GLYPHS = {\n \"pencil\": {\n \"box\": 24,\n \"hotspot\": [\n 5,\n 19\n ],\n \"paths\": [\n {\n \"role\": \"ink\",\n \"d\": \"M 5 19 L 7.5 16.5 L 16 8 L 19 11 L 10.5 19.5 L 8 19 Z\"\n },\n {\n \"role\": \"detail\",\n \"d\": \"M 14 6 L 19 11\",\n \"width\": 1.2\n }\n ]\n },\n \"pen\": {\n \"box\": 24,\n \"hotspot\": [\n 5,\n 19\n ],\n \"paths\": [\n {\n \"role\": \"ink\",\n \"d\": \"M 5 19 L 8.5 9.5 L 13 5 L 18 10 L 13.5 14.5 Z\"\n },\n {\n \"role\": \"detail\",\n \"d\": \"M 8.4 13.9 L 12.2 10.1\",\n \"width\": 0.9\n }\n ]\n },\n \"penPlus\": {\n \"box\": 24,\n \"hotspot\": [\n 3,\n 17\n ],\n \"paths\": [\n {\n \"role\": \"ink\",\n \"d\": \"M 3 17 L 6.5 7.5 L 11 3 L 16 8 L 11.5 12.5 Z\"\n },\n {\n \"role\": \"detail\",\n \"d\": \"M 6.4 11.9 L 10.2 8.1\",\n \"width\": 0.9\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 16.1 13.5 L 18.9 13.5 L 18.9 16.1 L 21.5 16.1 L 21.5 18.9 L 18.9 18.9 L 18.9 21.5 L 16.1 21.5 L 16.1 18.9 L 13.5 18.9 L 13.5 16.1 L 16.1 16.1 Z\"\n }\n ]\n },\n \"eyedropper\": {\n \"box\": 24,\n \"hotspot\": [\n 5,\n 19\n ],\n \"paths\": [\n {\n \"role\": \"ink\",\n \"d\": \"M 5 19 L 6.8 14.8 L 14.4 7.2 L 16.8 9.6 L 9.2 17.2 Z\"\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 18.2 2.6 A 3.2 3.2 0 1 0 18.2 9 A 3.2 3.2 0 1 0 18.2 2.6 Z\"\n }\n ]\n },\n \"brush\": {\n \"box\": 24,\n \"hotspot\": [\n 12,\n 12\n ],\n \"radius\": 9.9,\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 12 2.1 A 9.9 9.9 0 1 0 12 21.9 A 9.9 9.9 0 1 0 12 2.1 Z\",\n \"width\": 1.6\n }\n ]\n },\n \"crosshairRect\": {\n \"box\": 24,\n \"hotspot\": [\n 9,\n 9\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 1.6 9 L 6.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 11.6 9 L 16.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 1.6 L 9 6.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 11.6 L 9 16.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 14.2 14.2 L 21 14.2 L 21 21 L 14.2 21 Z\"\n }\n ]\n },\n \"crosshairEllipse\": {\n \"box\": 24,\n \"hotspot\": [\n 9,\n 9\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 1.6 9 L 6.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 11.6 9 L 16.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 1.6 L 9 6.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 11.6 L 9 16.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 17.6 13.9 A 3.7 3.7 0 1 0 17.6 21.3 A 3.7 3.7 0 1 0 17.6 13.9 Z\"\n }\n ]\n },\n \"crosshairLine\": {\n \"box\": 24,\n \"hotspot\": [\n 9,\n 9\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 1.6 9 L 6.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 11.6 9 L 16.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 1.6 L 9 6.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 11.6 L 9 16.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 14.2 21 L 21 14.2\",\n \"width\": 2.2\n }\n ]\n },\n \"crosshairStar\": {\n \"box\": 24,\n \"hotspot\": [\n 9,\n 9\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 1.6 9 L 6.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 11.6 9 L 16.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 1.6 L 9 6.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 11.6 L 9 16.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 17.6 13.4 L 18.72 16.06 L 21.59 16.3 L 19.41 18.19 L 20.07 21 L 17.6 19.5 L 15.13 21 L 15.79 18.19 L 13.61 16.3 L 16.48 16.06 Z\"\n }\n ]\n },\n \"crosshairPolygon\": {\n \"box\": 24,\n \"hotspot\": [\n 9,\n 9\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 1.6 9 L 6.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 11.6 9 L 16.4 9\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 1.6 L 9 6.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"stroke\",\n \"d\": \"M 9 11.6 L 9 16.4\",\n \"width\": 1.6\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 17.6 13.5 L 21.5 16.33 L 20.01 20.92 L 15.19 20.92 L 13.7 16.33 Z\"\n }\n ]\n },\n \"resize\": {\n \"box\": 24,\n \"hotspot\": [\n 12,\n 12\n ],\n \"paths\": [\n {\n \"role\": \"ink\",\n \"d\": \"M 2.5 12 L 7 8.8 L 7 10.7 L 17 10.7 L 17 8.8 L 21.5 12 L 17 15.2 L 17 13.3 L 7 13.3 L 7 15.2 Z\"\n }\n ]\n },\n \"rotate\": {\n \"box\": 24,\n \"hotspot\": [\n 12,\n 12\n ],\n \"paths\": [\n {\n \"role\": \"stroke\",\n \"d\": \"M 5.8 9.74 A 6.6 6.6 0 0 1 18.2 9.74\",\n \"width\": 2.8\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 4.22 14.07 L 2.98 8.72 L 8.62 10.77 Z\"\n },\n {\n \"role\": \"ink\",\n \"d\": \"M 19.78 14.07 L 15.38 10.77 L 21.02 8.72 Z\"\n }\n ]\n }\n} as const satisfies Record<string, CursorGlyph>;\n\n/** Every glyph name in the set. */\nexport type CursorGlyphName = keyof typeof GLYPHS;\n","import { bakeCursor, quantizeCursorAngle } from './bake';\nimport type { BakeOptions } from './bake';\nimport { GLYPHS } from './glyphs';\nimport type { CursorGlyphName } from './glyphs';\nimport type { CursorGlyph } from './types';\n\nconst cache = new Map<string, string>();\n\n/**\n * The baked cursor string for a named glyph, memoized.\n *\n * The key space is bounded by the glyph set times 16 angles times the handful\n * of sizes and fallbacks in use, so the cache needs no eviction.\n */\nexport function cursorFor(name: CursorGlyphName, opts: BakeOptions = {}): string {\n const size = opts.size ?? 24;\n const fallback = opts.fallback ?? 'default';\n // The step index, not the raw angle: the hover pump feeds a continuously\n // varying selection rotation, so a raw-angle key would never hit.\n const step = quantizeCursorAngle(opts.angle ?? 0);\n const key = `${name}|${size}|${step}|${fallback}`;\n const hit = cache.get(key);\n if (hit !== undefined) return hit;\n\n // Widened deliberately: the key type says this is always defined, but the\n // guard is what makes a bad name from untyped JS throw instead of baking\n // `undefined` into a cursor string that silently does nothing.\n const glyph = (GLYPHS as Record<string, CursorGlyph | undefined>)[name];\n if (glyph === undefined) {\n throw new Error(`unknown cursor glyph: ${String(name)}`);\n }\n const css = bakeCursor(glyph, { size, fallback, angle: opts.angle ?? 0 });\n cache.set(key, css);\n return css;\n}\n","import { cursorFor } from './registry';\nimport type { CursorGlyphName } from './glyphs';\nimport { CURSOR_MAX_CSS_PX } from './types';\n\n/**\n * What a tool, action or affordance declares as its cursor.\n *\n * A bare string is a CSS cursor value and passes through untouched, so every\n * declaration written before this type existed keeps working.\n */\nexport type CursorSpec = string | CursorGlyphSpec;\n\n/** The glyph form of a {@link CursorSpec}. */\nexport interface CursorGlyphSpec {\n readonly glyph: CursorGlyphName;\n /** Rendered size in CSS px. Default 24. */\n readonly size?: number;\n /**\n * Size in world units instead of `size`, so the glyph tracks zoom — a brush\n * radius ring. Forces the painted tier at every zoom level, because a baked\n * image would have to be rebuilt on every wheel tick.\n */\n readonly worldRadius?: number;\n /** Clockwise rotation in radians, quantized to 22.5° at bake. */\n readonly angle?: number;\n /** Keyword drawn if the browser rejects the image. Default 'default'. */\n readonly fallback?: string;\n}\n\n/**\n * Which tier a spec reaches the screen through.\n *\n * Tools never choose this — they declare what they want and the resolver\n * picks, so a brush cursor stays a brush cursor across the radius where it\n * stops being expressible as a CSS cursor at all.\n */\nexport type ResolvedCursor =\n | { readonly kind: 'css'; readonly css: string }\n | {\n readonly kind: 'painted';\n readonly glyph: CursorGlyphName;\n readonly angle: number;\n /** CSS px, when the spec fixed one. */\n readonly size?: number;\n /** World units; the painter scales this by the live view. */\n readonly worldRadius?: number;\n };\n\n/**\n * Resolve a spec to the tier that can actually draw it.\n *\n * Escalates to painted when the glyph is sized in world units, or when its\n * fixed size is past {@link CURSOR_MAX_CSS_PX} — above which the browser drops\n * the image and silently falls back to the keyword.\n */\nexport function resolveCursorTier(spec: CursorSpec): ResolvedCursor {\n if (typeof spec === 'string') return { kind: 'css', css: spec };\n const { glyph, size, worldRadius, angle = 0, fallback } = spec;\n if (worldRadius !== undefined) {\n return { kind: 'painted', glyph, angle, worldRadius };\n }\n if (size !== undefined && size > CURSOR_MAX_CSS_PX) {\n return { kind: 'painted', glyph, angle, size };\n }\n return {\n kind: 'css',\n css: cursorFor(glyph, {\n ...(size !== undefined ? { size } : {}),\n ...(fallback !== undefined ? { fallback } : {}),\n angle,\n }),\n };\n}\n\n/**\n * A {@link CursorSpec} as a CSS cursor value.\n *\n * Every consumer of a widened cursor field calls this, so a declaration never\n * has to know which form it holds. A painted cursor answers `'none'` — the\n * native cursor gets out of the way and the painted-tier layer draws it. A\n * caller that writes that string without the layer installed leaves the user\n * with no cursor, so reach for {@link resolveCursorTier} if you need to know.\n */\nexport function resolveCursor(spec: CursorSpec | undefined): string | undefined {\n if (spec === undefined) return undefined;\n const r = resolveCursorTier(spec);\n return r.kind === 'css' ? r.css : 'none';\n}\n","/**\n * The painted cursor a surface is currently showing, shared between whoever\n * decides it and the layer that draws it.\n *\n * Two slots rather than one, mirroring the precedence the cursor pipeline\n * already has: `<Canvas>` writes the active tool's cursor to `base` from\n * render, and the hover pump writes an affordance's or an action's cursor to\n * `override` imperatively. The override wins while it is set, and clearing it\n * restores the base without a React commit — the same reason the pump uses an\n * inline style for the CSS tier.\n */\n\nimport type { ResolvedCursor } from './resolve';\n\n/** The painted arm of `ResolvedCursor` — what this module stores. */\nexport type PaintedCursor = Extract<ResolvedCursor, { kind: 'painted' }>;\n\n/** One frame's worth of painted cursor: which glyph, and where. */\nexport interface PaintedCursorFrame {\n readonly cursor: PaintedCursor;\n /** Pointer position in the layer's own space: canvas-local CSS px. */\n readonly at: { readonly x: number; readonly y: number };\n}\n\n/** A surface's painted cursor, in two slots: a base and an override that wins while set. */\nexport interface PaintedCursorState {\n /** The active tool's cursor. */\n setBase(cursor: PaintedCursor | null): void;\n /** An affordance's or action's cursor; wins over the base while set. */\n setOverride(cursor: PaintedCursor | null): void;\n /** Canvas-local CSS px. */\n setPointer(x: number, y: number): void;\n /** Pointer left the surface: nothing to draw until it returns. */\n clearPointer(): void;\n /**\n * Whether a painted cursor is set at all, regardless of where the pointer is.\n *\n * Lets the pointer path skip measuring the canvas rect on every move in the\n * overwhelmingly common case where the compositor is drawing the cursor and\n * nothing here needs its position.\n */\n active(): boolean;\n /** What the layer should draw this frame, or null for none. */\n current(): PaintedCursorFrame | null;\n /** Run `fn` whenever the answer to `current()` may have changed. */\n subscribe(fn: () => void): () => void;\n}\n\n/**\n * Structural, not `Object.is`. Both writers rebuild their cursor object from a\n * spec every time they run — the pump on every pointermove, `<Canvas>` on\n * every render — so identity comparison would report a change constantly and\n * drive a repaint per pointer event.\n */\nfunction same(a: PaintedCursor | null, b: PaintedCursor | null): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n return (\n a.glyph === b.glyph &&\n a.angle === b.angle &&\n a.size === b.size &&\n a.worldRadius === b.worldRadius\n );\n}\n\n/** An empty store: no cursor set, pointer outside the surface. */\nexport function createPaintedCursorState(): PaintedCursorState {\n let base: PaintedCursor | null = null;\n let override: PaintedCursor | null = null;\n let at: { x: number; y: number } | null = null;\n const subs = new Set<() => void>();\n\n const notify = (): void => {\n for (const fn of subs) fn();\n };\n\n return {\n setBase(cursor) {\n if (same(base, cursor)) return;\n base = cursor;\n notify();\n },\n setOverride(cursor) {\n if (same(override, cursor)) return;\n override = cursor;\n notify();\n },\n setPointer(x, y) {\n // A moved pointer only matters while something is being painted; a\n // notify per pointermove otherwise would request a redraw per event\n // for a cursor the compositor is drawing itself.\n if (at && at.x === x && at.y === y) return;\n at = { x, y };\n if (override ?? base) notify();\n },\n clearPointer() {\n if (at === null) return;\n at = null;\n notify();\n },\n active() {\n return (override ?? base) !== null;\n },\n current() {\n const cursor = override ?? base;\n if (!cursor || !at) return null;\n return { cursor, at };\n },\n subscribe(fn) {\n subs.add(fn);\n return () => { subs.delete(fn); };\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weasel-js/cursor",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "description": "Tool cursors as authored glyphs: bake one to a CSS url() cursor string, or paint it when it is too big to be one.",
5
5
  "license": "MIT",
6
6
  "type": "module",