@heyramzi/ui 2.11.0 → 2.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/dist/agents/helpers.js +38 -38
  2. package/dist/agents/page-state.js +49 -50
  3. package/dist/agents/vendor/color.js +146 -127
  4. package/dist/agents/vendor/draw.js +617 -508
  5. package/dist/agents/vendor/engine.js +698 -599
  6. package/dist/agents/vendor/mesh.js +653 -683
  7. package/dist/agents/vendor/plastic.js +3913 -3112
  8. package/dist/agents/vendor/presets.js +163 -164
  9. package/dist/agents/vendor/shapes.js +21 -20
  10. package/dist/agents/vendor/ticker.js +41 -42
  11. package/dist/agents/vendor/types.js +1 -0
  12. package/dist/agents/vendor/wear.js +1421 -1448
  13. package/dist/ai/confirmation/confirmation-context.svelte.js +6 -6
  14. package/dist/ai/confirmation/index.js +3 -1
  15. package/dist/ai/index.js +33 -0
  16. package/dist/ai/prompt-input/index.js +3 -1
  17. package/dist/ai/reasoning/index.js +3 -1
  18. package/dist/ai/reasoning/reasoning-context.svelte.js +6 -6
  19. package/dist/ai/suggestion/index.js +3 -1
  20. package/dist/ai/typing-dots/index.js +3 -1
  21. package/dist/animations/text-swap.js +24 -24
  22. package/dist/brand/index.js +54 -48
  23. package/dist/canvas/card/resize.js +5 -3
  24. package/dist/canvas/edges/edge-path.js +255 -242
  25. package/dist/canvas/editor/context-menu.js +39 -36
  26. package/dist/canvas/editor/dispatch.svelte.js +154 -151
  27. package/dist/canvas/editor/editor-context.js +7 -2
  28. package/dist/canvas/editor/editor-mode.js +1 -0
  29. package/dist/canvas/editor/frame.js +73 -76
  30. package/dist/canvas/editor/hand-context.js +5 -2
  31. package/dist/canvas/editor/history-core.js +163 -191
  32. package/dist/canvas/editor/keymap.js +95 -169
  33. package/dist/canvas/editor/layout-actions.js +143 -144
  34. package/dist/canvas/editor/lock.js +52 -51
  35. package/dist/canvas/editor/rebase.js +217 -240
  36. package/dist/canvas/editor/style.js +132 -137
  37. package/dist/canvas/editor/text-field-context.js +7 -2
  38. package/dist/canvas/editor/z-order.js +65 -70
  39. package/dist/canvas/headless/alignment.js +22 -16
  40. package/dist/canvas/headless/break-apart-find.js +286 -279
  41. package/dist/canvas/headless/break-apart-pieces.js +62 -58
  42. package/dist/canvas/headless/break-apart-plan.js +299 -325
  43. package/dist/canvas/headless/break-apart-style.js +75 -70
  44. package/dist/canvas/headless/break-apart.js +188 -181
  45. package/dist/canvas/headless/canvas-cleanup.js +78 -58
  46. package/dist/canvas/headless/edge-anchors.js +115 -116
  47. package/dist/canvas/headless/edge-memo.js +24 -22
  48. package/dist/canvas/headless/edge-serialization.js +17 -27
  49. package/dist/canvas/headless/figure-geometry.js +232 -264
  50. package/dist/canvas/headless/fit-viewport.js +84 -67
  51. package/dist/canvas/headless/grid.js +88 -86
  52. package/dist/canvas/headless/icon-node.js +44 -36
  53. package/dist/canvas/headless/index.js +1 -0
  54. package/dist/canvas/headless/linkify.js +17 -19
  55. package/dist/canvas/headless/pen-marks.js +101 -95
  56. package/dist/canvas/headless/sketch.js +231 -209
  57. package/dist/canvas/headless/snap-guides.js +99 -83
  58. package/dist/canvas/headless/svg-figure.js +133 -117
  59. package/dist/canvas/headless/table-layout.js +146 -107
  60. package/dist/canvas/index.js +2 -0
  61. package/dist/canvas/lod/lod-mode.svelte.js +20 -18
  62. package/dist/canvas/lod/render-all.svelte.js +36 -39
  63. package/dist/canvas/ops/arrange.js +78 -71
  64. package/dist/canvas/ops/ops.js +260 -255
  65. package/dist/canvas/pen/colors.js +37 -35
  66. package/dist/canvas/pen/eraser.js +28 -27
  67. package/dist/canvas/pen/geometry.js +120 -130
  68. package/dist/canvas/pen/index.js +1 -0
  69. package/dist/canvas/pen/ops.js +11 -11
  70. package/dist/canvas/pen/pen-session.js +150 -159
  71. package/dist/canvas/pen/plugin.js +107 -104
  72. package/dist/canvas/pen/shapes.js +73 -68
  73. package/dist/canvas/pen/stroke-path.js +32 -30
  74. package/dist/canvas/pen/types.js +21 -21
  75. package/dist/canvas/pen/ui.js +10 -8
  76. package/dist/canvas/plugin.js +46 -46
  77. package/dist/canvas/screen/glyphs.js +12 -11
  78. package/dist/canvas/screen/labels.js +22 -22
  79. package/dist/canvas/screen/parts.js +98 -111
  80. package/dist/canvas/screen/plugin.js +47 -56
  81. package/dist/canvas/screen/types.js +30 -35
  82. package/dist/canvas/screen/ui.js +7 -7
  83. package/dist/canvas/test-utils.js +14 -14
  84. package/dist/canvas/types.js +1 -0
  85. package/dist/canvas/ui/forward-wheel.js +24 -25
  86. package/dist/canvas/ui/icons.js +64 -131
  87. package/dist/canvas/ui/place-panel.js +58 -49
  88. package/dist/canvas/ui/shortcut-tip.js +14 -12
  89. package/dist/canvas/ui/tools.js +300 -385
  90. package/dist/charts/dashboard/chart.js +4 -0
  91. package/dist/charts/dashboard/data-table.js +11 -0
  92. package/dist/charts/dashboard/windows.js +8 -3
  93. package/dist/charts/edge-fade.js +10 -5
  94. package/dist/charts/hover-band.js +16 -11
  95. package/dist/charts/index.js +0 -1
  96. package/dist/charts/motion.js +11 -7
  97. package/dist/charts/primitives/chart-utils.js +12 -12
  98. package/dist/components/copy/clipboard.js +32 -32
  99. package/dist/components/copy/copied.svelte.js +31 -23
  100. package/dist/components/data-table/data-table.js +39 -39
  101. package/dist/components/docs/index.js +4 -0
  102. package/dist/components/docs/scroll-spy.svelte.js +73 -68
  103. package/dist/components/list-page/filter-section.js +4 -1
  104. package/dist/components/list-page/index.js +2 -0
  105. package/dist/components/list-page/list-table.js +7 -2
  106. package/dist/components/list-page/queue-rail.js +14 -0
  107. package/dist/components/list-page/section-host.js +4 -2
  108. package/dist/components/list-page/step.js +4 -3
  109. package/dist/components/list-page/types.js +1 -0
  110. package/dist/components/picker/trigger.js +48 -30
  111. package/dist/components/picker/types.js +1 -0
  112. package/dist/components/segmented/collapse.js +38 -27
  113. package/dist/components/segmented/index.js +8 -0
  114. package/dist/components/segmented/segmented.js +67 -42
  115. package/dist/components/star-rating-input/index.js +3 -1
  116. package/dist/components/table-view/types.js +1 -0
  117. package/dist/components/tags-input/tags.js +81 -69
  118. package/dist/components/theme/index.js +4 -0
  119. package/dist/components/toolbar/index.js +4 -0
  120. package/dist/components/toolbar/toolbar-trigger.js +14 -1
  121. package/dist/editor/anchors.js +17 -13
  122. package/dist/editor/block-id.js +52 -51
  123. package/dist/editor/block-ops.js +108 -80
  124. package/dist/editor/doc-blocks.js +155 -166
  125. package/dist/editor/extensions.js +139 -180
  126. package/dist/editor/index.js +4 -0
  127. package/dist/editor/markdown-test-helpers.js +4 -2
  128. package/dist/editor/markdown.js +420 -408
  129. package/dist/editor/serializer.js +204 -200
  130. package/dist/editor/slash-items.js +89 -101
  131. package/dist/editor/youtube-url.js +25 -20
  132. package/dist/effects/animated-beam/path-calculator.svelte.js +43 -38
  133. package/dist/effects/animated-beam/types.js +1 -0
  134. package/dist/effects/border-beam/css.js +92 -78
  135. package/dist/effects/border-beam/presets.js +1705 -1720
  136. package/dist/effects/border-beam/register-properties.js +43 -33
  137. package/dist/effects/border-beam/types.js +1 -0
  138. package/dist/effects/image-comparison/compare.js +3 -2
  139. package/dist/effects/image-reveal/types.js +1 -0
  140. package/dist/effects/magnetic/magnetic.js +58 -52
  141. package/dist/effects/reveal/observe.js +50 -40
  142. package/dist/effects/reveal/types.js +1 -0
  143. package/dist/effects/shader-gradient/color.js +36 -29
  144. package/dist/effects/shader-gradient/renderer.js +384 -295
  145. package/dist/effects/shader-gradient/shaders.js +31 -0
  146. package/dist/effects/shader-gradient/types.js +1 -0
  147. package/dist/icons/apple-symbol-exports.js +99 -124
  148. package/dist/icons/sf-symbols.js +357 -461
  149. package/dist/icons/trades.js +43 -28
  150. package/dist/inbox/index.js +1 -0
  151. package/dist/inbox/logic.js +126 -122
  152. package/dist/inbox/types.js +43 -43
  153. package/dist/lib/arrive.js +19 -12
  154. package/dist/lib/icon-types.js +1 -0
  155. package/dist/lib/initials.js +16 -10
  156. package/dist/lib/layout.js +5 -0
  157. package/dist/lib/panel.js +13 -0
  158. package/dist/lib/result.js +22 -22
  159. package/dist/lib/sliding-pill.js +59 -44
  160. package/dist/lib/theme-transition.js +43 -43
  161. package/dist/lib/tone.js +31 -24
  162. package/dist/lib/utils.js +3 -1
  163. package/dist/onboarding/define-tours.js +24 -23
  164. package/dist/onboarding/onboarding.svelte.js +16 -12
  165. package/dist/onboarding/pick.js +10 -6
  166. package/dist/onboarding/stores.js +40 -41
  167. package/dist/onboarding/wait.js +24 -23
  168. package/dist/panel/side-panel-types.js +1 -0
  169. package/dist/record/demo-data.js +459 -486
  170. package/dist/record/fields.js +129 -131
  171. package/dist/record/format.js +70 -82
  172. package/dist/record/gutter.js +1 -0
  173. package/dist/record/kinds.js +95 -105
  174. package/dist/record/logic.js +238 -219
  175. package/dist/record/types.js +49 -48
  176. package/dist/shadcn/accordion/index.js +3 -1
  177. package/dist/shadcn/alert/index.js +3 -1
  178. package/dist/shadcn/alert-dialog/index.js +3 -1
  179. package/dist/shadcn/attachment/index.js +3 -1
  180. package/dist/shadcn/avatar/index.js +3 -1
  181. package/dist/shadcn/breadcrumb/index.js +3 -1
  182. package/dist/shadcn/bubble/index.js +3 -1
  183. package/dist/shadcn/button/index.js +3 -1
  184. package/dist/shadcn/calendar/index.js +3 -1
  185. package/dist/shadcn/card/index.js +3 -1
  186. package/dist/shadcn/checkbox/index.js +3 -1
  187. package/dist/shadcn/code-block/index.js +3 -1
  188. package/dist/shadcn/collapsible/index.js +3 -1
  189. package/dist/shadcn/command/index.js +4 -1
  190. package/dist/shadcn/context-menu/index.js +3 -1
  191. package/dist/shadcn/dialog/index.js +3 -1
  192. package/dist/shadcn/dropdown-menu/menu-item.js +40 -20
  193. package/dist/shadcn/empty/index.js +3 -1
  194. package/dist/shadcn/field/index.js +3 -1
  195. package/dist/shadcn/hooks/highlighter.js +77 -75
  196. package/dist/shadcn/hooks/is-mobile.svelte.js +3 -3
  197. package/dist/shadcn/hooks/use-highlighted-code.svelte.js +62 -62
  198. package/dist/shadcn/input/index.js +3 -1
  199. package/dist/shadcn/kbd/index.js +3 -1
  200. package/dist/shadcn/label/index.js +3 -1
  201. package/dist/shadcn/marker/index.js +3 -1
  202. package/dist/shadcn/message/index.js +3 -1
  203. package/dist/shadcn/message-scroller/index.js +3 -1
  204. package/dist/shadcn/popover/index.js +3 -1
  205. package/dist/shadcn/progress/index.js +3 -1
  206. package/dist/shadcn/progress/tone.js +8 -7
  207. package/dist/shadcn/radio-group/index.js +3 -1
  208. package/dist/shadcn/scroll-area/index.js +3 -1
  209. package/dist/shadcn/select/index.js +3 -1
  210. package/dist/shadcn/separator/index.js +3 -1
  211. package/dist/shadcn/sheet/index.js +3 -1
  212. package/dist/shadcn/sidebar/context.svelte.js +42 -28
  213. package/dist/shadcn/sidebar/index.js +3 -1
  214. package/dist/shadcn/skeleton/index.js +3 -1
  215. package/dist/shadcn/slider/index.js +3 -1
  216. package/dist/shadcn/switch/index.js +3 -1
  217. package/dist/shadcn/table/index.js +3 -1
  218. package/dist/shadcn/tabs/index.js +3 -1
  219. package/dist/shadcn/textarea/index.js +3 -1
  220. package/dist/shadcn/toggle/index.js +3 -1
  221. package/dist/shadcn/toggle-group/index.js +3 -1
  222. package/dist/shadcn/tooltip/context.js +7 -2
  223. package/dist/shadcn/tooltip/index.js +3 -1
  224. package/dist/shadcn/trace/index.js +3 -1
  225. package/dist/slides/bridges.js +65 -59
  226. package/dist/slides/deck-controller.svelte.js +210 -207
  227. package/dist/slides/export/capture.js +66 -71
  228. package/dist/slides/export/ffmpeg.js +76 -72
  229. package/dist/slides/export/index.js +3 -0
  230. package/dist/slides/export/launch.js +9 -6
  231. package/dist/slides/export/pdf.js +60 -54
  232. package/dist/slides/export/types.js +1 -0
  233. package/dist/slides/labels.js +24 -24
  234. package/dist/slides/layout.js +39 -41
  235. package/dist/slides/model.js +9 -7
  236. package/dist/slides/navigation.js +78 -73
  237. package/dist/slides/types.js +2 -2
  238. package/dist/status/define-statuses.js +18 -14
  239. package/dist/status/status-types.js +1 -0
  240. package/dist/test-setup.js +24 -20
  241. package/dist/thread/logic.js +73 -77
  242. package/dist/thread/types.js +21 -21
  243. package/dist/work/board-order.js +94 -90
  244. package/dist/work/dates.js +16 -9
  245. package/dist/work/detail/detail-types.js +18 -18
  246. package/dist/work/docs/edited.js +32 -29
  247. package/dist/work/docs/index.js +4 -0
  248. package/dist/work/docs/labels.js +34 -34
  249. package/dist/work/docs/menu.js +113 -106
  250. package/dist/work/docs/outline.js +39 -29
  251. package/dist/work/gantt/logic.js +211 -235
  252. package/dist/work/inbox/inbox-logic.js +71 -75
  253. package/dist/work/inbox/inbox-statuses.js +75 -72
  254. package/dist/work/inbox/inbox-types.js +1 -0
  255. package/dist/work/kanban-drag.js +27 -22
  256. package/dist/work/kanban-types.js +1 -0
  257. package/dist/work/presets/sales.js +36 -32
  258. package/dist/work/reorder.js +9 -8
  259. package/dist/work/task-types.js +1 -0
  260. package/dist/work/time/duration.js +51 -46
  261. package/dist/work/time/time-types.js +23 -23
  262. package/dist/work/use-kanban-dnd.svelte.js +126 -125
  263. package/package.json +3 -1
  264. package/src/components/list-page/master-detail.svelte +3 -1
  265. package/src/components/list-page/rail-row.svelte +4 -4
  266. package/src/components/tags-input/tags-input.svelte +2 -0
  267. package/src/editor/markdown.ts +30 -35
