@heyramzi/ui 2.11.1 → 2.11.2

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 (266) hide show
  1. package/dist/agents/helpers.js +38 -38
  2. package/dist/agents/page-state.js +49 -50
  3. package/dist/agents/vendor/color.js +146 -127
  4. package/dist/agents/vendor/draw.js +617 -508
  5. package/dist/agents/vendor/engine.js +698 -599
  6. package/dist/agents/vendor/mesh.js +653 -683
  7. package/dist/agents/vendor/plastic.js +3913 -3112
  8. package/dist/agents/vendor/presets.js +163 -164
  9. package/dist/agents/vendor/shapes.js +21 -20
  10. package/dist/agents/vendor/ticker.js +41 -42
  11. package/dist/agents/vendor/types.js +1 -0
  12. package/dist/agents/vendor/wear.js +1421 -1448
  13. package/dist/ai/confirmation/confirmation-context.svelte.js +6 -6
  14. package/dist/ai/confirmation/index.js +3 -1
  15. package/dist/ai/index.js +33 -0
  16. package/dist/ai/prompt-input/index.js +3 -1
  17. package/dist/ai/reasoning/index.js +3 -1
  18. package/dist/ai/reasoning/reasoning-context.svelte.js +6 -6
  19. package/dist/ai/suggestion/index.js +3 -1
  20. package/dist/ai/typing-dots/index.js +3 -1
  21. package/dist/animations/text-swap.js +24 -24
  22. package/dist/brand/index.js +54 -48
  23. package/dist/canvas/card/resize.js +5 -3
  24. package/dist/canvas/edges/edge-path.js +255 -242
  25. package/dist/canvas/editor/context-menu.js +39 -36
  26. package/dist/canvas/editor/dispatch.svelte.js +154 -151
  27. package/dist/canvas/editor/editor-context.js +7 -2
  28. package/dist/canvas/editor/editor-mode.js +1 -0
  29. package/dist/canvas/editor/frame.js +73 -76
  30. package/dist/canvas/editor/hand-context.js +5 -2
  31. package/dist/canvas/editor/history-core.js +163 -191
  32. package/dist/canvas/editor/keymap.js +95 -169
  33. package/dist/canvas/editor/layout-actions.js +143 -144
  34. package/dist/canvas/editor/lock.js +52 -51
  35. package/dist/canvas/editor/rebase.js +217 -240
  36. package/dist/canvas/editor/style.js +132 -137
  37. package/dist/canvas/editor/text-field-context.js +7 -2
  38. package/dist/canvas/editor/z-order.js +65 -70
  39. package/dist/canvas/headless/alignment.js +22 -16
  40. package/dist/canvas/headless/break-apart-find.js +286 -279
  41. package/dist/canvas/headless/break-apart-pieces.js +62 -58
  42. package/dist/canvas/headless/break-apart-plan.js +299 -325
  43. package/dist/canvas/headless/break-apart-style.js +75 -70
  44. package/dist/canvas/headless/break-apart.js +188 -181
  45. package/dist/canvas/headless/canvas-cleanup.js +78 -58
  46. package/dist/canvas/headless/edge-anchors.js +115 -116
  47. package/dist/canvas/headless/edge-memo.js +24 -22
  48. package/dist/canvas/headless/edge-serialization.js +17 -27
  49. package/dist/canvas/headless/figure-geometry.js +232 -264
  50. package/dist/canvas/headless/fit-viewport.js +84 -67
  51. package/dist/canvas/headless/grid.js +88 -86
  52. package/dist/canvas/headless/icon-node.js +44 -36
  53. package/dist/canvas/headless/index.js +1 -0
  54. package/dist/canvas/headless/linkify.js +17 -19
  55. package/dist/canvas/headless/pen-marks.js +101 -95
  56. package/dist/canvas/headless/sketch.js +231 -209
  57. package/dist/canvas/headless/snap-guides.js +99 -83
  58. package/dist/canvas/headless/svg-figure.js +133 -117
  59. package/dist/canvas/headless/table-layout.js +146 -107
  60. package/dist/canvas/index.js +2 -0
  61. package/dist/canvas/lod/lod-mode.svelte.js +20 -18
  62. package/dist/canvas/lod/render-all.svelte.js +36 -39
  63. package/dist/canvas/ops/arrange.js +78 -71
  64. package/dist/canvas/ops/ops.js +260 -255
  65. package/dist/canvas/pen/colors.js +37 -35
  66. package/dist/canvas/pen/eraser.js +28 -27
  67. package/dist/canvas/pen/geometry.js +120 -130
  68. package/dist/canvas/pen/index.js +1 -0
  69. package/dist/canvas/pen/ops.js +11 -11
  70. package/dist/canvas/pen/pen-session.js +150 -159
  71. package/dist/canvas/pen/plugin.js +107 -104
  72. package/dist/canvas/pen/shapes.js +73 -68
  73. package/dist/canvas/pen/stroke-path.js +32 -30
  74. package/dist/canvas/pen/types.js +21 -21
  75. package/dist/canvas/pen/ui.js +10 -8
  76. package/dist/canvas/plugin.js +46 -46
  77. package/dist/canvas/screen/glyphs.js +12 -11
  78. package/dist/canvas/screen/labels.js +22 -22
  79. package/dist/canvas/screen/parts.js +98 -111
  80. package/dist/canvas/screen/plugin.js +47 -56
  81. package/dist/canvas/screen/types.js +30 -35
  82. package/dist/canvas/screen/ui.js +7 -7
  83. package/dist/canvas/test-utils.js +14 -14
  84. package/dist/canvas/types.js +1 -0
  85. package/dist/canvas/ui/forward-wheel.js +24 -25
  86. package/dist/canvas/ui/icons.js +64 -131
  87. package/dist/canvas/ui/place-panel.js +58 -49
  88. package/dist/canvas/ui/shortcut-tip.js +14 -12
  89. package/dist/canvas/ui/tools.js +300 -385
  90. package/dist/charts/dashboard/chart.js +4 -0
  91. package/dist/charts/dashboard/data-table.js +11 -0
  92. package/dist/charts/dashboard/windows.js +8 -3
  93. package/dist/charts/edge-fade.js +10 -5
  94. package/dist/charts/hover-band.js +16 -11
  95. package/dist/charts/index.js +0 -1
  96. package/dist/charts/motion.js +11 -7
  97. package/dist/charts/primitives/chart-utils.js +12 -12
  98. package/dist/components/copy/clipboard.js +32 -32
  99. package/dist/components/copy/copied.svelte.js +31 -23
  100. package/dist/components/data-table/data-table.js +39 -39
  101. package/dist/components/docs/index.js +4 -0
  102. package/dist/components/docs/scroll-spy.svelte.js +73 -68
  103. package/dist/components/list-page/filter-section.js +4 -1
  104. package/dist/components/list-page/index.js +2 -0
  105. package/dist/components/list-page/list-table.js +7 -2
  106. package/dist/components/list-page/queue-rail.js +14 -0
  107. package/dist/components/list-page/section-host.js +4 -2
  108. package/dist/components/list-page/step.js +4 -3
  109. package/dist/components/list-page/types.js +1 -0
  110. package/dist/components/picker/trigger.js +48 -30
  111. package/dist/components/picker/types.js +1 -0
  112. package/dist/components/segmented/collapse.js +38 -27
  113. package/dist/components/segmented/index.js +8 -0
  114. package/dist/components/segmented/segmented.js +67 -42
  115. package/dist/components/star-rating-input/index.js +3 -1
  116. package/dist/components/table-view/types.js +1 -0
  117. package/dist/components/tags-input/tags.js +81 -69
  118. package/dist/components/theme/index.js +4 -0
  119. package/dist/components/toolbar/index.js +4 -0
  120. package/dist/components/toolbar/toolbar-trigger.js +14 -1
  121. package/dist/editor/anchors.js +17 -13
  122. package/dist/editor/block-id.js +52 -51
  123. package/dist/editor/block-ops.js +108 -80
  124. package/dist/editor/doc-blocks.js +155 -166
  125. package/dist/editor/extensions.js +139 -180
  126. package/dist/editor/index.js +4 -0
  127. package/dist/editor/markdown-test-helpers.js +4 -2
  128. package/dist/editor/markdown.js +420 -408
  129. package/dist/editor/serializer.js +204 -200
  130. package/dist/editor/slash-items.js +89 -101
  131. package/dist/editor/youtube-url.js +25 -20
  132. package/dist/effects/animated-beam/path-calculator.svelte.js +43 -38
  133. package/dist/effects/animated-beam/types.js +1 -0
  134. package/dist/effects/border-beam/css.js +92 -78
  135. package/dist/effects/border-beam/presets.js +1705 -1720
  136. package/dist/effects/border-beam/register-properties.js +43 -33
  137. package/dist/effects/border-beam/types.js +1 -0
  138. package/dist/effects/image-comparison/compare.js +3 -2
  139. package/dist/effects/image-reveal/types.js +1 -0
  140. package/dist/effects/magnetic/magnetic.js +58 -52
  141. package/dist/effects/reveal/observe.js +50 -40
  142. package/dist/effects/reveal/types.js +1 -0
  143. package/dist/effects/shader-gradient/color.js +36 -29
  144. package/dist/effects/shader-gradient/renderer.js +384 -295
  145. package/dist/effects/shader-gradient/shaders.js +31 -0
  146. package/dist/effects/shader-gradient/types.js +1 -0
  147. package/dist/icons/apple-symbol-exports.js +99 -124
  148. package/dist/icons/sf-symbols.js +357 -461
  149. package/dist/icons/trades.js +43 -28
  150. package/dist/inbox/index.js +1 -0
  151. package/dist/inbox/logic.js +126 -122
  152. package/dist/inbox/types.js +43 -43
  153. package/dist/lib/arrive.js +19 -12
  154. package/dist/lib/icon-types.js +1 -0
  155. package/dist/lib/initials.js +16 -10
  156. package/dist/lib/layout.js +5 -0
  157. package/dist/lib/panel.js +13 -0
  158. package/dist/lib/result.js +22 -22
  159. package/dist/lib/sliding-pill.js +59 -44
  160. package/dist/lib/theme-transition.js +43 -43
  161. package/dist/lib/tone.js +31 -24
  162. package/dist/lib/utils.js +3 -1
  163. package/dist/onboarding/define-tours.js +24 -23
  164. package/dist/onboarding/onboarding.svelte.js +16 -12
  165. package/dist/onboarding/pick.js +10 -6
  166. package/dist/onboarding/stores.js +40 -41
  167. package/dist/onboarding/wait.js +24 -23
  168. package/dist/panel/side-panel-types.js +1 -0
  169. package/dist/record/demo-data.js +459 -486
  170. package/dist/record/fields.js +129 -131
  171. package/dist/record/format.js +70 -82
  172. package/dist/record/gutter.js +1 -0
  173. package/dist/record/kinds.js +95 -105
  174. package/dist/record/logic.js +238 -219
  175. package/dist/record/types.js +49 -48
  176. package/dist/shadcn/accordion/index.js +3 -1
  177. package/dist/shadcn/alert/index.js +3 -1
  178. package/dist/shadcn/alert-dialog/index.js +3 -1
  179. package/dist/shadcn/attachment/index.js +3 -1
  180. package/dist/shadcn/avatar/index.js +3 -1
  181. package/dist/shadcn/breadcrumb/index.js +3 -1
  182. package/dist/shadcn/bubble/index.js +3 -1
  183. package/dist/shadcn/button/index.js +3 -1
  184. package/dist/shadcn/calendar/index.js +3 -1
  185. package/dist/shadcn/card/index.js +3 -1
  186. package/dist/shadcn/checkbox/index.js +3 -1
  187. package/dist/shadcn/code-block/index.js +3 -1
  188. package/dist/shadcn/collapsible/index.js +3 -1
  189. package/dist/shadcn/command/index.js +4 -1
  190. package/dist/shadcn/context-menu/index.js +3 -1
  191. package/dist/shadcn/dialog/index.js +3 -1
  192. package/dist/shadcn/dropdown-menu/menu-item.js +40 -20
  193. package/dist/shadcn/empty/index.js +3 -1
  194. package/dist/shadcn/field/index.js +3 -1
  195. package/dist/shadcn/hooks/highlighter.js +77 -75
  196. package/dist/shadcn/hooks/is-mobile.svelte.js +3 -3
  197. package/dist/shadcn/hooks/use-highlighted-code.svelte.js +62 -62
  198. package/dist/shadcn/input/index.js +3 -1
  199. package/dist/shadcn/kbd/index.js +3 -1
  200. package/dist/shadcn/label/index.js +3 -1
  201. package/dist/shadcn/marker/index.js +3 -1
  202. package/dist/shadcn/message/index.js +3 -1
  203. package/dist/shadcn/message-scroller/index.js +3 -1
  204. package/dist/shadcn/popover/index.js +3 -1
  205. package/dist/shadcn/progress/index.js +3 -1
  206. package/dist/shadcn/progress/tone.js +8 -7
  207. package/dist/shadcn/radio-group/index.js +3 -1
  208. package/dist/shadcn/scroll-area/index.js +3 -1
  209. package/dist/shadcn/select/index.js +3 -1
  210. package/dist/shadcn/separator/index.js +3 -1
  211. package/dist/shadcn/sheet/index.js +3 -1
  212. package/dist/shadcn/sidebar/context.svelte.js +42 -28
  213. package/dist/shadcn/sidebar/index.js +3 -1
  214. package/dist/shadcn/skeleton/index.js +3 -1
  215. package/dist/shadcn/slider/index.js +3 -1
  216. package/dist/shadcn/switch/index.js +3 -1
  217. package/dist/shadcn/table/index.js +3 -1
  218. package/dist/shadcn/tabs/index.js +3 -1
  219. package/dist/shadcn/textarea/index.js +3 -1
  220. package/dist/shadcn/toggle/index.js +3 -1
  221. package/dist/shadcn/toggle-group/index.js +3 -1
  222. package/dist/shadcn/tooltip/context.js +7 -2
  223. package/dist/shadcn/tooltip/index.js +3 -1
  224. package/dist/shadcn/trace/index.js +3 -1
  225. package/dist/slides/bridges.js +65 -59
  226. package/dist/slides/deck-controller.svelte.js +210 -207
  227. package/dist/slides/export/capture.js +66 -71
  228. package/dist/slides/export/ffmpeg.js +76 -72
  229. package/dist/slides/export/index.js +3 -0
  230. package/dist/slides/export/launch.js +9 -6
  231. package/dist/slides/export/pdf.js +60 -54
  232. package/dist/slides/export/types.js +1 -0
  233. package/dist/slides/labels.js +24 -24
  234. package/dist/slides/layout.js +39 -41
  235. package/dist/slides/model.js +9 -7
  236. package/dist/slides/navigation.js +78 -73
  237. package/dist/slides/types.js +2 -2
  238. package/dist/status/define-statuses.js +18 -14
  239. package/dist/status/status-types.js +1 -0
  240. package/dist/test-setup.js +24 -20
  241. package/dist/thread/logic.js +73 -77
  242. package/dist/thread/types.js +21 -21
  243. package/dist/work/board-order.js +94 -90
  244. package/dist/work/dates.js +16 -9
  245. package/dist/work/detail/detail-types.js +18 -18
  246. package/dist/work/docs/edited.js +32 -29
  247. package/dist/work/docs/index.js +4 -0
  248. package/dist/work/docs/labels.js +34 -34
  249. package/dist/work/docs/menu.js +113 -106
  250. package/dist/work/docs/outline.js +39 -29
  251. package/dist/work/gantt/logic.js +211 -235
  252. package/dist/work/inbox/inbox-logic.js +71 -75
  253. package/dist/work/inbox/inbox-statuses.js +75 -72
  254. package/dist/work/inbox/inbox-types.js +1 -0
  255. package/dist/work/kanban-drag.js +27 -22
  256. package/dist/work/kanban-types.js +1 -0
  257. package/dist/work/presets/sales.js +36 -32
  258. package/dist/work/reorder.js +9 -8
  259. package/dist/work/task-types.js +1 -0
  260. package/dist/work/time/duration.js +51 -46
  261. package/dist/work/time/time-types.js +23 -23
  262. package/dist/work/use-kanban-dnd.svelte.js +126 -125
  263. package/package.json +3 -1
  264. package/src/components/list-page/master-detail.svelte +3 -1
  265. package/src/components/list-page/rail-row.svelte +4 -4
  266. package/src/editor/markdown.ts +30 -35
