@heyramzi/ui 2.1.0 → 2.3.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 (317) hide show
  1. package/COMPONENTS.md +1 -1
  2. package/package.json +7 -8
  3. package/scripts/sync-shadcn.mjs +3 -4
  4. package/src/agents/agent-avatar.svelte +604 -575
  5. package/src/agents/agent-cast.svelte +179 -183
  6. package/src/agents/demo.svelte +125 -113
  7. package/src/agents/helpers.ts +23 -17
  8. package/src/agents/index.ts +4 -4
  9. package/src/agents/page-state.ts +6 -6
  10. package/src/agents/types.ts +6 -6
  11. package/src/ai/ai-button/AiButton.svelte +4 -12
  12. package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
  13. package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
  14. package/src/ai/response/Response.css +2 -3
  15. package/src/animations/avatar-group-hover.css +2 -16
  16. package/src/animations/card-tilt.css +3 -6
  17. package/src/animations/digit-pop-in.css +2 -15
  18. package/src/animations/dropdown.css +4 -17
  19. package/src/animations/input-clear-dissolve.css +5 -18
  20. package/src/animations/input-shake.css +9 -41
  21. package/src/animations/learn-more-hover.css +17 -9
  22. package/src/animations/like-button.css +2 -4
  23. package/src/animations/page-side-by-side.css +2 -10
  24. package/src/animations/plus-menu-morph.css +2 -5
  25. package/src/animations/shimmer-text.css +5 -18
  26. package/src/animations/spinning-counter.css +2 -6
  27. package/src/animations/success-check.css +2 -15
  28. package/src/animations/thinking-states.css +2 -7
  29. package/src/brand/brand.test.ts +21 -5
  30. package/src/brand/index.ts +4 -10
  31. package/src/canvas/card/card-frame.svelte +43 -39
  32. package/src/canvas/card/resize-handles.svelte +89 -80
  33. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  34. package/src/canvas/chrome/lock-badge.svelte +8 -8
  35. package/src/canvas/chrome/side-handles.svelte +24 -19
  36. package/src/canvas/chrome/size-chip.svelte +30 -30
  37. package/src/canvas/chrome/snap-guides.svelte +20 -20
  38. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  39. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  40. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  41. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  42. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  43. package/src/canvas/edges/bend-handle.svelte +46 -46
  44. package/src/canvas/edges/edge-gradient.svelte +10 -10
  45. package/src/canvas/edges/gradient-edge.svelte +45 -29
  46. package/src/canvas/editor/lock.test.ts +6 -1
  47. package/src/canvas/editor/style.ts +7 -1
  48. package/src/canvas/headless/canvas-cleanup.test.ts +1 -1
  49. package/src/canvas/headless/svg-figure.test.ts +5 -1
  50. package/src/canvas/lod/lod-switch.svelte +111 -106
  51. package/src/canvas/ops/ops.ts +1 -1
  52. package/src/canvas/pen/PenField.svelte +87 -81
  53. package/src/canvas/pen/PenOverlay.svelte +127 -127
  54. package/src/canvas/pen/ShapeNode.svelte +31 -32
  55. package/src/canvas/pen/TextNode.svelte +54 -53
  56. package/src/canvas/pen/host-field.test.svelte +7 -7
  57. package/src/canvas/pen/shapes.ts +7 -1
  58. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  59. package/src/canvas/screen/PartView.svelte +138 -72
  60. package/src/canvas/screen/ScreenBody.svelte +81 -60
  61. package/src/canvas/screen/ScreenCard.svelte +20 -20
  62. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  63. package/src/canvas/ui/FrameLayer.svelte +59 -59
  64. package/src/canvas/ui/PenLayer.svelte +39 -30
  65. package/src/canvas/ui/StylePanel.svelte +119 -102
  66. package/src/canvas/ui/canvas-grid.svelte +137 -129
  67. package/src/charts/animated-value.svelte +1 -1
  68. package/src/charts/area-chart.css +2 -5
  69. package/src/charts/area-chart.svelte +1 -1
  70. package/src/charts/bar-chart.css +2 -3
  71. package/src/charts/bar-chart.svelte +1 -1
  72. package/src/charts/charts.css +5 -17
  73. package/src/charts/crosshair.svelte +1 -1
  74. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  75. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  76. package/src/charts/dashboard/data-panel.svelte +50 -50
  77. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  78. package/src/charts/dashboard/data-table.svelte +138 -138
  79. package/src/charts/dashboard/panel-grid.svelte +30 -30
  80. package/src/charts/dashboard/period-filter.svelte +25 -25
  81. package/src/charts/dashboard/rating-chip.svelte +19 -19
  82. package/src/charts/dashboard/stat-band.svelte +32 -32
  83. package/src/charts/dashboard/stat-tile.svelte +106 -99
  84. package/src/charts/donut-chart.svelte +1 -1
  85. package/src/charts/edge-fade.svelte +1 -1
  86. package/src/charts/figure.svelte +1 -1
  87. package/src/charts/gauge-chart.css +4 -7
  88. package/src/charts/gauge-chart.svelte +1 -1
  89. package/src/charts/hover-focus.svelte +1 -1
  90. package/src/charts/legend.css +1 -3
  91. package/src/charts/legend.svelte +1 -1
  92. package/src/charts/line-chart.svelte +1 -1
  93. package/src/charts/number-chart.css +6 -9
  94. package/src/charts/number-chart.svelte +1 -1
  95. package/src/charts/reference-line.svelte +19 -19
  96. package/src/charts/reveal.svelte +1 -1
  97. package/src/charts/ring-chart.css +2 -4
  98. package/src/charts/ring-chart.svelte +1 -1
  99. package/src/charts/sparkline.svelte +5 -2
  100. package/src/charts/stat-card.svelte +1 -1
  101. package/src/charts/time-series-chart.css +2 -5
  102. package/src/charts/trend-pill.css +2 -4
  103. package/src/charts/trend-pill.svelte +1 -1
  104. package/src/components/copy/copy-button.svelte +93 -98
  105. package/src/components/copy/copy-button.test.ts +10 -2
  106. package/src/components/data-table/infinite-scroll.svelte +42 -42
  107. package/src/components/data-table/pagination.svelte +34 -22
  108. package/src/components/data-table/row-arrow.svelte +19 -16
  109. package/src/components/data-table/row-context-menu.svelte +52 -52
  110. package/src/components/data-table/sortable-head.svelte +25 -25
  111. package/src/components/data-table/table-skeleton.svelte +41 -36
  112. package/src/components/docs/doc-page.svelte +18 -9
  113. package/src/components/docs/on-this-page.svelte +16 -3
  114. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  115. package/src/components/field-row/field-row.svelte +10 -8
  116. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  117. package/src/components/icon/icon.svelte +14 -14
  118. package/src/components/language-flag/language-flag.svelte +3 -1
  119. package/src/components/list-page/detail-empty.svelte +34 -34
  120. package/src/components/list-page/detail-header.svelte +55 -47
  121. package/src/components/list-page/detail-panel.svelte +51 -59
  122. package/src/components/list-page/detail-row.svelte +19 -19
  123. package/src/components/list-page/detail-stack.svelte +21 -21
  124. package/src/components/list-page/empty-state.svelte +30 -30
  125. package/src/components/list-page/filter-item.svelte +20 -20
  126. package/src/components/list-page/filter-popover.svelte +60 -55
  127. package/src/components/list-page/grouped-list.svelte +4 -2
  128. package/src/components/list-page/index.ts +5 -1
  129. package/src/components/list-page/list-page-shell.svelte +2 -5
  130. package/src/components/list-page/master-detail.svelte +87 -88
  131. package/src/components/list-page/notice-bar.svelte +38 -38
  132. package/src/components/list-page/prev-next.svelte +63 -63
  133. package/src/components/list-page/rail-body.svelte +18 -18
  134. package/src/components/list-page/rail-empty.svelte +23 -30
  135. package/src/components/list-page/rail-head.svelte +24 -24
  136. package/src/components/list-page/rail-row.svelte +77 -73
  137. package/src/components/list-page/section-host.ts +2 -3
  138. package/src/components/locale-switch/locale-switch.svelte +41 -41
  139. package/src/components/mark/mark.svelte +34 -38
  140. package/src/components/open-in/index.ts +1 -5
  141. package/src/components/open-in/open-in.svelte +40 -40
  142. package/src/components/picker/mark-picker.svelte +49 -49
  143. package/src/components/picker/picker.svelte +5 -3
  144. package/src/components/section-header/section-header.test.ts +7 -1
  145. package/src/components/section-header/section-label.svelte +20 -20
  146. package/src/components/segmented/animated-tabs.svelte +31 -2
  147. package/src/components/segmented/collapse.test.ts +2 -1
  148. package/src/components/segmented/index.ts +2 -0
  149. package/src/components/segmented/segment.svelte +52 -49
  150. package/src/components/segmented/segmented-filter.svelte +79 -80
  151. package/src/components/segmented/segmented-nav.svelte +34 -40
  152. package/src/components/segmented/segmented-overflow.svelte +53 -50
  153. package/src/components/segmented/segmented-track.svelte +62 -46
  154. package/src/components/segmented/segmented.ts +2 -3
  155. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  156. package/src/components/svg/svg.svelte +4 -4
  157. package/src/components/table-view/index.ts +1 -1
  158. package/src/components/table-view/table-view.svelte +352 -13
  159. package/src/components/table-view/table-view.test.ts +108 -1
  160. package/src/components/table-view/types.ts +22 -11
  161. package/src/components/tags-input/index.ts +13 -0
  162. package/src/components/tags-input/tags-input.svelte +305 -0
  163. package/src/components/tags-input/tags.test.ts +64 -0
  164. package/src/components/tags-input/tags.ts +142 -0
  165. package/src/components/theme/index.ts +2 -4
  166. package/src/components/theme/theme-provider.svelte +1 -3
  167. package/src/components/toolbar/toolbar-search.svelte +25 -25
  168. package/src/components/toolbar/toolbar.svelte +11 -7
  169. package/src/components/toolbar/toolbar.test.ts +10 -1
  170. package/src/design/index.css +21 -78
  171. package/src/design/polish.css +26 -35
  172. package/src/editor/editor-toolbar.svelte +104 -99
  173. package/src/editor/editor.svelte +296 -266
  174. package/src/editor/image-toolbar.svelte +20 -22
  175. package/src/editor/index.ts +1 -4
  176. package/src/editor/inline-editor.svelte +280 -238
  177. package/src/editor/markdown.ts +15 -15
  178. package/src/editor/renderer.svelte +71 -71
  179. package/src/editor/slash-menu.svelte +24 -27
  180. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  181. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  182. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  183. package/src/effects/magic-card/MagicCard.css +14 -7
  184. package/src/effects/marquee/Marquee.svelte +3 -5
  185. package/src/effects/reveal/observe.ts +0 -3
  186. package/src/effects/spotlight/Spotlight.svelte +1 -1
  187. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  188. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  189. package/src/gallery.test.ts +14 -4
  190. package/src/icons/trades.ts +1 -1
  191. package/src/inbox/README.md +1 -3
  192. package/src/inbox/inbox.test.ts +181 -138
  193. package/src/inbox/index.ts +20 -20
  194. package/src/inbox/logic.ts +114 -111
  195. package/src/inbox/thread-inbox.svelte +227 -151
  196. package/src/inbox/types.ts +130 -108
  197. package/src/lib/theme-transition.ts +2 -4
  198. package/src/lib/tone.ts +2 -4
  199. package/src/lib/utils.ts +2 -2
  200. package/src/modules.test.ts +79 -69
  201. package/src/onboarding/define-tours.test.ts +55 -40
  202. package/src/onboarding/define-tours.ts +23 -23
  203. package/src/onboarding/demo.svelte +24 -5
  204. package/src/onboarding/index.ts +1 -8
  205. package/src/onboarding/onboarding-provider.svelte +19 -7
  206. package/src/onboarding/onboarding.svelte.ts +16 -16
  207. package/src/onboarding/pick.test.ts +61 -52
  208. package/src/onboarding/pick.ts +16 -13
  209. package/src/onboarding/stores.test.ts +80 -80
  210. package/src/onboarding/stores.ts +41 -39
  211. package/src/onboarding/tour-card.svelte +30 -5
  212. package/src/onboarding/tour-runner.svelte +12 -3
  213. package/src/onboarding/types.ts +20 -12
  214. package/src/onboarding/wait.ts +22 -22
  215. package/src/panel/side-panel.svelte +94 -93
  216. package/src/record/demo-data.ts +291 -225
  217. package/src/record/field-section.svelte +38 -34
  218. package/src/record/fields.ts +148 -96
  219. package/src/record/format.ts +63 -56
  220. package/src/record/index.ts +49 -35
  221. package/src/record/info-list.svelte +145 -114
  222. package/src/record/kinds.ts +31 -31
  223. package/src/record/logic.ts +216 -214
  224. package/src/record/next-up.svelte +45 -34
  225. package/src/record/project-list.svelte +56 -50
  226. package/src/record/record-demo.svelte +130 -110
  227. package/src/record/record-header.svelte +37 -28
  228. package/src/record/record-shell.svelte +54 -54
  229. package/src/record/record-stats.svelte +25 -25
  230. package/src/record/record-timeline.svelte +134 -114
  231. package/src/record/record.test.ts +430 -309
  232. package/src/record/steps-rail.svelte +225 -216
  233. package/src/record/types.ts +234 -215
  234. package/src/record/where-we-are.svelte +62 -58
  235. package/src/shadcn/button/button.svelte +1 -1
  236. package/src/shadcn/card/card-title.svelte +1 -5
  237. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  238. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  239. package/src/shadcn/table/table-cell.svelte +2 -3
  240. package/src/shadcn/utilities.css +3 -8
  241. package/src/slides/Controls.svelte +150 -124
  242. package/src/slides/Deck.svelte +243 -238
  243. package/src/slides/Fragment.svelte +64 -57
  244. package/src/slides/Overview.svelte +106 -101
  245. package/src/slides/Presenter.svelte +300 -266
  246. package/src/slides/Slide.svelte +185 -170
  247. package/src/slides/SlideList.svelte +16 -12
  248. package/src/slides/ambient.d.ts +1 -1
  249. package/src/slides/bridges.ts +42 -23
  250. package/src/slides/deck-controller.svelte.ts +47 -35
  251. package/src/slides/demo-slides.svelte +330 -306
  252. package/src/slides/demo.svelte +164 -160
  253. package/src/slides/export/capture.ts +20 -16
  254. package/src/slides/export/ffmpeg.ts +43 -38
  255. package/src/slides/export/index.ts +5 -5
  256. package/src/slides/export/launch.ts +3 -1
  257. package/src/slides/export/pdf.ts +30 -27
  258. package/src/slides/export/types.ts +10 -8
  259. package/src/slides/index.ts +11 -11
  260. package/src/slides/labels.ts +22 -19
  261. package/src/slides/layout.ts +9 -2
  262. package/src/slides/model.ts +4 -4
  263. package/src/slides/navigation.ts +43 -31
  264. package/src/slides/theme.css +5 -7
  265. package/src/slides/types.ts +9 -10
  266. package/src/status/define-statuses.ts +17 -24
  267. package/src/status/index.ts +5 -5
  268. package/src/status/status-badge.svelte +3 -1
  269. package/src/status/status-dot.svelte +1 -3
  270. package/src/status/status-icon.svelte +1 -1
  271. package/src/status/status-types.ts +21 -21
  272. package/src/tasks/board-order.ts +2 -4
  273. package/src/tasks/demo.svelte +189 -31
  274. package/src/tasks/detail/detail-demo.svelte +73 -13
  275. package/src/tasks/detail/detail-types.ts +45 -45
  276. package/src/tasks/detail/task-detail.svelte +40 -10
  277. package/src/tasks/detail/task-property.svelte +8 -2
  278. package/src/tasks/detail/task-thread.svelte +53 -12
  279. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  280. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  281. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  282. package/src/tasks/gantt/gantt-header.svelte +9 -3
  283. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  284. package/src/tasks/gantt/gantt.css +2 -1
  285. package/src/tasks/gantt/gantt.test.ts +3 -1
  286. package/src/tasks/gantt/logic.test.ts +110 -20
  287. package/src/tasks/gantt/logic.ts +42 -6
  288. package/src/tasks/inbox/inbox-card.svelte +13 -5
  289. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  290. package/src/tasks/inbox/inbox-logic.ts +6 -40
  291. package/src/tasks/inbox/inbox-statuses.ts +23 -23
  292. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  293. package/src/tasks/inbox/inbox-types.ts +54 -58
  294. package/src/tasks/inbox/index.ts +22 -21
  295. package/src/tasks/index.ts +49 -52
  296. package/src/tasks/kanban-board.svelte +16 -12
  297. package/src/tasks/kanban-types.ts +2 -3
  298. package/src/tasks/presets/sales.ts +6 -6
  299. package/src/tasks/task-gantt.svelte +28 -8
  300. package/src/tasks/task-list.svelte +41 -17
  301. package/src/tasks/time/duration.ts +40 -40
  302. package/src/tasks/time/time-chip.svelte +9 -2
  303. package/src/tasks/time/time-dot.svelte +1 -1
  304. package/src/tasks/time/time-ring.svelte +11 -3
  305. package/src/tasks/time/time-tracker.svelte +57 -15
  306. package/src/tasks/time/time-types.ts +57 -57
  307. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  308. package/src/thread/index.ts +7 -7
  309. package/src/thread/logic.ts +36 -33
  310. package/src/thread/thread.svelte +82 -16
  311. package/src/thread/thread.test.ts +58 -47
  312. package/src/thread/types.ts +68 -68
  313. package/src/tokens/craft.css +17 -53
  314. package/src/tokens/index.css +3 -13
  315. package/src/tokens/palette.css +13 -11
  316. package/src/tokens/primitives.css +10 -23
  317. package/src/tasks/inbox/inbox.svelte +0 -203
