@1agh/maude 0.40.0 → 0.42.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/apps/studio/ai-banner.tsx +2 -2
- package/apps/studio/annotations-layer.tsx +53 -2
- package/apps/studio/api.ts +504 -44
- 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 +26 -4
- package/apps/studio/bin/_pdf-playwright.mjs +13 -2
- package/apps/studio/bin/_png-playwright.mjs +15 -2
- package/apps/studio/bin/_pptx-playwright.mjs +17 -4
- package/apps/studio/bin/_screenshot-playwright.mjs +17 -2
- package/apps/studio/bin/_svg-playwright.mjs +26 -4
- 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/read-annotations.mjs +138 -7
- package/apps/studio/bin/screenshot.sh +53 -4
- 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 +744 -11
- package/apps/studio/canvas-lib.tsx +219 -2
- package/apps/studio/canvas-shell.tsx +487 -20
- package/apps/studio/client/app.jsx +1476 -76
- package/apps/studio/client/comments-overlay.css +130 -126
- package/apps/studio/client/export-center.jsx +426 -0
- package/apps/studio/client/github.js +8 -0
- package/apps/studio/client/styles/3-shell-maude.css +65 -0
- package/apps/studio/client/styles/4-components.css +150 -0
- package/apps/studio/comments-overlay.tsx +148 -41
- package/apps/studio/config.schema.json +2 -2
- package/apps/studio/context-menu.tsx +15 -5
- package/apps/studio/contextual-toolbar.tsx +262 -4
- package/apps/studio/cursors-overlay.tsx +4 -4
- package/apps/studio/dist/client.bundle.js +20 -20
- package/apps/studio/dist/comment-mount.js +59 -1
- 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 +19 -26
- package/apps/studio/exporters/_runtime.ts +104 -0
- package/apps/studio/exporters/html.ts +12 -20
- package/apps/studio/exporters/index.ts +14 -2
- package/apps/studio/exporters/jobs.ts +334 -0
- package/apps/studio/exporters/pdf.ts +16 -20
- package/apps/studio/exporters/png.ts +12 -20
- package/apps/studio/exporters/pptx.ts +22 -23
- package/apps/studio/exporters/scope.ts +1 -0
- package/apps/studio/exporters/svg.ts +14 -22
- package/apps/studio/exporters/video.ts +15 -17
- package/apps/studio/git/service.ts +3 -1
- package/apps/studio/history.ts +47 -1
- package/apps/studio/http.ts +368 -50
- package/apps/studio/input-router.tsx +12 -0
- 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/server.ts +3 -1
- 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/camera-reveal.test.tsx +173 -0
- package/apps/studio/test/canvas-edit.test.ts +50 -0
- package/apps/studio/test/canvas-origin-gate.test.ts +24 -0
- package/apps/studio/test/canvas-rects.test.ts +198 -0
- package/apps/studio/test/comments-overlay.test.ts +117 -0
- package/apps/studio/test/dns-rebinding-guard.test.ts +27 -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-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/export-center.test.tsx +287 -0
- package/apps/studio/test/export-shim-multi-capture.test.ts +83 -0
- package/apps/studio/test/exporters/history.test.ts +32 -3
- package/apps/studio/test/exporters/jobs.test.ts +263 -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/tool-palette-insert-anchor.test.ts +84 -0
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +211 -0
- package/apps/studio/tool-palette.tsx +122 -2
- package/apps/studio/undo-hud.tsx +2 -2
- 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/whats-new.json +45 -0
- package/apps/studio/ws.ts +6 -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/cli/lib/gitignore-block.mjs +1 -0
- package/package.json +8 -8
- package/plugins/design/dependencies.json +10 -2
|
@@ -42,7 +42,14 @@ import { createRoot } from 'react-dom/client';
|
|
|
42
42
|
|
|
43
43
|
import { CommentsOverlay } from './comments-overlay.tsx';
|
|
44
44
|
import { deriveFile, hoverTargetToSelection } from './dom-selection.ts';
|
|
45
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
type HoverTarget,
|
|
47
|
+
isOverlayTarget,
|
|
48
|
+
type RouterAction,
|
|
49
|
+
resolveHoverTarget,
|
|
50
|
+
useInputRouter,
|
|
51
|
+
} from './input-router.tsx';
|
|
52
|
+
import { ElementResizeOverlay } from './use-element-resize.tsx';
|
|
46
53
|
import {
|
|
47
54
|
MaybeSelectionSetProvider,
|
|
48
55
|
type Selection,
|
|
@@ -83,7 +90,7 @@ const COMMENT_CLAIMS: ReadonlySet<RouterAction['kind']> = new Set<RouterAction['
|
|
|
83
90
|
// bare DS specimen. On a UI canvas (`.dc-canvas` present) the inner shell owns
|
|
84
91
|
// hover-halo painting + `resolveHoverTarget` element anchoring, so the lite
|
|
85
92
|
// layer defers to it.
|
|
86
|
-
function isBareSpecimen(): boolean {
|
|
93
|
+
export function isBareSpecimen(): boolean {
|
|
87
94
|
return typeof document !== 'undefined' && !document.querySelector('.dc-canvas');
|
|
88
95
|
}
|
|
89
96
|
|
|
@@ -94,8 +101,12 @@ function pickSpecimenEl(clientX: number, clientY: number): HTMLElement | null {
|
|
|
94
101
|
if (typeof document === 'undefined') return null;
|
|
95
102
|
const hit = document.elementFromPoint(clientX, clientY) as HTMLElement | null;
|
|
96
103
|
if (!hit) return null;
|
|
97
|
-
// Never anchor to comment chrome or the
|
|
98
|
-
if (
|
|
104
|
+
// Never anchor to comment chrome, the selection/resize overlay, or the root.
|
|
105
|
+
if (
|
|
106
|
+
hit.closest(
|
|
107
|
+
'.cm-composer, .cm-thread, .cm-mention-popup, .cm-pin, [data-mc-hover-halo], [data-mc-selection-halo], .dc-el-resize-handle'
|
|
108
|
+
)
|
|
109
|
+
) {
|
|
99
110
|
return null;
|
|
100
111
|
}
|
|
101
112
|
const tag = hit.tagName;
|
|
@@ -103,6 +114,20 @@ function pickSpecimenEl(clientX: number, clientY: number): HTMLElement | null {
|
|
|
103
114
|
return hit;
|
|
104
115
|
}
|
|
105
116
|
|
|
117
|
+
// feature-element-editing-robustness Stage E — the SELECT anchor for a bare
|
|
118
|
+
// specimen. Generalizes `pickSpecimenEl` (the comment anchor) by climbing to the
|
|
119
|
+
// hit element's own `data-cd-id`, else its nearest stamped ancestor — every JSX
|
|
120
|
+
// element the pipeline stamps unconditionally (canvas-pipeline.ts), so a
|
|
121
|
+
// specimen element always resolves to a cd-id the Inspector's `edit-css`/
|
|
122
|
+
// `edit-attr` can target. Falls back to the bare hit when (defensively) nothing
|
|
123
|
+
// is stamped, in which case the selection degrades to a `cssPath` selector.
|
|
124
|
+
export function pickSpecimenSelectEl(clientX: number, clientY: number): HTMLElement | null {
|
|
125
|
+
const hit = pickSpecimenEl(clientX, clientY);
|
|
126
|
+
if (!hit) return null;
|
|
127
|
+
const stamped = hit.closest('[data-cd-id]') as HTMLElement | null;
|
|
128
|
+
return stamped ?? hit;
|
|
129
|
+
}
|
|
130
|
+
|
|
106
131
|
function CommentHost({ children, file }: { children: ReactNode; file: string | undefined }) {
|
|
107
132
|
const { tool, setTool } = useToolMode();
|
|
108
133
|
const selSet = useSelectionSet();
|
|
@@ -120,6 +145,57 @@ function CommentHost({ children, file }: { children: ReactNode; file: string | u
|
|
|
120
145
|
if (tool !== 'comment') setHoverEl(null);
|
|
121
146
|
}, [tool]);
|
|
122
147
|
|
|
148
|
+
// feature-element-editing-robustness Stage E — element SELECT on a bare
|
|
149
|
+
// specimen. `selectedEl` drives the selection halo; `isSpecimen` gates the
|
|
150
|
+
// resize overlay so it mounts on specimens ONLY (a UI canvas already mounts
|
|
151
|
+
// its own inside CanvasShell — mounting a second here would double the handles).
|
|
152
|
+
const [selectedEl, setSelectedEl] = useState<HTMLElement | null>(null);
|
|
153
|
+
const [isSpecimen, setIsSpecimen] = useState(false);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
// The inner DesignCanvas (`.dc-canvas`) mounts a beat after this layer, so
|
|
156
|
+
// re-check on the next frame + a short timeout before deciding.
|
|
157
|
+
const check = () => setIsSpecimen(isBareSpecimen());
|
|
158
|
+
check();
|
|
159
|
+
const raf = requestAnimationFrame(check);
|
|
160
|
+
const t = setTimeout(check, 120);
|
|
161
|
+
return () => {
|
|
162
|
+
cancelAnimationFrame(raf);
|
|
163
|
+
clearTimeout(t);
|
|
164
|
+
};
|
|
165
|
+
}, []);
|
|
166
|
+
|
|
167
|
+
// Specimen select — a capture-phase Cmd/Ctrl-click. Self-gated on
|
|
168
|
+
// `isBareSpecimen()` at event time: a UI canvas has its own CanvasShell select
|
|
169
|
+
// router (specimens have none), so this is the ONLY select handler on a
|
|
170
|
+
// specimen and a pure no-op on a UI canvas — no double-handling, no need to
|
|
171
|
+
// touch the shared COMMENT_CLAIMS (which would preventDefault a UI canvas's
|
|
172
|
+
// own select). `selSet.replace` posts `dgn:'select-set'` to the parent shell,
|
|
173
|
+
// so the Inspector opens (Stage C) + edits persist via `edit-css` (Stage E3).
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (typeof document === 'undefined') return;
|
|
176
|
+
const onDown = (e: PointerEvent) => {
|
|
177
|
+
if (e.button !== 0 || !(e.metaKey || e.ctrlKey)) return; // select gesture only
|
|
178
|
+
if (!isBareSpecimen()) return; // UI canvas → CanvasShell owns select
|
|
179
|
+
if (isOverlayTarget(e.target)) return; // comment chrome owns its clicks
|
|
180
|
+
const el = pickSpecimenSelectEl(e.clientX, e.clientY);
|
|
181
|
+
if (!el) return;
|
|
182
|
+
e.preventDefault();
|
|
183
|
+
e.stopImmediatePropagation();
|
|
184
|
+
const cdId = el.getAttribute('data-cd-id');
|
|
185
|
+
const sel = hoverTargetToSelection({ el, cdId, artboardId: null } as HoverTarget);
|
|
186
|
+
if (e.shiftKey) selSet.add(sel);
|
|
187
|
+
else selSet.replace(sel);
|
|
188
|
+
setSelectedEl(el);
|
|
189
|
+
};
|
|
190
|
+
document.addEventListener('pointerdown', onDown, true);
|
|
191
|
+
return () => document.removeEventListener('pointerdown', onDown, true);
|
|
192
|
+
}, [selSet]);
|
|
193
|
+
|
|
194
|
+
// Drop the selection halo when the selection clears (Esc / parent force-clear).
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (selSet.selected.length === 0) setSelectedEl(null);
|
|
197
|
+
}, [selSet.selected]);
|
|
198
|
+
|
|
123
199
|
// Reflect the active tool onto the host (and body, since the host is
|
|
124
200
|
// display:contents and can't carry a paintable cursor). Comment-mode CSS
|
|
125
201
|
// keys off `[data-active-tool="comment"]`.
|
|
@@ -187,11 +263,69 @@ function CommentHost({ children, file }: { children: ReactNode; file: string | u
|
|
|
187
263
|
<div data-mc-host ref={hostRef} style={{ display: 'contents' }}>
|
|
188
264
|
{children}
|
|
189
265
|
{hoverEl ? <MountHoverHalo el={hoverEl} /> : null}
|
|
266
|
+
{selectedEl ? <MountSelectionHalo el={selectedEl} /> : null}
|
|
267
|
+
{isSpecimen ? <ElementResizeOverlay /> : null}
|
|
190
268
|
<CommentsOverlay />
|
|
191
269
|
</div>
|
|
192
270
|
);
|
|
193
271
|
}
|
|
194
272
|
|
|
273
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
274
|
+
// MountSelectionHalo — Stage E. A steadier accent outline around the SELECTED
|
|
275
|
+
// specimen element (vs the lighter hover halo). rAF-follows the element's screen
|
|
276
|
+
// box; inline-styled (a bare specimen doesn't load canvas-lib's HALO_CSS).
|
|
277
|
+
|
|
278
|
+
function MountSelectionHalo({ el }: { el: HTMLElement }) {
|
|
279
|
+
const ref = useRef<HTMLDivElement | null>(null);
|
|
280
|
+
const targetRef = useRef<HTMLElement>(el);
|
|
281
|
+
targetRef.current = el;
|
|
282
|
+
const rafRef = useRef<number | null>(null);
|
|
283
|
+
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
const tick = () => {
|
|
286
|
+
rafRef.current = null;
|
|
287
|
+
const div = ref.current;
|
|
288
|
+
const t = targetRef.current;
|
|
289
|
+
if (div && t?.isConnected) {
|
|
290
|
+
const r = t.getBoundingClientRect();
|
|
291
|
+
if (r.width === 0 && r.height === 0) {
|
|
292
|
+
div.style.display = 'none';
|
|
293
|
+
} else {
|
|
294
|
+
div.style.display = 'block';
|
|
295
|
+
div.style.left = `${Math.round(r.left)}px`;
|
|
296
|
+
div.style.top = `${Math.round(r.top)}px`;
|
|
297
|
+
div.style.width = `${Math.round(r.width)}px`;
|
|
298
|
+
div.style.height = `${Math.round(r.height)}px`;
|
|
299
|
+
}
|
|
300
|
+
} else if (div) {
|
|
301
|
+
div.style.display = 'none';
|
|
302
|
+
}
|
|
303
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
304
|
+
};
|
|
305
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
306
|
+
return () => {
|
|
307
|
+
if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
|
|
308
|
+
};
|
|
309
|
+
}, []);
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
<div
|
|
313
|
+
ref={ref}
|
|
314
|
+
aria-hidden="true"
|
|
315
|
+
data-mc-selection-halo=""
|
|
316
|
+
style={{
|
|
317
|
+
position: 'fixed',
|
|
318
|
+
display: 'none',
|
|
319
|
+
pointerEvents: 'none',
|
|
320
|
+
zIndex: 2147483645,
|
|
321
|
+
border: '1.5px solid var(--maude-hud-accent, oklch(0.680 0.180 268))',
|
|
322
|
+
borderRadius: '3px',
|
|
323
|
+
boxSizing: 'border-box',
|
|
324
|
+
}}
|
|
325
|
+
/>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
195
329
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
196
330
|
// MountHoverHalo — fixed-position outline tracking the hovered element's screen
|
|
197
331
|
// bounds via rAF. Inline-styled (no dependency on canvas-lib's HALO_CSS, which
|