@heyramzi/ui 2.9.0 → 2.10.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 (312) hide show
  1. package/dist/agents/helpers.js +52 -0
  2. package/dist/agents/index.js +3 -0
  3. package/dist/agents/page-state.js +65 -0
  4. package/dist/agents/types.js +1 -0
  5. package/dist/agents/vendor/color.js +149 -0
  6. package/dist/agents/vendor/draw.js +548 -0
  7. package/dist/agents/vendor/engine.js +632 -0
  8. package/dist/agents/vendor/mesh.js +727 -0
  9. package/dist/agents/vendor/plastic.js +3273 -0
  10. package/dist/agents/vendor/presets.js +171 -0
  11. package/dist/agents/vendor/shapes.js +24 -0
  12. package/dist/agents/vendor/ticker.js +56 -0
  13. package/dist/agents/vendor/types.js +0 -0
  14. package/dist/agents/vendor/wear.js +1525 -0
  15. package/dist/ai/ai-button/index.js +2 -0
  16. package/dist/ai/confirmation/confirmation-context.svelte.js +12 -0
  17. package/dist/ai/confirmation/index.js +9 -0
  18. package/dist/ai/generation-progress/index.js +2 -0
  19. package/dist/ai/index.js +11 -0
  20. package/dist/ai/loading-state/index.js +3 -0
  21. package/dist/ai/prompt-input/index.js +6 -0
  22. package/dist/ai/reasoning/index.js +4 -0
  23. package/dist/ai/reasoning/reasoning-context.svelte.js +12 -0
  24. package/dist/ai/response/index.js +3 -0
  25. package/dist/ai/suggestion/index.js +3 -0
  26. package/dist/ai/thinking-dots/index.js +3 -0
  27. package/dist/ai/typing-dots/index.js +2 -0
  28. package/dist/ai/voice-waveform/index.js +3 -0
  29. package/dist/animations/text-swap.js +30 -0
  30. package/dist/brand/index.js +65 -0
  31. package/dist/canvas/card/resize.js +10 -0
  32. package/dist/canvas/edges/edge-path.js +284 -0
  33. package/dist/canvas/editor/context-menu.js +42 -0
  34. package/dist/canvas/editor/dispatch.svelte.js +157 -0
  35. package/dist/canvas/editor/editor-context.js +8 -0
  36. package/dist/canvas/editor/editor-mode.js +0 -0
  37. package/dist/canvas/editor/frame.js +87 -0
  38. package/dist/canvas/editor/hand-context.js +8 -0
  39. package/dist/canvas/editor/history-core.js +221 -0
  40. package/dist/canvas/editor/keymap.js +193 -0
  41. package/dist/canvas/editor/layout-actions.js +170 -0
  42. package/dist/canvas/editor/lock.js +65 -0
  43. package/dist/canvas/editor/rebase.js +279 -0
  44. package/dist/canvas/editor/style.js +174 -0
  45. package/dist/canvas/editor/text-field-context.js +8 -0
  46. package/dist/canvas/editor/z-order.js +82 -0
  47. package/dist/canvas/headless/alignment.js +20 -0
  48. package/dist/canvas/headless/break-apart-find.js +324 -0
  49. package/dist/canvas/headless/break-apart-pieces.js +74 -0
  50. package/dist/canvas/headless/break-apart-plan.js +378 -0
  51. package/dist/canvas/headless/break-apart-style.js +93 -0
  52. package/dist/canvas/headless/break-apart.js +206 -0
  53. package/dist/canvas/headless/canvas-cleanup.js +70 -0
  54. package/dist/canvas/headless/edge-anchors.js +134 -0
  55. package/dist/canvas/headless/edge-memo.js +28 -0
  56. package/dist/canvas/headless/edge-serialization.js +31 -0
  57. package/dist/canvas/headless/figure-geometry.js +331 -0
  58. package/dist/canvas/headless/fit-viewport.js +82 -0
  59. package/dist/canvas/headless/grid.js +96 -0
  60. package/dist/canvas/headless/icon-node.js +45 -0
  61. package/dist/canvas/headless/index.js +1 -0
  62. package/dist/canvas/headless/linkify.js +25 -0
  63. package/dist/canvas/headless/pen-marks.js +115 -0
  64. package/dist/canvas/headless/sketch.js +250 -0
  65. package/dist/canvas/headless/snap-guides.js +100 -0
  66. package/dist/canvas/headless/svg-figure.js +137 -0
  67. package/dist/canvas/headless/table-layout.js +161 -0
  68. package/dist/canvas/index.js +25 -0
  69. package/dist/canvas/lod/lod-mode.svelte.js +22 -0
  70. package/dist/canvas/lod/render-all.svelte.js +45 -0
  71. package/dist/canvas/ops/arrange.js +83 -0
  72. package/dist/canvas/ops/ops.js +318 -0
  73. package/dist/canvas/pen/colors.js +41 -0
  74. package/dist/canvas/pen/eraser.js +35 -0
  75. package/dist/canvas/pen/geometry.js +143 -0
  76. package/dist/canvas/pen/index.js +9 -0
  77. package/dist/canvas/pen/ops.js +15 -0
  78. package/dist/canvas/pen/pen-session.js +171 -0
  79. package/dist/canvas/pen/plugin.js +129 -0
  80. package/dist/canvas/pen/shapes.js +80 -0
  81. package/dist/canvas/pen/stroke-path.js +37 -0
  82. package/dist/canvas/pen/types.js +30 -0
  83. package/dist/canvas/pen/ui.js +20 -0
  84. package/dist/canvas/plugin.js +51 -0
  85. package/dist/canvas/screen/glyphs.js +24 -0
  86. package/dist/canvas/screen/labels.js +24 -0
  87. package/dist/canvas/screen/parts.js +140 -0
  88. package/dist/canvas/screen/plugin.js +71 -0
  89. package/dist/canvas/screen/types.js +44 -0
  90. package/dist/canvas/screen/ui.js +13 -0
  91. package/dist/canvas/test-utils.js +18 -0
  92. package/dist/canvas/types.js +0 -0
  93. package/dist/canvas/ui/forward-wheel.js +27 -0
  94. package/dist/canvas/ui/icons.js +133 -0
  95. package/dist/canvas/ui/place-panel.js +57 -0
  96. package/dist/canvas/ui/shortcut-tip.js +18 -0
  97. package/dist/canvas/ui/tools.js +411 -0
  98. package/dist/charts/dashboard/chart.js +1 -0
  99. package/dist/charts/dashboard/data-table.js +7 -0
  100. package/dist/charts/dashboard/index.js +13 -0
  101. package/dist/charts/dashboard/windows.js +5 -0
  102. package/dist/charts/edge-fade.js +7 -0
  103. package/dist/charts/hover-band.js +13 -0
  104. package/dist/charts/index.js +19 -0
  105. package/dist/charts/motion.js +15 -0
  106. package/dist/charts/primitives/chart-utils.js +22 -0
  107. package/dist/charts/primitives/index.js +4 -0
  108. package/dist/components/copy/clipboard.js +38 -0
  109. package/dist/components/copy/copied.svelte.js +26 -0
  110. package/dist/components/copy/index.js +3 -0
  111. package/dist/components/data-table/data-table.js +55 -0
  112. package/dist/components/data-table/index.js +7 -0
  113. package/dist/components/docs/index.js +3 -0
  114. package/dist/components/docs/scroll-spy.svelte.js +71 -0
  115. package/dist/components/field-row/index.js +1 -0
  116. package/dist/components/hold-to-confirm/index.js +1 -0
  117. package/dist/components/icon/index.js +1 -0
  118. package/dist/components/language-flag/index.js +1 -0
  119. package/dist/components/list-page/filter-section.js +4 -0
  120. package/dist/components/list-page/index.js +25 -0
  121. package/dist/components/list-page/list-table.js +5 -0
  122. package/dist/components/list-page/queue-rail.js +3 -0
  123. package/dist/components/list-page/section-host.js +8 -0
  124. package/dist/components/list-page/step.js +5 -0
  125. package/dist/components/list-page/types.js +0 -0
  126. package/dist/components/locale-switch/index.js +1 -0
  127. package/dist/components/mark/index.js +1 -0
  128. package/dist/components/open-in/index.js +1 -0
  129. package/dist/components/picker/index.js +3 -0
  130. package/dist/components/picker/trigger.js +33 -0
  131. package/dist/components/picker/types.js +0 -0
  132. package/dist/components/section-card/index.js +1 -0
  133. package/dist/components/section-header/index.js +2 -0
  134. package/dist/components/segmented/collapse.js +32 -0
  135. package/dist/components/segmented/index.js +8 -0
  136. package/dist/components/segmented/segmented.js +50 -0
  137. package/dist/components/star-rating-input/index.js +2 -0
  138. package/dist/components/svg/index.js +1 -0
  139. package/dist/components/table-view/index.js +1 -0
  140. package/dist/components/table-view/types.js +0 -0
  141. package/dist/components/tags-input/index.js +2 -0
  142. package/dist/components/tags-input/tags.js +90 -0
  143. package/dist/components/theme/index.js +3 -0
  144. package/dist/components/toolbar/index.js +3 -0
  145. package/dist/components/toolbar/toolbar-trigger.js +4 -0
  146. package/dist/editor/anchors.js +17 -0
  147. package/dist/editor/block-id.js +62 -0
  148. package/dist/editor/block-ops.js +105 -0
  149. package/dist/editor/doc-blocks.js +181 -0
  150. package/dist/editor/extensions.js +200 -0
  151. package/dist/editor/index.js +4 -0
  152. package/dist/editor/markdown-test-helpers.js +6 -0
  153. package/dist/editor/markdown.js +486 -0
  154. package/dist/editor/serializer.js +268 -0
  155. package/dist/editor/slash-items.js +104 -0
  156. package/dist/editor/youtube-url.js +27 -0
  157. package/dist/effects/animated-beam/index.js +1 -0
  158. package/dist/effects/animated-beam/path-calculator.svelte.js +42 -0
  159. package/dist/effects/animated-beam/types.js +0 -0
  160. package/dist/effects/border-beam/css.js +420 -0
  161. package/dist/effects/border-beam/presets.js +1792 -0
  162. package/dist/effects/border-beam/register-properties.js +39 -0
  163. package/dist/effects/border-beam/types.js +0 -0
  164. package/dist/effects/image-comparison/compare.js +4 -0
  165. package/dist/effects/image-reveal/index.js +1 -0
  166. package/dist/effects/image-reveal/types.js +0 -0
  167. package/dist/effects/magnetic/magnetic.js +54 -0
  168. package/dist/effects/reveal/index.js +4 -0
  169. package/dist/effects/reveal/observe.js +43 -0
  170. package/dist/effects/reveal/types.js +0 -0
  171. package/dist/effects/shader-gradient/color.js +34 -0
  172. package/dist/effects/shader-gradient/index.js +1 -0
  173. package/dist/effects/shader-gradient/renderer.js +320 -0
  174. package/dist/effects/shader-gradient/shaders.js +280 -0
  175. package/dist/effects/shader-gradient/types.js +0 -0
  176. package/dist/icons/apple-symbol-exports.js +126 -0
  177. package/dist/icons/sf-symbols.js +465 -0
  178. package/dist/icons/trades.js +34 -0
  179. package/dist/inbox/index.js +4 -0
  180. package/dist/inbox/logic.js +156 -0
  181. package/dist/inbox/types.js +46 -0
  182. package/dist/index.js +18 -0
  183. package/dist/lib/arrive.js +13 -0
  184. package/dist/lib/icon-types.js +0 -0
  185. package/dist/lib/initials.js +17 -0
  186. package/dist/lib/layout.js +1 -0
  187. package/dist/lib/panel.js +3 -0
  188. package/dist/lib/result.js +26 -0
  189. package/dist/lib/sliding-pill.js +45 -0
  190. package/dist/lib/theme-transition.js +53 -0
  191. package/dist/lib/tone.js +32 -0
  192. package/dist/lib/utils.js +5 -0
  193. package/dist/onboarding/define-tours.js +28 -0
  194. package/dist/onboarding/index.js +4 -0
  195. package/dist/onboarding/onboarding.svelte.js +15 -0
  196. package/dist/onboarding/pick.js +16 -0
  197. package/dist/onboarding/stores.js +50 -0
  198. package/dist/onboarding/types.js +1 -0
  199. package/dist/onboarding/wait.js +25 -0
  200. package/dist/panel/index.js +1 -0
  201. package/dist/panel/side-panel-types.js +0 -0
  202. package/dist/record/demo-data.js +512 -0
  203. package/dist/record/fields.js +148 -0
  204. package/dist/record/format.js +97 -0
  205. package/dist/record/gutter.js +1 -0
  206. package/dist/record/index.js +15 -0
  207. package/dist/record/kinds.js +110 -0
  208. package/dist/record/logic.js +255 -0
  209. package/dist/record/types.js +52 -0
  210. package/dist/shadcn/accordion/index.js +5 -0
  211. package/dist/shadcn/alert/index.js +5 -0
  212. package/dist/shadcn/alert-dialog/index.js +12 -0
  213. package/dist/shadcn/attachment/index.js +10 -0
  214. package/dist/shadcn/avatar/index.js +7 -0
  215. package/dist/shadcn/badge/index.js +2 -0
  216. package/dist/shadcn/breadcrumb/index.js +8 -0
  217. package/dist/shadcn/bubble/index.js +5 -0
  218. package/dist/shadcn/button/index.js +2 -0
  219. package/dist/shadcn/calendar/index.js +19 -0
  220. package/dist/shadcn/card/index.js +8 -0
  221. package/dist/shadcn/checkbox/index.js +2 -0
  222. package/dist/shadcn/code-block/index.js +9 -0
  223. package/dist/shadcn/collapsible/index.js +4 -0
  224. package/dist/shadcn/command/index.js +13 -0
  225. package/dist/shadcn/context-menu/index.js +17 -0
  226. package/dist/shadcn/dialog/index.js +11 -0
  227. package/dist/shadcn/dropdown-menu/index.js +19 -0
  228. package/dist/shadcn/dropdown-menu/menu-item.js +23 -0
  229. package/dist/shadcn/empty/index.js +7 -0
  230. package/dist/shadcn/field/index.js +12 -0
  231. package/dist/shadcn/hooks/highlighter.js +92 -0
  232. package/dist/shadcn/hooks/is-mobile.svelte.js +7 -0
  233. package/dist/shadcn/hooks/use-highlighted-code.svelte.js +65 -0
  234. package/dist/shadcn/hover-card/index.js +5 -0
  235. package/dist/shadcn/input/index.js +3 -0
  236. package/dist/shadcn/input-otp/index.js +5 -0
  237. package/dist/shadcn/kbd/index.js +3 -0
  238. package/dist/shadcn/label/index.js +2 -0
  239. package/dist/shadcn/marker/index.js +4 -0
  240. package/dist/shadcn/message/index.js +7 -0
  241. package/dist/shadcn/message-scroller/index.js +8 -0
  242. package/dist/shadcn/popover/index.js +9 -0
  243. package/dist/shadcn/progress/index.js +2 -0
  244. package/dist/shadcn/progress/tone.js +11 -0
  245. package/dist/shadcn/radio-group/index.js +3 -0
  246. package/dist/shadcn/scroll-area/index.js +3 -0
  247. package/dist/shadcn/select/index.js +12 -0
  248. package/dist/shadcn/separator/index.js +2 -0
  249. package/dist/shadcn/sheet/index.js +11 -0
  250. package/dist/shadcn/sidebar/constants.js +6 -0
  251. package/dist/shadcn/sidebar/context.svelte.js +38 -0
  252. package/dist/shadcn/sidebar/index.js +25 -0
  253. package/dist/shadcn/skeleton/index.js +2 -0
  254. package/dist/shadcn/slider/index.js +2 -0
  255. package/dist/shadcn/sonner/index.js +1 -0
  256. package/dist/shadcn/spinner/index.js +1 -0
  257. package/dist/shadcn/switch/index.js +2 -0
  258. package/dist/shadcn/table/index.js +9 -0
  259. package/dist/shadcn/tabs/index.js +5 -0
  260. package/dist/shadcn/textarea/index.js +2 -0
  261. package/dist/shadcn/toggle/index.js +3 -0
  262. package/dist/shadcn/toggle-group/index.js +3 -0
  263. package/dist/shadcn/tooltip/context.js +8 -0
  264. package/dist/shadcn/tooltip/index.js +6 -0
  265. package/dist/shadcn/trace/index.js +9 -0
  266. package/dist/slides/bridges.js +66 -0
  267. package/dist/slides/deck-controller.svelte.js +221 -0
  268. package/dist/slides/export/capture.js +81 -0
  269. package/dist/slides/export/ffmpeg.js +85 -0
  270. package/dist/slides/export/index.js +4 -0
  271. package/dist/slides/export/launch.js +8 -0
  272. package/dist/slides/export/pdf.js +78 -0
  273. package/dist/slides/export/types.js +0 -0
  274. package/dist/slides/index.js +7 -0
  275. package/dist/slides/labels.js +26 -0
  276. package/dist/slides/layout.js +47 -0
  277. package/dist/slides/model.js +9 -0
  278. package/dist/slides/navigation.js +89 -0
  279. package/dist/slides/types.js +6 -0
  280. package/dist/status/define-statuses.js +16 -0
  281. package/dist/status/index.js +4 -0
  282. package/dist/status/status-types.js +0 -0
  283. package/dist/test-setup.js +26 -0
  284. package/dist/thread/index.js +3 -0
  285. package/dist/thread/logic.js +94 -0
  286. package/dist/thread/types.js +23 -0
  287. package/dist/work/board-order.js +109 -0
  288. package/dist/work/dates.js +19 -0
  289. package/dist/work/detail/detail-types.js +20 -0
  290. package/dist/work/docs/edited.js +39 -0
  291. package/dist/work/docs/index.js +7 -0
  292. package/dist/work/docs/labels.js +38 -0
  293. package/dist/work/docs/menu.js +121 -0
  294. package/dist/work/docs/outline.js +39 -0
  295. package/dist/work/gantt/logic.js +260 -0
  296. package/dist/work/inbox/inbox-logic.js +94 -0
  297. package/dist/work/inbox/inbox-statuses.js +86 -0
  298. package/dist/work/inbox/inbox-types.js +0 -0
  299. package/dist/work/inbox/index.js +1 -0
  300. package/dist/work/index.js +21 -0
  301. package/dist/work/kanban-drag.js +25 -0
  302. package/dist/work/kanban-types.js +0 -0
  303. package/dist/work/presets/sales.js +35 -0
  304. package/dist/work/reorder.js +10 -0
  305. package/dist/work/task-types.js +0 -0
  306. package/dist/work/time/duration.js +60 -0
  307. package/dist/work/time/time-types.js +25 -0
  308. package/dist/work/use-kanban-dnd.svelte.js +128 -0
  309. package/package.json +45 -3
  310. package/src/components/toolbar/toolbar-trigger.ts +8 -2
  311. package/src/record/demo-data.ts +4 -4
  312. package/src/record/record-demo.svelte +2 -2
