@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.
- package/dist/agents/helpers.js +38 -38
- package/dist/agents/page-state.js +49 -50
- package/dist/agents/vendor/color.js +146 -127
- package/dist/agents/vendor/draw.js +617 -508
- package/dist/agents/vendor/engine.js +698 -599
- package/dist/agents/vendor/mesh.js +653 -683
- package/dist/agents/vendor/plastic.js +3913 -3112
- package/dist/agents/vendor/presets.js +163 -164
- package/dist/agents/vendor/shapes.js +21 -20
- package/dist/agents/vendor/ticker.js +41 -42
- package/dist/agents/vendor/types.js +1 -0
- package/dist/agents/vendor/wear.js +1421 -1448
- package/dist/ai/confirmation/confirmation-context.svelte.js +6 -6
- package/dist/ai/confirmation/index.js +3 -1
- package/dist/ai/index.js +33 -0
- package/dist/ai/prompt-input/index.js +3 -1
- package/dist/ai/reasoning/index.js +3 -1
- package/dist/ai/reasoning/reasoning-context.svelte.js +6 -6
- package/dist/ai/suggestion/index.js +3 -1
- package/dist/ai/typing-dots/index.js +3 -1
- package/dist/animations/text-swap.js +24 -24
- package/dist/brand/index.js +54 -48
- package/dist/canvas/card/resize.js +5 -3
- package/dist/canvas/edges/edge-path.js +255 -242
- package/dist/canvas/editor/context-menu.js +39 -36
- package/dist/canvas/editor/dispatch.svelte.js +154 -151
- package/dist/canvas/editor/editor-context.js +7 -2
- package/dist/canvas/editor/editor-mode.js +1 -0
- package/dist/canvas/editor/frame.js +73 -76
- package/dist/canvas/editor/hand-context.js +5 -2
- package/dist/canvas/editor/history-core.js +163 -191
- package/dist/canvas/editor/keymap.js +95 -169
- package/dist/canvas/editor/layout-actions.js +143 -144
- package/dist/canvas/editor/lock.js +52 -51
- package/dist/canvas/editor/rebase.js +217 -240
- package/dist/canvas/editor/style.js +132 -137
- package/dist/canvas/editor/text-field-context.js +7 -2
- package/dist/canvas/editor/z-order.js +65 -70
- package/dist/canvas/headless/alignment.js +22 -16
- package/dist/canvas/headless/break-apart-find.js +286 -279
- package/dist/canvas/headless/break-apart-pieces.js +62 -58
- package/dist/canvas/headless/break-apart-plan.js +299 -325
- package/dist/canvas/headless/break-apart-style.js +75 -70
- package/dist/canvas/headless/break-apart.js +188 -181
- package/dist/canvas/headless/canvas-cleanup.js +78 -58
- package/dist/canvas/headless/edge-anchors.js +115 -116
- package/dist/canvas/headless/edge-memo.js +24 -22
- package/dist/canvas/headless/edge-serialization.js +17 -27
- package/dist/canvas/headless/figure-geometry.js +232 -264
- package/dist/canvas/headless/fit-viewport.js +84 -67
- package/dist/canvas/headless/grid.js +88 -86
- package/dist/canvas/headless/icon-node.js +44 -36
- package/dist/canvas/headless/index.js +1 -0
- package/dist/canvas/headless/linkify.js +17 -19
- package/dist/canvas/headless/pen-marks.js +101 -95
- package/dist/canvas/headless/sketch.js +231 -209
- package/dist/canvas/headless/snap-guides.js +99 -83
- package/dist/canvas/headless/svg-figure.js +133 -117
- package/dist/canvas/headless/table-layout.js +146 -107
- package/dist/canvas/index.js +2 -0
- package/dist/canvas/lod/lod-mode.svelte.js +20 -18
- package/dist/canvas/lod/render-all.svelte.js +36 -39
- package/dist/canvas/ops/arrange.js +78 -71
- package/dist/canvas/ops/ops.js +260 -255
- package/dist/canvas/pen/colors.js +37 -35
- package/dist/canvas/pen/eraser.js +28 -27
- package/dist/canvas/pen/geometry.js +120 -130
- package/dist/canvas/pen/index.js +1 -0
- package/dist/canvas/pen/ops.js +11 -11
- package/dist/canvas/pen/pen-session.js +150 -159
- package/dist/canvas/pen/plugin.js +107 -104
- package/dist/canvas/pen/shapes.js +73 -68
- package/dist/canvas/pen/stroke-path.js +32 -30
- package/dist/canvas/pen/types.js +21 -21
- package/dist/canvas/pen/ui.js +10 -8
- package/dist/canvas/plugin.js +46 -46
- package/dist/canvas/screen/glyphs.js +12 -11
- package/dist/canvas/screen/labels.js +22 -22
- package/dist/canvas/screen/parts.js +98 -111
- package/dist/canvas/screen/plugin.js +47 -56
- package/dist/canvas/screen/types.js +30 -35
- package/dist/canvas/screen/ui.js +7 -7
- package/dist/canvas/test-utils.js +14 -14
- package/dist/canvas/types.js +1 -0
- package/dist/canvas/ui/forward-wheel.js +24 -25
- package/dist/canvas/ui/icons.js +64 -131
- package/dist/canvas/ui/place-panel.js +58 -49
- package/dist/canvas/ui/shortcut-tip.js +14 -12
- package/dist/canvas/ui/tools.js +300 -385
- package/dist/charts/dashboard/chart.js +4 -0
- package/dist/charts/dashboard/data-table.js +11 -0
- package/dist/charts/dashboard/windows.js +8 -3
- package/dist/charts/edge-fade.js +10 -5
- package/dist/charts/hover-band.js +16 -11
- package/dist/charts/index.js +0 -1
- package/dist/charts/motion.js +11 -7
- package/dist/charts/primitives/chart-utils.js +12 -12
- package/dist/components/copy/clipboard.js +32 -32
- package/dist/components/copy/copied.svelte.js +31 -23
- package/dist/components/data-table/data-table.js +39 -39
- package/dist/components/docs/index.js +4 -0
- package/dist/components/docs/scroll-spy.svelte.js +73 -68
- package/dist/components/list-page/filter-section.js +4 -1
- package/dist/components/list-page/index.js +2 -0
- package/dist/components/list-page/list-table.js +7 -2
- package/dist/components/list-page/queue-rail.js +14 -0
- package/dist/components/list-page/section-host.js +4 -2
- package/dist/components/list-page/step.js +4 -3
- package/dist/components/list-page/types.js +1 -0
- package/dist/components/picker/trigger.js +48 -30
- package/dist/components/picker/types.js +1 -0
- package/dist/components/segmented/collapse.js +38 -27
- package/dist/components/segmented/index.js +8 -0
- package/dist/components/segmented/segmented.js +67 -42
- package/dist/components/star-rating-input/index.js +3 -1
- package/dist/components/table-view/types.js +1 -0
- package/dist/components/tags-input/tags.js +81 -69
- package/dist/components/theme/index.js +4 -0
- package/dist/components/toolbar/index.js +4 -0
- package/dist/components/toolbar/toolbar-trigger.js +14 -1
- package/dist/editor/anchors.js +17 -13
- package/dist/editor/block-id.js +52 -51
- package/dist/editor/block-ops.js +108 -80
- package/dist/editor/doc-blocks.js +155 -166
- package/dist/editor/extensions.js +139 -180
- package/dist/editor/index.js +4 -0
- package/dist/editor/markdown-test-helpers.js +4 -2
- package/dist/editor/markdown.js +420 -408
- package/dist/editor/serializer.js +204 -200
- package/dist/editor/slash-items.js +89 -101
- package/dist/editor/youtube-url.js +25 -20
- package/dist/effects/animated-beam/path-calculator.svelte.js +43 -38
- package/dist/effects/animated-beam/types.js +1 -0
- package/dist/effects/border-beam/css.js +92 -78
- package/dist/effects/border-beam/presets.js +1705 -1720
- package/dist/effects/border-beam/register-properties.js +43 -33
- package/dist/effects/border-beam/types.js +1 -0
- package/dist/effects/image-comparison/compare.js +3 -2
- package/dist/effects/image-reveal/types.js +1 -0
- package/dist/effects/magnetic/magnetic.js +58 -52
- package/dist/effects/reveal/observe.js +50 -40
- package/dist/effects/reveal/types.js +1 -0
- package/dist/effects/shader-gradient/color.js +36 -29
- package/dist/effects/shader-gradient/renderer.js +384 -295
- package/dist/effects/shader-gradient/shaders.js +31 -0
- package/dist/effects/shader-gradient/types.js +1 -0
- package/dist/icons/apple-symbol-exports.js +99 -124
- package/dist/icons/sf-symbols.js +357 -461
- package/dist/icons/trades.js +43 -28
- package/dist/inbox/index.js +1 -0
- package/dist/inbox/logic.js +126 -122
- package/dist/inbox/types.js +43 -43
- package/dist/lib/arrive.js +19 -12
- package/dist/lib/icon-types.js +1 -0
- package/dist/lib/initials.js +16 -10
- package/dist/lib/layout.js +5 -0
- package/dist/lib/panel.js +13 -0
- package/dist/lib/result.js +22 -22
- package/dist/lib/sliding-pill.js +59 -44
- package/dist/lib/theme-transition.js +43 -43
- package/dist/lib/tone.js +31 -24
- package/dist/lib/utils.js +3 -1
- package/dist/onboarding/define-tours.js +24 -23
- package/dist/onboarding/onboarding.svelte.js +16 -12
- package/dist/onboarding/pick.js +10 -6
- package/dist/onboarding/stores.js +40 -41
- package/dist/onboarding/wait.js +24 -23
- package/dist/panel/side-panel-types.js +1 -0
- package/dist/record/demo-data.js +459 -486
- package/dist/record/fields.js +129 -131
- package/dist/record/format.js +70 -82
- package/dist/record/gutter.js +1 -0
- package/dist/record/kinds.js +95 -105
- package/dist/record/logic.js +238 -219
- package/dist/record/types.js +49 -48
- package/dist/shadcn/accordion/index.js +3 -1
- package/dist/shadcn/alert/index.js +3 -1
- package/dist/shadcn/alert-dialog/index.js +3 -1
- package/dist/shadcn/attachment/index.js +3 -1
- package/dist/shadcn/avatar/index.js +3 -1
- package/dist/shadcn/breadcrumb/index.js +3 -1
- package/dist/shadcn/bubble/index.js +3 -1
- package/dist/shadcn/button/index.js +3 -1
- package/dist/shadcn/calendar/index.js +3 -1
- package/dist/shadcn/card/index.js +3 -1
- package/dist/shadcn/checkbox/index.js +3 -1
- package/dist/shadcn/code-block/index.js +3 -1
- package/dist/shadcn/collapsible/index.js +3 -1
- package/dist/shadcn/command/index.js +4 -1
- package/dist/shadcn/context-menu/index.js +3 -1
- package/dist/shadcn/dialog/index.js +3 -1
- package/dist/shadcn/dropdown-menu/menu-item.js +40 -20
- package/dist/shadcn/empty/index.js +3 -1
- package/dist/shadcn/field/index.js +3 -1
- package/dist/shadcn/hooks/highlighter.js +77 -75
- package/dist/shadcn/hooks/is-mobile.svelte.js +3 -3
- package/dist/shadcn/hooks/use-highlighted-code.svelte.js +62 -62
- package/dist/shadcn/input/index.js +3 -1
- package/dist/shadcn/kbd/index.js +3 -1
- package/dist/shadcn/label/index.js +3 -1
- package/dist/shadcn/marker/index.js +3 -1
- package/dist/shadcn/message/index.js +3 -1
- package/dist/shadcn/message-scroller/index.js +3 -1
- package/dist/shadcn/popover/index.js +3 -1
- package/dist/shadcn/progress/index.js +3 -1
- package/dist/shadcn/progress/tone.js +8 -7
- package/dist/shadcn/radio-group/index.js +3 -1
- package/dist/shadcn/scroll-area/index.js +3 -1
- package/dist/shadcn/select/index.js +3 -1
- package/dist/shadcn/separator/index.js +3 -1
- package/dist/shadcn/sheet/index.js +3 -1
- package/dist/shadcn/sidebar/context.svelte.js +42 -28
- package/dist/shadcn/sidebar/index.js +3 -1
- package/dist/shadcn/skeleton/index.js +3 -1
- package/dist/shadcn/slider/index.js +3 -1
- package/dist/shadcn/switch/index.js +3 -1
- package/dist/shadcn/table/index.js +3 -1
- package/dist/shadcn/tabs/index.js +3 -1
- package/dist/shadcn/textarea/index.js +3 -1
- package/dist/shadcn/toggle/index.js +3 -1
- package/dist/shadcn/toggle-group/index.js +3 -1
- package/dist/shadcn/tooltip/context.js +7 -2
- package/dist/shadcn/tooltip/index.js +3 -1
- package/dist/shadcn/trace/index.js +3 -1
- package/dist/slides/bridges.js +65 -59
- package/dist/slides/deck-controller.svelte.js +210 -207
- package/dist/slides/export/capture.js +66 -71
- package/dist/slides/export/ffmpeg.js +76 -72
- package/dist/slides/export/index.js +3 -0
- package/dist/slides/export/launch.js +9 -6
- package/dist/slides/export/pdf.js +60 -54
- package/dist/slides/export/types.js +1 -0
- package/dist/slides/labels.js +24 -24
- package/dist/slides/layout.js +39 -41
- package/dist/slides/model.js +9 -7
- package/dist/slides/navigation.js +78 -73
- package/dist/slides/types.js +2 -2
- package/dist/status/define-statuses.js +18 -14
- package/dist/status/status-types.js +1 -0
- package/dist/test-setup.js +24 -20
- package/dist/thread/logic.js +73 -77
- package/dist/thread/types.js +21 -21
- package/dist/work/board-order.js +94 -90
- package/dist/work/dates.js +16 -9
- package/dist/work/detail/detail-types.js +18 -18
- package/dist/work/docs/edited.js +32 -29
- package/dist/work/docs/index.js +4 -0
- package/dist/work/docs/labels.js +34 -34
- package/dist/work/docs/menu.js +113 -106
- package/dist/work/docs/outline.js +39 -29
- package/dist/work/gantt/logic.js +211 -235
- package/dist/work/inbox/inbox-logic.js +71 -75
- package/dist/work/inbox/inbox-statuses.js +75 -72
- package/dist/work/inbox/inbox-types.js +1 -0
- package/dist/work/kanban-drag.js +27 -22
- package/dist/work/kanban-types.js +1 -0
- package/dist/work/presets/sales.js +36 -32
- package/dist/work/reorder.js +9 -8
- package/dist/work/task-types.js +1 -0
- package/dist/work/time/duration.js +51 -46
- package/dist/work/time/time-types.js +23 -23
- package/dist/work/use-kanban-dnd.svelte.js +126 -125
- package/package.json +3 -1
- package/src/components/list-page/master-detail.svelte +3 -1
- package/src/components/list-page/rail-row.svelte +4 -4
- package/src/components/tags-input/tags-input.svelte +2 -0
- 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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
90
|
+
"BUTTON",
|
|
91
|
+
"A",
|
|
92
|
+
"SUMMARY",
|
|
93
|
+
"SELECT",
|
|
94
|
+
"OPTION"
|
|
171
95
|
]);
|
|
172
96
|
const SPACE_OWNING_ROLES = new Set([
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
4
|
-
|
|
4
|
+
width: 250,
|
|
5
|
+
height: 150
|
|
5
6
|
};
|
|
6
7
|
function setX(nodes, x) {
|
|
7
|
-
|
|
8
|
+
for (const n of nodes) n.position.x = x;
|
|
8
9
|
}
|
|
9
10
|
function setY(nodes, y) {
|
|
10
|
-
|
|
11
|
+
for (const n of nodes) n.position.y = y;
|
|
11
12
|
}
|
|
12
13
|
const ALIGNERS = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
}
|