@heyramzi/ui 1.54.2 → 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 (361) hide show
  1. package/COMPONENTS.md +3 -2
  2. package/README.md +34 -9
  3. package/package.json +198 -6
  4. package/src/ai/generation-progress/GenerationProgress.svelte +12 -12
  5. package/src/ambient.d.ts +8 -0
  6. package/src/brand/brand.test.ts +67 -0
  7. package/src/brand/index.ts +137 -0
  8. package/src/canvas/README.md +236 -0
  9. package/src/canvas/card/card-frame.svelte +61 -0
  10. package/src/canvas/card/card.css +39 -0
  11. package/src/canvas/card/resize-handles.svelte +119 -0
  12. package/src/canvas/card/resize.ts +33 -0
  13. package/src/canvas/chrome/chrome-zoom.svelte +120 -0
  14. package/src/canvas/chrome/chrome.css +231 -0
  15. package/src/canvas/chrome/lock-badge.svelte +18 -0
  16. package/src/canvas/chrome/side-handles.svelte +30 -0
  17. package/src/canvas/chrome/size-chip.svelte +45 -0
  18. package/src/canvas/chrome/snap-guides.svelte +69 -0
  19. package/src/canvas/components/icons/ToolIcon.svelte +30 -0
  20. package/src/canvas/components/shared/svg/PreviewSvg.svelte +67 -0
  21. package/src/canvas/components/shared/svg/ShapeOutline.svelte +76 -0
  22. package/src/canvas/components/shared/svg/StrokeNode.svelte +69 -0
  23. package/src/canvas/components/shared/svg/TextRule.svelte +37 -0
  24. package/src/canvas/edges/bend-handle.svelte +76 -0
  25. package/src/canvas/edges/edge-gradient.svelte +30 -0
  26. package/src/canvas/edges/edge-path.test.ts +58 -0
  27. package/src/canvas/edges/edge-path.ts +293 -0
  28. package/src/canvas/edges/gradient-edge.svelte +43 -0
  29. package/src/canvas/editor/context-menu.test.ts +65 -0
  30. package/src/canvas/editor/context-menu.ts +48 -0
  31. package/src/canvas/editor/dispatch.svelte.test.ts +243 -0
  32. package/src/canvas/editor/dispatch.svelte.ts +205 -0
  33. package/src/canvas/editor/editor-context.ts +62 -0
  34. package/src/canvas/editor/editor-mode.ts +67 -0
  35. package/src/canvas/editor/frame.ts +98 -0
  36. package/src/canvas/editor/hand-context.ts +15 -0
  37. package/src/canvas/editor/history-core.test.ts +239 -0
  38. package/src/canvas/editor/history-core.ts +247 -0
  39. package/src/canvas/editor/keymap.test.ts +161 -0
  40. package/src/canvas/editor/keymap.ts +151 -0
  41. package/src/canvas/editor/layout-actions.test.ts +156 -0
  42. package/src/canvas/editor/layout-actions.ts +219 -0
  43. package/src/canvas/editor/lock.test.ts +68 -0
  44. package/src/canvas/editor/lock.ts +87 -0
  45. package/src/canvas/editor/rebase.test.ts +208 -0
  46. package/src/canvas/editor/rebase.ts +272 -0
  47. package/src/canvas/editor/style.ts +184 -0
  48. package/src/canvas/editor/text-field-context.ts +41 -0
  49. package/src/canvas/editor/z-order.test.ts +75 -0
  50. package/src/canvas/editor/z-order.ts +87 -0
  51. package/src/canvas/headless/alignment.test.ts +59 -0
  52. package/src/canvas/headless/alignment.ts +23 -0
  53. package/src/canvas/headless/break-apart-find.ts +418 -0
  54. package/src/canvas/headless/break-apart-pieces.ts +104 -0
  55. package/src/canvas/headless/break-apart-plan.ts +439 -0
  56. package/src/canvas/headless/break-apart-style.ts +106 -0
  57. package/src/canvas/headless/break-apart.ts +244 -0
  58. package/src/canvas/headless/canvas-cleanup.test.ts +181 -0
  59. package/src/canvas/headless/canvas-cleanup.ts +102 -0
  60. package/src/canvas/headless/edge-anchors.test.ts +70 -0
  61. package/src/canvas/headless/edge-anchors.ts +143 -0
  62. package/src/canvas/headless/edge-memo.test.ts +58 -0
  63. package/src/canvas/headless/edge-memo.ts +32 -0
  64. package/src/canvas/headless/edge-serialization.test.ts +55 -0
  65. package/src/canvas/headless/edge-serialization.ts +32 -0
  66. package/src/canvas/headless/figure-geometry.ts +293 -0
  67. package/src/canvas/headless/fit-viewport.test.ts +85 -0
  68. package/src/canvas/headless/fit-viewport.ts +137 -0
  69. package/src/canvas/headless/grid.test.ts +34 -0
  70. package/src/canvas/headless/grid.ts +94 -0
  71. package/src/canvas/headless/icon-node.test.ts +23 -0
  72. package/src/canvas/headless/icon-node.ts +66 -0
  73. package/src/canvas/headless/index.ts +9 -0
  74. package/src/canvas/headless/linkify.test.ts +23 -0
  75. package/src/canvas/headless/linkify.ts +25 -0
  76. package/src/canvas/headless/pen-marks.ts +133 -0
  77. package/src/canvas/headless/sketch.test.ts +99 -0
  78. package/src/canvas/headless/sketch.ts +302 -0
  79. package/src/canvas/headless/snap-guides.test.ts +61 -0
  80. package/src/canvas/headless/snap-guides.ts +146 -0
  81. package/src/canvas/headless/svg-figure.test.ts +49 -0
  82. package/src/canvas/headless/svg-figure.ts +176 -0
  83. package/src/canvas/headless/table-layout.test.ts +158 -0
  84. package/src/canvas/headless/table-layout.ts +309 -0
  85. package/src/canvas/index.ts +35 -0
  86. package/src/canvas/lod/lod-mode.svelte.ts +30 -0
  87. package/src/canvas/lod/lod-switch.svelte +140 -0
  88. package/src/canvas/lod/render-all.svelte.ts +52 -0
  89. package/src/canvas/ops/arrange.ts +108 -0
  90. package/src/canvas/ops/ops.test.ts +98 -0
  91. package/src/canvas/ops/ops.ts +347 -0
  92. package/src/canvas/pen/PenField.svelte +128 -0
  93. package/src/canvas/pen/PenField.test.ts +87 -0
  94. package/src/canvas/pen/PenOverlay.svelte +172 -0
  95. package/src/canvas/pen/ShapeNode.svelte +64 -0
  96. package/src/canvas/pen/TextNode.svelte +97 -0
  97. package/src/canvas/pen/colors.ts +28 -0
  98. package/src/canvas/pen/eraser.ts +46 -0
  99. package/src/canvas/pen/geometry.ts +148 -0
  100. package/src/canvas/pen/host-field.test.svelte +13 -0
  101. package/src/canvas/pen/index.ts +40 -0
  102. package/src/canvas/pen/ops.ts +16 -0
  103. package/src/canvas/pen/pen-session.ts +177 -0
  104. package/src/canvas/pen/pen.css +149 -0
  105. package/src/canvas/pen/plugin.ts +139 -0
  106. package/src/canvas/pen/shapes.ts +104 -0
  107. package/src/canvas/pen/stroke-path.ts +54 -0
  108. package/src/canvas/pen/types.ts +96 -0
  109. package/src/canvas/pen/ui.ts +26 -0
  110. package/src/canvas/plugin.test.ts +45 -0
  111. package/src/canvas/plugin.ts +159 -0
  112. package/src/canvas/screen/GlyphIcon.svelte +25 -0
  113. package/src/canvas/screen/PartView.svelte +176 -0
  114. package/src/canvas/screen/ScreenBody.svelte +142 -0
  115. package/src/canvas/screen/ScreenCard.svelte +32 -0
  116. package/src/canvas/screen/glyphs/button.svg +1 -0
  117. package/src/canvas/screen/glyphs/close.svg +1 -0
  118. package/src/canvas/screen/glyphs/down.svg +1 -0
  119. package/src/canvas/screen/glyphs/header.svg +1 -0
  120. package/src/canvas/screen/glyphs/hills.svg +1 -0
  121. package/src/canvas/screen/glyphs/image.svg +1 -0
  122. package/src/canvas/screen/glyphs/input.svg +1 -0
  123. package/src/canvas/screen/glyphs/list.svg +1 -0
  124. package/src/canvas/screen/glyphs/modal.svg +1 -0
  125. package/src/canvas/screen/glyphs/plus.svg +1 -0
  126. package/src/canvas/screen/glyphs/up.svg +1 -0
  127. package/src/canvas/screen/glyphs.ts +40 -0
  128. package/src/canvas/screen/labels.ts +41 -0
  129. package/src/canvas/screen/parts.ts +164 -0
  130. package/src/canvas/screen/plugin.ts +67 -0
  131. package/src/canvas/screen/screen.css +593 -0
  132. package/src/canvas/screen/types.ts +47 -0
  133. package/src/canvas/screen/ui.ts +13 -0
  134. package/src/canvas/test-utils.ts +18 -0
  135. package/src/canvas/types.ts +53 -0
  136. package/src/canvas/ui/CanvasToolbar.svelte +169 -0
  137. package/src/canvas/ui/FrameLayer.svelte +116 -0
  138. package/src/canvas/ui/PenLayer.svelte +39 -0
  139. package/src/canvas/ui/StylePanel.svelte +268 -0
  140. package/src/canvas/ui/canvas-grid.svelte +156 -0
  141. package/src/canvas/ui/canvas-ui.css +350 -0
  142. package/src/canvas/ui/forward-wheel.ts +29 -0
  143. package/src/canvas/ui/icons.ts +124 -0
  144. package/src/canvas/ui/place-panel.ts +80 -0
  145. package/src/canvas/ui/shortcut-tip.test.ts +16 -0
  146. package/src/canvas/ui/shortcut-tip.ts +17 -0
  147. package/src/canvas/ui/tools.ts +146 -0
  148. package/src/charts/README.md +104 -0
  149. package/src/charts/ambient.d.ts +4 -0
  150. package/src/charts/animated-value.css +7 -0
  151. package/src/charts/animated-value.svelte +110 -0
  152. package/src/charts/area-chart.css +29 -0
  153. package/src/charts/area-chart.svelte +204 -0
  154. package/src/charts/bar-chart.css +119 -0
  155. package/src/charts/bar-chart.svelte +275 -0
  156. package/src/charts/charts.css +201 -0
  157. package/src/charts/crosshair.svelte +35 -0
  158. package/src/charts/dashboard/chart.ts +5 -0
  159. package/src/charts/dashboard/dashboard-empty.svelte +55 -0
  160. package/src/charts/dashboard/dashboard-section.svelte +58 -0
  161. package/src/charts/dashboard/data-panel.svelte +81 -0
  162. package/src/charts/dashboard/data-sort-head.svelte +102 -0
  163. package/src/charts/dashboard/data-table.svelte +278 -0
  164. package/src/charts/dashboard/data-table.ts +26 -0
  165. package/src/charts/dashboard/index.ts +25 -0
  166. package/src/charts/dashboard/panel-grid.svelte +43 -0
  167. package/src/charts/dashboard/period-filter.svelte +53 -0
  168. package/src/charts/dashboard/rating-chip.svelte +35 -0
  169. package/src/charts/dashboard/stat-band.svelte +47 -0
  170. package/src/charts/dashboard/stat-tile.svelte +176 -0
  171. package/src/charts/dashboard/windows.ts +8 -0
  172. package/src/charts/donut-chart.css +56 -0
  173. package/src/charts/donut-chart.svelte +158 -0
  174. package/src/charts/edge-fade.svelte +43 -0
  175. package/src/charts/edge-fade.ts +16 -0
  176. package/src/charts/figure.css +100 -0
  177. package/src/charts/figure.svelte +122 -0
  178. package/src/charts/gauge-chart.css +94 -0
  179. package/src/charts/gauge-chart.svelte +302 -0
  180. package/src/charts/hover-band.test.ts +30 -0
  181. package/src/charts/hover-band.ts +21 -0
  182. package/src/charts/hover-focus.svelte +85 -0
  183. package/src/charts/index.ts +31 -0
  184. package/src/charts/legend.css +136 -0
  185. package/src/charts/legend.svelte +112 -0
  186. package/src/charts/line-chart.css +26 -0
  187. package/src/charts/line-chart.svelte +256 -0
  188. package/src/charts/motion.ts +19 -0
  189. package/src/charts/number-chart.css +179 -0
  190. package/src/charts/number-chart.svelte +187 -0
  191. package/src/charts/primitives/chart-container.svelte +57 -0
  192. package/src/charts/primitives/chart-style.svelte +36 -0
  193. package/src/charts/primitives/chart-tooltip.svelte +161 -0
  194. package/src/charts/primitives/chart-utils.ts +54 -0
  195. package/src/charts/primitives/index.ts +11 -0
  196. package/src/charts/reference-line.css +18 -0
  197. package/src/charts/reference-line.svelte +31 -0
  198. package/src/charts/reveal.svelte +61 -0
  199. package/src/charts/ring-chart.css +104 -0
  200. package/src/charts/ring-chart.svelte +166 -0
  201. package/src/charts/sparkline.css +45 -0
  202. package/src/charts/sparkline.svelte +141 -0
  203. package/src/charts/stat-card.css +105 -0
  204. package/src/charts/stat-card.svelte +267 -0
  205. package/src/charts/time-series-chart.css +28 -0
  206. package/src/charts/time-series-chart.svelte +179 -0
  207. package/src/charts/trend-pill.css +38 -0
  208. package/src/charts/trend-pill.svelte +48 -0
  209. package/src/components/copy/copy-button.svelte +1 -1
  210. package/src/components/docs/doc-page.svelte +1 -1
  211. package/src/components/docs/on-this-page.svelte +2 -2
  212. package/src/components/list-page/empty-state.svelte +1 -1
  213. package/src/components/list-page/filter-popover.svelte +1 -1
  214. package/src/components/list-page/section-label.svelte +1 -1
  215. package/src/components/segmented/segment.svelte +4 -4
  216. package/src/components/theme/theme-provider.svelte +6 -4
  217. package/src/components/toolbar/index.ts +5 -0
  218. package/src/components/toolbar/toolbar.svelte +168 -0
  219. package/src/components/toolbar/toolbar.test.ts +66 -0
  220. package/src/editor/README.md +58 -0
  221. package/src/editor/ambient.d.ts +1 -0
  222. package/src/editor/block-id.ts +77 -0
  223. package/src/editor/block-ops.test.ts +237 -0
  224. package/src/editor/block-ops.ts +157 -0
  225. package/src/editor/content.css +418 -0
  226. package/src/editor/editor-toolbar.svelte +303 -0
  227. package/src/editor/editor.svelte +462 -0
  228. package/src/editor/extensions.ts +145 -0
  229. package/src/editor/image-toolbar.svelte +123 -0
  230. package/src/editor/index.ts +17 -0
  231. package/src/editor/inline-editor.svelte +261 -0
  232. package/src/editor/markdown-escaping.test.ts +158 -0
  233. package/src/editor/markdown-marks.test.ts +89 -0
  234. package/src/editor/markdown-test-helpers.ts +13 -0
  235. package/src/editor/markdown-youtube.test.ts +93 -0
  236. package/src/editor/markdown.test.ts +250 -0
  237. package/src/editor/markdown.ts +511 -0
  238. package/src/editor/renderer.svelte +92 -0
  239. package/src/editor/serializer.test.ts +133 -0
  240. package/src/editor/serializer.ts +370 -0
  241. package/src/editor/slash-items.ts +115 -0
  242. package/src/editor/slash-menu.svelte +159 -0
  243. package/src/editor/youtube-url.ts +35 -0
  244. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +13 -13
  245. package/src/lib/result.ts +19 -0
  246. package/src/onboarding/README.md +120 -0
  247. package/src/onboarding/define-tours.test.ts +50 -0
  248. package/src/onboarding/define-tours.ts +31 -0
  249. package/src/onboarding/demo.svelte +86 -0
  250. package/src/onboarding/index.ts +12 -0
  251. package/src/onboarding/onboarding-provider.svelte +120 -0
  252. package/src/onboarding/onboarding.svelte.ts +24 -0
  253. package/src/onboarding/pick.test.ts +79 -0
  254. package/src/onboarding/pick.ts +36 -0
  255. package/src/onboarding/stores.test.ts +103 -0
  256. package/src/onboarding/stores.ts +58 -0
  257. package/src/onboarding/tour-card.svelte +70 -0
  258. package/src/onboarding/tour-runner.svelte +197 -0
  259. package/src/onboarding/types.ts +34 -0
  260. package/src/onboarding/wait.ts +29 -0
  261. package/src/shadcn/command/command-input.svelte +1 -1
  262. package/src/slides/Controls.svelte +138 -0
  263. package/src/slides/Deck.svelte +271 -0
  264. package/src/slides/Fragment.svelte +71 -0
  265. package/src/slides/Overview.svelte +113 -0
  266. package/src/slides/Presenter.svelte +292 -0
  267. package/src/slides/README.md +89 -0
  268. package/src/slides/Slide.svelte +179 -0
  269. package/src/slides/SlideList.svelte +19 -0
  270. package/src/slides/ambient.d.ts +1 -0
  271. package/src/slides/bridges.ts +69 -0
  272. package/src/slides/deck-controller.svelte.ts +292 -0
  273. package/src/slides/demo-slides.svelte +358 -0
  274. package/src/slides/demo.svelte +176 -0
  275. package/src/slides/export/capture.ts +113 -0
  276. package/src/slides/export/ffmpeg.ts +120 -0
  277. package/src/slides/export/index.ts +9 -0
  278. package/src/slides/export/launch.ts +10 -0
  279. package/src/slides/export/pdf.ts +152 -0
  280. package/src/slides/export/types.ts +48 -0
  281. package/src/slides/index.ts +19 -0
  282. package/src/slides/labels.ts +48 -0
  283. package/src/slides/layout.ts +51 -0
  284. package/src/slides/model.ts +47 -0
  285. package/src/slides/navigation.ts +100 -0
  286. package/src/slides/theme.css +205 -0
  287. package/src/slides/types.ts +86 -0
  288. package/src/tasks/README.md +312 -0
  289. package/src/tasks/ambient.d.ts +3 -0
  290. package/src/tasks/board-order.ts +137 -0
  291. package/src/tasks/dates.ts +31 -0
  292. package/src/tasks/define-statuses.ts +32 -0
  293. package/src/tasks/demo.svelte +389 -0
  294. package/src/tasks/detail/detail-demo.svelte +186 -0
  295. package/src/tasks/detail/detail-types.ts +54 -0
  296. package/src/tasks/detail/task-chip.svelte +28 -0
  297. package/src/tasks/detail/task-detail.svelte +315 -0
  298. package/src/tasks/detail/task-property.svelte +17 -0
  299. package/src/tasks/detail/task-thread.svelte +258 -0
  300. package/src/tasks/gantt/gantt-bar.svelte +243 -0
  301. package/src/tasks/gantt/gantt-grid.svelte +94 -0
  302. package/src/tasks/gantt/gantt-group-row.svelte +55 -0
  303. package/src/tasks/gantt/gantt-header.svelte +71 -0
  304. package/src/tasks/gantt/gantt-task-row.svelte +186 -0
  305. package/src/tasks/gantt/gantt.css +22 -0
  306. package/src/tasks/gantt/gantt.test.ts +77 -0
  307. package/src/tasks/gantt/logic.test.ts +163 -0
  308. package/src/tasks/gantt/logic.ts +248 -0
  309. package/src/tasks/inbox/inbox-card.svelte +283 -0
  310. package/src/tasks/inbox/inbox-draft.svelte +84 -0
  311. package/src/tasks/inbox/inbox-logic.ts +55 -0
  312. package/src/tasks/inbox/inbox-statuses.ts +48 -0
  313. package/src/tasks/inbox/inbox-trace.svelte +45 -0
  314. package/src/tasks/inbox/inbox-types.ts +108 -0
  315. package/src/tasks/inbox/inbox.svelte +203 -0
  316. package/src/tasks/inbox/index.ts +23 -0
  317. package/src/tasks/index.ts +85 -0
  318. package/src/tasks/kanban-board.svelte +561 -0
  319. package/src/tasks/kanban-column.svelte +117 -0
  320. package/src/tasks/kanban-drag.ts +38 -0
  321. package/src/tasks/kanban-types.ts +65 -0
  322. package/src/tasks/presets/sales.ts +16 -0
  323. package/src/tasks/record/demo-data.ts +286 -0
  324. package/src/tasks/record/field-section.svelte +53 -0
  325. package/src/tasks/record/fields.ts +142 -0
  326. package/src/tasks/record/format.ts +87 -0
  327. package/src/tasks/record/gutter.ts +2 -0
  328. package/src/tasks/record/index.ts +54 -0
  329. package/src/tasks/record/info-list.svelte +316 -0
  330. package/src/tasks/record/kinds.ts +42 -0
  331. package/src/tasks/record/logic.ts +324 -0
  332. package/src/tasks/record/next-up.svelte +70 -0
  333. package/src/tasks/record/project-list.svelte +133 -0
  334. package/src/tasks/record/record-demo.svelte +324 -0
  335. package/src/tasks/record/record-header.svelte +70 -0
  336. package/src/tasks/record/record-shell.svelte +138 -0
  337. package/src/tasks/record/record-stats.svelte +48 -0
  338. package/src/tasks/record/record-timeline.svelte +231 -0
  339. package/src/tasks/record/record.test.ts +350 -0
  340. package/src/tasks/record/steps-rail.svelte +474 -0
  341. package/src/tasks/record/types.ts +280 -0
  342. package/src/tasks/record/where-we-are.svelte +121 -0
  343. package/src/tasks/reorder.ts +19 -0
  344. package/src/tasks/side-panel-types.ts +16 -0
  345. package/src/tasks/side-panel.svelte +193 -0
  346. package/src/tasks/status-badge.svelte +63 -0
  347. package/src/tasks/status-dot.svelte +228 -0
  348. package/src/tasks/status-icon.svelte +30 -0
  349. package/src/tasks/status-types.ts +40 -0
  350. package/src/tasks/task-gantt.svelte +230 -0
  351. package/src/tasks/task-list.svelte +470 -0
  352. package/src/tasks/task-types.ts +33 -0
  353. package/src/tasks/time/duration.ts +69 -0
  354. package/src/tasks/time/time-chip.svelte +62 -0
  355. package/src/tasks/time/time-dot.svelte +58 -0
  356. package/src/tasks/time/time-ring.svelte +74 -0
  357. package/src/tasks/time/time-tracker.svelte +565 -0
  358. package/src/tasks/time/time-types.ts +74 -0
  359. package/src/tasks/use-kanban-dnd.svelte.ts +173 -0
  360. package/src/tokens/base.css +5 -5
  361. package/src/tokens/palette.css +115 -120