@@ -0,0 +1,170 @@
1
+ import { alignNodesHorizontally, alignNodesVertically } from "../headless/alignment.js";
2
+ const DEFAULT_GROUP_MIN = {
3
+ width: 250,
4
+ height: 150
5
+ };
6
+ function setX(nodes, x) {
7
+ for (const n of nodes)n.position.x = x;
8
+ }
9
+ function setY(nodes, y) {
10
+ for (const n of nodes)n.position.y = y;
11
+ }
12
+ const ALIGNERS = {
13
+ left: (nodes)=>setX(nodes, Math.min(...nodes.map((n)=>n.position.x))),
14
+ right: (nodes)=>setX(nodes, Math.max(...nodes.map((n)=>n.position.x))),
15
+ top: (nodes)=>alignNodesHorizontally(nodes),
16
+ bottom: (nodes)=>setY(nodes, Math.max(...nodes.map((n)=>n.position.y))),
17
+ "center-h": (nodes)=>alignNodesVertically(nodes),
18
+ "center-v": (nodes)=>setY(nodes, nodes.reduce((sum, n)=>sum + n.position.y, 0) / nodes.length)
19
+ };
20
+ export function alignNodes(selected, direction) {
21
+ if (selected.length < 2) return new Map();
22
+ const mutableNodes = selected.map((n)=>({
23
+ id: n.id,
24
+ position: {
25
+ ...n.position
26
+ }
27
+ }));
28
+ ALIGNERS[direction](mutableNodes);
29
+ return new Map(mutableNodes.map((n)=>[
30
+ n.id,
31
+ n.position
32
+ ]));
33
+ }
34
+ const TIDY_GAP = 40;
35
+ export function tidySelectedNodes(selected) {
36
+ if (selected.length < 2) return new Map();
37
+ const xs = selected.map((n)=>n.position.x);
38
+ const ys = selected.map((n)=>n.position.y);
39
+ const xSpread = Math.max(...xs) - Math.min(...xs);
40
+ const ySpread = Math.max(...ys) - Math.min(...ys);
41
+ const horizontal = xSpread >= ySpread;
42
+ const sorted = [
43
+ ...selected
44
+ ].sort((a, b)=>horizontal ? a.position.x - b.position.x : a.position.y - b.position.y);
45
+ return horizontal ? stackAlongX(sorted) : stackAlongY(sorted);
46
+ }
47
+ function stackAlongX(sorted) {
48
+ const result = new Map();
49
+ let cursorX = sorted[0].position.x;
50
+ for (const node of sorted){
51
+ result.set(node.id, {
52
+ x: cursorX,
53
+ y: node.position.y
54
+ });
55
+ cursorX += (node.measured?.width ?? node.width ?? 200) + TIDY_GAP;
56
+ }
57
+ return result;
58
+ }
59
+ function stackAlongY(sorted) {
60
+ const result = new Map();
61
+ let cursorY = sorted[0].position.y;
62
+ for (const node of sorted){
63
+ result.set(node.id, {
64
+ x: node.position.x,
65
+ y: cursorY
66
+ });
67
+ cursorY += (node.measured?.height ?? node.height ?? 100) + TIDY_GAP;
68
+ }
69
+ return result;
70
+ }
71
+ const GROUP_PADDING = 32;
72
+ const GROUP_HEADER = 36;
73
+ const GROUP_GAP = 24;
74
+ const ROW_TOLERANCE = 8;
75
+ export function tidyGroup(children, min = DEFAULT_GROUP_MIN) {
76
+ if (children.length === 0) return null;
77
+ const width = (n)=>n.measured?.width ?? n.width ?? 200;
78
+ const height = (n)=>n.measured?.height ?? n.height ?? 100;
79
+ const rows = [];
80
+ for (const n of [
81
+ ...children
82
+ ].sort((a, b)=>a.position.y - b.position.y)){
83
+ const row = rows.at(-1);
84
+ if (row && n.position.y < row.bottom - ROW_TOLERANCE) {
85
+ row.items.push(n);
86
+ row.bottom = Math.max(row.bottom, n.position.y + height(n));
87
+ } else {
88
+ rows.push({
89
+ bottom: n.position.y + height(n),
90
+ items: [
91
+ n
92
+ ]
93
+ });
94
+ }
95
+ }
96
+ const positions = new Map();
97
+ let y = GROUP_PADDING + GROUP_HEADER;
98
+ let right = 0;
99
+ for (const row of rows){
100
+ let x = GROUP_PADDING;
101
+ for (const n of row.items.sort((a, b)=>a.position.x - b.position.x)){
102
+ positions.set(n.id, {
103
+ x,
104
+ y
105
+ });
106
+ x += width(n) + GROUP_GAP;
107
+ }
108
+ right = Math.max(right, x - GROUP_GAP);
109
+ y += Math.max(...row.items.map(height)) + GROUP_GAP;
110
+ }
111
+ return {
112
+ positions,
113
+ size: {
114
+ width: Math.max(min.width, right + GROUP_PADDING),
115
+ height: Math.max(min.height, y - GROUP_GAP + GROUP_PADDING)
116
+ }
117
+ };
118
+ }
119
+ export function groupSelectionOps(selected, groupLabel, { type = "group", data = {} } = {}) {
120
+ if (selected.length < 2) return [];
121
+ const parentId = selected[0].parentId;
122
+ const members = selected.filter((n)=>n.parentId === parentId);
123
+ if (members.length < 2) return [];
124
+ const width = (n)=>n.measured?.width ?? n.width ?? 200;
125
+ const height = (n)=>n.measured?.height ?? n.height ?? 100;
126
+ const minX = Math.min(...members.map((n)=>n.position.x));
127
+ const minY = Math.min(...members.map((n)=>n.position.y));
128
+ const maxX = Math.max(...members.map((n)=>n.position.x + width(n)));
129
+ const maxY = Math.max(...members.map((n)=>n.position.y + height(n)));
130
+ const origin = {
131
+ x: minX - GROUP_PADDING,
132
+ y: minY - GROUP_PADDING - GROUP_HEADER
133
+ };
134
+ const groupId = crypto.randomUUID();
135
+ return [
136
+ {
137
+ kind: "addNode",
138
+ node: {
139
+ id: groupId,
140
+ type,
141
+ position: origin,
142
+ data: {
143
+ ...data,
144
+ label: groupLabel
145
+ },
146
+ width: maxX - minX + GROUP_PADDING * 2,
147
+ height: maxY - minY + GROUP_PADDING * 2 + GROUP_HEADER,
148
+ ...parentId ? {
149
+ parentId
150
+ } : {}
151
+ }
152
+ },
153
+ ...members.flatMap((n)=>[
154
+ {
155
+ kind: "setParent",
156
+ nodeId: n.id,
157
+ parentId: groupId,
158
+ extent: "parent"
159
+ },
160
+ {
161
+ kind: "moveNode",
162
+ nodeId: n.id,
163
+ position: {
164
+ x: n.position.x - origin.x,
165
+ y: n.position.y - origin.y
166
+ }
167
+ }
168
+ ])
169
+ ];
170
+ }
@@ -0,0 +1,65 @@
1
+ export function isLocked(node) {
2
+ const data = node?.data;
3
+ return data?.locked === true;
4
+ }
5
+ export const LOCK_FLAGS = {
6
+ draggable: false,
7
+ deletable: false
8
+ };
9
+ export function withLockFlags(node) {
10
+ if (node.data?.syncedCopy) return node;
11
+ if (isLocked(node)) return {
12
+ ...node,
13
+ ...LOCK_FLAGS
14
+ };
15
+ if (node.draggable !== false && node.deletable !== false) return node;
16
+ const { draggable: _drag, deletable: _delete, ...rest } = node;
17
+ return rest;
18
+ }
19
+ function onlyUnlocks(op) {
20
+ if (op.kind !== "patchNode") return false;
21
+ const patch = op;
22
+ return patch.data !== undefined && patch.style === undefined && patch.width === undefined && patch.height === undefined && Object.keys(patch.data).every((key)=>key === "locked");
23
+ }
24
+ export function refuseLockedOps(nodes, ops) {
25
+ const locked = new Set(nodes.filter(isLocked).map((node)=>node.id));
26
+ if (locked.size === 0) return [
27
+ ...ops
28
+ ];
29
+ return ops.filter((op)=>{
30
+ const nodeId = op.nodeId;
31
+ return typeof nodeId !== "string" || !locked.has(nodeId) || onlyUnlocks(op);
32
+ });
33
+ }
34
+ export function deletableIds(nodes, ids) {
35
+ const parentOf = new Map(nodes.map((node)=>[
36
+ node.id,
37
+ node.parentId
38
+ ]));
39
+ const holdsLocked = new Set();
40
+ for (const node of nodes){
41
+ if (!isLocked(node)) continue;
42
+ for(let up = node.parentId; up; up = parentOf.get(up))holdsLocked.add(up);
43
+ }
44
+ const byId = new Map(nodes.map((node)=>[
45
+ node.id,
46
+ node
47
+ ]));
48
+ return ids.filter((id)=>!isLocked(byId.get(id)) && !holdsLocked.has(id));
49
+ }
50
+ export function toggleLock(nodes, picked) {
51
+ const targets = nodes.filter((node)=>picked.has(node.id) && !node.data.syncedCopy);
52
+ if (targets.length === 0) return null;
53
+ const locked = !targets.every(isLocked);
54
+ const ids = new Set(targets.map((node)=>node.id));
55
+ return {
56
+ locked,
57
+ nodes: nodes.map((node)=>ids.has(node.id) ? withLockFlags({
58
+ ...node,
59
+ data: {
60
+ ...node.data,
61
+ locked
62
+ }
63
+ }) : node)
64
+ };
65
+ }
@@ -0,0 +1,279 @@
1
+ import { tryCatch } from "../../lib/result.js";
2
+ import { applyOps } from "../ops/ops.js";
3
+ import { sortParentsFirst } from "../headless/canvas-cleanup.js";
4
+ const EDGE_PATCH_FIELDS = [
5
+ "source",
6
+ "target",
7
+ "label",
8
+ "animated",
9
+ "type",
10
+ "style",
11
+ "sourceHandle",
12
+ "targetHandle"
13
+ ];
14
+ function json(value) {
15
+ return JSON.stringify(value ?? null);
16
+ }
17
+ function objectStyle(node) {
18
+ return node.style && typeof node.style === "object" ? node.style : undefined;
19
+ }
20
+ function numberOrUndefined(value) {
21
+ return typeof value === "number" ? value : undefined;
22
+ }
23
+ function addNodeOp(node) {
24
+ const style = objectStyle(node);
25
+ const width = numberOrUndefined(node.width);
26
+ const height = numberOrUndefined(node.height);
27
+ return {
28
+ kind: "addNode",
29
+ node: {
30
+ id: node.id,
31
+ type: String(node.type ?? "node"),
32
+ position: node.position ?? {
33
+ x: 0,
34
+ y: 0
35
+ },
36
+ data: node.data ?? {},
37
+ ...style ? {
38
+ style
39
+ } : {},
40
+ ...width !== undefined ? {
41
+ width
42
+ } : {},
43
+ ...height !== undefined ? {
44
+ height
45
+ } : {},
46
+ ...node.parentId ? {
47
+ parentId: node.parentId
48
+ } : {}
49
+ }
50
+ };
51
+ }
52
+ function sizePatchOf(before, node) {
53
+ const patch = {};
54
+ const style = objectStyle(node);
55
+ if (style && json(before.style) !== json(node.style)) patch.style = style;
56
+ const width = numberOrUndefined(node.width);
57
+ if (width !== undefined && before.width !== node.width) patch.width = width;
58
+ const height = numberOrUndefined(node.height);
59
+ if (height !== undefined && before.height !== node.height) patch.height = height;
60
+ return patch;
61
+ }
62
+ function changedData(before, data) {
63
+ if (!data) return undefined;
64
+ const changed = Object.entries(data).filter(([key, value])=>json(before?.[key]) !== json(value));
65
+ return changed.length > 0 ? Object.fromEntries(changed) : undefined;
66
+ }
67
+ function changedNodeOps(before, node) {
68
+ const ops = [];
69
+ if (node.position && json(before.position) !== json(node.position)) {
70
+ ops.push({
71
+ kind: "moveNode",
72
+ nodeId: node.id,
73
+ position: node.position
74
+ });
75
+ }
76
+ const parentId = node.parentId ?? null;
77
+ if ((before.parentId ?? null) !== parentId) {
78
+ ops.push({
79
+ kind: "setParent",
80
+ nodeId: node.id,
81
+ parentId
82
+ });
83
+ }
84
+ const data = changedData(before.data, node.data);
85
+ if (data) ops.push({
86
+ kind: "patchNode",
87
+ nodeId: node.id,
88
+ data
89
+ });
90
+ const sizePatch = sizePatchOf(before, node);
91
+ if (Object.keys(sizePatch).length > 0) {
92
+ ops.push({
93
+ kind: "patchNode",
94
+ nodeId: node.id,
95
+ ...sizePatch
96
+ });
97
+ }
98
+ return ops;
99
+ }
100
+ function nodeOps(base, current) {
101
+ const baseNodes = new Map(base.nodes.map((n)=>[
102
+ n.id,
103
+ n
104
+ ]));
105
+ const currentIds = new Set(current.nodes.map((n)=>n.id));
106
+ const removed = base.nodes.filter((n)=>!currentIds.has(n.id)).map((n)=>({
107
+ kind: "removeNode",
108
+ nodeId: n.id
109
+ }));
110
+ const added = sortParentsFirst(current.nodes.filter((n)=>!baseNodes.has(n.id))).map(addNodeOp);
111
+ const changed = current.nodes.flatMap((node)=>{
112
+ const before = baseNodes.get(node.id);
113
+ return before ? changedNodeOps(before, node) : [];
114
+ });
115
+ return [
116
+ ...removed,
117
+ ...added,
118
+ ...changed
119
+ ];
120
+ }
121
+ function addEdgeOp(edge) {
122
+ const optional = (key)=>typeof edge[key] === "string" ? {
123
+ [key]: edge[key]
124
+ } : {};
125
+ const record = (key)=>{
126
+ const value = edge[key];
127
+ return value && typeof value === "object" && !Array.isArray(value) ? {
128
+ [key]: value
129
+ } : {};
130
+ };
131
+ return {
132
+ kind: "addEdge",
133
+ edge: {
134
+ id: edge.id,
135
+ source: String(edge.source),
136
+ target: String(edge.target),
137
+ ...optional("sourceHandle"),
138
+ ...optional("targetHandle"),
139
+ ...optional("type"),
140
+ ...optional("label"),
141
+ ...typeof edge.animated === "boolean" ? {
142
+ animated: edge.animated
143
+ } : {},
144
+ ...record("style"),
145
+ ...record("data")
146
+ }
147
+ };
148
+ }
149
+ function patchEdgeOps(before, edge) {
150
+ const patch = {};
151
+ for (const field of EDGE_PATCH_FIELDS){
152
+ if (edge[field] !== undefined && json(before[field]) !== json(edge[field])) {
153
+ patch[field] = edge[field];
154
+ }
155
+ }
156
+ if (Object.keys(patch).length === 0) return [];
157
+ return [
158
+ {
159
+ kind: "patchEdge",
160
+ edgeId: edge.id,
161
+ patch
162
+ }
163
+ ];
164
+ }
165
+ function edgeOps(base, current) {
166
+ const baseEdges = new Map(base.edges.map((e)=>[
167
+ e.id,
168
+ e
169
+ ]));
170
+ const currentIds = new Set(current.edges.map((e)=>e.id));
171
+ const removed = base.edges.filter((e)=>!currentIds.has(e.id)).map((e)=>({
172
+ kind: "removeEdge",
173
+ edgeId: e.id
174
+ }));
175
+ const upserted = current.edges.flatMap((edge)=>{
176
+ const before = baseEdges.get(edge.id);
177
+ return before ? patchEdgeOps(before, edge) : [
178
+ addEdgeOp(edge)
179
+ ];
180
+ });
181
+ return [
182
+ ...removed,
183
+ ...upserted
184
+ ];
185
+ }
186
+ export function diffToOps(base, current) {
187
+ return [
188
+ ...nodeOps(base, current),
189
+ ...edgeOps(base, current)
190
+ ];
191
+ }
192
+ const NODE_FIELDS = [
193
+ "type",
194
+ "zIndex"
195
+ ];
196
+ function nodeFieldPatches(base, current) {
197
+ const baseNodes = new Map(base.nodes.map((n)=>[
198
+ n.id,
199
+ n
200
+ ]));
201
+ const patches = [];
202
+ for (const node of current.nodes){
203
+ const before = baseNodes.get(node.id);
204
+ if (!before) continue;
205
+ const set = {};
206
+ const unset = [];
207
+ for (const field of NODE_FIELDS){
208
+ if (json(before[field]) === json(node[field])) continue;
209
+ if (node[field] === undefined) unset.push(field);
210
+ else set[field] = node[field];
211
+ }
212
+ const unsetData = Object.keys(before.data ?? {}).filter((key)=>!(key in (node.data ?? {})));
213
+ if (unset.length + unsetData.length + Object.keys(set).length > 0) {
214
+ patches.push({
215
+ nodeId: node.id,
216
+ set,
217
+ unset,
218
+ unsetData
219
+ });
220
+ }
221
+ }
222
+ return patches;
223
+ }
224
+ function withoutKeys(record, keys) {
225
+ return Object.fromEntries(Object.entries(record).filter(([key])=>!keys.includes(key)));
226
+ }
227
+ function applyNodeFieldPatches(nodes, patches) {
228
+ const byId = new Map(patches.map((patch)=>[
229
+ patch.nodeId,
230
+ patch
231
+ ]));
232
+ const seen = new Set();
233
+ const next = nodes.map((node)=>{
234
+ const patch = typeof node.id === "string" ? byId.get(node.id) : undefined;
235
+ if (!patch) return node;
236
+ seen.add(patch.nodeId);
237
+ const patched = withoutKeys({
238
+ ...node,
239
+ ...patch.set
240
+ }, patch.unset);
241
+ if (patch.unsetData.length === 0 || typeof node.data !== "object" || node.data === null) {
242
+ return patched;
243
+ }
244
+ return {
245
+ ...patched,
246
+ data: withoutKeys(node.data, patch.unsetData)
247
+ };
248
+ });
249
+ return seen.size === byId.size ? next : null;
250
+ }
251
+ function parentsFirst(nodes) {
252
+ const ids = new Set(nodes.map((node)=>node.id));
253
+ const seen = new Set();
254
+ const misordered = nodes.some((node)=>{
255
+ const early = ids.has(node.parentId) && !seen.has(node.parentId);
256
+ seen.add(node.id);
257
+ return early;
258
+ });
259
+ if (!misordered) return nodes;
260
+ const wrapped = nodes.map((node)=>({
261
+ id: String(node.id),
262
+ parentId: typeof node.parentId === "string" ? node.parentId : undefined,
263
+ node
264
+ }));
265
+ return sortParentsFirst(wrapped).map(({ node })=>node);
266
+ }
267
+ export function rebaseDiagram(serverDoc, lastSaved, local) {
268
+ const ops = diffToOps(lastSaved, local);
269
+ const res = tryCatch(()=>applyOps({
270
+ nodes: serverDoc.nodes,
271
+ edges: serverDoc.edges
272
+ }, ops));
273
+ if (!res.ok) return null;
274
+ const nodes = applyNodeFieldPatches(res.value.nodes, nodeFieldPatches(lastSaved, local));
275
+ return nodes ? {
276
+ ...res.value,
277
+ nodes: parentsFirst(nodes)
278
+ } : null;
279
+ }
@@ -0,0 +1,174 @@
1
+ import { PEN_COLORS, SHAPE_NODE, STROKE_NODE, TEXT_NODE } from "../pen/types.js";
2
+ export const FONT_SIZES = [
3
+ 16,
4
+ 20,
5
+ 28,
6
+ 40
7
+ ];
8
+ const SHAPE_WIDTHS = [
9
+ 2,
10
+ 3,
11
+ 5
12
+ ];
13
+ const PEN_WIDTHS = [
14
+ 2,
15
+ 4,
16
+ 8
17
+ ];
18
+ const HIGHLIGHTER_WIDTHS = [
19
+ 12,
20
+ 20,
21
+ 32
22
+ ];
23
+ const FRAME_WIDTHS = [
24
+ 1,
25
+ 2,
26
+ 4
27
+ ];
28
+ const CONTROLS = {
29
+ shape: [
30
+ "fill",
31
+ "stroke",
32
+ "width"
33
+ ],
34
+ text: [
35
+ "stroke",
36
+ "fontSize",
37
+ "fontWeight"
38
+ ],
39
+ stroke: [
40
+ "stroke",
41
+ "width"
42
+ ],
43
+ frame: [
44
+ "fill",
45
+ "stroke",
46
+ "width"
47
+ ]
48
+ };
49
+ function kindOf(node, frameType) {
50
+ if (node.type === SHAPE_NODE) return "shape";
51
+ if (node.type === TEXT_NODE) return "text";
52
+ if (node.type === STROKE_NODE) return "stroke";
53
+ return node.type === frameType && node.data.frame === true ? "frame" : null;
54
+ }
55
+ function widthsOf(kind, node) {
56
+ if (kind === "shape") return SHAPE_WIDTHS;
57
+ if (kind === "frame") return FRAME_WIDTHS;
58
+ return node.data.tool === "highlighter" ? HIGHLIGHTER_WIDTHS : PEN_WIDTHS;
59
+ }
60
+ function widthKey(kind) {
61
+ if (kind === "stroke") return "size";
62
+ return "strokeWidth";
63
+ }
64
+ function colorOf(value) {
65
+ return PEN_COLORS.find((color)=>color === value);
66
+ }
67
+ function levelOf(kind, node) {
68
+ const widths = widthsOf(kind, node);
69
+ const stored = Number(node.data[widthKey(kind)]);
70
+ if (!Number.isFinite(stored)) return kind === "frame" ? 0 : 1;
71
+ let nearest = 0;
72
+ for(let at = 1; at < widths.length; at += 1){
73
+ if (Math.abs(widths[at] - stored) < Math.abs(widths[nearest] - stored)) nearest = at;
74
+ }
75
+ return nearest;
76
+ }
77
+ const VALUES = {
78
+ shape: (node, color)=>({
79
+ fill: node.data.filled ? colorOf(node.data.fill) ?? color : "none",
80
+ stroke: color,
81
+ width: levelOf("shape", node)
82
+ }),
83
+ text: (node, color)=>({
84
+ stroke: color,
85
+ fontSize: Number(node.data.fontSize) || 20,
86
+ fontWeight: node.data.fontWeight === "bold" ? "bold" : "regular"
87
+ }),
88
+ stroke: (node, color)=>({
89
+ stroke: color,
90
+ width: levelOf("stroke", node)
91
+ }),
92
+ frame: (node)=>({
93
+ fill: colorOf(node.data.fill) ?? "none",
94
+ stroke: colorOf(node.data.stroke) ?? "ink",
95
+ width: levelOf("frame", node)
96
+ })
97
+ };
98
+ function valuesOf(kind, node) {
99
+ return VALUES[kind](node, colorOf(node.data.color) ?? "ink");
100
+ }
101
+ export function summarizeStyle(nodes, frameType) {
102
+ const controls = new Set();
103
+ const seen = new Map();
104
+ let widths = [];
105
+ for (const node of nodes){
106
+ const kind = kindOf(node, frameType);
107
+ if (!kind) continue;
108
+ const values = valuesOf(kind, node);
109
+ for (const control of CONTROLS[kind]){
110
+ controls.add(control);
111
+ const set = seen.get(control) ?? new Set();
112
+ set.add(values[control]);
113
+ seen.set(control, set);
114
+ }
115
+ if (widths.length === 0 && CONTROLS[kind].includes("width")) widths = widthsOf(kind, node);
116
+ }
117
+ if (controls.size === 0) return null;
118
+ const values = {};
119
+ for (const [control, set] of seen){
120
+ if (set.size === 1) Object.assign(values, {
121
+ [control]: [
122
+ ...set
123
+ ][0]
124
+ });
125
+ }
126
+ return {
127
+ controls,
128
+ values,
129
+ widths
130
+ };
131
+ }
132
+ function fillData(kind, fill) {
133
+ if (kind === "frame") return {
134
+ fill
135
+ };
136
+ return fill === "none" ? {
137
+ filled: false
138
+ } : {
139
+ filled: true,
140
+ fill
141
+ };
142
+ }
143
+ function textData(change) {
144
+ const data = {};
145
+ if (change.fontSize !== undefined) data.fontSize = change.fontSize;
146
+ if (change.fontWeight !== undefined) data.fontWeight = change.fontWeight;
147
+ return data;
148
+ }
149
+ function dataFor(kind, node, change) {
150
+ const data = {};
151
+ if (change.stroke) data[kind === "frame" ? "stroke" : "color"] = change.stroke;
152
+ if (change.fill !== undefined && (kind === "shape" || kind === "frame")) {
153
+ Object.assign(data, fillData(kind, change.fill));
154
+ }
155
+ if (change.width !== undefined && kind !== "text") {
156
+ data[widthKey(kind)] = widthsOf(kind, node)[change.width];
157
+ }
158
+ if (kind === "text") Object.assign(data, textData(change));
159
+ return data;
160
+ }
161
+ export function styleOps(nodes, change, frameType) {
162
+ const ops = [];
163
+ for (const node of nodes){
164
+ const kind = kindOf(node, frameType);
165
+ if (!kind) continue;
166
+ const data = dataFor(kind, node, change);
167
+ if (Object.keys(data).length > 0) ops.push({
168
+ kind: "patchNode",
169
+ nodeId: node.id,
170
+ data
171
+ });
172
+ }
173
+ return ops;
174
+ }
@@ -0,0 +1,8 @@
1
+ import { getContext, setContext } from "svelte";
2
+ export const TEXT_FIELD_KEY = Symbol("canvas-text-field");
3
+ export function setTextField(component) {
4
+ setContext(TEXT_FIELD_KEY, component);
5
+ }
6
+ export function useTextField() {
7
+ return getContext(TEXT_FIELD_KEY) ?? null;
8
+ }