@@ -1,43 +1,59 @@
1
1
  <svelte:options namespace="svg" />
2
2
 
3
3
  <script lang="ts">
4
- // =========================
5
- // IMPORTS
6
- // =========================
7
- import { BaseEdge } from "@xyflow/svelte";
8
- import EdgeGradient from "./edge-gradient.svelte";
4
+ // =========================
5
+ // IMPORTS
6
+ // =========================
7
+ import { BaseEdge } from "@xyflow/svelte";
8
+ import EdgeGradient from "./edge-gradient.svelte";
9
9
 
10
- // =========================
11
- // TYPES
12
- // =========================
13
- interface Props {
14
- id: string;
15
- path: string;
16
- from: { x: number; y: number };
17
- to: { x: number; y: number };
18
- fromColor: string;
19
- toColor?: string;
20
- markerEnd?: string;
21
- /** The edge's own style. A stroke set here beats the gradient. */
22
- style?: string;
23
- class?: string;
24
- }
10
+ // =========================
11
+ // TYPES
12
+ // =========================
13
+ interface Props {
14
+ id: string;
15
+ path: string;
16
+ from: { x: number; y: number };
17
+ to: { x: number; y: number };
18
+ fromColor: string;
19
+ toColor?: string;
20
+ markerEnd?: string;
21
+ /** The edge's own style. A stroke set here beats the gradient. */
22
+ style?: string;
23
+ class?: string;
24
+ }
25
25
 
