@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,193 +1,119 @@
1
+ /** True while the event's target owns its own text editing. */
1
2
  export function isEditingTarget(target) {
2
- const el = target;
3
- const tag = el?.tagName;
4
- return tag === "INPUT" || tag === "TEXTAREA" || el?.isContentEditable === true;
3
+ const el = target;
4
+ const tag = el?.tagName;
5
+ return tag === "INPUT" || tag === "TEXTAREA" || el?.isContentEditable === true;
5
6
  }
7
+ /** The action a key means on the canvas, or null when the canvas does not own it. */
6
8
  export function resolveEditorKeyAction(event, hasSelection) {
7
- const key = event.key.toLowerCase();
8
- const mod = event.metaKey || event.ctrlKey;
9
- const editing = isEditingTarget(event.target);
10
- if (mod) return modAction(key, event.shiftKey, editing);
11
- if (event.key === "Delete" || event.key === "Backspace") {
12
- return hasSelection && !editing ? "delete" : null;
13
- }
14
- return editing || event.altKey ? null : plainAction(event);
9
+ const key = event.key.toLowerCase();
10
+ const mod = event.metaKey || event.ctrlKey;
11
+ const editing = isEditingTarget(event.target);
12
+ if (mod) return modAction(key, event.shiftKey, editing);
13
+ if (event.key === "Delete" || event.key === "Backspace") {
14
+ return hasSelection && !editing ? "delete" : null;
15
+ }
16
+ return editing || event.altKey ? null : plainAction(event);
15
17
  }
18
+ /** Shift with a digit zooms. Read by code, so a layout that moves the digits still works. */
16
19
  const SHIFT_ZOOM = new Map([
17
- [
18
- "Digit0",
19
- "zoom-100"
20
- ],
21
- [
22
- "Digit1",
23
- "zoom-fit"
24
- ],
25
- [
26
- "Digit2",
27
- "zoom-selection"
28
- ]
20
+ ["Digit0", "zoom-100"],
21
+ ["Digit1", "zoom-fit"],
22
+ ["Digit2", "zoom-selection"]
29
23
  ]);
30
24
  const PLAIN_KEYS = new Map([
31
- [
32
- "]",
33
- "bring-forward"
34
- ],
35
- [
36
- "[",
37
- "send-backward"
38
- ],
39
- [
40
- "+",
41
- "zoom-in"
42
- ],
43
- [
44
- "=",
45
- "zoom-in"
46
- ],
47
- [
48
- "-",
49
- "zoom-out"
50
- ],
51
- [
52
- "_",
53
- "zoom-out"
54
- ]
25
+ ["]", "bring-forward"],
26
+ ["[", "send-backward"],
27
+ ["+", "zoom-in"],
28
+ ["=", "zoom-in"],
29
+ ["-", "zoom-out"],
30
+ ["_", "zoom-out"]
55
31
  ]);
32
+ /** Zoom keys with no chord: + and -, and Shift with 0, 1 or 2. */
56
33
  function plainAction(event) {
57
- const zoom = event.shiftKey ? SHIFT_ZOOM.get(event.code) : undefined;
58
- return zoom ?? PLAIN_KEYS.get(event.key) ?? null;
34
+ const zoom = event.shiftKey ? SHIFT_ZOOM.get(event.code) : undefined;
35
+ return zoom ?? PLAIN_KEYS.get(event.key) ?? null;
59
36
  }