@@ -1,161 +1,200 @@
1
+ // Table geometry: column widths, and the height that follows.
2
+ // One owner, because the component, node-sizing and board-doc must agree.
3
+ // Flat numbers stay in metrics.ts.
4
+ /** 13px Satoshi. Deliberately wide: guessing narrow leaves a gap. */
1
5
  const CELL_CHAR_W = 6.7;
6
+ /** px-3 on both sides of a cell. */
2
7
  export const TABLE_CELL_PAD_X = 12;
8
+ /** One line of a cell: 13px over leading-[18px]. */
3
9
  const ROW_LINE_H = 18;
10
+ /** The hairline between two rows. */
4
11
  const ROW_BORDER = 1;
12
+ /** The "no rows configured" placeholder. */
5
13
  const TABLE_EMPTY_H = 56;
14
+ /** A section band: 11px caps over a 16px line + py-1.5 + border. */
6
15
  const SECTION_ROW_H = 29;
16
+ /** A note under a row: 11px over a 16px line. */
7
17
  const NOTE_LINE_H = 16;
18
+ /** Indent of the first cell per depth step. */
8
19
  export const TABLE_DEPTH_INDENT = 16;
20
+ // The table card's own rhythm.
21
+ /** Title row: 24px icon chip + py-3 + border. */
9
22
  export const TABLE_TITLE_H = 49;