26
- // =========================
27
- // PROPS
28
- // =========================
29
- let { id, path, from, to, fromColor, toColor = fromColor, markerEnd, style = "", class: className = "" }: Props = $props();
26
+ // =========================
27
+ // PROPS
28
+ // =========================
29
+ let {
30
+ id,
31
+ path,
32
+ from,
33
+ to,
34
+ fromColor,
35
+ toColor = fromColor,
36
+ markerEnd,
37
+ style = "",
38
+ class: className = "",
39
+ }: Props = $props();
30
40
 
31
- // =========================
32
- // DERIVED
33
- // =========================
34
- let gradientId = $derived(`canvas-gradient-${id.replace(/[^\w-]/g, "_")}`);
41
+ // =========================
42
+ // DERIVED
43
+ // =========================
44
+ let gradientId = $derived(`canvas-gradient-${id.replace(/[^\w-]/g, "_")}`);
35
45
  </script>
36
46
 
37
47
  <!-- =========================== -->
38
48
  <!-- MARKUP -->
39
49
  <!-- =========================== -->
40
50
  <EdgeGradient id={gradientId} {from} {to} {fromColor} {toColor} />
41
- <BaseEdge {id} {path} {markerEnd} class="canvas-edge {className}" style="stroke: url(#{gradientId}); {style}" />
51
+ <BaseEdge
52
+ {id}
53
+ {path}
54
+ {markerEnd}
55
+ class="canvas-edge {className}"
56
+ style="stroke: url(#{gradientId}); {style}"
57
+ />
42
58
  <!-- Hidden until a test run crosses this link; the canvas stylesheet draws the moving dashes. -->
