@heyramzi/ui 2.11.0 → 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 (267) 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/components/tags-input/tags-input.svelte +2 -0
  267. package/src/editor/markdown.ts +30 -35
@@ -1,5 +1,7 @@
1
+ // Break apart, the look: which sketch accent or pen colour a drawn colour maps to, and how big a note sets.
1
2
  import { SKETCH_ACCENTS, sketchColors } from "./sketch.js";
2
3
  import { PEN_HEX } from "../pen/colors.js";
4
+ /** A note's type and spacing in px (sketch-note.svelte), for estimating how tall a card sets. */
3
5
  export const NOTE_TITLE_PX = 19;
4
6
  export const NOTE_BODY_PX = 15;
5
7
  const NOTE_TITLE_LINE = 26;
@@ -7,87 +9,90 @@ const NOTE_BODY_LINE = 21.75;
7
9
  const NOTE_PAD_X = 40;
8
10
  const NOTE_PAD_Y = 32;
9
11
  const NOTE_GAP = 10;
10
- const NOTE_GLYPH = 0.55;
11
- const NOTE_GLYPH_CAPS = 0.7;
12
- const FIT_MARGIN = 0.92;
12
+ /** Average glyph width as a share of the type size. */
13
+ const NOTE_GLYPH = .55;
14
+ const NOTE_GLYPH_CAPS = .7;
15
+ /** Aim a little under the drawn height, since the estimate wraps by character count. */
16
+ const FIT_MARGIN = .92;
13
17
  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));
18
+ const m = hex?.trim().match(/^#([\da-f]{3}|[\da-f]{6})$/i);
19
+ if (!m) return null;
20
+ const full = m[1].length === 3 ? [...m[1]].map((c) => c + c).join("") : m[1];
21
+ return [
22
+ 0,
23
+ 2,
24
+ 4
25
+ ].map((i) => Number.parseInt(full.slice(i, i + 2), 16));
24
26
  }
27
+ /** The sketch accent nearest the rect's stroke. A grey or an unknown colour reads as ink, the default. */
25
28
  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;
29
+ const rgb = channels(stroke);
30
+ if (!rgb) return undefined;
31
+ const spread = Math.max(...rgb) - Math.min(...rgb);
32
+ if (spread < 40) return undefined;
33
+ let best = null;
34
+ for (const name of SKETCH_ACCENTS) {
35
+ const target = channels(sketchColors(name).stroke);
36
+ if (!target) continue;
37
+ const distance = Math.hypot(...rgb.map((v, i) => v - target[i]));
38
+ if (!best || distance < best.distance) best = {
39
+ name,
40
+ distance
41
+ };
42
+ }
43
+ return best && best.name !== "ink" && best.distance < 90 ? best.name : undefined;
41
44
  }
45
+ /** Card text is markdown: a stray `*` or a leading `-` would turn into formatting. */
42
46
  export function escapeMarkdown(line) {
43
- return line.replace(/([\\`*_~[\]])/g, "\\$1").replace(/^([#>+-]|\d+\.)/, "\\$1");
47
+ return line.replace(/([\\`*_~[\]])/g, "\\$1").replace(/^([#>+-]|\d+\.)/, "\\$1");
44
48
  }
45
49
  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;
50
+ const max = Math.max(r, g, b);
51
+ const d = max - Math.min(r, g, b);
52
+ if (d === 0) return 0;
53
+ const h = max === r ? (g - b) / d % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
54
+ return (h * 60 + 360) % 360;
51
55
  }
56
+ /** The pen colour whose hue is nearest, or undefined for a grey, a white or a black. */
52
57
  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;
58
+ const rgb = channels(color);
59
+ if (!rgb || Math.max(...rgb) - Math.min(...rgb) < 40) return undefined;
60
+ const hue = hueOf(rgb);
61
+ let best = null;
62
+ for (const name of Object.keys(PEN_HEX)) {
63
+ const target = channels(PEN_HEX[name].light);
64
+ if (!target || name === "ink") continue;
65
+ const diff = Math.abs(hue - hueOf(target));
66
+ const gap = Math.min(diff, 360 - diff);
67
+ if (!best || gap < best.gap) best = {
68
+ name,
69
+ gap
70
+ };
71
+ }
72
+ return best?.name;
68
73
  }
74
+ /** A solid colour, not white or paper. */
69
75
  export function inked(color) {
70
- const rgb = channels(color);
71
- return Boolean(rgb) && Math.min(...rgb) < 240;
76
+ const rgb = channels(color);
77
+ return Boolean(rgb) && Math.min(...rgb) < 240;
72
78
  }
73
- export const round1 = (n)=>Math.round(n * 10) / 10;
79
+ export const round1 = (n) => Math.round(n * 10) / 10;
80
+ /** The share a note shows at: its title at the drawn size, or smaller until the copy fits the drawn box. */
74
81
  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;
82
+ // Capitals run wide, and a word wider than the card breaks mid-word, so that counts as too tall.
83
+ const glyph = (text) => text === text.toUpperCase() ? NOTE_GLYPH_CAPS : NOTE_GLYPH;
84
+ const lines = (text, px, inner) => {
85
+ const widest = Math.max(...text.split(/\s+/).map((w) => w.length)) * px * glyph(text);
86
+ if (widest > inner) return 99;
87
+ return Math.max(1, Math.ceil(text.length * px * glyph(text) / inner));
88
+ };
89
+ const heightAt = (z) => {
90
+ const inner = Math.max(boxW / z - NOTE_PAD_X, 40);
91
+ const title = lines([card.kicker, card.title].filter(Boolean).join(" "), NOTE_TITLE_PX, inner);
92
+ const body = card.body.reduce((n, line) => n + lines(line, NOTE_BODY_PX, inner), 0);
93
+ return (NOTE_PAD_Y + title * NOTE_TITLE_LINE + (body ? NOTE_GAP + body * NOTE_BODY_LINE : 0)) * z;
94
+ };
95
+ let z = matched;
96
+ for (let i = 0; i < 16 && z > .2 && heightAt(z) > boxH * FIT_MARGIN; i += 1) z *= .92;
97
+ return Math.round(z * 1e3) / 1e3;
93
98
  }