23
+ /** Column heads: 16px line + py-2 + border. */
10
24
  export const TABLE_COLHEAD_H = 33;
25
+ /** py-2.5 top and bottom. */
11
26
  const TABLE_ROW_PAD_Y = 20;
27
+ /** The narrowest a table card gets, however little it says. */
12
28
  export const TABLE_MIN_W = 360;
29
+ /** The widest it gets, so a 13px line stays under about 75 characters. */
13
30
  export const TABLE_MAX_W = 760;
31
+ /** A column left blank to be filled in still gets room to write. */
14
32
  const FILL_COL_CH = 32;
33
+ /** Excalifont runs about 12% wider than Satoshi over the same text. */
15
34
  const HAND_FONT_FACTOR = 1.12;
35
+ /** The widest a drag may make a card. */
16
36
  export const TABLE_RESIZE_MAX_W = 1600;
37
+ /** The floor. Under 9 characters a head like "Price" wraps. */
17
38
  const MIN_COL_CH = 9;
39
+ /** The ceiling, so one long column cannot eat the card. */
18
40
  const MAX_COL_CH = 48;
19
41
  export function tableColumnCount(headers, rows) {
20
- return Math.max(headers.length, rows.reduce((max, row)=>Math.max(max, row.cells?.length ?? 0), 0), 1);
42
+ return Math.max(headers.length, rows.reduce((max, row) => Math.max(max, row.cells?.length ?? 0), 0), 1);
21
43
  }