@@ -0,0 +1,41 @@
1
+ // The text field a host hands the pen. A plain textarea keeps the canvas MIT-clean; a host that
2
+ // formats text provides a component, set once with the page skin.
3
+
4
+ import { getContext, setContext, type Component } from "svelte";
5
+
6
+ /** What the pen asks of a text field. A host component takes these and may take more. */
7
+ export type TextFieldProps = {
8
+ value: string;
9
+ placeholder?: string;
10
+ ariaLabel?: string;
11
+ class?: string;
12
+ style?: string;
13
+ /** `live` is true while typing, so a run of keystrokes folds into one undo step. */
14
+ onCommit: (next: string, live: boolean) => void;
15
+ /** A click on the text opens it. A pen text that opens on double-click turns this off. */
16
+ clickToEdit?: boolean;
17
+ /** The field closed: focus left it or Escape. */
18
+ onExit?: () => void;
19
+ /** Shown, never opened: a locked card. */
20
+ readonly?: boolean;
21
+ };
22
+
23
+ /** What the pen calls on a field it holds a reference to. */
24
+ export type TextFieldHandle = {
25
+ edit: (point?: { x: number; y: number }) => void | Promise<void>;
26
+ };
27
+
28
+ export type TextFieldComponent = Component<TextFieldProps, TextFieldHandle>;
29
+
30
+ /** For a host that mounts a pen node with `mount(..., { context })`, as a test does. */
31
+ export const TEXT_FIELD_KEY = Symbol("canvas-text-field");
32
+
33
+ /** Call during component init. */
34
+ export function setTextField(component: TextFieldComponent): void {
35
+ setContext(TEXT_FIELD_KEY, component);
36
+ }
37
+
38
+ /** The host's field, or null: the pen then draws its own textarea. */
39
+ export function useTextField(): TextFieldComponent | null {
40
+ return getContext<TextFieldComponent | undefined>(TEXT_FIELD_KEY) ?? null;
41
+ }
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import type { Node } from "@xyflow/svelte";
3
+ import { reorderStack, topLayer } from "./z-order.js";
4
+
5
+ function card(id: string, extra: Partial<Node> = {}): Node {
6
+ return { id, type: "note", position: { x: 0, y: 0 }, data: {}, ...extra };
7
+ }
8
+
9
+ /** Ids bottom to top, as the canvas paints them. */
10
+ function stack(nodes: Node[], parentId?: string): string[] {
11
+ return nodes
12
+ .map((node, index) => ({ node, index }))
13
+ .filter(({ node }) => (node.parentId ?? "") === (parentId ?? ""))
14
+ .sort((a, b) => (a.node.zIndex ?? 0) - (b.node.zIndex ?? 0) || a.index - b.index)
15
+ .map(({ node }) => node.id);
16
+ }
17
+
18
+ describe("reorderStack", () => {
19
+ const board = [card("a"), card("b"), card("c"), card("d")];
20
+
21
+ it("brings one card to the front and sends one to the back", () => {
22
+ expect(stack(reorderStack(board, new Set(["a"]), "front") ?? board)).toEqual([
23
+ "b",
24
+ "c",
25
+ "d",
26
+ "a",
27
+ ]);
28
+ expect(stack(reorderStack(board, new Set(["d"]), "back") ?? board)).toEqual([
29
+ "d",
30
+ "a",
31
+ "b",
32
+ "c",
33
+ ]);
34
+ });
35
+
36
+ it("moves one step and keeps picked cards in their order", () => {
37
+ const up = reorderStack(board, new Set(["a", "c"]), "forward") ?? board;
38
+ expect(stack(up)).toEqual(["b", "a", "d", "c"]);
39
+ const down = reorderStack(board, new Set(["b", "d"]), "backward") ?? board;
40
+ expect(stack(down)).toEqual(["b", "a", "d", "c"]);
41
+ });
42
+
43
+ it("returns null when nothing would change", () => {
44
+ expect(reorderStack(board, new Set(["d"]), "front")).toBeNull();
45
+ expect(reorderStack(board, new Set(["a"]), "back")).toBeNull();
46
+ expect(reorderStack(board, new Set(["d"]), "forward")).toBeNull();
47
+ });
48
+
49
+ it("writes only the layers that changed", () => {
50
+ const next = reorderStack(board, new Set(["a"]), "front") ?? board;
51
+ expect(next.find((n) => n.id === "b")?.zIndex).toBeUndefined();
52
+ expect(next.find((n) => n.id === "a")?.zIndex).toBe(3);
53
+ });
54
+
55
+ it("stacks children inside their own group and leaves ink out", () => {
56
+ const nodes = [
57
+ card("g", { type: "wrapper" }),
58
+ card("x", { parentId: "g" }),
59
+ card("y", { parentId: "g" }),
60
+ card("ink", { type: "stroke" }),
61
+ ];
62
+ const next = reorderStack(nodes, new Set(["x"]), "front") ?? nodes;
63
+ expect(stack(next, "g")).toEqual(["y", "x"]);
64
+ expect(next.find((n) => n.id === "g")?.zIndex).toBeUndefined();
65
+ expect(reorderStack(nodes, new Set(["ink"]), "front")).toBeNull();
66
+ });
67
+ });
68
+
69
+ describe("topLayer", () => {
70
+ it("is undefined until a card has been raised, then one above the top", () => {
71
+ expect(topLayer([card("a"), card("b")])).toBeUndefined();
72
+ expect(topLayer([card("a", { zIndex: 4 }), card("b")])).toBe(5);
73
+ expect(topLayer([card("g"), card("x", { parentId: "g", zIndex: 9 })])).toBeUndefined();
74
+ });
75
+ });
@@ -0,0 +1,87 @@
1
+ // Stacking order for cards: pure functions over nodes. A card's place is its `zIndex` (missing
2
+ // is 0), and array order breaks a tie, as xyflow paints equal layers.
3
+
4
+ import type { Node } from "@xyflow/svelte";
5
+
6
+ export type StackMove = "forward" | "backward" | "front" | "back";
7
+
8
+ /** Ink is an annotation layer pinned above every card by pen.css, so it has no place in a stack. */
9
+ export const INK_TYPES: ReadonlySet<string> = new Set(["stroke", "text", "shape"]);
10
+
11
+ type Slot = { node: Node; index: number };
12
+
13
+ function layerOf(node: Node): number {
14
+ return typeof node.zIndex === "number" ? node.zIndex : 0;
15
+ }
16
+
17
+ /** Cards stack among their siblings: a child never leaves its group's own stack. */
18
+ function stacks(nodes: readonly Node[]): Map<string, Slot[]> {
19
+ const byParent = new Map<string, Slot[]>();
20
+ nodes.forEach((node, index) => {
21
+ if (INK_TYPES.has(node.type ?? "")) return;
22
+ const key = node.parentId ?? "";
23
+ const stack = byParent.get(key) ?? [];
24
+ stack.push({ node, index });
25
+ byParent.set(key, stack);
26
+ });
27
+ for (const stack of byParent.values()) {
28
+ stack.sort((a, b) => layerOf(a.node) - layerOf(b.node) || a.index - b.index);
29
+ }
30
+ return byParent;
31
+ }
32
+
33
+ /** One stack after a move, bottom first. Selected cards keep their order among themselves. */
34
+ function moved(stack: Slot[], picked: ReadonlySet<string>, move: StackMove): Slot[] {
35
+ const isPicked = (slot: Slot) => picked.has(slot.node.id);
36
+ if (move === "front") return [...stack.filter((s) => !isPicked(s)), ...stack.filter(isPicked)];
37
+ if (move === "back") return [...stack.filter(isPicked), ...stack.filter((s) => !isPicked(s))];
38
+ const next = [...stack];
39
+ // One step: each picked card trades places with the unpicked card beside it.
40
+ if (move === "forward") {
41
+ for (let i = next.length - 2; i >= 0; i--) {
42
+ if (isPicked(next[i]) && !isPicked(next[i + 1]))
43
+ [next[i], next[i + 1]] = [next[i + 1], next[i]];
44
+ }
45
+ } else {
46
+ for (let i = 1; i < next.length; i++) {
47
+ if (isPicked(next[i]) && !isPicked(next[i - 1]))
48
+ [next[i], next[i - 1]] = [next[i - 1], next[i]];
49
+ }
50
+ }
51
+ return next;
52
+ }
53
+
54
+ /**
55
+ * The board after a stacking move, or null when nothing changes. Only a stack holding a
56
+ * picked card is touched, and only cards whose layer changed are rewritten.
57
+ */
58
+ export function reorderStack(
59
+ nodes: readonly Node[],
60
+ picked: ReadonlySet<string>,
61
+ move: StackMove,
62
+ ): Node[] | null {
63
+ const layers = new Map<string, number>();
64
+ for (const stack of stacks(nodes).values()) {
65
+ if (!stack.some((slot) => picked.has(slot.node.id))) continue;
66
+ const next = moved(stack, picked, move);
67
+ if (next.every((slot, i) => slot === stack[i])) continue;
68
+ next.forEach((slot, rank) => {
69
+ if (layerOf(slot.node) !== rank) layers.set(slot.node.id, rank);
70
+ });
71
+ }
72
+ if (layers.size === 0) return null;
73
+ return nodes.map((node) => {
74
+ const layer = layers.get(node.id);
75
+ return layer === undefined ? node : { ...node, zIndex: layer };
76
+ });
77
+ }
78
+
79
+ /** The layer a new card needs to land on top of its siblings, or undefined while no card has been raised. */
80
+ export function topLayer(nodes: readonly Node[], parentId?: string): number | undefined {
81
+ let top = 0;
82
+ for (const node of nodes) {
83
+ if (INK_TYPES.has(node.type ?? "") || (node.parentId ?? "") !== (parentId ?? "")) continue;
84
+ top = Math.max(top, layerOf(node));
85
+ }
86
+ return top > 0 ? top + 1 : undefined;
87
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import { alignNodesHorizontally, alignNodesVertically } from "./alignment.js";
3
+ import { makeNode } from "../test-utils.js";
4
+
5
+ describe("alignNodesHorizontally", () => {
6
+ it("sets all nodes to the topmost Y", () => {
7
+ const nodes = [
8
+ makeNode({ id: "a", position: { x: 0, y: 100 } }),
9
+ makeNode({ id: "b", position: { x: 50, y: 50 } }),
10
+ makeNode({ id: "c", position: { x: 100, y: 200 } }),
11
+ ];
12
+ alignNodesHorizontally(nodes);
13
+ expect(nodes.map((n) => n.position.y)).toEqual([50, 50, 50]);
14
+ });
15
+
16
+ it("preserves X positions", () => {
17
+ const nodes = [
18
+ makeNode({ id: "a", position: { x: 10, y: 100 } }),
19
+ makeNode({ id: "b", position: { x: 200, y: 50 } }),
20
+ ];
21
+ alignNodesHorizontally(nodes);
22
+ expect(nodes[0].position.x).toBe(10);
23
+ expect(nodes[1].position.x).toBe(200);
24
+ });
25
+
26
+ it("does nothing for fewer than 2 nodes", () => {
27
+ const single = [makeNode({ id: "a", position: { x: 5, y: 99 } })];
28
+ alignNodesHorizontally(single);
29
+ expect(single[0].position.y).toBe(99);
30
+ });
31
+ });
32
+
33
+ describe("alignNodesVertically", () => {
34
+ it("sets all nodes to the leftmost X", () => {
35
+ const nodes = [
36
+ makeNode({ id: "a", position: { x: 100, y: 0 } }),
37
+ makeNode({ id: "b", position: { x: 50, y: 50 } }),
38
+ makeNode({ id: "c", position: { x: 200, y: 100 } }),
39
+ ];
40
+ alignNodesVertically(nodes);
41
+ expect(nodes.map((n) => n.position.x)).toEqual([50, 50, 50]);
42
+ });
43
+
44
+ it("preserves Y positions", () => {
45
+ const nodes = [
46
+ makeNode({ id: "a", position: { x: 100, y: 10 } }),
47
+ makeNode({ id: "b", position: { x: 50, y: 200 } }),
48
+ ];
49
+ alignNodesVertically(nodes);
50
+ expect(nodes[0].position.y).toBe(10);
51
+ expect(nodes[1].position.y).toBe(200);
52
+ });
53
+
54
+ it("does nothing for fewer than 2 nodes", () => {
55
+ const single = [makeNode({ id: "a", position: { x: 99, y: 5 } })];
56
+ alignNodesVertically(single);
57
+ expect(single[0].position.x).toBe(99);
58
+ });
59
+ });
@@ -0,0 +1,23 @@
1
+ import type { CanvasNode } from "../types.js";
2
+
3
+ /**
4
+ * Align nodes horizontally (same row): set all to the topmost Y.
5
+ */
6
+ export function alignNodesHorizontally(nodes: CanvasNode[]): void {
7
+ if (nodes.length < 2) return;
8
+ const targetY = Math.min(...nodes.map((n) => n.position.y));
9
+ for (const node of nodes) {
10
+ node.position = { ...node.position, y: targetY };
11
+ }
12
+ }
13
+
14
+ /**
15
+ * Align nodes vertically (same column): set all to the leftmost X.
16
+ */
17
+ export function alignNodesVertically(nodes: CanvasNode[]): void {
18
+ if (nodes.length < 2) return;
19
+ const targetX = Math.min(...nodes.map((n) => n.position.x));
20
+ for (const node of nodes) {
21
+ node.position = { ...node.position, x: targetX };
22
+ }
23
+ }