37
+ /** The move an arrow key asks for: 1px, or 10px with Shift. Null for anything else, or while a field owns the keys. */
60
38
  export function resolveNudge(event) {
61
- if (event.metaKey || event.ctrlKey || event.altKey || isEditingTarget(event.target)) return null;
62
- const step = event.shiftKey ? 10 : 1;
63
- switch(event.key){
64
- case "ArrowLeft":
65
- return {
66
- dx: -step,
67
- dy: 0
68
- };
69
- case "ArrowRight":
70
- return {
71
- dx: step,
72
- dy: 0
73
- };
74
- case "ArrowUp":
75
- return {
76
- dx: 0,
77
- dy: -step
78
- };
79
- case "ArrowDown":
80
- return {
81
- dx: 0,
82
- dy: step
83
- };
84
- default:
85
- return null;
86
- }
39
+ if (event.metaKey || event.ctrlKey || event.altKey || isEditingTarget(event.target)) return null;
40
+ const step = event.shiftKey ? 10 : 1;
41
+ switch (event.key) {
42
+ case "ArrowLeft": return {
43
+ dx: -step,
44
+ dy: 0
45
+ };
46
+ case "ArrowRight": return {
47
+ dx: step,
48
+ dy: 0
49
+ };
50
+ case "ArrowUp": return {
51
+ dx: 0,
52
+ dy: -step
53
+ };
54
+ case "ArrowDown": return {
55
+ dx: 0,
56
+ dy: step
57
+ };
58
+ default: return null;
59
+ }
87
60
  }
88
- const both = (action)=>[
89
- action,
90
- action
91
- ];
61
+ const both = (action) => [action, action];
92
62
  const CHORDS = new Map([
93
- [
94
- "c",
95
- [
96
- "copy",
97
- "copy-as-png"
98
- ]
99
- ],
100
- [
101
- "v",
102
- both("paste")
103
- ],
104
- [
105
- "a",
106
- both("select-all")
107
- ],
108
- [
109
- "d",
110
- [
111
- "duplicate",
112
- null
113
- ]
114
- ],
115
- [
116
- "g",
117
- [
118
- "group",
119
- "ungroup"
120
- ]
121
- ],
122
- [
123
- "l",
124
- [
125
- null,
126
- "lock"
127
- ]
128
- ],
129
- [
130
- "]",
131
- both("bring-to-front")
132
- ],
133
- [
134
- "[",
135
- both("send-to-back")
136
- ],
137
- [
138
- "enter",
139
- both("present")
140
- ],
141
- [
142
- "=",
143
- both("zoom-in")
144
- ],
145
- [
146
- "+",
147
- both("zoom-in")
148
- ],
149
- [
150
- "-",
151
- both("zoom-out")
152
- ],
153
- [
154
- "_",
155
- both("zoom-out")
156
- ]
63
+ ["c", ["copy", "copy-as-png"]],
64
+ ["v", both("paste")],
65
+ ["a", both("select-all")],
66
+ ["d", ["duplicate", null]],
67
+ ["g", ["group", "ungroup"]],
68
+ ["l", [null, "lock"]],
69
+ ["]", both("bring-to-front")],
70
+ ["[", both("send-to-back")],
71
+ ["enter", both("present")],
72
+ ["=", both("zoom-in")],
73
+ ["+", both("zoom-in")],
74
+ ["-", both("zoom-out")],
75
+ ["_", both("zoom-out")]
157
76
  ]);
77
+ /** The chord half of the map. `editing` is true while a field owns the keys. */
158
78
  function modAction(key, shift, editing) {
159
- if (key === "z") return shift ? "redo" : "undo";
160
- if (key === "y") return "redo";
161
- if (key === "s" && !shift) return "save";
162
- if (editing) return null;
163
- return CHORDS.get(key)?.[shift ? 1 : 0] ?? null;
79
+ if (key === "z") return shift ? "redo" : "undo";
80
+ if (key === "y") return "redo";
81
+ // Save works whatever has focus, and whatever the autosave preference is.
82
+ if (key === "s" && !shift) return "save";
83
+ // Cut, copy, paste and select-all belong to the focused field while one is
84
+ // being typed in; the canvas only claims them otherwise.
85
+ if (editing) return null;
86
+ return CHORDS.get(key)?.[shift ? 1 : 0] ?? null;
164
87
  }
