@heyramzi/ui 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/package.json +7 -8
  2. package/scripts/sync-shadcn.mjs +3 -4
  3. package/src/agents/agent-avatar.svelte +604 -575
  4. package/src/agents/agent-cast.svelte +179 -183
  5. package/src/agents/demo.svelte +125 -113
  6. package/src/agents/helpers.ts +23 -17
  7. package/src/agents/index.ts +4 -4
  8. package/src/agents/page-state.ts +6 -6
  9. package/src/agents/types.ts +6 -6
  10. package/src/ai/ai-button/AiButton.svelte +4 -12
  11. package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
  12. package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
  13. package/src/ai/response/Response.css +2 -3
  14. package/src/animations/avatar-group-hover.css +2 -16
  15. package/src/animations/card-tilt.css +3 -6
  16. package/src/animations/digit-pop-in.css +2 -15
  17. package/src/animations/dropdown.css +4 -17
  18. package/src/animations/input-clear-dissolve.css +5 -18
  19. package/src/animations/input-shake.css +9 -41
  20. package/src/animations/learn-more-hover.css +17 -9
  21. package/src/animations/like-button.css +2 -4
  22. package/src/animations/page-side-by-side.css +2 -10
  23. package/src/animations/plus-menu-morph.css +2 -5
  24. package/src/animations/shimmer-text.css +5 -18
  25. package/src/animations/spinning-counter.css +2 -6
  26. package/src/animations/success-check.css +2 -15
  27. package/src/animations/thinking-states.css +2 -7
  28. package/src/brand/brand.test.ts +21 -5
  29. package/src/brand/index.ts +4 -10
  30. package/src/canvas/card/card-frame.svelte +43 -39
  31. package/src/canvas/card/resize-handles.svelte +89 -80
  32. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  33. package/src/canvas/chrome/lock-badge.svelte +8 -8
  34. package/src/canvas/chrome/side-handles.svelte +24 -19
  35. package/src/canvas/chrome/size-chip.svelte +30 -30
  36. package/src/canvas/chrome/snap-guides.svelte +20 -20
  37. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  38. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  39. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  40. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  41. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  42. package/src/canvas/edges/bend-handle.svelte +46 -46
  43. package/src/canvas/edges/edge-gradient.svelte +10 -10
  44. package/src/canvas/edges/gradient-edge.svelte +45 -29
  45. package/src/canvas/editor/lock.test.ts +6 -1
  46. package/src/canvas/editor/style.ts +7 -1
  47. package/src/canvas/headless/svg-figure.test.ts +5 -1
  48. package/src/canvas/lod/lod-switch.svelte +111 -106
  49. package/src/canvas/pen/PenField.svelte +87 -81
  50. package/src/canvas/pen/PenOverlay.svelte +127 -127
  51. package/src/canvas/pen/ShapeNode.svelte +31 -32
  52. package/src/canvas/pen/TextNode.svelte +54 -53
  53. package/src/canvas/pen/host-field.test.svelte +7 -7
  54. package/src/canvas/pen/shapes.ts +7 -1
  55. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  56. package/src/canvas/screen/PartView.svelte +138 -72
  57. package/src/canvas/screen/ScreenBody.svelte +81 -60
  58. package/src/canvas/screen/ScreenCard.svelte +20 -20
  59. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  60. package/src/canvas/ui/FrameLayer.svelte +59 -59
  61. package/src/canvas/ui/PenLayer.svelte +39 -30
  62. package/src/canvas/ui/StylePanel.svelte +119 -102
  63. package/src/canvas/ui/canvas-grid.svelte +137 -129
  64. package/src/charts/animated-value.svelte +1 -1
  65. package/src/charts/area-chart.css +2 -5
  66. package/src/charts/area-chart.svelte +1 -1
  67. package/src/charts/bar-chart.css +2 -3
  68. package/src/charts/bar-chart.svelte +1 -1
  69. package/src/charts/charts.css +5 -17
  70. package/src/charts/crosshair.svelte +1 -1
  71. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  72. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  73. package/src/charts/dashboard/data-panel.svelte +50 -50
  74. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  75. package/src/charts/dashboard/data-table.svelte +138 -138
  76. package/src/charts/dashboard/panel-grid.svelte +30 -30
  77. package/src/charts/dashboard/period-filter.svelte +25 -25
  78. package/src/charts/dashboard/rating-chip.svelte +19 -19
  79. package/src/charts/dashboard/stat-band.svelte +32 -32
  80. package/src/charts/dashboard/stat-tile.svelte +106 -99
  81. package/src/charts/donut-chart.svelte +1 -1
  82. package/src/charts/edge-fade.svelte +1 -1
  83. package/src/charts/figure.svelte +1 -1
  84. package/src/charts/gauge-chart.css +4 -7
  85. package/src/charts/gauge-chart.svelte +1 -1
  86. package/src/charts/hover-focus.svelte +1 -1
  87. package/src/charts/legend.css +1 -3
  88. package/src/charts/legend.svelte +1 -1
  89. package/src/charts/line-chart.svelte +1 -1
  90. package/src/charts/number-chart.css +6 -9
  91. package/src/charts/number-chart.svelte +1 -1
  92. package/src/charts/reference-line.svelte +19 -19
  93. package/src/charts/reveal.svelte +1 -1
  94. package/src/charts/ring-chart.css +2 -4
  95. package/src/charts/ring-chart.svelte +1 -1
  96. package/src/charts/sparkline.svelte +5 -2
  97. package/src/charts/stat-card.svelte +1 -1
  98. package/src/charts/time-series-chart.css +2 -5
  99. package/src/charts/trend-pill.css +2 -4
  100. package/src/charts/trend-pill.svelte +1 -1
  101. package/src/components/copy/copy-button.svelte +93 -98
  102. package/src/components/copy/copy-button.test.ts +10 -2
  103. package/src/components/data-table/infinite-scroll.svelte +42 -42
  104. package/src/components/data-table/pagination.svelte +34 -22
  105. package/src/components/data-table/row-arrow.svelte +19 -16
  106. package/src/components/data-table/row-context-menu.svelte +52 -52
  107. package/src/components/data-table/sortable-head.svelte +25 -25
  108. package/src/components/data-table/table-skeleton.svelte +41 -36
  109. package/src/components/docs/doc-page.svelte +18 -9
  110. package/src/components/docs/on-this-page.svelte +16 -3
  111. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  112. package/src/components/field-row/field-row.svelte +10 -8
  113. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  114. package/src/components/icon/icon.svelte +14 -14
  115. package/src/components/language-flag/language-flag.svelte +3 -1
  116. package/src/components/list-page/detail-empty.svelte +34 -34
  117. package/src/components/list-page/detail-header.svelte +55 -47
  118. package/src/components/list-page/detail-panel.svelte +51 -59
  119. package/src/components/list-page/detail-row.svelte +19 -19
  120. package/src/components/list-page/detail-stack.svelte +21 -21
  121. package/src/components/list-page/empty-state.svelte +30 -30
  122. package/src/components/list-page/filter-item.svelte +20 -20
  123. package/src/components/list-page/filter-popover.svelte +60 -55
  124. package/src/components/list-page/grouped-list.svelte +4 -2
  125. package/src/components/list-page/index.ts +5 -1
  126. package/src/components/list-page/list-page-shell.svelte +2 -5
  127. package/src/components/list-page/master-detail.svelte +87 -88
  128. package/src/components/list-page/notice-bar.svelte +38 -38
  129. package/src/components/list-page/prev-next.svelte +63 -63
  130. package/src/components/list-page/rail-body.svelte +18 -18
  131. package/src/components/list-page/rail-empty.svelte +23 -30
  132. package/src/components/list-page/rail-head.svelte +24 -24
  133. package/src/components/list-page/rail-row.svelte +77 -73
  134. package/src/components/list-page/section-host.ts +2 -3
  135. package/src/components/locale-switch/locale-switch.svelte +41 -41
  136. package/src/components/mark/mark.svelte +34 -38
  137. package/src/components/open-in/index.ts +1 -5
  138. package/src/components/open-in/open-in.svelte +40 -40
  139. package/src/components/picker/mark-picker.svelte +49 -49
  140. package/src/components/picker/picker.svelte +5 -3
  141. package/src/components/section-header/section-header.test.ts +7 -1
  142. package/src/components/section-header/section-label.svelte +20 -20
  143. package/src/components/segmented/animated-tabs.svelte +31 -2
  144. package/src/components/segmented/collapse.test.ts +2 -1
  145. package/src/components/segmented/index.ts +2 -0
  146. package/src/components/segmented/segment.svelte +52 -49
  147. package/src/components/segmented/segmented-filter.svelte +79 -80
  148. package/src/components/segmented/segmented-nav.svelte +34 -40
  149. package/src/components/segmented/segmented-overflow.svelte +53 -50
  150. package/src/components/segmented/segmented-track.svelte +62 -46
  151. package/src/components/segmented/segmented.ts +2 -3
  152. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  153. package/src/components/svg/svg.svelte +4 -4
  154. package/src/components/table-view/table-view.svelte +17 -2
  155. package/src/components/table-view/table-view.test.ts +7 -1
  156. package/src/components/table-view/types.ts +11 -11
  157. package/src/components/tags-input/index.ts +13 -0
  158. package/src/components/tags-input/tags-input.svelte +305 -0
  159. package/src/components/tags-input/tags.test.ts +64 -0
  160. package/src/components/tags-input/tags.ts +142 -0
  161. package/src/components/theme/index.ts +2 -4
  162. package/src/components/theme/theme-provider.svelte +1 -3
  163. package/src/components/toolbar/toolbar-search.svelte +25 -25
  164. package/src/components/toolbar/toolbar.svelte +11 -7
  165. package/src/components/toolbar/toolbar.test.ts +10 -1
  166. package/src/design/index.css +21 -78
  167. package/src/design/polish.css +26 -35
  168. package/src/editor/editor-toolbar.svelte +104 -99
  169. package/src/editor/editor.svelte +296 -266
  170. package/src/editor/image-toolbar.svelte +20 -22
  171. package/src/editor/index.ts +1 -4
  172. package/src/editor/inline-editor.svelte +280 -238
  173. package/src/editor/markdown.ts +15 -15
  174. package/src/editor/renderer.svelte +71 -71
  175. package/src/editor/slash-menu.svelte +24 -27
  176. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  177. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  178. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  179. package/src/effects/magic-card/MagicCard.css +14 -7
  180. package/src/effects/marquee/Marquee.svelte +3 -5
  181. package/src/effects/reveal/observe.ts +0 -3
  182. package/src/effects/spotlight/Spotlight.svelte +1 -1
  183. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  184. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  185. package/src/gallery.test.ts +14 -4
  186. package/src/inbox/README.md +1 -3
  187. package/src/inbox/inbox.test.ts +181 -138
  188. package/src/inbox/index.ts +20 -20
  189. package/src/inbox/logic.ts +114 -111
  190. package/src/inbox/thread-inbox.svelte +227 -151
  191. package/src/inbox/types.ts +130 -108
  192. package/src/lib/theme-transition.ts +2 -4
  193. package/src/lib/tone.ts +2 -4
  194. package/src/lib/utils.ts +2 -2
  195. package/src/modules.test.ts +79 -69
  196. package/src/onboarding/define-tours.test.ts +55 -40
  197. package/src/onboarding/define-tours.ts +23 -23
  198. package/src/onboarding/demo.svelte +24 -5
  199. package/src/onboarding/index.ts +1 -8
  200. package/src/onboarding/onboarding-provider.svelte +19 -7
  201. package/src/onboarding/onboarding.svelte.ts +16 -16
  202. package/src/onboarding/pick.test.ts +61 -52
  203. package/src/onboarding/pick.ts +16 -13
  204. package/src/onboarding/stores.test.ts +80 -80
  205. package/src/onboarding/stores.ts +41 -39
  206. package/src/onboarding/tour-card.svelte +30 -5
  207. package/src/onboarding/tour-runner.svelte +12 -3
  208. package/src/onboarding/types.ts +20 -12
  209. package/src/onboarding/wait.ts +22 -22
  210. package/src/panel/side-panel.svelte +94 -93
  211. package/src/record/demo-data.ts +291 -225
  212. package/src/record/field-section.svelte +38 -34
  213. package/src/record/fields.ts +148 -96
  214. package/src/record/format.ts +63 -56
  215. package/src/record/index.ts +49 -35
  216. package/src/record/info-list.svelte +145 -114
  217. package/src/record/kinds.ts +31 -31
  218. package/src/record/logic.ts +216 -214
  219. package/src/record/next-up.svelte +45 -34
  220. package/src/record/project-list.svelte +56 -50
  221. package/src/record/record-demo.svelte +130 -110
  222. package/src/record/record-header.svelte +37 -28
  223. package/src/record/record-shell.svelte +54 -54
  224. package/src/record/record-stats.svelte +25 -25
  225. package/src/record/record-timeline.svelte +134 -114
  226. package/src/record/record.test.ts +430 -309
  227. package/src/record/steps-rail.svelte +225 -216
  228. package/src/record/types.ts +234 -215
  229. package/src/record/where-we-are.svelte +62 -58
  230. package/src/shadcn/button/button.svelte +1 -1
  231. package/src/shadcn/card/card-title.svelte +1 -5
  232. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  233. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  234. package/src/shadcn/table/table-cell.svelte +2 -3
  235. package/src/shadcn/utilities.css +3 -8
  236. package/src/slides/Controls.svelte +150 -124
  237. package/src/slides/Deck.svelte +243 -238
  238. package/src/slides/Fragment.svelte +64 -57
  239. package/src/slides/Overview.svelte +106 -101
  240. package/src/slides/Presenter.svelte +300 -266
  241. package/src/slides/Slide.svelte +185 -170
  242. package/src/slides/SlideList.svelte +16 -12
  243. package/src/slides/ambient.d.ts +1 -1
  244. package/src/slides/bridges.ts +42 -23
  245. package/src/slides/deck-controller.svelte.ts +47 -35
  246. package/src/slides/demo-slides.svelte +330 -306
  247. package/src/slides/demo.svelte +164 -160
  248. package/src/slides/export/capture.ts +20 -16
  249. package/src/slides/export/ffmpeg.ts +43 -38
  250. package/src/slides/export/index.ts +5 -5
  251. package/src/slides/export/launch.ts +3 -1
  252. package/src/slides/export/pdf.ts +30 -27
  253. package/src/slides/export/types.ts +10 -8
  254. package/src/slides/index.ts +11 -11
  255. package/src/slides/labels.ts +22 -19
  256. package/src/slides/layout.ts +9 -2
  257. package/src/slides/model.ts +4 -4
  258. package/src/slides/navigation.ts +43 -31
  259. package/src/slides/theme.css +5 -7
  260. package/src/slides/types.ts +9 -10
  261. package/src/status/define-statuses.ts +17 -24
  262. package/src/status/index.ts +5 -5
  263. package/src/status/status-badge.svelte +3 -1
  264. package/src/status/status-dot.svelte +1 -3
  265. package/src/status/status-icon.svelte +1 -1
  266. package/src/status/status-types.ts +21 -21
  267. package/src/tasks/board-order.ts +2 -4
  268. package/src/tasks/demo.svelte +189 -31
  269. package/src/tasks/detail/detail-demo.svelte +73 -13
  270. package/src/tasks/detail/detail-types.ts +45 -45
  271. package/src/tasks/detail/task-detail.svelte +40 -10
  272. package/src/tasks/detail/task-property.svelte +8 -2
  273. package/src/tasks/detail/task-thread.svelte +53 -12
  274. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  275. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  276. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  277. package/src/tasks/gantt/gantt-header.svelte +9 -3
  278. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  279. package/src/tasks/gantt/gantt.css +2 -1
  280. package/src/tasks/gantt/gantt.test.ts +3 -1
  281. package/src/tasks/gantt/logic.test.ts +110 -20
  282. package/src/tasks/gantt/logic.ts +42 -6
  283. package/src/tasks/inbox/inbox-card.svelte +13 -5
  284. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  285. package/src/tasks/inbox/inbox-logic.ts +6 -40
  286. package/src/tasks/inbox/inbox-statuses.ts +23 -23
  287. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  288. package/src/tasks/inbox/inbox-types.ts +54 -58
  289. package/src/tasks/inbox/index.ts +22 -21
  290. package/src/tasks/index.ts +49 -52
  291. package/src/tasks/kanban-board.svelte +16 -12
  292. package/src/tasks/kanban-types.ts +2 -3
  293. package/src/tasks/presets/sales.ts +6 -6
  294. package/src/tasks/task-gantt.svelte +28 -8
  295. package/src/tasks/task-list.svelte +41 -17
  296. package/src/tasks/time/duration.ts +40 -40
  297. package/src/tasks/time/time-chip.svelte +9 -2
  298. package/src/tasks/time/time-dot.svelte +1 -1
  299. package/src/tasks/time/time-ring.svelte +11 -3
  300. package/src/tasks/time/time-tracker.svelte +57 -15
  301. package/src/tasks/time/time-types.ts +57 -57
  302. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  303. package/src/thread/index.ts +7 -7
  304. package/src/thread/logic.ts +36 -33
  305. package/src/thread/thread.svelte +82 -16
  306. package/src/thread/thread.test.ts +58 -47
  307. package/src/thread/types.ts +68 -68
  308. package/src/tokens/craft.css +17 -53
  309. package/src/tokens/index.css +3 -13
  310. package/src/tokens/palette.css +13 -11
  311. package/src/tokens/primitives.css +10 -23
  312. package/src/tasks/inbox/inbox.svelte +0 -203