@@ -1,3 +1,5 @@
1
+ // One pen gesture, pointer down to up, in flow coordinates. It talks to the
2
+ // editor through a host, so the overlay stays thin.
1
3
  import { strokesAlong } from "./eraser.js";
2
4
  import { buildStrokeNode } from "./geometry.js";
3
5
  import { addPenNode, removePenNode } from "./ops.js";
@@ -5,167 +7,156 @@ import { buildShapeNode, buildTextNode, dragBox } from "./shapes.js";
5
7
  import { strokePath } from "./stroke-path.js";
6
8
  import { PEN_DEFAULTS } from "./types.js";
7
9
  const ERASER_RADIUS = 10;
10
+ /** Samples closer than this, in screen pixels, add nothing to a stroke. */
8
11
  const MIN_SAMPLE = 1;
9
12
  function isShapeTool(tool) {
10
- return tool === "rectangle" || tool === "ellipse" || tool === "diamond";
13
+ return tool === "rectangle" || tool === "ellipse" || tool === "diamond";
11
14
  }
12
15
  export function createPenSession(host, getSettings) {
13
- let endGesture = null;
14
- let start = null;
15
- let last = null;
16
- let samples = [];
17
- const erased = new Set();
18
- function settingsNow() {
19
- const settings = getSettings();
20
- const fallback = settings.tool === "highlighter" ? PEN_DEFAULTS.highlighter.size : PEN_DEFAULTS.pen.size;
21
- return {
22
- ...settings,
23
- size: settings.size ?? fallback
24
- };
25
- }
26
- function reset() {
27
- endGesture = null;
28
- start = null;
29
- last = null;
30
- samples = [];
31
- erased.clear();
32
- host.onPreview?.(null);
33
- }
34
- function erase(from, to, radius) {
35
- const remaining = host.getNodes().filter((node)=>!erased.has(node.id));
36
- const ids = strokesAlong(remaining, from, to, radius);
37
- if (ids.length === 0) return;
38
- for (const id of ids)erased.add(id);
39
- host.commit(ids.map(removePenNode));
40
- }
41
- function placeText(at, color) {
42
- const op = addPenNode(buildTextNode(at, {
43
- color
44
- }));
45
- host.commit([
46
- op
47
- ]);
48
- if (op.kind === "addNode" && op.node.id) host.onTextPlaced?.(op.node.id);
49
- }
50
- function sampleOf(at, pressure) {
51
- return pressure === undefined ? [
52
- at.x,
53
- at.y
54
- ] : [
55
- at.x,
56
- at.y,
57
- pressure
58
- ];
59
- }
60
- function previewStroke(pointerDown) {
61
- const { tool, color, size } = settingsNow();
62
- if (tool !== "pen" && tool !== "highlighter") return;
63
- const hasPressure = samples.some((point)=>point[2] !== undefined);
64
- const d = strokePath(samples, {
65
- size,
66
- tool,
67
- simulatePressure: !hasPressure
68
- }, !pointerDown);
69
- host.onPreview?.({
70
- kind: "stroke",
71
- d,
72
- color,
73
- tool
74
- });
75
- }
76
- return {
77
- get active () {
78
- return start !== null;
79
- },
80
- down (at, pressure) {
81
- if (start) return;
82
- const { tool, color, zoom } = settingsNow();
83
- if (tool === "text") {
84
- placeText(at, color);
85
- return;
86
- }
87
- endGesture = host.startGesture();
88
- start = at;
89
- last = at;
90
- if (tool === "eraser") {
91
- const radius = ERASER_RADIUS / zoom;
92
- host.onPreview?.({
93
- kind: "eraser",
94
- at,
95
- radius
96
- });
97
- erase(at, at, radius);
98
- return;
99
- }
100
- if (tool === "pen" || tool === "highlighter") {
101
- samples = [
102
- sampleOf(at, pressure)
103
- ];
104
- previewStroke(true);
105
- }
106
- },
107
- move (at, pressure) {
108
- if (!start || !last) return;
109
- const { tool, color, zoom } = settingsNow();
110
- if (tool === "eraser") {
111
- const radius = ERASER_RADIUS / zoom;
112
- host.onPreview?.({
113
- kind: "eraser",
114
- at,
115
- radius
116
- });
117
- erase(last, at, radius);
118
- last = at;
119
- return;
120
- }
121
- if (isShapeTool(tool)) {
122
- host.onPreview?.({
123
- kind: "shape",
124
- shape: tool,
125
- box: dragBox(start, at),
126
- color
127
- });
128
- last = at;
129
- return;
130
- }
131
- if (Math.hypot(at.x - last.x, at.y - last.y) * zoom < MIN_SAMPLE) return;
132
- samples.push(sampleOf(at, pressure));
133
- last = at;
134
- previewStroke(true);
135
- },
136
- up (at, options = {}) {
137
- if (!start || !endGesture) return;
138
- const { tool, color, size, zoom } = settingsNow();
139
- const close = endGesture;
140
- try {
141
- if (tool === "pen" || tool === "highlighter") {
142
- const node = buildStrokeNode(samples, {
143
- color,
144
- size,
145
- tool,
146
- zoom
147
- });
148
- if (node) host.commit([
149
- addPenNode(node)
150
- ]);
151
- } else if (isShapeTool(tool)) {
152
- const box = dragBox(start, at, options.square);
153
- host.commit([
154
- addPenNode(buildShapeNode(tool, box, {
155
- color
156
- }))
157
- ]);
158
- }
159
- } finally{
160
- close();
161
- reset();
162
- }
163
- },
164
- cancel () {
165
- if (!endGesture) return;
166
- const close = endGesture;
167
- close();
168
- reset();
169
- }
170
- };
16
+ let endGesture = null;
17
+ let start = null;
18
+ let last = null;
19
+ let samples = [];
20
+ const erased = new Set();
21
+ function settingsNow() {
22
+ const settings = getSettings();
23
+ const fallback = settings.tool === "highlighter" ? PEN_DEFAULTS.highlighter.size : PEN_DEFAULTS.pen.size;
24
+ return {
25
+ ...settings,
26
+ size: settings.size ?? fallback
27
+ };
28
+ }
29
+ function reset() {
30
+ endGesture = null;
31
+ start = null;
32
+ last = null;
33
+ samples = [];
34
+ erased.clear();
35
+ host.onPreview?.(null);
36
+ }
37
+ function erase(from, to, radius) {
38
+ const remaining = host.getNodes().filter((node) => !erased.has(node.id));
39
+ const ids = strokesAlong(remaining, from, to, radius);
40
+ if (ids.length === 0) return;
41
+ for (const id of ids) erased.add(id);
42
+ host.commit(ids.map(removePenNode));
43
+ }
44
+ function placeText(at, color) {
45
+ const op = addPenNode(buildTextNode(at, { color }));
46
+ host.commit([op]);
47
+ if (op.kind === "addNode" && op.node.id) host.onTextPlaced?.(op.node.id);
48
+ }
49
+ function sampleOf(at, pressure) {
50
+ return pressure === undefined ? [at.x, at.y] : [
51
+ at.x,
52
+ at.y,
53
+ pressure
54
+ ];
55
+ }
56
+ function previewStroke(pointerDown) {
57
+ const { tool, color, size } = settingsNow();
58
+ if (tool !== "pen" && tool !== "highlighter") return;
59
+ const hasPressure = samples.some((point) => point[2] !== undefined);
60
+ const d = strokePath(samples, {
61
+ size,
62
+ tool,
63
+ simulatePressure: !hasPressure
64
+ }, !pointerDown);
65
+ host.onPreview?.({
66
+ kind: "stroke",
67
+ d,
68
+ color,
69
+ tool
70
+ });
71
+ }
72
+ return {
73
+ /** True while a gesture is open, so the overlay knows to keep capturing. */
74
+ get active() {
75
+ return start !== null;
76
+ },
77
+ down(at, pressure) {
78
+ if (start) return;
79
+ const { tool, color, zoom } = settingsNow();
80
+ if (tool === "text") {
81
+ placeText(at, color);
82
+ return;
83
+ }
84
+ endGesture = host.startGesture();
85
+ start = at;
86
+ last = at;
87
+ if (tool === "eraser") {
88
+ const radius = ERASER_RADIUS / zoom;
89
+ host.onPreview?.({
90
+ kind: "eraser",
91
+ at,
92
+ radius
93
+ });
94
+ erase(at, at, radius);
95
+ return;
96
+ }
97
+ if (tool === "pen" || tool === "highlighter") {
98
+ samples = [sampleOf(at, pressure)];
99
+ previewStroke(true);
100
+ }
101
+ },
102
+ move(at, pressure) {
103
+ if (!start || !last) return;
104
+ const { tool, color, zoom } = settingsNow();
105
+ if (tool === "eraser") {
106
+ const radius = ERASER_RADIUS / zoom;
107
+ host.onPreview?.({
108
+ kind: "eraser",
109
+ at,
110
+ radius
111
+ });
112
+ erase(last, at, radius);
113
+ last = at;
114
+ return;
115
+ }
116
+ if (isShapeTool(tool)) {
117
+ host.onPreview?.({
118
+ kind: "shape",
119
+ shape: tool,
120
+ box: dragBox(start, at),
121
+ color
122
+ });
123
+ last = at;
124
+ return;
125
+ }
126
+ if (Math.hypot(at.x - last.x, at.y - last.y) * zoom < MIN_SAMPLE) return;
127
+ samples.push(sampleOf(at, pressure));
128
+ last = at;
129
+ previewStroke(true);
130
+ },
131
+ /** Pointer up. Writes the node, then closes the gesture so it's one undo step. */
132
+ up(at, options = {}) {
133
+ if (!start || !endGesture) return;
134
+ const { tool, color, size, zoom } = settingsNow();
135
+ const close = endGesture;
136
+ try {
137
+ if (tool === "pen" || tool === "highlighter") {
138
+ const node = buildStrokeNode(samples, {
139
+ color,
140
+ size,
141
+ tool,
142
+ zoom
143
+ });
144
+ if (node) host.commit([addPenNode(node)]);
145
+ } else if (isShapeTool(tool)) {
146
+ const box = dragBox(start, at, options.square);
147
+ host.commit([addPenNode(buildShapeNode(tool, box, { color }))]);
148
+ }
149
+ } finally {
150
+ close();
151
+ reset();
152
+ }
153
+ },
154
+ /** Escape or a lost pointer. Whatever the eraser already removed stays removed. */
155
+ cancel() {
156
+ if (!endGesture) return;
157
+ const close = endGesture;
158
+ close();
159
+ reset();
160
+ }
161
+ };
171
162
  }