88
+ /** Tags and roles that activate on Space themselves, so the canvas must not steal it. */
165
89
  const SPACE_OWNING_TAGS = new Set([
166
- "BUTTON",
167
- "A",
168
- "SUMMARY",
169
- "SELECT",
170
- "OPTION"
90
+ "BUTTON",
91
+ "A",
92
+ "SUMMARY",
93
+ "SELECT",
94
+ "OPTION"
171
95
  ]);
172
96
  const SPACE_OWNING_ROLES = new Set([
173
- "button",
174
- "checkbox",
175
- "switch",
176
- "menuitem",
177
- "menuitemcheckbox",
178
- "menuitemradio",
179
- "option",
180
- "radio",
181
- "tab"
97
+ "button",
98
+ "checkbox",
99
+ "switch",
100
+ "menuitem",
101
+ "menuitemcheckbox",
102
+ "menuitemradio",
103
+ "option",
104
+ "radio",
105
+ "tab"
182
106
  ]);
107
+ /** Space means hand tool over the canvas, never in a field or on a Space-activating control. Reads code, so remapped layouts still pan. */
183
108
  export function isPanActivationKey(event) {
184
- if (event.code !== "Space" && event.key !== " ") return false;
185
- if (event.metaKey || event.ctrlKey || event.altKey) return false;
186
- if (isEditingTarget(event.target)) return false;
187
- return !ownsSpace(event.target);
109
+ if (event.code !== "Space" && event.key !== " ") return false;
110
+ if (event.metaKey || event.ctrlKey || event.altKey) return false;
111
+ if (isEditingTarget(event.target)) return false;
112
+ return !ownsSpace(event.target);
188
113
  }
114
+ /** A control that already does something on Space: a button, a checkbox, a tab. */
189
115
  function ownsSpace(el) {
190
- if (el?.tagName && SPACE_OWNING_TAGS.has(el.tagName)) return true;
191
- const role = el?.getAttribute?.("role");
192
- return Boolean(role && SPACE_OWNING_ROLES.has(role));
116
+ if (el?.tagName && SPACE_OWNING_TAGS.has(el.tagName)) return true;
117
+ const role = el?.getAttribute?.("role");
118
+ return Boolean(role && SPACE_OWNING_ROLES.has(role));
193
119
  }
@@ -1,170 +1,169 @@
1
1
  import { alignNodesHorizontally, alignNodesVertically } from "../headless/alignment.js";
2
+ /** The smallest a group frame gets. An app passes its own to `tidyGroup`. */
2
3
  const DEFAULT_GROUP_MIN = {
3
- width: 250,
4
- height: 150
4
+ width: 250,
5
+ height: 150
5
6
  };
6
7
  function setX(nodes, x) {
7
- for (const n of nodes)n.position.x = x;
8
+ for (const n of nodes) n.position.x = x;
8
9
  }
9
10
  function setY(nodes, y) {
10
- for (const n of nodes)n.position.y = y;
11
+ for (const n of nodes) n.position.y = y;
11
12
  }
12
13
  const ALIGNERS = {
13
- left: (nodes)=>setX(nodes, Math.min(...nodes.map((n)=>n.position.x))),
14
- right: (nodes)=>setX(nodes, Math.max(...nodes.map((n)=>n.position.x))),
15
- top: (nodes)=>alignNodesHorizontally(nodes),
16
- bottom: (nodes)=>setY(nodes, Math.max(...nodes.map((n)=>n.position.y))),
17
- "center-h": (nodes)=>alignNodesVertically(nodes),
18
- "center-v": (nodes)=>setY(nodes, nodes.reduce((sum, n)=>sum + n.position.y, 0) / nodes.length)
14
+ left: (nodes) => setX(nodes, Math.min(...nodes.map((n) => n.position.x))),
15
+ right: (nodes) => setX(nodes, Math.max(...nodes.map((n) => n.position.x))),
16
+ top: (nodes) => alignNodesHorizontally(nodes),
17
+ bottom: (nodes) => setY(nodes, Math.max(...nodes.map((n) => n.position.y))),
18
+ "center-h": (nodes) => alignNodesVertically(nodes),
19
+ "center-v": (nodes) => setY(nodes, nodes.reduce((sum, n) => sum + n.position.y, 0) / nodes.length)
19
20
  };
