@weasel-js/core 1.9.1 → 1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 6a1afa1: Canvas text in a CSS generic family — `sans-serif`, `monospace`, `system-ui` and the rest — now renders in that family. The dynamic glyph atlas quoted every family, and a quoted `"sans-serif"` names a font nobody has, so the browser drew it in its default serif; every `DiagramView` on default options was affected.
8
+
9
+ Every font string the kit builds now goes through one helper, `cssFamilyName`, exported from `@weasel-js/font` and `@weasel-js/core`: a generic keyword stays bare, a single named family is quoted, and a family list passes through as written. `fontString` and the DOM face's fallback therefore now quote a named family (`"Helvetica"`), which CSS reads the same.
10
+ - c28a3cb: A canvas no longer claims Space or Enter when focus is on a control those keys activate — a button, a link, a checkbox — so a mounted canvas stops canceling a focused button's activation with its held-Space pan. Other shortcuts still reach the canvas from a focused button. The test is exported as `activatesFocusedControl`, beside `isEditableTarget`.
11
+
12
+ `DiagramView` takes `enableKeybindings`, and a view-only diagram (no `onMove` or `onConnect`) now defaults it off, so it no longer takes held Space from the rest of the page. Pass `enableKeybindings` to keep a view-only diagram's keys on.
13
+ - 7cb27b7: Fields now refer to each other by the full path their values are read and written at (`'camera.type'`, `'pose.y'`).
14
+
15
+ `pair` is no longer a shared label string. A row is declared once, on its first leaf: `pair: { with: 'pose.y', label: 'Position' }`, where `with` names the other leaves on the row (one path or several) and `label` overrides what the row reads, which is otherwise the declaring leaf's `name`. This is a breaking change to the shape: a schema that set `pair: 'Position'` on each member now sets `pair` on the first member only. labkit's builder follows: `.pair({ with: 'y', label: 'Offset' })`. `pairRowsOf` (core) resolves a surface's leaves to their rows, and `SelectionPanel`, `ToolOptionsBar`, and labkit's `ControlPanel` all group rows through it.
16
+
17
+ A new built-in pref kind, `field`, holds such a path. Its control is a picker over the fields of the surface drawing it, labeled `Name (path)`, narrowed by the leaf's optional `kinds`. `prefFieldChoices` lists a schema's fields, with a flag for whether a group's key is part of a path, since a prefs form nests values by group and a node's property panel does not. `PrefsForm` takes `fields` for a form that edits another schema, and `PrefSchemaEditor` edits a leaf's `pair` by picking its partners from the schema's fields.
18
+ - e05c820: `PrefSchemaEditor` now has undo and redo: Undo and Redo buttons over the structure, and Mod+Z, Shift+Mod+Z, and Mod+Y anywhere inside it. A run of edits to one node's attributes undoes as one step, and undo restores the selection. A `schema` the editor did not write itself starts the history over. Add pref and Add group no longer create a node with a made-up key: each opens a dialog asking for a name, an id, and a pref's kind; the id follows the name in camelCase until it is typed into, and a taken or invalid id is refused. The structure tree shows each node as its name with its key beside it, or its key alone when it has no name. Each pane has a header, and the preview's carries a Show hidden switch and Reset values. The exported literal is syntax-highlighted.
19
+
20
+ `@weasel-js/ui` adds `CodeBlock`, a syntax-highlighted block of source with optional line numbers, built on `prism-react-renderer` and colored from the new `--wzl-code-keyword`, `--wzl-code-string`, `--wzl-code-number`, `--wzl-code-constant`, `--wzl-code-property`, and `--wzl-code-name` theme tokens, which have a value for each mode.
21
+
22
+ `historyKey(event)` (`@weasel-js/routing`, re-exported from core) says whether a key event asks for undo or redo. labkit's trial chrome and `LayeredCurveEditor` now read their undo keys through it, so Mod+Y redoes in both.
23
+ - Updated dependencies [6a1afa1]
24
+ - Updated dependencies [c28a3cb]
25
+ - Updated dependencies [e05c820]
26
+ - @weasel-js/font@1.9.3
27
+ - @weasel-js/text@1.9.3
28
+ - @weasel-js/routing@1.9.3
29
+ - @weasel-js/cursor@1.9.3
30
+ - @weasel-js/geom@1.9.3
31
+ - @weasel-js/gestures@1.9.3
32
+ - @weasel-js/history@1.9.3
33
+ - @weasel-js/modes@1.9.3
34
+ - @weasel-js/paint@1.9.3
35
+ - @weasel-js/quantity@1.9.3
36
+ - @weasel-js/react@1.9.3
37
+ - @weasel-js/registry@1.9.3
38
+ - @weasel-js/select@1.9.3
39
+
40
+ ## 1.9.2
41
+
42
+ ### Patch Changes
43
+
44
+ - 3ba97df: `reconcileSpecs(scene, prev, next)` brings a scene from one list of node specs to another: it adds and removes by id and writes only the fields that changed between the two lists, as one untracked step. What the scene did since `prev` survives unless `next` changes that same field.
45
+
46
+ `DiagramView` no longer remounts on a new `specs` array. It reconciles the new specs into the scene it holds, so the view, the pick and anything moved stay put. `onMove` turns on dragging and reports every node a drag or a layout run moved. `onConnect` makes ports grabbable and reports the edge a connect describes without drawing it, and `canConnect` and `portOptions` configure that. A `ref` gets `layout(algorithm)`, which re-runs a layout animated from where the nodes stand, and the run's `live` controls; `live` configures the run.
47
+
48
+ `diagramScene` gives every spec an id, and takes `layout: 'none'` to leave each node at its `at`.
49
+
50
+ `fitDiagram`'s scale and anchor arguments are now one options object.
51
+ - 1b7af8d: `sampleTrack` and timeline keyframe tracks compute their values with blits, the same engine tweens and springs run on. A track of number arrays or numeric objects no longer needs an `interpolate`, and keys whose shapes differ throw. A `segmentCache` handed a different track or a replaced `keys` array rebuilds instead of answering for the old one. Edits to keys must go through `timeline.edit` (or a dropped `segmentCache`) to take effect, which numeric tracks used to get away without.
52
+ - @weasel-js/cursor@1.9.2
53
+ - @weasel-js/font@1.9.2
54
+ - @weasel-js/geom@1.9.2
55
+ - @weasel-js/gestures@1.9.2
56
+ - @weasel-js/history@1.9.2
57
+ - @weasel-js/modes@1.9.2
58
+ - @weasel-js/paint@1.9.2
59
+ - @weasel-js/quantity@1.9.2
60
+ - @weasel-js/react@1.9.2
61
+ - @weasel-js/registry@1.9.2
62
+ - @weasel-js/routing@1.9.2
63
+ - @weasel-js/select@1.9.2
64
+ - @weasel-js/text@1.9.2
65
+
3
66
  ## 1.9.1
4
67
 
