@1agh/maude 0.39.1 → 0.41.0
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/README.md +1 -1
- package/apps/studio/ai-banner.tsx +2 -2
- package/apps/studio/annotations-context-toolbar.tsx +44 -1
- package/apps/studio/annotations-layer.tsx +306 -5
- package/apps/studio/annotations-model.ts +107 -6
- package/apps/studio/api.ts +1316 -67
- package/apps/studio/artboard-marquee.tsx +1 -1
- package/apps/studio/bin/_canvas-rects-playwright.mjs +55 -0
- package/apps/studio/bin/_canvas-rects-static.mjs +166 -0
- package/apps/studio/bin/_fetch-asset.mjs +556 -0
- package/apps/studio/bin/_html-playwright.mjs +9 -1
- package/apps/studio/bin/_pdf-playwright.mjs +8 -1
- package/apps/studio/bin/_png-playwright.mjs +8 -1
- package/apps/studio/bin/_pw-launch.mjs +54 -0
- package/apps/studio/bin/_svg-playwright.mjs +8 -1
- package/apps/studio/bin/_video-playwright.mjs +452 -0
- package/apps/studio/bin/annotate.mjs +576 -34
- package/apps/studio/bin/canvas-rects.sh +152 -0
- package/apps/studio/bin/fetch-asset.sh +34 -0
- package/apps/studio/bin/prep.sh +8 -1
- package/apps/studio/bin/read-annotations.mjs +138 -7
- package/apps/studio/bin/smoke.sh +42 -6
- package/apps/studio/build.ts +21 -0
- package/apps/studio/canvas-comment-mount.tsx +138 -4
- package/apps/studio/canvas-edit.ts +2625 -111
- package/apps/studio/canvas-lib.tsx +238 -2
- package/apps/studio/canvas-list-watch.ts +6 -2
- package/apps/studio/canvas-shell.tsx +514 -20
- package/apps/studio/client/app.jsx +2480 -52
- package/apps/studio/client/comments-overlay.css +130 -126
- package/apps/studio/client/github.js +15 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +860 -0
- package/apps/studio/client/panels/timeline-parse.js +229 -0
- package/apps/studio/client/panels/timeline-snap.js +55 -0
- package/apps/studio/client/styles/3-shell-maude.css +155 -0
- package/apps/studio/comments-overlay.tsx +148 -41
- package/apps/studio/config.schema.json +14 -0
- package/apps/studio/context-menu.tsx +16 -6
- package/apps/studio/context.ts +113 -1
- package/apps/studio/contextual-toolbar.tsx +262 -4
- package/apps/studio/cursors-overlay.tsx +4 -4
- package/apps/studio/dist/client.bundle.js +89 -17
- package/apps/studio/dist/comment-mount.js +59 -1
- package/apps/studio/dist/runtime/.min-sizes.json +11 -1
- package/apps/studio/dist/runtime/@remotion_media.js +491 -0
- package/apps/studio/dist/runtime/@remotion_player.js +56 -0
- package/apps/studio/dist/runtime/@remotion_transitions.js +300 -0
- package/apps/studio/dist/runtime/@remotion_transitions_clock-wipe.js +1 -0
- package/apps/studio/dist/runtime/@remotion_transitions_fade.js +1 -0
- package/apps/studio/dist/runtime/@remotion_transitions_flip.js +1 -0
- package/apps/studio/dist/runtime/@remotion_transitions_none.js +1 -0
- package/apps/studio/dist/runtime/@remotion_transitions_slide.js +1 -0
- package/apps/studio/dist/runtime/@remotion_transitions_wipe.js +1 -0
- package/apps/studio/dist/runtime/REMOTION-LICENSE.md +28 -0
- package/apps/studio/dist/runtime/remotion.js +42 -0
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +127 -1
- package/apps/studio/drag-state.ts +24 -0
- package/apps/studio/equal-spacing-detector.ts +205 -0
- package/apps/studio/export-dialog.tsx +1 -1
- package/apps/studio/exporters/_browser-bundles.ts +117 -0
- package/apps/studio/exporters/_runtime.ts +69 -0
- package/apps/studio/exporters/html.ts +4 -3
- package/apps/studio/exporters/index.ts +28 -2
- package/apps/studio/exporters/pdf.ts +4 -3
- package/apps/studio/exporters/png.ts +5 -3
- package/apps/studio/exporters/pptx.ts +6 -4
- package/apps/studio/exporters/svg.ts +9 -5
- package/apps/studio/exporters/video-encode-lib.ts +200 -0
- package/apps/studio/exporters/video-render-lib.ts +108 -0
- package/apps/studio/exporters/video.ts +184 -0
- package/apps/studio/history.ts +47 -1
- package/apps/studio/http.ts +758 -27
- package/apps/studio/input-router.tsx +19 -1
- package/apps/studio/marquee-overlay.tsx +1 -1
- package/apps/studio/measure-overlay.tsx +241 -0
- package/apps/studio/participants-chrome.tsx +3 -3
- package/apps/studio/runtime-bundle.ts +30 -0
- package/apps/studio/server.ts +34 -9
- package/apps/studio/sizing-mode.ts +117 -0
- package/apps/studio/spacing-handles.ts +166 -0
- package/apps/studio/test/annotate-write.test.ts +890 -0
- package/apps/studio/test/annotations-roundtrip.test.ts +47 -0
- package/apps/studio/test/camera-reveal.test.tsx +173 -0
- package/apps/studio/test/canvas-create-api.test.ts +76 -0
- package/apps/studio/test/canvas-edit.test.ts +140 -0
- package/apps/studio/test/canvas-list-watch.test.ts +49 -0
- package/apps/studio/test/canvas-media-drop.test.ts +30 -1
- package/apps/studio/test/canvas-origin-gate.test.ts +54 -0
- package/apps/studio/test/canvas-rects.test.ts +198 -0
- package/apps/studio/test/clip-addressing.test.ts +732 -0
- package/apps/studio/test/comments-overlay.test.ts +117 -0
- package/apps/studio/test/config-reload.test.ts +230 -0
- package/apps/studio/test/dns-rebinding-guard.test.ts +74 -0
- package/apps/studio/test/dom-selection.test.ts +130 -0
- package/apps/studio/test/edit-css-occurrence.test.ts +81 -0
- package/apps/studio/test/edit-persistence.test.ts +91 -0
- package/apps/studio/test/edit-scope-api.test.ts +115 -0
- package/apps/studio/test/element-resize.test.ts +136 -0
- package/apps/studio/test/element-structural-api.test.ts +360 -0
- package/apps/studio/test/element-structural-edit.test.ts +233 -0
- package/apps/studio/test/equal-spacing-detector.test.ts +165 -1
- package/apps/studio/test/exporters/runtime.test.ts +59 -0
- package/apps/studio/test/file-lock.test.ts +84 -0
- package/apps/studio/test/fixtures/video-comp-fixture.tsx +82 -0
- package/apps/studio/test/history-rollback.test.ts +26 -0
- package/apps/studio/test/knob-props-authored.test.ts +87 -0
- package/apps/studio/test/read-annotations.test.ts +154 -0
- package/apps/studio/test/sizing-mode.test.ts +102 -0
- package/apps/studio/test/spacing-handles.test.ts +138 -0
- package/apps/studio/test/specimen-select.test.ts +88 -0
- package/apps/studio/test/timeline-parse.test.ts +127 -0
- package/apps/studio/test/timeline-snap.test.ts +85 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +84 -0
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +211 -0
- package/apps/studio/test/video-asset.test.ts +163 -0
- package/apps/studio/test/video-comp-fixture.test.ts +50 -0
- package/apps/studio/test/video-comp.test.ts +168 -0
- package/apps/studio/test/video-render-bridge.test.ts +149 -0
- package/apps/studio/tool-palette.tsx +122 -2
- package/apps/studio/undo-hud.tsx +2 -2
- package/apps/studio/use-annotation-resize.tsx +6 -3
- package/apps/studio/use-canvas-media-drop.tsx +66 -4
- package/apps/studio/use-element-resize.tsx +732 -0
- package/apps/studio/use-keyboard-discipline.tsx +205 -15
- package/apps/studio/use-selection-set.tsx +14 -0
- package/apps/studio/use-spacing-handles.tsx +388 -0
- package/apps/studio/video-comp.tsx +444 -0
- package/apps/studio/whats-new.json +55 -0
- package/apps/studio/ws.ts +5 -0
- package/cli/commands/design.mjs +6 -1
- package/cli/commands/design.test.mjs +49 -1
- package/cli/lib/fetch-asset.test.mjs +213 -0
- package/package.json +8 -8
- package/plugins/design/dependencies.json +10 -2
- package/plugins/design/templates/_shell.html +25 -2
- package/plugins/design/templates/design-system-inspiration/_MAPPING.md +1 -1
|
@@ -28,10 +28,19 @@
|
|
|
28
28
|
* the 180 ms position tween per the plan note.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import { useEffect, useRef } from 'react';
|
|
32
|
-
|
|
31
|
+
import { type ReactNode, useEffect, useRef } from 'react';
|
|
33
32
|
import { resolveSelectionEl } from './dom-selection.ts';
|
|
34
|
-
import {
|
|
33
|
+
import { isElementDragActive } from './drag-state.ts';
|
|
34
|
+
import {
|
|
35
|
+
type AlignAxisMode,
|
|
36
|
+
computeAlign,
|
|
37
|
+
computeDistribute,
|
|
38
|
+
computeTidyGrid,
|
|
39
|
+
type KeyedRect,
|
|
40
|
+
type MovedRect,
|
|
41
|
+
type SpacingAxis,
|
|
42
|
+
} from './equal-spacing-detector.ts';
|
|
43
|
+
import { type Selection, useSelectionSet } from './use-selection-set.tsx';
|
|
35
44
|
|
|
36
45
|
const CTX_TOOLBAR_CSS = `
|
|
37
46
|
.dc-elem-ctx-tb {
|
|
@@ -66,11 +75,17 @@ const CTX_TOOLBAR_CSS = `
|
|
|
66
75
|
font: inherit;
|
|
67
76
|
cursor: pointer;
|
|
68
77
|
color: inherit;
|
|
78
|
+
display: inline-flex;
|
|
79
|
+
align-items: center;
|
|
69
80
|
transition: background-color 80ms linear;
|
|
70
81
|
}
|
|
71
|
-
.dc-elem-ctx-tb button:hover {
|
|
82
|
+
.dc-elem-ctx-tb button:hover:not(:disabled) {
|
|
72
83
|
background: color-mix(in oklab, var(--maude-hud-accent, #d63b1f) 8%, transparent);
|
|
73
84
|
}
|
|
85
|
+
.dc-elem-ctx-tb button:disabled {
|
|
86
|
+
cursor: default;
|
|
87
|
+
opacity: 0.4;
|
|
88
|
+
}
|
|
74
89
|
.dc-elem-ctx-tb .dc-elem-ctx-count {
|
|
75
90
|
padding: 4px 8px 4px 10px;
|
|
76
91
|
color: var(--maude-chrome-fg-1, rgba(40,30,20,0.7));
|
|
@@ -78,6 +93,17 @@ const CTX_TOOLBAR_CSS = `
|
|
|
78
93
|
margin-right: 2px;
|
|
79
94
|
font-variant-numeric: tabular-nums;
|
|
80
95
|
}
|
|
96
|
+
.dc-elem-ctx-tb .dc-elem-ctx-icon {
|
|
97
|
+
display: inline-flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
}
|
|
101
|
+
.dc-elem-ctx-tb .dc-elem-ctx-divider {
|
|
102
|
+
width: 1px;
|
|
103
|
+
align-self: stretch;
|
|
104
|
+
background: var(--maude-chrome-border, rgba(0,0,0,0.10));
|
|
105
|
+
margin: 0 4px;
|
|
106
|
+
}
|
|
81
107
|
@media (prefers-reduced-motion: reduce) {
|
|
82
108
|
.dc-elem-ctx-tb, .dc-elem-ctx-tb button { transition: none; }
|
|
83
109
|
}
|
|
@@ -133,6 +159,98 @@ function openComposerForSelection(
|
|
|
133
159
|
}
|
|
134
160
|
}
|
|
135
161
|
|
|
162
|
+
interface AlignTarget {
|
|
163
|
+
sel: Selection;
|
|
164
|
+
x: number;
|
|
165
|
+
y: number;
|
|
166
|
+
w: number;
|
|
167
|
+
h: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resolve every selection to its current world-space box, gated on
|
|
172
|
+
* absolute/fixed positioning — align/distribute/tidy-up write `left`/`top`,
|
|
173
|
+
* which is only meaningful for out-of-flow elements (this codebase
|
|
174
|
+
* deliberately never does an implicit convert-to-absolute-on-drag; see the
|
|
175
|
+
* Stage-D dogfood notes). Returns `null` (all-or-nothing) if any selection
|
|
176
|
+
* doesn't resolve, or isn't out-of-flow, or has a non-numeric `left`/`top`
|
|
177
|
+
* (e.g. positioned via `right`/`bottom` instead — a known v1 limit).
|
|
178
|
+
*/
|
|
179
|
+
function resolveAlignTargets(sels: Selection[]): AlignTarget[] | null {
|
|
180
|
+
const out: AlignTarget[] = [];
|
|
181
|
+
for (const sel of sels) {
|
|
182
|
+
const node = resolveSelectionEl(document, sel);
|
|
183
|
+
if (!node) return null;
|
|
184
|
+
const cs = getComputedStyle(node as HTMLElement);
|
|
185
|
+
if (cs.position !== 'absolute' && cs.position !== 'fixed') return null;
|
|
186
|
+
const left = Number.parseFloat(cs.left);
|
|
187
|
+
const top = Number.parseFloat(cs.top);
|
|
188
|
+
if (!Number.isFinite(left) || !Number.isFinite(top)) return null;
|
|
189
|
+
out.push({
|
|
190
|
+
sel,
|
|
191
|
+
x: left,
|
|
192
|
+
y: top,
|
|
193
|
+
w: (node as HTMLElement).offsetWidth,
|
|
194
|
+
h: (node as HTMLElement).offsetHeight,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Post one `reposition-request` per moved rect — the same message shape +
|
|
201
|
+
* write lane (`repositionElement` → `/_api/edit-css` ×2, per-prop undo) an
|
|
202
|
+
* in-canvas element drag already posts. No new endpoint, no new trust
|
|
203
|
+
* boundary; Cmd+Z steps back one element at a time (same N-step precedent
|
|
204
|
+
* as Task L4's paste-style). */
|
|
205
|
+
function commitMoves(targets: AlignTarget[], moved: MovedRect[]): void {
|
|
206
|
+
for (const m of moved) {
|
|
207
|
+
const t = targets[Number(m.key)];
|
|
208
|
+
if (!t?.sel.id) continue;
|
|
209
|
+
try {
|
|
210
|
+
window.parent.postMessage(
|
|
211
|
+
{
|
|
212
|
+
dgn: 'reposition-request',
|
|
213
|
+
id: t.sel.id,
|
|
214
|
+
left: m.x,
|
|
215
|
+
top: m.y,
|
|
216
|
+
beforeLeft: Math.round(t.x),
|
|
217
|
+
beforeTop: Math.round(t.y),
|
|
218
|
+
idIndex: t.sel.index ?? 0,
|
|
219
|
+
},
|
|
220
|
+
'*'
|
|
221
|
+
);
|
|
222
|
+
} catch {
|
|
223
|
+
/* detached / cross-origin */
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function toKeyedRects(targets: AlignTarget[]): KeyedRect[] {
|
|
229
|
+
return targets.map((t, i) => ({ key: String(i), x: t.x, y: t.y, w: t.w, h: t.h }));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function CtxIconButton({
|
|
233
|
+
label,
|
|
234
|
+
title,
|
|
235
|
+
onClick,
|
|
236
|
+
disabled,
|
|
237
|
+
children,
|
|
238
|
+
}: {
|
|
239
|
+
label: string;
|
|
240
|
+
title: string;
|
|
241
|
+
onClick: () => void;
|
|
242
|
+
disabled?: boolean;
|
|
243
|
+
children: ReactNode;
|
|
244
|
+
}) {
|
|
245
|
+
return (
|
|
246
|
+
<button type="button" title={title} aria-label={label} onClick={onClick} disabled={disabled}>
|
|
247
|
+
<span className="dc-elem-ctx-icon" aria-hidden="true">
|
|
248
|
+
{children}
|
|
249
|
+
</span>
|
|
250
|
+
</button>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
136
254
|
export function ContextualToolbar() {
|
|
137
255
|
ensureStyles();
|
|
138
256
|
const { selected } = useSelectionSet();
|
|
@@ -154,6 +272,18 @@ export function ContextualToolbar() {
|
|
|
154
272
|
}
|
|
155
273
|
const tick = () => {
|
|
156
274
|
rafRef.current = null;
|
|
275
|
+
// Dogfood 2026-07-07 — "hrozně zavaší ten toolbar" during a reorder drag:
|
|
276
|
+
// the floating pill has nothing useful to say mid-drag anyway (Inspect /
|
|
277
|
+
// Copy CSS / Copy ID on an element that's about to move), and its own
|
|
278
|
+
// per-frame resolveSelectionEl + getBoundingClientRect work was
|
|
279
|
+
// compounding with the drag's own DOM churn + the resize/spacing
|
|
280
|
+
// overlays' rAF loops. Hide it for the duration, like Figma/Webflow do.
|
|
281
|
+
if (isElementDragActive()) {
|
|
282
|
+
div.style.display = 'none';
|
|
283
|
+
div.setAttribute('data-on', 'false');
|
|
284
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
157
287
|
let xMin = Number.POSITIVE_INFINITY;
|
|
158
288
|
let yMin = Number.POSITIVE_INFINITY;
|
|
159
289
|
let xMax = Number.NEGATIVE_INFINITY;
|
|
@@ -204,6 +334,30 @@ export function ContextualToolbar() {
|
|
|
204
334
|
const primary = elementSelections[0];
|
|
205
335
|
const count = elementSelections.length;
|
|
206
336
|
|
|
337
|
+
// Task L5 — align (≥2) / distribute (≥3) / tidy-up (≥2) for a multi-element
|
|
338
|
+
// selection. Button enablement is count-gated (matching MultiArtboardToolbar's
|
|
339
|
+
// G7 cluster); the absolute/fixed-positioning eligibility check happens lazily
|
|
340
|
+
// on click via `resolveAlignTargets` (a silent no-op otherwise, rather than a
|
|
341
|
+
// per-frame DOM scan just to grey out a button).
|
|
342
|
+
const alignOk = count >= 2;
|
|
343
|
+
const distributeOk = count >= 3;
|
|
344
|
+
|
|
345
|
+
const runAlign = (mode: AlignAxisMode) => {
|
|
346
|
+
const targets = resolveAlignTargets(elementSelections);
|
|
347
|
+
if (!targets) return;
|
|
348
|
+
commitMoves(targets, computeAlign(toKeyedRects(targets), mode));
|
|
349
|
+
};
|
|
350
|
+
const runDistribute = (axis: SpacingAxis) => {
|
|
351
|
+
const targets = resolveAlignTargets(elementSelections);
|
|
352
|
+
if (!targets) return;
|
|
353
|
+
commitMoves(targets, computeDistribute(toKeyedRects(targets), axis));
|
|
354
|
+
};
|
|
355
|
+
const runTidyUp = () => {
|
|
356
|
+
const targets = resolveAlignTargets(elementSelections);
|
|
357
|
+
if (!targets) return;
|
|
358
|
+
commitMoves(targets, computeTidyGrid(toKeyedRects(targets)));
|
|
359
|
+
};
|
|
360
|
+
|
|
207
361
|
return (
|
|
208
362
|
<div ref={ref} className="dc-elem-ctx-tb" role="toolbar" aria-label="Element actions">
|
|
209
363
|
<span className="dc-elem-ctx-count">{count === 1 ? '1 element' : `${count} elements`}</span>
|
|
@@ -247,6 +401,110 @@ export function ContextualToolbar() {
|
|
|
247
401
|
>
|
|
248
402
|
Comment
|
|
249
403
|
</button>
|
|
404
|
+
{alignOk && (
|
|
405
|
+
<>
|
|
406
|
+
<span className="dc-elem-ctx-divider" aria-hidden="true" />
|
|
407
|
+
<CtxIconButton label="Align left" title="Align left" onClick={() => runAlign('left')}>
|
|
408
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
409
|
+
<line x1="0.5" y1="0.5" x2="0.5" y2="13.5" stroke="currentColor" />
|
|
410
|
+
<rect x="1" y="2" width="7" height="3" fill="currentColor" />
|
|
411
|
+
<rect x="1" y="9" width="11" height="3" fill="currentColor" />
|
|
412
|
+
</svg>
|
|
413
|
+
</CtxIconButton>
|
|
414
|
+
<CtxIconButton
|
|
415
|
+
label="Align center horizontally"
|
|
416
|
+
title="Align center (horizontal)"
|
|
417
|
+
onClick={() => runAlign('center-x')}
|
|
418
|
+
>
|
|
419
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
420
|
+
<line x1="7" y1="0.5" x2="7" y2="13.5" stroke="currentColor" />
|
|
421
|
+
<rect x="3.5" y="2" width="7" height="3" fill="currentColor" />
|
|
422
|
+
<rect x="1.5" y="9" width="11" height="3" fill="currentColor" />
|
|
423
|
+
</svg>
|
|
424
|
+
</CtxIconButton>
|
|
425
|
+
<CtxIconButton label="Align right" title="Align right" onClick={() => runAlign('right')}>
|
|
426
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
427
|
+
<line x1="13.5" y1="0.5" x2="13.5" y2="13.5" stroke="currentColor" />
|
|
428
|
+
<rect x="6" y="2" width="7" height="3" fill="currentColor" />
|
|
429
|
+
<rect x="2" y="9" width="11" height="3" fill="currentColor" />
|
|
430
|
+
</svg>
|
|
431
|
+
</CtxIconButton>
|
|
432
|
+
<CtxIconButton label="Align top" title="Align top" onClick={() => runAlign('top')}>
|
|
433
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
434
|
+
<line x1="0.5" y1="0.5" x2="13.5" y2="0.5" stroke="currentColor" />
|
|
435
|
+
<rect x="2" y="1" width="3" height="7" fill="currentColor" />
|
|
436
|
+
<rect x="9" y="1" width="3" height="11" fill="currentColor" />
|
|
437
|
+
</svg>
|
|
438
|
+
</CtxIconButton>
|
|
439
|
+
<CtxIconButton
|
|
440
|
+
label="Align center vertically"
|
|
441
|
+
title="Align center (vertical)"
|
|
442
|
+
onClick={() => runAlign('center-y')}
|
|
443
|
+
>
|
|
444
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
445
|
+
<line x1="0.5" y1="7" x2="13.5" y2="7" stroke="currentColor" />
|
|
446
|
+
<rect x="2" y="3.5" width="3" height="7" fill="currentColor" />
|
|
447
|
+
<rect x="9" y="1.5" width="3" height="11" fill="currentColor" />
|
|
448
|
+
</svg>
|
|
449
|
+
</CtxIconButton>
|
|
450
|
+
<CtxIconButton
|
|
451
|
+
label="Align bottom"
|
|
452
|
+
title="Align bottom"
|
|
453
|
+
onClick={() => runAlign('bottom')}
|
|
454
|
+
>
|
|
455
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
456
|
+
<line x1="0.5" y1="13.5" x2="13.5" y2="13.5" stroke="currentColor" />
|
|
457
|
+
<rect x="2" y="6" width="3" height="7" fill="currentColor" />
|
|
458
|
+
<rect x="9" y="2" width="3" height="11" fill="currentColor" />
|
|
459
|
+
</svg>
|
|
460
|
+
</CtxIconButton>
|
|
461
|
+
<span className="dc-elem-ctx-divider" aria-hidden="true" />
|
|
462
|
+
<CtxIconButton
|
|
463
|
+
label="Distribute horizontally"
|
|
464
|
+
title={
|
|
465
|
+
distributeOk
|
|
466
|
+
? 'Distribute horizontally — equal gaps between elements'
|
|
467
|
+
: 'Select at least 3 elements to distribute'
|
|
468
|
+
}
|
|
469
|
+
disabled={!distributeOk}
|
|
470
|
+
onClick={() => runDistribute('x')}
|
|
471
|
+
>
|
|
472
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
473
|
+
<rect x="0.5" y="3" width="3" height="8" fill="currentColor" />
|
|
474
|
+
<rect x="5.5" y="3" width="3" height="8" fill="currentColor" />
|
|
475
|
+
<rect x="10.5" y="3" width="3" height="8" fill="currentColor" />
|
|
476
|
+
</svg>
|
|
477
|
+
</CtxIconButton>
|
|
478
|
+
<CtxIconButton
|
|
479
|
+
label="Distribute vertically"
|
|
480
|
+
title={
|
|
481
|
+
distributeOk
|
|
482
|
+
? 'Distribute vertically — equal gaps between elements'
|
|
483
|
+
: 'Select at least 3 elements to distribute'
|
|
484
|
+
}
|
|
485
|
+
disabled={!distributeOk}
|
|
486
|
+
onClick={() => runDistribute('y')}
|
|
487
|
+
>
|
|
488
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
489
|
+
<rect x="3" y="0.5" width="8" height="3" fill="currentColor" />
|
|
490
|
+
<rect x="3" y="5.5" width="8" height="3" fill="currentColor" />
|
|
491
|
+
<rect x="3" y="10.5" width="8" height="3" fill="currentColor" />
|
|
492
|
+
</svg>
|
|
493
|
+
</CtxIconButton>
|
|
494
|
+
<CtxIconButton
|
|
495
|
+
label="Tidy up into a grid"
|
|
496
|
+
title="Tidy up — snap into a clean grid"
|
|
497
|
+
onClick={runTidyUp}
|
|
498
|
+
>
|
|
499
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
|
500
|
+
<rect x="0.5" y="0.5" width="5.5" height="5.5" fill="currentColor" />
|
|
501
|
+
<rect x="8" y="0.5" width="5.5" height="5.5" fill="currentColor" />
|
|
502
|
+
<rect x="0.5" y="8" width="5.5" height="5.5" fill="currentColor" />
|
|
503
|
+
<rect x="8" y="8" width="5.5" height="5.5" fill="currentColor" />
|
|
504
|
+
</svg>
|
|
505
|
+
</CtxIconButton>
|
|
506
|
+
</>
|
|
507
|
+
)}
|
|
250
508
|
</div>
|
|
251
509
|
);
|
|
252
510
|
}
|
|
@@ -55,7 +55,7 @@ const CURSOR_CSS = `
|
|
|
55
55
|
top: 14px;
|
|
56
56
|
left: 10px;
|
|
57
57
|
padding: 1px 8px;
|
|
58
|
-
font-family: var(--font-mono, ui-monospace, monospace);
|
|
58
|
+
font-family: var(--maude-chrome-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
59
59
|
font-weight: 500;
|
|
60
60
|
font-size: 10px;
|
|
61
61
|
line-height: 1.4;
|
|
@@ -72,7 +72,7 @@ const CURSOR_CSS = `
|
|
|
72
72
|
left: 0;
|
|
73
73
|
pointer-events: none;
|
|
74
74
|
border: 2px solid;
|
|
75
|
-
border-radius:
|
|
75
|
+
border-radius: 2px;
|
|
76
76
|
box-sizing: border-box;
|
|
77
77
|
will-change: transform, width, height;
|
|
78
78
|
}
|
|
@@ -81,12 +81,12 @@ const CURSOR_CSS = `
|
|
|
81
81
|
top: -18px;
|
|
82
82
|
left: -2px;
|
|
83
83
|
padding: 1px 5px;
|
|
84
|
-
font-family:
|
|
84
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
85
85
|
font-weight: 600;
|
|
86
86
|
font-size: 10px;
|
|
87
87
|
line-height: 1.3;
|
|
88
88
|
color: var(--maude-hud-accent-fg, #fff);
|
|
89
|
-
border-radius:
|
|
89
|
+
border-radius: 2px 2px 0 0;
|
|
90
90
|
white-space: nowrap;
|
|
91
91
|
}
|
|
92
92
|
/* Phase 30 / DDR-120 — soft editing-presence. When a peer (human or a bridged
|