21
+ /**
22
+ * Compute new positions for the given nodes based on alignment direction.
23
+ * Returns a Map of nodeId to new position.
24
+ */
20
25
  export function alignNodes(selected, direction) {
21
- if (selected.length < 2) return new Map();
22
- const mutableNodes = selected.map((n)=>({
23
- id: n.id,
24
- position: {
25
- ...n.position
26
- }
27
- }));
28
- ALIGNERS[direction](mutableNodes);
29
- return new Map(mutableNodes.map((n)=>[
30
- n.id,
31
- n.position
32
- ]));
26
+ if (selected.length < 2) return new Map();
27
+ const mutableNodes = selected.map((n) => ({
28
+ id: n.id,
29
+ position: { ...n.position }
30
+ }));
31
+ ALIGNERS[direction](mutableNodes);
32
+ return new Map(mutableNodes.map((n) => [n.id, n.position]));
33
33
  }
34
34
  const TIDY_GAP = 40;
35
+ /**
36
+ * Distribute selected nodes evenly along their dominant axis.
37
+ * Detects whether nodes are mostly horizontal or vertical and
38
+ * equalizes gaps while preserving the cross-axis positions.
39
+ */
35
40
  export function tidySelectedNodes(selected) {
36
- if (selected.length < 2) return new Map();
37
- const xs = selected.map((n)=>n.position.x);
38
- const ys = selected.map((n)=>n.position.y);
39
- const xSpread = Math.max(...xs) - Math.min(...xs);
40
- const ySpread = Math.max(...ys) - Math.min(...ys);
41
- const horizontal = xSpread >= ySpread;
42
- const sorted = [
43
- ...selected
44
- ].sort((a, b)=>horizontal ? a.position.x - b.position.x : a.position.y - b.position.y);
45
- return horizontal ? stackAlongX(sorted) : stackAlongY(sorted);
41
+ if (selected.length < 2) return new Map();
42
+ const xs = selected.map((n) => n.position.x);
43
+ const ys = selected.map((n) => n.position.y);
44
+ const xSpread = Math.max(...xs) - Math.min(...xs);
45
+ const ySpread = Math.max(...ys) - Math.min(...ys);
46
+ const horizontal = xSpread >= ySpread;
47
+ const sorted = [...selected].sort((a, b) => horizontal ? a.position.x - b.position.x : a.position.y - b.position.y);
48
+ return horizontal ? stackAlongX(sorted) : stackAlongY(sorted);
46
49
  }
47
50
  function stackAlongX(sorted) {
48
- const result = new Map();
49
- let cursorX = sorted[0].position.x;
50
- for (const node of sorted){
51
- result.set(node.id, {
52
- x: cursorX,
53
- y: node.position.y
54
- });
55
- cursorX += (node.measured?.width ?? node.width ?? 200) + TIDY_GAP;
56
- }
57
- return result;
51
+ const result = new Map();
52
+ let cursorX = sorted[0].position.x;
53
+ for (const node of sorted) {
54
+ result.set(node.id, {
55
+ x: cursorX,
56
+ y: node.position.y
57
+ });
58
+ cursorX += (node.measured?.width ?? node.width ?? 200) + TIDY_GAP;
59
+ }
60
+ return result;
58
61
  }
59
62
  function stackAlongY(sorted) {
60
- const result = new Map();
61
- let cursorY = sorted[0].position.y;
62
- for (const node of sorted){
63
- result.set(node.id, {
64
- x: node.position.x,
65
- y: cursorY
66
- });
67
- cursorY += (node.measured?.height ?? node.height ?? 100) + TIDY_GAP;
68
- }
69
- return result;
63
+ const result = new Map();
64
+ let cursorY = sorted[0].position.y;
65
+ for (const node of sorted) {
66
+ result.set(node.id, {
67
+ x: node.position.x,
68
+ y: cursorY
69
+ });
70
+ cursorY += (node.measured?.height ?? node.height ?? 100) + TIDY_GAP;
71
+ }
72
+ return result;
70
73
  }
