@heyramzi/ui 2.9.0 → 2.10.1

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 (313) hide show
  1. package/dist/agents/helpers.js +52 -0
  2. package/dist/agents/index.js +3 -0
  3. package/dist/agents/page-state.js +65 -0
  4. package/dist/agents/types.js +1 -0
  5. package/dist/agents/vendor/color.js +149 -0
  6. package/dist/agents/vendor/draw.js +548 -0
  7. package/dist/agents/vendor/engine.js +632 -0
  8. package/dist/agents/vendor/mesh.js +727 -0
  9. package/dist/agents/vendor/plastic.js +3273 -0
  10. package/dist/agents/vendor/presets.js +171 -0
  11. package/dist/agents/vendor/shapes.js +24 -0
  12. package/dist/agents/vendor/ticker.js +56 -0
  13. package/dist/agents/vendor/types.js +0 -0
  14. package/dist/agents/vendor/wear.js +1525 -0
  15. package/dist/ai/ai-button/index.js +2 -0
  16. package/dist/ai/confirmation/confirmation-context.svelte.js +12 -0
  17. package/dist/ai/confirmation/index.js +9 -0
  18. package/dist/ai/generation-progress/index.js +2 -0
  19. package/dist/ai/index.js +11 -0
  20. package/dist/ai/loading-state/index.js +3 -0
  21. package/dist/ai/prompt-input/index.js +6 -0
  22. package/dist/ai/reasoning/index.js +4 -0
  23. package/dist/ai/reasoning/reasoning-context.svelte.js +12 -0
  24. package/dist/ai/response/index.js +3 -0
  25. package/dist/ai/suggestion/index.js +3 -0
  26. package/dist/ai/thinking-dots/index.js +3 -0
  27. package/dist/ai/typing-dots/index.js +2 -0
  28. package/dist/ai/voice-waveform/index.js +3 -0
  29. package/dist/animations/text-swap.js +30 -0
  30. package/dist/brand/index.js +65 -0
  31. package/dist/canvas/card/resize.js +10 -0
  32. package/dist/canvas/edges/edge-path.js +284 -0
  33. package/dist/canvas/editor/context-menu.js +42 -0
  34. package/dist/canvas/editor/dispatch.svelte.js +157 -0
  35. package/dist/canvas/editor/editor-context.js +8 -0
  36. package/dist/canvas/editor/editor-mode.js +0 -0
  37. package/dist/canvas/editor/frame.js +87 -0
  38. package/dist/canvas/editor/hand-context.js +8 -0
  39. package/dist/canvas/editor/history-core.js +221 -0
  40. package/dist/canvas/editor/keymap.js +193 -0
  41. package/dist/canvas/editor/layout-actions.js +170 -0
  42. package/dist/canvas/editor/lock.js +65 -0
  43. package/dist/canvas/editor/rebase.js +279 -0
  44. package/dist/canvas/editor/style.js +174 -0
  45. package/dist/canvas/editor/text-field-context.js +8 -0
  46. package/dist/canvas/editor/z-order.js +82 -0
  47. package/dist/canvas/headless/alignment.js +20 -0
  48. package/dist/canvas/headless/break-apart-find.js +324 -0
  49. package/dist/canvas/headless/break-apart-pieces.js +74 -0
  50. package/dist/canvas/headless/break-apart-plan.js +378 -0
  51. package/dist/canvas/headless/break-apart-style.js +93 -0
  52. package/dist/canvas/headless/break-apart.js +206 -0
  53. package/dist/canvas/headless/canvas-cleanup.js +70 -0
  54. package/dist/canvas/headless/edge-anchors.js +134 -0
  55. package/dist/canvas/headless/edge-memo.js +28 -0
  56. package/dist/canvas/headless/edge-serialization.js +31 -0
  57. package/dist/canvas/headless/figure-geometry.js +331 -0
  58. package/dist/canvas/headless/fit-viewport.js +82 -0
  59. package/dist/canvas/headless/grid.js +96 -0
  60. package/dist/canvas/headless/icon-node.js +45 -0
  61. package/dist/canvas/headless/index.js +1 -0
  62. package/dist/canvas/headless/linkify.js +25 -0
  63. package/dist/canvas/headless/pen-marks.js +115 -0
  64. package/dist/canvas/headless/sketch.js +250 -0
  65. package/dist/canvas/headless/snap-guides.js +100 -0
  66. package/dist/canvas/headless/svg-figure.js +137 -0
  67. package/dist/canvas/headless/table-layout.js +161 -0
  68. package/dist/canvas/index.js +25 -0
  69. package/dist/canvas/lod/lod-mode.svelte.js +22 -0
  70. package/dist/canvas/lod/render-all.svelte.js +45 -0
  71. package/dist/canvas/ops/arrange.js +83 -0
  72. package/dist/canvas/ops/ops.js +318 -0
  73. package/dist/canvas/pen/colors.js +41 -0
  74. package/dist/canvas/pen/eraser.js +35 -0
  75. package/dist/canvas/pen/geometry.js +143 -0
  76. package/dist/canvas/pen/index.js +9 -0
  77. package/dist/canvas/pen/ops.js +15 -0
  78. package/dist/canvas/pen/pen-session.js +171 -0
  79. package/dist/canvas/pen/plugin.js +129 -0
  80. package/dist/canvas/pen/shapes.js +80 -0
  81. package/dist/canvas/pen/stroke-path.js +37 -0
  82. package/dist/canvas/pen/types.js +30 -0
  83. package/dist/canvas/pen/ui.js +20 -0
  84. package/dist/canvas/plugin.js +51 -0
  85. package/dist/canvas/screen/glyphs.js +24 -0
  86. package/dist/canvas/screen/labels.js +24 -0
  87. package/dist/canvas/screen/parts.js +140 -0
  88. package/dist/canvas/screen/plugin.js +71 -0
  89. package/dist/canvas/screen/types.js +44 -0
  90. package/dist/canvas/screen/ui.js +13 -0
  91. package/dist/canvas/test-utils.js +18 -0
  92. package/dist/canvas/types.js +0 -0
  93. package/dist/canvas/ui/forward-wheel.js +27 -0
  94. package/dist/canvas/ui/icons.js +133 -0
  95. package/dist/canvas/ui/place-panel.js +57 -0
  96. package/dist/canvas/ui/shortcut-tip.js +18 -0
  97. package/dist/canvas/ui/tools.js +411 -0
  98. package/dist/charts/dashboard/chart.js +1 -0
  99. package/dist/charts/dashboard/data-table.js +7 -0
  100. package/dist/charts/dashboard/index.js +13 -0
  101. package/dist/charts/dashboard/windows.js +5 -0
  102. package/dist/charts/edge-fade.js +7 -0
  103. package/dist/charts/hover-band.js +13 -0
  104. package/dist/charts/index.js +19 -0
  105. package/dist/charts/motion.js +15 -0
  106. package/dist/charts/primitives/chart-utils.js +22 -0
  107. package/dist/charts/primitives/index.js +4 -0
  108. package/dist/components/copy/clipboard.js +38 -0
  109. package/dist/components/copy/copied.svelte.js +26 -0
  110. package/dist/components/copy/index.js +3 -0
  111. package/dist/components/data-table/data-table.js +55 -0
  112. package/dist/components/data-table/index.js +7 -0
  113. package/dist/components/docs/index.js +3 -0
  114. package/dist/components/docs/scroll-spy.svelte.js +71 -0
  115. package/dist/components/field-row/index.js +1 -0
  116. package/dist/components/hold-to-confirm/index.js +1 -0
  117. package/dist/components/icon/index.js +1 -0
  118. package/dist/components/language-flag/index.js +1 -0
  119. package/dist/components/list-page/filter-section.js +4 -0
  120. package/dist/components/list-page/index.js +25 -0
  121. package/dist/components/list-page/list-table.js +5 -0
  122. package/dist/components/list-page/queue-rail.js +3 -0
  123. package/dist/components/list-page/section-host.js +8 -0
  124. package/dist/components/list-page/step.js +5 -0
  125. package/dist/components/list-page/types.js +0 -0
  126. package/dist/components/locale-switch/index.js +1 -0
  127. package/dist/components/mark/index.js +1 -0
  128. package/dist/components/open-in/index.js +1 -0
  129. package/dist/components/picker/index.js +3 -0
  130. package/dist/components/picker/trigger.js +33 -0
  131. package/dist/components/picker/types.js +0 -0
  132. package/dist/components/section-card/index.js +1 -0
  133. package/dist/components/section-header/index.js +2 -0
  134. package/dist/components/segmented/collapse.js +32 -0
  135. package/dist/components/segmented/index.js +8 -0
  136. package/dist/components/segmented/segmented.js +50 -0
  137. package/dist/components/star-rating-input/index.js +2 -0
  138. package/dist/components/svg/index.js +1 -0
  139. package/dist/components/table-view/index.js +1 -0
  140. package/dist/components/table-view/types.js +0 -0
  141. package/dist/components/tags-input/index.js +2 -0
  142. package/dist/components/tags-input/tags.js +90 -0
  143. package/dist/components/theme/index.js +3 -0
  144. package/dist/components/toolbar/index.js +3 -0
  145. package/dist/components/toolbar/toolbar-trigger.js +4 -0
  146. package/dist/editor/anchors.js +17 -0
  147. package/dist/editor/block-id.js +62 -0
  148. package/dist/editor/block-ops.js +105 -0
  149. package/dist/editor/doc-blocks.js +181 -0
  150. package/dist/editor/extensions.js +200 -0
  151. package/dist/editor/index.js +4 -0
  152. package/dist/editor/markdown-test-helpers.js +6 -0
  153. package/dist/editor/markdown.js +486 -0
  154. package/dist/editor/serializer.js +268 -0
  155. package/dist/editor/slash-items.js +104 -0
  156. package/dist/editor/youtube-url.js +27 -0
  157. package/dist/effects/animated-beam/index.js +1 -0
  158. package/dist/effects/animated-beam/path-calculator.svelte.js +42 -0
  159. package/dist/effects/animated-beam/types.js +0 -0
  160. package/dist/effects/border-beam/css.js +420 -0
  161. package/dist/effects/border-beam/presets.js +1792 -0
  162. package/dist/effects/border-beam/register-properties.js +39 -0
  163. package/dist/effects/border-beam/types.js +0 -0
  164. package/dist/effects/image-comparison/compare.js +4 -0
  165. package/dist/effects/image-reveal/index.js +1 -0
  166. package/dist/effects/image-reveal/types.js +0 -0
  167. package/dist/effects/magnetic/magnetic.js +54 -0
  168. package/dist/effects/reveal/index.js +4 -0
  169. package/dist/effects/reveal/observe.js +43 -0
  170. package/dist/effects/reveal/types.js +0 -0
  171. package/dist/effects/shader-gradient/color.js +34 -0
  172. package/dist/effects/shader-gradient/index.js +1 -0
  173. package/dist/effects/shader-gradient/renderer.js +320 -0
  174. package/dist/effects/shader-gradient/shaders.js +280 -0
  175. package/dist/effects/shader-gradient/types.js +0 -0
  176. package/dist/icons/apple-symbol-exports.js +126 -0
  177. package/dist/icons/sf-symbols.js +465 -0
  178. package/dist/icons/trades.js +34 -0
  179. package/dist/inbox/index.js +4 -0
  180. package/dist/inbox/logic.js +156 -0
  181. package/dist/inbox/types.js +46 -0
  182. package/dist/index.js +18 -0
  183. package/dist/lib/arrive.js +13 -0
  184. package/dist/lib/icon-types.js +0 -0
  185. package/dist/lib/initials.js +17 -0
  186. package/dist/lib/layout.js +1 -0
  187. package/dist/lib/panel.js +3 -0
  188. package/dist/lib/result.js +26 -0
  189. package/dist/lib/sliding-pill.js +45 -0
  190. package/dist/lib/theme-transition.js +53 -0
  191. package/dist/lib/tone.js +32 -0
  192. package/dist/lib/utils.js +5 -0
  193. package/dist/onboarding/define-tours.js +28 -0
  194. package/dist/onboarding/index.js +4 -0
  195. package/dist/onboarding/onboarding.svelte.js +15 -0
  196. package/dist/onboarding/pick.js +16 -0
  197. package/dist/onboarding/stores.js +50 -0
  198. package/dist/onboarding/types.js +1 -0
  199. package/dist/onboarding/wait.js +25 -0
  200. package/dist/panel/index.js +1 -0
  201. package/dist/panel/side-panel-types.js +0 -0
  202. package/dist/record/demo-data.js +512 -0
  203. package/dist/record/fields.js +148 -0
  204. package/dist/record/format.js +97 -0
  205. package/dist/record/gutter.js +1 -0
  206. package/dist/record/index.js +15 -0
  207. package/dist/record/kinds.js +110 -0
  208. package/dist/record/logic.js +255 -0
  209. package/dist/record/types.js +52 -0
  210. package/dist/shadcn/accordion/index.js +5 -0
  211. package/dist/shadcn/alert/index.js +5 -0
  212. package/dist/shadcn/alert-dialog/index.js +12 -0
  213. package/dist/shadcn/attachment/index.js +10 -0
  214. package/dist/shadcn/avatar/index.js +7 -0
  215. package/dist/shadcn/badge/index.js +2 -0
  216. package/dist/shadcn/breadcrumb/index.js +8 -0
  217. package/dist/shadcn/bubble/index.js +5 -0
  218. package/dist/shadcn/button/index.js +2 -0
  219. package/dist/shadcn/calendar/index.js +19 -0
  220. package/dist/shadcn/card/index.js +8 -0
  221. package/dist/shadcn/checkbox/index.js +2 -0
  222. package/dist/shadcn/code-block/index.js +9 -0
  223. package/dist/shadcn/collapsible/index.js +4 -0
  224. package/dist/shadcn/command/index.js +13 -0
  225. package/dist/shadcn/context-menu/index.js +17 -0
  226. package/dist/shadcn/dialog/index.js +11 -0
  227. package/dist/shadcn/dropdown-menu/index.js +19 -0
  228. package/dist/shadcn/dropdown-menu/menu-item.js +23 -0
  229. package/dist/shadcn/empty/index.js +7 -0
  230. package/dist/shadcn/field/index.js +12 -0
  231. package/dist/shadcn/hooks/highlighter.js +92 -0
  232. package/dist/shadcn/hooks/is-mobile.svelte.js +7 -0
  233. package/dist/shadcn/hooks/use-highlighted-code.svelte.js +65 -0
  234. package/dist/shadcn/hover-card/index.js +5 -0
  235. package/dist/shadcn/input/index.js +3 -0
  236. package/dist/shadcn/input-otp/index.js +5 -0
  237. package/dist/shadcn/kbd/index.js +3 -0
  238. package/dist/shadcn/label/index.js +2 -0
  239. package/dist/shadcn/marker/index.js +4 -0
  240. package/dist/shadcn/message/index.js +7 -0
  241. package/dist/shadcn/message-scroller/index.js +8 -0
  242. package/dist/shadcn/popover/index.js +9 -0
  243. package/dist/shadcn/progress/index.js +2 -0
  244. package/dist/shadcn/progress/tone.js +11 -0
  245. package/dist/shadcn/radio-group/index.js +3 -0
  246. package/dist/shadcn/scroll-area/index.js +3 -0
  247. package/dist/shadcn/select/index.js +12 -0
  248. package/dist/shadcn/separator/index.js +2 -0
  249. package/dist/shadcn/sheet/index.js +11 -0
  250. package/dist/shadcn/sidebar/constants.js +6 -0
  251. package/dist/shadcn/sidebar/context.svelte.js +38 -0
  252. package/dist/shadcn/sidebar/index.js +25 -0
  253. package/dist/shadcn/skeleton/index.js +2 -0
  254. package/dist/shadcn/slider/index.js +2 -0
  255. package/dist/shadcn/sonner/index.js +1 -0
  256. package/dist/shadcn/spinner/index.js +1 -0
  257. package/dist/shadcn/switch/index.js +2 -0
  258. package/dist/shadcn/table/index.js +9 -0
  259. package/dist/shadcn/tabs/index.js +5 -0
  260. package/dist/shadcn/textarea/index.js +2 -0
  261. package/dist/shadcn/toggle/index.js +3 -0
  262. package/dist/shadcn/toggle-group/index.js +3 -0
  263. package/dist/shadcn/tooltip/context.js +8 -0
  264. package/dist/shadcn/tooltip/index.js +6 -0
  265. package/dist/shadcn/trace/index.js +9 -0
  266. package/dist/slides/bridges.js +66 -0
  267. package/dist/slides/deck-controller.svelte.js +221 -0
  268. package/dist/slides/export/capture.js +81 -0
  269. package/dist/slides/export/ffmpeg.js +85 -0
  270. package/dist/slides/export/index.js +4 -0
  271. package/dist/slides/export/launch.js +8 -0
  272. package/dist/slides/export/pdf.js +78 -0
  273. package/dist/slides/export/types.js +0 -0
  274. package/dist/slides/index.js +7 -0
  275. package/dist/slides/labels.js +26 -0
  276. package/dist/slides/layout.js +47 -0
  277. package/dist/slides/model.js +9 -0
  278. package/dist/slides/navigation.js +89 -0
  279. package/dist/slides/types.js +6 -0
  280. package/dist/status/define-statuses.js +16 -0
  281. package/dist/status/index.js +4 -0
  282. package/dist/status/status-types.js +0 -0
  283. package/dist/test-setup.js +26 -0
  284. package/dist/thread/index.js +3 -0
  285. package/dist/thread/logic.js +94 -0
  286. package/dist/thread/types.js +23 -0
  287. package/dist/work/board-order.js +109 -0
  288. package/dist/work/dates.js +19 -0
  289. package/dist/work/detail/detail-types.js +20 -0
  290. package/dist/work/docs/edited.js +39 -0
  291. package/dist/work/docs/index.js +7 -0
  292. package/dist/work/docs/labels.js +38 -0
  293. package/dist/work/docs/menu.js +121 -0
  294. package/dist/work/docs/outline.js +39 -0
  295. package/dist/work/gantt/logic.js +260 -0
  296. package/dist/work/inbox/inbox-logic.js +94 -0
  297. package/dist/work/inbox/inbox-statuses.js +86 -0
  298. package/dist/work/inbox/inbox-types.js +0 -0
  299. package/dist/work/inbox/index.js +1 -0
  300. package/dist/work/index.js +21 -0
  301. package/dist/work/kanban-drag.js +25 -0
  302. package/dist/work/kanban-types.js +0 -0
  303. package/dist/work/presets/sales.js +35 -0
  304. package/dist/work/reorder.js +10 -0
  305. package/dist/work/task-types.js +0 -0
  306. package/dist/work/time/duration.js +60 -0
  307. package/dist/work/time/time-types.js +25 -0
  308. package/dist/work/use-kanban-dnd.svelte.js +128 -0
  309. package/package.json +45 -3
  310. package/src/components/toolbar/toolbar-trigger.ts +8 -2
  311. package/src/record/demo-data.ts +4 -4
  312. package/src/record/record-demo.svelte +2 -2
  313. package/src/status/status-dot.svelte +0 -2
