@heyramzi/ui 1.54.1 → 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 (664) hide show
  1. package/COMPONENTS.md +3 -2
  2. package/README.md +34 -9
  3. package/package.json +200 -7
  4. package/scripts/sync-shadcn.mjs +2 -2
  5. package/src/ai/ai-button/AiButton.svelte +1 -1
  6. package/src/ai/confirmation/Confirmation.svelte +1 -1
  7. package/src/ai/confirmation/ConfirmationAction.svelte +1 -1
  8. package/src/ai/confirmation/ConfirmationActions.svelte +1 -1
  9. package/src/ai/confirmation/ConfirmationTitle.svelte +1 -1
  10. package/src/ai/generation-progress/GenerationProgress.svelte +13 -13
  11. package/src/ai/loading-state/PixelLoader.svelte +1 -1
  12. package/src/ai/prompt-input/PromptInput.svelte +1 -1
  13. package/src/ai/prompt-input/PromptInputBody.svelte +1 -1
  14. package/src/ai/prompt-input/PromptInputSubmit.svelte +1 -1
  15. package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -1
  16. package/src/ai/prompt-input/PromptInputToolbar.svelte +1 -1
  17. package/src/ai/reasoning/Reasoning.svelte +1 -1
  18. package/src/ai/reasoning/ReasoningContent.svelte +1 -1
  19. package/src/ai/reasoning/ReasoningTrigger.svelte +1 -1
  20. package/src/ai/response/Response.svelte +1 -1
  21. package/src/ai/suggestion/Suggestion.svelte +1 -1
  22. package/src/ai/suggestion/Suggestions.svelte +1 -1
  23. package/src/ai/thinking-dots/ThinkingDots.svelte +1 -1
  24. package/src/ai/typing-dots/TypingDots.svelte +1 -1
  25. package/src/ai/voice-waveform/VoiceWaveform.svelte +1 -1
  26. package/src/ambient.d.ts +8 -0
  27. package/src/brand/brand.test.ts +67 -0
  28. package/src/brand/index.ts +137 -0
  29. package/src/canvas/README.md +236 -0
  30. package/src/canvas/card/card-frame.svelte +61 -0
  31. package/src/canvas/card/card.css +39 -0
  32. package/src/canvas/card/resize-handles.svelte +119 -0
  33. package/src/canvas/card/resize.ts +33 -0
  34. package/src/canvas/chrome/chrome-zoom.svelte +120 -0
  35. package/src/canvas/chrome/chrome.css +231 -0
  36. package/src/canvas/chrome/lock-badge.svelte +18 -0
  37. package/src/canvas/chrome/side-handles.svelte +30 -0
  38. package/src/canvas/chrome/size-chip.svelte +45 -0
  39. package/src/canvas/chrome/snap-guides.svelte +69 -0
  40. package/src/canvas/components/icons/ToolIcon.svelte +30 -0
  41. package/src/canvas/components/shared/svg/PreviewSvg.svelte +67 -0
  42. package/src/canvas/components/shared/svg/ShapeOutline.svelte +76 -0
  43. package/src/canvas/components/shared/svg/StrokeNode.svelte +69 -0
  44. package/src/canvas/components/shared/svg/TextRule.svelte +37 -0
  45. package/src/canvas/edges/bend-handle.svelte +76 -0
  46. package/src/canvas/edges/edge-gradient.svelte +30 -0
  47. package/src/canvas/edges/edge-path.test.ts +58 -0
  48. package/src/canvas/edges/edge-path.ts +293 -0
  49. package/src/canvas/edges/gradient-edge.svelte +43 -0
  50. package/src/canvas/editor/context-menu.test.ts +65 -0
  51. package/src/canvas/editor/context-menu.ts +48 -0
  52. package/src/canvas/editor/dispatch.svelte.test.ts +243 -0
  53. package/src/canvas/editor/dispatch.svelte.ts +205 -0
  54. package/src/canvas/editor/editor-context.ts +62 -0
  55. package/src/canvas/editor/editor-mode.ts +67 -0
  56. package/src/canvas/editor/frame.ts +98 -0
  57. package/src/canvas/editor/hand-context.ts +15 -0
  58. package/src/canvas/editor/history-core.test.ts +239 -0
  59. package/src/canvas/editor/history-core.ts +247 -0
  60. package/src/canvas/editor/keymap.test.ts +161 -0
  61. package/src/canvas/editor/keymap.ts +151 -0
  62. package/src/canvas/editor/layout-actions.test.ts +156 -0
  63. package/src/canvas/editor/layout-actions.ts +219 -0
  64. package/src/canvas/editor/lock.test.ts +68 -0
  65. package/src/canvas/editor/lock.ts +87 -0
  66. package/src/canvas/editor/rebase.test.ts +208 -0
  67. package/src/canvas/editor/rebase.ts +272 -0
  68. package/src/canvas/editor/style.ts +184 -0
  69. package/src/canvas/editor/text-field-context.ts +41 -0
  70. package/src/canvas/editor/z-order.test.ts +75 -0
  71. package/src/canvas/editor/z-order.ts +87 -0
  72. package/src/canvas/headless/alignment.test.ts +59 -0
  73. package/src/canvas/headless/alignment.ts +23 -0
  74. package/src/canvas/headless/break-apart-find.ts +418 -0
  75. package/src/canvas/headless/break-apart-pieces.ts +104 -0
  76. package/src/canvas/headless/break-apart-plan.ts +439 -0
  77. package/src/canvas/headless/break-apart-style.ts +106 -0
  78. package/src/canvas/headless/break-apart.ts +244 -0
  79. package/src/canvas/headless/canvas-cleanup.test.ts +181 -0
  80. package/src/canvas/headless/canvas-cleanup.ts +102 -0
  81. package/src/canvas/headless/edge-anchors.test.ts +70 -0
  82. package/src/canvas/headless/edge-anchors.ts +143 -0
  83. package/src/canvas/headless/edge-memo.test.ts +58 -0
  84. package/src/canvas/headless/edge-memo.ts +32 -0
  85. package/src/canvas/headless/edge-serialization.test.ts +55 -0
  86. package/src/canvas/headless/edge-serialization.ts +32 -0
  87. package/src/canvas/headless/figure-geometry.ts +293 -0
  88. package/src/canvas/headless/fit-viewport.test.ts +85 -0
  89. package/src/canvas/headless/fit-viewport.ts +137 -0
  90. package/src/canvas/headless/grid.test.ts +34 -0
  91. package/src/canvas/headless/grid.ts +94 -0
  92. package/src/canvas/headless/icon-node.test.ts +23 -0
  93. package/src/canvas/headless/icon-node.ts +66 -0
  94. package/src/canvas/headless/index.ts +9 -0
  95. package/src/canvas/headless/linkify.test.ts +23 -0
  96. package/src/canvas/headless/linkify.ts +25 -0
  97. package/src/canvas/headless/pen-marks.ts +133 -0
  98. package/src/canvas/headless/sketch.test.ts +99 -0
  99. package/src/canvas/headless/sketch.ts +302 -0
  100. package/src/canvas/headless/snap-guides.test.ts +61 -0
  101. package/src/canvas/headless/snap-guides.ts +146 -0
  102. package/src/canvas/headless/svg-figure.test.ts +49 -0
  103. package/src/canvas/headless/svg-figure.ts +176 -0
  104. package/src/canvas/headless/table-layout.test.ts +158 -0
  105. package/src/canvas/headless/table-layout.ts +309 -0
  106. package/src/canvas/index.ts +35 -0
  107. package/src/canvas/lod/lod-mode.svelte.ts +30 -0
  108. package/src/canvas/lod/lod-switch.svelte +140 -0
  109. package/src/canvas/lod/render-all.svelte.ts +52 -0
  110. package/src/canvas/ops/arrange.ts +108 -0
  111. package/src/canvas/ops/ops.test.ts +98 -0
  112. package/src/canvas/ops/ops.ts +347 -0
  113. package/src/canvas/pen/PenField.svelte +128 -0
  114. package/src/canvas/pen/PenField.test.ts +87 -0
  115. package/src/canvas/pen/PenOverlay.svelte +172 -0
  116. package/src/canvas/pen/ShapeNode.svelte +64 -0
  117. package/src/canvas/pen/TextNode.svelte +97 -0
  118. package/src/canvas/pen/colors.ts +28 -0
  119. package/src/canvas/pen/eraser.ts +46 -0
  120. package/src/canvas/pen/geometry.ts +148 -0
  121. package/src/canvas/pen/host-field.test.svelte +13 -0
  122. package/src/canvas/pen/index.ts +40 -0
  123. package/src/canvas/pen/ops.ts +16 -0
  124. package/src/canvas/pen/pen-session.ts +177 -0
  125. package/src/canvas/pen/pen.css +149 -0
  126. package/src/canvas/pen/plugin.ts +139 -0
  127. package/src/canvas/pen/shapes.ts +104 -0
  128. package/src/canvas/pen/stroke-path.ts +54 -0
  129. package/src/canvas/pen/types.ts +96 -0
  130. package/src/canvas/pen/ui.ts +26 -0
  131. package/src/canvas/plugin.test.ts +45 -0
  132. package/src/canvas/plugin.ts +159 -0
  133. package/src/canvas/screen/GlyphIcon.svelte +25 -0
  134. package/src/canvas/screen/PartView.svelte +176 -0
  135. package/src/canvas/screen/ScreenBody.svelte +142 -0
  136. package/src/canvas/screen/ScreenCard.svelte +32 -0
  137. package/src/canvas/screen/glyphs/button.svg +1 -0
  138. package/src/canvas/screen/glyphs/close.svg +1 -0
  139. package/src/canvas/screen/glyphs/down.svg +1 -0
  140. package/src/canvas/screen/glyphs/header.svg +1 -0
  141. package/src/canvas/screen/glyphs/hills.svg +1 -0
  142. package/src/canvas/screen/glyphs/image.svg +1 -0
  143. package/src/canvas/screen/glyphs/input.svg +1 -0
  144. package/src/canvas/screen/glyphs/list.svg +1 -0
  145. package/src/canvas/screen/glyphs/modal.svg +1 -0
  146. package/src/canvas/screen/glyphs/plus.svg +1 -0
  147. package/src/canvas/screen/glyphs/up.svg +1 -0
  148. package/src/canvas/screen/glyphs.ts +40 -0
  149. package/src/canvas/screen/labels.ts +41 -0
  150. package/src/canvas/screen/parts.ts +164 -0
  151. package/src/canvas/screen/plugin.ts +67 -0
  152. package/src/canvas/screen/screen.css +593 -0
  153. package/src/canvas/screen/types.ts +47 -0
  154. package/src/canvas/screen/ui.ts +13 -0
  155. package/src/canvas/test-utils.ts +18 -0
  156. package/src/canvas/types.ts +53 -0
  157. package/src/canvas/ui/CanvasToolbar.svelte +169 -0
  158. package/src/canvas/ui/FrameLayer.svelte +116 -0
  159. package/src/canvas/ui/PenLayer.svelte +39 -0
  160. package/src/canvas/ui/StylePanel.svelte +268 -0
  161. package/src/canvas/ui/canvas-grid.svelte +156 -0
  162. package/src/canvas/ui/canvas-ui.css +350 -0
  163. package/src/canvas/ui/forward-wheel.ts +29 -0
  164. package/src/canvas/ui/icons.ts +124 -0
  165. package/src/canvas/ui/place-panel.ts +80 -0
  166. package/src/canvas/ui/shortcut-tip.test.ts +16 -0
  167. package/src/canvas/ui/shortcut-tip.ts +17 -0
  168. package/src/canvas/ui/tools.ts +146 -0
  169. package/src/charts/README.md +104 -0
  170. package/src/charts/ambient.d.ts +4 -0
  171. package/src/charts/animated-value.css +7 -0
  172. package/src/charts/animated-value.svelte +110 -0
  173. package/src/charts/area-chart.css +29 -0
  174. package/src/charts/area-chart.svelte +204 -0
  175. package/src/charts/bar-chart.css +119 -0
  176. package/src/charts/bar-chart.svelte +275 -0
  177. package/src/charts/charts.css +201 -0
  178. package/src/charts/crosshair.svelte +35 -0
  179. package/src/charts/dashboard/chart.ts +5 -0
  180. package/src/charts/dashboard/dashboard-empty.svelte +55 -0
  181. package/src/charts/dashboard/dashboard-section.svelte +58 -0
  182. package/src/charts/dashboard/data-panel.svelte +81 -0
  183. package/src/charts/dashboard/data-sort-head.svelte +102 -0
  184. package/src/charts/dashboard/data-table.svelte +278 -0
  185. package/src/charts/dashboard/data-table.ts +26 -0
  186. package/src/charts/dashboard/index.ts +25 -0
  187. package/src/charts/dashboard/panel-grid.svelte +43 -0
  188. package/src/charts/dashboard/period-filter.svelte +53 -0
  189. package/src/charts/dashboard/rating-chip.svelte +35 -0
  190. package/src/charts/dashboard/stat-band.svelte +47 -0
  191. package/src/charts/dashboard/stat-tile.svelte +176 -0
  192. package/src/charts/dashboard/windows.ts +8 -0
  193. package/src/charts/donut-chart.css +56 -0
  194. package/src/charts/donut-chart.svelte +158 -0
  195. package/src/charts/edge-fade.svelte +43 -0
  196. package/src/charts/edge-fade.ts +16 -0
  197. package/src/charts/figure.css +100 -0
  198. package/src/charts/figure.svelte +122 -0
  199. package/src/charts/gauge-chart.css +94 -0
  200. package/src/charts/gauge-chart.svelte +302 -0
  201. package/src/charts/hover-band.test.ts +30 -0
  202. package/src/charts/hover-band.ts +21 -0
  203. package/src/charts/hover-focus.svelte +85 -0
  204. package/src/charts/index.ts +31 -0
  205. package/src/charts/legend.css +136 -0
  206. package/src/charts/legend.svelte +112 -0
  207. package/src/charts/line-chart.css +26 -0
  208. package/src/charts/line-chart.svelte +256 -0
  209. package/src/charts/motion.ts +19 -0
  210. package/src/charts/number-chart.css +179 -0
  211. package/src/charts/number-chart.svelte +187 -0
  212. package/src/charts/primitives/chart-container.svelte +57 -0
  213. package/src/charts/primitives/chart-style.svelte +36 -0
  214. package/src/charts/primitives/chart-tooltip.svelte +161 -0
  215. package/src/charts/primitives/chart-utils.ts +54 -0
  216. package/src/charts/primitives/index.ts +11 -0
  217. package/src/charts/reference-line.css +18 -0
  218. package/src/charts/reference-line.svelte +31 -0
  219. package/src/charts/reveal.svelte +61 -0
  220. package/src/charts/ring-chart.css +104 -0
  221. package/src/charts/ring-chart.svelte +166 -0
  222. package/src/charts/sparkline.css +45 -0
  223. package/src/charts/sparkline.svelte +141 -0
  224. package/src/charts/stat-card.css +105 -0
  225. package/src/charts/stat-card.svelte +267 -0
  226. package/src/charts/time-series-chart.css +28 -0
  227. package/src/charts/time-series-chart.svelte +179 -0
  228. package/src/charts/trend-pill.css +38 -0
  229. package/src/charts/trend-pill.svelte +48 -0
  230. package/src/components/copy/clipboard.ts +13 -9
  231. package/src/components/copy/copy-button.svelte +2 -2
  232. package/src/components/data-table/row-arrow.svelte +1 -1
  233. package/src/components/data-table/sortable-head.svelte +1 -1
  234. package/src/components/docs/doc-page.svelte +1 -1
  235. package/src/components/docs/on-this-page.svelte +2 -2
  236. package/src/components/field-row/field-row.svelte +1 -1
  237. package/src/components/hold-to-confirm/hold-to-confirm.svelte +1 -1
  238. package/src/components/language-flag/language-flag.svelte +1 -1
  239. package/src/components/list-page/detail-empty.svelte +1 -1
  240. package/src/components/list-page/detail-header.svelte +1 -1
  241. package/src/components/list-page/detail-panel.svelte +1 -1
  242. package/src/components/list-page/detail-row.svelte +1 -1
  243. package/src/components/list-page/detail-stack.svelte +1 -1
  244. package/src/components/list-page/empty-state.svelte +1 -1
  245. package/src/components/list-page/filter-item.svelte +1 -1
  246. package/src/components/list-page/filter-popover.svelte +2 -2
  247. package/src/components/list-page/filter-section.ts +1 -1
  248. package/src/components/list-page/grouped-list.svelte +1 -1
  249. package/src/components/list-page/list-page-shell.svelte +1 -1
  250. package/src/components/list-page/list-table.ts +1 -1
  251. package/src/components/list-page/master-detail.svelte +1 -1
  252. package/src/components/list-page/notice-bar.svelte +1 -1
  253. package/src/components/list-page/prev-next.svelte +1 -1
  254. package/src/components/list-page/rail-body.svelte +1 -1
  255. package/src/components/list-page/rail-empty.svelte +1 -1
  256. package/src/components/list-page/rail-head.svelte +1 -1
  257. package/src/components/list-page/rail-row.svelte +1 -1
  258. package/src/components/list-page/section-label.svelte +2 -2
  259. package/src/components/list-page/toolbar-search.svelte +1 -1
  260. package/src/components/list-page/toolbar-trigger.ts +1 -1
  261. package/src/components/mark/mark.svelte +1 -1
  262. package/src/components/open-in/open-in.svelte +1 -1
  263. package/src/components/picker/mark-picker.svelte +1 -1
  264. package/src/components/picker/picker.svelte +1 -1
  265. package/src/components/section-card/section-card.svelte +1 -1
  266. package/src/components/segmented/animated-tabs.svelte +1 -1
  267. package/src/components/segmented/section-runs.svelte +1 -1
  268. package/src/components/segmented/segment.svelte +5 -5
  269. package/src/components/segmented/segmented-filter.svelte +1 -1
  270. package/src/components/segmented/segmented-nav.svelte +1 -1
  271. package/src/components/segmented/segmented-overflow.svelte +1 -1
  272. package/src/components/segmented/segmented-track.svelte +1 -1
  273. package/src/components/star-rating-input/star-rating-input.svelte +1 -1
  274. package/src/components/table-view/table-view.svelte +1 -1
  275. package/src/components/theme/theme-provider.svelte +6 -4
  276. package/src/components/toolbar/index.ts +5 -0
  277. package/src/components/toolbar/toolbar.svelte +168 -0
  278. package/src/components/toolbar/toolbar.test.ts +66 -0
  279. package/src/editor/README.md +58 -0
  280. package/src/editor/ambient.d.ts +1 -0
  281. package/src/editor/block-id.ts +77 -0
  282. package/src/editor/block-ops.test.ts +237 -0
  283. package/src/editor/block-ops.ts +157 -0
  284. package/src/editor/content.css +418 -0
  285. package/src/editor/editor-toolbar.svelte +303 -0
  286. package/src/editor/editor.svelte +462 -0
  287. package/src/editor/extensions.ts +145 -0
  288. package/src/editor/image-toolbar.svelte +123 -0
  289. package/src/editor/index.ts +17 -0
  290. package/src/editor/inline-editor.svelte +261 -0
  291. package/src/editor/markdown-escaping.test.ts +158 -0
  292. package/src/editor/markdown-marks.test.ts +89 -0
  293. package/src/editor/markdown-test-helpers.ts +13 -0
  294. package/src/editor/markdown-youtube.test.ts +93 -0
  295. package/src/editor/markdown.test.ts +250 -0
  296. package/src/editor/markdown.ts +511 -0
  297. package/src/editor/renderer.svelte +92 -0
  298. package/src/editor/serializer.test.ts +133 -0
  299. package/src/editor/serializer.ts +370 -0
  300. package/src/editor/slash-items.ts +115 -0
  301. package/src/editor/slash-menu.svelte +159 -0
  302. package/src/editor/youtube-url.ts +35 -0
  303. package/src/effects/animated-beam/AnimatedBeam.svelte +1 -1
  304. package/src/effects/border-beam/register-properties.ts +4 -2
  305. package/src/effects/image-comparison/ImageComparison.svelte +1 -1
  306. package/src/effects/image-reveal/ImageReveal.svelte +1 -1
  307. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +13 -13
  308. package/src/effects/magic-card/MagicCard.svelte +1 -1
  309. package/src/effects/marquee/Marquee.svelte +1 -1
  310. package/src/effects/progressive-blur/ProgressiveBlur.svelte +1 -1
  311. package/src/effects/reveal/AnimatedGroup.svelte +1 -1
  312. package/src/effects/reveal/InView.svelte +1 -1
  313. package/src/effects/reveal/TextEffect.svelte +1 -1
  314. package/src/effects/shader-gradient/ShaderGradient.svelte +1 -1
  315. package/src/effects/spotlight/Spotlight.svelte +1 -1
  316. package/src/effects/theme-toggle/ThemeToggle.svelte +4 -4
  317. package/src/lib/result.ts +19 -0
  318. package/src/lib/utils.ts +18 -0
  319. package/src/onboarding/README.md +120 -0
  320. package/src/onboarding/define-tours.test.ts +50 -0
  321. package/src/onboarding/define-tours.ts +31 -0
  322. package/src/onboarding/demo.svelte +86 -0
  323. package/src/onboarding/index.ts +12 -0
  324. package/src/onboarding/onboarding-provider.svelte +120 -0
  325. package/src/onboarding/onboarding.svelte.ts +24 -0
  326. package/src/onboarding/pick.test.ts +79 -0
  327. package/src/onboarding/pick.ts +36 -0
  328. package/src/onboarding/stores.test.ts +103 -0
  329. package/src/onboarding/stores.ts +58 -0
  330. package/src/onboarding/tour-card.svelte +70 -0
  331. package/src/onboarding/tour-runner.svelte +197 -0
  332. package/src/onboarding/types.ts +34 -0
  333. package/src/onboarding/wait.ts +29 -0
  334. package/src/shadcn/accordion/accordion-content.svelte +1 -1
  335. package/src/shadcn/accordion/accordion-item.svelte +1 -1
  336. package/src/shadcn/accordion/accordion-trigger.svelte +1 -1
  337. package/src/shadcn/accordion/accordion.svelte +1 -1
  338. package/src/shadcn/alert/alert-description.svelte +1 -1
  339. package/src/shadcn/alert/alert-title.svelte +1 -1
  340. package/src/shadcn/alert/alert.svelte +1 -1
  341. package/src/shadcn/alert-dialog/alert-dialog-action.svelte +1 -1
  342. package/src/shadcn/alert-dialog/alert-dialog-cancel.svelte +1 -1
  343. package/src/shadcn/alert-dialog/alert-dialog-content.svelte +1 -1
  344. package/src/shadcn/alert-dialog/alert-dialog-description.svelte +1 -1
  345. package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +1 -1
  346. package/src/shadcn/alert-dialog/alert-dialog-header.svelte +1 -1
  347. package/src/shadcn/alert-dialog/alert-dialog-overlay.svelte +1 -1
  348. package/src/shadcn/alert-dialog/alert-dialog-title.svelte +1 -1
  349. package/src/shadcn/attachment/attachment-action.svelte +1 -1
  350. package/src/shadcn/attachment/attachment-actions.svelte +1 -1
  351. package/src/shadcn/attachment/attachment-content.svelte +1 -1
  352. package/src/shadcn/attachment/attachment-description.svelte +1 -1
  353. package/src/shadcn/attachment/attachment-group.svelte +1 -1
  354. package/src/shadcn/attachment/attachment-media.svelte +1 -1
  355. package/src/shadcn/attachment/attachment-title.svelte +1 -1
  356. package/src/shadcn/attachment/attachment-trigger.svelte +1 -1
  357. package/src/shadcn/attachment/attachment.svelte +1 -1
  358. package/src/shadcn/avatar/avatar-badge.svelte +1 -1
  359. package/src/shadcn/avatar/avatar-fallback.svelte +1 -1
  360. package/src/shadcn/avatar/avatar-group-count.svelte +1 -1
  361. package/src/shadcn/avatar/avatar-group.svelte +1 -1
  362. package/src/shadcn/avatar/avatar-image.svelte +1 -1
  363. package/src/shadcn/avatar/avatar.svelte +1 -1
  364. package/src/shadcn/badge/badge.svelte +1 -1
  365. package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +1 -1
  366. package/src/shadcn/breadcrumb/breadcrumb-item.svelte +1 -1
  367. package/src/shadcn/breadcrumb/breadcrumb-link.svelte +1 -1
  368. package/src/shadcn/breadcrumb/breadcrumb-list.svelte +1 -1
  369. package/src/shadcn/breadcrumb/breadcrumb-page.svelte +1 -1
  370. package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +1 -1
  371. package/src/shadcn/breadcrumb/breadcrumb.svelte +1 -1
  372. package/src/shadcn/bubble/bubble-content.svelte +1 -1
  373. package/src/shadcn/bubble/bubble-group.svelte +1 -1
  374. package/src/shadcn/bubble/bubble-reactions.svelte +1 -1
  375. package/src/shadcn/bubble/bubble.svelte +1 -1
  376. package/src/shadcn/button/button.svelte +1 -1
  377. package/src/shadcn/calendar/calendar-cell.svelte +1 -1
  378. package/src/shadcn/calendar/calendar-day.svelte +1 -1
  379. package/src/shadcn/calendar/calendar-grid-body.svelte +1 -1
  380. package/src/shadcn/calendar/calendar-grid-head.svelte +1 -1
  381. package/src/shadcn/calendar/calendar-grid-row.svelte +1 -1
  382. package/src/shadcn/calendar/calendar-grid.svelte +1 -1
  383. package/src/shadcn/calendar/calendar-head-cell.svelte +1 -1
  384. package/src/shadcn/calendar/calendar-header.svelte +1 -1
  385. package/src/shadcn/calendar/calendar-heading.svelte +1 -1
  386. package/src/shadcn/calendar/calendar-month-select.svelte +1 -1
  387. package/src/shadcn/calendar/calendar-month.svelte +1 -1
  388. package/src/shadcn/calendar/calendar-months.svelte +1 -1
  389. package/src/shadcn/calendar/calendar-nav.svelte +1 -1
  390. package/src/shadcn/calendar/calendar-next-button.svelte +1 -1
  391. package/src/shadcn/calendar/calendar-prev-button.svelte +1 -1
  392. package/src/shadcn/calendar/calendar-year-select.svelte +1 -1
  393. package/src/shadcn/calendar/calendar.svelte +1 -1
  394. package/src/shadcn/card/card-action.svelte +1 -1
  395. package/src/shadcn/card/card-content.svelte +1 -1
  396. package/src/shadcn/card/card-description.svelte +1 -1
  397. package/src/shadcn/card/card-footer.svelte +1 -1
  398. package/src/shadcn/card/card-header.svelte +1 -1
  399. package/src/shadcn/card/card-title.svelte +1 -1
  400. package/src/shadcn/card/card.svelte +1 -1
  401. package/src/shadcn/checkbox/checkbox.svelte +1 -1
  402. package/src/shadcn/code-block/code-block-actions.svelte +1 -1
  403. package/src/shadcn/code-block/code-block-content.svelte +1 -1
  404. package/src/shadcn/code-block/code-block-footer.svelte +1 -1
  405. package/src/shadcn/code-block/code-block-header.svelte +1 -1
  406. package/src/shadcn/code-block/code-block-line.svelte +1 -1
  407. package/src/shadcn/code-block/code-block-title.svelte +1 -1
  408. package/src/shadcn/code-block/code-block.svelte +1 -1
  409. package/src/shadcn/command/command-dialog.svelte +1 -1
  410. package/src/shadcn/command/command-empty.svelte +1 -1
  411. package/src/shadcn/command/command-group.svelte +1 -1
  412. package/src/shadcn/command/command-input.svelte +2 -2
  413. package/src/shadcn/command/command-item.svelte +1 -1
  414. package/src/shadcn/command/command-link-item.svelte +1 -1
  415. package/src/shadcn/command/command-list.svelte +1 -1
  416. package/src/shadcn/command/command-separator.svelte +1 -1
  417. package/src/shadcn/command/command-shortcut.svelte +1 -1
  418. package/src/shadcn/command/command.svelte +1 -1
  419. package/src/shadcn/context-menu/context-menu-checkbox-item.svelte +1 -1
  420. package/src/shadcn/context-menu/context-menu-content.svelte +2 -2
  421. package/src/shadcn/context-menu/context-menu-group-heading.svelte +1 -1
  422. package/src/shadcn/context-menu/context-menu-item.svelte +1 -1
  423. package/src/shadcn/context-menu/context-menu-label.svelte +1 -1
  424. package/src/shadcn/context-menu/context-menu-radio-item.svelte +1 -1
  425. package/src/shadcn/context-menu/context-menu-separator.svelte +1 -1
  426. package/src/shadcn/context-menu/context-menu-shortcut.svelte +1 -1
  427. package/src/shadcn/context-menu/context-menu-sub-content.svelte +1 -1
  428. package/src/shadcn/context-menu/context-menu-sub-trigger.svelte +1 -1
  429. package/src/shadcn/context-menu/context-menu-trigger.svelte +1 -1
  430. package/src/shadcn/dialog/dialog-content.svelte +1 -1
  431. package/src/shadcn/dialog/dialog-description.svelte +1 -1
  432. package/src/shadcn/dialog/dialog-footer.svelte +1 -1
  433. package/src/shadcn/dialog/dialog-header.svelte +1 -1
  434. package/src/shadcn/dialog/dialog-overlay.svelte +1 -1
  435. package/src/shadcn/dialog/dialog-title.svelte +1 -1
  436. package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -1
  437. package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +1 -1
  438. package/src/shadcn/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  439. package/src/shadcn/dropdown-menu/dropdown-menu-item.svelte +1 -1
  440. package/src/shadcn/dropdown-menu/dropdown-menu-label.svelte +1 -1
  441. package/src/shadcn/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
  442. package/src/shadcn/dropdown-menu/dropdown-menu-separator.svelte +1 -1
  443. package/src/shadcn/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
  444. package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  445. package/src/shadcn/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
  446. package/src/shadcn/empty/empty-content.svelte +1 -1
  447. package/src/shadcn/empty/empty-description.svelte +1 -1
  448. package/src/shadcn/empty/empty-header.svelte +1 -1
  449. package/src/shadcn/empty/empty-media.svelte +1 -1
  450. package/src/shadcn/empty/empty-title.svelte +1 -1
  451. package/src/shadcn/empty/empty.svelte +1 -1
  452. package/src/shadcn/field/field-content.svelte +1 -1
  453. package/src/shadcn/field/field-description.svelte +1 -1
  454. package/src/shadcn/field/field-error.svelte +1 -1
  455. package/src/shadcn/field/field-group.svelte +1 -1
  456. package/src/shadcn/field/field-label.svelte +1 -1
  457. package/src/shadcn/field/field-legend.svelte +1 -1
  458. package/src/shadcn/field/field-separator.svelte +1 -1
  459. package/src/shadcn/field/field-set.svelte +1 -1
  460. package/src/shadcn/field/field-title.svelte +1 -1
  461. package/src/shadcn/field/field.svelte +1 -1
  462. package/src/shadcn/field/simple-field.svelte +1 -1
  463. package/src/shadcn/hover-card/hover-card-content.svelte +1 -1
  464. package/src/shadcn/input/input.svelte +1 -1
  465. package/src/shadcn/input/secret-input.svelte +1 -1
  466. package/src/shadcn/input-otp/input-otp-group.svelte +1 -1
  467. package/src/shadcn/input-otp/input-otp-separator.svelte +1 -1
  468. package/src/shadcn/input-otp/input-otp-slot.svelte +1 -1
  469. package/src/shadcn/input-otp/input-otp.svelte +1 -1
  470. package/src/shadcn/kbd/kbd-group.svelte +1 -1
  471. package/src/shadcn/kbd/kbd.svelte +1 -1
  472. package/src/shadcn/label/label.svelte +1 -1
  473. package/src/shadcn/marker/marker-content.svelte +1 -1
  474. package/src/shadcn/marker/marker-icon.svelte +1 -1
  475. package/src/shadcn/marker/marker.svelte +1 -1
  476. package/src/shadcn/message/message-avatar.svelte +1 -1
  477. package/src/shadcn/message/message-content.svelte +1 -1
  478. package/src/shadcn/message/message-footer.svelte +1 -1
  479. package/src/shadcn/message/message-group.svelte +1 -1
  480. package/src/shadcn/message/message-header.svelte +1 -1
  481. package/src/shadcn/message/message.svelte +1 -1
  482. package/src/shadcn/message-scroller/message-scroller-button.svelte +1 -1
  483. package/src/shadcn/message-scroller/message-scroller-content.svelte +1 -1
  484. package/src/shadcn/message-scroller/message-scroller-item.svelte +1 -1
  485. package/src/shadcn/message-scroller/message-scroller-viewport.svelte +1 -1
  486. package/src/shadcn/message-scroller/message-scroller.svelte +1 -1
  487. package/src/shadcn/popover/popover-content.svelte +1 -1
  488. package/src/shadcn/popover/popover-description.svelte +1 -1
  489. package/src/shadcn/popover/popover-header.svelte +1 -1
  490. package/src/shadcn/popover/popover-title.svelte +1 -1
  491. package/src/shadcn/popover/popover-trigger.svelte +1 -1
  492. package/src/shadcn/progress/progress.svelte +1 -1
  493. package/src/shadcn/radio-group/radio-group-item.svelte +1 -1
  494. package/src/shadcn/radio-group/radio-group.svelte +1 -1
  495. package/src/shadcn/scroll-area/scroll-area-scrollbar.svelte +1 -1
  496. package/src/shadcn/scroll-area/scroll-area.svelte +1 -1
  497. package/src/shadcn/select/select-content.svelte +2 -2
  498. package/src/shadcn/select/select-group-heading.svelte +1 -1
  499. package/src/shadcn/select/select-group.svelte +1 -1
  500. package/src/shadcn/select/select-item.svelte +1 -1
  501. package/src/shadcn/select/select-label.svelte +1 -1
  502. package/src/shadcn/select/select-scroll-down-button.svelte +1 -1
  503. package/src/shadcn/select/select-scroll-up-button.svelte +1 -1
  504. package/src/shadcn/select/select-separator.svelte +1 -1
  505. package/src/shadcn/select/select-trigger.svelte +1 -1
  506. package/src/shadcn/separator/separator.svelte +1 -1
  507. package/src/shadcn/sheet/sheet-content.svelte +1 -1
  508. package/src/shadcn/sheet/sheet-description.svelte +1 -1
  509. package/src/shadcn/sheet/sheet-footer.svelte +1 -1
  510. package/src/shadcn/sheet/sheet-header.svelte +1 -1
  511. package/src/shadcn/sheet/sheet-overlay.svelte +1 -1
  512. package/src/shadcn/sheet/sheet-title.svelte +1 -1
  513. package/src/shadcn/sidebar/sidebar-content.svelte +1 -1
  514. package/src/shadcn/sidebar/sidebar-footer.svelte +1 -1
  515. package/src/shadcn/sidebar/sidebar-group-action.svelte +1 -1
  516. package/src/shadcn/sidebar/sidebar-group-content.svelte +1 -1
  517. package/src/shadcn/sidebar/sidebar-group-label.svelte +1 -1
  518. package/src/shadcn/sidebar/sidebar-group.svelte +1 -1
  519. package/src/shadcn/sidebar/sidebar-header.svelte +1 -1
  520. package/src/shadcn/sidebar/sidebar-input.svelte +1 -1
  521. package/src/shadcn/sidebar/sidebar-inset.svelte +1 -1
  522. package/src/shadcn/sidebar/sidebar-menu-action.svelte +1 -1
  523. package/src/shadcn/sidebar/sidebar-menu-badge.svelte +1 -1
  524. package/src/shadcn/sidebar/sidebar-menu-button.svelte +1 -1
  525. package/src/shadcn/sidebar/sidebar-menu-item.svelte +1 -1
  526. package/src/shadcn/sidebar/sidebar-menu-skeleton.svelte +1 -1
  527. package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +1 -1
  528. package/src/shadcn/sidebar/sidebar-menu-sub-item.svelte +1 -1
  529. package/src/shadcn/sidebar/sidebar-menu-sub.svelte +1 -1
  530. package/src/shadcn/sidebar/sidebar-menu.svelte +1 -1
  531. package/src/shadcn/sidebar/sidebar-provider.svelte +1 -1
  532. package/src/shadcn/sidebar/sidebar-rail.svelte +1 -1
  533. package/src/shadcn/sidebar/sidebar-separator.svelte +1 -1
  534. package/src/shadcn/sidebar/sidebar-trigger.svelte +1 -1
  535. package/src/shadcn/sidebar/sidebar.svelte +1 -1
  536. package/src/shadcn/skeleton/skeleton.svelte +1 -1
  537. package/src/shadcn/slider/slider.svelte +1 -1
  538. package/src/shadcn/spinner/spinner.svelte +1 -1
  539. package/src/shadcn/switch/switch.svelte +1 -1
  540. package/src/shadcn/table/table-body.svelte +1 -1
  541. package/src/shadcn/table/table-caption.svelte +1 -1
  542. package/src/shadcn/table/table-cell.svelte +1 -1
  543. package/src/shadcn/table/table-footer.svelte +1 -1
  544. package/src/shadcn/table/table-head.svelte +1 -1
  545. package/src/shadcn/table/table-header.svelte +1 -1
  546. package/src/shadcn/table/table-row.svelte +1 -1
  547. package/src/shadcn/table/table.svelte +1 -1
  548. package/src/shadcn/tabs/tabs-content.svelte +1 -1
  549. package/src/shadcn/tabs/tabs-list.svelte +1 -1
  550. package/src/shadcn/tabs/tabs-trigger.svelte +1 -1
  551. package/src/shadcn/tabs/tabs.svelte +1 -1
  552. package/src/shadcn/textarea/textarea.svelte +1 -1
  553. package/src/shadcn/toggle/toggle.svelte +1 -1
  554. package/src/shadcn/toggle-group/toggle-group-item.svelte +1 -1
  555. package/src/shadcn/toggle-group/toggle-group.svelte +1 -1
  556. package/src/shadcn/tooltip/tooltip-content.svelte +2 -2
  557. package/src/shadcn/trace/trace-collapsible-content.svelte +1 -1
  558. package/src/shadcn/trace/trace-collapsible-trigger.svelte +1 -1
  559. package/src/shadcn/trace/trace-collapsible.svelte +1 -1
  560. package/src/shadcn/trace/trace-description.svelte +1 -1
  561. package/src/shadcn/trace/trace-icon.svelte +1 -1
  562. package/src/shadcn/trace/trace-line.svelte +1 -1
  563. package/src/shadcn/trace/trace-title.svelte +1 -1
  564. package/src/shadcn/trace/trace.svelte +1 -1
  565. package/src/slides/Controls.svelte +138 -0
  566. package/src/slides/Deck.svelte +271 -0
  567. package/src/slides/Fragment.svelte +71 -0
  568. package/src/slides/Overview.svelte +113 -0
  569. package/src/slides/Presenter.svelte +292 -0
  570. package/src/slides/README.md +89 -0
  571. package/src/slides/Slide.svelte +179 -0
  572. package/src/slides/SlideList.svelte +19 -0
  573. package/src/slides/ambient.d.ts +1 -0
  574. package/src/slides/bridges.ts +69 -0
  575. package/src/slides/deck-controller.svelte.ts +292 -0
  576. package/src/slides/demo-slides.svelte +358 -0
  577. package/src/slides/demo.svelte +176 -0
  578. package/src/slides/export/capture.ts +113 -0
  579. package/src/slides/export/ffmpeg.ts +120 -0
  580. package/src/slides/export/index.ts +9 -0
  581. package/src/slides/export/launch.ts +10 -0
  582. package/src/slides/export/pdf.ts +152 -0
  583. package/src/slides/export/types.ts +48 -0
  584. package/src/slides/index.ts +19 -0
  585. package/src/slides/labels.ts +48 -0
  586. package/src/slides/layout.ts +51 -0
  587. package/src/slides/model.ts +47 -0
  588. package/src/slides/navigation.ts +100 -0
  589. package/src/slides/theme.css +205 -0
  590. package/src/slides/types.ts +86 -0
  591. package/src/tasks/README.md +312 -0
  592. package/src/tasks/ambient.d.ts +3 -0
  593. package/src/tasks/board-order.ts +137 -0
  594. package/src/tasks/dates.ts +31 -0
  595. package/src/tasks/define-statuses.ts +32 -0
  596. package/src/tasks/demo.svelte +389 -0
  597. package/src/tasks/detail/detail-demo.svelte +186 -0
  598. package/src/tasks/detail/detail-types.ts +54 -0
  599. package/src/tasks/detail/task-chip.svelte +28 -0
  600. package/src/tasks/detail/task-detail.svelte +315 -0
  601. package/src/tasks/detail/task-property.svelte +17 -0
  602. package/src/tasks/detail/task-thread.svelte +258 -0
  603. package/src/tasks/gantt/gantt-bar.svelte +243 -0
  604. package/src/tasks/gantt/gantt-grid.svelte +94 -0
  605. package/src/tasks/gantt/gantt-group-row.svelte +55 -0
  606. package/src/tasks/gantt/gantt-header.svelte +71 -0
  607. package/src/tasks/gantt/gantt-task-row.svelte +186 -0
  608. package/src/tasks/gantt/gantt.css +22 -0
  609. package/src/tasks/gantt/gantt.test.ts +77 -0
  610. package/src/tasks/gantt/logic.test.ts +163 -0
  611. package/src/tasks/gantt/logic.ts +248 -0
  612. package/src/tasks/inbox/inbox-card.svelte +283 -0
  613. package/src/tasks/inbox/inbox-draft.svelte +84 -0
  614. package/src/tasks/inbox/inbox-logic.ts +55 -0
  615. package/src/tasks/inbox/inbox-statuses.ts +48 -0
  616. package/src/tasks/inbox/inbox-trace.svelte +45 -0
  617. package/src/tasks/inbox/inbox-types.ts +108 -0
  618. package/src/tasks/inbox/inbox.svelte +203 -0
  619. package/src/tasks/inbox/index.ts +23 -0
  620. package/src/tasks/index.ts +85 -0
  621. package/src/tasks/kanban-board.svelte +561 -0
  622. package/src/tasks/kanban-column.svelte +117 -0
  623. package/src/tasks/kanban-drag.ts +38 -0
  624. package/src/tasks/kanban-types.ts +65 -0
  625. package/src/tasks/presets/sales.ts +16 -0
  626. package/src/tasks/record/demo-data.ts +286 -0
  627. package/src/tasks/record/field-section.svelte +53 -0
  628. package/src/tasks/record/fields.ts +142 -0
  629. package/src/tasks/record/format.ts +87 -0
  630. package/src/tasks/record/gutter.ts +2 -0
  631. package/src/tasks/record/index.ts +54 -0
  632. package/src/tasks/record/info-list.svelte +316 -0
  633. package/src/tasks/record/kinds.ts +42 -0
  634. package/src/tasks/record/logic.ts +324 -0
  635. package/src/tasks/record/next-up.svelte +70 -0
  636. package/src/tasks/record/project-list.svelte +133 -0
  637. package/src/tasks/record/record-demo.svelte +324 -0
  638. package/src/tasks/record/record-header.svelte +70 -0
  639. package/src/tasks/record/record-shell.svelte +138 -0
  640. package/src/tasks/record/record-stats.svelte +48 -0
  641. package/src/tasks/record/record-timeline.svelte +231 -0
  642. package/src/tasks/record/record.test.ts +350 -0
  643. package/src/tasks/record/steps-rail.svelte +474 -0
  644. package/src/tasks/record/types.ts +280 -0
  645. package/src/tasks/record/where-we-are.svelte +121 -0
  646. package/src/tasks/reorder.ts +19 -0
  647. package/src/tasks/side-panel-types.ts +16 -0
  648. package/src/tasks/side-panel.svelte +193 -0
  649. package/src/tasks/status-badge.svelte +63 -0
  650. package/src/tasks/status-dot.svelte +228 -0
  651. package/src/tasks/status-icon.svelte +30 -0
  652. package/src/tasks/status-types.ts +40 -0
  653. package/src/tasks/task-gantt.svelte +230 -0
  654. package/src/tasks/task-list.svelte +470 -0
  655. package/src/tasks/task-types.ts +33 -0
  656. package/src/tasks/time/duration.ts +69 -0
  657. package/src/tasks/time/time-chip.svelte +62 -0
  658. package/src/tasks/time/time-dot.svelte +58 -0
  659. package/src/tasks/time/time-ring.svelte +74 -0
  660. package/src/tasks/time/time-tracker.svelte +565 -0
  661. package/src/tasks/time/time-types.ts +74 -0
  662. package/src/tasks/use-kanban-dnd.svelte.ts +173 -0
  663. package/src/tokens/base.css +5 -5
  664. package/src/tokens/palette.css +115 -120