74
+ // A group box drawn around a selection: the padding on every side, plus the
75
+ // room the container's own title row takes at the top.
71
76
  const GROUP_PADDING = 32;
72
77
  const GROUP_HEADER = 36;
73
78
  const GROUP_GAP = 24;
74
79
  const ROW_TOLERANCE = 8;
80
+ /**
81
+ * Tidy a group's direct children: rows by reading order, even gaps, then the
82
+ * box fitted around them. Null when the group is empty.
83
+ */
75
84
  export function tidyGroup(children, min = DEFAULT_GROUP_MIN) {
76
- if (children.length === 0) return null;
77
- const width = (n)=>n.measured?.width ?? n.width ?? 200;
78
- const height = (n)=>n.measured?.height ?? n.height ?? 100;
79
- const rows = [];
80
- for (const n of [
81
- ...children
82
- ].sort((a, b)=>a.position.y - b.position.y)){
83
- const row = rows.at(-1);
84
- if (row && n.position.y < row.bottom - ROW_TOLERANCE) {
85
- row.items.push(n);
86
- row.bottom = Math.max(row.bottom, n.position.y + height(n));
87
- } else {
88
- rows.push({
89
- bottom: n.position.y + height(n),
90
- items: [
91
- n
92
- ]
93
- });
94
- }
95
- }
96
- const positions = new Map();
97
- let y = GROUP_PADDING + GROUP_HEADER;
98
- let right = 0;
99
- for (const row of rows){
100
- let x = GROUP_PADDING;
101
- for (const n of row.items.sort((a, b)=>a.position.x - b.position.x)){
102
- positions.set(n.id, {
103
- x,
104
- y
105
- });
106
- x += width(n) + GROUP_GAP;
107
- }
108
- right = Math.max(right, x - GROUP_GAP);
109
- y += Math.max(...row.items.map(height)) + GROUP_GAP;
110
- }
111
- return {
112
- positions,
113
- size: {
114
- width: Math.max(min.width, right + GROUP_PADDING),
115
- height: Math.max(min.height, y - GROUP_GAP + GROUP_PADDING)
116
- }
117
- };
85
+ if (children.length === 0) return null;
86
+ const width = (n) => n.measured?.width ?? n.width ?? 200;
87
+ const height = (n) => n.measured?.height ?? n.height ?? 100;
88
+ // A node joins the current row when it overlaps it vertically by more than a hair.
89
+ const rows = [];
90
+ for (const n of [...children].sort((a, b) => a.position.y - b.position.y)) {
91
+ const row = rows.at(-1);
92
+ if (row && n.position.y < row.bottom - ROW_TOLERANCE) {
93
+ row.items.push(n);
94
+ row.bottom = Math.max(row.bottom, n.position.y + height(n));
95
+ } else {
96
+ rows.push({
97
+ bottom: n.position.y + height(n),
98
+ items: [n]
99
+ });
100
+ }
101
+ }
102
+ const positions = new Map();
103
+ let y = GROUP_PADDING + GROUP_HEADER;
104
+ let right = 0;
105
+ for (const row of rows) {
106
+ let x = GROUP_PADDING;
107
+ for (const n of row.items.sort((a, b) => a.position.x - b.position.x)) {
108
+ positions.set(n.id, {
109
+ x,
110
+ y
111
+ });
112
+ x += width(n) + GROUP_GAP;
113
+ }
114
+ right = Math.max(right, x - GROUP_GAP);
115
+ y += Math.max(...row.items.map(height)) + GROUP_GAP;
116
+ }
117
+ return {
118
+ positions,
119
+ size: {
120
+ width: Math.max(min.width, right + GROUP_PADDING),
121
+ height: Math.max(min.height, y - GROUP_GAP + GROUP_PADDING)
122
+ }
123
+ };
118
124
  }