@@ -1,129 +1,132 @@
1
+ // The pen's node kinds and ops, with no DOM, so the CLI can add strokes.
2
+ // ui.ts adds the renderers.
1
3
  import { z } from "zod";
2
4
  import { CanvasOpError } from "../ops/ops.js";
3
5
  import { definePlugin } from "../plugin.js";
4
6
  import { buildStrokeNode } from "./geometry.js";
5
7
  import { buildShapeNode, buildTextNode } from "./shapes.js";
6
8
  import { PEN_COLORS, SHAPE_KINDS, SHAPE_NODE, STROKE_NODE, TEXT_NODE } from "./types.js";
9
+ // =========================
10
+ // SCHEMAS
11
+ // =========================
7
12
  const colorSchema = z.enum(PEN_COLORS);
8
13
  const pointSchema = z.object({
9
- x: z.number(),
10
- y: z.number()
14
+ x: z.number(),
15
+ y: z.number()
11
16
  });
12
- const MAX_POINTS = 5000;
17
+ /** A board holds a few thousand strokes at most, and one stroke past this is a scribble pasted from a file. */
18
+ const MAX_POINTS = 5e3;
13
19
  const addStrokeOp = z.object({
14
- kind: z.literal("addStroke"),
15
- id: z.string().min(1).optional(),
16
- points: z.array(z.tuple([
17
- z.number(),
18
- z.number()
19
- ]).rest(z.number())).min(1).max(MAX_POINTS),
20
- color: colorSchema.optional(),
21
- size: z.number().positive().optional(),
22
- tool: z.enum([
23
- "pen",
24
- "highlighter"
25
- ]).optional()
20
+ kind: z.literal("addStroke"),
21
+ id: z.string().min(1).optional(),
22
+ /** Flow coordinates, x then y then an optional 0 to 1 pressure. Simplified on the way in. */
23
+ points: z.array(z.tuple([z.number(), z.number()]).rest(z.number())).min(1).max(MAX_POINTS),
24
+ color: colorSchema.optional(),
25
+ size: z.number().positive().optional(),
26
+ tool: z.enum(["pen", "highlighter"]).optional()
26
27
  });