@@ -1,10 +1,5 @@
1
- /* Thinking states — transitions.dev 28.
2
- A status line that swaps copy while a model works. This file owns only the
3
- swap: JS exits the old line upward and enters the new one from below, held
4
- back by --think-gap. The sweep is `.t-shimmer` from shimmer-text.css — put
5
- both classes on the line and keep data-text in sync with the text, or the
6
- shimmer paints the wrong words. The hidden sizer carries your longest state
7
- and is what stops the box resizing mid-swap. */
1
+ /* Thinking states, transitions.dev 28: put `.t-shimmer` on the line too and
2
+ keep data-text in sync with the text, or the shimmer paints the wrong words. */
8
3
  :root {
9
4
  --think-hold: 2000ms;
10
5
  --think-swap: var(--duration-quick, 150ms);
@@ -1,7 +1,14 @@
1
1
  import { readFileSync, readdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { describe, expect, it } from "vite-plus/test";
4
- import { BRAND_COLOR_VARS, applyBrand, brandCss, brandStyle, brandVars, defineBrand } from "./index.js";
4
+ import {
5
+ BRAND_COLOR_VARS,
6
+ applyBrand,
7
+ brandCss,
8
+ brandStyle,
9
+ brandVars,
10
+ defineBrand,
11
+ } from "./index.js";
5
12
 
6
13
  const TOKENS = join(import.meta.dirname, "..", "tokens");
7
14
  const palette = readFileSync(join(TOKENS, "palette.css"), "utf8");
@@ -9,7 +16,10 @@ const palette = readFileSync(join(TOKENS, "palette.css"), "utf8");
9
16
  describe("brandVars", () => {
10
17
  it("sets only the roles a brand names", () => {
11
18
  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" });
19
+ expect(brandVars(brand)).toEqual({
20
+ "--brand": "oklch(0.5 0.2 272)",
21
+ "--brand-signal": "#f0503d",
22
+ });
13
23
  });
14
24
 
15
25
  it("maps fonts and extra properties", () => {
@@ -27,7 +37,9 @@ describe("brandVars", () => {
27
37
  });
28
38
 
29
39
  it("refuses a value that would break out of its declaration", () => {
30
- expect(() => brandVars({ colors: { brand: "red; } body { display: none" } })).toThrow(/--brand/);
40
+ expect(() => brandVars({ colors: { brand: "red; } body { display: none" } })).toThrow(
41
+ /--brand/,
42
+ );
31
43
  });
32
44
  });
33
45
 
@@ -36,7 +48,9 @@ describe("the three ways to apply one", () => {
36
48
 
37
49
  it("writes a style attribute and a rule", () => {
38
50
  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");
51
+ expect(brandCss(brand, ".client")).toBe(
52
+ ".client {\n --brand: blue;\n --brand-ink: black;\n}\n",
53
+ );
40
54
  });
41
55
 
42
56
  it("applies to an element and puts back what it found", () => {
@@ -61,7 +75,9 @@ describe("the tokens", () => {
61
75
  it("carry no client palette", () => {
62
76
  for (const file of readdirSync(TOKENS).filter((f) => f.endsWith(".css"))) {
63
77
  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/);
78
+ expect(css, file).not.toMatch(
79
+ /brand-(indigo|sand|terra|night|veil|dusk|cream|linen|mist)|--(indigo|sand|terra)-\d/,
80
+ );
65
81
  }
66
82
  });
67
83
  });
@@ -1,13 +1,6 @@
1
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.
2
+ * A brand replaces only the token roles it names. Put `brandStyle(brand)` on the server's
3
+ * `<html style>`, or the first paint shows the neutral look.
11
4
  */
12
5
 
13
6
  /** Any CSS colour: oklch(), hex, a named colour or a var(). */
@@ -86,7 +79,8 @@ export function defineBrand<const B extends Brand>(brand: B): B {
86
79
  }
87
80
 
88
81
  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`);
82
+ if (/[;{}<>]/.test(value))
83
+ throw new Error(`brand: ${name} holds a character CSS would read as more than a value`);
90
84
  return value;
91
85
  }
92
86
 
@@ -1,47 +1,47 @@
1
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";
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
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.
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
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
- }
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
26
 
27
- // =========================
28
- // PROPS
29
- // =========================
30
- let { id, data, selected = false, resize, children }: Props = $props();
27
+ // =========================
28
+ // PROPS
29
+ // =========================
30
+ let { id, data, selected = false, resize, children }: Props = $props();
31
31
 
32
- // =========================
33
- // STATE
34
- // =========================
35
- const editor = getDiagramEditorContext();
36
- let frame = $state<HTMLElement>();
32
+ // =========================
33
+ // STATE
34
+ // =========================
35
+ const editor = getDiagramEditorContext();
36
+ let frame = $state<HTMLElement>();
37
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 }));
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
45
  </script>
46
46
 
47
47
  <!-- =========================== -->
@@ -53,7 +53,11 @@ let grips = $derived(!!resize && !!editor && selected && !isLocked({ data }));
53
53
  {/if}
54
54
 
55
55
  {#if framed}
56
- <div class="cv-card-frame" bind:this={frame} style:--cv-card-h={floor > 0 ? `${floor}px` : undefined}>
56
+ <div
57
+ class="cv-card-frame"
58
+ bind:this={frame}
59
+ style:--cv-card-h={floor > 0 ? `${floor}px` : undefined}
60
+ >
57
61
  {@render children()}
58
62
  </div>
59
63
  {:else}
@@ -1,98 +1,103 @@
1
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";
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
15
 
16
- // The grips of a selected card. One drag is one undo step: width, kept height and extra data close together.
16
+ // The grips of a selected card. One drag is one undo step: width, kept height and extra data close together.
17
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
- }
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
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;
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
34
 
35
- // =========================
36
- // PROPS
37
- // =========================
38
- let { id, resize, frame }: Props = $props();
35
+ // =========================
36
+ // PROPS
37
+ // =========================
38
+ let { id, resize, frame }: Props = $props();
39
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);
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
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));
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
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
- }
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
69
 
70
- function start() {
71
- endGesture = editor?.startGesture() ?? null;
72
- if (both && !box) measureContent();
73
- }
70
+ function start() {
71
+ endGesture = editor?.startGesture() ?? null;
72
+ if (both && !box) measureContent();
73
+ }
74
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 });
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> = {
81
+ ...resize.extraData?.({ width, height: size.height }),
82
+ };
83
+ // A text card keeps growing with its words, and an image's height follows its width.
84
+ if (both) patch.userHeight = keptHeight(size.height, contentHeight);
85
+ if (Object.keys(patch).length > 0) editor?.patchNodeData(id, patch);
86
+ // The drag gave the node a height; the content and the saved floor size it from here.
87
+ updateNode(id, { height: undefined });
88
+ }
89
+ endGesture?.();
90
+ endGesture = null;
86
91
  }
87
- endGesture?.();
88
- endGesture = null;
89
- }
90
92
  </script>
91
93
 
92
94
  <!-- =========================== -->
93
95
  <!-- MARKUP -->
94
96
  <!-- =========================== -->
95
- {#snippet control(position: (typeof CORNERS)[number] | (typeof EDGES)[number], variant: ResizeControlVariant)}
97
+ {#snippet control(
98
+ position: (typeof CORNERS)[number] | (typeof EDGES)[number],
99
+ variant: ResizeControlVariant,
100
+ )}
96
101
  <NodeResizeControl
97
102
  nodeId={id}
98
103
  {position}
@@ -102,7 +107,11 @@ function finish(size: { width: number; height: number }) {
102
107
  {minHeight}
103
108
  keepAspectRatio={ratio}
104
109
  resizeDirection={direction}
105
- class={variant === ResizeControlVariant.Line ? "cv-edge" : direction ? "cv-grip cv-grip-x" : "cv-grip"}
110
+ class={variant === ResizeControlVariant.Line
111
+ ? "cv-edge"
112
+ : direction
113
+ ? "cv-grip cv-grip-x"
114
+ : "cv-grip"}
106
115
  onResizeStart={start}
107
116
  onResizeEnd={(_, size) => finish(size)}
108
117
  />
@@ -1,120 +1,121 @@
1
1
  <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import { useConnection, useStore } from "@xyflow/svelte";
6
- import { untrack } from "svelte";
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { useConnection, useStore } from "@xyflow/svelte";
6
+ import { untrack } from "svelte";
7
7
 
8
- // A root `--canvas-unzoom` restyled every card per zoom frame. It lives only on nodes showing chrome: selected, hovered, or all while a link is dragged.
8
+ // A root `--canvas-unzoom` restyled every card per zoom frame. It lives only on nodes showing chrome: selected, hovered, or all while a link is dragged.
9
9
 
10
- // =========================
11
- // CONSTANTS
12
- // =========================
13
- const MIN_ZOOM = 0.05;
14
- const NODE = ".svelte-flow__node";
10
+ // =========================
11
+ // CONSTANTS
12
+ // =========================
13
+ const MIN_ZOOM = 0.05;
14
+ const NODE = ".svelte-flow__node";
15
15
 
16
- // =========================
17
- // STATE
18
- // =========================
19
- const store = useStore();
20
- const connection = useConnection();
16
+ // =========================
17
+ // STATE
18
+ // =========================
19
+ const store = useStore();
20
+ const connection = useConnection();
21
21
 
22
- // Plain collections: they change on selection and hover, never per frame, and nothing reads them reactively.
23
- const selected = new Set<HTMLElement>();
24
- let hovered: HTMLElement | null = null;
22
+ // Plain collections: they change on selection and hover, never per frame, and nothing reads them reactively.
23
+ const selected = new Set<HTMLElement>();
24
+ let hovered: HTMLElement | null = null;
25
25
 
26
- // =========================
27
- // DERIVED
28
- // =========================
29
- let unzoom = $derived(String(1 / Math.max(store.viewport.zoom, MIN_ZOOM)));
30
- let linking = $derived(connection.current.inProgress);
26
+ // =========================
27
+ // DERIVED
28
+ // =========================
29
+ let unzoom = $derived(String(1 / Math.max(store.viewport.zoom, MIN_ZOOM)));
30
+ let linking = $derived(connection.current.inProgress);
31
31
 
32
- // =========================
33
- // FUNCTIONS
34
- // =========================
35
- const paint = (node: HTMLElement | null, value = unzoom) => node?.style.setProperty("--canvas-unzoom", value);
32
+ // =========================
33
+ // FUNCTIONS
34
+ // =========================
35
+ const paint = (node: HTMLElement | null, value = unzoom) =>
36
+ node?.style.setProperty("--canvas-unzoom", value);
36
37
 
37
- function paintShowing(value: string) {
38
- for (const node of selected) {
39
- if (node.isConnected) paint(node, value);
40
- else selected.delete(node);
41
- }
42
- paint(hovered, value);
43
- }
44
-
45
- // Chrome that belongs to one card (grips, quick-add) hides while several are selected: see chrome.css.
46
- function flagMulti() {
47
- store.domNode?.toggleAttribute("data-cv-multi", selected.size > 1);
48
- }
49
-
50
- function track(node: HTMLElement) {
51
- if (node.classList.contains("selected")) {
52
- if (!selected.has(node)) {
53
- selected.add(node);
54
- paint(node);
38
+ function paintShowing(value: string) {
39
+ for (const node of selected) {
40
+ if (node.isConnected) paint(node, value);
41
+ else selected.delete(node);
55
42
  }
56
- } else selected.delete(node);
57
- flagMulti();
58
- }
43
+ paint(hovered, value);
44
+ }
59
45
 
60
- // =========================
61
- // EFFECTS
62
- // =========================
63
- $effect(() => {
64
- paintShowing(unzoom);
65
- });
46
+ // Chrome that belongs to one card (grips, quick-add) hides while several are selected: see chrome.css.
47
+ function flagMulti() {
48
+ store.domNode?.toggleAttribute("data-cv-multi", selected.size > 1);
49
+ }
66
50
 
67
- $effect(() => {
68
- const root = store.domNode;
69
- if (!root) return;
70
- if (linking) {
71
- root.dataset.cvLinking = "";
72
- const value = unzoom;
73
- for (const node of root.querySelectorAll<HTMLElement>(NODE)) paint(node, value);
74
- return () => delete root.dataset.cvLinking;
51
+ function track(node: HTMLElement) {
52
+ if (node.classList.contains("selected")) {
53
+ if (!selected.has(node)) {
54
+ selected.add(node);
55
+ paint(node);
56
+ }
57
+ } else selected.delete(node);
58
+ flagMulti();
75
59
  }
76
- });
77
60
 
78
- function watch(root: HTMLElement) {
79
- const over = (event: PointerEvent) => {
80
- const node = (event.target as Element | null)?.closest<HTMLElement>(NODE) ?? null;
81
- if (node === hovered) return;
82
- hovered = node;
83
- paint(node);
84
- };
85
- root.addEventListener("pointerover", over, { passive: true });
61
+ // =========================
62
+ // EFFECTS
63
+ // =========================
64
+ $effect(() => {
65
+ paintShowing(unzoom);
66
+ });
86
67
 
87
- // A node picks up or loses the `selected` class when the selection changes, and arrives selected when pasted or created.
88
- for (const node of root.querySelectorAll<HTMLElement>(`${NODE}.selected`)) track(node);
89
- const classes = new MutationObserver((records) => {
90
- for (const record of records) {
91
- const node = record.target as HTMLElement;
92
- if (node.matches(NODE)) track(node);
68
+ $effect(() => {
69
+ const root = store.domNode;
70
+ if (!root) return;
71
+ if (linking) {
72
+ root.dataset.cvLinking = "";
73
+ const value = unzoom;
74
+ for (const node of root.querySelectorAll<HTMLElement>(NODE)) paint(node, value);
75
+ return () => delete root.dataset.cvLinking;
93
76
  }
94
77
  });
95
- classes.observe(root, { attributes: true, attributeFilter: ["class"], subtree: true });
96
- const arrivals = new MutationObserver((records) => {
97
- for (const record of records) {
98
- for (const added of record.addedNodes) {
99
- if (added instanceof HTMLElement && added.matches(NODE)) track(added);
78
+
79
+ function watch(root: HTMLElement) {
80
+ const over = (event: PointerEvent) => {
81
+ const node = (event.target as Element | null)?.closest<HTMLElement>(NODE) ?? null;
82
+ if (node === hovered) return;
83
+ hovered = node;
84
+ paint(node);
85
+ };
86
+ root.addEventListener("pointerover", over, { passive: true });
87
+
88
+ // A node picks up or loses the `selected` class when the selection changes, and arrives selected when pasted or created.
89
+ for (const node of root.querySelectorAll<HTMLElement>(`${NODE}.selected`)) track(node);
90
+ const classes = new MutationObserver((records) => {
91
+ for (const record of records) {
92
+ const node = record.target as HTMLElement;
93
+ if (node.matches(NODE)) track(node);
100
94
  }
101
- }
102
- });
103
- const layer = root.querySelector(".svelte-flow__nodes");
104
- if (layer) arrivals.observe(layer, { childList: true });
95
+ });
96
+ classes.observe(root, { attributes: true, attributeFilter: ["class"], subtree: true });
97
+ const arrivals = new MutationObserver((records) => {
98
+ for (const record of records) {
99
+ for (const added of record.addedNodes) {
100
+ if (added instanceof HTMLElement && added.matches(NODE)) track(added);
101
+ }
102
+ }
103
+ });
104
+ const layer = root.querySelector(".svelte-flow__nodes");
105
+ if (layer) arrivals.observe(layer, { childList: true });
105
106
 
106
- return () => {
107
- root.removeEventListener("pointerover", over);
108
- classes.disconnect();
109
- arrivals.disconnect();
110
- selected.clear();
111
- root.removeAttribute("data-cv-multi");
112
- };
113
- }
107
+ return () => {
108
+ root.removeEventListener("pointerover", over);
109
+ classes.disconnect();
110
+ arrivals.disconnect();
111
+ selected.clear();
112
+ root.removeAttribute("data-cv-multi");
113
+ };
114
+ }
114
115
 
115
- $effect(() => {
116
- const root = store.domNode;
117
- if (!root) return;
118
- return untrack(() => watch(root));
119
- });
116
+ $effect(() => {
117
+ const root = store.domNode;
118
+ if (!root) return;
119
+ return untrack(() => watch(root));
120
+ });
120
121
  </script>
@@ -1,13 +1,13 @@
1
1
  <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import type { Snippet } from "svelte";
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
6
 
7
- // =========================
8
- // PROPS
9
- // =========================
10
- let { label, children }: { label: string; children: Snippet } = $props();
7
+ // =========================
8
+ // PROPS
9
+ // =========================
10
+ let { label, children }: { label: string; children: Snippet } = $props();
11
11
  </script>
12
12
 
13
13
  <!-- =========================== -->