44
+ /** Fractions summing to 1. `explicit` is relative, so [1,1,3] and [10,10,30]
45
+ * are one split. Otherwise the content decides. */
22
46
  export function tableColumnWeights(headers, rows, explicit) {
23
- const count = tableColumnCount(headers, rows);
24
- if (explicit && explicit.length === count && explicit.every((w)=>w > 0)) {
25
- return normalize(explicit);
26
- }
27
- return normalize(naturalColumnChars(headers, rows, count));
47
+ const count = tableColumnCount(headers, rows);
48
+ if (explicit && explicit.length === count && explicit.every((w) => w > 0)) {
49
+ return normalize(explicit);
50
+ }
51
+ return normalize(naturalColumnChars(headers, rows, count));
28
52
  }
53
+ /** Characters each column wants: its longest line, between the floor and the ceiling. */
29
54
  function naturalColumnChars(headers, rows, count) {
30
- return Array.from({
31
- length: count
32
- }, (_, i)=>{
33
- const head = headers[i]?.length ?? 0;
34
- const cells = rows.filter((row)=>!row.section).map((row)=>row.cells?.[i] ?? "");
35
- const body = cells.reduce((max, cell)=>Math.max(max, cell.length), 0);
36
- const blank = cells.length > 0 && cells.every(isBlankCell);
37
- const wants = blank ? Math.max(head, FILL_COL_CH) : Math.max(head, body);
38
- return Math.min(MAX_COL_CH, Math.max(MIN_COL_CH, wants));
39
- });
40
- }
55
+ return Array.from({ length: count }, (_, i) => {
56
+ const head = headers[i]?.length ?? 0;
57
+ const cells = rows.filter((row) => !row.section).map((row) => row.cells?.[i] ?? "");
58
+ const body = cells.reduce((max, cell) => Math.max(max, cell.length), 0);
59
+ // "...", "…" or nothing: the column is a blank to write in, not content.
60
+ const blank = cells.length > 0 && cells.every(isBlankCell);
61
+ const wants = blank ? Math.max(head, FILL_COL_CH) : Math.max(head, body);
62
+ return Math.min(MAX_COL_CH, Math.max(MIN_COL_CH, wants));
63
+ });
64
+ }
65
+ /** A placeholder rather than a value: empty, dots, an ellipsis, a dash or a question mark. */
41
66
  export function isBlankCell(value) {
42
- return /^[\s.…?\-–—_]*$/.test(value);
67
+ return /^[\s.…?\-–—_]*$/.test(value);
43
68
  }
69
+ /** The narrowest a column gets: enough for a head like "Price" not to wrap. */
44
70
  export const TABLE_COL_MIN_W = MIN_COL_CH * CELL_CHAR_W + TABLE_CELL_PAD_X * 2;
71
+ /** `minmax(floor, …fr)`, not a bare `fr`: a long cell must not widen its column,
72
+ * and a thin weight must not squeeze a head below the floor. */
45
73
  export function tableGridTemplate(weights, floor = 0) {
46
- const min = floor > 0 ? `${floor}px` : "0";
47
- return weights.map((w)=>`minmax(${min}, ${w.toFixed(4)}fr)`).join(" ");
74
+ const min = floor > 0 ? `${floor}px` : "0";
75
+ return weights.map((w) => `minmax(${min}, ${w.toFixed(4)}fr)`).join(" ");
48
76
  }
77
+ /** Pixel widths for weights, with columns under the floor pinned there, as the grid does. */
49
78
  export function tableColumnPx(weights, width) {
50
- const out = weights.map(()=>0);
51
- const pinned = new Set();
52
- let changed = true;
53
- while(changed){
54
- changed = false;
55
- const free = weights.reduce((sum, w, i)=>sum + (pinned.has(i) ? 0 : w), 0);
56
- const room = width - pinned.size * TABLE_COL_MIN_W;
57
- weights.forEach((w, i)=>{
58
- if (pinned.has(i)) return;
59
- out[i] = free > 0 ? room * w / free : 0;
60
- if (out[i] < TABLE_COL_MIN_W && pinned.size < weights.length - 1) {
61
- pinned.add(i);
62
- changed = true;
63
- }
64
- });
65
- }
66
- return out.map((px, i)=>pinned.has(i) ? TABLE_COL_MIN_W : px);
79
+ const out = weights.map(() => 0);
80
+ const pinned = new Set();
81
+ let changed = true;
82
+ while (changed) {
83
+ changed = false;
84
+ const free = weights.reduce((sum, w, i) => sum + (pinned.has(i) ? 0 : w), 0);
85
+ const room = width - pinned.size * TABLE_COL_MIN_W;
86
+ weights.forEach((w, i) => {
87
+ if (pinned.has(i)) return;
88
+ out[i] = free > 0 ? room * w / free : 0;
89
+ if (out[i] < TABLE_COL_MIN_W && pinned.size < weights.length - 1) {
90
+ pinned.add(i);
91
+ changed = true;
92
+ }
93
+ });
94
+ }
95
+ return out.map((px, i) => pinned.has(i) ? TABLE_COL_MIN_W : px);
67
96
  }