125
+ /** Wrap a selection in a frame node, or [] when there is nothing to group. */
119
126
  export function groupSelectionOps(selected, groupLabel, { type = "group", data = {} } = {}) {
120
- if (selected.length < 2) return [];
121
- const parentId = selected[0].parentId;
122
- const members = selected.filter((n)=>n.parentId === parentId);
123
- if (members.length < 2) return [];
124
- const width = (n)=>n.measured?.width ?? n.width ?? 200;
125
- const height = (n)=>n.measured?.height ?? n.height ?? 100;
126
- const minX = Math.min(...members.map((n)=>n.position.x));
127
- const minY = Math.min(...members.map((n)=>n.position.y));
128
- const maxX = Math.max(...members.map((n)=>n.position.x + width(n)));
129
- const maxY = Math.max(...members.map((n)=>n.position.y + height(n)));
130
- const origin = {
131
- x: minX - GROUP_PADDING,
132
- y: minY - GROUP_PADDING - GROUP_HEADER
133
- };
134
- const groupId = crypto.randomUUID();
135
- return [
136
- {
137
- kind: "addNode",
138
- node: {
139
- id: groupId,
140
- type,
141
- position: origin,
142
- data: {
143
- ...data,
144
- label: groupLabel
145
- },
146
- width: maxX - minX + GROUP_PADDING * 2,
147
- height: maxY - minY + GROUP_PADDING * 2 + GROUP_HEADER,
148
- ...parentId ? {
149
- parentId
150
- } : {}
151
- }
152
- },
153
- ...members.flatMap((n)=>[
154
- {
155
- kind: "setParent",
156
- nodeId: n.id,
157
- parentId: groupId,
158
- extent: "parent"
159
- },
160
- {
161
- kind: "moveNode",
162
- nodeId: n.id,
163
- position: {
164
- x: n.position.x - origin.x,
165
- y: n.position.y - origin.y
166
- }
167
- }
168
- ])
169
- ];
127
+ if (selected.length < 2) return [];
128
+ const parentId = selected[0].parentId;
129
+ const members = selected.filter((n) => n.parentId === parentId);
130
+ if (members.length < 2) return [];
131
+ const width = (n) => n.measured?.width ?? n.width ?? 200;
132
+ const height = (n) => n.measured?.height ?? n.height ?? 100;
133
+ const minX = Math.min(...members.map((n) => n.position.x));
134
+ const minY = Math.min(...members.map((n) => n.position.y));
135
+ const maxX = Math.max(...members.map((n) => n.position.x + width(n)));
136
+ const maxY = Math.max(...members.map((n) => n.position.y + height(n)));
137
+ const origin = {
138
+ x: minX - GROUP_PADDING,
139
+ y: minY - GROUP_PADDING - GROUP_HEADER
140
+ };
141
+ const groupId = crypto.randomUUID();
142
+ return [{
143
+ kind: "addNode",
144
+ node: {
145
+ id: groupId,
146
+ type,
147
+ position: origin,
148
+ data: {
149
+ ...data,
150
+ label: groupLabel
151
+ },
152
+ width: maxX - minX + GROUP_PADDING * 2,
153
+ height: maxY - minY + GROUP_PADDING * 2 + GROUP_HEADER,
154
+ ...parentId ? { parentId } : {}
155
+ }
156
+ }, ...members.flatMap((n) => [{
157
+ kind: "setParent",
158
+ nodeId: n.id,
159
+ parentId: groupId,
160
+ extent: "parent"
161
+ }, {
162
+ kind: "moveNode",
163
+ nodeId: n.id,
164
+ position: {
165
+ x: n.position.x - origin.x,
166
+ y: n.position.y - origin.y
167
+ }
168
+ }])];
170
169
  }