@@ -0,0 +1,98 @@
1
+ // A frame from the frame tool: a titled box that adopts the cards it covers.
2
+ // A group (Cmd+G) is the same box untitled.
3
+
4
+ import type { CanvasOp } from "../ops/ops.js";
5
+
6
+ export interface FrameRect {
7
+ x: number;
8
+ y: number;
9
+ width: number;
10
+ height: number;
11
+ }
12
+
13
+ type Boxed = {
14
+ id: string;
15
+ position: { x: number; y: number };
16
+ measured?: { width?: number; height?: number };
17
+ width?: number | null;
18
+ height?: number | null;
19
+ parentId?: string;
20
+ type?: string;
21
+ };
22
+
23
+ /** A drag shorter than this on both sides was a click. */
24
+ const CLICK_DRAG = 8;
25
+ const DEFAULT_FRAME = { width: 480, height: 300 } as const;
26
+ const MIN_FRAME = { width: 80, height: 60 } as const;
27
+
28
+ /** The box a drag from one corner to the other covers, or the default size for a click. */
29
+ export function frameRect(from: { x: number; y: number }, to: { x: number; y: number }): FrameRect {
30
+ const width = Math.abs(to.x - from.x);
31
+ const height = Math.abs(to.y - from.y);
32
+ if (width < CLICK_DRAG && height < CLICK_DRAG) {
33
+ return { x: Math.round(from.x), y: Math.round(from.y), ...DEFAULT_FRAME };
34
+ }
35
+ return {
36
+ x: Math.round(Math.min(from.x, to.x)),
37
+ y: Math.round(Math.min(from.y, to.y)),
38
+ width: Math.round(Math.max(width, MIN_FRAME.width)),
39
+ height: Math.round(Math.max(height, MIN_FRAME.height)),
40
+ };
41
+ }
42
+
43
+ function sizeOf(node: Boxed): { width: number; height: number } | null {
44
+ const width = node.measured?.width ?? node.width;
45
+ const height = node.measured?.height ?? node.height;
46
+ return width && height ? { width, height } : null;
47
+ }
48
+
49
+ export interface FrameOptions {
50
+ /** The node type of the frame, the host's group type. */
51
+ type: string;
52
+ title: string;
53
+ /** Extra `data` on the frame beside its title. */
54
+ data?: Record<string, unknown>;
55
+ }
56
+
57
+ /** The op list for one frame, and the id it was given. Top-level cards wholly inside the box become its children. */
58
+ export function frameOps(
59
+ rect: FrameRect,
60
+ nodes: readonly Boxed[],
61
+ { type, title, data = {} }: FrameOptions,
62
+ ): { id: string; ops: CanvasOp[] } {
63
+ const id = crypto.randomUUID();
64
+ const inside = nodes.filter((node) => {
65
+ const size = sizeOf(node);
66
+ if (!size || node.parentId || node.type === type) return false;
67
+ const { x, y } = node.position;
68
+ return (
69
+ x >= rect.x &&
70
+ y >= rect.y &&
71
+ x + size.width <= rect.x + rect.width &&
72
+ y + size.height <= rect.y + rect.height
73
+ );
74
+ });
75
+ const ops: CanvasOp[] = [
76
+ {
77
+ kind: "addNode",
78
+ node: {
79
+ id,
80
+ type,
81
+ position: { x: rect.x, y: rect.y },
82
+ data: { ...data, label: title, frame: true },
83
+ width: rect.width,
84
+ height: rect.height,
85
+ style: { width: rect.width, height: rect.height },
86
+ },
87
+ },
88
+ ...inside.flatMap((node): CanvasOp[] => [
89
+ { kind: "setParent", nodeId: node.id, parentId: id, extent: "parent" },
90
+ {
91
+ kind: "moveNode",
92
+ nodeId: node.id,
93
+ position: { x: node.position.x - rect.x, y: node.position.y - rect.y },
94
+ },
95
+ ]),
96
+ ];
97
+ return { id, ops };
98
+ }
@@ -0,0 +1,15 @@
1
+ // Whether the host page is drawn by hand. The host sets it once and the pen's
2
+ // text and shapes read it. With no host, they keep the app's look.
3
+
4
+ import { getContext, setContext } from "svelte";
5
+
6
+ const KEY = Symbol("canvas-hand-drawn");
7
+
8
+ /** Call during component init. `read` is a getter, so a toggle reaches mounted nodes. */
9
+ export function setHandDrawn(read: () => boolean): void {
10
+ setContext(KEY, read);
11
+ }
12
+
13
+ export function useHandDrawn(): () => boolean {
14
+ return getContext<(() => boolean) | undefined>(KEY) ?? (() => false);
15
+ }
@@ -0,0 +1,239 @@
1
+ import { expectDefined } from "../test-utils.js";
2
+ import { describe, expect, it } from "vite-plus/test";
3
+ import type { Node, Edge } from "@xyflow/svelte";
4
+ import {
5
+ applyHistoryEntry,
6
+ diffFlowState,
7
+ mergeHistoryEntries,
8
+ type FlowState,
9
+ } from "./history-core.js";
10
+
11
+ function assertNever(): never {
12
+ throw new Error("an unchanged card was serialised");
13
+ }
14
+
15
+ function node(id: string, overrides: Partial<Node> = {}): Node {
16
+ return {
17
+ id,
18
+ type: "node",
19
+ position: { x: 0, y: 0 },
20
+ data: { label: id },
21
+ ...overrides,
22
+ } as Node;
23
+ }
24
+
25
+ function edge(id: string, source: string, target: string, overrides: Partial<Edge> = {}): Edge {
26
+ return { id, source, target, ...overrides } as Edge;
27
+ }
28
+
29
+ function state(nodes: Node[], edges: Edge[] = []): FlowState {
30
+ return { nodes, edges };
31
+ }
32
+
33
+ describe("diffFlowState", () => {
34
+ it("returns null when nothing changed", () => {
35
+ const a = state([node("a")], [edge("e", "a", "a")]);
36
+ const b = state([node("a")], [edge("e", "a", "a")]);
37
+ expect(diffFlowState(a, b)).toBeNull();
38
+ });
39
+
40
+ it("ignores volatile fields: selection is not an undo step", () => {
41
+ const a = state([node("a")]);
42
+ const b = state([node("a", { selected: true, dragging: true } as Partial<Node>)]);
43
+ expect(diffFlowState(a, b)).toBeNull();
44
+ });
45
+
46
+ it("captures only affected items, not the whole document", () => {
47
+ const untouched = node("keep");
48
+ const a = state([untouched, node("move")]);
49
+ const b = state([untouched, node("move", { position: { x: 100, y: 50 } })]);
50
+ const entry = expectDefined(diffFlowState(a, b), "diff entry");
51
+ expect(entry.nodesBefore.map((n) => n.id)).toEqual(["move"]);
52
+ expect(entry.nodesAfter.map((n) => n.id)).toEqual(["move"]);
53
+ });
54
+
55
+ it("never reads a card that is the same object on both sides", () => {
56
+ const untouched = { ...node("big"), toJSON: () => assertNever() };
57
+ const base = state([untouched, node("a")]);
58
+ const current = state([untouched, node("a", { position: { x: 5, y: 0 } })]);
59
+ const entry = expectDefined(diffFlowState(base, current));
60
+ expect(entry.nodesBefore.map((n) => n.id)).toEqual(["a"]);
61
+ });
62
+
63
+ it("still sees a card that was replaced by an equal-looking copy as unchanged", () => {
64
+ const base = state([node("a")]);
65
+ const current = state([{ ...node("a"), selected: true }]);
66
+ expect(diffFlowState(base, current)).toBeNull();
67
+ });
68
+
69
+ it("captures additions and removals", () => {
70
+ const a = state([node("a")], [edge("e1", "a", "a")]);
71
+ const b = state([node("b")], []);
72
+ const entry = expectDefined(diffFlowState(a, b), "diff entry");
73
+ expect(entry.nodesBefore.map((n) => n.id)).toEqual(["a"]);
74
+ expect(entry.nodesAfter.map((n) => n.id)).toEqual(["b"]);
75
+ expect(entry.edgesBefore.map((e) => e.id)).toEqual(["e1"]);
76
+ expect(entry.edgesAfter).toEqual([]);
77
+ });
78
+ });
79
+
80
+ describe("applyHistoryEntry", () => {
81
+ it("undo restores a data edit, redo reapplies it", () => {
82
+ const before = state([node("a", { data: { label: "old" } })]);
83
+ const after = state([node("a", { data: { label: "new" } })]);
84
+ const entry = expectDefined(diffFlowState(before, after), "diff entry");
85
+
86
+ const undone = applyHistoryEntry(after, entry, "undo");
87
+ expect(undone.nodes[0].data.label).toBe("old");
88
+
89
+ const redone = applyHistoryEntry(undone, entry, "redo");
90
+ expect(redone.nodes[0].data.label).toBe("new");
91
+ });
92
+
93
+ it("undo of a delete restores the node and its edges", () => {
94
+ const before = state([node("a"), node("b")], [edge("e", "a", "b")]);
95
+ const after = state([node("a")], []);
96
+ const entry = expectDefined(diffFlowState(before, after), "diff entry");
97
+
98
+ const undone = applyHistoryEntry(after, entry, "undo");
99
+ expect(undone.nodes.map((n) => n.id).sort()).toEqual(["a", "b"]);
100
+ expect(undone.edges.map((e) => e.id)).toEqual(["e"]);
101
+ });
102
+
103
+ it("undo of an add removes the added node", () => {
104
+ const before = state([node("a")]);
105
+ const after = state([node("a"), node("b")]);
106
+ const entry = expectDefined(diffFlowState(before, after), "diff entry");
107
+
108
+ const undone = applyHistoryEntry(after, entry, "undo");
109
+ expect(undone.nodes.map((n) => n.id)).toEqual(["a"]);
110
+ });
111
+
112
+ it("restores parents before children for SvelteFlow ordering", () => {
113
+ const parent = node("parent");
114
+ const child = node("child", { parentId: "parent" } as Partial<Node>);
115
+ const before = state([parent, child]);
116
+ const after = state([]);
117
+ const entry = expectDefined(diffFlowState(before, after), "diff entry");
118
+
119
+ const undone = applyHistoryEntry(after, entry, "undo");
120
+ const ids = undone.nodes.map((n) => n.id);
121
+ expect(ids.indexOf("parent")).toBeLessThan(ids.indexOf("child"));
122
+ });
123
+
124
+ // A mind map reads child order from edge order: on 12 Oct 2026 undoing a reparent
125
+ // appended the restored edge, and the next relayout swapped Alpha's children.
126
+ it("undo of a reparent puts the restored edge back at its index, redo too", () => {
127
+ const nodes = [node("alpha"), node("gamma"), node("a1"), node("a2"), node("g1")];
128
+ const before = state(nodes, [
129
+ edge("e-alpha-a1", "alpha", "a1"),
130
+ edge("e-alpha-a2", "alpha", "a2"),
131
+ edge("e-gamma-g1", "gamma", "g1"),
132
+ ]);
133
+ const after = state(nodes, [
134
+ edge("e-alpha-a2", "alpha", "a2"),
135
+ edge("e-gamma-a1", "gamma", "a1"),
136
+ edge("e-gamma-g1", "gamma", "g1"),
137
+ ]);
138
+ const entry = expectDefined(diffFlowState(before, after), "diff entry");
139
+
140
+ const undone = applyHistoryEntry(after, entry, "undo");
141
+ expect(undone.edges.map((e) => e.id)).toEqual(["e-alpha-a1", "e-alpha-a2", "e-gamma-g1"]);
142
+ const redone = applyHistoryEntry(undone, entry, "redo");
143
+ expect(redone.edges.map((e) => e.id)).toEqual(["e-alpha-a2", "e-gamma-a1", "e-gamma-g1"]);
144
+ });
145
+
146
+ // A reorder under the same parent keeps the edge's id and content, so only its index moved.
147
+ it("undo of a sibling reorder restores the edge order", () => {
148
+ const before = state(
149
+ [node("a1"), node("a2", { position: { x: 0, y: 60 } })],
150
+ [edge("e-alpha-a1", "alpha", "a1"), edge("e-alpha-a2", "alpha", "a2")],
151
+ );
152
+ const after = state(
153
+ [node("a1", { position: { x: 0, y: 60 } }), node("a2")],
154
+ [edge("e-alpha-a2", "alpha", "a2"), edge("e-alpha-a1", "alpha", "a1")],
155
+ );
156
+ const entry = expectDefined(diffFlowState(before, after), "diff entry");
157
+
158
+ const undone = applyHistoryEntry(after, entry, "undo");
159
+ expect(undone.edges.map((e) => e.id)).toEqual(["e-alpha-a1", "e-alpha-a2"]);
160
+ const redone = applyHistoryEntry(undone, entry, "redo");
161
+ expect(redone.edges.map((e) => e.id)).toEqual(["e-alpha-a2", "e-alpha-a1"]);
162
+ });
163
+
164
+ it("undo of a delete puts nodes back at their index, without regrouping a valid order", () => {
165
+ const parent = node("parent");
166
+ const before = state([parent, node("free"), node("child", { parentId: "parent" })]);
167
+ const after = state([parent]);
168
+ const entry = expectDefined(diffFlowState(before, after), "diff entry");
169
+
170
+ const undone = applyHistoryEntry(after, entry, "undo");
171
+ expect(undone.nodes.map((n) => n.id)).toEqual(["parent", "free", "child"]);
172
+ });
173
+
174
+ it("round-trips a compound change (move + edit + add + remove)", () => {
175
+ const before = state(
176
+ [node("stay"), node("move"), node("gone", { data: { label: "bye" } })],
177
+ [edge("e1", "stay", "move")],
178
+ );
179
+ const after = state(
180
+ [
181
+ node("stay"),
182
+ node("move", { position: { x: 40, y: 40 }, data: { label: "moved" } }),
183
+ node("fresh"),
184
+ ],
185
+ [edge("e1", "stay", "move", { label: "renamed" } as Partial<Edge>)],
186
+ );
187
+ const entry = expectDefined(diffFlowState(before, after), "diff entry");
188
+
189
+ const undone = applyHistoryEntry(after, entry, "undo");
190
+ expect(diffFlowState(before, undone)).toBeNull();
191
+
192
+ const redone = applyHistoryEntry(undone, entry, "redo");
193
+ expect(diffFlowState(after, redone)).toBeNull();
194
+ });
195
+ });
196
+
197
+ describe("mergeHistoryEntries", () => {
198
+ it("keeps the earliest before and the latest after for a node both touched", () => {
199
+ const s0 = state([node("a", { data: { label: "0" } })]);
200
+ const s1 = state([node("a", { data: { label: "1" } })]);
201
+ const s2 = state([node("a", { data: { label: "2" } })]);
202
+ const merged = mergeHistoryEntries(
203
+ expectDefined(diffFlowState(s0, s1), "first"),
204
+ expectDefined(diffFlowState(s1, s2), "second"),
205
+ );
206
+
207
+ expect(applyHistoryEntry(s2, merged, "undo").nodes[0].data.label).toBe("0");
208
+ expect(applyHistoryEntry(s0, merged, "redo").nodes[0].data.label).toBe("2");
209
+ });
210
+
211
+ it("merges different nodes, and a node added then edited has no before", () => {
212
+ const s0 = state([node("a")]);
213
+ const s1 = state([node("a"), node("b", { data: { label: "new" } })]);
214
+ const s2 = state([
215
+ node("a", { position: { x: 9, y: 9 } }),
216
+ node("b", { data: { label: "newer" } }),
217
+ ]);
218
+ const merged = mergeHistoryEntries(
219
+ expectDefined(diffFlowState(s0, s1), "first"),
220
+ expectDefined(diffFlowState(s1, s2), "second"),
221
+ );
222
+
223
+ expect(merged.nodesBefore.map((n) => n.id)).toEqual(["a"]);
224
+ expect(merged.nodesAfter.map((n) => n.id).sort()).toEqual(["a", "b"]);
225
+ expect(applyHistoryEntry(s2, merged, "undo").nodes.map((n) => n.id)).toEqual(["a"]);
226
+ });
227
+
228
+ it("carries node order from the earliest before to the latest after", () => {
229
+ const s0 = state([node("a"), node("b"), node("c")]);
230
+ const s1 = state([node("b"), node("a", { position: { x: 5, y: 5 } }), node("c")]);
231
+ const s2 = state([node("b"), node("c"), node("a", { position: { x: 1, y: 1 } })]);
232
+ const merged = mergeHistoryEntries(
233
+ expectDefined(diffFlowState(s0, s1), "first"),
234
+ expectDefined(diffFlowState(s1, s2), "second"),
235
+ );
236
+
237
+ expect(merged.nodeOrder).toEqual({ before: ["a", "b", "c"], after: ["b", "c", "a"] });
238
+ });
239
+ });
@@ -0,0 +1,247 @@
1
+ // Pure diff/apply core for diagram history. Each entry holds only the touched
2
+ // nodes and edges, so ops, layouts and xyflow's own writes share one mechanism.
3
+
4
+ import type { Node, Edge } from "@xyflow/svelte";
5
+ import { sortParentsFirst } from "../headless/canvas-cleanup.js";
6
+
7
+ export type FlowState = { nodes: Node[]; edges: Edge[] };
8
+
9
+ /** Each side's id sequence, kept only when the step changed it. */
10
+ export type IdOrder = { before: string[]; after: string[] };
11
+
12
+ export type HistoryEntry = {
13
+ nodesBefore: Node[];
14
+ nodesAfter: Node[];
15
+ edgesBefore: Edge[];
16
+ edgesAfter: Edge[];
17
+ // WHY: array order is data. Node order is stacking, edge order is mind map child order.
18
+ nodeOrder?: IdOrder;
19
+ edgeOrder?: IdOrder;
20
+ };
21
+
22
+ // Fields that churn without changing the document. Stripped, so selecting isn't
23
+ // an undo step and undo doesn't restore a stale selection.
24
+ const VOLATILE_NODE_FIELDS = new Set(["selected", "dragging", "measured", "resizing"]);
25
+ const VOLATILE_EDGE_FIELDS = new Set(["selected"]);
26
+
27
+ function stripVolatile<T extends Record<string, unknown>>(record: T, volatile: Set<string>): T {
28
+ const out: Record<string, unknown> = {};
29
+ for (const [key, value] of Object.entries(record)) {
30
+ if (volatile.has(key) || value === undefined) continue;
31
+ out[key] = value;
32
+ }
33
+ return out as T;
34
+ }
35
+
36
+ function clone<T>(value: T): T {
37
+ return JSON.parse(JSON.stringify(value)) as T;
38
+ }
39
+
40
+ function signature(record: Record<string, unknown>, volatile: Set<string>): string {
41
+ return JSON.stringify(stripVolatile(record, volatile));
42
+ }
43
+
44
+ /** True when two versions of a card differ only in fields that churn without changing the document. */
45
+ export function isSameNode(a: Node, b: Node): boolean {
46
+ return (
47
+ a === b ||
48
+ signature(a as Record<string, unknown>, VOLATILE_NODE_FIELDS) ===
49
+ signature(b as Record<string, unknown>, VOLATILE_NODE_FIELDS)
50
+ );
51
+ }
52
+
53
+ export function isSameEdge(a: Edge, b: Edge): boolean {
54
+ return (
55
+ a === b ||
56
+ signature(a as Record<string, unknown>, VOLATILE_EDGE_FIELDS) ===
57
+ signature(b as Record<string, unknown>, VOLATILE_EDGE_FIELDS)
58
+ );
59
+ }
60
+
61
+ function diffOrder(base: { id: string }[], current: { id: string }[]): IdOrder | undefined {
62
+ const before = base.map((item) => item.id);
63
+ const after = current.map((item) => item.id);
64
+ const same = before.length === after.length && before.every((id, i) => id === after[i]);
65
+ return same ? undefined : { before, after };
66
+ }
67
+
68
+ function diffCollection<T extends { id: string }>(
69
+ base: T[],
70
+ current: T[],
71
+ volatile: Set<string>,
72
+ ): { before: T[]; after: T[] } {
73
+ const baseById = new Map(base.map((item) => [item.id, item]));
74
+ const currentById = new Map(current.map((item) => [item.id, item]));
75
+ const before: T[] = [];
76
+ const after: T[] = [];
77
+
78
+ for (const item of base) {
79
+ const counterpart = currentById.get(item.id);
80
+ if (!counterpart) {
81
+ before.push(clone(stripVolatile(item as Record<string, unknown>, volatile) as T));
82
+ continue;
83
+ }
84
+ // The same object is the same card: an untouched card costs nothing, however big the board.
85
+ if (
86
+ item !== counterpart &&
87
+ signature(item as Record<string, unknown>, volatile) !==
88
+ signature(counterpart as Record<string, unknown>, volatile)
89
+ ) {
90
+ before.push(clone(stripVolatile(item as Record<string, unknown>, volatile) as T));
91
+ after.push(clone(stripVolatile(counterpart as Record<string, unknown>, volatile) as T));
92
+ }
93
+ }
94
+ for (const item of current) {
95
+ if (!baseById.has(item.id)) {
96
+ after.push(clone(stripVolatile(item as Record<string, unknown>, volatile) as T));
97
+ }
98
+ }
99
+ return { before, after };
100
+ }
101
+
102
+ /**
103
+ * Diff two flow states into a history entry, or null when nothing changed. An order change
104
+ * alone is no step, so a bare reshuffle adds no undo entry.
105
+ */
106
+ export function diffFlowState(base: FlowState, current: FlowState): HistoryEntry | null {
107
+ const nodes = diffCollection(base.nodes, current.nodes, VOLATILE_NODE_FIELDS);
108
+ const edges = diffCollection(base.edges, current.edges, VOLATILE_EDGE_FIELDS);
109
+ if (
110
+ nodes.before.length === 0 &&
111
+ nodes.after.length === 0 &&
112
+ edges.before.length === 0 &&
113
+ edges.after.length === 0
114
+ ) {
115
+ return null;
116
+ }
117
+ const nodeOrder = diffOrder(base.nodes, current.nodes);
118
+ const edgeOrder = diffOrder(base.edges, current.edges);
119
+ return {
120
+ nodesBefore: nodes.before,
121
+ nodesAfter: nodes.after,
122
+ edgesBefore: edges.before,
123
+ edgesAfter: edges.after,
124
+ ...(nodeOrder && { nodeOrder }),
125
+ ...(edgeOrder && { edgeOrder }),
126
+ };
127
+ }
128
+
129
+ /** Stable sort into `order`. An id the entry never saw keeps its place after the known ones. */
130
+ function sortByOrder<T extends { id: string }>(items: T[], order: string[] | undefined): T[] {
131
+ if (!order) return items;
132
+ const rank = new Map(order.map((id, index) => [id, index]));
133
+ const at = (item: T) => rank.get(item.id) ?? order.length;
134
+ return items.toSorted((a, b) => at(a) - at(b));
135
+ }
136
+
137
+ /** Only a child ahead of its parent breaks SvelteFlow, so a valid order is never regrouped. */
138
+ function parentsFirst(nodes: Node[]): Node[] {
139
+ const seen = new Set<string>();
140
+ const ids = new Set(nodes.map((node) => node.id));
141
+ for (const node of nodes) {
142
+ if (node.parentId && ids.has(node.parentId) && !seen.has(node.parentId)) {
143
+ return sortParentsFirst(nodes);
144
+ }
145
+ seen.add(node.id);
146
+ }
147
+ return nodes;
148
+ }
149
+
150
+ function applyCollection<T extends { id: string }>(items: T[], restore: T[], discard: T[]): T[] {
151
+ const restoreById = new Map(restore.map((item) => [item.id, item]));
152
+ const discardIds = new Set(discard.map((item) => item.id));
153
+ // Drop pure additions of the direction being reverted.
154
+ let next = items.filter((item) => !(discardIds.has(item.id) && !restoreById.has(item.id)));
155
+ // Restore changed items in place.
156
+ const present = new Set(next.map((item) => item.id));
157
+ next = next.map((item) => {
158
+ const restored = restoreById.get(item.id);
159
+ if (!restored) return item;
160
+ // Selection is where you are, not what the document held, so an undo leaves it be.
161
+ const selected = (item as { selected?: boolean }).selected;
162
+ return { ...clone(restored), ...(selected !== undefined && { selected }) };
163
+ });
164
+ // Re-add items that were removed in the direction being reverted.
165
+ for (const item of restore) {
166
+ if (!present.has(item.id)) next.push(clone(item));
167
+ }
168
+ return next;
169
+ }
170
+
171
+ /** Apply a history entry to a state: "undo" restores the before side, "redo" the after side. */
172
+ export function applyHistoryEntry(
173
+ state: FlowState,
174
+ entry: HistoryEntry,
175
+ direction: "undo" | "redo",
176
+ ): FlowState {
177
+ const [restoreNodes, discardNodes] =
178
+ direction === "undo"
179
+ ? [entry.nodesBefore, entry.nodesAfter]
180
+ : [entry.nodesAfter, entry.nodesBefore];
181
+ const [restoreEdges, discardEdges] =
182
+ direction === "undo"
183
+ ? [entry.edgesBefore, entry.edgesAfter]
184
+ : [entry.edgesAfter, entry.edgesBefore];
185
+ const side = direction === "undo" ? "before" : "after";
186
+
187
+ return {
188
+ // WHY parentsFirst: SvelteFlow requires parents before children; a
189
+ // re-added container must land ahead of the children it re-adopts.
190
+ nodes: parentsFirst(
191
+ sortByOrder(
192
+ applyCollection(state.nodes, restoreNodes, discardNodes),
193
+ entry.nodeOrder?.[side],
194
+ ),
195
+ ),
196
+ edges: sortByOrder(
197
+ applyCollection(state.edges, restoreEdges, discardEdges),
198
+ entry.edgeOrder?.[side],
199
+ ),
200
+ };
201
+ }
202
+
203
+ /** An id's earliest before and latest after, so a node touched by both steps keeps its outer states. */
204
+ function mergeSides<T extends { id: string }>(
205
+ first: { before: T[]; after: T[] },
206
+ second: { before: T[]; after: T[] },
207
+ ): { before: T[]; after: T[] } {
208
+ const firstTouched = new Set([...first.before, ...first.after].map((item) => item.id));
209
+ const secondTouched = new Set([...second.before, ...second.after].map((item) => item.id));
210
+ const before = [...first.before, ...second.before.filter((item) => !firstTouched.has(item.id))];
211
+ const after = [...first.after.filter((item) => !secondTouched.has(item.id)), ...second.after];
212
+ return { before, after };
213
+ }
214
+
215
+ function sameIds(a: string[], b: string[]): boolean {
216
+ return a.length === b.length && a.every((id, i) => id === b[i]);
217
+ }
218
+
219
+ /** No order change on one side means that step left the order alone, so the other side's end stands in. */
220
+ function mergeOrder(first: IdOrder | undefined, second: IdOrder | undefined): IdOrder | undefined {
221
+ const before = first?.before ?? second?.before;
222
+ const after = second?.after ?? first?.after;
223
+ if (!before || !after) return undefined;
224
+ return sameIds(before, after) ? undefined : { before, after };
225
+ }
226
+
227
+ /** Fold two consecutive steps into one: undoing it restores the state before `first`. */
228
+ export function mergeHistoryEntries(first: HistoryEntry, second: HistoryEntry): HistoryEntry {
229
+ const nodes = mergeSides(
230
+ { before: first.nodesBefore, after: first.nodesAfter },
231
+ { before: second.nodesBefore, after: second.nodesAfter },
232
+ );
233
+ const edges = mergeSides(
234
+ { before: first.edgesBefore, after: first.edgesAfter },
235
+ { before: second.edgesBefore, after: second.edgesAfter },
236
+ );
237
+ const nodeOrder = mergeOrder(first.nodeOrder, second.nodeOrder);
238
+ const edgeOrder = mergeOrder(first.edgeOrder, second.edgeOrder);
239
+ return {
240
+ nodesBefore: nodes.before,
241
+ nodesAfter: nodes.after,
242
+ edgesBefore: edges.before,
243
+ edgesAfter: edges.after,
244
+ ...(nodeOrder && { nodeOrder }),
245
+ ...(edgeOrder && { edgeOrder }),
246
+ };
247
+ }