43
59
  <path d={path} class="canvas-edge-flow" fill="none" aria-hidden="true" />
@@ -1,6 +1,11 @@
1
1
  import { describe, expect, it } from "vite-plus/test";
2
2
  import type { Node } from "@xyflow/svelte";
3
- import { deletableIds, refuseLockedOps, toggleLock, withLockFlags } from "./lock.js";
3
+ import {
4
+ deletableIds,
5
+ refuseLockedOps,
6
+ toggleLock,
7
+ withLockFlags,
8
+ } from "./lock.js";
4
9
 
5
10
  function card(id: string, data: Record<string, unknown> = {}, extra: Partial<Node> = {}): Node {
6
11
  return { id, type: "note", position: { x: 0, y: 0 }, data, ...extra };
@@ -2,7 +2,13 @@
2
2
  // A change is one patchNode per card, so it is one undo step.
3
3
 
4
4
  import type { CanvasOp } from "../ops/ops.js";
5
- import { PEN_COLORS, SHAPE_NODE, STROKE_NODE, TEXT_NODE, type PenColor } from "../pen/types.js";
5
+ import {
6
+ PEN_COLORS,
7
+ SHAPE_NODE,
8
+ STROKE_NODE,
9
+ TEXT_NODE,
10
+ type PenColor,
11
+ } from "../pen/types.js";
6
12
 
7
13
  export type FillValue = PenColor | "none";
8
14
  export type FontWeight = "regular" | "bold";
@@ -3,7 +3,7 @@ import { sortParentsFirst, stripCallbacks } from "./canvas-cleanup.js";
3
3
  import { makeNode } from "../test-utils.js";
4
4
  import type { CanvasNode } from "../types.js";
5
5
 
6
- /** Read serialized data as a loose record: these tests exercise legacy fields
6
+ /** Read serialized data as a loose record: these tests exercise old fields
7
7
  * that are no longer part of any per-type shape but must survive the denylist. */
8
8
  function loose(node: { data: unknown }): Record<string, unknown> {
9
9
  return node.data as Record<string, unknown>;
@@ -1,5 +1,9 @@
1
1
  import { describe, expect, it } from "vite-plus/test";
2
- import { figureBoxHeight, figureRatio, sanitizeFigureSvg } from "./svg-figure.js";
2
+ import {
3
+ figureBoxHeight,
4
+ figureRatio,
5
+ sanitizeFigureSvg,
6
+ } from "./svg-figure.js";
3
7
 
4
8
  describe("sanitizeFigureSvg", () => {
5
9
  it("keeps the shapes, the text and the geometry", () => {
@@ -1,81 +1,82 @@
1
1
  <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import type { Snippet } from "svelte";
6
- import { useStore } from "@xyflow/svelte";
7
- import SideHandles from "../chrome/side-handles.svelte";
8
- import { lodMode } from "./lod-mode.svelte.js";
9
- import { renderAll } from "./render-all.svelte.js";
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import { useStore } from "@xyflow/svelte";
7
+ import SideHandles from "../chrome/side-handles.svelte";
8
+ import { lodMode } from "./lod-mode.svelte.js";
9
+ import { renderAll } from "./render-all.svelte.js";
10
10
 
11
- // Zoomed out, a card is a box at its measured size, so no arrow moves.
12
- // Never measured, selected, dragged or exported: full.
11
+ // Zoomed out, a card is a box at its measured size, so no arrow moves.
12
+ // Never measured, selected, dragged or exported: full.
13
13
 
14
- // =========================
15
- // TYPES
16
- // =========================
17
- interface Props {
18
- id: string;
19
- selected?: boolean;
20
- dragging?: boolean;
21
- /** A CSS colour: the card's own, for the box's border and icon well. */
22
- tone: string;
23
- title: string;
24
- /** The full card. */
25
- children: Snippet;
26
- }
14
+ // =========================
15
+ // TYPES
16
+ // =========================
17
+ interface Props {
18
+ id: string;
19
+ selected?: boolean;
20
+ dragging?: boolean;
21
+ /** A CSS colour: the card's own, for the box's border and icon well. */
22
+ tone: string;
23
+ title: string;
24
+ /** The full card. */
25
+ children: Snippet;
26
+ }
27
27
 
28
- // =========================
29
- // PROPS
30
- // =========================
31
- let { id, selected = false, dragging = false, tone, title, children }: Props = $props();
28
+ // =========================
29
+ // PROPS
30
+ // =========================
31
+ let { id, selected = false, dragging = false, tone, title, children }: Props = $props();
32
32
 
33
- // =========================
34
- // STATE
35
- // =========================
36
- const store = useStore();
37
- const mode = lodMode(store);
38
- let probe = $state<HTMLElement>();
39
- // xyflow keeps the size of a card that scrolled away, so it comes back as a box.
40
- let size = $state<{ width: number; height: number } | null>(measured());
41
- // Plain on purpose: only the observer below reads it.
42
- let boxed = false;
33
+ // =========================
34
+ // STATE
35
+ // =========================
36
+ const store = useStore();
37
+ const mode = lodMode(store);
38
+ let probe = $state<HTMLElement>();
39
+ // xyflow keeps the size of a card that scrolled away, so it comes back as a box.
40
+ let size = $state<{ width: number; height: number } | null>(measured());
41
+ // Plain on purpose: only the observer below reads it.
42
+ let boxed = false;
43
43
 
44
- // =========================
45
- // DERIVED
46
- // =========================
47
- let simple = $derived(mode.low && size !== null && !selected && !dragging && !renderAll.on);
44
+ // =========================
45
+ // DERIVED
46
+ // =========================
47
+ let simple = $derived(mode.low && size !== null && !selected && !dragging && !renderAll.on);
48
48
 
49
- // =========================
50
- // EFFECTS
51
- // =========================
52
- // Sizes the simple box from the full card. The observer's first call carries it, so a mount forces no layout.
53
- $effect(() => {
54
- const node = probe?.closest<HTMLElement>(".svelte-flow__node");
55
- if (!node) return;
56
- const observer = new ResizeObserver(([entry]) => {
57
- if (boxed) return;
58
- const box = entry.borderBoxSize[0];
59
- const width = Math.round(box.inlineSize);
60
- const height = Math.round(box.blockSize);
61
- if (width && height && (width !== size?.width || height !== size?.height)) size = { width, height };
49
+ // =========================
50
+ // EFFECTS
51
+ // =========================
52
+ // Sizes the simple box from the full card. The observer's first call carries it, so a mount forces no layout.
53
+ $effect(() => {
54
+ const node = probe?.closest<HTMLElement>(".svelte-flow__node");
55
+ if (!node) return;
56
+ const observer = new ResizeObserver(([entry]) => {
57
+ if (boxed) return;
58
+ const box = entry.borderBoxSize[0];
59
+ const width = Math.round(box.inlineSize);
60
+ const height = Math.round(box.blockSize);
61
+ if (width && height && (width !== size?.width || height !== size?.height))
62
+ size = { width, height };
63
+ });
64
+ observer.observe(node);
65
+ return () => observer.disconnect();
62
66
  });
63
- observer.observe(node);
64
- return () => observer.disconnect();
65
- });
66
67
 
67
- // Set after the render that swaps the box in, so the read above never sees the box's own size.
68
- $effect(() => {
69
- boxed = simple;
70
- });
68
+ // Set after the render that swaps the box in, so the read above never sees the box's own size.
69
+ $effect(() => {
70
+ boxed = simple;
71
+ });
71
72
 
72
- // =========================
73
- // FUNCTIONS
74
- // =========================
75
- function measured(): { width: number; height: number } | null {
76
- const known = store.nodeLookup.get(id)?.measured;
77
- return known?.width && known.height ? { width: known.width, height: known.height } : null;
78
- }
73
+ // =========================
74
+ // FUNCTIONS
75
+ // =========================
76
+ function measured(): { width: number; height: number } | null {
77
+ const known = store.nodeLookup.get(id)?.measured;
78
+ return known?.width && known.height ? { width: known.width, height: known.height } : null;
79
+ }
79
80
  </script>
80
81
 
81
82
  <!-- =========================== -->
@@ -83,7 +84,11 @@ function measured(): { width: number; height: number } | null {
83
84
  <!-- =========================== -->
84
85
  <span class="cv-lod-probe" bind:this={probe} hidden></span>
85
86
  {#if simple && size}
86
- <div class="cv-lod-box" style="width: {size.width}px; height: {size.height}px; --cv-lod-tone: {tone}" data-lod="simple">
87
+ <div
88
+ class="cv-lod-box"
89
+ style="width: {size.width}px; height: {size.height}px; --cv-lod-tone: {tone}"
90
+ data-lod="simple"
91
+ >
87
92
  <span class="cv-lod-well"></span>
88
93
  <span class="cv-lod-title">{title}</span>
89
94
  </div>
@@ -96,45 +101,45 @@ function measured(): { width: number; height: number } | null {
96
101
  <!-- STYLES -->
97
102
  <!-- =========================== -->
98
103
  <style>
99
- :global([data-cv-every-card] .svelte-flow__node) {
100
- animation: none;
101
- }
104
+ :global([data-cv-every-card] .svelte-flow__node) {
105
+ animation: none;
106
+ }
102
107
 
103
- /* A dot on a zoomed-out box is too small to link from; unpainted, it keeps its place for the links. */
104
- :global(.cv-lod-handle) {
105
- visibility: hidden;
106
- }
108
+ /* A dot on a zoomed-out box is too small to link from; unpainted, it keeps its place for the links. */
109
+ :global(.cv-lod-handle) {
110
+ visibility: hidden;
111
+ }
107
112
 
108
- /* The architecture card's own frame and layout (architecture-node.svelte), so selecting a card doesn't change how it looks. */
109
- .cv-lod-box {
110
- position: relative;
111
- box-sizing: border-box;
112
- display: flex;
113
- align-items: flex-start;
114
- gap: 10px;
115
- overflow: hidden;
116
- padding: var(--card-pad, 12px);
117
- border: 1px solid color-mix(in oklab, var(--cv-lod-tone) 20%, transparent);
118
- border-radius: var(--card-radius, 12px);
119
- background: var(--card, var(--cv-chrome-surface, #fff));
120
- color: var(--cv-ink, var(--foreground, #1a1a1f));
121
- contain: strict;
122
- }
113
+ /* The architecture card's own frame and layout (architecture-node.svelte), so selecting a card doesn't change how it looks. */
114
+ .cv-lod-box {
115
+ position: relative;
116
+ box-sizing: border-box;
117
+ display: flex;
118
+ align-items: flex-start;
119
+ gap: 10px;
120
+ overflow: hidden;
121
+ padding: var(--card-pad, 12px);
122
+ border: 1px solid color-mix(in oklab, var(--cv-lod-tone) 20%, transparent);
123
+ border-radius: var(--card-radius, 12px);
124
+ background: var(--card, var(--cv-chrome-surface, #fff));
125
+ color: var(--cv-ink, var(--foreground, #1a1a1f));
126
+ contain: strict;
127
+ }
123
128
 
124
- /* The icon well, without the icon. */
125
- .cv-lod-well {
126
- flex: none;
127
- width: 32px;
128
- height: 32px;
129
- border-radius: 9999px;
130
- background: color-mix(in oklab, var(--cv-lod-tone) 10%, transparent);
131
- }
129
+ /* The icon well, without the icon. */
130
+ .cv-lod-well {
131
+ flex: none;
132
+ width: 32px;
133
+ height: 32px;
134
+ border-radius: 9999px;
135
+ background: color-mix(in oklab, var(--cv-lod-tone) 10%, transparent);
136
+ }
132
137
 
133
- .cv-lod-title {
134
- overflow: hidden;
135
- font-size: 14px;
136
- font-weight: 500;
137
- line-height: 1.25;
138
- overflow-wrap: anywhere;
139
- }
138
+ .cv-lod-title {
139
+ overflow: hidden;
140
+ font-size: 14px;
141
+ font-weight: 500;
142
+ line-height: 1.25;
143
+ overflow-wrap: anywhere;
144
+ }
140
145
  </style>
@@ -36,7 +36,7 @@ export const patchNodeOp = z.object({
36
36
  kind: z.literal("patchNode"),
37
37
  nodeId: z.string().min(1),
38
38
  data: z.record(z.string(), z.unknown()).optional(),
39
- // Merged into the node's style object. A legacy CSS-string style is replaced
39
+ // Merged into the node's style object. A CSS-string style from an old save is replaced
40
40
  // wholesale (there is nothing sane to merge into).
41
41
  style: z.record(z.string(), z.unknown()).optional(),
42
42
  width: z.number().optional(),
@@ -1,90 +1,95 @@
1
1
  <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import { useTextField, type TextFieldHandle } from "../editor/text-field-context.js";
6
- import { useHandDrawn } from "../editor/hand-context.js";
7
- import "./pen.css";
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { useTextField, type TextFieldHandle } from "../editor/text-field-context.js";
6
+ import { useHandDrawn } from "../editor/hand-context.js";
7
+ import "./pen.css";
8
8
 
9
- // The words of a pen text or a shape label: the host's field when it gave one (formatted text,
10
- // its own bar and undo), else a plain textarea.
9
+ // The words of a pen text or a shape label: the host's field when it gave one (formatted text,
10
+ // its own bar and undo), else a plain textarea.
11
11
 
12
- // =========================
13
- // TYPES
14
- // =========================
15
- interface Props {
16
- value: string;
17
- /** Which pen element it sits in. Sets the size and the box it keeps. */
18
- variant: "text" | "label";
19
- onCommit: (next: string, live: boolean) => void;
20
- ariaLabel: string;
21
- /** Typing needs the canvas editor; without one the words are shown, not changed. */
22
- readonly?: boolean;
23
- selected?: boolean;
24
- color?: string;
25
- fontSize?: number;
26
- fontWeight?: number;
27
- center?: boolean;
28
- column?: boolean;
29
- /** Called when the field closes. */
30
- onExit?: () => void;
31
- }
12
+ // =========================
13
+ // TYPES
14
+ // =========================
15
+ interface Props {
16
+ value: string;
17
+ /** Which pen element it sits in. Sets the size and the box it keeps. */
18
+ variant: "text" | "label";
19
+ onCommit: (next: string, live: boolean) => void;
20
+ ariaLabel: string;
21
+ /** Typing needs the canvas editor; without one the words are shown, not changed. */
22
+ readonly?: boolean;
23
+ selected?: boolean;
24
+ color?: string;
25
+ fontSize?: number;
26
+ fontWeight?: number;
27
+ center?: boolean;
28
+ column?: boolean;
29
+ /** Called when the field closes. */
30
+ onExit?: () => void;
31
+ }
32
32
 
33
- // =========================
34
- // PROPS
35
- // =========================
36
- let {
37
- value,
38
- variant,
39
- onCommit,
40
- ariaLabel,
41
- readonly = false,
42
- selected = false,
43
- color,
44
- fontSize,
45
- fontWeight,
46
- center = false,
47
- column = false,
48
- onExit,
49
- }: Props = $props();
33
+ // =========================
34
+ // PROPS
35
+ // =========================
36
+ let {
37
+ value,
38
+ variant,
39
+ onCommit,
40
+ ariaLabel,
41
+ readonly = false,
42
+ selected = false,
43
+ color,
44
+ fontSize,
45
+ fontWeight,
46
+ center = false,
47
+ column = false,
48
+ onExit,
49
+ }: Props = $props();
50
50
 
51
- // =========================
52
- // STATE
53
- // =========================
54
- const Field = useTextField();
55
- const handDrawn = useHandDrawn();
56
- let textarea = $state<HTMLTextAreaElement>();
57
- let field = $state<TextFieldHandle>();
58
- let editing = $state(false);
51
+ // =========================
52
+ // STATE
53
+ // =========================
54
+ const Field = useTextField();
55
+ const handDrawn = useHandDrawn();
56
+ let textarea = $state<HTMLTextAreaElement>();
57
+ let field = $state<TextFieldHandle>();
58
+ let editing = $state(false);
59
59
 
60
- // =========================
61
- // DERIVED
62
- // =========================
63
- let style = $derived(
64
- [color && `color: ${color}`, fontSize && `font-size: ${fontSize}px`, fontWeight && `font-weight: ${fontWeight}`, center && "text-align: center"]
65
- .filter(Boolean)
66
- .join("; "),
67
- );
60
+ // =========================
61
+ // DERIVED
62
+ // =========================
63
+ let style = $derived(
64
+ [
65
+ color && `color: ${color}`,
66
+ fontSize && `font-size: ${fontSize}px`,
67
+ fontWeight && `font-weight: ${fontWeight}`,
68
+ center && "text-align: center",
69
+ ]
70
+ .filter(Boolean)
71
+ .join("; "),
72
+ );
68
73
 
69
- // =========================
70
- // FUNCTIONS
71
- // =========================
72
- /** Open the field for typing. A host field opens where `point` landed, else at the end. */
73
- export function edit(point?: { x: number; y: number }) {
74
- if (readonly) return;
75
- if (field) return field.edit(point);
76
- textarea?.focus();
77
- }
74
+ // =========================
75
+ // FUNCTIONS
76
+ // =========================
77
+ /** Open the field for typing. A host field opens where `point` landed, else at the end. */
78
+ export function edit(point?: { x: number; y: number }) {
79
+ if (readonly) return;
80
+ if (field) return field.edit(point);
81
+ textarea?.focus();
82
+ }
78
83
 
79
- function close() {
80
- editing = false;
81
- onExit?.();
82
- }
84
+ function close() {
85
+ editing = false;
86
+ onExit?.();
87
+ }
83
88
 
84
- // A text you aren't typing in drags like any card, so a first press must not focus it.
85
- function press(event: MouseEvent) {
86
- if (variant === "text" && !editing) event.preventDefault();
87
- }
89
+ // A text you aren't typing in drags like any card, so a first press must not focus it.
90
+ function press(event: MouseEvent) {
91
+ if (variant === "text" && !editing) event.preventDefault();
92
+ }
88
93
  </script>
89
94
 
90
95
  <!-- =========================== -->
@@ -98,7 +103,9 @@ function press(event: MouseEvent) {
98
103
  {ariaLabel}
99
104
  {readonly}
100
105
  {style}
101
- class="pen-field is-{variant} is-rich {handDrawn() ? 'hand' : ''} {selected ? 'selected' : ''} {column ? 'column' : ''}"
106
+ class="pen-field is-{variant} is-rich {handDrawn() ? 'hand' : ''} {selected
107
+ ? 'selected'
108
+ : ''} {column ? 'column' : ''}"
102
109
  clickToEdit={variant === "label"}
103
110
  onExit={close}
104
111
  />
@@ -123,6 +130,5 @@ function press(event: MouseEvent) {
123
130
  // Typing must never reach the canvas shortcuts, where Delete would remove the node.
124
131
  event.stopPropagation();
125
132
  if (event.key === "Escape") event.currentTarget.blur();
126
- }}
127
- ></textarea>
133
+ }}></textarea>
128
134
  {/if}