@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
package/src/ambient.d.ts CHANGED
@@ -2,3 +2,11 @@
2
2
  * A stylesheet imported for its side effect, bundled by the consumer's Vite.
3
3
  */
4
4
  declare module "*.css";
5
+
6
+ /**
7
+ * An SVG imported as a URL, resolved by the consumer's Vite.
8
+ */
9
+ declare module "*.svg" {
10
+ const src: string;
11
+ export default src;
12
+ }
@@ -0,0 +1,67 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { describe, expect, it } from "vite-plus/test";
4
+ import { BRAND_COLOR_VARS, applyBrand, brandCss, brandStyle, brandVars, defineBrand } from "./index.js";
5
+
6
+ const TOKENS = join(import.meta.dirname, "..", "tokens");
7
+ const palette = readFileSync(join(TOKENS, "palette.css"), "utf8");
8
+
9
+ describe("brandVars", () => {
10
+ it("sets only the roles a brand names", () => {
11
+ const brand = defineBrand({ colors: { brand: "oklch(0.5 0.2 272)", signal: "#f0503d" } });
12
+ expect(brandVars(brand)).toEqual({ "--brand": "oklch(0.5 0.2 272)", "--brand-signal": "#f0503d" });
13
+ });
14
+
15
+ it("maps fonts and extra properties", () => {
16
+ const brand = defineBrand({
17
+ colors: { brand: "red" },
18
+ fonts: { sans: '"Satoshi", system-ui', mono: "ui-monospace" },
19
+ vars: { "--logo-height": "24px" },
20
+ });
21
+ expect(brandVars(brand)).toEqual({
22
+ "--brand": "red",
23
+ "--font-sans": '"Satoshi", system-ui',
24
+ "--font-mono": "ui-monospace",
25
+ "--logo-height": "24px",
26
+ });
27
+ });
28
+
29
+ it("refuses a value that would break out of its declaration", () => {
30
+ expect(() => brandVars({ colors: { brand: "red; } body { display: none" } })).toThrow(/--brand/);
31
+ });
32
+ });
33
+
34
+ describe("the three ways to apply one", () => {
35
+ const brand = defineBrand({ colors: { brand: "blue", ink: "black" } });
36
+
37
+ it("writes a style attribute and a rule", () => {
38
+ expect(brandStyle(brand)).toBe("--brand:blue;--brand-ink:black");
39
+ expect(brandCss(brand, ".client")).toBe(".client {\n --brand: blue;\n --brand-ink: black;\n}\n");
40
+ });
41
+
42
+ it("applies to an element and puts back what it found", () => {
43
+ const root = document.createElement("div");
44
+ root.style.setProperty("--brand", "green");
45
+ const reset = applyBrand(brand, root);
46
+ expect(root.style.getPropertyValue("--brand")).toBe("blue");
47
+ expect(root.style.getPropertyValue("--brand-ink")).toBe("black");
48
+ reset();
49
+ expect(root.style.getPropertyValue("--brand")).toBe("green");
50
+ expect(root.style.getPropertyValue("--brand-ink")).toBe("");
51
+ });
52
+ });
53
+
54
+ describe("the tokens", () => {
55
+ it("define a neutral default for every role a brand can set", () => {
56
+ for (const name of Object.values(BRAND_COLOR_VARS)) {
57
+ expect(palette, name).toMatch(new RegExp(`^\\s+${name}: `, "m"));
58
+ }
59
+ });
60
+
61
+ it("carry no client palette", () => {
62
+ for (const file of readdirSync(TOKENS).filter((f) => f.endsWith(".css"))) {
63
+ const css = readFileSync(join(TOKENS, file), "utf8");
64
+ expect(css, file).not.toMatch(/brand-(indigo|sand|terra|night|veil|dusk|cream|linen|mist)|--(indigo|sand|terra)-\d/);
65
+ }
66
+ });
67
+ });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * A client's brand is a typed object in its own `brand.ts`, with no build step:
3
+ *
4
+ * export const brand = defineBrand({ colors: { brand: "oklch(0.5 0.2 272)" } });
5
+ *
6
+ * `@heyramzi/ui/tokens.css` ships a neutral look. A brand replaces only the roles it names:
7
+ * - on the server, put `brandStyle(brand)` on the `<html style>` so the first paint is right;
8
+ * - or call `applyBrand(brand)` once in the browser;
9
+ * - or write `brandCss(brand)` into a stylesheet.
10
+ * Light and dark come from the tokens, which read these roles, so a brand never types both.
11
+ */
12
+
13
+ /** Any CSS colour: oklch(), hex, a named colour or a var(). */
14
+ export type CssColor = string;
15
+
16
+ export type BrandColors = {
17
+ /** The signature colour: focus ring, links, active states, the mark. */
18
+ brand: CssColor;
19
+ /** The same colour lifted for a dark page. Falls back to the neutral default. */
20
+ brandOnDark?: CssColor;
21
+ /** Warmth: badges, highlights, soft accents. */
22
+ highlight?: CssColor;
23
+ /** Attention: warnings, errors, energy. */
24
+ signal?: CssColor;
25
+ /** The dark page background. */
26
+ ink?: CssColor;
27
+ /** Cards and panels on a dark page. */
28
+ surface?: CssColor;
29
+ /** Borders and dividers on a dark page. */
30
+ line?: CssColor;
31
+ /** Hover surfaces and inputs on a dark page. */
32
+ raised?: CssColor;
33
+ /** Text on a dark page, and a light page's background tone. */
34
+ paper?: CssColor;
35
+ /** Cards on a light page. */
36
+ surfaceLight?: CssColor;
37
+ /** Borders on a light page. */
38
+ lineLight?: CssColor;
39
+ };
40
+
41
+ export type BrandFonts = {
42
+ /** The body family, as CSS: `"Satoshi", system-ui, sans-serif`. */
43
+ sans?: string;
44
+ /** Headings. Falls back to the body family. */
45
+ heading?: string;
46
+ /** Code and figures. */
47
+ mono?: string;
48
+ /** Quotes. Falls back to the mono family. */
49
+ quote?: string;
50
+ };
51
+
52
+ export type Brand = {
53
+ colors: BrandColors;
54
+ fonts?: BrandFonts;
55
+ /** Extra custom properties the brand owns, such as a logo size. Each key starts with `--`. */
56
+ vars?: Record<`--${string}`, string>;
57
+ };
58
+
59
+ const COLOR_VARS = {
60
+ brand: "--brand",
61
+ brandOnDark: "--brand-on-dark",
62
+ highlight: "--brand-highlight",
63
+ signal: "--brand-signal",
64
+ ink: "--brand-ink",
65
+ surface: "--brand-surface",
66
+ line: "--brand-line",
67
+ raised: "--brand-raised",
68
+ paper: "--brand-paper",
69
+ surfaceLight: "--brand-surface-light",
70
+ lineLight: "--brand-line-light",
71
+ } as const satisfies Record<keyof BrandColors, string>;
72
+
73
+ const FONT_VARS = {
74
+ sans: "--font-sans",
75
+ heading: "--font-heading",
76
+ mono: "--font-mono",
77
+ quote: "--font-quote",
78
+ } as const satisfies Record<keyof BrandFonts, string>;
79
+
80
+ /** The custom property each colour role sets. The tokens read these and nothing else. */
81
+ export const BRAND_COLOR_VARS: Readonly<Record<keyof BrandColors, string>> = COLOR_VARS;
82
+
83
+ /** Keeps a typed brand typed at the call site. It returns the object unchanged. */
84
+ export function defineBrand<const B extends Brand>(brand: B): B {
85
+ return brand;
86
+ }
87
+
88
+ function checked(name: string, value: string): string {
89
+ if (/[;{}<>]/.test(value)) throw new Error(`brand: ${name} holds a character CSS would read as more than a value`);
90
+ return value;
91
+ }
92
+
93
+ /** The custom properties a brand sets, by name. Roles the brand leaves out keep the neutral default. */
94
+ export function brandVars(brand: Brand): Record<string, string> {
95
+ const out: Record<string, string> = {};
96
+ for (const [key, name] of Object.entries(COLOR_VARS)) {
97
+ const value = brand.colors[key as keyof BrandColors];
98
+ if (value !== undefined) out[name] = checked(name, value);
99
+ }
100
+ for (const [key, name] of Object.entries(FONT_VARS)) {
101
+ const value = brand.fonts?.[key as keyof BrandFonts];
102
+ if (value !== undefined) out[name] = checked(name, value);
103
+ }
104
+ for (const [name, value] of Object.entries(brand.vars ?? {})) out[name] = checked(name, value);
105
+ return out;
106
+ }
107
+
108
+ /** The brand as a `style` attribute value: put it on `<html>` in `app.html` or a layout. */
109
+ export function brandStyle(brand: Brand): string {
110
+ return Object.entries(brandVars(brand))
111
+ .map(([name, value]) => `${name}:${value}`)
112
+ .join(";");
113
+ }
114
+
115
+ /** The brand as a rule, for a stylesheet or a `<style>` in `<svelte:head>`. */
116
+ export function brandCss(brand: Brand, selector = ":root"): string {
117
+ const body = Object.entries(brandVars(brand))
118
+ .map(([name, value]) => ` ${name}: ${value};`)
119
+ .join("\n");
120
+ return `${selector} {\n${body}\n}\n`;
121
+ }
122
+
123
+ /** Sets the brand on the page in the browser. Returns a function that puts the neutral look back. */
124
+ export function applyBrand(brand: Brand, root: HTMLElement = document.documentElement): () => void {
125
+ const vars = brandVars(brand);
126
+ const before = new Map<string, string>();
127
+ for (const [name, value] of Object.entries(vars)) {
128
+ before.set(name, root.style.getPropertyValue(name));
129
+ root.style.setProperty(name, value);
130
+ }
131
+ return () => {
132
+ for (const [name, value] of before) {
133
+ if (value) root.style.setProperty(name, value);
134
+ else root.style.removeProperty(name);
135
+ }
136
+ };
137
+ }
@@ -0,0 +1,236 @@
1
+ # @heyramzi/ui/canvas
2
+
3
+ A canvas for Svelte 5 and SvelteKit, built on [@xyflow/svelte](https://svelteflow.dev). Cards and arrows from xyflow, a hand-drawing pen on top, and a plugin API for your own card types.
4
+
5
+ Strokes, text and shapes are ordinary xyflow nodes. So undo, copy and save work with no extra code. The same ops run on a server or in a CLI, with no browser.
6
+
7
+ MIT. It runs on any domain and costs nothing per install.
8
+
9
+ ## What's in it
10
+
11
+ - **Pen layer.** Freehand strokes (perfect-freehand), a highlighter, an eraser, plain text, and rectangle, ellipse and diamond shapes. Colours are token names, so a board drawn in light mode reads fine in dark.
12
+ - **Tool bar.** A floating bar with the tools, undo and redo. Pick from 6 colours and 3 brush sizes. Keys: V, H, P, M, E, T, R, O, D.
13
+ - **Plugin API.** Register your own card types, ops and layout passes. One registry builds xyflow's `nodeTypes` for you.
14
+ - **Ops.** 8 core ops (`addNode`, `removeNode`, `patchNode`, `moveNode`, `setParent`, `addEdge`, `removeEdge`, `patchEdge`) as plain functions over nodes and edges. Zod checks every one.
15
+ - **Undo history.** `createCanvasDispatcher` wraps each edit in a transaction. A whole pen stroke is one undo step.
16
+ - **Headless.** Everything that doesn't need a DOM, for servers and CLIs.
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ npm install @heyramzi/ui @xyflow/svelte svelte
22
+ ```
23
+
24
+ Peers: `svelte` ^5.57.2 and `@xyflow/svelte` ^1.7.0. The canvas is the one subpath whose peer you add yourself.
25
+
26
+ The package ships Svelte source, so your app needs `@sveltejs/vite-plugin-svelte`. A SvelteKit app has it already.
27
+
28
+ ## Use it in SvelteKit
29
+
30
+ Drop this in `src/routes/+page.svelte`.
31
+
32
+ ```svelte
33
+ <script lang="ts">
34
+ import { Background, SvelteFlow, type Edge, type Node } from "@xyflow/svelte";
35
+ import "@xyflow/svelte/dist/style.css";
36
+ import { createCanvasDispatcher, createRegistry } from "@heyramzi/ui/canvas";
37
+ import { penUiPlugin } from "@heyramzi/ui/canvas/pen/ui.js";
38
+ import type { PenColor } from "@heyramzi/ui/canvas/pen/index.js";
39
+ import {
40
+ brushSize,
41
+ DEFAULT_BRUSH_LEVEL,
42
+ isDrawingTool,
43
+ type CanvasTool,
44
+ } from "@heyramzi/ui/canvas/ui/tools.js";
45
+ import CanvasToolbar from "@heyramzi/ui/canvas/ui/CanvasToolbar.svelte";
46
+ import PenLayer from "@heyramzi/ui/canvas/ui/PenLayer.svelte";
47
+
48
+ const registry = createRegistry([penUiPlugin]);
49
+
50
+ let nodes = $state.raw<Node[]>([]);
51
+ let edges = $state.raw<Edge[]>([]);
52
+ let tool = $state<CanvasTool>("select");
53
+ let color = $state<PenColor>("ink");
54
+ let level = $state(DEFAULT_BRUSH_LEVEL);
55
+
56
+ const history = createCanvasDispatcher({
57
+ getState: () => ({ nodes, edges }),
58
+ setState: (next) => {
59
+ nodes = next.nodes;
60
+ edges = next.edges;
61
+ },
62
+ // Save here. It fires once per commit, undo and redo.
63
+ onMutated: () => {},
64
+ opDefinitions: [...registry.ops.values()],
65
+ });
66
+
67
+ const labels = {
68
+ toolbar: "Canvas tools",
69
+ style: "Style",
70
+ tools: {
71
+ select: "Select", hand: "Hand", pen: "Pen", highlighter: "Highlighter",
72
+ eraser: "Eraser", text: "Text", rectangle: "Rectangle", ellipse: "Ellipse",
73
+ diamond: "Diamond", shapes: "Shapes",
74
+ },
75
+ colors: { ink: "Ink", red: "Red", blue: "Blue", green: "Green", amber: "Amber", violet: "Violet" },
76
+ sizes: ["Small", "Medium", "Large"],
77
+ undo: "Undo",
78
+ redo: "Redo",
79
+ } as const;
80
+ </script>
81
+
82
+ <div style="height: 100vh">
83
+ <SvelteFlow bind:nodes bind:edges nodeTypes={registry.nodeTypes()} fitView>
84
+ <Background />
85
+ {#if isDrawingTool(tool)}
86
+ <PenLayer
87
+ {tool}
88
+ {color}
89
+ size={brushSize(tool, level)}
90
+ {nodes}
91
+ commit={(ops) => history.dispatch(ops)}
92
+ startGesture={() => {
93
+ history.begin();
94
+ return history.commit;
95
+ }}
96
+ />
97
+ {/if}
98
+ <CanvasToolbar
99
+ bind:tool
100
+ bind:color
101
+ bind:level
102
+ {labels}
103
+ canUndo={history.canUndo}
104
+ canRedo={history.canRedo}
105
+ onUndo={history.undo}
106
+ onRedo={history.redo}
107
+ />
108
+ </SvelteFlow>
109
+ </div>
110
+ ```
111
+
112
+ Pick the pen tool, drag, and you've drawn a stroke. Press undo and it's gone. The whole gesture is one step.
113
+
114
+ `PenLayer` and `CanvasToolbar` both read xyflow's store, so they go inside `<SvelteFlow>`. The tool bar takes a `labels` object because the package has no i18n. Pass your own strings.
115
+
116
+ Components import by path:
117
+
118
+ - `@heyramzi/ui/canvas/ui/CanvasToolbar.svelte`
119
+ - `@heyramzi/ui/canvas/ui/PenLayer.svelte`
120
+ - `@heyramzi/ui/canvas/ui/canvas-grid.svelte`, a dot grid that fades as you zoom out
121
+
122
+ The tool bar's styles read your theme tokens (`--card`, `--border`, `--foreground`, `--ring`) and fall back to plain colours when you have none.
123
+
124
+ ## The plugin API
125
+
126
+ A plugin is a name plus node kinds, ops and layout passes. Write it once for the server, then add the Svelte component for the browser. That's how the pen does it: `penPlugin` has no DOM, and `penUiPlugin` adds the renderers.
127
+
128
+ ```ts
129
+ // invoice-plugin.ts, safe on a server
130
+ import { z } from "zod";
131
+ import { definePlugin, patchData, type OpState } from "@heyramzi/ui/canvas";
132
+
133
+ const setTotal = z.object({
134
+ kind: z.literal("setTotal"),
135
+ nodeId: z.string().min(1),
136
+ total: z.number(),
137
+ });
138
+
139
+ function applySetTotal(state: OpState, op: z.infer<typeof setTotal>, opIndex: number) {
140
+ patchData(state, op.nodeId, opIndex, () => ({ total: op.total }));
141
+ }
142
+
143
+ export const invoicePlugin = definePlugin({
144
+ id: "invoice",
145
+ nodes: [
146
+ {
147
+ type: "invoice",
148
+ label: "Invoice",
149
+ size: { width: 240 },
150
+ palette: { group: "Billing", order: 1 },
151
+ createData: () => ({ total: 0 }),
152
+ },
153
+ ],
154
+ ops: [
155
+ { kind: "setTotal", schema: setTotal, apply: applySetTotal },
156
+ ],
157
+ });
158
+ ```
159
+
160
+ ```ts
161
+ // invoice-ui.ts, browser only
162
+ import { definePlugin } from "@heyramzi/ui/canvas";
163
+ import InvoiceCard from "./InvoiceCard.svelte";
164
+ import { invoicePlugin } from "./invoice-plugin";
165
+
166
+ export const invoiceUiPlugin = definePlugin({
167
+ ...invoicePlugin,
168
+ nodes: (invoicePlugin.nodes ?? []).map((kind) => ({ ...kind, component: InvoiceCard })),
169
+ });
170
+ ```
171
+
172
+ Then `createRegistry([penUiPlugin, invoiceUiPlugin])` gives you:
173
+
174
+ - `nodeTypes()`, the map xyflow wants.
175
+ - `ops`, to pass as `opDefinitions` to the dispatcher or `applyOps`.
176
+ - `parseOp(input)`, which checks one op against its kind's schema.
177
+ - `pickLayout(data)`, the first layout pass whose `matches` says yes.
178
+ - `contentSizedTypes()` and `layoutOwnedTypes()`, so a layout knows which heights the DOM owns.
179
+
180
+ A node type or op kind can have only one owner. If a second plugin claims one, `createRegistry` throws and names both.
181
+
182
+ A node kind can also set `validParents` (which containers it may sit in) and `sizing`. A layout pass is `{ id, matches, run }`, and `run` returns the new nodes and edges.
183
+
184
+ ## The pen
185
+
186
+ A stroke is a node with `type: "stroke"`. Its `data` holds the simplified points, a colour token, a size and a tool. Text is `"text"` and a shape is `"shape"`. They're real nodes, so the eraser, selection, copy and your save code treat them like cards.
187
+
188
+ The pen is `PenOverlay` (or `PenLayer` inside xyflow), and it needs 2 callbacks from you:
189
+
190
+ - `commit(ops)` receives `addNode` and `removeNode` ops. Send them to `history.dispatch`.
191
+ - `startGesture()` opens an undo transaction and returns the function that closes it. A stroke is a single history step.
192
+
193
+ The eraser removes whole strokes, never part of one. A swipe across a big shape doesn't wipe it.
194
+
195
+ The pen has 6 colours: `ink`, `red`, `blue`, `green`, `amber` and `violet`. `pen.css` maps them to CSS variables with a light and a dark value. Dark mode applies under a `.dark` class on an ancestor or on `.svelte-flow`.
196
+
197
+ ## Headless ops
198
+
199
+ Import from `@heyramzi/ui/canvas` on a server, in a worker or in a CLI. The root export touches no DOM.
200
+
201
+ ```ts
202
+ import { applyOps, createRegistry, type RawCanvasData } from "@heyramzi/ui/canvas";
203
+ import { penPlugin } from "@heyramzi/ui/canvas/pen/index.js";
204
+ import { compilePenMark, penMarkSchema } from "@heyramzi/ui/canvas/headless/index.js";
205
+
206
+ const registry = createRegistry([penPlugin]);
207
+
208
+ let board: RawCanvasData = { nodes: [], edges: [] };
209
+
210
+ // Ops from a request body or a CLI flag. An unknown kind or a bad shape throws.
211
+ const ops = [
212
+ { kind: "addShape", shape: "rectangle", box: { x: 0, y: 0, width: 200, height: 120 }, label: "Intake" },
213
+ { kind: "addText", at: { x: 20, y: 160 }, text: "Where leads land" },
214
+ ].map((op) => registry.parseOp(op));
215
+
216
+ board = applyOps(board, ops, [...registry.ops.values()]);
217
+ ```
218
+
219
+ `applyOps` is pure. It returns a new board and never edits the one you pass. A bad op throws a `CanvasOpError` with the index of the op that failed, so you know which one in a list of 57.
220
+
221
+ For a whole drawing in one call, write pen marks as JSON. There are 3 kinds: `stroke`, `text` and `shape`. They compile to the same nodes the pen tool draws.
222
+
223
+ ```ts
224
+ const marks = penMarkSchema.array().parse(JSON.parse(input));
225
+ const frames = new Map(); // frame id to { x, y }, if a mark uses `in`
226
+ const nodes = marks.map((mark, i) => compilePenMark(mark, i, frames));
227
+ board = { ...board, nodes: [...board.nodes, ...nodes] };
228
+ ```
229
+
230
+ A mark with no `id` gets `pen-<n>` from its place in the list. Run the same file twice and you get the same ids.
231
+
232
+ The root export also has the pure helpers the editor uses: edge anchors, alignment, snap guides, fit-to-viewport, table layout, SVG figure sanitising and the sketch (rough.js) geometry. Import what you need.
233
+
234
+ ## Develop
235
+
236
+ Tests run in jsdom, so no browser. From the repo root, `pnpm --filter @heyramzi/ui test` runs them and `pnpm --filter @heyramzi/ui check` typechecks.
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import { getDiagramEditorContext } from "../editor/editor-context.js";
7
+ import { isLocked } from "../editor/lock.js";
8
+ import { userHeightOf, type CardResize } from "./resize.js";
9
+ import ResizeHandles from "./resize-handles.svelte";
10
+ import "./card.css";
11
+
12
+ // The frame every card sits in: it keeps the saved height as a floor and draws the grips
13
+ // on a selected card. A card whose policy is `false` has neither.
14
+
15
+ // =========================
16
+ // TYPES
17
+ // =========================
18
+ interface Props {
19
+ id: string;
20
+ data?: unknown;
21
+ selected?: boolean;
22
+ /** How this card type resizes, or false when its size isn't the person's to set. */
23
+ resize: CardResize | false;
24
+ children: Snippet;
25
+ }
26
+
27
+ // =========================
28
+ // PROPS
29
+ // =========================
30
+ let { id, data, selected = false, resize, children }: Props = $props();
31
+
32
+ // =========================
33
+ // STATE
34
+ // =========================
35
+ const editor = getDiagramEditorContext();
36
+ let frame = $state<HTMLElement>();
37
+
38
+ // =========================
39
+ // DERIVED
40
+ // =========================
41
+ let floor = $derived(userHeightOf(data));
42
+ // A box card is sized by the node itself, so it needs no frame.
43
+ let framed = $derived(!!resize && resize.store !== "box");
44
+ let grips = $derived(!!resize && !!editor && selected && !isLocked({ data }));
45
+ </script>
46
+
47
+ <!-- =========================== -->
48
+ <!-- MARKUP -->
49
+ <!-- =========================== -->
50
+ <!-- The grips come first, so a link dot on the same edge stays on top of them. -->
51
+ {#if resize && grips}
52
+ <ResizeHandles {id} {resize} {frame} />
53
+ {/if}
54
+
55
+ {#if framed}
56
+ <div class="cv-card-frame" bind:this={frame} style:--cv-card-h={floor > 0 ? `${floor}px` : undefined}>
57
+ {@render children()}
58
+ </div>
59
+ {:else}
60
+ {@render children()}
61
+ {/if}
@@ -0,0 +1,39 @@
1
+ /* The card frame and its resize hit areas. Handle looks come from chrome.css. */
2
+
3
+ /* A one-row grid: at least the saved height, growing with the content.
4
+ `min-height: 100%` shows a live drag at once. No position, so grips still anchor to the node. */
5
+ .cv-card-frame {
6
+ display: grid;
7
+ grid-template-rows: minmax(var(--cv-card-h, 0px), auto);
8
+ min-height: 100%;
9
+ }
10
+
11
+ /* A drag drops the saved floor, so the card can shrink as well as grow. */
12
+ .cv-card-frame[data-resizing] {
13
+ grid-template-rows: auto;
14
+ }
15
+
16
+ /* The edges are hit areas only, a finger wide at every zoom. */
17
+ .svelte-flow .svelte-flow__resize-control.line.cv-edge {
18
+ border-width: 0;
19
+ background: transparent;
20
+ }
21
+
22
+ .svelte-flow .svelte-flow__resize-control.line.cv-edge:is(.left, .right) {
23
+ width: calc(10px * var(--canvas-unzoom, 1));
24
+ }
25
+
26
+ .svelte-flow .svelte-flow__resize-control.line.cv-edge:is(.top, .bottom) {
27
+ height: calc(10px * var(--canvas-unzoom, 1));
28
+ }
29
+
30
+ /* A text card's side grips are sideways-moving pills near the top of the edge, since the middle belongs to the connection dot. */
31
+ .svelte-flow .svelte-flow__resize-control.handle.cv-grip-x {
32
+ top: calc(22px * var(--canvas-unzoom, 1)) !important;
33
+ /* Above the card's own positioned content, or half the pill hides behind it. */
34
+ z-index: 3;
35
+ width: 8px;
36
+ height: 20px;
37
+ border-radius: 9999px;
38
+ cursor: ew-resize;
39
+ }
@@ -0,0 +1,119 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { NodeResizeControl, ResizeControlVariant, useSvelteFlow } from "@xyflow/svelte";
6
+ import { getDiagramEditorContext } from "../editor/editor-context.js";
7
+ import {
8
+ DEFAULT_MAX_WIDTH,
9
+ DEFAULT_MIN_HEIGHT,
10
+ DEFAULT_MIN_WIDTH,
11
+ keptHeight,
12
+ type CardResize,
13
+ } from "./resize.js";
14
+ import "./card.css";
15
+
16
+ // The grips of a selected card. One drag is one undo step: width, kept height and extra data close together.
17
+
18
+ // =========================
19
+ // TYPES
20
+ // =========================
21
+ interface Props {
22
+ id: string;
23
+ resize: CardResize;
24
+ /** The framed element, read for the height its content needs. */
25
+ frame?: HTMLElement;
26
+ }
27
+
28
+ // =========================
29
+ // CONSTANTS
30
+ // =========================
31
+ const CORNERS = ["top-left", "top-right", "bottom-left", "bottom-right"] as const;
32
+ const EDGES = ["left", "right", "top", "bottom"] as const;
33
+ const SIDES = ["left", "right"] as const;
34
+
35
+ // =========================
36
+ // PROPS
37
+ // =========================
38
+ let { id, resize, frame }: Props = $props();
39
+
40
+ // =========================
41
+ // STATE
42
+ // =========================
43
+ const editor = getDiagramEditorContext();
44
+ const { updateNode } = useSvelteFlow();
45
+ let endGesture: (() => void) | null = null;
46
+ // The height the content needs, read when a drag starts.
47
+ let contentHeight = $state(DEFAULT_MIN_HEIGHT);
48
+
49
+ // =========================
50
+ // DERIVED
51
+ // =========================
52
+ let both = $derived(resize.axes === "both");
53
+ let ratio = $derived(resize.axes === "ratio");
54
+ let direction = $derived(both || ratio ? undefined : ("horizontal" as const));
55
+ let box = $derived(resize.store === "box");
56
+ let minWidth = $derived(resize.minWidth ?? DEFAULT_MIN_WIDTH);
57
+ let maxWidth = $derived(resize.maxWidth ?? DEFAULT_MAX_WIDTH);
58
+ let minHeight = $derived(both && !box ? contentHeight : (resize.minHeight ?? DEFAULT_MIN_HEIGHT));
59
+
60
+ // =========================
61
+ // FUNCTIONS
62
+ // =========================
63
+ function measureContent() {
64
+ if (!frame) return;
65
+ // With the saved floor dropped, the frame is as tall as the content needs.
66
+ frame.setAttribute("data-resizing", "");
67
+ contentHeight = Math.max(DEFAULT_MIN_HEIGHT, Math.ceil(frame.offsetHeight));
68
+ }
69
+
70
+ function start() {
71
+ endGesture = editor?.startGesture() ?? null;
72
+ if (both && !box) measureContent();
73
+ }
74
+
75
+ function finish(size: { width: number; height: number }) {
76
+ frame?.removeAttribute("data-resizing");
77
+ const width = Math.round(size.width);
78
+ if (!box) {
79
+ editor?.setNodeWidth(id, width);
80
+ const patch: Record<string, unknown> = { ...resize.extraData?.({ width, height: size.height }) };
81
+ // A text card keeps growing with its words, and an image's height follows its width.
82
+ if (both) patch.userHeight = keptHeight(size.height, contentHeight);
83
+ if (Object.keys(patch).length > 0) editor?.patchNodeData(id, patch);
84
+ // The drag gave the node a height; the content and the saved floor size it from here.
85
+ updateNode(id, { height: undefined });
86
+ }
87
+ endGesture?.();
88
+ endGesture = null;
89
+ }
90
+ </script>
91
+
92
+ <!-- =========================== -->
93
+ <!-- MARKUP -->
94
+ <!-- =========================== -->
95
+ {#snippet control(position: (typeof CORNERS)[number] | (typeof EDGES)[number], variant: ResizeControlVariant)}
96
+ <NodeResizeControl
97
+ nodeId={id}
98
+ {position}
99
+ {variant}
100
+ {minWidth}
101
+ {maxWidth}
102
+ {minHeight}
103
+ keepAspectRatio={ratio}
104
+ resizeDirection={direction}
105
+ class={variant === ResizeControlVariant.Line ? "cv-edge" : direction ? "cv-grip cv-grip-x" : "cv-grip"}
106
+ onResizeStart={start}
107
+ onResizeEnd={(_, size) => finish(size)}
108
+ />
109
+ {/snippet}
110
+
111
+ <!-- A text card moves only sideways, so it gets a grip on each side edge and no corners. -->
112
+ {#each both || ratio ? EDGES : SIDES as edge (edge)}
113
+ {#if !ratio}
114
+ {@render control(edge, ResizeControlVariant.Line)}
115
+ {/if}
116
+ {/each}
117
+ {#each direction ? SIDES : CORNERS as grip (grip)}
118
+ {@render control(grip, ResizeControlVariant.Handle)}
119
+ {/each}