27
28
  const addTextOp = z.object({
28
- kind: z.literal("addText"),
29
- id: z.string().min(1).optional(),
30
- at: pointSchema,
31
- text: z.string(),
32
- color: colorSchema.optional(),
33
- fontSize: z.number().positive().optional()
29
+ kind: z.literal("addText"),
30
+ id: z.string().min(1).optional(),
31
+ at: pointSchema,
32
+ text: z.string(),
33
+ color: colorSchema.optional(),
34
+ fontSize: z.number().positive().optional()
34
35
  });
35
36
  const addShapeOp = z.object({
36
- kind: z.literal("addShape"),
37
- id: z.string().min(1).optional(),
38
- shape: z.enum(SHAPE_KINDS),
39
- box: z.object({
40
- x: z.number(),
41
- y: z.number(),
42
- width: z.number().nonnegative(),
43
- height: z.number().nonnegative()
44
- }),
45
- color: colorSchema.optional(),
46
- filled: z.boolean().optional(),
47
- label: z.string().optional()
37
+ kind: z.literal("addShape"),
38
+ id: z.string().min(1).optional(),
39
+ shape: z.enum(SHAPE_KINDS),
40
+ box: z.object({
41
+ x: z.number(),
42
+ y: z.number(),
43
+ width: z.number().nonnegative(),
44
+ height: z.number().nonnegative()
45
+ }),
46
+ color: colorSchema.optional(),
47
+ filled: z.boolean().optional(),
48
+ label: z.string().optional()
48
49
  });
