dsviz 0.0.0-stage → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -2
  3. package/dist/camera.d.ts +25 -0
  4. package/dist/camera.js +27 -0
  5. package/dist/camera.js.map +7 -0
  6. package/dist/connector.d.ts +65 -0
  7. package/dist/connector.js +108 -0
  8. package/dist/connector.js.map +7 -0
  9. package/dist/describe.d.ts +44 -0
  10. package/dist/describe.js +131 -0
  11. package/dist/describe.js.map +7 -0
  12. package/dist/ds/array.d.ts +51 -0
  13. package/dist/ds/array.js +62 -0
  14. package/dist/ds/array.js.map +7 -0
  15. package/dist/ds/common.d.ts +9 -0
  16. package/dist/ds/common.js +10 -0
  17. package/dist/ds/common.js.map +7 -0
  18. package/dist/ds/graph.d.ts +39 -0
  19. package/dist/ds/graph.js +57 -0
  20. package/dist/ds/graph.js.map +7 -0
  21. package/dist/ds/grid.d.ts +30 -0
  22. package/dist/ds/grid.js +41 -0
  23. package/dist/ds/grid.js.map +7 -0
  24. package/dist/ds/index.d.ts +8 -0
  25. package/dist/ds/index.js +9 -0
  26. package/dist/ds/index.js.map +7 -0
  27. package/dist/ds/linkedList.d.ts +46 -0
  28. package/dist/ds/linkedList.js +68 -0
  29. package/dist/ds/linkedList.js.map +7 -0
  30. package/dist/ds/pointer.d.ts +23 -0
  31. package/dist/ds/pointer.js +35 -0
  32. package/dist/ds/pointer.js.map +7 -0
  33. package/dist/ds/stackQueue.d.ts +5 -0
  34. package/dist/ds/stackQueue.js +49 -0
  35. package/dist/ds/stackQueue.js.map +7 -0
  36. package/dist/ds/tree.d.ts +53 -0
  37. package/dist/ds/tree.js +87 -0
  38. package/dist/ds/tree.js.map +7 -0
  39. package/dist/easing.d.ts +14 -0
  40. package/dist/easing.js +21 -0
  41. package/dist/easing.js.map +7 -0
  42. package/dist/fx.d.ts +21 -0
  43. package/dist/fx.js +27 -0
  44. package/dist/fx.js.map +7 -0
  45. package/dist/geom/index.d.ts +2 -0
  46. package/dist/geom/index.js +3 -0
  47. package/dist/geom/index.js.map +7 -0
  48. package/dist/geom/math.d.ts +34 -0
  49. package/dist/geom/math.js +74 -0
  50. package/dist/geom/math.js.map +7 -0
  51. package/dist/geom/shapes.d.ts +69 -0
  52. package/dist/geom/shapes.js +105 -0
  53. package/dist/geom/shapes.js.map +7 -0
  54. package/dist/handle.d.ts +30 -0
  55. package/dist/handle.js +13 -0
  56. package/dist/handle.js.map +7 -0
  57. package/dist/index.d.ts +36 -0
  58. package/dist/index.js +35 -0
  59. package/dist/index.js.map +7 -0
  60. package/dist/mathtext.d.ts +29 -0
  61. package/dist/mathtext.js +142 -0
  62. package/dist/mathtext.js.map +7 -0
  63. package/dist/mem/index.d.ts +2 -0
  64. package/dist/mem/index.js +3 -0
  65. package/dist/mem/index.js.map +7 -0
  66. package/dist/mem/memory.d.ts +47 -0
  67. package/dist/mem/memory.js +41 -0
  68. package/dist/mem/memory.js.map +7 -0
  69. package/dist/mem/stackHeap.d.ts +48 -0
  70. package/dist/mem/stackHeap.js +76 -0
  71. package/dist/mem/stackHeap.js.map +7 -0
  72. package/dist/path.d.ts +23 -0
  73. package/dist/path.js +49 -0
  74. package/dist/path.js.map +7 -0
  75. package/dist/player.d.ts +49 -0
  76. package/dist/player.js +100 -0
  77. package/dist/player.js.map +7 -0
  78. package/dist/plot/index.d.ts +2 -0
  79. package/dist/plot/index.js +3 -0
  80. package/dist/plot/index.js.map +7 -0
  81. package/dist/plot/marks.d.ts +120 -0
  82. package/dist/plot/marks.js +200 -0
  83. package/dist/plot/marks.js.map +7 -0
  84. package/dist/plot/plane.d.ts +45 -0
  85. package/dist/plot/plane.js +41 -0
  86. package/dist/plot/plane.js.map +7 -0
  87. package/dist/prob/bars.d.ts +67 -0
  88. package/dist/prob/bars.js +82 -0
  89. package/dist/prob/bars.js.map +7 -0
  90. package/dist/prob/index.d.ts +3 -0
  91. package/dist/prob/index.js +4 -0
  92. package/dist/prob/index.js.map +7 -0
  93. package/dist/prob/tree.d.ts +39 -0
  94. package/dist/prob/tree.js +57 -0
  95. package/dist/prob/tree.js.map +7 -0
  96. package/dist/prob/venn.d.ts +38 -0
  97. package/dist/prob/venn.js +89 -0
  98. package/dist/prob/venn.js.map +7 -0
  99. package/dist/react/Diagram.d.ts +47 -0
  100. package/dist/react/Diagram.js +41 -0
  101. package/dist/react/Diagram.js.map +7 -0
  102. package/dist/react/MathText.d.ts +9 -0
  103. package/dist/react/MathText.js +15 -0
  104. package/dist/react/MathText.js.map +7 -0
  105. package/dist/react/controls-styles.d.ts +9 -0
  106. package/dist/react/controls-styles.js +109 -0
  107. package/dist/react/controls-styles.js.map +7 -0
  108. package/dist/react/controls.d.ts +84 -0
  109. package/dist/react/controls.js +146 -0
  110. package/dist/react/controls.js.map +7 -0
  111. package/dist/react/hooks.d.ts +48 -0
  112. package/dist/react/hooks.js +67 -0
  113. package/dist/react/hooks.js.map +7 -0
  114. package/dist/react/index.d.ts +4 -0
  115. package/dist/react/index.js +5 -0
  116. package/dist/react/index.js.map +7 -0
  117. package/dist/render.d.ts +61 -0
  118. package/dist/render.js +294 -0
  119. package/dist/render.js.map +7 -0
  120. package/dist/scene.d.ts +138 -0
  121. package/dist/scene.js +94 -0
  122. package/dist/scene.js.map +7 -0
  123. package/dist/sched/gantt.d.ts +49 -0
  124. package/dist/sched/gantt.js +65 -0
  125. package/dist/sched/gantt.js.map +7 -0
  126. package/dist/sched/index.d.ts +2 -0
  127. package/dist/sched/index.js +3 -0
  128. package/dist/sched/index.js.map +7 -0
  129. package/dist/sched/simulate.d.ts +55 -0
  130. package/dist/sched/simulate.js +78 -0
  131. package/dist/sched/simulate.js.map +7 -0
  132. package/dist/static.d.ts +8 -0
  133. package/dist/static.js +26 -0
  134. package/dist/static.js.map +7 -0
  135. package/dist/styles.d.ts +3 -0
  136. package/dist/styles.js +241 -0
  137. package/dist/styles.js.map +7 -0
  138. package/dist/sys/architecture.d.ts +104 -0
  139. package/dist/sys/architecture.js +167 -0
  140. package/dist/sys/architecture.js.map +7 -0
  141. package/dist/sys/index.d.ts +1 -0
  142. package/dist/sys/index.js +2 -0
  143. package/dist/sys/index.js.map +7 -0
  144. package/dist/themes.d.ts +13 -0
  145. package/dist/themes.js +8 -0
  146. package/dist/themes.js.map +7 -0
  147. package/dist/uml/classDiagram.d.ts +72 -0
  148. package/dist/uml/classDiagram.js +84 -0
  149. package/dist/uml/classDiagram.js.map +7 -0
  150. package/dist/uml/index.d.ts +3 -0
  151. package/dist/uml/index.js +4 -0
  152. package/dist/uml/index.js.map +7 -0
  153. package/dist/uml/sequence.d.ts +58 -0
  154. package/dist/uml/sequence.js +121 -0
  155. package/dist/uml/sequence.js.map +7 -0
  156. package/dist/uml/stateMachine.d.ts +40 -0
  157. package/dist/uml/stateMachine.js +89 -0
  158. package/dist/uml/stateMachine.js.map +7 -0
  159. package/dist/vec.d.ts +21 -0
  160. package/dist/vec.js +24 -0
  161. package/dist/vec.js.map +7 -0
  162. package/package.json +93 -4
  163. package/src/camera.ts +50 -0
  164. package/src/connector.ts +182 -0
  165. package/src/describe.ts +181 -0
  166. package/src/ds/array.ts +108 -0
  167. package/src/ds/common.ts +16 -0
  168. package/src/ds/graph.ts +104 -0
  169. package/src/ds/grid.ts +70 -0
  170. package/src/ds/index.ts +8 -0
  171. package/src/ds/linkedList.ts +117 -0
  172. package/src/ds/pointer.ts +55 -0
  173. package/src/ds/stackQueue.ts +59 -0
  174. package/src/ds/tree.ts +139 -0
  175. package/src/easing.ts +24 -0
  176. package/src/fx.ts +46 -0
  177. package/src/geom/index.ts +2 -0
  178. package/src/geom/math.ts +85 -0
  179. package/src/geom/shapes.ts +158 -0
  180. package/src/handle.ts +40 -0
  181. package/src/index.ts +37 -0
  182. package/src/mathtext.ts +139 -0
  183. package/src/mem/index.ts +2 -0
  184. package/src/mem/memory.ts +85 -0
  185. package/src/mem/stackHeap.ts +127 -0
  186. package/src/path.ts +68 -0
  187. package/src/player.ts +153 -0
  188. package/src/plot/index.ts +2 -0
  189. package/src/plot/marks.ts +298 -0
  190. package/src/plot/plane.ts +82 -0
  191. package/src/prob/bars.ts +132 -0
  192. package/src/prob/index.ts +3 -0
  193. package/src/prob/tree.ts +94 -0
  194. package/src/prob/venn.ts +144 -0
  195. package/src/react/Diagram.tsx +97 -0
  196. package/src/react/MathText.tsx +21 -0
  197. package/src/react/controls-styles.ts +111 -0
  198. package/src/react/controls.tsx +297 -0
  199. package/src/react/hooks.ts +112 -0
  200. package/src/react/index.ts +4 -0
  201. package/src/render.ts +445 -0
  202. package/src/scene.ts +238 -0
  203. package/src/sched/gantt.ts +110 -0
  204. package/src/sched/index.ts +2 -0
  205. package/src/sched/simulate.ts +140 -0
  206. package/src/static.ts +35 -0
  207. package/src/styles.ts +272 -0
  208. package/src/sys/architecture.ts +257 -0
  209. package/src/sys/index.ts +1 -0
  210. package/src/themes.ts +26 -0
  211. package/src/uml/classDiagram.ts +145 -0
  212. package/src/uml/index.ts +3 -0
  213. package/src/uml/sequence.ts +218 -0
  214. package/src/uml/stateMachine.ts +133 -0
  215. package/src/vec.ts +28 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/react/controls.tsx"],