@@ -0,0 +1,378 @@
1
+ import { distanceToBox, distanceToSegment, areaOf, contains, num, solid, within } from "./figure-geometry.js";
2
+ import { LAND_TOLERANCE } from "./break-apart-find.js";
3
+ import { NOTE_TITLE_PX, accentOf, escapeMarkdown, fitScale, inked, penColorOf } from "./break-apart-style.js";
4
+ const LABEL_H = 27;
5
+ const BADGE_REACH = 6;
6
+ const BADGE_LINK_REACH = 60;
7
+ const MARK_MIN = 24;
8
+ 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;
13
+ }
14
+ 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
+ };
38
+ }
39
+ 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
+ };
44
+ }
45
+ 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;
53
+ }
54
+ 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
+ });
64
+ }
65
+ 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
+ };
75
+ }
76
+ 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
+ } : {};
83
+ }
84
+ 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
+ }
90
+ 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));
93
+ }
94
+ function colorOf(el) {
95
+ const stroked = solid(el.attrs.stroke) ? penColorOf(el.attrs.stroke) : undefined;
96
+ return stroked ?? penColorOf(el.attrs.fill) ?? "ink";
97
+ }
98
+ 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
+ });
127
+ }
128
+ 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
+ });
148
+ }
149
+ 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
+ }
164
+ }
165
+ 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;
179
+ }
180
+ 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];
202
+ }
203
+ 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;
215
+ }
216
+ 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;
228
+ }
229
+ 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;
240
+ }
241
+ 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
+ });
273
+ }
274
+ 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;
291
+ }
292
+ 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
+ };
316
+ }
317
+ 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
+ });
338
+ }
339
+ 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
+ });
378
+ }
@@ -0,0 +1,93 @@
1
+ import { SKETCH_ACCENTS, sketchColors } from "./sketch.js";
2
+ import { PEN_HEX } from "../pen/colors.js";
3
+ export const NOTE_TITLE_PX = 19;
4
+ export const NOTE_BODY_PX = 15;
5
+ const NOTE_TITLE_LINE = 26;
6
+ const NOTE_BODY_LINE = 21.75;
7
+ const NOTE_PAD_X = 40;
8
+ const NOTE_PAD_Y = 32;
9
+ const NOTE_GAP = 10;
10
+ const NOTE_GLYPH = 0.55;
11
+ const NOTE_GLYPH_CAPS = 0.7;
12
+ const FIT_MARGIN = 0.92;
13
+ function channels(hex) {
14
+ const m = hex?.trim().match(/^#([\da-f]{3}|[\da-f]{6})$/i);
15
+ if (!m) return null;
16
+ const full = m[1].length === 3 ? [
17
+ ...m[1]
18
+ ].map((c)=>c + c).join("") : m[1];
19
+ return [
20
+ 0,
21
+ 2,
22
+ 4
23
+ ].map((i)=>Number.parseInt(full.slice(i, i + 2), 16));
24
+ }
25
+ export function accentOf(stroke) {
26
+ const rgb = channels(stroke);
27
+ if (!rgb) return undefined;
28
+ const spread = Math.max(...rgb) - Math.min(...rgb);
29
+ if (spread < 40) return undefined;
30
+ let best = null;
31
+ for (const name of SKETCH_ACCENTS){
32
+ const target = channels(sketchColors(name).stroke);
33
+ if (!target) continue;
34
+ const distance = Math.hypot(...rgb.map((v, i)=>v - target[i]));
35
+ if (!best || distance < best.distance) best = {
36
+ name,
37
+ distance
38
+ };
39
+ }
40
+ return best && best.name !== "ink" && best.distance < 90 ? best.name : undefined;
41
+ }
42
+ export function escapeMarkdown(line) {
43
+ return line.replace(/([\\`*_~[\]])/g, "\\$1").replace(/^([#>+-]|\d+\.)/, "\\$1");
44
+ }
45
+ function hueOf([r, g, b]) {
46
+ const max = Math.max(r, g, b);
47
+ const d = max - Math.min(r, g, b);
48
+ if (d === 0) return 0;
49
+ const h = max === r ? (g - b) / d % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
50
+ return (h * 60 + 360) % 360;
51
+ }
52
+ export function penColorOf(color) {
53
+ const rgb = channels(color);
54
+ if (!rgb || Math.max(...rgb) - Math.min(...rgb) < 40) return undefined;
55
+ const hue = hueOf(rgb);
56
+ let best = null;
57
+ for (const name of Object.keys(PEN_HEX)){
58
+ const target = channels(PEN_HEX[name].light);
59
+ if (!target || name === "ink") continue;
60
+ const diff = Math.abs(hue - hueOf(target));
61
+ const gap = Math.min(diff, 360 - diff);
62
+ if (!best || gap < best.gap) best = {
63
+ name,
64
+ gap
65
+ };
66
+ }
67
+ return best?.name;
68
+ }
69
+ export function inked(color) {
70
+ const rgb = channels(color);
71
+ return Boolean(rgb) && Math.min(...rgb) < 240;
72
+ }
73
+ export const round1 = (n)=>Math.round(n * 10) / 10;
74
+ export function fitScale(card, matched, boxW, boxH) {
75
+ const glyph = (text)=>text === text.toUpperCase() ? NOTE_GLYPH_CAPS : NOTE_GLYPH;
76
+ const lines = (text, px, inner)=>{
77
+ const widest = Math.max(...text.split(/\s+/).map((w)=>w.length)) * px * glyph(text);
78
+ if (widest > inner) return 99;
79
+ return Math.max(1, Math.ceil(text.length * px * glyph(text) / inner));
80
+ };
81
+ const heightAt = (z)=>{
82
+ const inner = Math.max(boxW / z - NOTE_PAD_X, 40);
83
+ const title = lines([
84
+ card.kicker,
85
+ card.title
86
+ ].filter(Boolean).join(" "), NOTE_TITLE_PX, inner);
87
+ const body = card.body.reduce((n, line)=>n + lines(line, NOTE_BODY_PX, inner), 0);
88
+ return (NOTE_PAD_Y + title * NOTE_TITLE_LINE + (body ? NOTE_GAP + body * NOTE_BODY_LINE : 0)) * z;
89
+ };
90
+ let z = matched;
91
+ for(let i = 0; i < 16 && z > 0.2 && heightAt(z) > boxH * FIT_MARGIN; i += 1)z *= 0.92;
92
+ return Math.round(z * 1000) / 1000;
93
+ }
@@ -0,0 +1,206 @@
1
+ import { sanitizeFigureSvg } from "./svg-figure.js";
2
+ import { PEN_HEX } from "../pen/colors.js";
3
+ import { isEl, translateOf, union, viewBoxOf, boxOfEl } from "./figure-geometry.js";
4
+ import { findPieces } from "./break-apart-pieces.js";
5
+ import { NOTE_BODY_PX, NOTE_TITLE_PX, round1 } from "./break-apart-style.js";
6
+ import { addCards, addEdges, addFrames, addShapes, freshId, linksOf, placeBadges, startPlan } from "./break-apart-plan.js";
7
+ const TEXT_NODE = 3;
8
+ const ELEMENT_NODE = 1;
9
+ const CAPTION_GAP = 8;
10
+ export function treeFromElement(el) {
11
+ const attrs = {};
12
+ for (const attr of Array.from(el.attributes ?? []))attrs[attr.name] = attr.value;
13
+ const children = [];
14
+ for (const child of Array.from(el.childNodes ?? [])){
15
+ if (child.nodeType === TEXT_NODE) children.push(child.textContent ?? "");
16
+ else if (child.nodeType === ELEMENT_NODE) children.push(treeFromElement(child));
17
+ }
18
+ return {
19
+ tag: el.localName ?? "",
20
+ attrs,
21
+ children
22
+ };
23
+ }
24
+ function escapeText(value) {
25
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
26
+ }
27
+ function serialize(child) {
28
+ if (typeof child === "string") return escapeText(child);
29
+ const attrs = Object.entries(child.attrs).map(([name, value])=>` ${name}="${value.replace(/&/g, "&amp;").replace(/"/g, "&quot;")}"`).join("");
30
+ return `<${child.tag}${attrs}>${child.children.map(serialize).join("")}</${child.tag}>`;
31
+ }
32
+ function prune(el, removed) {
33
+ const children = [];
34
+ for (const child of el.children){
35
+ if (removed.has(child)) continue;
36
+ if (isEl(child) && child.tag === "g") {
37
+ const kept = prune(child, removed);
38
+ if (kept.children.some((c)=>isEl(c) ? true : c.trim() !== "")) children.push(kept);
39
+ continue;
40
+ }
41
+ children.push(child);
42
+ }
43
+ return {
44
+ ...el,
45
+ children
46
+ };
47
+ }
48
+ function leftover(el) {
49
+ let box = null;
50
+ for (const child of el.children){
51
+ if (!isEl(child) || child.tag === "defs" || child.tag === "title" || child.tag === "desc") continue;
52
+ if (child.tag === "g") {
53
+ const shift = translateOf(child) ?? {
54
+ x: 0,
55
+ y: 0
56
+ };
57
+ const inner = leftover(child);
58
+ if (inner) {
59
+ box = union(box, {
60
+ x1: inner.x1 + shift.x,
61
+ y1: inner.y1 + shift.y,
62
+ x2: inner.x2 + shift.x,
63
+ y2: inner.y2 + shift.y
64
+ });
65
+ }
66
+ continue;
67
+ }
68
+ box = union(box, boxOfEl(child, {
69
+ x: 0,
70
+ y: 0
71
+ }));
72
+ }
73
+ return box;
74
+ }
75
+ function replaceWithText(plan) {
76
+ const { node, taken, ops, drawing, scale, artTop } = plan;
77
+ ops.push({
78
+ kind: "removeNode",
79
+ nodeId: node.id
80
+ });
81
+ const accent = node.data.accent;
82
+ const color = Object.keys(PEN_HEX).find((c)=>c === accent) ?? "ink";
83
+ const textWidth = Math.round(plan.artWidth);
84
+ const words = [
85
+ [
86
+ String(node.data.label ?? ""),
87
+ NOTE_TITLE_PX,
88
+ node.position.y
89
+ ],
90
+ [
91
+ String(node.data.caption ?? ""),
92
+ NOTE_BODY_PX,
93
+ Math.round(node.position.y + artTop + (drawing.y2 - drawing.y1) * scale + CAPTION_GAP)
94
+ ]
95
+ ];
96
+ for (const [i, [text, fontSize, y]] of words.entries()){
97
+ if (!text) continue;
98
+ ops.push({
99
+ kind: "addNode",
100
+ node: {
101
+ id: freshId(`${node.id}-text-${i === 0 ? "title" : "caption"}`, taken),
102
+ type: "text",
103
+ position: {
104
+ x: node.position.x,
105
+ y
106
+ },
107
+ data: {
108
+ text,
109
+ color,
110
+ fontSize,
111
+ fixedWidth: true
112
+ },
113
+ width: textWidth,
114
+ height: Math.round(fontSize * 1.4),
115
+ ...node.parentId ? {
116
+ parentId: node.parentId
117
+ } : {}
118
+ }
119
+ });
120
+ }
121
+ }
122
+ function shrinkFigure(plan, rest, ink) {
123
+ const { node, drawing, scale, ops } = plan;
124
+ const crop = {
125
+ x1: Math.max(ink.x1, drawing.x1),
126
+ y1: drawing.y1,
127
+ x2: Math.min(ink.x2, drawing.x2),
128
+ y2: drawing.y2
129
+ };
130
+ const cropW = crop.x2 - crop.x1;
131
+ const cropH = crop.y2 - crop.y1;
132
+ const markup = sanitizeFigureSvg(serialize({
133
+ ...rest,
134
+ attrs: {
135
+ ...rest.attrs,
136
+ viewBox: `${round1(crop.x1)} ${round1(crop.y1)} ${round1(cropW)} ${round1(cropH)}`
137
+ }
138
+ }));
139
+ const width = Math.round(cropW * scale);
140
+ const board = node.data.board;
141
+ ops.push({
142
+ kind: "patchNode",
143
+ nodeId: node.id,
144
+ data: {
145
+ svg: markup ?? "",
146
+ ...board?.w ? {
147
+ board: {
148
+ ...board,
149
+ w: width
150
+ }
151
+ } : {}
152
+ },
153
+ width,
154
+ style: {
155
+ width
156
+ }
157
+ });
158
+ const shift = {
159
+ x: Math.round(node.position.x + (crop.x1 - drawing.x1) * scale),
160
+ y: Math.round(node.position.y + (crop.y1 - drawing.y1) * scale)
161
+ };
162
+ if (shift.x !== node.position.x || shift.y !== node.position.y) {
163
+ ops.push({
164
+ kind: "moveNode",
165
+ nodeId: node.id,
166
+ position: shift
167
+ });
168
+ }
169
+ }
170
+ export function breakApartFigure(input) {
171
+ const { node, tree } = input;
172
+ const drawing = viewBoxOf(tree);
173
+ const artWidth = input.art?.width ?? node.width;
174
+ if (!drawing || !artWidth) return null;
175
+ const pieces = findPieces(tree, drawing);
176
+ if (!pieces) return null;
177
+ const plan = startPlan(input, pieces, drawing, artWidth);
178
+ addFrames(plan);
179
+ addShapes(plan);
180
+ const links = linksOf(plan);
181
+ const linkLabels = placeBadges(plan, links);
182
+ addCards(plan);
183
+ addEdges(plan, links, linkLabels);
184
+ const counts = {
185
+ cards: plan.cards.length,
186
+ edges: links.length,
187
+ shapes: plan.shapes,
188
+ frames: plan.loose.frames.length
189
+ };
190
+ const rest = prune(tree, plan.removed);
191
+ const ink = leftover(rest);
192
+ if (!ink) {
193
+ replaceWithText(plan);
194
+ return {
195
+ ops: plan.ops,
196
+ ...counts,
197
+ remains: false
198
+ };
199
+ }
200
+ shrinkFigure(plan, rest, ink);
201
+ return {
202
+ ops: plan.ops,
203
+ ...counts,
204
+ remains: true
205
+ };
206
+ }