50
+ // =========================
51
+ // HANDLERS
52
+ // =========================
49
53
  function appendNode(state, node, opIndex) {
50
- const id = node.id ?? crypto.randomUUID();
51
- if (state.nodes.some((existing)=>existing.id === id)) {
52
- throw new CanvasOpError(`Node id already exists: ${id}`, opIndex);
53
- }
54
- state.nodes = [
55
- ...state.nodes,
56
- {
57
- ...node,
58
- id
59
- }
60
- ];
54
+ const id = node.id ?? crypto.randomUUID();
55
+ if (state.nodes.some((existing) => existing.id === id)) {
56
+ throw new CanvasOpError(`Node id already exists: ${id}`, opIndex);
57
+ }
58
+ state.nodes = [...state.nodes, {
59
+ ...node,
60
+ id
61
+ }];
61
62
  }
62
63
  function applyAddStroke(state, op, opIndex) {
63
- const node = buildStrokeNode(op.points, {
64
- id: op.id,
65
- color: op.color,
66
- size: op.size,
67
- tool: op.tool
68
- });
69
- if (!node) throw new CanvasOpError("addStroke needs at least one point", opIndex);
70
- appendNode(state, node, opIndex);
64
+ const node = buildStrokeNode(op.points, {
65
+ id: op.id,
66
+ color: op.color,
67
+ size: op.size,
68
+ tool: op.tool
69
+ });
70
+ if (!node) throw new CanvasOpError("addStroke needs at least one point", opIndex);
71
+ appendNode(state, node, opIndex);
71
72
  }