68
97
  function charWidth(font) {
69
- return font === "hand" ? CELL_CHAR_W * HAND_FONT_FACTOR : CELL_CHAR_W;
98
+ return font === "hand" ? CELL_CHAR_W * HAND_FONT_FACTOR : CELL_CHAR_W;
70
99
  }
100
+ /** The width that fits the content, clamped to TABLE_MIN_W..TABLE_MAX_W and
101
+ * never past `ceiling`. A card the user resized keeps that width instead. */
71
102
  export function tableWidth(headers, rows, ceiling = TABLE_MAX_W, font = "plain") {
72
- const fitted = Math.ceil(naturalWidth(headers, rows, font));
73
- return Math.min(ceiling, TABLE_MAX_W, Math.max(TABLE_MIN_W, fitted));
103
+ const fitted = Math.ceil(naturalWidth(headers, rows, font));
104
+ return Math.min(ceiling, TABLE_MAX_W, Math.max(TABLE_MIN_W, fitted));
74
105
  }
106
+ /** The narrowest a drag may make a card: every column at its floor. */
75
107
  export function tableMinWidth(headers, rows) {
76
- return tableColumnCount(headers, rows) * TABLE_COL_MIN_W + 2;
108
+ return tableColumnCount(headers, rows) * TABLE_COL_MIN_W + 2;
77
109
  }
110
+ /** The width at which every column holds its longest line, up to the per-column cap. */
78
111
  function naturalWidth(headers, rows, font) {
79
- const count = tableColumnCount(headers, rows);
80
- return naturalColumnChars(headers, rows, count).reduce((sum, ch)=>sum + ch * charWidth(font) + TABLE_CELL_PAD_X * 2, 0);
112
+ const count = tableColumnCount(headers, rows);
113
+ return naturalColumnChars(headers, rows, count).reduce((sum, ch) => sum + ch * charWidth(font) + TABLE_CELL_PAD_X * 2, 0);
81
114
  }
115
+ /** Column weights for a card `width` wide. A card with room for the content
116
+ * splits by content; a squeezed one falls back to the author's weights. */
82
117
  export function tableWeightsAt(headers, rows, width, { explicit, font = "plain" } = {}) {
83
- if (width >= naturalWidth(headers, rows, font)) return tableColumnWeights(headers, rows);
84
- return tableColumnWeights(headers, rows, explicit);
118
+ if (width >= naturalWidth(headers, rows, font)) return tableColumnWeights(headers, rows);
119
+ return tableColumnWeights(headers, rows, explicit);
85
120
  }
121
+ /** Figure columns get `tabular-nums`. Not right-aligned: a table node is read
122
+ * across, and a right-aligned middle column opens a gutter mid-sentence. */
86
123
  export function tableNumericColumns(rows, count) {
87
- return Array.from({
88
- length: count
89
- }, (_, i)=>{
90
- const values = rows.filter((row)=>!row.section).map((row)=>row.cells?.[i]?.trim() ?? "").filter(Boolean);
91
- return values.length > 0 && values.every(isFigure);
92
- });
124
+ return Array.from({ length: count }, (_, i) => {
125
+ const values = rows.filter((row) => !row.section).map((row) => row.cells?.[i]?.trim() ?? "").filter(Boolean);
126
+ return values.length > 0 && values.every(isFigure);
127
+ });
93
128
  }
129
+ /** A price, a count, a percentage, a multiplier. Not "$20 a month". */
94
130
  function isFigure(value) {
95
- return /^[-+]?[$€£]?\s?\d[\d\s.,]*\s?[%x×]?$/.test(value);
131
+ return /^[-+]?[$€£]?\s?\d[\d\s.,]*\s?[%x×]?$/.test(value);
96
132
  }
133
+ /** How many lines a cell takes at the width its column gets. */
97
134
  function wrappedLines(text, columnWidth, charW = CELL_CHAR_W) {
98
- const inner = Math.max(charW, columnWidth - TABLE_CELL_PAD_X * 2);
99
- const perLine = Math.max(1, Math.floor(inner / charW));
100
- return Math.max(1, Math.ceil(text.length / perLine));
135
+ const inner = Math.max(charW, columnWidth - TABLE_CELL_PAD_X * 2);
136
+ const perLine = Math.max(1, Math.floor(inner / charW));
137
+ return Math.max(1, Math.ceil(text.length / perLine));
101
138
  }
