@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,378 +1,352 @@
1
1
  import { distanceToBox, distanceToSegment, areaOf, contains, num, solid, within } from "./figure-geometry.js";
2
2
  import { LAND_TOLERANCE } from "./break-apart-find.js";
3
3
  import { NOTE_TITLE_PX, accentOf, escapeMarkdown, fitScale, inked, penColorOf } from "./break-apart-style.js";
4
+ /** The heading above the drawing; mirrors LABEL_H in svg-figure.ts. */
4
5
  const LABEL_H = 27;
6
+ /** A step number sits this many drawing units from the card or arrow it numbers. */
5
7
  const BADGE_REACH = 6;
6
8
  const BADGE_LINK_REACH = 60;
7
9
  const MARK_MIN = 24;
8
10
  export function freshId(base, taken) {
9
- let id = base;
10
- for(let n = 2; taken.has(id); n += 1)id = `${base}-${n}`;
11
- taken.add(id);
12
- return id;
11
+ let id = base;
12
+ for (let n = 2; taken.has(id); n += 1) id = `${base}-${n}`;
13
+ taken.add(id);
14
+ return id;
13
15
  }
14
16
  export function startPlan(source, pieces, drawing, artWidth) {
15
- const { node } = source;
16
- const taken = new Set(source.takenIds ?? []);
17
- taken.add(node.id);
18
- const cardIds = pieces.cards.map((_, i)=>freshId(`${node.id}-card-${i + 1}`, taken));
19
- const frameIds = pieces.loose.frames.map((_, i)=>freshId(`${node.id}-frame-${i + 1}`, taken));
20
- return {
21
- node,
22
- drawing,
23
- scale: artWidth / (drawing.x2 - drawing.x1),
24
- artLeft: source.art?.left ?? 0,
25
- artTop: source.art?.top ?? (node.data.label ? LABEL_H : 0),
26
- artWidth,
27
- cards: pieces.cards,
28
- loose: pieces.loose,
29
- arrows: pieces.arrows,
30
- edgeData: source.edgeData,
31
- taken,
32
- removed: new Set(),
33
- ops: [],
34
- cardIds,
35
- frameIds,
36
- shapes: 0
37
- };
17
+ const { node } = source;
18
+ const taken = new Set(source.takenIds ?? []);
19
+ taken.add(node.id);
20
+ const cardIds = pieces.cards.map((_, i) => freshId(`${node.id}-card-${i + 1}`, taken));
21
+ const frameIds = pieces.loose.frames.map((_, i) => freshId(`${node.id}-frame-${i + 1}`, taken));
22
+ return {
23
+ node,
24
+ drawing,
25
+ scale: artWidth / (drawing.x2 - drawing.x1),
26
+ artLeft: source.art?.left ?? 0,
27
+ artTop: source.art?.top ?? (node.data.label ? LABEL_H : 0),
28
+ artWidth,
29
+ cards: pieces.cards,
30
+ loose: pieces.loose,
31
+ arrows: pieces.arrows,
32
+ edgeData: source.edgeData,
33
+ taken,
34
+ removed: new Set(),
35
+ ops: [],
36
+ cardIds,
37
+ frameIds,
38
+ shapes: 0
39
+ };
38
40
  }
41
+ // =========================
42
+ // PLACEMENT
43
+ // =========================
39
44
  export function toBoard(plan, p) {
40
- return {
41
- x: Math.round(plan.node.position.x + plan.artLeft + (p.x - plan.drawing.x1) * plan.scale),
42
- y: Math.round(plan.node.position.y + plan.artTop + (p.y - plan.drawing.y1) * plan.scale)
43
- };
45
+ return {
46
+ x: Math.round(plan.node.position.x + plan.artLeft + (p.x - plan.drawing.x1) * plan.scale),
47
+ y: Math.round(plan.node.position.y + plan.artTop + (p.y - plan.drawing.y1) * plan.scale)
48
+ };
44
49
  }