@@ -6,201 +6,208 @@ import { NOTE_BODY_PX, NOTE_TITLE_PX, round1 } from "./break-apart-style.js";
6
6
  import { addCards, addEdges, addFrames, addShapes, freshId, linksOf, placeBadges, startPlan } from "./break-apart-plan.js";
7
7
  const TEXT_NODE = 3;
8
8
  const ELEMENT_NODE = 1;
9
+ /** Space between the drawing and the caption set under it. */
9
10
  const CAPTION_GAP = 8;
11
+ // =========================
12
+ // TREE
13
+ // =========================
14
+ /** Read a DOM element (or any node shaped like one) into a tree. */
10
15
  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
- };
16
+ const attrs = {};
17
+ for (const attr of Array.from(el.attributes ?? [])) attrs[attr.name] = attr.value;
18
+ const children = [];
19
+ for (const child of Array.from(el.childNodes ?? [])) {
20
+ if (child.nodeType === TEXT_NODE) children.push(child.textContent ?? "");
21
+ else if (child.nodeType === ELEMENT_NODE) children.push(treeFromElement(child));
22
+ }
23
+ return {
24
+ tag: el.localName ?? "",
25
+ attrs,
26
+ children
27
+ };
23
28
  }
24
29
  function escapeText(value) {
25
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
30
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
26
31
  }
27
32
  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}>`;
33
+ if (typeof child === "string") return escapeText(child);
34
+ const attrs = Object.entries(child.attrs).map(([name, value]) => ` ${name}="${value.replace(/&/g, "&amp;").replace(/"/g, "&quot;")}"`).join("");
35
+ return `<${child.tag}${attrs}>${child.children.map(serialize).join("")}</${child.tag}>`;
31
36
  }
37
+ // =========================
38
+ // WHAT IS LEFT
39
+ // =========================
32
40
  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
- };
41
+ const children = [];
42
+ for (const child of el.children) {
43
+ if (removed.has(child)) continue;
44
+ if (isEl(child) && child.tag === "g") {
45
+ const kept = prune(child, removed);
46
+ // A group left with no shape, only whitespace, is gone.
47
+ if (kept.children.some((c) => isEl(c) ? true : c.trim() !== "")) children.push(kept);
48
+ continue;
49
+ }
50
+ children.push(child);
51
+ }
52
+ return {
53
+ ...el,
54
+ children
55
+ };
47
56
  }
57
+ /** The ink left once the cards are out: drawable shapes and text, ignoring defs. */
48
58
  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;
59
+ let box = null;
60
+ for (const child of el.children) {
61
+ if (!isEl(child) || child.tag === "defs" || child.tag === "title" || child.tag === "desc") continue;
62
+ if (child.tag === "g") {
63
+ const shift = translateOf(child) ?? {
64
+ x: 0,
65
+ y: 0
66
+ };
67
+ const inner = leftover(child);
68
+ if (inner) {
69
+ box = union(box, {
70
+ x1: inner.x1 + shift.x,
71
+ y1: inner.y1 + shift.y,
72
+ x2: inner.x2 + shift.x,
73
+ y2: inner.y2 + shift.y
74
+ });
75
+ }
76
+ continue;
77
+ }
78
+ box = union(box, boxOfEl(child, {
79
+ x: 0,
80
+ y: 0
81
+ }));
82
+ }
83
+ return box;
74
84
  }
85
+ /** Nothing is left to draw: the figure goes, and its title and caption stay as text above and below the pieces. */
75
86
  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