139
+ /** Pixel widths of the columns when the card is `width` wide. */
102
140
  export function tableColumnsAt(headers, rows, width, { sizing, weights }) {
103
- const count = tableColumnCount(headers, rows);
104
- const inner = Math.max(count * TABLE_COL_MIN_W, width - 2);
105
- const pinned = sizing.columnWidths;
106
- if (pinned && pinned.length === count && pinned.every((w)=>w > 0)) {
107
- return tableColumnPx(pinned, inner);
108
- }
109
- const split = sizing.userWidth != null ? tableColumnWeights(headers, rows, weights) : tableWeightsAt(headers, rows, width, {
110
- explicit: weights,
111
- font: sizing.font
112
- });
113
- return tableColumnPx(split, inner);
114
- }
141
+ const count = tableColumnCount(headers, rows);
142
+ const inner = Math.max(count * TABLE_COL_MIN_W, width - 2);
143
+ const pinned = sizing.columnWidths;
144
+ if (pinned && pinned.length === count && pinned.every((w) => w > 0)) {
145
+ return tableColumnPx(pinned, inner);
146
+ }
147
+ const split = sizing.userWidth != null ? tableColumnWeights(headers, rows, weights) : tableWeightsAt(headers, rows, width, {
148
+ explicit: weights,
149
+ font: sizing.font
150
+ });
151
+ return tableColumnPx(split, inner);
152
+ }
153
+ /** The card's width and the pixel width of each column, from what the user fixed. */
115
154
  export function tableColumns(headers, rows, sizing = {}, weights) {
116
- const count = tableColumnCount(headers, rows);
117
- const pinned = sizing.columnWidths;
118
- const hasPinned = pinned && pinned.length === count && pinned.every((w)=>w > 0);
119
- const width = sizing.userWidth ?? (hasPinned ? Math.round(pinned.reduce((sum, w)=>sum + w, 0)) + 2 : tableWidth(headers, rows, TABLE_MAX_W, sizing.font));
120
- return {
121
- width,
122
- columns: tableColumnsAt(headers, rows, width, {
123
- sizing,
124
- weights
125
- })
126
- };
127
- }
155
+ const count = tableColumnCount(headers, rows);
156
+ const pinned = sizing.columnWidths;
157
+ const hasPinned = pinned && pinned.length === count && pinned.every((w) => w > 0);
158
+ const width = sizing.userWidth ?? (hasPinned ? Math.round(pinned.reduce((sum, w) => sum + w, 0)) + 2 : tableWidth(headers, rows, TABLE_MAX_W, sizing.font));
159
+ return {
160
+ width,
161
+ columns: tableColumnsAt(headers, rows, width, {
162
+ sizing,
163
+ weights
164
+ })
165
+ };
166
+ }
167
+ /** Each row's height: its content, or the height the user pinned if that is more. */
128
168
  export function tableRowHeights(rows, columns, sizing = {}) {
129
- const charW = charWidth(sizing.font ?? "plain");
130
- const full = columns.reduce((sum, px)=>sum + px, 0);
131
- return rows.map((row, i)=>{
132
- if (row.section) return Math.max(SECTION_ROW_H, sizing.rowHeights?.[i] ?? 0);
133
- const indent = (row.depth ?? 0) * TABLE_DEPTH_INDENT;
134
- const lines = columns.reduce((max, px, c)=>Math.max(max, wrappedLines(row.cells?.[c] ?? "", c === 0 ? px - indent : px, charW)), 1);
135
- const note = row.note ? wrappedLines(row.note, full - indent) * NOTE_LINE_H : 0;
136
- const content = TABLE_ROW_PAD_Y + lines * ROW_LINE_H + ROW_BORDER + note;
137
- return Math.max(content, sizing.rowHeights?.[i] ?? 0);
138
- });
139
- }
169
+ const charW = charWidth(sizing.font ?? "plain");
170
+ const full = columns.reduce((sum, px) => sum + px, 0);
171
+ return rows.map((row, i) => {
172
+ if (row.section) return Math.max(SECTION_ROW_H, sizing.rowHeights?.[i] ?? 0);
173
+ const indent = (row.depth ?? 0) * TABLE_DEPTH_INDENT;
174
+ const lines = columns.reduce((max, px, c) => Math.max(max, wrappedLines(row.cells?.[c] ?? "", c === 0 ? px - indent : px, charW)), 1);
175
+ const note = row.note ? wrappedLines(row.note, full - indent) * NOTE_LINE_H : 0;
176
+ const content = TABLE_ROW_PAD_Y + lines * ROW_LINE_H + ROW_BORDER + note;
177
+ return Math.max(content, sizing.rowHeights?.[i] ?? 0);
178
+ });
179
+ }
180
+ /** The card's height at a given width, wrapping included. A height the user set is a floor. */
140
181
  export function tableHeight(headers, rows, width, { explicit, sizing = {} } = {}) {
141
- const head = headers.length > 0 ? TABLE_COLHEAD_H : 0;
142
- if (rows.length === 0) return TABLE_TITLE_H + head + TABLE_EMPTY_H;
143
- const columns = tableColumnsAt(headers, rows, width, {
144
- sizing,
145
- weights: explicit
146
- });
147
- const body = tableRowHeights(rows, columns, sizing).reduce((sum, h)=>sum + h, 0);
148
- return Math.max(sizing.userHeight ?? 0, TABLE_TITLE_H + head + body);
149
- }
182
+ const head = headers.length > 0 ? TABLE_COLHEAD_H : 0;
183
+ if (rows.length === 0) return TABLE_TITLE_H + head + TABLE_EMPTY_H;
184
+ const columns = tableColumnsAt(headers, rows, width, {
185
+ sizing,
186
+ weights: explicit
187
+ });
188
+ const body = tableRowHeights(rows, columns, sizing).reduce((sum, h) => sum + h, 0);
189
+ return Math.max(sizing.userHeight ?? 0, TABLE_TITLE_H + head + body);
190
+ }
191
+ /** The width a column needs to hold its longest line, for a double-click on its border. */
150
192
  export function tableColumnFit(headers, rows, col, font = "plain") {
151
- const longest = [
152
- headers[col] ?? "",
153
- ...rows.filter((row)=>!row.section).map((row)=>row.cells?.[col] ?? "")
154
- ].reduce((max, text)=>Math.max(max, text.length), 0);
155
- const px = Math.ceil(longest * charWidth(font)) + TABLE_CELL_PAD_X * 2;
156
- return Math.max(TABLE_COL_MIN_W, Math.min(px, TABLE_RESIZE_MAX_W));
193
+ const longest = [headers[col] ?? "", ...rows.filter((row) => !row.section).map((row) => row.cells?.[col] ?? "")].reduce((max, text) => Math.max(max, text.length), 0);
194
+ const px = Math.ceil(longest * charWidth(font)) + TABLE_CELL_PAD_X * 2;
195
+ return Math.max(TABLE_COL_MIN_W, Math.min(px, TABLE_RESIZE_MAX_W));
157
196
  }
158
197
  function normalize(weights) {
159
- const total = weights.reduce((sum, w)=>sum + w, 0);
160
- return total > 0 ? weights.map((w)=>w / total) : weights.map(()=>1 / weights.length);
198
+ const total = weights.reduce((sum, w) => sum + w, 0);
199
+ return total > 0 ? weights.map((w) => w / total) : weights.map(() => 1 / weights.length);
161
200
  }
@@ -1,3 +1,5 @@
1
+ // @heyramzi/ui/canvas public API. Server-safe: nothing here touches the DOM.
2
+ // Svelte components are imported by path, like "@heyramzi/ui/canvas/ui/canvas-grid.svelte".
1
3
  export * from "./types.js";