50
+ /** The smallest frame round a box, so a card inside two frames sits in the inner one. */
45
51
  function frameOf(plan, box, self = -1) {
46
- const { frames } = plan.loose;
47
- let best = -1;
48
- frames.forEach((frame, i)=>{
49
- if (i === self || !within(box, frame.box, 1) || areaOf(frame.box) <= areaOf(box)) return;
50
- if (best < 0 || areaOf(frame.box) < areaOf(frames[best].box)) best = i;
51
- });
52
- return best;
52
+ const { frames } = plan.loose;
53
+ let best = -1;
54
+ frames.forEach((frame, i) => {
55
+ if (i === self || !within(box, frame.box, 1) || areaOf(frame.box) <= areaOf(box)) return;
56
+ if (best < 0 || areaOf(frame.box) < areaOf(frames[best].box)) best = i;
57
+ });
58
+ return best;
53
59
  }
60
+ /** Where a frame's own space starts on the board, or the figure's origin when there is no frame. */
54
61
  function frameOrigin(plan, frame) {
55
- if (frame < 0) return {
56
- x: 0,
57
- y: 0
58
- };
59
- const { box } = plan.loose.frames[frame];
60
- return toBoard(plan, {
61
- x: box.x1,
62
- y: box.y1
63
- });
62
+ if (frame < 0) return {
63
+ x: 0,
64
+ y: 0
65
+ };
66
+ const { box } = plan.loose.frames[frame];
67
+ return toBoard(plan, {
68
+ x: box.x1,
69
+ y: box.y1
70
+ });
64
71
  }
72
+ /** A position in the frame's own space, or the figure's when there is no frame. */
65
73
  function placed(plan, box, frame) {
66
- const at = toBoard(plan, {
67
- x: box.x1,
68
- y: box.y1
69
- });
70
- const origin = frameOrigin(plan, frame);
71
- return {
72
- x: at.x - origin.x,
73
- y: at.y - origin.y
74
- };
74
+ const at = toBoard(plan, {
75
+ x: box.x1,
76
+ y: box.y1
77
+ });
78
+ const origin = frameOrigin(plan, frame);
79
+ return {
80
+ x: at.x - origin.x,
81
+ y: at.y - origin.y
82
+ };
75
83
  }
76
84
  function parentOf(plan, frame) {
77
- if (frame >= 0) return {
78
- parentId: plan.frameIds[frame]
79
- };
80
- return plan.node.parentId ? {
81
- parentId: plan.node.parentId
82
- } : {};
85
+ if (frame >= 0) return { parentId: plan.frameIds[frame] };
86
+ return plan.node.parentId ? { parentId: plan.node.parentId } : {};
83
87
  }
84
88
  function sizeOf(plan, box) {
85
- return {
86
- width: Math.round((box.x2 - box.x1) * plan.scale),
87
- height: Math.round((box.y2 - box.y1) * plan.scale)
88
- };
89
+ return {
90
+ width: Math.round((box.x2 - box.x1) * plan.scale),
91
+ height: Math.round((box.y2 - box.y1) * plan.scale)
92
+ };
89
93
  }
94
+ /** A shape drawn with fill alone gets the thinnest line the pen has. */
90
95
  function strokeOf(plan, el) {
91
- if (!solid(el.attrs.stroke)) return 1;
92
- return Math.max(1, Math.round(num(el.attrs["stroke-width"], 1.5) * plan.scale));
96
+ if (!solid(el.attrs.stroke)) return 1;
97
+ return Math.max(1, Math.round(num(el.attrs["stroke-width"], 1.5) * plan.scale));
93
98
  }
94
99
  function colorOf(el) {
95
- const stroked = solid(el.attrs.stroke) ? penColorOf(el.attrs.stroke) : undefined;
96
- return stroked ?? penColorOf(el.attrs.fill) ?? "ink";
100
+ const stroked = solid(el.attrs.stroke) ? penColorOf(el.attrs.stroke) : undefined;
101
+ return stroked ?? penColorOf(el.attrs.fill) ?? "ink";
97
102
  }