72
73
  function applyAddText(state, op, opIndex) {
73
- appendNode(state, buildTextNode(op.at, {
74
- id: op.id,
75
- text: op.text,
76
- color: op.color,
77
- fontSize: op.fontSize
78
- }), opIndex);
74
+ appendNode(state, buildTextNode(op.at, {
75
+ id: op.id,
76
+ text: op.text,
77
+ color: op.color,
78
+ fontSize: op.fontSize
79
+ }), opIndex);
79
80
  }
80
81
  function applyAddShape(state, op, opIndex) {
81
- appendNode(state, buildShapeNode(op.shape, op.box, {
82
- id: op.id,
83
- color: op.color,
84
- filled: op.filled,
85
- label: op.label
86
- }), opIndex);
82
+ appendNode(state, buildShapeNode(op.shape, op.box, {
83
+ id: op.id,
84
+ color: op.color,
85
+ filled: op.filled,
86
+ label: op.label
87
+ }), opIndex);
87
88
  }
89
+ // =========================
90
+ // PLUGIN
91
+ // =========================
92
+ /** No `palette` on any kind: the tool bar's pen tools create these, not the card palette. */
88
93
  export const penPlugin = definePlugin({
89
- id: "pen",
90
- nodes: [
91
- {
92
- type: STROKE_NODE,
93
- label: "Stroke"
94
- },
95
- {
96
- type: TEXT_NODE,
97
- label: "Text",
98
- sizing: "content-sized",
99
- size: {
100
- width: 120
101
- }
102
- },
103
- {
104
- type: SHAPE_NODE,
105
- label: "Shape",
106
- size: {
107
- width: 160,
108
- height: 100
109
- }
110
- }
111
- ],
112
- ops: [
113
- {
114
- kind: "addStroke",
115
- schema: addStrokeOp,
116
- apply: applyAddStroke
117
- },
118
- {
119
- kind: "addText",
120
- schema: addTextOp,
121
- apply: applyAddText
122
- },
123
- {
124
- kind: "addShape",
125
- schema: addShapeOp,
126
- apply: applyAddShape
127
- }
128
- ]
94
+ id: "pen",
95
+ nodes: [
96
+ {
97
+ type: STROKE_NODE,
98
+ label: "Stroke"
99
+ },
100
+ {
101
+ type: TEXT_NODE,
102
+ label: "Text",
103
+ sizing: "content-sized",
104
+ size: { width: 120 }
105
+ },
106
+ {
107
+ type: SHAPE_NODE,
108
+ label: "Shape",
109
+ size: {
110
+ width: 160,
111
+ height: 100
112
+ }
113
+ }
114
+ ],
115
+ ops: [
116
+ {
117
+ kind: "addStroke",
118
+ schema: addStrokeOp,
119
+ apply: applyAddStroke
120
+ },
121
+ {
122
+ kind: "addText",
123
+ schema: addTextOp,
124
+ apply: applyAddText
125
+ },
126
+ {
127
+ kind: "addShape",
128
+ schema: addShapeOp,
129
+ apply: applyAddShape
130
+ }
131
+ ]
129
132
  });