2
4
  export * from "./plugin.js";
3
5
  export { tryCatch, tryCatchAsync } from "../lib/result.js";
@@ -1,22 +1,24 @@
1
1
  import { untrack } from "svelte";
2
- export const LOW_BELOW = 0.4;
3
- export const FULL_ABOVE = 0.46;
2
+ // One flag per canvas: a card reading it re-renders when it flips, not on every zoom frame.
3
+ /** Below this zoom a card is a box. */
4
+ export const LOW_BELOW = .4;
5
+ /** Back to the full card above this. The gap stops a zoom held at the line from swapping every card each frame. */
6
+ export const FULL_ABOVE = .46;
4
7
  const modes = new WeakMap();
8
+ /** The shared flag for one canvas's store. The first card to ask starts the one effect that keeps it. */
5
9
  export function lodMode(store) {
6
- const known = modes.get(store);
7
- if (known) return known;
8
- const mode = $state({
9
- low: store.viewport.zoom < LOW_BELOW
10
- });
11
- modes.set(store, mode);
12
- $effect.root(()=>{
13
- $effect(()=>{
14
- const zoom = store.viewport.zoom;
15
- untrack(()=>{
16
- if (mode.low && zoom > FULL_ABOVE) mode.low = false;
17
- else if (!mode.low && zoom < LOW_BELOW) mode.low = true;
18
- });
19
- });
20
- });
21
- return mode;
10
+ const known = modes.get(store);
11
+ if (known) return known;
12
+ const mode = $state({ low: store.viewport.zoom < LOW_BELOW });
13
+ modes.set(store, mode);
14
+ $effect.root(() => {
15
+ $effect(() => {
16
+ const zoom = store.viewport.zoom;
17
+ untrack(() => {
18
+ if (mode.low && zoom > FULL_ABOVE) mode.low = false;
19
+ else if (!mode.low && zoom < LOW_BELOW) mode.low = true;
20
+ });
21
+ });
22
+ });
23
+ return mode;
22
24
  }
@@ -1,45 +1,42 @@
1
1
  import { tick } from "svelte";
2
- const hold = $state({
3
- count: 0
4
- });
5
- export const renderAll = {
6
- get on () {
7
- return hold.count > 0;
8
- }
9
- };
10
- const frame = ()=>new Promise((resolve)=>requestAnimationFrame(()=>resolve()));
2
+ // An export reads the page's markup, so it holds this open: every card is drawn in full until it lets go.
3
+ const hold = $state({ count: 0 });
4
+ /** True while something needs every card drawn in full. */
5
+ export const renderAll = { get on() {
6
+ return hold.count > 0;
7
+ } };
8
+ const frame = () => new Promise((resolve) => requestAnimationFrame(() => resolve()));
9
+ // Arrows draw a beat after their cards, once the cards are measured, so they are counted too.
11
10
  const DRAWN = ".svelte-flow__node, .svelte-flow__edge";
12
11
  const SETTLE_FRAMES = 8;
13
- const GIVE_UP_MS = 4000;
12
+ const GIVE_UP_MS = 4e3;
13
+ /** The page's card fade-in is switched off under this attribute, so a card that remounts is captured solid. */
14
14
  export const EVERY_CARD_ATTR = "data-cv-every-card";
15
+ /** Runs `use` with every card drawn in full. Waits until the number of cards and arrows on the page holds still. */
15
16
  export async function withEveryCard(root, use) {
16
- hold.count += 1;
17
- const marked = root instanceof Element ? root : null;
18
- marked?.setAttribute(EVERY_CARD_ATTR, "");
19
- try {
20
- await tick();
21
- const start = performance.now();
22
- let last = -1;
23
- let still = 0;
24
- while(still < SETTLE_FRAMES && performance.now() - start < GIVE_UP_MS){
25
- await frame();
26
- const now = root.querySelectorAll(DRAWN).length;
27
- still = now === last ? still + 1 : 0;
28
- last = now;
29
- }
30
- if ("getAnimations" in root) {
31
- const running = root.getAnimations({
32
- subtree: true
33
- });
34
- await Promise.race([
35
- Promise.allSettled(running.map((animation)=>animation.finished)),
36
- new Promise((resolve)=>setTimeout(resolve, 1000))
37
- ]);
38
- }
39
- await frame();
40
- return await use();
41
- } finally{
42
- hold.count -= 1;
43
- if (hold.count === 0) marked?.removeAttribute(EVERY_CARD_ATTR);
44
- }
17
+ hold.count += 1;
18
+ const marked = root instanceof Element ? root : null;
19
+ marked?.setAttribute(EVERY_CARD_ATTR, "");
20
+ try {
21
+ await tick();
22
+ const start = performance.now();
23
+ let last = -1;
24
+ let still = 0;
25
+ while (still < SETTLE_FRAMES && performance.now() - start < GIVE_UP_MS) {
26
+ await frame();
27
+ const now = root.querySelectorAll(DRAWN).length;
28
+ still = now === last ? still + 1 : 0;
29
+ last = now;
30
+ }
31
+ // A fade that began before the switch-off still has to land.
32
+ if ("getAnimations" in root) {
33
+ const running = root.getAnimations({ subtree: true });
34
+ await Promise.race([Promise.allSettled(running.map((animation) => animation.finished)), new Promise((resolve) => setTimeout(resolve, 1e3))]);
35
+ }
36
+ await frame();
37
+ return await use();
38
+ } finally {
39
+ hold.count -= 1;
40
+ if (hold.count === 0) marked?.removeAttribute(EVERY_CARD_ATTR);
41
+ }
45
42
  }
@@ -1,83 +1,90 @@
1
1
  export const ALIGNMENTS = [
2
- "left",
3
- "right",
4
- "top",
5
- "bottom",
6
- "center-horizontal",
7
- "center-vertical"
2
+ "left",
3
+ "right",
4
+ "top",
5
+ "bottom",
6
+ "center-horizontal",
7
+ "center-vertical"
8
8
  ];