103
+ // =========================
104
+ // FRAMES AND SHAPES
105
+ // =========================
106
+ /** Frames come first so what sits in them draws above. */
98
107
  export function addFrames(plan) {
99
- plan.loose.frames.forEach((frame, i)=>{
100
- plan.removed.add(frame.el);
101
- const parent = frameOf(plan, frame.box, i);
102
- const { width, height } = sizeOf(plan, frame.box);
103
- plan.ops.push({
104
- kind: "addNode",
105
- node: {
106
- id: plan.frameIds[i],
107
- type: "wrapper",
108
- position: placed(plan, frame.box, parent),
109
- data: {
110
- nodeType: "wrapper",
111
- label: "",
112
- frame: true,
113
- stroke: colorOf(frame.el),
114
- strokeWidth: strokeOf(plan, frame.el),
115
- fill: inked(frame.el.attrs.fill) ? penColorOf(frame.el.attrs.fill) ?? "none" : "none"
116
- },
117
- style: {
118
- width,
119
- height
120
- },
121
- width,
122
- height,
123
- ...parentOf(plan, parent)
124
- }
125
- });
126
- });
108
+ plan.loose.frames.forEach((frame, i) => {
109
+ plan.removed.add(frame.el);
110
+ const parent = frameOf(plan, frame.box, i);
111
+ const { width, height } = sizeOf(plan, frame.box);
112
+ plan.ops.push({
113
+ kind: "addNode",
114
+ node: {
115
+ id: plan.frameIds[i],
116
+ type: "wrapper",
117
+ position: placed(plan, frame.box, parent),
118
+ data: {
119
+ nodeType: "wrapper",
120
+ label: "",
121
+ frame: true,
122
+ stroke: colorOf(frame.el),
123
+ strokeWidth: strokeOf(plan, frame.el),
124
+ fill: inked(frame.el.attrs.fill) ? penColorOf(frame.el.attrs.fill) ?? "none" : "none"
125
+ },
126
+ style: {
127
+ width,
128
+ height
129
+ },
130
+ width,
131
+ height,
132
+ ...parentOf(plan, parent)
133
+ }
134
+ });
135
+ });
127
136
  }
128
137
  function addShape(plan, shape, box, frame) {
129
- plan.shapes += 1;
130
- const { width, height } = sizeOf(plan, box);
131
- plan.ops.push({
132
- kind: "addNode",
133
- node: {
134
- id: freshId(`${plan.node.id}-shape-${plan.shapes}`, plan.taken),
135
- type: "shape",
136
- position: placed(plan, box, frame),
137
- data: {
138
- strokeWidth: 2,
139
- filled: false,
140
- label: "",
141
- ...shape
142
- },
143
- width,
144
- height,
145
- ...parentOf(plan, frame)
146
- }
147
- });
138
+ plan.shapes += 1;
139
+ const { width, height } = sizeOf(plan, box);
140
+ plan.ops.push({
141
+ kind: "addNode",
142
+ node: {
143
+ id: freshId(`${plan.node.id}-shape-${plan.shapes}`, plan.taken),
144
+ type: "shape",
145
+ position: placed(plan, box, frame),
146
+ data: {
147
+ strokeWidth: 2,
148
+ filled: false,
149
+ label: "",
150
+ ...shape
151
+ },
152
+ width,
153
+ height,
154
+ ...parentOf(plan, frame)
155
+ }
156
+ });
148
157
  }