4
+ "sourcesContent": ["import * as RSelect from '@radix-ui/react-select';\nimport * as RSlider from '@radix-ui/react-slider';\nimport * as RSwitch from '@radix-ui/react-switch';\nimport * as RToggle from '@radix-ui/react-toggle-group';\nimport { ReactNode, useEffect, useId, useRef, useState } from 'react';\nimport { injectControlStyles } from './controls-styles';\nimport type { PlayerState } from './hooks';\n\n/*\n * Controls. Each is a thin skin over a Radix primitive, so keyboard handling,\n * focus, ARIA and pointer capture are the library's job, not ours. The skin is\n * plain CSS on the same --dv-* variables the SVG uses (controls-styles.ts), which\n * means the diagram and its controls always match, and getbetter can restyle both\n * from one place. In getbetter you can also swap any of these for the app's own\n * shadcn components: they all take simple value/onChange props.\n */\n\nconst cx = (...c: Array<string | false | undefined>) => c.filter(Boolean).join(' ');\n\n/**\n * A readout whose width never changes. The slider track is flex: 1 next to it,\n * so if \"9\" \u2192 \"10\" made the readout wider, the track (and every notch on it)\n * would shift sideways the moment you started sliding. The trick: an invisible\n * copy of the widest text sits in the same grid cell as the real text, so the\n * box is always exactly as wide as the widest value will ever be.\n */\nfunction Readout({ children, widest }: { children: ReactNode; widest?: string }) {\n return (\n <span className=\"dvc-readout\">\n {widest && <span className=\"dvc-readout-sizer\" aria-hidden>{widest}</span>}\n <span>{children}</span>\n </span>\n );\n}\n\nfunction Field({ label, readout, widest, children, id }: { label?: string; readout?: ReactNode; widest?: string; children: ReactNode; id?: string }) {\n return (\n <div className=\"dvc dvc-field\">\n {label && <label className=\"dvc-label\" htmlFor={id}>{label}</label>}\n {children}\n {readout !== undefined && <Readout widest={widest}>{readout}</Readout>}\n </div>\n );\n}\n\n/** Width of an element, kept up to date. */\nfunction useWidth<T extends HTMLElement>() {\n const ref = useRef<T>(null);\n const [w, setW] = useState(0);\n useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(([e]) => setW(e.contentRect.width));\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n return [ref, w] as const;\n}\n\n/**\n * Notches as dots inside the track. They live in a layer inset by half the\n * thumb's width on each side, because Radix keeps the thumb inside the track:\n * its centre at fraction p is r + p\u00B7(W \u2212 2r), not p\u00B7W. Same formula here, so a\n * notch sits exactly under the thumb at its value. Too dense to read \u2192 none.\n */\nfunction Ticks({ fractions, on, width }: { fractions: number[]; on: (i: number) => boolean; width: number }) {\n if (fractions.length < 2 || !width || width / (fractions.length - 1) < 10) return null;\n return (\n <span className=\"dvc-ticks\" aria-hidden>\n {fractions.map((f, i) => <span key={i} className={cx('dvc-tick', on(i) && 'is-on')} style={{ left: `${f * 100}%` }} />)}\n </span>\n );\n}\n\n/** The longest string a formatter produces at the ends of its range (a good guess for the widest). */\nconst widestOf = (format: ((v: number) => ReactNode) | undefined, min: number, max: number, step: number) => {\n if (!format) return undefined;\n const xs = [min, max, min + step, max - step].map((v) => format(v)).filter((x): x is string | number => typeof x === 'string' || typeof x === 'number');\n return xs.map(String).sort((a, b) => b.length - a.length)[0];\n};\n\nexport interface SliderProps {\n value: number;\n onChange: (v: number) => void;\n min: number;\n max: number;\n step?: number;\n label?: string;\n /** Right-hand readout, e.g. `n = 64`. */\n format?: (v: number) => ReactNode;\n /** Draw a tick at each of these values. */\n ticks?: number[];\n className?: string;\n}\n\nexport function Slider({ value, onChange, min, max, step = 1, label, format, ticks, className }: SliderProps) {\n injectControlStyles();\n const id = useId();\n const [ref, width] = useWidth<HTMLSpanElement>();\n return (\n <Field label={label} id={id} readout={format ? format(value) : undefined} widest={widestOf(format, min, max, step)}>\n <RSlider.Root ref={ref} id={id} className={cx('dvc-slider', className)} value={[value]} min={min} max={max} step={step} onValueChange={([v]) => onChange(v)}>\n <RSlider.Track className=\"dvc-track\">\n <RSlider.Range className=\"dvc-range\" />\n </RSlider.Track>\n {ticks && <Ticks fractions={ticks.map((t) => (t - min) / (max - min))} on={(i) => ticks[i] <= value} width={width} />}\n <RSlider.Thumb className=\"dvc-thumb\" aria-label={label ?? 'value'} />\n </RSlider.Root>\n </Field>\n );\n}\n\nexport interface SegmentedProps<T extends string> {\n value: T;\n onChange: (v: T) => void;\n options: ReadonlyArray<T | { value: T; label: ReactNode }>;\n label?: string;\n}\n\n/** Use instead of a dropdown when there are 2\u20134 options: every choice stays visible, one click. */\nexport function Segmented<T extends string>({ value, onChange, options, label }: SegmentedProps<T>) {\n injectControlStyles();\n return (\n <Field label={label}>\n <RToggle.Root type=\"single\" className=\"dvc-seg\" value={value} onValueChange={(v) => v && onChange(v as T)} aria-label={label}>\n {options.map((o) => {\n const v = typeof o === 'string' ? o : o.value;\n return <RToggle.Item key={v} value={v} className=\"dvc-seg-item\">{typeof o === 'string' ? o : o.label}</RToggle.Item>;\n })}\n </RToggle.Root>\n </Field>\n );\n}\n\nexport interface SelectProps<T extends string> {\n value: T;\n onChange: (v: T) => void;\n options: ReadonlyArray<T | { value: T; label: ReactNode }>;\n label?: string;\n}\n\nexport function Select<T extends string>({ value, onChange, options, label }: SelectProps<T>) {\n injectControlStyles();\n const norm = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o));\n return (\n <Field label={label}>\n <RSelect.Root value={value} onValueChange={(v) => onChange(v as T)}>\n <RSelect.Trigger className=\"dvc-select\" aria-label={label}>\n <RSelect.Value />\n <RSelect.Icon className=\"dvc-caret\">\u25BE</RSelect.Icon>\n </RSelect.Trigger>\n <RSelect.Portal>\n <RSelect.Content className=\"dvc dvc-menu\" position=\"popper\" sideOffset={4}>\n <RSelect.Viewport>\n {norm.map((o) => (\n <RSelect.Item key={o.value} value={o.value} className=\"dvc-menu-item\">\n <RSelect.ItemText>{o.label}</RSelect.ItemText>\n </RSelect.Item>\n ))}\n </RSelect.Viewport>\n </RSelect.Content>\n </RSelect.Portal>\n </RSelect.Root>\n </Field>\n );\n}\n\nexport function Switch({ checked, onChange, label }: { checked: boolean; onChange: (v: boolean) => void; label?: string }) {\n injectControlStyles();\n const id = useId();\n return (\n <Field label={label} id={id}>\n <RSwitch.Root id={id} className=\"dvc-switch\" checked={checked} onCheckedChange={onChange}>\n <RSwitch.Thumb className=\"dvc-switch-thumb\" />\n </RSwitch.Root>\n </Field>\n );\n}\n\nexport interface NumberStepperProps {\n value: number;\n onChange: (v: number) => void;\n min?: number;\n max?: number;\n step?: number;\n label?: string;\n}\n\n/** \u2212 n + : for small integers where dragging a slider would be fiddly (a search target). */\nexport function NumberStepper({ value, onChange, min = -Infinity, max = Infinity, step = 1, label }: NumberStepperProps) {\n injectControlStyles();\n const set = (v: number) => onChange(Math.min(max, Math.max(min, v)));\n return (\n <Field label={label}>\n <div className=\"dvc dvc-stepper\" role=\"group\" aria-label={label}>\n <button type=\"button\" onClick={() => set(value - step)} disabled={value <= min} aria-label=\"decrease\">\u2212</button>\n <output>{value}</output>\n <button type=\"button\" onClick={() => set(value + step)} disabled={value >= max} aria-label=\"increase\">+</button>\n </div>\n </Field>\n );\n}\n\nexport interface TransportProps {\n /** What usePlayer() returns. */\n player: Pick<PlayerState<unknown>, 'index' | 'count' | 'playing' | 'first' | 'prev' | 'toggle' | 'next' | 'seek'>;\n}\n\nconst Icon = ({ d }: { d: string }) => <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden><path d={d} fill=\"currentColor\" /></svg>;\n\n/** first / prev / play / next, a scrubber with a tick per step, and \"Step i / n\". `<Transport player={usePlayer(...)} />` */\nexport function Transport({ player: p }: TransportProps) {\n injectControlStyles();\n const last = Math.max(0, p.count - 1);\n const [ref, width] = useWidth<HTMLSpanElement>();\n return (\n <div className=\"dvc dvc-transport\">\n <button type=\"button\" onClick={p.first} disabled={p.index === 0} aria-label=\"First step\"><Icon d=\"M3 3h2v10H3zM13 3v10L6 8z\" /></button>\n <button type=\"button\" onClick={p.prev} disabled={p.index === 0} aria-label=\"Previous step\"><Icon d=\"M12 3v10L5 8z\" /></button>\n <button type=\"button\" className=\"is-play\" onClick={p.toggle} aria-label={p.playing ? 'Pause' : 'Play'}>\n <Icon d={p.playing ? 'M4 3h3v10H4zM9 3h3v10H9z' : 'M4 3l9 5-9 5z'} />\n </button>\n <button type=\"button\" onClick={p.next} disabled={p.index >= last} aria-label=\"Next step\"><Icon d=\"M4 3v10l7-5zM11 3h2v10h-2z\" /></button>\n <RSlider.Root ref={ref} className=\"dvc-slider dvc-scrub\" value={[p.index]} min={0} max={last} step={1} onValueChange={([v]) => p.seek(v)}>\n <RSlider.Track className=\"dvc-track\">\n <RSlider.Range className=\"dvc-range\" />\n </RSlider.Track>\n <Ticks fractions={Array.from({ length: p.count }, (_, i) => (last ? i / last : 0))} on={(i) => i <= p.index} width={width} />\n <RSlider.Thumb className=\"dvc-thumb\" aria-label=\"Step\" />\n </RSlider.Root>\n <Readout widest={`Step ${last} / ${last}`}>Step {p.index} / {last}</Readout>\n </div>\n );\n}\n\n/**\n * A panel of source lines with the active one(s) highlighted. Feed it `frame.line`\n * from a generator that yields `{ ..., line }`. Indentation is preserved.\n */\nexport function CodeTrace({ lines, active }: { lines: readonly string[]; active?: number | readonly number[] | null }) {\n injectControlStyles();\n const on = new Set(active == null ? [] : typeof active === 'number' ? [active] : active);\n return (\n <pre className=\"dvc dvc-code\" aria-label=\"Algorithm steps\">\n {lines.map((l, i) => (\n <div key={i} className={cx('dvc-code-line', on.has(i) && 'is-active')} aria-current={on.has(i) ? 'step' : undefined}>{l || ' '}</div>\n ))}\n </pre>\n );\n}\n\nexport interface ButtonProps {\n children: ReactNode;\n onClick: () => void;\n /** primary = filled, ghost = outlined. */\n variant?: 'primary' | 'ghost';\n disabled?: boolean;\n title?: string;\n}\n\nexport function Button({ children, onClick, variant = 'ghost', disabled, title }: ButtonProps) {\n injectControlStyles();\n return (\n <button type=\"button\" className={cx('dvc dvc-btn', variant === 'primary' && 'is-primary')} onClick={onClick} disabled={disabled} title={title}>\n {children}\n </button>\n );\n}\n\nexport interface TextInputProps {\n value: string;\n /** Called on Enter or blur (not every keystroke), so half-typed input doesn't re-run a diagram. */\n onCommit: (v: string) => void;\n label?: string;\n width?: number | string;\n placeholder?: string;\n}\n\nexport function TextInput({ value, onCommit, label, width = '10ch', placeholder }: TextInputProps) {\n injectControlStyles();\n const id = useId();\n return (\n <Field label={label} id={id}>\n <input\n id={id}\n key={value /* reset the uncontrolled input when the value changes from outside */}\n className=\"dvc-input\"\n defaultValue={value}\n placeholder={placeholder}\n spellCheck={false}\n style={{ width }}\n onKeyDown={(e) => e.key === 'Enter' && onCommit((e.target as HTMLInputElement).value)}\n onBlur={(e) => e.target.value !== value && onCommit(e.target.value)}\n />\n </Field>\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,48 @@
1
+ import { DependencyList } from 'react';
2
+ import { SceneNode } from '../scene';
3
+ import { PlayerOptions } from '../player';
4
+ export interface PlayerState<S> {
5
+ frame: S | undefined;
6
+ /** Every frame, computed up front. Handy for a fixed camera: `fitViewBox(frames.map(draw))`. */
7
+ frames: readonly S[];
8
+ index: number;
9
+ count: number;
10
+ playing: boolean;
11
+ next(): void;
12
+ prev(): void;
13
+ first(): void;
14
+ seek(i: number): void;
15
+ toggle(): void;
16
+ play(): void;
17
+ pause(): void;
18
+ }
19
+ /**
20
+ * Step through generator frames. `makeFrames` is re-run whenever `deps` change
21
+ * (new input => new run), which resets to step 0.
22
+ *
23
+ * const p = usePlayer(() => binarySearch(nums, target), [nums, target]);
24
+ * p.frame // the current snapshot
25
+ *
26
+ * The Player is a plain mutable object, so React needs a nudge to re-render when
27
+ * it changes: we subscribe and bump a counter (useReducer's cheapest form).
28
+ */
29
+ export interface UsePlayerOptions<S> extends Omit<PlayerOptions<S>, 'onFrame'> {
30
+ /** Open on the last frame (show the finished result first, let people rewind). Default 'first'. */
31
+ start?: 'first' | 'end';
32
+ }
33
+ export declare function usePlayer<S>(makeFrames: () => Iterable<S>, deps: DependencyList, options?: UsePlayerOptions<S>): PlayerState<S>;
34
+ /** A counter that increments every `ms` while `active`. Drives time-based diagrams (a "now" cursor, a sim). */
35
+ export declare function useTicker(ms: number, active?: boolean): number;
36
+ /** Memoise a scene from state. Just `useMemo`, named so the intent reads well at call sites. */
37
+ export declare const useScene: <T>(build: () => T, deps: DependencyList) => T;
38
+ /**
39
+ * Celebrations inside a diagram. `fire(x, y)` adds a burst of particles at a
40
+ * scene position; they clear themselves a moment later.
41
+ *
42
+ * const [particles, fire] = useBurst();
43
+ * <Diagram scene={combine(stuff, particles)} />
44
+ */
45
+ export declare function useBurst(lifetime?: number): [SceneNode[], (x: number, y: number, opts?: {
46
+ count?: number;
47
+ radius?: number;
48
+ }) => void];
@@ -0,0 +1,67 @@
1
+ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
2
+ import { burst } from "../fx.js";
3
+ import { createPlayer } from "../player.js";
4
+ function usePlayer(makeFrames, deps, options) {
5
+ const [, bump] = useReducer((n) => n + 1, 0);
6
+ const ref = useRef(null);
7
+ if (!ref.current) {
8
+ ref.current = createPlayer(makeFrames(), options);
9
+ if (options?.start === "end") ref.current.seek(ref.current.frames.length - 1);
10
+ }
11
+ const player = ref.current;
12
+ useEffect(() => {
13
+ const off = player.subscribe(() => bump());
14
+ return () => (off(), void 0);
15
+ }, [player]);
16
+ const first = useRef(true);
17
+ useEffect(() => {
18
+ if (first.current) return void (first.current = false);
19
+ player.load(makeFrames());
20
+ if (options?.start === "end") player.seek(player.frames.length - 1);
21
+ }, deps);
22
+ useEffect(() => () => player.destroy(), [player]);
23
+ return {
24
+ frame: player.frame,
25
+ frames: player.frames,
26
+ index: player.index,
27
+ count: player.frames.length,
28
+ playing: player.playing,
29
+ next: player.next,
30
+ prev: player.prev,
31
+ first: useCallback(() => player.seek(0), [player]),
32
+ seek: player.seek,
33
+ toggle: player.toggle,
34
+ play: player.play,
35
+ pause: player.pause
36
+ };
37
+ }
38
+ function useTicker(ms, active = true) {
39
+ const [n, setN] = useState(0);
40
+ useEffect(() => {
41
+ if (!active) return;
42
+ const id = setInterval(() => setN((v) => v + 1), ms);
43
+ return () => clearInterval(id);
44
+ }, [ms, active]);
45
+ return n;
46
+ }
47
+ const useScene = (build, deps) => useMemo(build, deps);
48
+ function useBurst(lifetime = 650) {
49
+ const [nodes, setNodes] = useState([]);
50
+ const n = useRef(0);
51
+ const timers = useRef([]);
52
+ useEffect(() => () => timers.current.forEach(clearTimeout), []);
53
+ const fire = useCallback((x, y, opts) => {
54
+ const id = `burst${n.current++}`;
55
+ const parts = burst({ id, x, y, ...opts });
56
+ setNodes((cur) => [...cur, ...parts]);
57
+ timers.current.push(window.setTimeout(() => setNodes((cur) => cur.filter((p) => !p.id.startsWith(`${id}:`))), lifetime));
58
+ }, [lifetime]);
59
+ return [nodes, fire];
60
+ }
61
+ export {
62
+ useBurst,
63
+ usePlayer,
64
+ useScene,
65
+ useTicker
66
+ };
67
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/react/hooks.ts"],
4
+ "sourcesContent": ["import { DependencyList, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';\nimport { burst } from '../fx';\nimport { SceneNode } from '../scene';\nimport { Player, PlayerOptions, createPlayer } from '../player';\n\nexport interface PlayerState<S> {\n frame: S | undefined;\n /** Every frame, computed up front. Handy for a fixed camera: `fitViewBox(frames.map(draw))`. */\n frames: readonly S[];\n index: number;\n count: number;\n playing: boolean;\n next(): void;\n prev(): void;\n first(): void;\n seek(i: number): void;\n toggle(): void;\n play(): void;\n pause(): void;\n}\n\n/**\n * Step through generator frames. `makeFrames` is re-run whenever `deps` change\n * (new input => new run), which resets to step 0.\n *\n * const p = usePlayer(() => binarySearch(nums, target), [nums, target]);\n * p.frame // the current snapshot\n *\n * The Player is a plain mutable object, so React needs a nudge to re-render when\n * it changes: we subscribe and bump a counter (useReducer's cheapest form).\n */\nexport interface UsePlayerOptions<S> extends Omit<PlayerOptions<S>, 'onFrame'> {\n /** Open on the last frame (show the finished result first, let people rewind). Default 'first'. */\n start?: 'first' | 'end';\n}\n\nexport function usePlayer<S>(makeFrames: () => Iterable<S>, deps: DependencyList, options?: UsePlayerOptions<S>): PlayerState<S> {\n const [, bump] = useReducer((n: number) => n + 1, 0);\n const ref = useRef<Player<S> | null>(null);\n if (!ref.current) {\n ref.current = createPlayer<S>(makeFrames(), options);\n if (options?.start === 'end') ref.current.seek(ref.current.frames.length - 1);\n }\n const player = ref.current;\n\n useEffect(() => {\n const off = player.subscribe(() => bump());\n return () => (off(), void 0);\n }, [player]);\n\n const first = useRef(true);\n useEffect(() => {\n // The first run already loaded frames in createPlayer; only reload on later changes.\n if (first.current) return void (first.current = false);\n player.load(makeFrames());\n if (options?.start === 'end') player.seek(player.frames.length - 1);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n useEffect(() => () => player.destroy(), [player]);\n\n return {\n frame: player.frame,\n frames: player.frames,\n index: player.index,\n count: player.frames.length,\n playing: player.playing,\n next: player.next,\n prev: player.prev,\n first: useCallback(() => player.seek(0), [player]),\n seek: player.seek,\n toggle: player.toggle,\n play: player.play,\n pause: player.pause,\n };\n}\n\n/** A counter that increments every `ms` while `active`. Drives time-based diagrams (a \"now\" cursor, a sim). */\nexport function useTicker(ms: number, active = true): number {\n const [n, setN] = useState(0);\n useEffect(() => {\n if (!active) return;\n const id = setInterval(() => setN((v) => v + 1), ms);\n return () => clearInterval(id);\n }, [ms, active]);\n return n;\n}\n\n/** Memoise a scene from state. Just `useMemo`, named so the intent reads well at call sites. */\nexport const useScene = <T,>(build: () => T, deps: DependencyList): T => useMemo(build, deps);\n\n/**\n * Celebrations inside a diagram. `fire(x, y)` adds a burst of particles at a\n * scene position; they clear themselves a moment later.\n *\n * const [particles, fire] = useBurst();\n * <Diagram scene={combine(stuff, particles)} />\n */\nexport function useBurst(lifetime = 650): [SceneNode[], (x: number, y: number, opts?: { count?: number; radius?: number }) => void] {\n const [nodes, setNodes] = useState<SceneNode[]>([]);\n const n = useRef(0);\n const timers = useRef<number[]>([]);\n useEffect(() => () => timers.current.forEach(clearTimeout), []);\n const fire = useCallback((x: number, y: number, opts?: { count?: number; radius?: number }) => {\n const id = `burst${n.current++}`;\n const parts = burst({ id, x, y, ...opts });\n setNodes((cur) => [...cur, ...parts]);\n // removing them from the scene IS the fade-out\n timers.current.push(window.setTimeout(() => setNodes((cur) => cur.filter((p) => !p.id.startsWith(`${id}:`))), lifetime));\n }, [lifetime]);\n return [nodes, fire];\n}\n"],
5
+ "mappings": "AAAA,SAAyB,aAAa,WAAW,SAAS,YAAY,QAAQ,gBAAgB;AAC9F,SAAS,aAAa;AAEtB,SAAgC,oBAAoB;AAiC7C,SAAS,UAAa,YAA+B,MAAsB,SAA+C;AAC/H,QAAM,CAAC,EAAE,IAAI,IAAI,WAAW,CAAC,MAAc,IAAI,GAAG,CAAC;AACnD,QAAM,MAAM,OAAyB,IAAI;AACzC,MAAI,CAAC,IAAI,SAAS;AAChB,QAAI,UAAU,aAAgB,WAAW,GAAG,OAAO;AACnD,QAAI,SAAS,UAAU,MAAO,KAAI,QAAQ,KAAK,IAAI,QAAQ,OAAO,SAAS,CAAC;AAAA,EAC9E;AACA,QAAM,SAAS,IAAI;AAEnB,YAAU,MAAM;AACd,UAAM,MAAM,OAAO,UAAU,MAAM,KAAK,CAAC;AACzC,WAAO,OAAO,IAAI,GAAG;AAAA,EACvB,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,QAAQ,OAAO,IAAI;AACzB,YAAU,MAAM;AAEd,QAAI,MAAM,QAAS,QAAO,MAAM,MAAM,UAAU;AAChD,WAAO,KAAK,WAAW,CAAC;AACxB,QAAI,SAAS,UAAU,MAAO,QAAO,KAAK,OAAO,OAAO,SAAS,CAAC;AAAA,EAEpE,GAAG,IAAI;AAEP,YAAU,MAAM,MAAM,OAAO,QAAQ,GAAG,CAAC,MAAM,CAAC;AAEhD,SAAO;AAAA,IACL,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,IACf,OAAO,OAAO;AAAA,IACd,OAAO,OAAO,OAAO;AAAA,IACrB,SAAS,OAAO;AAAA,IAChB,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,OAAO,YAAY,MAAM,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;AAAA,IACjD,MAAM,OAAO;AAAA,IACb,QAAQ,OAAO;AAAA,IACf,MAAM,OAAO;AAAA,IACb,OAAO,OAAO;AAAA,EAChB;AACF;AAGO,SAAS,UAAU,IAAY,SAAS,MAAc;AAC3D,QAAM,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;AAC5B,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,KAAK,YAAY,MAAM,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE;AACnD,WAAO,MAAM,cAAc,EAAE;AAAA,EAC/B,GAAG,CAAC,IAAI,MAAM,CAAC;AACf,SAAO;AACT;AAGO,MAAM,WAAW,CAAK,OAAgB,SAA4B,QAAQ,OAAO,IAAI;AASrF,SAAS,SAAS,WAAW,KAAgG;AAClI,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAsB,CAAC,CAAC;AAClD,QAAM,IAAI,OAAO,CAAC;AAClB,QAAM,SAAS,OAAiB,CAAC,CAAC;AAClC,YAAU,MAAM,MAAM,OAAO,QAAQ,QAAQ,YAAY,GAAG,CAAC,CAAC;AAC9D,QAAM,OAAO,YAAY,CAAC,GAAW,GAAW,SAA+C;AAC7F,UAAM,KAAK,QAAQ,EAAE,SAAS;AAC9B,UAAM,QAAQ,MAAM,EAAE,IAAI,GAAG,GAAG,GAAG,KAAK,CAAC;AACzC,aAAS,CAAC,QAAQ,CAAC,GAAG,KAAK,GAAG,KAAK,CAAC;AAEpC,WAAO,QAAQ,KAAK,OAAO,WAAW,MAAM,SAAS,CAAC,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,WAAW,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;AAAA,EACzH,GAAG,CAAC,QAAQ,CAAC;AACb,SAAO,CAAC,OAAO,IAAI;AACrB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,4 @@
1
+ export * from './Diagram';
2
+ export * from './hooks';
3
+ export * from './controls';
4
+ export * from './MathText';
@@ -0,0 +1,5 @@
1
+ export * from "./Diagram.js";
2
+ export * from "./hooks.js";
3
+ export * from "./controls.js";
4
+ export * from "./MathText.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/react/index.ts"],
4
+ "sourcesContent": ["export * from './Diagram';\nexport * from './hooks';\nexport * from './controls';\nexport * from './MathText';\n"],
5
+ "mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
6
+ "names": []
7
+ }
@@ -0,0 +1,61 @@
1
+ import { Scene, SceneNode } from './scene';
2
+ import { Easing } from './easing';
3
+ import { Vec } from './vec';
4
+ export interface ViewOptions {
5
+ /** Animation length in ms. Default 380. */
6
+ duration?: number;
7
+ ease?: Easing;
8
+ /** 'auto' fits the viewBox to the scene (and animates it); or pass a fixed [x, y, w, h]. */
9
+ viewBox?: 'auto' | [number, number, number, number];
10
+ /**
11
+ * 'natural' (default): the svg's width/height follow the viewBox, so one scene
12
+ * unit is one CSS pixel (times `scale`). Text and strokes come out the same
13
+ * size in every diagram; CSS `max-width: 100%` lets it shrink on small screens.
14
+ * 'fill': no size attributes; you size the svg with CSS and the drawing stretches.
15
+ */
16
+ size?: 'natural' | 'fill';
17
+ /** Pixels per scene unit in 'natural' mode. Default 1. */
18
+ scale?: number;
19
+ /** Padding around the scene in 'auto' mode. Default 24. */
20
+ padding?: number;
21
+ /**
22
+ * How an 'auto' camera follows the scene. 'grow' (default): only grows when
23
+ * something would fall outside, shrinks only when the content is much smaller.
24
+ * 'fit': refit on every render. See camera.ts for when to use which.
25
+ */
26
+ camera?: 'grow' | 'fit';
27
+ /** Inject the default stylesheet into <head>. Default true. */
28
+ injectStyles?: boolean;
29
+ }
30
+ export interface RenderOptions {
31
+ /** Override the duration for this render only. Use 0 while dragging. */
32
+ duration?: number;
33
+ /** Set false to leave the camera exactly where it is for this render (e.g. while dragging). */
34
+ camera?: boolean;
35
+ }
36
+ export type ViewEventType = 'click' | 'dragstart' | 'drag' | 'dragend';
37
+ export interface ViewEvent {
38
+ type: ViewEventType;
39
+ id: string;
40
+ node: SceneNode;
41
+ /** Pointer position in scene (world) coordinates. */
42
+ x: number;
43
+ y: number;
44
+ original: Event;
45
+ /** True when the event came from the arrow keys rather than a pointer. */
46
+ keyboard?: boolean;
47
+ }
48
+ export interface View {
49
+ readonly svg: SVGSVGElement;
50
+ /** The last scene passed to render(). */
51
+ readonly scene: Scene;
52
+ render(scene: Scene, opts?: RenderOptions): void;
53
+ /** Switch between auto-fit and a fixed camera (e.g. a plot whose axes must not move). */
54
+ setViewBox(viewBox: 'auto' | [number, number, number, number]): void;
55
+ on(type: ViewEventType, handler: (e: ViewEvent) => void): () => void;
56
+ /** Convert browser client coords (e.g. event.clientX) into scene coords. */
57
+ toWorld(clientX: number, clientY: number): Vec;
58
+ destroy(): void;
59
+ }
60
+ /** Create a view inside an existing <svg> element. */
61
+ export declare function createView(svg: SVGSVGElement, options?: ViewOptions): View;
package/dist/render.js ADDED
@@ -0,0 +1,294 @@
1
+ import { resampleProps } from "./path.js";
2
+ import { nextCamera } from "./camera.js";
3
+ import { describe, numericProps, tagFor } from "./describe.js";
4
+ import { bounds } from "./scene.js";
5
+ import { easeInOutCubic } from "./easing.js";
6
+ import { injectStyles } from "./styles.js";
7
+ import { feel, geometry } from "./themes.js";
8
+ import { V2 } from "./vec.js";
9
+ const SVG_NS = "http://www.w3.org/2000/svg";
10
+ const definedOnly = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
11
+ const lerp = (a, b, t) => a + (b - a) * t;
12
+ function createView(svg, options = {}) {
13
+ const opts = { viewBox: "auto", padding: 24, injectStyles: true, ...definedOnly(options) };
14
+ if (opts.injectStyles && typeof document !== "undefined") injectStyles(svg.ownerDocument);
15
+ svg.classList.add("dv-root");
16
+ const exitLayer = document.createElementNS(SVG_NS, "g");
17
+ const liveLayer = document.createElementNS(SVG_NS, "g");
18
+ svg.append(exitLayer, liveLayer);
19
+ const ease = () => opts.ease ?? feel.ease;
20
+ const duration = () => opts.duration ?? feel.duration;
21
+ const entries = /* @__PURE__ */ new Map();
22
+ let current = { nodes: [] };
23
+ let raf = 0;
24
+ let vb = { from: [0, 0, 1, 1], to: [0, 0, 1, 1], cur: [0, 0, 1, 1], start: 0, dur: 0 };
25
+ const natural = (opts.size ?? "natural") === "natural";
26
+ const scale = opts.scale ?? 1;
27
+ function setBox(box) {
28
+ svg.setAttribute("viewBox", box.map((v) => Math.round(v * 100) / 100).join(" "));
29
+ if (natural) {
30
+ svg.setAttribute("width", String(Math.round(box[2] * scale)));
31
+ svg.setAttribute("height", String(Math.round(box[3] * scale)));
32
+ }
33
+ }
34
+ svg.classList.toggle("dv-clip", Array.isArray(opts.viewBox));
35
+ if (Array.isArray(opts.viewBox)) {
36
+ vb.cur = vb.to = vb.from = [...opts.viewBox];
37
+ setBox(vb.cur);
38
+ }
39
+ const reducedMotion = () => typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches;
40
+ function paint(e) {
41
+ const d = describe(e.node, e.cur, geometry);
42
+ if (e.grabbed) d.attrs.class += " dv-grabbed";
43
+ for (const k in d.attrs) {
44
+ if (e.el.getAttribute(k) !== d.attrs[k]) e.el.setAttribute(k, d.attrs[k]);
45
+ }
46
+ if (d.text !== void 0 && e.el.textContent !== d.text) e.el.textContent = d.text;
47
+ if (d.html !== void 0 && e.el._html !== d.html) {
48
+ e.el._html = d.html;
49
+ e.el.innerHTML = d.html;
50
+ }
51
+ }
52
+ function frame(now) {
53
+ let busy = false;
54
+ for (const [id, e] of entries) {
55
+ if (e.settled) continue;
56
+ const t = e.dur <= 0 ? 1 : Math.min(1, Math.max(0, (now - e.start) / e.dur));
57
+ const k = ease()(t);
58
+ for (const key2 in e.to) e.cur[key2] = lerp(e.from[key2] ?? e.to[key2], e.to[key2], k);
59
+ paint(e);
60
+ if (t < 1) busy = true;
61
+ else if (e.exiting) {
62
+ e.el.remove();
63
+ entries.delete(id);
64
+ } else e.settled = true;
65
+ }
66
+ if (!Array.isArray(opts.viewBox)) {
67
+ const t = vb.dur <= 0 ? 1 : Math.min(1, (now - vb.start) / vb.dur);
68
+ const k = easeInOutCubic(t);
69
+ vb.cur = vb.to.map((v, i) => lerp(vb.from[i], v, k));
70
+ setBox(vb.cur);
71
+ if (t < 1) busy = true;
72
+ }
73
+ raf = busy ? requestAnimationFrame(frame) : 0;
74
+ }
75
+ function render(scene, ro = {}) {
76
+ const dur = reducedMotion() ? 0 : ro.duration ?? duration();
77
+ const now = performance.now();
78
+ current = scene;
79
+ const next = /* @__PURE__ */ new Map();
80
+ for (const n of scene.nodes) {
81
+ if (next.has(n.id)) console.warn(`[dsviz] duplicate node id "${n.id}" \u2014 only the first is drawn`);
82
+ else next.set(n.id, n);
83
+ }
84
+ for (const [id, e] of entries) {
85
+ if (next.has(id) || e.exiting) continue;
86
+ e.exiting = true;
87
+ e.settled = false;
88
+ e.from = { ...e.cur };
89
+ e.to = { ...e.cur, opacity: 0, ...e.node.exit ?? {} };
90
+ e.start = now;
91
+ e.dur = dur;
92
+ exitLayer.appendChild(e.el);
93
+ }
94
+ let i = 0;
95
+ for (const node of next.values()) {
96
+ const to = numericProps(node);
97
+ let e = entries.get(node.id);
98
+ if (e && tagFor(e.node.kind) !== tagFor(node.kind)) {
99
+ e.el.remove();
100
+ entries.delete(node.id);
101
+ e = void 0;
102
+ }
103
+ if (!e) {
104
+ const from = { ...to, opacity: 0, ...node.enter ?? {} };
105
+ e = {
106
+ node,
107
+ el: document.createElementNS(SVG_NS, tagFor(node.kind)),
108
+ from,
109
+ to,
110
+ cur: { ...from },
111
+ start: now,
112
+ dur,
113
+ exiting: false,
114
+ settled: false
115
+ };
116
+ e.el.setAttribute("data-id", node.id);
117
+ entries.set(node.id, e);
118
+ } else {
119
+ if (node.kind === "path" && e.node.kind === "path" && e.node.points.length !== node.points.length) {
120
+ e.cur = { ...e.cur, ...resampleProps(e.cur, e.node.points.length, node.points.length, !!node.closed) };
121
+ }
122
+ e.exiting = false;
123
+ e.from = { ...to, ...e.cur };
124
+ e.to = to;
125
+ e.node = node;
126
+ e.start = now;
127
+ e.dur = dur;
128
+ e.settled = false;
129
+ }
130
+ const at = liveLayer.childNodes[i] ?? null;
131
+ if (at !== e.el) liveLayer.insertBefore(e.el, at);
132
+ i++;
133
+ }
134
+ if (!Array.isArray(opts.viewBox) && next.size && ro.camera !== false) {
135
+ const b = bounds(scene);
136
+ const p = opts.padding;
137
+ const first = vb.to[2] === 1 && vb.to[3] === 1;
138
+ const target = nextCamera(first ? null : vb.to, [b.x - p, b.y - p, b.w + 2 * p, b.h + 2 * p], opts.camera ?? "grow");
139
+ const moved = target.some((v, k) => v !== vb.to[k]);
140
+ if (first || moved) vb = { from: first ? target : [...vb.cur], to: target, cur: [...vb.cur], start: now, dur: first ? 0 : dur };
141
+ }
142
+ if (raf) cancelAnimationFrame(raf);
143
+ frame(now);
144
+ }
145
+ const handlers = /* @__PURE__ */ new Map();
146
+ function toWorld(clientX, clientY) {
147
+ const m = svg.getScreenCTM();
148
+ if (!m) return V2(clientX, clientY);
149
+ const p = new DOMPoint(clientX, clientY).matrixTransform(m.inverse());
150
+ return V2(p.x, p.y);
151
+ }
152
+ function emit(type, id, ev, at) {
153
+ const e = entries.get(id);
154
+ const set = handlers.get(type);
155
+ if (!e || !set) return;
156
+ let w = at ?? toWorld(ev.clientX, ev.clientY);
157
+ if (type !== "click") w = keepInView(w, e.node);
158
+ for (const h of set) h({ type, id, node: e.node, x: w.x, y: w.y, original: ev, keyboard: !!at });
159
+ }
160
+ function keepInView(w, node) {
161
+ const [x, y, bw, bh] = vb.to;
162
+ if (bw <= 1 || bh <= 1) return w;
163
+ const b = bounds({ nodes: [node] });
164
+ const mx = Math.min(b.w / 2, bw / 4), my = Math.min(b.h / 2, bh / 4);
165
+ return V2(Math.max(x + mx, Math.min(x + bw - mx, w.x)), Math.max(y + my, Math.min(y + bh - my, w.y)));
166
+ }
167
+ const idOf = (t) => t instanceof Element ? t.closest("[data-id]")?.getAttribute("data-id") ?? null : null;
168
+ const grab = (id, on) => {
169
+ const e = entries.get(id);
170
+ if (e) e.grabbed = on, paint(e);
171
+ };
172
+ let drag = null;
173
+ let swallowClick = false;
174
+ const onPointerDown = (ev) => {
175
+ swallowClick = false;
176
+ const id = idOf(ev.target);
177
+ const e = id ? entries.get(id) : void 0;
178
+ if (!id || !e?.node.draggable) return;
179
+ ev.preventDefault();
180
+ svg.setPointerCapture(ev.pointerId);
181
+ key = null;
182
+ drag = { id, pointer: ev.pointerId, sx: ev.clientX, sy: ev.clientY, moved: false };
183
+ grab(id, true);
184
+ svg.classList.add("dv-dragging");
185
+ emit("dragstart", id, ev);
186
+ };
187
+ const onPointerMove = (ev) => {
188
+ if (!drag || ev.pointerId !== drag.pointer) return;
189
+ if (Math.hypot(ev.clientX - drag.sx, ev.clientY - drag.sy) > 3) drag.moved = true;
190
+ emit("drag", drag.id, ev);
191
+ };
192
+ const onPointerUp = (ev) => {
193
+ if (!drag || ev.pointerId !== drag.pointer) return;
194
+ const cancelled = ev.type === "pointercancel";
195
+ swallowClick = !cancelled;
196
+ emit("dragend", drag.id, ev);
197
+ if (!drag.moved && !cancelled) emit("click", drag.id, ev);
198
+ grab(drag.id, false);
199
+ svg.classList.remove("dv-dragging");
200
+ drag = null;
201
+ };
202
+ const onClick = (ev) => {
203
+ if (swallowClick) {
204
+ swallowClick = false;
205
+ return;
206
+ }
207
+ const id = idOf(ev.target);
208
+ if (id) emit("click", id, ev);
209
+ };
210
+ let key = null;
211
+ const STEPS = {
212
+ ArrowLeft: [-1, 0],
213
+ ArrowRight: [1, 0],
214
+ ArrowUp: [0, -1],
215
+ ArrowDown: [0, 1]
216
+ };
217
+ const centre = (n) => {
218
+ const b = bounds({ nodes: [n] });
219
+ return V2(b.x + b.w / 2, b.y + b.h / 2);
220
+ };
221
+ const onKeyDown = (ev) => {
222
+ const dir = STEPS[ev.key];
223
+ const id = idOf(ev.target);
224
+ const e = id ? entries.get(id) : void 0;
225
+ if (!dir || !id || !e?.node.draggable) return;
226
+ ev.preventDefault();
227
+ if (!key || key.id !== id) key = { id, at: centre(e.node), down: false };
228
+ const step = Math.max(vb.to[2], vb.to[3]) / 60 * (ev.shiftKey ? 5 : 1);
229
+ if (!key.down) emit("dragstart", id, ev, key.at), key.down = true;
230
+ key.at = V2(key.at.x + dir[0] * step, key.at.y + dir[1] * step);
231
+ emit("drag", id, ev, key.at);
232
+ };
233
+ const endKey = (ev) => {
234
+ if (!key?.down) return;
235
+ key.down = false;
236
+ emit("dragend", key.id, ev, key.at);
237
+ };
238
+ const onKeyUp = (ev) => STEPS[ev.key] && endKey(ev);
239
+ const onFocusOut = (ev) => (endKey(ev), key = null);
240
+ const onTouchStart = (ev) => {
241
+ const id = idOf(ev.target);
242
+ if (id && entries.get(id)?.node.draggable) ev.preventDefault();
243
+ };
244
+ svg.addEventListener("touchstart", onTouchStart, { passive: false });
245
+ svg.addEventListener("keydown", onKeyDown);
246
+ svg.addEventListener("keyup", onKeyUp);
247
+ svg.addEventListener("focusout", onFocusOut);
248
+ svg.addEventListener("pointerdown", onPointerDown);
249
+ svg.addEventListener("pointermove", onPointerMove);
250
+ svg.addEventListener("pointerup", onPointerUp);
251
+ svg.addEventListener("pointercancel", onPointerUp);
252
+ svg.addEventListener("click", onClick);
253
+ return {
254
+ svg,
255
+ get scene() {
256
+ return current;
257
+ },
258
+ render,
259
+ setViewBox(v) {
260
+ opts.viewBox = v;
261
+ svg.classList.toggle("dv-clip", Array.isArray(v));
262
+ if (Array.isArray(v)) {
263
+ vb.cur = vb.to = vb.from = [...v];
264
+ setBox(v);
265
+ } else render(current, { duration: 0 });
266
+ },
267
+ toWorld,
268
+ on(type, handler) {
269
+ if (!handlers.has(type)) handlers.set(type, /* @__PURE__ */ new Set());
270
+ handlers.get(type).add(handler);
271
+ return () => handlers.get(type)?.delete(handler);
272
+ },
273
+ destroy() {
274
+ if (raf) cancelAnimationFrame(raf);
275
+ svg.removeEventListener("touchstart", onTouchStart);
276
+ svg.removeEventListener("keydown", onKeyDown);
277
+ svg.removeEventListener("keyup", onKeyUp);
278
+ svg.removeEventListener("focusout", onFocusOut);
279
+ svg.removeEventListener("pointerdown", onPointerDown);
280
+ svg.removeEventListener("pointermove", onPointerMove);
281
+ svg.removeEventListener("pointerup", onPointerUp);
282
+ svg.removeEventListener("pointercancel", onPointerUp);
283
+ svg.removeEventListener("click", onClick);
284
+ exitLayer.remove();
285
+ liveLayer.remove();
286
+ entries.clear();
287
+ handlers.clear();
288
+ }
289
+ };
290
+ }
291
+ export {
292
+ createView
293
+ };
294
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/render.ts"],
4
+ "sourcesContent": ["/**\n * The DOM renderer: a tiny \"keyed reconciler + tween engine\" for SVG.\n *\n * Every call to view.render(scene) does three things:\n *\n * 1. DIFF by id \u2014 new ids are created, missing ids start exiting,\n * surviving ids are updated in place.\n * 2. RETARGET \u2014 each surviving node tweens from WHERE IT IS RIGHT NOW\n * (not where it was last told to go) to its new target.\n * That's what makes interrupting an animation look smooth.\n * 3. ANIMATE \u2014 one requestAnimationFrame loop interpolates every node's\n * numeric props and repaints via describe().\n *\n * Colours are NOT tweened here: tones are CSS classes, and CSS transitions\n * handle colour. JS only animates geometry.\n */\nimport { resampleProps } from './path';\nimport { ViewBox, nextCamera } from './camera';\nimport { describe, numericProps, tagFor } from './describe';\nimport { Box, NumericProps, Scene, SceneNode, bounds } from './scene';\nimport { Easing, easeInOutCubic } from './easing';\nimport { injectStyles } from './styles';\nimport { feel, geometry } from './themes';\nimport { V2, Vec } from './vec';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\nexport interface ViewOptions {\n /** Animation length in ms. Default 380. */\n duration?: number;\n ease?: Easing;\n /** 'auto' fits the viewBox to the scene (and animates it); or pass a fixed [x, y, w, h]. */\n viewBox?: 'auto' | [number, number, number, number];\n /**\n * 'natural' (default): the svg's width/height follow the viewBox, so one scene\n * unit is one CSS pixel (times `scale`). Text and strokes come out the same\n * size in every diagram; CSS `max-width: 100%` lets it shrink on small screens.\n * 'fill': no size attributes; you size the svg with CSS and the drawing stretches.\n */\n size?: 'natural' | 'fill';\n /** Pixels per scene unit in 'natural' mode. Default 1. */\n scale?: number;\n /** Padding around the scene in 'auto' mode. Default 24. */\n padding?: number;\n /**\n * How an 'auto' camera follows the scene. 'grow' (default): only grows when\n * something would fall outside, shrinks only when the content is much smaller.\n * 'fit': refit on every render. See camera.ts for when to use which.\n */\n camera?: 'grow' | 'fit';\n /** Inject the default stylesheet into <head>. Default true. */\n injectStyles?: boolean;\n}\n\nexport interface RenderOptions {\n /** Override the duration for this render only. Use 0 while dragging. */\n duration?: number;\n /** Set false to leave the camera exactly where it is for this render (e.g. while dragging). */\n camera?: boolean;\n}\n\nexport type ViewEventType = 'click' | 'dragstart' | 'drag' | 'dragend';\n\nexport interface ViewEvent {\n type: ViewEventType;\n id: string;\n node: SceneNode;\n /** Pointer position in scene (world) coordinates. */\n x: number;\n y: number;\n original: Event;\n /** True when the event came from the arrow keys rather than a pointer. */\n keyboard?: boolean;\n}\n\nexport interface View {\n readonly svg: SVGSVGElement;\n /** The last scene passed to render(). */\n readonly scene: Scene;\n render(scene: Scene, opts?: RenderOptions): void;\n /** Switch between auto-fit and a fixed camera (e.g. a plot whose axes must not move). */\n setViewBox(viewBox: 'auto' | [number, number, number, number]): void;\n on(type: ViewEventType, handler: (e: ViewEvent) => void): () => void;\n /** Convert browser client coords (e.g. event.clientX) into scene coords. */\n toWorld(clientX: number, clientY: number): Vec;\n destroy(): void;\n}\n\n/** Per-node bookkeeping. `cur` is what is on screen this frame. */\ninterface Entry {\n node: SceneNode;\n el: SVGElement;\n from: NumericProps;\n to: NumericProps;\n cur: NumericProps;\n start: number;\n dur: number;\n exiting: boolean;\n settled: boolean;\n /** Being dragged right now: gets the `dv-grabbed` class (the stylesheet gives it a lifted shadow). */\n grabbed?: boolean;\n}\n\n/** `{ ...defaults, ...options }` lets an explicit `undefined` erase a default; drop those keys first. */\nconst definedOnly = <T extends object>(o: T): Partial<T> =>\n Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)) as Partial<T>;\n\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t;\n\n/** Create a view inside an existing <svg> element. */\nexport function createView(svg: SVGSVGElement, options: ViewOptions = {}): View {\n const opts = { viewBox: 'auto' as 'auto' | [number, number, number, number], padding: 24, injectStyles: true, ...definedOnly(options) } as Required<Pick<ViewOptions, 'viewBox' | 'padding' | 'injectStyles'>> & ViewOptions;\n if (opts.injectStyles && typeof document !== 'undefined') injectStyles(svg.ownerDocument);\n svg.classList.add('dv-root');\n\n // Two layers: nodes that are leaving fade out *underneath* the live scene.\n const exitLayer = document.createElementNS(SVG_NS, 'g');\n const liveLayer = document.createElementNS(SVG_NS, 'g');\n svg.append(exitLayer, liveLayer);\n\n const ease = () => opts.ease ?? feel.ease;\n const duration = () => opts.duration ?? feel.duration;\n\n const entries = new Map<string, Entry>();\n let current: Scene = { nodes: [] };\n let raf = 0;\n\n // The viewBox is animated exactly like a node: from \u2192 to over a duration.\n let vb = { from: [0, 0, 1, 1], to: [0, 0, 1, 1], cur: [0, 0, 1, 1], start: 0, dur: 0 };\n // In natural mode the element's size tracks the viewBox, so 1 unit = `scale` px.\n const natural = (opts.size ?? 'natural') === 'natural';\n const scale = opts.scale ?? 1;\n function setBox(box: number[]) {\n svg.setAttribute('viewBox', box.map((v) => Math.round(v * 100) / 100).join(' '));\n if (natural) {\n svg.setAttribute('width', String(Math.round(box[2] * scale)));\n svg.setAttribute('height', String(Math.round(box[3] * scale)));\n }\n }\n // A fixed camera IS the figure: clip whatever falls outside it (a huge\n // circumcircle, a long tangent line) instead of letting it spill over the page.\n svg.classList.toggle('dv-clip', Array.isArray(opts.viewBox));\n if (Array.isArray(opts.viewBox)) {\n vb.cur = vb.to = vb.from = [...opts.viewBox];\n setBox(vb.cur);\n }\n\n const reducedMotion = () =>\n typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n function paint(e: Entry) {\n const d = describe(e.node, e.cur, geometry);\n if (e.grabbed) d.attrs.class += ' dv-grabbed';\n for (const k in d.attrs) {\n if (e.el.getAttribute(k) !== d.attrs[k]) e.el.setAttribute(k, d.attrs[k]);\n }\n if (d.text !== undefined && e.el.textContent !== d.text) e.el.textContent = d.text;\n if (d.html !== undefined && (e.el as SVGElement & { _html?: string })._html !== d.html) {\n (e.el as SVGElement & { _html?: string })._html = d.html; // compare against what we set, not the browser's serialisation\n e.el.innerHTML = d.html;\n }\n }\n\n function frame(now: number) {\n let busy = false;\n for (const [id, e] of entries) {\n if (e.settled) continue;\n const t = e.dur <= 0 ? 1 : Math.min(1, Math.max(0, (now - e.start) / e.dur));\n const k = ease()(t);\n for (const key in e.to) e.cur[key] = lerp(e.from[key] ?? e.to[key], e.to[key], k);\n paint(e);\n if (t < 1) busy = true;\n else if (e.exiting) {\n e.el.remove();\n entries.delete(id);\n } else e.settled = true;\n }\n if (!Array.isArray(opts.viewBox)) {\n // The camera never bounces, whatever the theme's easing.\n const t = vb.dur <= 0 ? 1 : Math.min(1, (now - vb.start) / vb.dur);\n const k = easeInOutCubic(t);\n vb.cur = vb.to.map((v, i) => lerp(vb.from[i], v, k));\n setBox(vb.cur);\n if (t < 1) busy = true;\n }\n raf = busy ? requestAnimationFrame(frame) : 0;\n }\n\n function render(scene: Scene, ro: RenderOptions = {}) {\n const dur = reducedMotion() ? 0 : (ro.duration ?? duration());\n const now = performance.now();\n current = scene;\n\n // Dedupe ids (and warn: duplicate ids are the #1 bug with keyed rendering).\n const next = new Map<string, SceneNode>();\n for (const n of scene.nodes) {\n if (next.has(n.id)) console.warn(`[dsviz] duplicate node id \"${n.id}\" \u2014 only the first is drawn`);\n else next.set(n.id, n);\n }\n\n // 1. Exits: anything on screen that is no longer in the scene.\n for (const [id, e] of entries) {\n if (next.has(id) || e.exiting) continue;\n e.exiting = true;\n e.settled = false;\n e.from = { ...e.cur };\n e.to = { ...e.cur, opacity: 0, ...(e.node.exit ?? {}) } as NumericProps;\n e.start = now;\n e.dur = dur;\n exitLayer.appendChild(e.el);\n }\n\n // 2. Enters + updates, and put DOM order == scene order (paint order).\n let i = 0;\n for (const node of next.values()) {\n const to = numericProps(node);\n let e = entries.get(node.id);\n if (e && tagFor(e.node.kind) !== tagFor(node.kind)) {\n e.el.remove(); // same id but a different shape type: just replace it\n entries.delete(node.id);\n e = undefined;\n }\n if (!e) {\n const from = { ...to, opacity: 0, ...(node.enter ?? {}) } as NumericProps;\n e = {\n node,\n el: document.createElementNS(SVG_NS, tagFor(node.kind)),\n from,\n to,\n cur: { ...from },\n start: now,\n dur,\n exiting: false,\n settled: false,\n };\n // The scene id, so a test, a stylesheet or devtools can find one node.\n e.el.setAttribute('data-id', node.id);\n entries.set(node.id, e);\n } else {\n // A path whose point count changed: resample what is on screen to the new\n // count first, so tweening point i -> point i still means something.\n if (node.kind === 'path' && e.node.kind === 'path' && e.node.points.length !== node.points.length) {\n e.cur = { ...e.cur, ...resampleProps(e.cur, e.node.points.length, node.points.length, !!node.closed) };\n }\n e.exiting = false; // it may have been mid-exit and come back\n e.from = { ...to, ...e.cur }; // start from what's on screen right now\n e.to = to;\n e.node = node;\n e.start = now;\n e.dur = dur;\n e.settled = false;\n }\n const at = liveLayer.childNodes[i] ?? null;\n if (at !== e.el) liveLayer.insertBefore(e.el, at);\n i++;\n }\n\n // 3. Retarget the camera (auto mode only, and not while something is being dragged).\n if (!Array.isArray(opts.viewBox) && next.size && ro.camera !== false) {\n const b: Box = bounds(scene);\n const p = opts.padding;\n const first = vb.to[2] === 1 && vb.to[3] === 1;\n const target = nextCamera(first ? null : (vb.to as ViewBox), [b.x - p, b.y - p, b.w + 2 * p, b.h + 2 * p], opts.camera ?? 'grow');\n const moved = target.some((v, k) => v !== vb.to[k]);\n if (first || moved) vb = { from: first ? target : [...vb.cur], to: target, cur: [...vb.cur], start: now, dur: first ? 0 : dur };\n }\n\n // Paint the first frame synchronously (instant feedback while dragging),\n // then let requestAnimationFrame take over.\n if (raf) cancelAnimationFrame(raf);\n frame(now);\n }\n\n // \u2500\u2500 events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const handlers = new Map<ViewEventType, Set<(e: ViewEvent) => void>>();\n\n function toWorld(clientX: number, clientY: number): Vec {\n const m = svg.getScreenCTM();\n if (!m) return V2(clientX, clientY);\n const p = new DOMPoint(clientX, clientY).matrixTransform(m.inverse());\n return V2(p.x, p.y);\n }\n\n function emit(type: ViewEventType, id: string, ev: PointerEvent | MouseEvent | KeyboardEvent, at?: Vec) {\n const e = entries.get(id);\n const set = handlers.get(type);\n if (!e || !set) return;\n let w = at ?? toWorld((ev as MouseEvent).clientX, (ev as MouseEvent).clientY);\n if (type !== 'click') w = keepInView(w, e.node);\n for (const h of set) h({ type, id, node: e.node, x: w.x, y: w.y, original: ev, keyboard: !!at });\n }\n\n // A drag can never take something off the canvas: clamp the reported point to\n // the camera box, inset by the dragged shape's half-size so all of it stays\n // visible. (The camera is frozen during a drag, so this box doesn't move.)\n function keepInView(w: Vec, node: SceneNode): Vec {\n const [x, y, bw, bh] = vb.to;\n if (bw <= 1 || bh <= 1) return w;\n const b = bounds({ nodes: [node] });\n const mx = Math.min(b.w / 2, bw / 4), my = Math.min(b.h / 2, bh / 4);\n return V2(Math.max(x + mx, Math.min(x + bw - mx, w.x)), Math.max(y + my, Math.min(y + bh - my, w.y)));\n }\n\n const idOf = (t: EventTarget | null): string | null =>\n t instanceof Element ? (t.closest('[data-id]')?.getAttribute('data-id') ?? null) : null;\n\n const grab = (id: string, on: boolean) => {\n const e = entries.get(id);\n if (e) (e.grabbed = on), paint(e);\n };\n\n let drag: { id: string; pointer: number; sx: number; sy: number; moved: boolean } | null = null;\n let swallowClick = false;\n\n const onPointerDown = (ev: PointerEvent) => {\n swallowClick = false; // a fresh press: whatever the last one left behind no longer applies\n const id = idOf(ev.target);\n const e = id ? entries.get(id) : undefined;\n if (!id || !e?.node.draggable) return;\n ev.preventDefault();\n svg.setPointerCapture(ev.pointerId);\n key = null;\n drag = { id, pointer: ev.pointerId, sx: ev.clientX, sy: ev.clientY, moved: false };\n grab(id, true);\n svg.classList.add('dv-dragging');\n emit('dragstart', id, ev);\n };\n const onPointerMove = (ev: PointerEvent) => {\n if (!drag || ev.pointerId !== drag.pointer) return;\n if (Math.hypot(ev.clientX - drag.sx, ev.clientY - drag.sy) > 3) drag.moved = true;\n emit('drag', drag.id, ev);\n };\n const onPointerUp = (ev: PointerEvent) => {\n if (!drag || ev.pointerId !== drag.pointer) return;\n // Pointer capture sends the browser's click to the <svg>, not the node, so\n // we decide here: a press that didn't move is a click on the node itself.\n // The native click that follows is swallowed either way.\n const cancelled = ev.type === 'pointercancel';\n swallowClick = !cancelled;\n emit('dragend', drag.id, ev);\n if (!drag.moved && !cancelled) emit('click', drag.id, ev);\n grab(drag.id, false);\n svg.classList.remove('dv-dragging');\n drag = null;\n };\n const onClick = (ev: MouseEvent) => {\n if (swallowClick) {\n swallowClick = false;\n return;\n }\n const id = idOf(ev.target);\n if (id) emit('click', id, ev);\n };\n\n // Keyboard dragging. Arrow keys move a VIRTUAL POINTER that starts on the\n // node and accumulates, and we emit the same dragstart/drag/dragend a mouse\n // would \u2014 so every drag handler works with the keyboard for free. The pointer\n // accumulates (rather than restarting from the node each press) so handlers\n // that snap to a grid still move: three small presses add up to one cell.\n let key: { id: string; at: Vec; down: boolean } | null = null;\n const STEPS: Record<string, [number, number]> = {\n ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1],\n };\n const centre = (n: SceneNode) => {\n const b = bounds({ nodes: [n] });\n return V2(b.x + b.w / 2, b.y + b.h / 2);\n };\n const onKeyDown = (ev: KeyboardEvent) => {\n const dir = STEPS[ev.key];\n const id = idOf(ev.target);\n const e = id ? entries.get(id) : undefined;\n if (!dir || !id || !e?.node.draggable) return;\n ev.preventDefault(); // don't scroll the page\n if (!key || key.id !== id) key = { id, at: centre(e.node), down: false };\n // One step is a fixed fraction of the view, so it feels the same at any zoom.\n const step = (Math.max(vb.to[2], vb.to[3]) / 60) * (ev.shiftKey ? 5 : 1);\n if (!key.down) emit('dragstart', id, ev, key.at), (key.down = true);\n key.at = V2(key.at.x + dir[0] * step, key.at.y + dir[1] * step);\n emit('drag', id, ev, key.at);\n };\n const endKey = (ev: Event) => {\n if (!key?.down) return;\n key.down = false;\n emit('dragend', key.id, ev as KeyboardEvent, key.at);\n };\n const onKeyUp = (ev: KeyboardEvent) => STEPS[ev.key] && endKey(ev);\n const onFocusOut = (ev: FocusEvent) => (endKey(ev), (key = null));\n\n // Touch: stop the page scrolling when a finger lands on something draggable.\n // CSS touch-action is unreliable on elements INSIDE an svg, and once the\n // browser decides a touch is a scroll it cancels the pointer events (the drag\n // just dies). A non-passive touchstart that cancels only on draggables keeps\n // scrolling over the rest of the diagram working.\n const onTouchStart = (ev: TouchEvent) => {\n const id = idOf(ev.target);\n if (id && entries.get(id)?.node.draggable) ev.preventDefault();\n };\n svg.addEventListener('touchstart', onTouchStart, { passive: false });\n svg.addEventListener('keydown', onKeyDown);\n svg.addEventListener('keyup', onKeyUp);\n svg.addEventListener('focusout', onFocusOut);\n svg.addEventListener('pointerdown', onPointerDown);\n svg.addEventListener('pointermove', onPointerMove);\n svg.addEventListener('pointerup', onPointerUp);\n svg.addEventListener('pointercancel', onPointerUp);\n svg.addEventListener('click', onClick);\n\n return {\n svg,\n get scene() {\n return current;\n },\n render,\n setViewBox(v) {\n opts.viewBox = v;\n svg.classList.toggle('dv-clip', Array.isArray(v));\n if (Array.isArray(v)) {\n vb.cur = vb.to = vb.from = [...v];\n setBox(v);\n } else render(current, { duration: 0 });\n },\n toWorld,\n on(type, handler) {\n if (!handlers.has(type)) handlers.set(type, new Set());\n handlers.get(type)!.add(handler);\n return () => handlers.get(type)?.delete(handler);\n },\n destroy() {\n if (raf) cancelAnimationFrame(raf);\n svg.removeEventListener('touchstart', onTouchStart);\n svg.removeEventListener('keydown', onKeyDown);\n svg.removeEventListener('keyup', onKeyUp);\n svg.removeEventListener('focusout', onFocusOut);\n svg.removeEventListener('pointerdown', onPointerDown);\n svg.removeEventListener('pointermove', onPointerMove);\n svg.removeEventListener('pointerup', onPointerUp);\n svg.removeEventListener('pointercancel', onPointerUp);\n svg.removeEventListener('click', onClick);\n exitLayer.remove();\n liveLayer.remove();\n entries.clear();\n handlers.clear();\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["key"]
7
+ }