9
+ // =========================
10
+ // ALIGN
11
+ // =========================
12
+ /** "center-horizontal" puts the centres on one vertical axis, as tldraw named it. */
9
13
  export function alignPositions(items, alignment) {
10
- const moved = new Map();
11
- if (items.length < 2) return moved;
12
- const left = Math.min(...items.map((item)=>item.position.x));
13
- const top = Math.min(...items.map((item)=>item.position.y));
14
- const right = Math.max(...items.map((item)=>item.position.x + item.width));
15
- const bottom = Math.max(...items.map((item)=>item.position.y + item.height));
16
- for (const item of items){
17
- const { x, y } = item.position;
18
- const next = {
19
- x,
20
- y
21
- };
22
- if (alignment === "left") next.x = left;
23
- else if (alignment === "right") next.x = right - item.width;
24
- else if (alignment === "top") next.y = top;
25
- else if (alignment === "bottom") next.y = bottom - item.height;
26
- else if (alignment === "center-horizontal") next.x = (left + right) / 2 - item.width / 2;
27
- else next.y = (top + bottom) / 2 - item.height / 2;
28
- if (next.x !== x || next.y !== y) moved.set(item.id, next);
29
- }
30
- return moved;
14
+ const moved = new Map();
15
+ if (items.length < 2) return moved;
16
+ const left = Math.min(...items.map((item) => item.position.x));
17
+ const top = Math.min(...items.map((item) => item.position.y));
18
+ const right = Math.max(...items.map((item) => item.position.x + item.width));
19
+ const bottom = Math.max(...items.map((item) => item.position.y + item.height));
20
+ for (const item of items) {
21
+ const { x, y } = item.position;
22
+ const next = {
23
+ x,
24
+ y
25
+ };
26
+ if (alignment === "left") next.x = left;
27
+ else if (alignment === "right") next.x = right - item.width;
28
+ else if (alignment === "top") next.y = top;
29
+ else if (alignment === "bottom") next.y = bottom - item.height;
30
+ else if (alignment === "center-horizontal") next.x = (left + right) / 2 - item.width / 2;
31
+ else next.y = (top + bottom) / 2 - item.height / 2;
32
+ if (next.x !== x || next.y !== y) moved.set(item.id, next);
33
+ }
34
+ return moved;
31
35
  }
36
+ // =========================
37
+ // DISTRIBUTE AND STACK
38
+ // =========================
32
39
  function along(axis) {
33
- const horizontal = axis === "horizontal";
34
- return {
35
- start: (item)=>horizontal ? item.position.x : item.position.y,
36
- size: (item)=>horizontal ? item.width : item.height,
37
- place: (item, at)=>horizontal ? {
38
- x: at,
39
- y: item.position.y
40
- } : {
41
- x: item.position.x,
42
- y: at
43
- }
44
- };
40
+ const horizontal = axis === "horizontal";
41
+ return {
42
+ start: (item) => horizontal ? item.position.x : item.position.y,
43
+ size: (item) => horizontal ? item.width : item.height,
44
+ place: (item, at) => horizontal ? {
45
+ x: at,
46
+ y: item.position.y
47
+ } : {
48
+ x: item.position.x,
49
+ y: at
50
+ }
51
+ };
45
52
  }
46
53
  function inOrder(items, axis) {
47
- const { start } = along(axis);
48
- return [
49
- ...items
50
- ].sort((a, b)=>start(a) - start(b));
54
+ const { start } = along(axis);
55
+ return [...items].sort((a, b) => start(a) - start(b));
51
56
  }
57
+ /** Space the boxes so every gap matches. The first and last stay where they are. */
52
58
  export function distributePositions(items, axis) {
53
- const moved = new Map();
54
- if (items.length < 3) return moved;
55
- const { start, size, place } = along(axis);
56
- const sorted = inOrder(items, axis);
57
- const first = sorted[0];
58
- const last = sorted[sorted.length - 1];
59
- if (!first || !last) return moved;
60
- const span = start(last) + size(last) - start(first);
61
- const used = sorted.reduce((sum, item)=>sum + size(item), 0);
62
- const gap = (span - used) / (sorted.length - 1);
63
- let cursor = start(first);
64
- for (const item of sorted){
65
- if (cursor !== start(item)) moved.set(item.id, place(item, cursor));
66
- cursor += size(item) + gap;
67
- }
68
- return moved;
59
+ const moved = new Map();
60
+ if (items.length < 3) return moved;
61
+ const { start, size, place } = along(axis);
62
+ const sorted = inOrder(items, axis);
63
+ const first = sorted[0];
64
+ const last = sorted[sorted.length - 1];
65
+ if (!first || !last) return moved;
66
+ const span = start(last) + size(last) - start(first);
67
+ const used = sorted.reduce((sum, item) => sum + size(item), 0);
68
+ const gap = (span - used) / (sorted.length - 1);
69
+ let cursor = start(first);
70
+ for (const item of sorted) {
71
+ if (cursor !== start(item)) moved.set(item.id, place(item, cursor));
72
+ cursor += size(item) + gap;
73
+ }
74
+ return moved;
69
75
  }
76
+ /** Put the boxes in a row or a column, `gap` apart, in the order they sit now. The first stays put. */
70
77
  export function stackPositions(items, axis, gap) {
71
- const moved = new Map();
72
- if (items.length < 2) return moved;
73
- const { start, size, place } = along(axis);
74
- const sorted = inOrder(items, axis);
75
- const first = sorted[0];
76
- if (!first) return moved;
77
- let cursor = start(first);
78
- for (const item of sorted){
79
- if (cursor !== start(item)) moved.set(item.id, place(item, cursor));
80
- cursor += size(item) + gap;
81
- }
82
- return moved;
78
+ const moved = new Map();
79
+ if (items.length < 2) return moved;
80
+ const { start, size, place } = along(axis);
81
+ const sorted = inOrder(items, axis);
82
+ const first = sorted[0];
83
+ if (!first) return moved;
84
+ let cursor = start(first);
85
+ for (const item of sorted) {
86
+ if (cursor !== start(item)) moved.set(item.id, place(item, cursor));
87
+ cursor += size(item) + gap;
88
+ }
89
+ return moved;
83
90
  }