149
158
  export function addShapes(plan) {
150
- for (const shape of plan.loose.shapes){
151
- plan.removed.add(shape.el);
152
- const fill = shape.el.attrs.fill;
153
- const fillPen = inked(fill) ? penColorOf(fill) : undefined;
154
- addShape(plan, {
155
- shape: "rectangle",
156
- color: colorOf(shape.el),
157
- strokeWidth: strokeOf(plan, shape.el),
158
- filled: inked(fill),
159
- ...fillPen ? {
160
- fill: fillPen
161
- } : {}
162
- }, shape.box, frameOf(plan, shape.box));
163
- }
159
+ for (const shape of plan.loose.shapes) {
160
+ plan.removed.add(shape.el);
161
+ const fill = shape.el.attrs.fill;
162
+ const fillPen = inked(fill) ? penColorOf(fill) : undefined;
163
+ addShape(plan, {
164
+ shape: "rectangle",
165
+ color: colorOf(shape.el),
166
+ strokeWidth: strokeOf(plan, shape.el),
167
+ filled: inked(fill),
168
+ ...fillPen ? { fill: fillPen } : {}
169
+ }, shape.box, frameOf(plan, shape.box));
170
+ }
164
171
  }
172
+ // =========================
173
+ // LINKS AND STEP NUMBERS
174
+ // =========================
165
175
  function cardAt(cards, point) {
166
- let best = -1;
167
- let bestDistance = Number.POSITIVE_INFINITY;
168
- cards.forEach((card, index)=>{
169
- if (!contains(card.box, point, LAND_TOLERANCE)) return;
170
- const dx = Math.max(card.box.x1 - point.x, 0, point.x - card.box.x2);
171
- const dy = Math.max(card.box.y1 - point.y, 0, point.y - card.box.y2);
172
- const distance = Math.hypot(dx, dy);
173
- if (distance < bestDistance) {
174
- best = index;
175
- bestDistance = distance;
176
- }
177
- });
178
- return best;
176
+ let best = -1;
177
+ let bestDistance = Number.POSITIVE_INFINITY;
178
+ cards.forEach((card, index) => {
179
+ if (!contains(card.box, point, LAND_TOLERANCE)) return;
180
+ const dx = Math.max(card.box.x1 - point.x, 0, point.x - card.box.x2);
181
+ const dy = Math.max(card.box.y1 - point.y, 0, point.y - card.box.y2);
182
+ const distance = Math.hypot(dx, dy);
183
+ if (distance < bestDistance) {
184
+ best = index;
185
+ bestDistance = distance;
186
+ }
187
+ });
188
+ return best;
179
189
  }
180
190
  function sideOf(box, point) {
181
- const gaps = [
182
- [
183
- "left",
184
- Math.abs(point.x - box.x1)
185
- ],
186
- [
187
- "right",
188
- Math.abs(point.x - box.x2)
189
- ],
190
- [
191
- "top",
192
- Math.abs(point.y - box.y1)
193
- ],
194
- [
195
- "bottom",
196
- Math.abs(point.y - box.y2)
197
- ]
198
- ];
199
- return [
200
- ...gaps
201
- ].sort((a, b)=>a[1] - b[1])[0][0];
191
+ const gaps = [
192
+ ["left", Math.abs(point.x - box.x1)],
193
+ ["right", Math.abs(point.x - box.x2)],
194
+ ["top", Math.abs(point.y - box.y1)],
195
+ ["bottom", Math.abs(point.y - box.y2)]
196
+ ];
197
+ return [...gaps].sort((a, b) => a[1] - b[1])[0][0];
202
198
  }
199
+ /** The arrows that run from one card to another. */
203
200
  export function linksOf(plan) {
204
- const links = [];
205
- for (const arrow of plan.arrows){
206
- const src = cardAt(plan.cards, arrow.from);
207
- const dst = cardAt(plan.cards, arrow.to);
208
- if (src >= 0 && dst >= 0 && src !== dst) links.push({
209
- arrow,
210
- src,
211
- dst
212
- });
213
- }
214
- return links;
201
+ const links = [];
202
+ for (const arrow of plan.arrows) {
203
+ const src = cardAt(plan.cards, arrow.from);
204
+ const dst = cardAt(plan.cards, arrow.to);
205
+ if (src >= 0 && dst >= 0 && src !== dst) links.push({
206
+ arrow,
207
+ src,
208
+ dst
209
+ });
210
+ }
211
+ return links;
215
212
  }