- }
87
+ const { node, taken, ops, drawing, scale, artTop } = plan;
88
+ ops.push({
89
+ kind: "removeNode",
90
+ nodeId: node.id
91
+ });
92
+ const accent = node.data.accent;
93
+ const color = Object.keys(PEN_HEX).find((c) => c === accent) ?? "ink";
94
+ const textWidth = Math.round(plan.artWidth);
95
+ const words = [[
96
+ String(node.data.label ?? ""),
97
+ NOTE_TITLE_PX,
98
+ node.position.y
99
+ ], [
100
+ String(node.data.caption ?? ""),
101
+ NOTE_BODY_PX,
102
+ Math.round(node.position.y + artTop + (drawing.y2 - drawing.y1) * scale + CAPTION_GAP)
103
+ ]];
104
+ for (const [i, [text, fontSize, y]] of words.entries()) {
105
+ if (!text) continue;
106
+ ops.push({
107
+ kind: "addNode",
108
+ node: {
109
+ id: freshId(`${node.id}-text-${i === 0 ? "title" : "caption"}`, taken),
110
+ type: "text",
111
+ position: {
112
+ x: node.position.x,
113
+ y
114
+ },
115
+ data: {
116
+ text,
117
+ color,
118
+ fontSize,
119
+ fixedWidth: true
120
+ },
121
+ width: textWidth,
122
+ height: Math.round(fontSize * 1.4),
123
+ ...node.parentId ? { parentId: node.parentId } : {}
124
+ }
125
+ });
126
+ }
121
127
  }
128
+ /** The figure keeps its height, so its caption stays under the pieces; only the sides shrink. */
122
129
  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
- }
130
+ const { node, drawing, scale, ops } = plan;
131
+ const crop = {
132
+ x1: Math.max(ink.x1, drawing.x1),
133
+ y1: drawing.y1,
134
+ x2: Math.min(ink.x2, drawing.x2),
135
+ y2: drawing.y2
136
+ };
137
+ const cropW = crop.x2 - crop.x1;
138
+ const cropH = crop.y2 - crop.y1;
139
+ const markup = sanitizeFigureSvg(serialize({
140
+ ...rest,
141
+ attrs: {
142
+ ...rest.attrs,
143
+ viewBox: `${round1(crop.x1)} ${round1(crop.y1)} ${round1(cropW)} ${round1(cropH)}`
144
+ }
145
+ }));
146
+ const width = Math.round(cropW * scale);
147
+ const board = node.data.board;
148
+ ops.push({
149
+ kind: "patchNode",
150
+ nodeId: node.id,
151
+ data: {
152
+ svg: markup ?? "",
153
+ ...board?.w ? { board: {
154
+ ...board,
155
+ w: width
156
+ } } : {}
157
+ },
158
+ width,
159
+ style: { width }
160
+ });
161
+ const shift = {
162
+ x: Math.round(node.position.x + (crop.x1 - drawing.x1) * scale),
163
+ y: Math.round(node.position.y + (crop.y1 - drawing.y1) * scale)
164
+ };
165
+ if (shift.x !== node.position.x || shift.y !== node.position.y) {
166
+ ops.push({
167
+ kind: "moveNode",
168
+ nodeId: node.id,
169
+ position: shift
170
+ });
171
+ }
169
172
  }
173
+ // =========================
174
+ // BREAK APART
175
+ // =========================
176
+ /** Ops that swap a figure for cards, links, shapes and frames, or null with none in it. The rest stays as a smaller figure. */
170
177
  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
- };
178
+ const { node, tree } = input;
179
+ const drawing = viewBoxOf(tree);
180
+ const artWidth = input.art?.width ?? node.width;
181
+ if (!drawing || !artWidth) return null;
182
+ const pieces = findPieces(tree, drawing);
183
+ if (!pieces) return null;
184
+ const plan = startPlan(input, pieces, drawing, artWidth);
185
+ addFrames(plan);
186
+ addShapes(plan);
187
+ const links = linksOf(plan);
188
+ const linkLabels = placeBadges(plan, links);
189
+ addCards(plan);
190
+ addEdges(plan, links, linkLabels);
191
+ const counts = {
192
+ cards: plan.cards.length,
193
+ edges: links.length,
194
+ shapes: plan.shapes,
195
+ frames: plan.loose.frames.length
196
+ };
197
+ const rest = prune(tree, plan.removed);
198
+ const ink = leftover(rest);
199
+ if (!ink) {
200
+ replaceWithText(plan);
201
+ return {
202
+ ops: plan.ops,
203
+ ...counts,
204
+ remains: false
205
+ };
206
+ }
207
+ shrinkFigure(plan, rest, ink);
208
+ return {
209
+ ops: plan.ops,
210
+ ...counts,
211
+ remains: true
212
+ };
206
213
  }