5
68
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"createTimeline.js","names":["trackEnd","track","tracksEnd","explicit","max","tracks","createTimeline","duration","opts","offset","lastVirtual","playhead","prevPlayhead","done","live","cancelled","wantPaused","wantScale","subscribers","loopOpt","loopsLeft","lap","booker","createBooker","warnedUnbooked","caches","cacheFor","c","applySampled","st","v","sampleTrack","path","fireEvents","k","end","firstAfter","to","i","from","ev","report","onWrap","tick","virtualNow","laps","finished","scale","base","onCancel","seed","register","rearm","s","t","loop","fn","cb","err"],"sources":["../../../src/animation/timeline/createTimeline.ts"],"sourcesContent":["import type { AnimationHandle } from '../types';\nimport { createBooker, firstAfter } from './booking';\nimport { sampleTrack } from './sampleTrack';\nimport type { EventTrack, SampledTrack, TimelineEvent, TimelineHandle, TimelineOptions, Track } from './types';\n\n/** The animator's internal `register`, narrowed to what a timeline needs. */\nexport type TimelineRegister = (seed: {\n id: number;\n cancelKey?: string;\n /** Registers under an existing `cancelKey` without cancelling whoever holds\n * it. A revived timeline is the same animation, not a new claim on the key. */\n keepExisting?: boolean;\n /** `scale` is the effective time scale this frame advanced at, zero while\n * paused at any level. */\n tick: (virtualNow: number, scale?: number) => boolean;\n onCancel?: () => void;\n kind: 'timeline';\n label?: string;\n progress?: () => number;\n}) => AnimationHandle;\n\n/** Where a timeline reports its crossings and laps to `animator.watch`. Each\n * call site checks `watched` first, so an unwatched timeline builds nothing. */\nexport interface TimelineReport {\n readonly watched: boolean;\n /** `path` is a scratch stack the timeline goes on mutating; copy it to keep it. */\n fire(track: EventTrack, path: readonly number[], event: TimelineEvent, lateBy: number): void;\n lap(lap: number): void;\n}\n\n/** End time of a track: its last key/event, or a nested timeline's own end. */\nfunction trackEnd(track: Track): number {\n switch (track.kind) {\n case 'sampled': return track.keys.length ? track.keys[track.keys.length - 1].t : 0;\n case 'event': return track.events.length ? track.events[track.events.length - 1].t : 0;\n case 'timeline': return track.at + tracksEnd(track.timeline.tracks, track.timeline.duration);\n }\n}\n\nfunction tracksEnd(tracks: Track[], explicit?: number): number {\n if (explicit != null) return explicit;\n let max = 0;\n for (const t of tracks) max = Math.max(max, trackEnd(t));\n return max;\n}\n\n/**\n * Create a timeline as a registered animator animation.\n *\n * The playhead is NOT the entry's `virtualNow` directly — it is\n * `virtualNow + offset`, where `offset` is what `seek` and looping move. That\n * keeps seek and wrap-around entirely inside the timeline and needs no setter\n * on the animator's entry.\n *\n * A finished timeline leaves the animator's table, so `seek` and a\n * duration-extending `edit` re-register it. Staying registered instead would\n * hold a slot — and the rAF loop — open for every timeline ever created.\n */\nexport function createTimeline(\n register: TimelineRegister,\n id: number,\n opts: TimelineOptions,\n report?: TimelineReport,\n): TimelineHandle {\n let duration = tracksEnd(opts.tracks, opts.duration);\n let offset = 0;\n let lastVirtual = 0;\n let playhead = 0;\n let prevPlayhead = -Infinity;\n let done = false;\n let live = true;\n let cancelled = false;\n let wantPaused = opts.autoplay === false;\n let wantScale = 1;\n\n const subscribers = new Set<() => void>();\n\n const loopOpt = opts.loop ?? false;\n let loopsLeft = loopOpt === true ? Infinity : loopOpt === false ? 0 : loopOpt;\n let lap = 0;\n\n const booker = opts.booking\n ? createBooker(opts.booking, {\n tracks: opts.tracks,\n duration: () => duration,\n lapsLeft: () => loopsLeft,\n lap: () => lap,\n playhead: () => playhead,\n })\n : null;\n let warnedUnbooked = false;\n\n // Per-sampled-track interpolator-factory caches, dropped wholesale by `edit`.\n let caches = new WeakMap<object, Map<number, (u: number) => unknown>>();\n const cacheFor = (track: object): Map<number, (u: number) => unknown> => {\n let c = caches.get(track);\n if (!c) { c = new Map(); caches.set(track, c); }\n return c;\n };\n\n const applySampled = (tracks: Track[], t: number): void => {\n for (const track of tracks) {\n if (track.kind === 'sampled') {\n const st = track as SampledTrack<unknown>;\n const v = sampleTrack(st, t, cacheFor(st));\n if (v !== undefined) st.onTick(v);\n } else if (track.kind === 'timeline') {\n applySampled(track.timeline.tracks, t - track.at);\n }\n }\n };\n\n // Track indexes from the root down to the track being fired, for `report`.\n const path: number[] = [];\n const fireEvents = (tracks: Track[], from: number, to: number): void => {\n for (let k = 0; k < tracks.length; k += 1) {\n const track = tracks[k];\n if (track.kind === 'event') {\n const end = firstAfter(track.events, to);\n for (let i = firstAfter(track.events, from); i < end; i += 1) {\n const ev = track.events[i];\n ev.fire?.(to - ev.t);\n if (report?.watched) {\n path.push(k);\n report.fire(track, path, ev, to - ev.t);\n path.pop();\n }\n if (ev.book && !booker && !warnedUnbooked) {\n warnedUnbooked = true;\n console.warn('timeline: an event has `book` but the timeline has no `booking` clock; it never books.');\n }\n }\n } else if (track.kind === 'timeline') {\n path.push(k);\n fireEvents(track.timeline.tracks, from - track.at, to - track.at);\n path.pop();\n }\n }\n };\n\n // Flush the outgoing pass's tail before re-arming, or an event between the\n // last tick and `duration` is dropped whenever a frame straddles the seam.\n const onWrap = (): void => {\n fireEvents(opts.tracks, prevPlayhead, duration);\n prevPlayhead = -Infinity;\n if (report?.watched) report.lap(lap);\n };\n\n const tick = (virtualNow: number, scale?: number): boolean => {\n lastVirtual = virtualNow;\n playhead = virtualNow + offset;\n\n // A far seek can skip a billion laps of a short duration, so an endless\n // loop takes them in one modulo rather than one iteration each.\n if (duration > 0 && playhead >= duration) {\n if (loopsLeft === Infinity) {\n const laps = Math.floor(playhead / duration);\n offset -= laps * duration;\n playhead -= laps * duration;\n lap += laps;\n onWrap();\n } else {\n while (playhead >= duration && loopsLeft > 0) {\n loopsLeft -= 1;\n offset -= duration;\n playhead -= duration;\n lap += 1;\n onWrap();\n }\n }\n }\n\n const finished = playhead >= duration;\n playhead = Math.min(playhead, duration);\n\n // Sampled before fired, so a handler reads the current frame's values.\n applySampled(opts.tracks, playhead);\n fireEvents(opts.tracks, prevPlayhead, playhead);\n prevPlayhead = playhead;\n booker?.tick(virtualNow, scale ?? (base.isPaused() ? 0 : base.timeScale()));\n\n if (finished && !done) { done = true; opts.onDone?.(); }\n // Re-read: `onDone` may have seeked back or extended the duration, and\n // retiring the entry would strand the replay its own handler just started.\n if (playhead < duration) return false;\n live = false;\n return true;\n };\n\n // Every cancel path inside the animator routes through here, so a timeline\n // cancelled by key or by `cancelAll` is as dead as one cancelled by hand.\n const onCancel = (): void => { cancelled = true; live = false; booker?.retract(); };\n\n const progress = (): number => (duration > 0 ? playhead / duration : 1);\n const seed = { id, cancelKey: opts.cancelKey, tick, onCancel, kind: 'timeline' as const, label: opts.label, progress };\n const base = register(seed);\n\n // A paused entry's scale is zero, so `virtualNow` never advances and the\n // playhead holds at 0 until the consumer resumes.\n if (opts.autoplay === false) base.pause();\n\n /** Put a finished timeline back on the animator's table. A re-registered\n * entry's `virtualNow` restarts at 0, so `offset` carries the playhead. */\n const rearm = (): void => {\n if (cancelled || playhead >= duration) return;\n done = false;\n if (live) return;\n booker?.rearm();\n lastVirtual = 0;\n offset = playhead;\n live = true;\n register({ ...seed, keepExisting: true });\n if (wantPaused) base.pause();\n base.setTimeScale(wantScale);\n };\n\n return {\n ...base,\n // Not just `onCancel`: cancelling a finished timeline never reaches the\n // animator, and must still keep a later seek from reviving it.\n cancel() { onCancel(); base.cancel(); },\n // Playback intent is tracked here, not read back from the entry: a revived\n // one is a fresh registration, and defaults to running at scale 1.\n pause() { wantPaused = true; base.pause(); booker?.retract(); },\n resume() { wantPaused = false; base.resume(); },\n setTimeScale(s) { wantScale = s; base.setTimeScale(s); },\n timeScale: () => (live ? base.timeScale() : wantScale),\n isPaused: () => (live ? base.isPaused() : wantPaused),\n seek(t) {\n booker?.retract();\n offset = t - lastVirtual;\n playhead = t;\n prevPlayhead = t;\n booker?.seek();\n rearm();\n },\n time: () => playhead,\n duration: () => duration,\n tracks: () => opts.tracks,\n setLoop(loop) {\n loopsLeft = loop === true ? Infinity : loop === false ? 0 : loop;\n booker?.retract();\n },\n loop: () => (loopsLeft === Infinity ? true : loopsLeft === 0 ? false : loopsLeft),\n edit(fn) {\n fn();\n caches = new WeakMap();\n duration = tracksEnd(opts.tracks, opts.duration);\n booker?.retract();\n rearm();\n for (const cb of subscribers) {\n try { cb(); } catch (err) { console.error('timeline: subscriber threw', err); }\n }\n },\n subscribe(cb) {\n subscribers.add(cb);\n return () => { subscribers.delete(cb); };\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"createTimeline.js","names":["trackEnd","track","tracksEnd","explicit","max","tracks","createTimeline","duration","opts","offset","lastVirtual","playhead","prevPlayhead","done","live","cancelled","wantPaused","wantScale","subscribers","loopOpt","loopsLeft","lap","booker","createBooker","warnedUnbooked","caches","cacheFor","c","applySampled","st","v","sampleTrack","path","fireEvents","k","end","firstAfter","to","i","from","ev","report","onWrap","tick","virtualNow","laps","finished","scale","base","onCancel","seed","register","rearm","s","t","loop","fn","cb","err"],"sources":["../../../src/animation/timeline/createTimeline.ts"],"sourcesContent":["import type { AnimationHandle } from '../types';\nimport { createBooker, firstAfter } from './booking';\nimport { sampleTrack } from './sampleTrack';\nimport type { EventTrack, SampledTrack, TimelineEvent, TimelineHandle, TimelineOptions, Track } from './types';\n\n/** The animator's internal `register`, narrowed to what a timeline needs. */\nexport type TimelineRegister = (seed: {\n id: number;\n cancelKey?: string;\n /** Registers under an existing `cancelKey` without cancelling whoever holds\n * it. A revived timeline is the same animation, not a new claim on the key. */\n keepExisting?: boolean;\n /** `scale` is the effective time scale this frame advanced at, zero while\n * paused at any level. */\n tick: (virtualNow: number, scale?: number) => boolean;\n onCancel?: () => void;\n kind: 'timeline';\n label?: string;\n progress?: () => number;\n}) => AnimationHandle;\n\n/** Where a timeline reports its crossings and laps to `animator.watch`. Each\n * call site checks `watched` first, so an unwatched timeline builds nothing. */\nexport interface TimelineReport {\n readonly watched: boolean;\n /** `path` is a scratch stack the timeline goes on mutating; copy it to keep it. */\n fire(track: EventTrack, path: readonly number[], event: TimelineEvent, lateBy: number): void;\n lap(lap: number): void;\n}\n\n/** End time of a track: its last key/event, or a nested timeline's own end. */\nfunction trackEnd(track: Track): number {\n switch (track.kind) {\n case 'sampled': return track.keys.length ? track.keys[track.keys.length - 1].t : 0;\n case 'event': return track.events.length ? track.events[track.events.length - 1].t : 0;\n case 'timeline': return track.at + tracksEnd(track.timeline.tracks, track.timeline.duration);\n }\n}\n\nfunction tracksEnd(tracks: Track[], explicit?: number): number {\n if (explicit != null) return explicit;\n let max = 0;\n for (const t of tracks) max = Math.max(max, trackEnd(t));\n return max;\n}\n\n/**\n * Create a timeline as a registered animator animation.\n *\n * The playhead is NOT the entry's `virtualNow` directly — it is\n * `virtualNow + offset`, where `offset` is what `seek` and looping move. That\n * keeps seek and wrap-around entirely inside the timeline and needs no setter\n * on the animator's entry.\n *\n * A finished timeline leaves the animator's table, so `seek` and a\n * duration-extending `edit` re-register it. Staying registered instead would\n * hold a slot — and the rAF loop — open for every timeline ever created.\n */\nexport function createTimeline(\n register: TimelineRegister,\n id: number,\n opts: TimelineOptions,\n report?: TimelineReport,\n): TimelineHandle {\n let duration = tracksEnd(opts.tracks, opts.duration);\n let offset = 0;\n let lastVirtual = 0;\n let playhead = 0;\n let prevPlayhead = -Infinity;\n let done = false;\n let live = true;\n let cancelled = false;\n let wantPaused = opts.autoplay === false;\n let wantScale = 1;\n\n const subscribers = new Set<() => void>();\n\n const loopOpt = opts.loop ?? false;\n let loopsLeft = loopOpt === true ? Infinity : loopOpt === false ? 0 : loopOpt;\n let lap = 0;\n\n const booker = opts.booking\n ? createBooker(opts.booking, {\n tracks: opts.tracks,\n duration: () => duration,\n lapsLeft: () => loopsLeft,\n lap: () => lap,\n playhead: () => playhead,\n })\n : null;\n let warnedUnbooked = false;\n\n // Per-sampled-track sampling caches, dropped wholesale by `edit`.\n let caches = new WeakMap<object, Map<number, (u: number) => unknown>>();\n const cacheFor = (track: object): Map<number, (u: number) => unknown> => {\n let c = caches.get(track);\n if (!c) { c = new Map(); caches.set(track, c); }\n return c;\n };\n\n const applySampled = (tracks: Track[], t: number): void => {\n for (const track of tracks) {\n if (track.kind === 'sampled') {\n const st = track as SampledTrack<unknown>;\n const v = sampleTrack(st, t, cacheFor(st));\n if (v !== undefined) st.onTick(v);\n } else if (track.kind === 'timeline') {\n applySampled(track.timeline.tracks, t - track.at);\n }\n }\n };\n\n // Track indexes from the root down to the track being fired, for `report`.\n const path: number[] = [];\n const fireEvents = (tracks: Track[], from: number, to: number): void => {\n for (let k = 0; k < tracks.length; k += 1) {\n const track = tracks[k];\n if (track.kind === 'event') {\n const end = firstAfter(track.events, to);\n for (let i = firstAfter(track.events, from); i < end; i += 1) {\n const ev = track.events[i];\n ev.fire?.(to - ev.t);\n if (report?.watched) {\n path.push(k);\n report.fire(track, path, ev, to - ev.t);\n path.pop();\n }\n if (ev.book && !booker && !warnedUnbooked) {\n warnedUnbooked = true;\n console.warn('timeline: an event has `book` but the timeline has no `booking` clock; it never books.');\n }\n }\n } else if (track.kind === 'timeline') {\n path.push(k);\n fireEvents(track.timeline.tracks, from - track.at, to - track.at);\n path.pop();\n }\n }\n };\n\n // Flush the outgoing pass's tail before re-arming, or an event between the\n // last tick and `duration` is dropped whenever a frame straddles the seam.\n const onWrap = (): void => {\n fireEvents(opts.tracks, prevPlayhead, duration);\n prevPlayhead = -Infinity;\n if (report?.watched) report.lap(lap);\n };\n\n const tick = (virtualNow: number, scale?: number): boolean => {\n lastVirtual = virtualNow;\n playhead = virtualNow + offset;\n\n // A far seek can skip a billion laps of a short duration, so an endless\n // loop takes them in one modulo rather than one iteration each.\n if (duration > 0 && playhead >= duration) {\n if (loopsLeft === Infinity) {\n const laps = Math.floor(playhead / duration);\n offset -= laps * duration;\n playhead -= laps * duration;\n lap += laps;\n onWrap();\n } else {\n while (playhead >= duration && loopsLeft > 0) {\n loopsLeft -= 1;\n offset -= duration;\n playhead -= duration;\n lap += 1;\n onWrap();\n }\n }\n }\n\n const finished = playhead >= duration;\n playhead = Math.min(playhead, duration);\n\n // Sampled before fired, so a handler reads the current frame's values.\n applySampled(opts.tracks, playhead);\n fireEvents(opts.tracks, prevPlayhead, playhead);\n prevPlayhead = playhead;\n booker?.tick(virtualNow, scale ?? (base.isPaused() ? 0 : base.timeScale()));\n\n if (finished && !done) { done = true; opts.onDone?.(); }\n // Re-read: `onDone` may have seeked back or extended the duration, and\n // retiring the entry would strand the replay its own handler just started.\n if (playhead < duration) return false;\n live = false;\n return true;\n };\n\n // Every cancel path inside the animator routes through here, so a timeline\n // cancelled by key or by `cancelAll` is as dead as one cancelled by hand.\n const onCancel = (): void => { cancelled = true; live = false; booker?.retract(); };\n\n const progress = (): number => (duration > 0 ? playhead / duration : 1);\n const seed = { id, cancelKey: opts.cancelKey, tick, onCancel, kind: 'timeline' as const, label: opts.label, progress };\n const base = register(seed);\n\n // A paused entry's scale is zero, so `virtualNow` never advances and the\n // playhead holds at 0 until the consumer resumes.\n if (opts.autoplay === false) base.pause();\n\n /** Put a finished timeline back on the animator's table. A re-registered\n * entry's `virtualNow` restarts at 0, so `offset` carries the playhead. */\n const rearm = (): void => {\n if (cancelled || playhead >= duration) return;\n done = false;\n if (live) return;\n booker?.rearm();\n lastVirtual = 0;\n offset = playhead;\n live = true;\n register({ ...seed, keepExisting: true });\n if (wantPaused) base.pause();\n base.setTimeScale(wantScale);\n };\n\n return {\n ...base,\n // Not just `onCancel`: cancelling a finished timeline never reaches the\n // animator, and must still keep a later seek from reviving it.\n cancel() { onCancel(); base.cancel(); },\n // Playback intent is tracked here, not read back from the entry: a revived\n // one is a fresh registration, and defaults to running at scale 1.\n pause() { wantPaused = true; base.pause(); booker?.retract(); },\n resume() { wantPaused = false; base.resume(); },\n setTimeScale(s) { wantScale = s; base.setTimeScale(s); },\n timeScale: () => (live ? base.timeScale() : wantScale),\n isPaused: () => (live ? base.isPaused() : wantPaused),\n seek(t) {\n booker?.retract();\n offset = t - lastVirtual;\n playhead = t;\n prevPlayhead = t;\n booker?.seek();\n rearm();\n },\n time: () => playhead,\n duration: () => duration,\n tracks: () => opts.tracks,\n setLoop(loop) {\n loopsLeft = loop === true ? Infinity : loop === false ? 0 : loop;\n booker?.retract();\n },\n loop: () => (loopsLeft === Infinity ? true : loopsLeft === 0 ? false : loopsLeft),\n edit(fn) {\n fn();\n caches = new WeakMap();\n duration = tracksEnd(opts.tracks, opts.duration);\n booker?.retract();\n rearm();\n for (const cb of subscribers) {\n try { cb(); } catch (err) { console.error('timeline: subscriber threw', err); }\n }\n },\n subscribe(cb) {\n subscribers.add(cb);\n return () => { subscribers.delete(cb); };\n },\n };\n}\n"],"mappings":"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"}
@@ -1,29 +1,71 @@
1
- import { resolveEasing as e } from "@weasel-js/geom";
1
+ import { axesOf as e } from "../engine/axes.js";
2
+ import { resolveEasing as t } from "@weasel-js/geom";
3
+ import { keys as n } from "@msb235/blits";
2
4
  //#region src/animation/timeline/sampleTrack.ts
3
- function t(e, t) {
4
- let n = 0, r = e.length - 1, i = -1;
5
- for (; n <= r;) {
6
- let a = n + r >> 1;
7
- e[a].t <= t ? (i = a, n = a + 1) : r = a - 1;
5
+ var r = {}, i = -1;
6
+ function a(r, i) {
7
+ let { keys: a } = r, o = Infinity, s = -Infinity, c = /* @__PURE__ */ new Map();
8
+ for (let e of a) o = Math.min(o, e.t), s = Math.max(s, e.t), c.set(e.t, e.value);
9
+ let l = s - o, u = {
10
+ track: r,
11
+ keys: a,
12
+ patch: null,
13
+ t0: o,
14
+ span: l,
15
+ exact: c,
16
+ first: a[0].value,
17
+ last: a[a.length - 1].value,
18
+ read: (e) => e
19
+ };
20
+ if (l <= 0) return u;
21
+ let d = (e) => e.easing ? t(e.easing) : void 0, { interpolator: f, interpolate: p } = r;
22
+ if (f || p) {
23
+ let e = a.map((e, t) => ({
24
+ i: t,
25
+ value: e.value
26
+ })), t = (e, t, n) => {
27
+ if (f) {
28
+ let r = i?.get(t.i);
29
+ return r || (r = f(e.value, t.value), i?.set(t.i, r)), {
30
+ i: -1,
31
+ value: r(n)
32
+ };
33
+ }
34
+ return {
35
+ i: -1,
36
+ value: p(e.value, t.value, n)
37
+ };
38
+ };
39
+ return u.patch = n(1, a.map((t, n) => ({
40
+ at: (t.t - o) / l,
41
+ delta: { v: e[n] },
42
+ ease: d(t)
43
+ })), { lerpBy: () => t }), u.read = (e) => e.value, u;
8
44
  }
9
- return i;
45
+ let m = e(a[0].value);
46
+ if (!m) return u;
47
+ for (let t of a) if (e(t.value)?.shape !== m.shape) throw Error(`sampleTrack: every key needs the shape of the first (${m.shape})`);
48
+ let h = m.shape === "number";
49
+ return u.patch = n(1, a.map((e) => ({
50
+ at: (e.t - o) / l,
51
+ delta: { v: h ? e.value : m.to(e.value) },
52
+ ease: d(e)
53
+ }))), h || (u.read = (e) => m.from(e)), u;
10
54
  }
11
- function n(n, r, i) {
12
- let { keys: a } = n;
13
- if (a.length === 0) return;
14
- let o = t(a, r);
15
- if (o < 0) return a[0].value;
16
- if (o >= a.length - 1) return a[a.length - 1].value;
17
- let s = a[o], c = a[o + 1], l = c.t - s.t, u = l <= 0 ? 1 : (r - s.t) / l, d = c.easing ? e(c.easing)(u) : u;
18
- if (n.interpolator) {
19
- let e = i?.get(o + 1);
20
- return e || (e = n.interpolator(s.value, c.value), i?.set(o + 1, e)), e(d);
55
+ function o(e, t, n) {
56
+ if (e.keys.length === 0) return;
57
+ let o = n, s = o?.get(i);
58
+ (!s || s.track !== e || s.keys !== e.keys) && (o?.clear(), s = a(e, n), o?.set(i, s));
59
+ let c = s.exact.get(t);
60
+ if (c !== void 0 || s.exact.has(t)) return c;
61
+ if (!s.patch) {
62
+ if (t < s.t0) return s.first;
63
+ if (t >= s.t0 + s.span) return s.last;
64
+ throw Error("sampleTrack: interpolate or interpolator is required for non-numeric keyframe values");
21
65
  }
22
- if (n.interpolate) return n.interpolate(s.value, c.value, d);
23
- if (typeof s.value == "number" && typeof c.value == "number") return s.value + (c.value - s.value) * d;
24
- throw Error("sampleTrack: interpolate or interpolator is required for non-numeric keyframe values");
66
+ return s.read(s.patch.at((t - s.t0) / s.span, 0, r).v);
25
67
  }
26
68
  //#endregion
27
- export { n as sampleTrack };
69
+ export { o as sampleTrack };
28
70
 
29
71
  //# sourceMappingURL=sampleTrack.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sampleTrack.js","names":["floorKeyIndex","lo","hi","keys","found","mid","sampleTrack","track","i","t","a","b","span","raw","u","resolveEasing","fn","segmentCache"],"sources":["../../../src/animation/timeline/sampleTrack.ts"],"sourcesContent":["import { resolveEasing } from '@weasel-js/geom';\nimport type { SampledTrack } from './types';\n\n/** Index of the last key at or before `t`, or -1 when `t` precedes all keys.\n * Binary search: tracks are sorted and may be long. */\nfunction floorKeyIndex<T>(keys: SampledTrack<T>['keys'], t: number): number {\n let lo = 0;\n let hi = keys.length - 1;\n let found = -1;\n while (lo <= hi) {\n const mid = (lo + hi) >> 1;\n if (keys[mid].t <= t) {\n found = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n return found;\n}\n\n/**\n * Sample a track at `t`. Pure: no state, no side effects, safe to call for any\n * `t` in any order — which is what makes scrubbing free.\n *\n * `segmentCache` memoizes `interpolator` factories by the index of the segment's\n * later key. Callers that mutate keys must drop the cache; `createTimeline`\n * drops it wholesale on `edit`.\n */\nexport function sampleTrack<T>(\n track: SampledTrack<T>,\n t: number,\n segmentCache?: Map<number, (u: number) => T>,\n): T | undefined {\n const { keys } = track;\n if (keys.length === 0) return undefined;\n\n const i = floorKeyIndex(keys, t);\n if (i < 0) return keys[0].value;\n if (i >= keys.length - 1) return keys[keys.length - 1].value;\n\n const a = keys[i];\n const b = keys[i + 1];\n const span = b.t - a.t;\n const raw = span <= 0 ? 1 : (t - a.t) / span;\n // `easing` belongs to the key being approached, so `b` supplies it.\n const u = b.easing ? resolveEasing(b.easing)(raw) : raw;\n\n if (track.interpolator) {\n let fn = segmentCache?.get(i + 1);\n if (!fn) {\n fn = track.interpolator(a.value, b.value);\n segmentCache?.set(i + 1, fn);\n }\n return fn(u);\n }\n if (track.interpolate) return track.interpolate(a.value, b.value, u);\n\n if (typeof a.value === 'number' && typeof b.value === 'number') {\n return ((a.value as number) + ((b.value as number) - (a.value as number)) * u) as unknown as T;\n }\n throw new Error('sampleTrack: interpolate or interpolator is required for non-numeric keyframe values');\n}\n"],"mappings":";;AAKA,SAASA,EAAiB,GAA+B,GAAmB;CAC1E,IAAIC,IAAK,GACLC,IAAKC,EAAK,SAAS,GACnBC,IAAQ;CACZ,OAAOH,KAAMC,IAAI;EACf,IAAMG,IAAOJ,IAAKC,KAAO;EACzB,AAAIC,EAAKE,EAAI,CAAC,KAAK,KACjB,IAAQA,GACR,IAAKA,IAAM,KAEX,IAAKA,IAAM;CAEf;CACA,OAAOD;AACT;AAUA,SAAgBE,EACd,GACA,GACA,GACe;CACf,IAAM,EAAE,YAASC;CACjB,IAAIJ,EAAK,WAAW,GAAG;CAEvB,IAAMK,IAAIR,EAAcG,GAAMM,CAAC;CAC/B,IAAID,IAAI,GAAG,OAAOL,EAAK,EAAE,CAAC;CAC1B,IAAIK,KAAKL,EAAK,SAAS,GAAG,OAAOA,EAAKA,EAAK,SAAS,EAAE,CAAC;CAEvD,IAAMO,IAAIP,EAAKK,IACTG,IAAIR,EAAKK,IAAI,IACbI,IAAOD,EAAE,IAAID,EAAE,GACfG,IAAMD,KAAQ,IAAI,KAAKH,IAAIC,EAAE,KAAKE,GAElCE,IAAIH,EAAE,SAASI,EAAcJ,EAAE,MAAM,CAAC,CAACE,CAAG,IAAIA;CAEpD,IAAIN,EAAM,cAAc;EACtB,IAAIS,IAAKC,GAAc,IAAIT,IAAI,CAAC;EAKhC,OAJKQ,MACH,IAAKT,EAAM,aAAaG,EAAE,OAAOC,EAAE,KAAK,GACxCM,GAAc,IAAIT,IAAI,GAAGQ,CAAE,IAEtBA,EAAGF,CAAC;CACb;CACA,IAAIP,EAAM,aAAa,OAAOA,EAAM,YAAYG,EAAE,OAAOC,EAAE,OAAOG,CAAC;CAEnE,IAAI,OAAOJ,EAAE,SAAU,YAAY,OAAOC,EAAE,SAAU,UACpD,OAASD,EAAE,SAAqBC,EAAE,QAAoBD,EAAE,SAAoBI;CAE9E,MAAU,MAAM,sFAAsF;AACxG"}
1
+ {"version":3,"file":"sampleTrack.js","names":["keys","keysPatch","NO_SETTING","BUILT","build","keys$1","track","t0","t1","exact","k","span","out","v","ease","resolveEasing","interpolator","interpolate","boxes","lerp","fn","cache","b","a","u","keys","i","axes","axesOf","scalar","sampleTrack","segmentCache","hit"],"sources":["../../../src/animation/timeline/sampleTrack.ts"],"sourcesContent":["import { keys as keysPatch, type Patch, type Setting } from '@msb235/blits';\nimport { resolveEasing } from '@weasel-js/geom';\nimport { axesOf } from '../engine/axes';\nimport type { SampledTrack } from './types';\n\ntype Out = { v: unknown };\n/** What an interpolated track's stops carry: blits finds the segment and eases it, and the\n * track's own function makes the value. */\ninterface Box { i: number; value: unknown }\n\ninterface Built {\n /** What it was built from, so a cache handed another track, or new keys, rebuilds. */\n track: object;\n keys: readonly unknown[];\n /** Null where there is nothing to interpolate: one key time, or values with no way to lerp. */\n patch: Patch<number, Out, void> | null;\n t0: number;\n span: number;\n /** Every key's time to the value sampling exactly there returns: the later of keys sharing a\n * time, and the key's own value where blits would reach it as `a + (b - a) * ease(1)`. */\n exact: Map<number, unknown>;\n first: unknown;\n last: unknown;\n read: (v: unknown) => unknown;\n}\n\nconst NO_SETTING = {} as Setting<void>;\n/** Where a cache holds its track's `Built`; segment indexes start at 1. A `WeakMap` keyed on the\n * cache instead cost about a fifth of every sample. */\nconst BUILT = -1;\n\nfunction build<T>(track: SampledTrack<T>, cache: Map<number, (u: number) => T> | undefined): Built {\n const { keys } = track;\n let t0 = Infinity;\n let t1 = -Infinity;\n const exact = new Map<number, unknown>();\n for (const k of keys) {\n t0 = Math.min(t0, k.t);\n t1 = Math.max(t1, k.t);\n exact.set(k.t, k.value);\n }\n const span = t1 - t0;\n const out: Built = { track, keys, patch: null, t0, span, exact, first: keys[0].value, last: keys[keys.length - 1].value, read: (v) => v };\n if (span <= 0) return out;\n\n const ease = (k: (typeof keys)[number]) => (k.easing ? resolveEasing(k.easing) : undefined);\n const { interpolator, interpolate } = track;\n if (interpolator || interpolate) {\n const boxes: Box[] = keys.map((k, i) => ({ i, value: k.value }));\n const lerp = (a: Box, b: Box, u: number): Box => {\n if (interpolator) {\n let fn = cache?.get(b.i);\n if (!fn) {\n fn = interpolator(a.value as T, b.value as T);\n cache?.set(b.i, fn);\n }\n return { i: -1, value: fn(u) };\n }\n return { i: -1, value: interpolate!(a.value as T, b.value as T, u) };\n };\n out.patch = keysPatch<number, Out>(\n 1,\n keys.map((k, i) => ({ at: (k.t - t0) / span, delta: { v: boxes[i] }, ease: ease(k) })),\n { lerpBy: () => lerp as (a: never, b: never, u: number) => unknown },\n );\n out.read = (v) => (v as Box).value;\n return out;\n }\n\n const axes = axesOf(keys[0].value);\n if (!axes) return out;\n for (const k of keys) {\n if (axesOf(k.value)?.shape !== axes.shape) {\n throw new Error(`sampleTrack: every key needs the shape of the first (${axes.shape})`);\n }\n }\n const scalar = axes.shape === 'number';\n out.patch = keysPatch<number, Out>(\n 1,\n keys.map((k) => ({ at: (k.t - t0) / span, delta: { v: scalar ? k.value : axes.to(k.value) }, ease: ease(k) })),\n );\n if (!scalar) out.read = (v) => axes.from(v as number[]);\n return out;\n}\n\n/**\n * Sample a track at `t`. Pure: no state, no side effects, safe to call for any\n * `t` in any order — which is what makes scrubbing free.\n *\n * `segmentCache` holds what sampling builds from the keys: the track's blits\n * patch and its `interpolator` factories. Handed another track or a replaced\n * `keys` array, it rebuilds; a key edited in place needs the cache dropped,\n * which `createTimeline` does on `edit`. With no cache, every call builds afresh.\n */\nexport function sampleTrack<T>(\n track: SampledTrack<T>,\n t: number,\n segmentCache?: Map<number, (u: number) => T>,\n): T | undefined {\n if (track.keys.length === 0) return undefined;\n const cache = segmentCache as Map<number, unknown> | undefined;\n let b = cache?.get(BUILT) as Built | undefined;\n if (!b || b.track !== track || b.keys !== track.keys) {\n cache?.clear();\n b = build(track, segmentCache);\n cache?.set(BUILT, b);\n }\n const hit = b.exact.get(t);\n if (hit !== undefined || b.exact.has(t)) return hit as T;\n if (!b.patch) {\n if (t < b.t0) return b.first as T;\n if (t >= b.t0 + b.span) return b.last as T;\n throw new Error('sampleTrack: interpolate or interpolator is required for non-numeric keyframe values');\n }\n return b.read(b.patch.at((t - b.t0) / b.span, 0, NO_SETTING).v) as T;\n}\n"],"mappings":";;;;AA0BA,IAAME,IAAa,CAAC,GAGdC,IAAQ;AAEd,SAASC,EAAS,GAAwB,GAAyD;CACjG,IAAM,EAAE,MAAAC,MAASC,GACbC,IAAK,UACLC,IAAK,WACHC,oBAAQ,IAAI,IAAqB;CACvC,KAAK,IAAMC,KAAKV,GAGd,AAFA,IAAK,KAAK,IAAIO,GAAIG,EAAE,CAAC,GACrB,IAAK,KAAK,IAAIF,GAAIE,EAAE,CAAC,GACrBD,EAAM,IAAIC,EAAE,GAAGA,EAAE,KAAK;CAExB,IAAMC,IAAOH,IAAKD,GACZK,IAAa;EAAE;EAAO,MAAAP;EAAM,OAAO;EAAM;EAAI;EAAM;EAAO,OAAOL,EAAK,EAAE,CAAC;EAAO,MAAMA,EAAKA,EAAK,SAAS,EAAE,CAAC;EAAO,OAAO,MAAMa;CAAE;CACxI,IAAIF,KAAQ,GAAG,OAAOC;CAEtB,IAAME,KAAQ,MAA8BJ,EAAE,SAASK,EAAcL,EAAE,MAAM,IAAI,KAAA,GAC3E,EAAE,iBAAc,mBAAgBJ;CACtC,IAAIU,KAAgBC,GAAa;EAC/B,IAAMC,IAAelB,EAAK,KAAK,GAAG,OAAO;GAAE;GAAG,OAAOU,EAAE;EAAM,EAAE,GACzDS,KAAQ,GAAQ,GAAQ,MAAmB;GAC/C,IAAIH,GAAc;IAChB,IAAII,IAAKC,GAAO,IAAIC,EAAE,CAAC;IAKvB,OAJKF,MACH,IAAKJ,EAAaO,EAAE,OAAYD,EAAE,KAAU,GAC5CD,GAAO,IAAIC,EAAE,GAAGF,CAAE,IAEb;KAAE,GAAG;KAAI,OAAOA,EAAGI,CAAC;IAAE;GAC/B;GACA,OAAO;IAAE,GAAG;IAAI,OAAOP,EAAaM,EAAE,OAAYD,EAAE,OAAYE,CAAC;GAAE;EACrE;EAOA,OANA,EAAI,QAAQvB,EACV,GACAD,EAAK,KAAK,GAAG,OAAO;GAAE,KAAKU,EAAE,IAAIH,KAAMI;GAAM,OAAO,EAAE,GAAGO,EAAMQ,GAAG;GAAG,MAAMZ,EAAKJ,CAAC;EAAE,EAAE,GACrF,EAAE,cAAcS,EAAmD,CACrE,GACA,EAAI,QAAQ,MAAON,EAAU,OACtBD;CACT;CAEA,IAAMe,IAAOC,EAAO5B,EAAK,EAAE,CAAC,KAAK;CACjC,IAAI,CAAC2B,GAAM,OAAOf;CAClB,KAAK,IAAMF,KAAKV,GACd,IAAI4B,EAAOlB,EAAE,KAAK,CAAC,EAAE,UAAUiB,EAAK,OAClC,MAAU,MAAM,wDAAwDA,EAAK,MAAM,EAAE;CAGzF,IAAME,IAASF,EAAK,UAAU;CAM9B,OALA,EAAI,QAAQ1B,EACV,GACAD,EAAK,KAAK,OAAO;EAAE,KAAKU,EAAE,IAAIH,KAAMI;EAAM,OAAO,EAAE,GAAGkB,IAASnB,EAAE,QAAQiB,EAAK,GAAGjB,EAAE,KAAK,EAAE;EAAG,MAAMI,EAAKJ,CAAC;CAAE,EAAE,CAC/G,GACKmB,MAAQ,EAAI,QAAQ,MAAMF,EAAK,KAAKd,CAAa,IAC/CD;AACT;AAWA,SAAgBkB,EACd,GACA,GACA,GACe;CACf,IAAIxB,EAAM,KAAK,WAAW,GAAG;CAC7B,IAAMe,IAAQU,GACVT,IAAID,GAAO,IAAIlB,CAAK;CACxB,CAAI,CAACmB,KAAKA,EAAE,UAAUhB,KAASgB,EAAE,SAAShB,EAAM,UAC9Ce,GAAO,MAAM,GACb,IAAIjB,EAAME,GAAOyB,CAAY,GAC7BV,GAAO,IAAIlB,GAAOmB,CAAC;CAErB,IAAMU,IAAMV,EAAE,MAAM,IAAI,CAAC;CACzB,IAAIU,MAAQ,KAAA,KAAaV,EAAE,MAAM,IAAI,CAAC,GAAG,OAAOU;CAChD,IAAI,CAACV,EAAE,OAAO;EACZ,IAAI,IAAIA,EAAE,IAAI,OAAOA,EAAE;EACvB,IAAI,KAAKA,EAAE,KAAKA,EAAE,MAAM,OAAOA,EAAE;EACjC,MAAU,MAAM,sFAAsF;CACxG;CACA,OAAOA,EAAE,KAAKA,EAAE,MAAM,IAAI,IAAIA,EAAE,MAAMA,EAAE,MAAM,GAAGpB,CAAU,CAAC,CAAC,CAAC;AAChE"}
@@ -2533,9 +2533,11 @@ interface Keyframe<T> {
2533
2533
  interface SampledTrack<T> {
2534
2534
  kind: 'sampled';
2535
2535
  label?: string;
2536
- /** Sorted ascending by `t`. `sampleTrack` assumes this and does not sort. */
2536
+ /** Sorted ascending by `t`. Of keys sharing a time, the last in the array is
2537
+ * the value at exactly that time. */
2537
2538
  keys: Keyframe<T>[];
2538
- /** Required when T is not `number`; defaults to numeric lerp otherwise. */
2539
+ /** Required unless every key's value is a number, a number array or a plain
2540
+ * object of numbers, all of one shape; those are lerped field by field. */
2539
2541
  interpolate?: Interpolate<T>;
2540
2542
  /** Built once per segment and cached. Takes precedence over `interpolate`. */
2541
2543
  interpolator?: InterpolatorFactory<T>;
@@ -4509,4 +4511,4 @@ declare function vignette({ amount, feather }: {
4509
4511
  feather?: number;
4510
4512
  }): Effect[];
4511
4513
 
4512
- export { type SvgIngestOptions as $, type PoseComposition as A, type PoseAdapter as B, type CullRect as C, type DrawCommand as D, type Effect as E, type GestureLifecycleOptions as F, type GroupDrawCommand as G, type SliceDep as H, IDENTITY_COLOR_MATRIX as I, type ToolsApi as J, type Tool as K, type Contribution as L, type LayerHit as M, type Node as N, type ChromeState as O, type PathDrawCommand as P, type VertexColorChannel as Q, type RenderOptions as R, SPRITE_STRIDE as S, type TextDrawCommand as T, type LayerGroup as U, type ViewAnimationOptions as V, WeaselRenderer as W, type InsertExtras as X, type SerializedScene as Y, type GeometryProjection as Z, type ContentHandlerEntry as _, type ImageDrawCommand as a, type LayoutFrame as a$, type AnyTool as a0, type PanBounds as a1, type Animator as a2, ColorOverrideRegistry as a3, type UseSceneOptions as a4, type SceneRegistry as a5, type RegisteredOp as a6, type PoseOverrides as a7, type DerivePoseFn as a8, type DerivePathFn as a9, type ColorOverride as aA, type ColorOverrideFn as aB, type ContainerNode as aC, type ContributionChrome as aD, type CreateOutlinesResult as aE, type CustomPaintContext as aF, type DecayLoopConfig as aG, type DecayOptions as aH, type EditAnchorsDep as aI, type EventBooking as aJ, type EventBookingHandle as aK, type EventTrack as aL, type FitViewToBoundsOptions as aM, type HitTestView as aN, IDENTITY_POSE_COMPOSITION as aO, type IngestCtx as aP, type IngestionDep as aQ, type InsertDep as aR, type Interpolate as aS, type InterpolatorFactory as aT, type Keyframe as aU, type LassoSelectDep as aV, type LayerCommandCache as aW, type LayerDrawFailure as aX, type LayerDrawSpan as aY, type LayerRecord as aZ, type LayoutDep as a_, type DerivedDep as aa, type ViewportDims as ab, type ViewApi as ac, type Affordance as ad, type AffordanceRegion as ae, type AffordanceBinding as af, type CommonAffordanceScratch as ag, type BooleansAdapter as ah, type CreateOutlinesAdapter as ai, type UseAnimatorOptions as aj, type AnimationHandle as ak, type SampledTrack as al, type SceneArrivalHandler as am, type AddLayerSpec as an, type AddNodeSpec as ao, type AnimateToBoundsOptions as ap, type AnimationInfo as aq, type AnimationKind as ar, type AnimatorEvent as as, type AnimatorListener as at, type AreaSelectDep as au, type BooleanOp as av, type BooleanOpResult as aw, type ClipFromPoseFn as ax, type ClipboardDep as ay, type ClipboardIngestCtx as az, type ImageMinification as b, isLayerVisible as b$, type LayoutMove as b0, type LeafNode as b1, type LiveAnimation as b2, type LoopFactory as b3, type LoopOptions as b4, type NestedTimeline as b5, type NodeAtPointDep as b6, type OutlinePathSpec as b7, type Overlay as b8, type PathInWorldPose as b9, type TextOutlinesFailure as bA, type TextPaintPath as bB, type TextToPathOptions as bC, type TimelineClock as bD, type TimelineEvent as bE, type TimelineHandle as bF, type TimelineOptions as bG, type TimelineTrack as bH, type ToolDef as bI, type Track as bJ, type TweenLoopOptions as bK, type TweenOptions as bL, type UseToolsOptions as bM, type UserLayerRecord as bN, VIEW_ANIMATION_KEY as bO, type ViewAnimationApi as bP, type ViewChannel as bQ, applyBooleanOp as bR, applyCreateOutlines as bS, asNodeId as bT, composeWorldPose as bU, createPointerStore as bV, depsUnchanged as bW, drawLayers as bX, drawOneLayer as bY, fitViewToBounds as bZ, isLayerPainted as b_, type PhysicsHandle as ba, type PhysicsOptions as bb, PointerContextProvider as bc, type PointerContextValue as bd, type PointerWorldPos as be, type PoseClosure as bf, type PoseOverride as bg, type ResizePolicy as bh, type SerializedLayer as bi, type SerializedNode as bj, type SnapDep as bk, type SpringOptions as bl, type StaggerBuilder as bm, type StaggerDelay as bn, type StaggerFactory as bo, type StaggerOptions as bp, type StaggerPerItem as bq, type StaggerSpringPoseOptions as br, type StaggerTweenOptions as bs, type SvgUnpacker as bt, type SystemLayerRecord as bu, type SystemLayerSpec as bv, type TextEditDep as bw, type TextNodeSource as bx, type TextOutlineSource as by, TextOutlinesError as bz, type RenderSpan as c, loadTextOutlines as c0, pathInPoseFrame as c1, pathInWorld as c2, rebaseLocalPose as c3, registerContentHandler as c4, resolveStrokeWidth as c5, subscribeToSources as c6, textToPath as c7, textToPathsByPaint as c8, useDecayLoop as c9, usePointerContext as ca, usePointerPosition as cb, useTools as cc, useViewAnimation as cd, useViewAnimationOn as ce, worldEditToStorage as cf, type RenderSpanStats as d, type RenderStats as e, type RenderTarget as f, type ShaderDrawCommand as g, type SolidPaint as h, type SpriteSheet as i, type SpritesDrawCommand as j, type StrokeOptions as k, type WeaselRendererOptions as l, blur as m, buildGradientRamp as n, cullDrawCommands as o, frameRect as p, vignette as q, registerEffect as r, type RenderLayer as s, tessellateStroke as t, type Dims as u, viewToMat3 as v, type RedrawSource as w, type Scene as x, type RectPose as y, type InertiaConfig as z };
4514
+ export { type SvgIngestOptions as $, type PoseComposition as A, type PoseAdapter as B, type CullRect as C, type DrawCommand as D, type Effect as E, type GestureLifecycleOptions as F, type GroupDrawCommand as G, type SliceDep as H, IDENTITY_COLOR_MATRIX as I, type ToolsApi as J, type Tool as K, type Contribution as L, type LayerHit as M, type Node as N, type ChromeState as O, type PathDrawCommand as P, type VertexColorChannel as Q, type RenderOptions as R, SPRITE_STRIDE as S, type TextDrawCommand as T, type LayerGroup as U, type ViewAnimationOptions as V, WeaselRenderer as W, type InsertExtras as X, type SerializedScene as Y, type GeometryProjection as Z, type ContentHandlerEntry as _, type ImageDrawCommand as a, type LayoutFrame as a$, type AnyTool as a0, type PanBounds as a1, type Animator as a2, ColorOverrideRegistry as a3, type UseSceneOptions as a4, type SceneRegistry as a5, type RegisteredOp as a6, type PoseOverrides as a7, type AddNodeSpec as a8, type DerivePoseFn as a9, type ColorOverride as aA, type ColorOverrideFn as aB, type ContainerNode as aC, type ContributionChrome as aD, type CreateOutlinesResult as aE, type CustomPaintContext as aF, type DecayLoopConfig as aG, type DecayOptions as aH, type EditAnchorsDep as aI, type EventBooking as aJ, type EventBookingHandle as aK, type EventTrack as aL, type FitViewToBoundsOptions as aM, type HitTestView as aN, IDENTITY_POSE_COMPOSITION as aO, type IngestCtx as aP, type IngestionDep as aQ, type InsertDep as aR, type Interpolate as aS, type InterpolatorFactory as aT, type Keyframe as aU, type LassoSelectDep as aV, type LayerCommandCache as aW, type LayerDrawFailure as aX, type LayerDrawSpan as aY, type LayerRecord as aZ, type LayoutDep as a_, type DerivePathFn as aa, type DerivedDep as ab, type ViewportDims as ac, type ViewApi as ad, type Affordance as ae, type AffordanceRegion as af, type AffordanceBinding as ag, type CommonAffordanceScratch as ah, type BooleansAdapter as ai, type CreateOutlinesAdapter as aj, type UseAnimatorOptions as ak, type AnimationHandle as al, type SampledTrack as am, type SceneArrivalHandler as an, type AddLayerSpec as ao, type AnimateToBoundsOptions as ap, type AnimationInfo as aq, type AnimationKind as ar, type AnimatorEvent as as, type AnimatorListener as at, type AreaSelectDep as au, type BooleanOp as av, type BooleanOpResult as aw, type ClipFromPoseFn as ax, type ClipboardDep as ay, type ClipboardIngestCtx as az, type ImageMinification as b, isLayerVisible as b$, type LayoutMove as b0, type LeafNode as b1, type LiveAnimation as b2, type LoopFactory as b3, type LoopOptions as b4, type NestedTimeline as b5, type NodeAtPointDep as b6, type OutlinePathSpec as b7, type Overlay as b8, type PathInWorldPose as b9, type TextOutlinesFailure as bA, type TextPaintPath as bB, type TextToPathOptions as bC, type TimelineClock as bD, type TimelineEvent as bE, type TimelineHandle as bF, type TimelineOptions as bG, type TimelineTrack as bH, type ToolDef as bI, type Track as bJ, type TweenLoopOptions as bK, type TweenOptions as bL, type UseToolsOptions as bM, type UserLayerRecord as bN, VIEW_ANIMATION_KEY as bO, type ViewAnimationApi as bP, type ViewChannel as bQ, applyBooleanOp as bR, applyCreateOutlines as bS, asNodeId as bT, composeWorldPose as bU, createPointerStore as bV, depsUnchanged as bW, drawLayers as bX, drawOneLayer as bY, fitViewToBounds as bZ, isLayerPainted as b_, type PhysicsHandle as ba, type PhysicsOptions as bb, PointerContextProvider as bc, type PointerContextValue as bd, type PointerWorldPos as be, type PoseClosure as bf, type PoseOverride as bg, type ResizePolicy as bh, type SerializedLayer as bi, type SerializedNode as bj, type SnapDep as bk, type SpringOptions as bl, type StaggerBuilder as bm, type StaggerDelay as bn, type StaggerFactory as bo, type StaggerOptions as bp, type StaggerPerItem as bq, type StaggerSpringPoseOptions as br, type StaggerTweenOptions as bs, type SvgUnpacker as bt, type SystemLayerRecord as bu, type SystemLayerSpec as bv, type TextEditDep as bw, type TextNodeSource as bx, type TextOutlineSource as by, TextOutlinesError as bz, type RenderSpan as c, loadTextOutlines as c0, pathInPoseFrame as c1, pathInWorld as c2, rebaseLocalPose as c3, registerContentHandler as c4, resolveStrokeWidth as c5, subscribeToSources as c6, textToPath as c7, textToPathsByPaint as c8, useDecayLoop as c9, usePointerContext as ca, usePointerPosition as cb, useTools as cc, useViewAnimation as cd, useViewAnimationOn as ce, worldEditToStorage as cf, type RenderSpanStats as d, type RenderStats as e, type RenderTarget as f, type ShaderDrawCommand as g, type SolidPaint as h, type SpriteSheet as i, type SpritesDrawCommand as j, type StrokeOptions as k, type WeaselRendererOptions as l, blur as m, buildGradientRamp as n, cullDrawCommands as o, frameRect as p, vignette as q, registerEffect as r, type RenderLayer as s, tessellateStroke as t, type Dims as u, viewToMat3 as v, type RedrawSource as w, type Scene as x, type RectPose as y, type InertiaConfig as z };
@@ -49,14 +49,16 @@ function h(e = {}) {
49
49
  name: "X",
50
50
  description: "Left edge, world units.",
51
51
  default: 0,
52
- pair: "Position"
52
+ pair: {
53
+ with: "pose.y",
54
+ label: "Position"
55
+ }
53
56
  },
54
57
  "pose.y": {
55
58
  kind: "number",
56
59
  name: "Y",
57
60
  description: "Top edge, world units.",
58
- default: 0,
59
- pair: "Position"
61
+ default: 0
60
62
  },
61
63
  "pose.width": {
62
64
  kind: "number",
@@ -64,15 +66,17 @@ function h(e = {}) {
64
66
  description: "Width, world units.",
65
67
  default: 0,
66
68
  min: 0,
67
- pair: "Size"
69
+ pair: {
70
+ with: "pose.height",
71
+ label: "Size"
72
+ }
68
73
  },
69
74
  "pose.height": {
70
75
  kind: "number",
71
76
  name: "H",
72
77
  description: "Height, world units.",
73
78
  default: 0,
74
- min: 0,
75
- pair: "Size"
79
+ min: 0
76
80
  },
77
81
  "pose.rotation": {
78
82
  kind: "number",
@@ -151,7 +155,10 @@ function h(e = {}) {
151
155
  default: "butt",
152
156
  control: "toggle",
153
157
  block: !0,
154
- pair: "Line",
158
+ pair: {
159
+ with: ["data.stroke.join", "data.stroke.align"],
160
+ label: "Line"
161
+ },
155
162
  options: [
156
163
  {
157
164
  value: "butt",
@@ -177,7 +184,6 @@ function h(e = {}) {
177
184
  default: "miter",
178
185
  control: "toggle",
179
186
  block: !0,
180
- pair: "Line",
181
187
  options: [
182
188
  {
183
189
  value: "miter",
@@ -203,7 +209,6 @@ function h(e = {}) {
203
209
  default: "center",
204
210
  control: "toggle",
205
211
  block: !0,
206
- pair: "Line",
207
212
  options: [
208
213
  {
209
214
  value: "inner",
@@ -261,7 +266,10 @@ function h(e = {}) {
261
266
  default: "",
262
267
  control: "select",
263
268
  block: !0,
264
- pair: "Markers",
269
+ pair: {
270
+ with: ["data.stroke.markerMid", "data.stroke.markerEnd"],
271
+ label: "Markers"
272
+ },
265
273
  encoding: d,
266
274
  options: m()
267
275
  },
@@ -272,7 +280,6 @@ function h(e = {}) {
272
280
  default: "",
273
281
  control: "select",
274
282
  block: !0,
275
- pair: "Markers",
276
283
  encoding: d,
277
284
  options: m()
278
285
  },
@@ -283,7 +290,6 @@ function h(e = {}) {
283
290
  default: "",
284
291
  control: "select",
285
292
  block: !0,
286
- pair: "Markers",
287
293
  encoding: d,
288
294
  options: m()
289
295
  }
@@ -317,14 +323,16 @@ function h(e = {}) {
317
323
  default: 16,
318
324
  min: 1,
319
325
  step: 1,
320
- pair: "Size / weight"
326
+ pair: {
327
+ with: "data.style.fontWeight",
328
+ label: "Size / weight"
329
+ }
321
330
  },
322
331
  fontWeight: {
323
332
  kind: "font-weight",
324
333
  name: "Weight",
325
334
  description: "Font weight, from the faces the family has.",
326
- default: 400,
327
- pair: "Size / weight"
335
+ default: 400
328
336
  },
329
337
  fontStyle: {
330
338
  kind: "boolean",
@@ -334,7 +342,15 @@ function h(e = {}) {
334
342
  control: "toggle",
335
343
  icon: "italic",
336
344
  short: ["I"],
337
- pair: "Style",
345
+ pair: {
346
+ with: [
347
+ "data.style.underline",
348
+ "data.style.strikethrough",
349
+ "data.style.overline",
350
+ "data.style.script"
351
+ ],
352
+ label: "Style"
353
+ },
338
354
  encoding: f
339
355
  },
340
356
  underline: {
@@ -344,8 +360,7 @@ function h(e = {}) {
344
360
  default: !1,
345
361
  control: "toggle",
346
362
  icon: "underline",
347
- short: ["U"],
348
- pair: "Style"
363
+ short: ["U"]
349
364
  },
350
365
  strikethrough: {
351
366
  kind: "boolean",
@@ -354,8 +369,7 @@ function h(e = {}) {
354
369
  default: !1,
355
370
  control: "toggle",
356
371
  icon: "strikethrough",
357
- short: ["S"],
358
- pair: "Style"
372
+ short: ["S"]
359
373
  },
360
374
  overline: {
361
375
  kind: "boolean",
@@ -364,8 +378,7 @@ function h(e = {}) {
364
378
  default: !1,
365
379
  control: "toggle",
366
380
  icon: "overline",
367
- short: ["O"],
368
- pair: "Style"
381
+ short: ["O"]
369
382
  },
370
383
  script: {
371
384
  kind: "enum",
@@ -374,7 +387,6 @@ function h(e = {}) {
374
387
  default: void 0,
375
388
  control: "toggle",
376
389
  clearable: !0,
377
- pair: "Style",
378
390
  options: [{
379
391
  value: "super",
380
392
  label: "Superscript",
@@ -1 +1 @@
1
- {"version":3,"file":"defaultNodeProperties.js","names":["rotationDegreesUnit","prefUnit","ANGLE_RADIANS","strokeWidthOf","w","stroke","strokeDashEncoding","strokeDashStyleOf","dash","style","dashForStrokeStyle","markerEncoding","ref","value","italicEncoding","stored","on","alignEncoding","direction","resolveAlign","edge","markerOptions","listMarkers","m","shapeSchema","opts","defaultNodeProperties","KIT_SHAPE_KINDS","name","inferredNodeProperties","inferredNodeRouting"],"sources":["../../../src/canvas/SceneCanvas/defaultNodeProperties.ts"],"sourcesContent":["import { inferredNodeRouting } from './defaultNodeRouting';\nimport { KIT_SHAPE_KINDS } from 'core/shapeKinds';\nimport { listMarkers } from 'core/strokeMarkers';\nimport { dashForStrokeStyle, strokeDashStyleOf } from '@weasel-js/paint';\nimport { resolveAlign, type TextAlign } from '@weasel-js/text';\nimport type { NodePropertiesEntry } from 'core/scene/NodeProperties';\nimport { ANGLE_RADIANS } from '@weasel-js/quantity';\nimport { prefUnit, type ToolPrefBooleanEncoding, type ToolPrefEnumEncoding, type ToolPrefGroup, type ToolPrefNumberUnit } from 'tools/prefs';\n\n/** Radians-stored / degrees-shown conversion for `pose.rotation` leaves.\n * Display rounds to 0.1° so a canonical radian value doesn't render as\n * a 15-digit float. */\nexport const rotationDegreesUnit: ToolPrefNumberUnit = prefUnit(ANGLE_RADIANS, 'deg', {\n precision: 1,\n suffix: '°',\n});\n\n/** `Stroke.width` off a partly-typed stroke object, or `undefined`. */\nfunction strokeWidthOf(stroke: Record<string, unknown> | undefined): number | { px: number } | undefined {\n const w = stroke?.width;\n if (typeof w === 'number') return w;\n if (typeof w === 'object' && w !== null && typeof (w as { px?: unknown }).px === 'number') {\n return w as { px: number };\n }\n return undefined;\n}\n\n/** `Stroke.dash` — a stored array of lengths — read and written as a named\n * style. The presets are multiples of the sibling `width`, which is why the\n * encoding is handed the whole stroke rather than the field. */\nconst strokeDashEncoding: ToolPrefEnumEncoding = {\n read: (dash, stroke) =>\n stroke === undefined\n ? undefined\n : strokeDashStyleOf(Array.isArray(dash) ? (dash as number[]) : undefined, strokeWidthOf(stroke)),\n write: (style, stroke) =>\n style === 'dashed' || style === 'dotted'\n ? dashForStrokeStyle(style, strokeWidthOf(stroke))\n // `solid` is stored as no dash at all. `custom` is reportable, not\n // authorable: it names an imported array, and the array it names is the\n // one already stored.\n : style === 'solid'\n ? undefined\n : stroke?.dash,\n};\n\n/** `Stroke.markerStart` / `markerMid` / `markerEnd` — a `MarkerRef` — read and\n * written as a bare key, with the empty string standing for no marker. */\nconst markerEncoding: ToolPrefEnumEncoding = {\n read: (ref) => {\n if (typeof ref === 'string') return ref;\n if (ref && typeof ref === 'object' && 'key' in ref) return String((ref as { key: string }).key);\n return '';\n },\n write: (value) => (value === '' ? undefined : value),\n};\n\n/** `TextStyle.fontStyle` as an Italic flag. Off removes the field rather than\n * storing `'normal'`, which is what an absent one already means. */\nconst italicEncoding: ToolPrefBooleanEncoding = {\n read: (stored) => stored === 'italic',\n write: (on) => (on ? 'italic' : undefined),\n};\n\n/** `TextStyle.align` read as the edge that paints, or as `justify`. The model\n * also holds the reading-order pair `start` / `end`, which the segments would\n * otherwise leave unlit; they read through `direction`, and a click writes the\n * edge. */\nconst alignEncoding: ToolPrefEnumEncoding = {\n read: (stored, style) => {\n if (stored === undefined || stored === 'justify') return stored;\n const direction = style?.direction === 'rtl' ? 'rtl' : 'ltr';\n return resolveAlign(stored as TextAlign, direction);\n },\n write: (edge) => edge,\n};\n\n/** Options come from the registry, so a consumer's registered entry appears in\n * the picker with no kit edit. A `select` rather than a `toggle`: nine options\n * will not fit a segmented strip. */\nconst markerOptions = () => [\n { value: '', label: 'None' },\n ...listMarkers().map((m) => ({ value: m.id, label: m.id })),\n];\n\n/** Build the standard shape schema — Layout (pose box + rotation) +\n * Appearance (fill / stroke), optionally a Text group. Matches the kit's\n * builtin-shape data template (`{ path, fill, stroke?, text? }`,\n * `useBuiltinShapeTools`). */\nfunction shapeSchema(opts: { text?: boolean } = {}): ToolPrefGroup {\n return {\n name: 'Properties',\n children: {\n layout: {\n name: 'Layout',\n children: {\n 'pose.x': { kind: 'number', name: 'X', description: 'Left edge, world units.', default: 0, pair: 'Position' },\n 'pose.y': { kind: 'number', name: 'Y', description: 'Top edge, world units.', default: 0, pair: 'Position' },\n 'pose.width': { kind: 'number', name: 'W', description: 'Width, world units.', default: 0, min: 0, pair: 'Size' },\n 'pose.height': { kind: 'number', name: 'H', description: 'Height, world units.', default: 0, min: 0, pair: 'Size' },\n 'pose.rotation': { kind: 'number', name: 'Rotation', description: 'Rotation about the box center.', default: 0, step: 1, unit: rotationDegreesUnit },\n },\n },\n // What the node *is* outranks how it is painted: a text node's copy is\n // the first thing an editor reaches for.\n ...(opts.text\n ? {\n content: {\n name: 'Content',\n children: {\n // `block`: the content is the section, so a 64px label column\n // reading \"Text\" inside a section reading \"Text\" is one word\n // twice and a narrower field for the sake of it.\n 'data.text': { kind: 'string', name: 'Text', description: 'Text content.', default: '', block: true },\n },\n },\n }\n : {}),\n appearance: {\n // Headless: `Fill` and `Stroke` head their own blocks, and an\n // `Appearance` rule over them names neither of the two paints a\n // reader is looking at.\n name: '',\n children: {\n // A `paint` leaf, not a `color` one: `data.fill` is the tagged\n // `FillStyle` union, so a color control pointed at it would read\n // `undefined` off a gradient and write a bare string over it.\n // `block` for the same reason every stroke field is: the kind bar\n // and the per-kind editor want the width a 64px label column takes.\n 'data.fill': { kind: 'paint', name: 'Fill', description: 'Fill paint.', default: { fill: 'solid', color: '#000000ff' }, alpha: true, block: true },\n // An object leaf: `data.stroke` is a whole `Stroke`, and its fields\n // belong to one value. Sibling leaves addressing into it would each\n // write one field of a value they can only half see.\n 'data.stroke': {\n kind: 'object',\n name: 'Stroke',\n description: 'Stroke paint and line geometry.',\n default: { paint: { fill: 'solid', color: '#000000ff' }, width: 1 },\n block: true,\n children: {\n // Every row is `block`: under a heading already reading STROKE,\n // a 64px label column spells the section name again and takes\n // the width the controls need. Each field leads with its own\n // glyph instead.\n // Unpaired: the paint field is a whole paint editor — a kind bar\n // over a per-kind body — and cannot share a row with a slider.\n width: { kind: 'number', name: 'Width', description: 'Stroke width, world units.', default: 1, min: 0, max: 20, step: 0.5, control: 'slider', icon: 'strokeWidth', block: true },\n paint: { kind: 'paint', name: 'Paint', description: 'Stroke paint.', default: { fill: 'solid', color: '#000000ff' }, alpha: true, block: true },\n // Three options each: a segmented control shows every one at\n // once where a select shows the current one and hides the rest\n // behind a click.\n cap: { kind: 'enum', name: 'Cap', description: 'How an open end is drawn.', default: 'butt', control: 'toggle', block: true, pair: 'Line', options: [{ value: 'butt', label: 'Butt', icon: 'capButt' }, { value: 'round', label: 'Round', icon: 'capRound' }, { value: 'square', label: 'Square', icon: 'capSquare' }] },\n join: { kind: 'enum', name: 'Join', description: 'How a corner is drawn.', default: 'miter', control: 'toggle', block: true, pair: 'Line', options: [{ value: 'miter', label: 'Miter', icon: 'joinMiter' }, { value: 'round', label: 'Round', icon: 'joinRound' }, { value: 'bevel', label: 'Bevel', icon: 'joinBevel' }] },\n align: { kind: 'enum', name: 'Align', description: 'Where the ribbon sits relative to the edge.', default: 'center', control: 'toggle', block: true, pair: 'Line', options: [{ value: 'inner', label: 'Inner', icon: 'alignInner' }, { value: 'center', label: 'Center', icon: 'alignCenter' }, { value: 'outer', label: 'Outer', icon: 'alignOuter' }] },\n // Its own row: three bars already fill the one above, and the\n // dash pattern is a property of the line rather than of how the\n // ribbon meets the geometry.\n dash: { kind: 'enum', name: 'Style', description: 'Solid, dashed or dotted. Dash lengths are multiples of the stroke width, so a style holds as the width changes.', default: 'solid', control: 'toggle', block: true, encoding: strokeDashEncoding, options: [{ value: 'solid', label: 'Solid', icon: 'dashSolid' }, { value: 'dashed', label: 'Dashed', icon: 'dashDashed' }, { value: 'dotted', label: 'Dotted', icon: 'dashDotted' }, { value: 'custom', label: 'Custom', icon: 'dashCustom', disabled: true }] },\n markerStart: { kind: 'enum', name: 'Start', description: 'Marker at the first vertex of each open subpath.', default: '', control: 'select', block: true, pair: 'Markers', encoding: markerEncoding, options: markerOptions() },\n markerMid: { kind: 'enum', name: 'Mid', description: 'Marker at every interior authored vertex.', default: '', control: 'select', block: true, pair: 'Markers', encoding: markerEncoding, options: markerOptions() },\n markerEnd: { kind: 'enum', name: 'End', description: 'Marker at the last vertex of each open subpath.', default: '', control: 'select', block: true, pair: 'Markers', encoding: markerEncoding, options: markerOptions() },\n },\n },\n },\n },\n ...(opts.text\n ? {\n text: {\n // Headless: `Character` and `Paragraph` carry the labels, and a\n // `Typography` heading over them names nothing a reader can't\n // already see.\n name: '',\n children: {\n // One object leaf, not a row of siblings addressing into it:\n // `data.style` is a `TextStyle`, and its character and\n // paragraph fields are one value that reads as two lists.\n // The groups head those lists and contribute nothing to the\n // path — a field inside one is still a field of the style.\n 'data.style': {\n kind: 'object',\n name: 'Style',\n description: 'Typography for the whole node. Its color is the node\\'s own `data.fill`, in Appearance.',\n default: {},\n block: true,\n children: {\n character: {\n name: 'Character',\n children: {\n fontFamily: { kind: 'font-family', name: 'Font', description: 'Registered font family.', default: 'sans-serif' },\n fontSize: { kind: 'number', name: 'Size', description: 'Font size, world units.', default: 16, min: 1, step: 1, pair: 'Size / weight' },\n fontWeight: { kind: 'font-weight', name: 'Weight', description: 'Font weight, from the faces the family has.', default: 400, pair: 'Size / weight' },\n // One row of segments, the strip every text editor\n // draws: the slant, the three rules, then the two\n // scripts. Italic is a flag here although the field\n // is `fontStyle`, whose only other value is upright.\n fontStyle: { kind: 'boolean', name: 'Italic', description: 'Set the text in its italic face.', default: false, control: 'toggle', icon: 'italic', short: ['I'], pair: 'Style', encoding: italicEncoding },\n underline: { kind: 'boolean', name: 'Underline', description: 'Underline the text.', default: false, control: 'toggle', icon: 'underline', short: ['U'], pair: 'Style' },\n strikethrough: { kind: 'boolean', name: 'Strikethrough', description: 'Strike through the text.', default: false, control: 'toggle', icon: 'strikethrough', short: ['S'], pair: 'Style' },\n overline: { kind: 'boolean', name: 'Overline', description: 'Rule the text above its ascent.', default: false, control: 'toggle', icon: 'overline', short: ['O'], pair: 'Style' },\n script: { kind: 'enum', name: 'Script', description: 'Set the text smaller and raised or lowered, on the line it would otherwise hold.', default: undefined, control: 'toggle', clearable: true, pair: 'Style', options: [{ value: 'super', label: 'Superscript', icon: 'superscript' }, { value: 'sub', label: 'Subscript', icon: 'subscript' }] },\n letterSpacing: { kind: 'number', name: 'Tracking', description: 'Extra advance per glyph, world units.', default: 0, step: 0.1 },\n textTransform: { kind: 'enum', name: 'Case', description: 'Draw the text in capitals, lowercase or title case. The text itself is unchanged.', default: 'none', control: 'toggle', options: [{ value: 'none', label: 'None', short: ['–'] }, { value: 'uppercase', label: 'Uppercase', short: ['AA'] }, { value: 'lowercase', label: 'Lowercase', short: ['aa'] }, { value: 'capitalize', label: 'Capitalize', short: ['Aa'] }] },\n fontVariantCaps: { kind: 'enum', name: 'Small caps', description: 'Draw lowercase letters as smaller capitals. The text itself is unchanged.', default: 'normal', control: 'toggle', options: [{ value: 'normal', label: 'Normal', short: ['–'] }, { value: 'small-caps', label: 'Small caps', short: ['Sc'] }] },\n },\n },\n paragraph: {\n name: 'Paragraph',\n children: {\n lineHeight: { kind: 'number', name: 'Leading', description: 'Line height as a multiple of font size.', default: 1.2, min: 0.5, step: 0.1 },\n wrap: { kind: 'boolean', name: 'Wrap', description: 'Break lines between words at the box width.', default: false, control: 'switch' },\n direction: { kind: 'enum', name: 'Direction', description: 'Reading direction.', default: 'ltr', control: 'toggle', options: [{ value: 'ltr', label: 'Left to right', short: ['LTR'] }, { value: 'rtl', label: 'Right to left', short: ['RTL'] }] },\n // Last, so the box's vertical alignment — a field of\n // the node rather than of its style — follows it.\n align: { kind: 'enum', name: 'Align', description: 'Horizontal alignment.', default: 'left', control: 'toggle', encoding: alignEncoding, options: [{ value: 'left', label: 'Left', icon: 'textAlignLeft' }, { value: 'center', label: 'Center', icon: 'textAlignCenter' }, { value: 'right', label: 'Right', icon: 'textAlignRight' }, { value: 'justify', label: 'Justify', icon: 'textAlignJustify' }] },\n },\n },\n },\n },\n 'data.verticalAlign': { kind: 'enum', name: 'Vertical', description: 'Where the lines sit in the box\\'s height.', default: 'top', control: 'toggle', options: [{ value: 'top', label: 'Top', icon: 'textAlignTop' }, { value: 'center', label: 'Middle', icon: 'textAlignMiddle' }, { value: 'bottom', label: 'Bottom', icon: 'textAlignBottom' }] },\n },\n },\n }\n : {}),\n },\n };\n}\n\n/**\n * Default properties-trait entries covering the kit's built-in shape\n * kinds (`KIT_SHAPE_KINDS`) — the sibling of `defaultNodeRouting`. Every\n * kind gets the standard box schema; `text` adds a Text group. In\n * lockstep with `KIT_SHAPE_KINDS` by construction (derived via `.map`).\n */\nexport const defaultNodeProperties: readonly NodePropertiesEntry[] =\n KIT_SHAPE_KINDS.map((name) => ({\n name,\n schema: shapeSchema({ text: name === 'text' }),\n }));\n\n/**\n * Properties-trait entries for the *inferred* routing kinds\n * (`inferredNodeRouting`: `text` / `path` / `image`) — the vocabulary\n * consumers produce when they don't tag `data.kind` (e.g. WeaselDraw).\n * In lockstep with `inferredNodeRouting` by construction.\n */\nexport const inferredNodeProperties: readonly NodePropertiesEntry[] =\n inferredNodeRouting.map((e) => ({\n name: e.name,\n schema: shapeSchema({ text: e.name === 'text' }),\n }));\n"],"mappings":";;;;;;;;AAYA,IAAaA,IAA0CC,EAASC,GAAe,OAAO;CACpF,WAAW;CACX,QAAQ;AACV,CAAC;AAGD,SAASC,EAAc,GAAkF;CACvG,IAAMC,IAAIC,GAAQ;CAElB,IADI,OAAOD,KAAM,YACb,OAAOA,KAAM,YAAYA,KAAc,OAAQA,EAAuB,MAAO,UAC/E,OAAOA;AAGX;AAKA,IAAME,IAA2C;CAC/C,OAAO,GAAM,MACXD,MAAW,KAAA,IACP,KAAA,IACAE,EAAkB,MAAM,QAAQC,CAAI,IAAKA,IAAoB,KAAA,GAAWL,EAAcE,CAAM,CAAC;CACnG,QAAQ,GAAO,MACbI,MAAU,YAAYA,MAAU,WAC5BC,EAAmBD,GAAON,EAAcE,CAAM,CAAC,IAI/CI,MAAU,UACR,KAAA,IACAJ,GAAQ;AAClB,GAIMM,IAAuC;CAC3C,OAAO,MACD,OAAOC,KAAQ,WAAiBA,IAChCA,KAAO,OAAOA,KAAQ,YAAY,SAASA,IAAY,OAAQA,EAAwB,GAAG,IACvF;CAET,QAAQ,MAAWC,MAAU,KAAK,KAAA,IAAYA;AAChD,GAIMC,IAA0C;CAC9C,OAAO,MAAWC,MAAW;CAC7B,QAAQ,MAAQC,IAAK,WAAW,KAAA;AAClC,GAMMC,IAAsC;CAC1C,OAAO,GAAQ,MAAU;EACvB,IAAIF,MAAW,KAAA,KAAaA,MAAW,WAAW,OAAOA;EACzD,IAAMG,IAAYT,GAAO,cAAc,QAAQ,QAAQ;EACvD,OAAOU,EAAaJ,GAAqBG,CAAS;CACpD;CACA,QAAQ,MAASE;AACnB,GAKMC,UAAsB,CAC1B;CAAE,OAAO;CAAI,OAAO;AAAO,GAC3B,GAAGC,EAAY,CAAC,CAAC,KAAK,OAAO;CAAE,OAAOC,EAAE;CAAI,OAAOA,EAAE;AAAG,EAAE,CAC5D;AAMA,SAASC,EAAY,IAA2B,CAAC,GAAkB;CACjE,OAAO;EACL,MAAM;EACN,UAAU;GACR,QAAQ;IACN,MAAM;IACN,UAAU;KACR,UAAU;MAAE,MAAM;MAAU,MAAM;MAAK,aAAa;MAA2B,SAAS;MAAG,MAAM;KAAW;KAC5G,UAAU;MAAE,MAAM;MAAU,MAAM;MAAK,aAAa;MAA0B,SAAS;MAAG,MAAM;KAAW;KAC3G,cAAc;MAAE,MAAM;MAAU,MAAM;MAAK,aAAa;MAAuB,SAAS;MAAG,KAAK;MAAG,MAAM;KAAO;KAChH,eAAe;MAAE,MAAM;MAAU,MAAM;MAAK,aAAa;MAAwB,SAAS;MAAG,KAAK;MAAG,MAAM;KAAO;KAClH,iBAAiB;MAAE,MAAM;MAAU,MAAM;MAAY,aAAa;MAAkC,SAAS;MAAG,MAAM;MAAG,MAAMxB;KAAoB;IACrJ;GACF;GAGA,GAAIyB,EAAK,OACL,EACE,SAAS;IACP,MAAM;IACN,UAAU,EAIR,aAAa;KAAE,MAAM;KAAU,MAAM;KAAQ,aAAa;KAAiB,SAAS;KAAI,OAAO;IAAK,EACtG;GACF,EACF,IACA,CAAC;GACL,YAAY;IAIV,MAAM;IACN,UAAU;KAMR,aAAa;MAAE,MAAM;MAAS,MAAM;MAAQ,aAAa;MAAe,SAAS;OAAE,MAAM;OAAS,OAAO;MAAY;MAAG,OAAO;MAAM,OAAO;KAAK;KAIjJ,eAAe;MACb,MAAM;MACN,MAAM;MACN,aAAa;MACb,SAAS;OAAE,OAAO;QAAE,MAAM;QAAS,OAAO;OAAY;OAAG,OAAO;MAAE;MAClE,OAAO;MACP,UAAU;OAOR,OAAO;QAAE,MAAM;QAAU,MAAM;QAAS,aAAa;QAA8B,SAAS;QAAG,KAAK;QAAG,KAAK;QAAI,MAAM;QAAK,SAAS;QAAU,MAAM;QAAe,OAAO;OAAK;OAC/K,OAAO;QAAE,MAAM;QAAS,MAAM;QAAS,aAAa;QAAiB,SAAS;SAAE,MAAM;SAAS,OAAO;QAAY;QAAG,OAAO;QAAM,OAAO;OAAK;OAI9I,KAAK;QAAE,MAAM;QAAQ,MAAM;QAAO,aAAa;QAA6B,SAAS;QAAQ,SAAS;QAAU,OAAO;QAAM,MAAM;QAAQ,SAAS;SAAC;UAAE,OAAO;UAAQ,OAAO;UAAQ,MAAM;SAAU;SAAG;UAAE,OAAO;UAAS,OAAO;UAAS,MAAM;SAAW;SAAG;UAAE,OAAO;UAAU,OAAO;UAAU,MAAM;SAAY;QAAC;OAAE;OACvT,MAAM;QAAE,MAAM;QAAQ,MAAM;QAAQ,aAAa;QAA0B,SAAS;QAAS,SAAS;QAAU,OAAO;QAAM,MAAM;QAAQ,SAAS;SAAC;UAAE,OAAO;UAAS,OAAO;UAAS,MAAM;SAAY;SAAG;UAAE,OAAO;UAAS,OAAO;UAAS,MAAM;SAAY;SAAG;UAAE,OAAO;UAAS,OAAO;UAAS,MAAM;SAAY;QAAC;OAAE;OAC1T,OAAO;QAAE,MAAM;QAAQ,MAAM;QAAS,aAAa;QAA+C,SAAS;QAAU,SAAS;QAAU,OAAO;QAAM,MAAM;QAAQ,SAAS;SAAC;UAAE,OAAO;UAAS,OAAO;UAAS,MAAM;SAAa;SAAG;UAAE,OAAO;UAAU,OAAO;UAAU,MAAM;SAAc;SAAG;UAAE,OAAO;UAAS,OAAO;UAAS,MAAM;SAAa;QAAC;OAAE;OAIxV,MAAM;QAAE,MAAM;QAAQ,MAAM;QAAS,aAAa;QAAmH,SAAS;QAAS,SAAS;QAAU,OAAO;QAAM,UAAUnB;QAAoB,SAAS;SAAC;UAAE,OAAO;UAAS,OAAO;UAAS,MAAM;SAAY;SAAG;UAAE,OAAO;UAAU,OAAO;UAAU,MAAM;SAAa;SAAG;UAAE,OAAO;UAAU,OAAO;UAAU,MAAM;SAAa;SAAG;UAAE,OAAO;UAAU,OAAO;UAAU,MAAM;UAAc,UAAU;SAAK;QAAC;OAAE;OACpf,aAAa;QAAE,MAAM;QAAQ,MAAM;QAAS,aAAa;QAAoD,SAAS;QAAI,SAAS;QAAU,OAAO;QAAM,MAAM;QAAW,UAAUK;QAAgB,SAASU,EAAc;OAAE;OAC9N,WAAW;QAAE,MAAM;QAAQ,MAAM;QAAO,aAAa;QAA6C,SAAS;QAAI,SAAS;QAAU,OAAO;QAAM,MAAM;QAAW,UAAUV;QAAgB,SAASU,EAAc;OAAE;OACnN,WAAW;QAAE,MAAM;QAAQ,MAAM;QAAO,aAAa;QAAmD,SAAS;QAAI,SAAS;QAAU,OAAO;QAAM,MAAM;QAAW,UAAUV;QAAgB,SAASU,EAAc;OAAE;MAC3N;KACF;IACF;GACF;GACA,GAAII,EAAK,OACL,EACE,MAAM;IAIJ,MAAM;IACN,UAAU;KAMR,cAAc;MACZ,MAAM;MACN,MAAM;MACN,aAAa;MACb,SAAS,CAAC;MACV,OAAO;MACP,UAAU;OACR,WAAW;QACT,MAAM;QACN,UAAU;SACR,YAAY;UAAE,MAAM;UAAe,MAAM;UAAQ,aAAa;UAA2B,SAAS;SAAa;SAC/G,UAAU;UAAE,MAAM;UAAU,MAAM;UAAQ,aAAa;UAA2B,SAAS;UAAI,KAAK;UAAG,MAAM;UAAG,MAAM;SAAgB;SACtI,YAAY;UAAE,MAAM;UAAe,MAAM;UAAU,aAAa;UAA+C,SAAS;UAAK,MAAM;SAAgB;SAKnJ,WAAW;UAAE,MAAM;UAAW,MAAM;UAAU,aAAa;UAAoC,SAAS;UAAO,SAAS;UAAU,MAAM;UAAU,OAAO,CAAC,GAAG;UAAG,MAAM;UAAS,UAAUX;SAAe;SACxM,WAAW;UAAE,MAAM;UAAW,MAAM;UAAa,aAAa;UAAuB,SAAS;UAAO,SAAS;UAAU,MAAM;UAAa,OAAO,CAAC,GAAG;UAAG,MAAM;SAAQ;SACvK,eAAe;UAAE,MAAM;UAAW,MAAM;UAAiB,aAAa;UAA4B,SAAS;UAAO,SAAS;UAAU,MAAM;UAAiB,OAAO,CAAC,GAAG;UAAG,MAAM;SAAQ;SACxL,UAAU;UAAE,MAAM;UAAW,MAAM;UAAY,aAAa;UAAmC,SAAS;UAAO,SAAS;UAAU,MAAM;UAAY,OAAO,CAAC,GAAG;UAAG,MAAM;SAAQ;SAChL,QAAQ;UAAE,MAAM;UAAQ,MAAM;UAAU,aAAa;UAAoF,SAAS,KAAA;UAAW,SAAS;UAAU,WAAW;UAAM,MAAM;UAAS,SAAS,CAAC;WAAE,OAAO;WAAS,OAAO;WAAe,MAAM;UAAc,GAAG;WAAE,OAAO;WAAO,OAAO;WAAa,MAAM;UAAY,CAAC;SAAE;SAClV,eAAe;UAAE,MAAM;UAAU,MAAM;UAAY,aAAa;UAAyC,SAAS;UAAG,MAAM;SAAI;SAC/H,eAAe;UAAE,MAAM;UAAQ,MAAM;UAAQ,aAAa;UAAqF,SAAS;UAAQ,SAAS;UAAU,SAAS;WAAC;YAAE,OAAO;YAAQ,OAAO;YAAQ,OAAO,CAAC,GAAG;WAAE;WAAG;YAAE,OAAO;YAAa,OAAO;YAAa,OAAO,CAAC,IAAI;WAAE;WAAG;YAAE,OAAO;YAAa,OAAO;YAAa,OAAO,CAAC,IAAI;WAAE;WAAG;YAAE,OAAO;YAAc,OAAO;YAAc,OAAO,CAAC,IAAI;WAAE;UAAC;SAAE;SACha,iBAAiB;UAAE,MAAM;UAAQ,MAAM;UAAc,aAAa;UAA6E,SAAS;UAAU,SAAS;UAAU,SAAS,CAAC;WAAE,OAAO;WAAU,OAAO;WAAU,OAAO,CAAC,GAAG;UAAE,GAAG;WAAE,OAAO;WAAc,OAAO;WAAc,OAAO,CAAC,IAAI;UAAE,CAAC;SAAE;QAClT;OACF;OACA,WAAW;QACT,MAAM;QACN,UAAU;SACR,YAAY;UAAE,MAAM;UAAU,MAAM;UAAW,aAAa;UAA2C,SAAS;UAAK,KAAK;UAAK,MAAM;SAAI;SACzI,MAAM;UAAE,MAAM;UAAW,MAAM;UAAQ,aAAa;UAA+C,SAAS;UAAO,SAAS;SAAS;SACrI,WAAW;UAAE,MAAM;UAAQ,MAAM;UAAa,aAAa;UAAsB,SAAS;UAAO,SAAS;UAAU,SAAS,CAAC;WAAE,OAAO;WAAO,OAAO;WAAiB,OAAO,CAAC,KAAK;UAAE,GAAG;WAAE,OAAO;WAAO,OAAO;WAAiB,OAAO,CAAC,KAAK;UAAE,CAAC;SAAE;SAGlP,OAAO;UAAE,MAAM;UAAQ,MAAM;UAAS,aAAa;UAAyB,SAAS;UAAQ,SAAS;UAAU,UAAUG;UAAe,SAAS;WAAC;YAAE,OAAO;YAAQ,OAAO;YAAQ,MAAM;WAAgB;WAAG;YAAE,OAAO;YAAU,OAAO;YAAU,MAAM;WAAkB;WAAG;YAAE,OAAO;YAAS,OAAO;YAAS,MAAM;WAAiB;WAAG;YAAE,OAAO;YAAW,OAAO;YAAW,MAAM;WAAmB;UAAC;SAAE;QAC3Y;OACF;MACF;KACF;KACA,sBAAsB;MAAE,MAAM;MAAQ,MAAM;MAAY,aAAa;MAA6C,SAAS;MAAO,SAAS;MAAU,SAAS;OAAC;QAAE,OAAO;QAAO,OAAO;QAAO,MAAM;OAAe;OAAG;QAAE,OAAO;QAAU,OAAO;QAAU,MAAM;OAAkB;OAAG;QAAE,OAAO;QAAU,OAAO;QAAU,MAAM;OAAkB;MAAC;KAAE;IACrV;GACF,EACF,IACA,CAAC;EACP;CACF;AACF;AAQA,IAAaS,IACXC,EAAgB,KAAK,OAAU;CAC7B;CACA,QAAQH,EAAY,EAAE,MAAMI,MAAS,OAAO,CAAC;AAC/C,EAAE,GAQSC,IACXC,EAAoB,KAAK,OAAO;CAC9B,MAAM,EAAE;CACR,QAAQN,EAAY,EAAE,MAAM,EAAE,SAAS,OAAO,CAAC;AACjD,EAAE"}
1
+ {"version":3,"file":"defaultNodeProperties.js","names":["rotationDegreesUnit","prefUnit","ANGLE_RADIANS","strokeWidthOf","w","stroke","strokeDashEncoding","strokeDashStyleOf","dash","style","dashForStrokeStyle","markerEncoding","ref","value","italicEncoding","stored","on","alignEncoding","direction","resolveAlign","edge","markerOptions","listMarkers","m","shapeSchema","opts","defaultNodeProperties","KIT_SHAPE_KINDS","name","inferredNodeProperties","inferredNodeRouting"],"sources":["../../../src/canvas/SceneCanvas/defaultNodeProperties.ts"],"sourcesContent":["import { inferredNodeRouting } from './defaultNodeRouting';\nimport { KIT_SHAPE_KINDS } from 'core/shapeKinds';\nimport { listMarkers } from 'core/strokeMarkers';\nimport { dashForStrokeStyle, strokeDashStyleOf } from '@weasel-js/paint';\nimport { resolveAlign, type TextAlign } from '@weasel-js/text';\nimport type { NodePropertiesEntry } from 'core/scene/NodeProperties';\nimport { ANGLE_RADIANS } from '@weasel-js/quantity';\nimport { prefUnit, type ToolPrefBooleanEncoding, type ToolPrefEnumEncoding, type ToolPrefGroup, type ToolPrefNumberUnit } from 'tools/prefs';\n\n/** Radians-stored / degrees-shown conversion for `pose.rotation` leaves.\n * Display rounds to 0.1° so a canonical radian value doesn't render as\n * a 15-digit float. */\nexport const rotationDegreesUnit: ToolPrefNumberUnit = prefUnit(ANGLE_RADIANS, 'deg', {\n precision: 1,\n suffix: '°',\n});\n\n/** `Stroke.width` off a partly-typed stroke object, or `undefined`. */\nfunction strokeWidthOf(stroke: Record<string, unknown> | undefined): number | { px: number } | undefined {\n const w = stroke?.width;\n if (typeof w === 'number') return w;\n if (typeof w === 'object' && w !== null && typeof (w as { px?: unknown }).px === 'number') {\n return w as { px: number };\n }\n return undefined;\n}\n\n/** `Stroke.dash` — a stored array of lengths — read and written as a named\n * style. The presets are multiples of the sibling `width`, which is why the\n * encoding is handed the whole stroke rather than the field. */\nconst strokeDashEncoding: ToolPrefEnumEncoding = {\n read: (dash, stroke) =>\n stroke === undefined\n ? undefined\n : strokeDashStyleOf(Array.isArray(dash) ? (dash as number[]) : undefined, strokeWidthOf(stroke)),\n write: (style, stroke) =>\n style === 'dashed' || style === 'dotted'\n ? dashForStrokeStyle(style, strokeWidthOf(stroke))\n // `solid` is stored as no dash at all. `custom` is reportable, not\n // authorable: it names an imported array, and the array it names is the\n // one already stored.\n : style === 'solid'\n ? undefined\n : stroke?.dash,\n};\n\n/** `Stroke.markerStart` / `markerMid` / `markerEnd` — a `MarkerRef` — read and\n * written as a bare key, with the empty string standing for no marker. */\nconst markerEncoding: ToolPrefEnumEncoding = {\n read: (ref) => {\n if (typeof ref === 'string') return ref;\n if (ref && typeof ref === 'object' && 'key' in ref) return String((ref as { key: string }).key);\n return '';\n },\n write: (value) => (value === '' ? undefined : value),\n};\n\n/** `TextStyle.fontStyle` as an Italic flag. Off removes the field rather than\n * storing `'normal'`, which is what an absent one already means. */\nconst italicEncoding: ToolPrefBooleanEncoding = {\n read: (stored) => stored === 'italic',\n write: (on) => (on ? 'italic' : undefined),\n};\n\n/** `TextStyle.align` read as the edge that paints, or as `justify`. The model\n * also holds the reading-order pair `start` / `end`, which the segments would\n * otherwise leave unlit; they read through `direction`, and a click writes the\n * edge. */\nconst alignEncoding: ToolPrefEnumEncoding = {\n read: (stored, style) => {\n if (stored === undefined || stored === 'justify') return stored;\n const direction = style?.direction === 'rtl' ? 'rtl' : 'ltr';\n return resolveAlign(stored as TextAlign, direction);\n },\n write: (edge) => edge,\n};\n\n/** Options come from the registry, so a consumer's registered entry appears in\n * the picker with no kit edit. A `select` rather than a `toggle`: nine options\n * will not fit a segmented strip. */\nconst markerOptions = () => [\n { value: '', label: 'None' },\n ...listMarkers().map((m) => ({ value: m.id, label: m.id })),\n];\n\n/** Build the standard shape schema — Layout (pose box + rotation) +\n * Appearance (fill / stroke), optionally a Text group. Matches the kit's\n * builtin-shape data template (`{ path, fill, stroke?, text? }`,\n * `useBuiltinShapeTools`). */\nfunction shapeSchema(opts: { text?: boolean } = {}): ToolPrefGroup {\n return {\n name: 'Properties',\n children: {\n layout: {\n name: 'Layout',\n children: {\n 'pose.x': { kind: 'number', name: 'X', description: 'Left edge, world units.', default: 0, pair: { with: 'pose.y', label: 'Position' } },\n 'pose.y': { kind: 'number', name: 'Y', description: 'Top edge, world units.', default: 0 },\n 'pose.width': { kind: 'number', name: 'W', description: 'Width, world units.', default: 0, min: 0, pair: { with: 'pose.height', label: 'Size' } },\n 'pose.height': { kind: 'number', name: 'H', description: 'Height, world units.', default: 0, min: 0 },\n 'pose.rotation': { kind: 'number', name: 'Rotation', description: 'Rotation about the box center.', default: 0, step: 1, unit: rotationDegreesUnit },\n },\n },\n // What the node *is* outranks how it is painted: a text node's copy is\n // the first thing an editor reaches for.\n ...(opts.text\n ? {\n content: {\n name: 'Content',\n children: {\n // `block`: the content is the section, so a 64px label column\n // reading \"Text\" inside a section reading \"Text\" is one word\n // twice and a narrower field for the sake of it.\n 'data.text': { kind: 'string', name: 'Text', description: 'Text content.', default: '', block: true },\n },\n },\n }\n : {}),\n appearance: {\n // Headless: `Fill` and `Stroke` head their own blocks, and an\n // `Appearance` rule over them names neither of the two paints a\n // reader is looking at.\n name: '',\n children: {\n // A `paint` leaf, not a `color` one: `data.fill` is the tagged\n // `FillStyle` union, so a color control pointed at it would read\n // `undefined` off a gradient and write a bare string over it.\n // `block` for the same reason every stroke field is: the kind bar\n // and the per-kind editor want the width a 64px label column takes.\n 'data.fill': { kind: 'paint', name: 'Fill', description: 'Fill paint.', default: { fill: 'solid', color: '#000000ff' }, alpha: true, block: true },\n // An object leaf: `data.stroke` is a whole `Stroke`, and its fields\n // belong to one value. Sibling leaves addressing into it would each\n // write one field of a value they can only half see.\n 'data.stroke': {\n kind: 'object',\n name: 'Stroke',\n description: 'Stroke paint and line geometry.',\n default: { paint: { fill: 'solid', color: '#000000ff' }, width: 1 },\n block: true,\n children: {\n // Every row is `block`: under a heading already reading STROKE,\n // a 64px label column spells the section name again and takes\n // the width the controls need. Each field leads with its own\n // glyph instead.\n // Unpaired: the paint field is a whole paint editor — a kind bar\n // over a per-kind body — and cannot share a row with a slider.\n width: { kind: 'number', name: 'Width', description: 'Stroke width, world units.', default: 1, min: 0, max: 20, step: 0.5, control: 'slider', icon: 'strokeWidth', block: true },\n paint: { kind: 'paint', name: 'Paint', description: 'Stroke paint.', default: { fill: 'solid', color: '#000000ff' }, alpha: true, block: true },\n // Three options each: a segmented control shows every one at\n // once where a select shows the current one and hides the rest\n // behind a click.\n cap: { kind: 'enum', name: 'Cap', description: 'How an open end is drawn.', default: 'butt', control: 'toggle', block: true, pair: { with: ['data.stroke.join', 'data.stroke.align'], label: 'Line' }, options: [{ value: 'butt', label: 'Butt', icon: 'capButt' }, { value: 'round', label: 'Round', icon: 'capRound' }, { value: 'square', label: 'Square', icon: 'capSquare' }] },\n join: { kind: 'enum', name: 'Join', description: 'How a corner is drawn.', default: 'miter', control: 'toggle', block: true, options: [{ value: 'miter', label: 'Miter', icon: 'joinMiter' }, { value: 'round', label: 'Round', icon: 'joinRound' }, { value: 'bevel', label: 'Bevel', icon: 'joinBevel' }] },\n align: { kind: 'enum', name: 'Align', description: 'Where the ribbon sits relative to the edge.', default: 'center', control: 'toggle', block: true, options: [{ value: 'inner', label: 'Inner', icon: 'alignInner' }, { value: 'center', label: 'Center', icon: 'alignCenter' }, { value: 'outer', label: 'Outer', icon: 'alignOuter' }] },\n // Its own row: three bars already fill the one above, and the\n // dash pattern is a property of the line rather than of how the\n // ribbon meets the geometry.\n dash: { kind: 'enum', name: 'Style', description: 'Solid, dashed or dotted. Dash lengths are multiples of the stroke width, so a style holds as the width changes.', default: 'solid', control: 'toggle', block: true, encoding: strokeDashEncoding, options: [{ value: 'solid', label: 'Solid', icon: 'dashSolid' }, { value: 'dashed', label: 'Dashed', icon: 'dashDashed' }, { value: 'dotted', label: 'Dotted', icon: 'dashDotted' }, { value: 'custom', label: 'Custom', icon: 'dashCustom', disabled: true }] },\n markerStart: { kind: 'enum', name: 'Start', description: 'Marker at the first vertex of each open subpath.', default: '', control: 'select', block: true, pair: { with: ['data.stroke.markerMid', 'data.stroke.markerEnd'], label: 'Markers' }, encoding: markerEncoding, options: markerOptions() },\n markerMid: { kind: 'enum', name: 'Mid', description: 'Marker at every interior authored vertex.', default: '', control: 'select', block: true, encoding: markerEncoding, options: markerOptions() },\n markerEnd: { kind: 'enum', name: 'End', description: 'Marker at the last vertex of each open subpath.', default: '', control: 'select', block: true, encoding: markerEncoding, options: markerOptions() },\n },\n },\n },\n },\n ...(opts.text\n ? {\n text: {\n // Headless: `Character` and `Paragraph` carry the labels, and a\n // `Typography` heading over them names nothing a reader can't\n // already see.\n name: '',\n children: {\n // One object leaf, not a row of siblings addressing into it:\n // `data.style` is a `TextStyle`, and its character and\n // paragraph fields are one value that reads as two lists.\n // The groups head those lists and contribute nothing to the\n // path — a field inside one is still a field of the style.\n 'data.style': {\n kind: 'object',\n name: 'Style',\n description: 'Typography for the whole node. Its color is the node\\'s own `data.fill`, in Appearance.',\n default: {},\n block: true,\n children: {\n character: {\n name: 'Character',\n children: {\n fontFamily: { kind: 'font-family', name: 'Font', description: 'Registered font family.', default: 'sans-serif' },\n fontSize: { kind: 'number', name: 'Size', description: 'Font size, world units.', default: 16, min: 1, step: 1, pair: { with: 'data.style.fontWeight', label: 'Size / weight' } },\n fontWeight: { kind: 'font-weight', name: 'Weight', description: 'Font weight, from the faces the family has.', default: 400 },\n // One row of segments, the strip every text editor\n // draws: the slant, the three rules, then the two\n // scripts. Italic is a flag here although the field\n // is `fontStyle`, whose only other value is upright.\n fontStyle: { kind: 'boolean', name: 'Italic', description: 'Set the text in its italic face.', default: false, control: 'toggle', icon: 'italic', short: ['I'], pair: { with: ['data.style.underline', 'data.style.strikethrough', 'data.style.overline', 'data.style.script'], label: 'Style' }, encoding: italicEncoding },\n underline: { kind: 'boolean', name: 'Underline', description: 'Underline the text.', default: false, control: 'toggle', icon: 'underline', short: ['U'] },\n strikethrough: { kind: 'boolean', name: 'Strikethrough', description: 'Strike through the text.', default: false, control: 'toggle', icon: 'strikethrough', short: ['S'] },\n overline: { kind: 'boolean', name: 'Overline', description: 'Rule the text above its ascent.', default: false, control: 'toggle', icon: 'overline', short: ['O'] },\n script: { kind: 'enum', name: 'Script', description: 'Set the text smaller and raised or lowered, on the line it would otherwise hold.', default: undefined, control: 'toggle', clearable: true, options: [{ value: 'super', label: 'Superscript', icon: 'superscript' }, { value: 'sub', label: 'Subscript', icon: 'subscript' }] },\n letterSpacing: { kind: 'number', name: 'Tracking', description: 'Extra advance per glyph, world units.', default: 0, step: 0.1 },\n textTransform: { kind: 'enum', name: 'Case', description: 'Draw the text in capitals, lowercase or title case. The text itself is unchanged.', default: 'none', control: 'toggle', options: [{ value: 'none', label: 'None', short: ['–'] }, { value: 'uppercase', label: 'Uppercase', short: ['AA'] }, { value: 'lowercase', label: 'Lowercase', short: ['aa'] }, { value: 'capitalize', label: 'Capitalize', short: ['Aa'] }] },\n fontVariantCaps: { kind: 'enum', name: 'Small caps', description: 'Draw lowercase letters as smaller capitals. The text itself is unchanged.', default: 'normal', control: 'toggle', options: [{ value: 'normal', label: 'Normal', short: ['–'] }, { value: 'small-caps', label: 'Small caps', short: ['Sc'] }] },\n },\n },\n paragraph: {\n name: 'Paragraph',\n children: {\n lineHeight: { kind: 'number', name: 'Leading', description: 'Line height as a multiple of font size.', default: 1.2, min: 0.5, step: 0.1 },\n wrap: { kind: 'boolean', name: 'Wrap', description: 'Break lines between words at the box width.', default: false, control: 'switch' },\n direction: { kind: 'enum', name: 'Direction', description: 'Reading direction.', default: 'ltr', control: 'toggle', options: [{ value: 'ltr', label: 'Left to right', short: ['LTR'] }, { value: 'rtl', label: 'Right to left', short: ['RTL'] }] },\n // Last, so the box's vertical alignment — a field of\n // the node rather than of its style — follows it.\n align: { kind: 'enum', name: 'Align', description: 'Horizontal alignment.', default: 'left', control: 'toggle', encoding: alignEncoding, options: [{ value: 'left', label: 'Left', icon: 'textAlignLeft' }, { value: 'center', label: 'Center', icon: 'textAlignCenter' }, { value: 'right', label: 'Right', icon: 'textAlignRight' }, { value: 'justify', label: 'Justify', icon: 'textAlignJustify' }] },\n },\n },\n },\n },\n 'data.verticalAlign': { kind: 'enum', name: 'Vertical', description: 'Where the lines sit in the box\\'s height.', default: 'top', control: 'toggle', options: [{ value: 'top', label: 'Top', icon: 'textAlignTop' }, { value: 'center', label: 'Middle', icon: 'textAlignMiddle' }, { value: 'bottom', label: 'Bottom', icon: 'textAlignBottom' }] },\n },\n },\n }\n : {}),\n },\n };\n}\n\n/**\n * Default properties-trait entries covering the kit's built-in shape\n * kinds (`KIT_SHAPE_KINDS`) — the sibling of `defaultNodeRouting`. Every\n * kind gets the standard box schema; `text` adds a Text group. In\n * lockstep with `KIT_SHAPE_KINDS` by construction (derived via `.map`).\n */\nexport const defaultNodeProperties: readonly NodePropertiesEntry[] =\n KIT_SHAPE_KINDS.map((name) => ({\n name,\n schema: shapeSchema({ text: name === 'text' }),\n }));\n\n/**\n * Properties-trait entries for the *inferred* routing kinds\n * (`inferredNodeRouting`: `text` / `path` / `image`) — the vocabulary\n * consumers produce when they don't tag `data.kind` (e.g. WeaselDraw).\n * In lockstep with `inferredNodeRouting` by construction.\n */\nexport const inferredNodeProperties: readonly NodePropertiesEntry[] =\n inferredNodeRouting.map((e) => ({\n name: e.name,\n schema: shapeSchema({ text: e.name === 'text' }),\n }));\n"],"mappings":"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"}