216
213
  function nearestCard(cards, badge) {
217
- const reach = badge.r + BADGE_REACH;
218
- let card = -1;
219
- let cardGap = Number.POSITIVE_INFINITY;
220
- cards.forEach((c, i)=>{
221
- const gap = distanceToBox(c.box, badge.center);
222
- if (gap <= reach && gap < cardGap) {
223
- card = i;
224
- cardGap = gap;
225
- }
226
- });
227
- return card;
214
+ const reach = badge.r + BADGE_REACH;
215
+ let card = -1;
216
+ let cardGap = Number.POSITIVE_INFINITY;
217
+ cards.forEach((c, i) => {
218
+ const gap = distanceToBox(c.box, badge.center);
219
+ if (gap <= reach && gap < cardGap) {
220
+ card = i;
221
+ cardGap = gap;
222
+ }
223
+ });
224
+ return card;
228
225
  }
229
226
  function nearestLink(links, badge) {
230
- let link = null;
231
- let linkGap = Math.max(BADGE_LINK_REACH, badge.r * 3);
232
- for (const l of links){
233
- const gap = distanceToSegment(l.arrow.from, l.arrow.to, badge.center);
234
- if (gap < linkGap) {
235
- link = l.arrow;
236
- linkGap = gap;
237
- }
238
- }
239
- return link;
227
+ let link = null;
228
+ let linkGap = Math.max(BADGE_LINK_REACH, badge.r * 3);
229
+ for (const l of links) {
230
+ const gap = distanceToSegment(l.arrow.from, l.arrow.to, badge.center);
231
+ if (gap < linkGap) {
232
+ link = l.arrow;
233
+ linkGap = gap;
234
+ }
235
+ }
236
+ return link;
240
237
  }
238
+ /** A step number with no card or link to join becomes a small filled circle of its own. */
241
239
  function addMark(plan, badge) {
242
- const side = Math.max(Math.round(badge.r * 2 * plan.scale), MARK_MIN);
243
- const at = toBoard(plan, badge.center);
244
- const frame = frameOf(plan, {
245
- x1: badge.center.x,
246
- y1: badge.center.y,
247
- x2: badge.center.x,
248
- y2: badge.center.y
249
- });
250
- const origin = frameOrigin(plan, frame);
251
- plan.shapes += 1;
252
- plan.ops.push({
253
- kind: "addNode",
254
- node: {
255
- id: freshId(`${plan.node.id}-mark-${plan.shapes}`, plan.taken),
256
- type: "shape",
257
- position: {
258
- x: at.x - origin.x - Math.round(side / 2),
259
- y: at.y - origin.y - Math.round(side / 2)
260
- },
261
- data: {
262
- shape: "ellipse",
263
- color: colorOf(badge.circle),
264
- strokeWidth: 2,
265
- filled: true,
266
- label: badge.label
267
- },
268
- width: side,
269
- height: side,
270
- ...parentOf(plan, frame)
271
- }
272
- });
240
+ const side = Math.max(Math.round(badge.r * 2 * plan.scale), MARK_MIN);
241
+ const at = toBoard(plan, badge.center);
242
+ const frame = frameOf(plan, {
243
+ x1: badge.center.x,
244
+ y1: badge.center.y,
245
+ x2: badge.center.x,
246
+ y2: badge.center.y
247
+ });
248
+ const origin = frameOrigin(plan, frame);
249
+ plan.shapes += 1;
250
+ plan.ops.push({
251
+ kind: "addNode",
252
+ node: {
253
+ id: freshId(`${plan.node.id}-mark-${plan.shapes}`, plan.taken),
254
+ type: "shape",
255
+ position: {
256
+ x: at.x - origin.x - Math.round(side / 2),
257
+ y: at.y - origin.y - Math.round(side / 2)
258
+ },
259
+ data: {
260
+ shape: "ellipse",
261
+ color: colorOf(badge.circle),
262
+ strokeWidth: 2,
263
+ filled: true,
264
+ label: badge.label
265
+ },
266
+ width: side,
267
+ height: side,
268
+ ...parentOf(plan, frame)
269
+ }
270
+ });
273
271
  }
272
+ /** A step number next to a card is its badge, next to a link its label, and otherwise a mark. */
274
273
  export function placeBadges(plan, links) {
275
- const linkLabels = new Map();
276
- for (const badge of plan.loose.badges){
277
- const card = nearestCard(plan.cards, badge);
278
- const link = card < 0 ? nearestLink(links, badge) : null;
279
- if (card >= 0) {
280
- const kicker = plan.cards[card].kicker;
281
- plan.cards[card].kicker = [
282
- badge.label,
283
- kicker
284
- ].filter(Boolean).join(" · ");
285
- } else if (link && !linkLabels.has(link)) linkLabels.set(link, badge.label);
286
- else addMark(plan, badge);
287
- plan.removed.add(badge.circle);
288
- plan.removed.add(badge.text);
289
- }
290
- return linkLabels;
274
+ const linkLabels = new Map();
275
+ for (const badge of plan.loose.badges) {
276
+ const card = nearestCard(plan.cards, badge);
277
+ const link = card < 0 ? nearestLink(links, badge) : null;
278
+ if (card >= 0) {
279
+ const kicker = plan.cards[card].kicker;
280
+ plan.cards[card].kicker = [badge.label, kicker].filter(Boolean).join(" · ");
281
+ } else if (link && !linkLabels.has(link)) linkLabels.set(link, badge.label);
282
+ else addMark(plan, badge);
283
+ plan.removed.add(badge.circle);
284
+ plan.removed.add(badge.text);
285
+ }
286
+ return linkLabels;
291
287
  }
288
+ // =========================
289
+ // CARDS AND EDGES
290
+ // =========================
292
291
  function noteData(plan, card, width, height) {
293
- const accent = accentOf(card.rect.attrs.stroke);
294
- const rx = num(card.rect.attrs.rx);
295
- const textScale = fitScale(card, card.titleSize * plan.scale / NOTE_TITLE_PX, width, height);
296
- return {
297
- nodeType: "note",
298
- label: card.title,
299
- ...card.body.length > 0 ? {
300
- content: card.body.map(escapeMarkdown).join("\n")
301
- } : {},
302
- ...card.kicker ? {
303
- kicker: card.kicker
304
- } : {},
305
- ...accent ? {
306
- accent
307
- } : {},
308
- ...rx < 3 ? {
309
- shape: "rect"
310
- } : {},
311
- userHeight: height,
312
- ...Math.abs(textScale - 1) > 0.02 ? {
313
- textScale
314
- } : {}
315
- };
292
+ const accent = accentOf(card.rect.attrs.stroke);
293
+ const rx = num(card.rect.attrs.rx);
294
+ const textScale = fitScale(card, card.titleSize * plan.scale / NOTE_TITLE_PX, width, height);
295
+ return {
296
+ nodeType: "note",
297
+ label: card.title,
298
+ ...card.body.length > 0 ? { content: card.body.map(escapeMarkdown).join("\n") } : {},
299
+ ...card.kicker ? { kicker: card.kicker } : {},
300
+ ...accent ? { accent } : {},
301
+ ...rx < 3 ? { shape: "rect" } : {},
302
+ userHeight: height,
303
+ ...Math.abs(textScale - 1) > .02 ? { textScale } : {}
304
+ };
316
305
  }
317
306
  export function addCards(plan) {
318
- const cardFrames = plan.cards.map((card)=>frameOf(plan, card.box));
319
- plan.cards.forEach((card, i)=>{
320
- for (const part of card.claimed)plan.removed.add(part);
321
- const frame = cardFrames[i];
322
- const { width, height } = sizeOf(plan, card.box);
323
- plan.ops.push({
324
- kind: "addNode",
325
- node: {
326
- id: plan.cardIds[i],
327
- type: "note",
328
- position: placed(plan, card.box, frame),
329
- data: noteData(plan, card, width, height),
330
- style: {
331
- width
332
- },
333
- width,
334
- ...parentOf(plan, frame)
335
- }
336
- });
337
- });
307
+ const cardFrames = plan.cards.map((card) => frameOf(plan, card.box));
308
+ plan.cards.forEach((card, i) => {
309
+ for (const part of card.claimed) plan.removed.add(part);
310
+ const frame = cardFrames[i];
311
+ const { width, height } = sizeOf(plan, card.box);
312
+ plan.ops.push({
313
+ kind: "addNode",
314
+ node: {
315
+ id: plan.cardIds[i],
316
+ type: "note",
317
+ position: placed(plan, card.box, frame),
318
+ data: noteData(plan, card, width, height),
319
+ style: { width },
320
+ width,
321
+ ...parentOf(plan, frame)
322
+ }
323
+ });
324
+ });
338
325
  }
339
326
  export function addEdges(plan, links, linkLabels) {
340
- links.forEach(({ arrow, src: a, dst: b }, i)=>{
341
- plan.removed.add(arrow.el);
342
- if (arrow.head) plan.removed.add(arrow.head);
343
- const reversed = arrow.startArrow && !arrow.endArrow;
344
- const [src, dst] = reversed ? [
345
- b,
346
- a
347
- ] : [
348
- a,
349
- b
350
- ];
351
- const [srcPt, dstPt] = reversed ? [
352
- arrow.to,
353
- arrow.from
354
- ] : [
355
- arrow.from,
356
- arrow.to
357
- ];
358
- const label = linkLabels.get(arrow);
359
- plan.ops.push({
360
- kind: "addEdge",
361
- edge: {
362
- id: freshId(`${plan.node.id}-link-${i + 1}`, plan.taken),
363
- source: plan.cardIds[src],
364
- target: plan.cardIds[dst],
365
- sourceHandle: sideOf(plan.cards[src].box, srcPt),
366
- targetHandle: sideOf(plan.cards[dst].box, dstPt),
367
- ...label ? {
368
- label
369
- } : {},
370
- data: {
371
- ...plan.edgeData,
372
- endArrow: arrow.startArrow || arrow.endArrow,
373
- startArrow: arrow.startArrow && arrow.endArrow
374
- }
375
- }
376
- });
377
- });
327
+ links.forEach(({ arrow, src: a, dst: b }, i) => {
328
+ plan.removed.add(arrow.el);
329
+ if (arrow.head) plan.removed.add(arrow.head);
330
+ // An arrow drawn tip-first reads backwards: the link runs from the tail to the tip.
331
+ const reversed = arrow.startArrow && !arrow.endArrow;
332
+ const [src, dst] = reversed ? [b, a] : [a, b];
333
+ const [srcPt, dstPt] = reversed ? [arrow.to, arrow.from] : [arrow.from, arrow.to];
334
+ const label = linkLabels.get(arrow);
335
+ plan.ops.push({
336
+ kind: "addEdge",
337
+ edge: {
338
+ id: freshId(`${plan.node.id}-link-${i + 1}`, plan.taken),
339
+ source: plan.cardIds[src],
340
+ target: plan.cardIds[dst],
341
+ sourceHandle: sideOf(plan.cards[src].box, srcPt),
342
+ targetHandle: sideOf(plan.cards[dst].box, dstPt),
343
+ ...label ? { label } : {},
344
+ data: {
345
+ ...plan.edgeData,
346
+ endArrow: arrow.startArrow || arrow.endArrow,
347
+ startArrow: arrow.startArrow && arrow.endArrow
348
+ }
349
+ }
350
+ });
351
+ });
378
352
  }