@agent-native/core 0.84.0 → 0.84.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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +13 -0
- package/corpus/core/docs/content/evals.mdx +34 -12
- package/corpus/core/docs/content/locales/ar-SA/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/de-DE/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/es-ES/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/fr-FR/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/hi-IN/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/ja-JP/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/ko-KR/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/pt-BR/evals.mdx +8 -0
- package/corpus/core/docs/content/locales/zh-CN/evals.mdx +7 -0
- package/corpus/core/docs/content/locales/zh-TW/evals.mdx +7 -0
- package/corpus/core/docs/design/durable-agent-runs.md +34 -35
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builder-engine.ts +81 -9
- package/corpus/core/src/agent/run-manager.ts +8 -7
- package/corpus/core/src/cli/design-connect.ts +175 -0
- package/corpus/core/src/cli/skills.ts +5 -7
- package/corpus/core/src/client/AgentPanel.tsx +8 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +4 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +45 -15
- package/corpus/core/src/client/sse-event-processor.ts +31 -4
- package/corpus/core/src/eval/define-eval.ts +4 -1
- package/corpus/core/src/eval/report.ts +23 -8
- package/corpus/core/src/eval/runner.ts +41 -6
- package/corpus/core/src/eval/types.ts +11 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/corpus/templates/content/AGENTS.md +9 -2
- package/corpus/templates/content/actions/_database-row-batch.ts +230 -0
- package/corpus/templates/content/actions/_database-utils.ts +17 -9
- package/corpus/templates/content/actions/_notion-action-utils.ts +26 -0
- package/corpus/templates/content/actions/connect-notion-status.ts +13 -4
- package/corpus/templates/content/actions/create-and-link-notion-page.ts +23 -0
- package/corpus/templates/content/actions/delete-database-items.ts +55 -0
- package/corpus/templates/content/actions/delete-document.ts +4 -4
- package/corpus/templates/content/actions/disconnect-notion.ts +16 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +1 -1
- package/corpus/templates/content/actions/duplicate-database-items.ts +191 -0
- package/corpus/templates/content/actions/link-notion-page.ts +10 -7
- package/corpus/templates/content/actions/list-notion-links.ts +3 -4
- package/corpus/templates/content/actions/pull-notion-page.ts +7 -9
- package/corpus/templates/content/actions/push-notion-page.ts +7 -9
- package/corpus/templates/content/actions/refresh-notion-sync-status.ts +24 -0
- package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +23 -0
- package/corpus/templates/content/actions/search-notion-pages.ts +67 -0
- package/corpus/templates/content/actions/unlink-notion-page.ts +23 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +27 -34
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +16 -13
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +10 -10
- package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +1 -1
- package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +18 -9
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +27 -34
- package/corpus/templates/content/app/components/editor/database/navigation-state.ts +10 -4
- package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +32 -17
- package/corpus/templates/content/app/hooks/use-content-database.ts +35 -0
- package/corpus/templates/content/app/hooks/use-notion.ts +108 -99
- package/corpus/templates/content/package.json +3 -0
- package/corpus/templates/content/parity/README.md +69 -0
- package/corpus/templates/content/parity/eval-scenarios.ts +118 -0
- package/corpus/templates/content/parity/exceptions.allowlist.ts +17 -0
- package/corpus/templates/content/parity/matrix.md +28 -0
- package/corpus/templates/content/parity/matrix.ts +606 -0
- package/corpus/templates/content/parity/matrix.types.ts +42 -0
- package/corpus/templates/content/parity/parity-evals.eval.ts +4 -0
- package/corpus/templates/content/parity/render-matrix.ts +90 -0
- package/corpus/templates/content/parity/scenario-to-eval.ts +57 -0
- package/corpus/templates/content/shared/api.ts +11 -0
- package/corpus/templates/design/actions/apply-motion-edit.ts +50 -18
- package/corpus/templates/design/actions/get-motion-timeline.ts +4 -14
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +156 -12
- package/corpus/templates/design/app/components/design/MotionDock.tsx +2 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +217 -75
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +39 -39
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +558 -37
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +75 -3
- package/corpus/templates/design/app/components/design/types.ts +13 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +17 -0
- package/corpus/templates/design/app/i18n-data.ts +245 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +762 -153
- package/corpus/templates/design/changelog/2026-06-30-copying-or-dragging-screen-elements-onto-the-infinite-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-questions-now-use-tighter-editor-typography-and-contr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-element-drags-can-be-cancelled-with-escape-before-they-commi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-pending-visual-style-edits-now-warn-before-you-leave-the-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-style-drags-stay-live-while-pending-edits-can-be-appl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-a-redundant-context-tab-above-the-composer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-motion-track-creation-no-longer-fails-in-local-editors.md +6 -0
- package/corpus/templates/design/server/plugins/db.ts +10 -0
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +61 -10
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/run-manager.d.ts +8 -7
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +8 -7
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/cli/design-connect.d.ts +2 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +140 -0
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +5 -7
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +4 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +3 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +2 -2
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +37 -14
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +14 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +21 -5
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/eval/define-eval.d.ts.map +1 -1
- package/dist/eval/define-eval.js +2 -1
- package/dist/eval/define-eval.js.map +1 -1
- package/dist/eval/report.d.ts.map +1 -1
- package/dist/eval/report.js +19 -4
- package/dist/eval/report.js.map +1 -1
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +38 -6
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval/types.d.ts +11 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/eval/types.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/docs/content/evals.mdx +34 -12
- package/docs/content/locales/ar-SA/evals.mdx +7 -0
- package/docs/content/locales/de-DE/evals.mdx +8 -0
- package/docs/content/locales/es-ES/evals.mdx +8 -0
- package/docs/content/locales/fr-FR/evals.mdx +8 -0
- package/docs/content/locales/hi-IN/evals.mdx +8 -0
- package/docs/content/locales/ja-JP/evals.mdx +7 -0
- package/docs/content/locales/ko-KR/evals.mdx +7 -0
- package/docs/content/locales/pt-BR/evals.mdx +8 -0
- package/docs/content/locales/zh-CN/evals.mdx +7 -0
- package/docs/content/locales/zh-TW/evals.mdx +7 -0
- package/docs/design/durable-agent-runs.md +34 -35
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/corpus/templates/content/server/routes/api/notion/disconnect.post.ts +0 -12
|
@@ -131,7 +131,14 @@ import {
|
|
|
131
131
|
type SetStateAction,
|
|
132
132
|
} from "react";
|
|
133
133
|
import { flushSync } from "react-dom";
|
|
134
|
-
import {
|
|
134
|
+
import {
|
|
135
|
+
useParams,
|
|
136
|
+
useNavigate,
|
|
137
|
+
Link,
|
|
138
|
+
useLocation,
|
|
139
|
+
useBlocker,
|
|
140
|
+
useBeforeUnload,
|
|
141
|
+
} from "react-router";
|
|
135
142
|
import { toast } from "sonner";
|
|
136
143
|
import * as Y from "yjs";
|
|
137
144
|
|
|
@@ -186,6 +193,8 @@ import type {
|
|
|
186
193
|
ElementInfo,
|
|
187
194
|
ElementSelectionIntent,
|
|
188
195
|
DeviceFrameType,
|
|
196
|
+
PortableStyleSnapshot,
|
|
197
|
+
PortableStyleSnapshotNode,
|
|
189
198
|
} from "@/components/design/types";
|
|
190
199
|
import {
|
|
191
200
|
DEVICE_FRAME_VIEWPORTS,
|
|
@@ -193,6 +202,16 @@ import {
|
|
|
193
202
|
} from "@/components/design/types";
|
|
194
203
|
import PromptPopover from "@/components/editor/PromptDialog";
|
|
195
204
|
import type { UploadedFile } from "@/components/editor/PromptDialog";
|
|
205
|
+
import {
|
|
206
|
+
AlertDialog,
|
|
207
|
+
AlertDialogAction,
|
|
208
|
+
AlertDialogCancel,
|
|
209
|
+
AlertDialogContent,
|
|
210
|
+
AlertDialogDescription,
|
|
211
|
+
AlertDialogFooter,
|
|
212
|
+
AlertDialogHeader,
|
|
213
|
+
AlertDialogTitle,
|
|
214
|
+
} from "@/components/ui/alert-dialog";
|
|
196
215
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
197
216
|
import { Button } from "@/components/ui/button";
|
|
198
217
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
@@ -332,11 +351,29 @@ function dedupeStringIds(ids: string[]): string[] {
|
|
|
332
351
|
return Array.from(new Set(ids.filter(Boolean)));
|
|
333
352
|
}
|
|
334
353
|
|
|
354
|
+
function sameStringIds(a: string[], b: string[]) {
|
|
355
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
356
|
+
}
|
|
357
|
+
|
|
335
358
|
export function isScreenRootElementInfo(info: ElementInfo | null | undefined) {
|
|
336
359
|
const tagName = info?.tagName?.toUpperCase();
|
|
337
360
|
return tagName === "BODY" || tagName === "HTML";
|
|
338
361
|
}
|
|
339
362
|
|
|
363
|
+
export function shouldIgnoreOverviewLayerCreationEcho(args: {
|
|
364
|
+
pendingLayerId: string | null | undefined;
|
|
365
|
+
pendingScreenId: string | null | undefined;
|
|
366
|
+
screenId: string;
|
|
367
|
+
info?: ElementInfo | null;
|
|
368
|
+
event: "select" | "clear";
|
|
369
|
+
}) {
|
|
370
|
+
if (!args.pendingLayerId) return false;
|
|
371
|
+
if (args.pendingScreenId && args.pendingScreenId !== args.screenId) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
return args.event === "clear" || isScreenRootElementInfo(args.info);
|
|
375
|
+
}
|
|
376
|
+
|
|
340
377
|
export function getSelectedScreenIdsForEditorState(args: {
|
|
341
378
|
activeFileId: string | null | undefined;
|
|
342
379
|
overviewSelectedScreenIds: string[];
|
|
@@ -866,6 +903,99 @@ interface LiveScreenSnapshot {
|
|
|
866
903
|
contentType?: string;
|
|
867
904
|
}
|
|
868
905
|
|
|
906
|
+
export interface PendingVisualStyleEdit {
|
|
907
|
+
screenId: string;
|
|
908
|
+
filename: string;
|
|
909
|
+
screenName: string;
|
|
910
|
+
selector: string;
|
|
911
|
+
sourceId?: string | null;
|
|
912
|
+
tagName?: string | null;
|
|
913
|
+
classes: string[];
|
|
914
|
+
styles: Record<string, string>;
|
|
915
|
+
updatedAt: number;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function pendingVisualStyleEditKey(edit: PendingVisualStyleEdit): string {
|
|
919
|
+
return [
|
|
920
|
+
edit.screenId,
|
|
921
|
+
edit.sourceId?.trim() || edit.selector.trim() || "unknown",
|
|
922
|
+
].join("::");
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
export function mergePendingVisualStyleEdit(
|
|
926
|
+
edits: readonly PendingVisualStyleEdit[],
|
|
927
|
+
nextEdit: PendingVisualStyleEdit,
|
|
928
|
+
): PendingVisualStyleEdit[] {
|
|
929
|
+
const nextKey = pendingVisualStyleEditKey(nextEdit);
|
|
930
|
+
let merged = false;
|
|
931
|
+
const next = edits.map((edit) => {
|
|
932
|
+
if (pendingVisualStyleEditKey(edit) !== nextKey) return edit;
|
|
933
|
+
merged = true;
|
|
934
|
+
return {
|
|
935
|
+
...edit,
|
|
936
|
+
...nextEdit,
|
|
937
|
+
classes: nextEdit.classes.length > 0 ? nextEdit.classes : edit.classes,
|
|
938
|
+
styles: { ...edit.styles, ...nextEdit.styles },
|
|
939
|
+
};
|
|
940
|
+
});
|
|
941
|
+
return merged ? next : [...edits, nextEdit];
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
export function getPendingVisualStylePropertyCount(
|
|
945
|
+
edits: readonly PendingVisualStyleEdit[],
|
|
946
|
+
): number {
|
|
947
|
+
return edits.reduce(
|
|
948
|
+
(count, edit) => count + Object.keys(edit.styles).length,
|
|
949
|
+
0,
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
export function shouldBlockPendingVisualStyleNavigation(args: {
|
|
954
|
+
hasPendingVisualStyleEdits: boolean;
|
|
955
|
+
currentPathname: string;
|
|
956
|
+
nextPathname: string;
|
|
957
|
+
}): boolean {
|
|
958
|
+
return (
|
|
959
|
+
args.hasPendingVisualStyleEdits &&
|
|
960
|
+
args.currentPathname !== args.nextPathname
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
export function formatPendingVisualStylePrompt(args: {
|
|
965
|
+
designId?: string | null;
|
|
966
|
+
designTitle?: string | null;
|
|
967
|
+
activeFileId?: string | null;
|
|
968
|
+
activeFilename?: string | null;
|
|
969
|
+
edits: readonly PendingVisualStyleEdit[];
|
|
970
|
+
}): string {
|
|
971
|
+
const title = args.designTitle?.trim();
|
|
972
|
+
const editPayload = args.edits.map((edit) => ({
|
|
973
|
+
screenId: edit.screenId,
|
|
974
|
+
filename: edit.filename,
|
|
975
|
+
screenName: edit.screenName,
|
|
976
|
+
selector: edit.selector,
|
|
977
|
+
sourceId: edit.sourceId ?? null,
|
|
978
|
+
tagName: edit.tagName ?? null,
|
|
979
|
+
classes: edit.classes,
|
|
980
|
+
styles: edit.styles,
|
|
981
|
+
}));
|
|
982
|
+
|
|
983
|
+
return [
|
|
984
|
+
`Apply these pending visual style edits${title ? ` to "${title}"` : ""}.`,
|
|
985
|
+
args.designId ? `Design id: "${args.designId}".` : "",
|
|
986
|
+
args.activeFileId
|
|
987
|
+
? `Active screen: "${args.activeFilename ?? args.activeFileId}" (${args.activeFileId}).`
|
|
988
|
+
: "",
|
|
989
|
+
"",
|
|
990
|
+
"Use the Design source tools to make the source match the current live canvas preview. Read each target screen, resolve source ids/selectors through the code-layer projection, then apply the style changes with focused source edits. Preserve layout, behavior, and unrelated styling.",
|
|
991
|
+
"",
|
|
992
|
+
"Pending style edits:",
|
|
993
|
+
JSON.stringify(editPayload, null, 2),
|
|
994
|
+
]
|
|
995
|
+
.filter((line) => line !== "")
|
|
996
|
+
.join("\n");
|
|
997
|
+
}
|
|
998
|
+
|
|
869
999
|
interface DesignData {
|
|
870
1000
|
id: string;
|
|
871
1001
|
title: string;
|
|
@@ -893,6 +1023,7 @@ interface CanvasLayerClipboardEntry {
|
|
|
893
1023
|
html: string;
|
|
894
1024
|
rootNodeId?: string;
|
|
895
1025
|
sourceFileId: string;
|
|
1026
|
+
portableStyleSnapshot?: PortableStyleSnapshot;
|
|
896
1027
|
}
|
|
897
1028
|
|
|
898
1029
|
interface SelectedCanvasLayerSnapshot extends CanvasLayerClipboardEntry {
|
|
@@ -1451,6 +1582,80 @@ function setAbsolutePositioningForNodeInHtml(
|
|
|
1451
1582
|
}
|
|
1452
1583
|
}
|
|
1453
1584
|
|
|
1585
|
+
function getAbsolutePositioningForNodeInHtml(
|
|
1586
|
+
content: string,
|
|
1587
|
+
nodeAttrId: string,
|
|
1588
|
+
): { x: number; y: number } | null {
|
|
1589
|
+
if (typeof window === "undefined") return null;
|
|
1590
|
+
try {
|
|
1591
|
+
const doc = new DOMParser().parseFromString(content, "text/html");
|
|
1592
|
+
const element = doc.querySelector(
|
|
1593
|
+
`[data-agent-native-node-id="${CSS.escape(nodeAttrId)}"]`,
|
|
1594
|
+
) as HTMLElement | null;
|
|
1595
|
+
if (!element) return null;
|
|
1596
|
+
return {
|
|
1597
|
+
x: Number.parseFloat(element.style.left || "0") || 0,
|
|
1598
|
+
y: Number.parseFloat(element.style.top || "0") || 0,
|
|
1599
|
+
};
|
|
1600
|
+
} catch {
|
|
1601
|
+
return null;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
function elementAtPortableStylePath(
|
|
1606
|
+
root: Element,
|
|
1607
|
+
node: PortableStyleSnapshotNode,
|
|
1608
|
+
): Element | null {
|
|
1609
|
+
let current: Element | null = root;
|
|
1610
|
+
for (const index of node.path) {
|
|
1611
|
+
if (!current || !Number.isInteger(index) || index < 0) return null;
|
|
1612
|
+
current = current.children.item(index);
|
|
1613
|
+
}
|
|
1614
|
+
return current;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function applyPortableStyles(
|
|
1618
|
+
element: Element | null,
|
|
1619
|
+
styles: Record<string, string>,
|
|
1620
|
+
) {
|
|
1621
|
+
if (!element) return;
|
|
1622
|
+
const host = styleHost(element);
|
|
1623
|
+
if (!host) return;
|
|
1624
|
+
Object.entries(styles).forEach(([property, value]) => {
|
|
1625
|
+
if (!value) return;
|
|
1626
|
+
if (property.startsWith("--") || property.includes("-")) {
|
|
1627
|
+
host.style.setProperty(property, value);
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
(host.style as any)[property] = value;
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
function applyPortableStyleSnapshotToHtml(
|
|
1635
|
+
content: string,
|
|
1636
|
+
nodeAttrId: string,
|
|
1637
|
+
snapshot?: PortableStyleSnapshot,
|
|
1638
|
+
): string {
|
|
1639
|
+
if (typeof window === "undefined" || !snapshot?.nodes?.length) {
|
|
1640
|
+
return content;
|
|
1641
|
+
}
|
|
1642
|
+
try {
|
|
1643
|
+
const doc = new DOMParser().parseFromString(content, "text/html");
|
|
1644
|
+
const root = doc.querySelector(
|
|
1645
|
+
`[data-agent-native-node-id="${CSS.escape(nodeAttrId)}"]`,
|
|
1646
|
+
);
|
|
1647
|
+
if (!root) return content;
|
|
1648
|
+
root.setAttribute("data-agent-native-preserve-styles", "true");
|
|
1649
|
+
snapshot.nodes.forEach((node) => {
|
|
1650
|
+
const target = elementAtPortableStylePath(root, node);
|
|
1651
|
+
if (target) applyPortableStyles(target, node.styles);
|
|
1652
|
+
});
|
|
1653
|
+
return `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
|
1654
|
+
} catch {
|
|
1655
|
+
return content;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1454
1659
|
function isAbsoluteCodeLayerNode(node: CodeLayerNode | null | undefined) {
|
|
1455
1660
|
const position = String(node?.style.position ?? "").toLowerCase();
|
|
1456
1661
|
return position === "absolute" || position === "fixed";
|
|
@@ -1951,6 +2156,7 @@ function setRootLayerPosition(
|
|
|
1951
2156
|
function prepareClonedHtmlLayer(
|
|
1952
2157
|
doc: Document,
|
|
1953
2158
|
layerHtml: string,
|
|
2159
|
+
styleSnapshot?: PortableStyleSnapshot,
|
|
1954
2160
|
): { element: Element; rootNodeId: string } | null {
|
|
1955
2161
|
const layerDoc = new DOMParser().parseFromString(
|
|
1956
2162
|
`<template>${layerHtml}</template>`,
|
|
@@ -1961,6 +2167,13 @@ function prepareClonedHtmlLayer(
|
|
|
1961
2167
|
layerDoc.body.firstElementChild;
|
|
1962
2168
|
if (!source) return null;
|
|
1963
2169
|
const clone = doc.importNode(source, true) as Element;
|
|
2170
|
+
if (styleSnapshot) {
|
|
2171
|
+
clone.setAttribute("data-agent-native-preserve-styles", "true");
|
|
2172
|
+
styleSnapshot.nodes.forEach((node) => {
|
|
2173
|
+
const target = elementAtPortableStylePath(clone, node);
|
|
2174
|
+
if (target) applyPortableStyles(target, node.styles);
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
1964
2177
|
const rootNodeId = uniqueLayerId("copy");
|
|
1965
2178
|
clone.setAttribute("data-agent-native-node-id", rootNodeId);
|
|
1966
2179
|
Array.from(clone.querySelectorAll("[data-agent-native-node-id]")).forEach(
|
|
@@ -1983,6 +2196,7 @@ function insertClonedHtmlLayers(
|
|
|
1983
2196
|
placement?: "before" | "after" | "inside";
|
|
1984
2197
|
stripRootPosition?: boolean;
|
|
1985
2198
|
positions?: Array<{ x: number; y: number } | null | undefined>;
|
|
2199
|
+
styleSnapshots?: Array<PortableStyleSnapshot | null | undefined>;
|
|
1986
2200
|
} = {},
|
|
1987
2201
|
): { content: string; rootNodeIds: string[] } | null {
|
|
1988
2202
|
if (typeof window === "undefined" || layerHtmls.length === 0) return null;
|
|
@@ -1992,7 +2206,11 @@ function insertClonedHtmlLayers(
|
|
|
1992
2206
|
const fragment = doc.createDocumentFragment();
|
|
1993
2207
|
const rootNodeIds: string[] = [];
|
|
1994
2208
|
layerHtmls.forEach((layerHtml, index) => {
|
|
1995
|
-
const prepared = prepareClonedHtmlLayer(
|
|
2209
|
+
const prepared = prepareClonedHtmlLayer(
|
|
2210
|
+
doc,
|
|
2211
|
+
layerHtml,
|
|
2212
|
+
options.styleSnapshots?.[index] ?? undefined,
|
|
2213
|
+
);
|
|
1996
2214
|
if (!prepared) return;
|
|
1997
2215
|
const position = options.positions?.[index];
|
|
1998
2216
|
if (position) {
|
|
@@ -3078,7 +3296,11 @@ function DesignWorkspaceRail({
|
|
|
3078
3296
|
"group-hover:bg-transparent group-hover:text-muted-foreground",
|
|
3079
3297
|
)}
|
|
3080
3298
|
>
|
|
3081
|
-
|
|
3299
|
+
{motionOpen ? (
|
|
3300
|
+
<IconChevronDown className="size-[15px]" />
|
|
3301
|
+
) : (
|
|
3302
|
+
<IconChevronUp className="size-[15px]" />
|
|
3303
|
+
)}
|
|
3082
3304
|
</span>
|
|
3083
3305
|
<span className="max-w-full truncate leading-none">
|
|
3084
3306
|
{"Motion" /* i18n-ignore */}
|
|
@@ -3970,6 +4192,9 @@ export default function DesignEditor() {
|
|
|
3970
4192
|
const [selectedElement, setSelectedElement] = useState<ElementInfo | null>(
|
|
3971
4193
|
null,
|
|
3972
4194
|
);
|
|
4195
|
+
const [pendingVisualStyleEdits, setPendingVisualStyleEdits] = useState<
|
|
4196
|
+
PendingVisualStyleEdit[]
|
|
4197
|
+
>([]);
|
|
3973
4198
|
const [textEditingState, setTextEditingState] = useState<{
|
|
3974
4199
|
active: boolean;
|
|
3975
4200
|
selector?: string;
|
|
@@ -6738,6 +6963,7 @@ export default function DesignEditor() {
|
|
|
6738
6963
|
// Canvas container ref for cursor overlay coordinate mapping.
|
|
6739
6964
|
const canvasContextMenuRef = useRef<CanvasContextMenuHandle | null>(null);
|
|
6740
6965
|
const canvasContainerRef = useRef<HTMLDivElement>(null);
|
|
6966
|
+
const activeEditorDragRef = useRef(false);
|
|
6741
6967
|
|
|
6742
6968
|
// Live handle to the active DesignCanvas preview iframe. DesignCanvas owns the
|
|
6743
6969
|
// <iframe> internally (tagged data-design-preview-iframe) and does not forward
|
|
@@ -6755,6 +6981,25 @@ export default function DesignEditor() {
|
|
|
6755
6981
|
[],
|
|
6756
6982
|
);
|
|
6757
6983
|
|
|
6984
|
+
const handleEditorDragStateChange = useCallback((active: boolean) => {
|
|
6985
|
+
activeEditorDragRef.current = active;
|
|
6986
|
+
}, []);
|
|
6987
|
+
|
|
6988
|
+
const cancelActiveEditorDrag = useCallback(() => {
|
|
6989
|
+
if (!activeEditorDragRef.current) return false;
|
|
6990
|
+
activeEditorDragRef.current = false;
|
|
6991
|
+
if (typeof document === "undefined") return true;
|
|
6992
|
+
document
|
|
6993
|
+
.querySelectorAll<HTMLIFrameElement>("iframe[data-design-preview-iframe]")
|
|
6994
|
+
.forEach((iframe) => {
|
|
6995
|
+
iframe.contentWindow?.postMessage(
|
|
6996
|
+
{ type: "agent-native:cancel-active-drag" },
|
|
6997
|
+
"*",
|
|
6998
|
+
);
|
|
6999
|
+
});
|
|
7000
|
+
return true;
|
|
7001
|
+
}, []);
|
|
7002
|
+
|
|
6758
7003
|
const handleRunDesignAudit = useCallback(async () => {
|
|
6759
7004
|
if (!id || !activeFile?.id) return;
|
|
6760
7005
|
const auditFileId = activeFile.id;
|
|
@@ -7002,6 +7247,98 @@ export default function DesignEditor() {
|
|
|
7002
7247
|
},
|
|
7003
7248
|
[liveScreenSnapshotsById],
|
|
7004
7249
|
);
|
|
7250
|
+
const recordPendingVisualStyleEdit = useCallback(
|
|
7251
|
+
(
|
|
7252
|
+
screenId: string,
|
|
7253
|
+
selector: string,
|
|
7254
|
+
styles: Record<string, string>,
|
|
7255
|
+
elementInfo?: ElementInfo,
|
|
7256
|
+
) => {
|
|
7257
|
+
if (!canEditDesign) return;
|
|
7258
|
+
const entries = Object.entries(styles).filter(
|
|
7259
|
+
([, value]) => value !== undefined,
|
|
7260
|
+
);
|
|
7261
|
+
if (entries.length === 0) return;
|
|
7262
|
+
const stylePatch = Object.fromEntries(entries);
|
|
7263
|
+
const screen = files.find((file) => file.id === screenId);
|
|
7264
|
+
const fallbackName = screen?.filename ?? screenId;
|
|
7265
|
+
const sourceId =
|
|
7266
|
+
elementInfo?.sourceId ??
|
|
7267
|
+
(screenId === activeFile?.id ? selectedElement?.sourceId : null);
|
|
7268
|
+
const proofId =
|
|
7269
|
+
typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
7270
|
+
? crypto.randomUUID()
|
|
7271
|
+
: `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
7272
|
+
const [firstProperty, firstValue] = entries[0];
|
|
7273
|
+
|
|
7274
|
+
setPendingVisualStyleEdits((current) =>
|
|
7275
|
+
mergePendingVisualStyleEdit(current, {
|
|
7276
|
+
screenId,
|
|
7277
|
+
filename: fallbackName,
|
|
7278
|
+
screenName: prettyScreenName(fallbackName),
|
|
7279
|
+
selector,
|
|
7280
|
+
sourceId,
|
|
7281
|
+
tagName: elementInfo?.tagName ?? null,
|
|
7282
|
+
classes: elementInfo?.classes ?? [],
|
|
7283
|
+
styles: stylePatch,
|
|
7284
|
+
updatedAt: Date.now(),
|
|
7285
|
+
}),
|
|
7286
|
+
);
|
|
7287
|
+
setPatchProof({
|
|
7288
|
+
id: proofId,
|
|
7289
|
+
fileId: screenId,
|
|
7290
|
+
filename: fallbackName,
|
|
7291
|
+
selector,
|
|
7292
|
+
sourceId: sourceId ?? undefined,
|
|
7293
|
+
property:
|
|
7294
|
+
entries.length === 1
|
|
7295
|
+
? firstProperty
|
|
7296
|
+
: entries.map(([property]) => property).join(", "),
|
|
7297
|
+
previousValue:
|
|
7298
|
+
elementInfo?.computedStyles?.[firstProperty] ??
|
|
7299
|
+
(screenId === activeFile?.id
|
|
7300
|
+
? selectedElement?.computedStyles?.[firstProperty]
|
|
7301
|
+
: undefined),
|
|
7302
|
+
nextValue:
|
|
7303
|
+
entries.length === 1
|
|
7304
|
+
? firstValue
|
|
7305
|
+
: entries
|
|
7306
|
+
.map(([property, value]) => `${property}: ${value}`)
|
|
7307
|
+
.join("; "),
|
|
7308
|
+
previousContent: getProjectionContentForScreen(screenId),
|
|
7309
|
+
capability: "deterministic-style-edit",
|
|
7310
|
+
confidence: 0.92,
|
|
7311
|
+
status: "runtime",
|
|
7312
|
+
createdAt: Date.now(),
|
|
7313
|
+
});
|
|
7314
|
+
|
|
7315
|
+
if (screenId !== activeFile?.id) return;
|
|
7316
|
+
setSelectedElement((prev) => {
|
|
7317
|
+
const base = elementInfo ?? prev;
|
|
7318
|
+
if (!base) return prev;
|
|
7319
|
+
return {
|
|
7320
|
+
...base,
|
|
7321
|
+
sourceId: sourceId ?? base.sourceId,
|
|
7322
|
+
selector: selector || base.selector,
|
|
7323
|
+
computedStyles: {
|
|
7324
|
+
...base.computedStyles,
|
|
7325
|
+
...stylePatch,
|
|
7326
|
+
},
|
|
7327
|
+
};
|
|
7328
|
+
});
|
|
7329
|
+
if (sourceId) {
|
|
7330
|
+
setSelectedLayerIdsState([sourceId]);
|
|
7331
|
+
}
|
|
7332
|
+
},
|
|
7333
|
+
[
|
|
7334
|
+
activeFile?.id,
|
|
7335
|
+
canEditDesign,
|
|
7336
|
+
files,
|
|
7337
|
+
getProjectionContentForScreen,
|
|
7338
|
+
selectedElement?.computedStyles,
|
|
7339
|
+
selectedElement?.sourceId,
|
|
7340
|
+
],
|
|
7341
|
+
);
|
|
7005
7342
|
const activeProjectionContent =
|
|
7006
7343
|
activeFile?.id !== undefined
|
|
7007
7344
|
? getProjectionContentForScreen(activeFile.id)
|
|
@@ -7688,6 +8025,7 @@ export default function DesignEditor() {
|
|
|
7688
8025
|
fileId?: unknown;
|
|
7689
8026
|
timelineId?: unknown;
|
|
7690
8027
|
updatedAt?: unknown;
|
|
8028
|
+
contentPatched?: unknown;
|
|
7691
8029
|
};
|
|
7692
8030
|
if (typeof response.timelineId === "string") {
|
|
7693
8031
|
setMotionTimelineId(response.timelineId);
|
|
@@ -7699,7 +8037,8 @@ export default function DesignEditor() {
|
|
|
7699
8037
|
}
|
|
7700
8038
|
if (
|
|
7701
8039
|
typeof response.fileId === "string" &&
|
|
7702
|
-
response.fileId === activeFile.id
|
|
8040
|
+
response.fileId === activeFile.id &&
|
|
8041
|
+
response.contentPatched !== false
|
|
7703
8042
|
) {
|
|
7704
8043
|
applyFileContentUpdate(response.fileId, localPatchedContent, {
|
|
7705
8044
|
refreshPreview: true,
|
|
@@ -7925,7 +8264,8 @@ export default function DesignEditor() {
|
|
|
7925
8264
|
// new primitive selected, matching Figma behaviour. We activate the
|
|
7926
8265
|
// target screen (so the layers panel shows its content) and select the
|
|
7927
8266
|
// new node, but do NOT switch to single/full view.
|
|
7928
|
-
pendingOverviewScreenSelectionRef.current =
|
|
8267
|
+
pendingOverviewScreenSelectionRef.current =
|
|
8268
|
+
options?.selectFrame === false ? null : screenId;
|
|
7929
8269
|
pendingOverviewLayerSelectionRef.current = nodeId;
|
|
7930
8270
|
clearPendingOverviewLayerSelectionTimer();
|
|
7931
8271
|
flushSync(() => {
|
|
@@ -7980,13 +8320,14 @@ export default function DesignEditor() {
|
|
|
7980
8320
|
const handleOverviewScreenSelectionChange = useCallback(
|
|
7981
8321
|
(ids: string[]) => {
|
|
7982
8322
|
const pendingId = pendingOverviewScreenSelectionRef.current;
|
|
7983
|
-
const fileIds = new Set(
|
|
8323
|
+
const fileIds = new Set(
|
|
8324
|
+
files.filter((file) => file.id !== boardFileId).map((file) => file.id),
|
|
8325
|
+
);
|
|
8326
|
+
const nextIds = ids.filter((layerId) => fileIds.has(layerId));
|
|
7984
8327
|
if (pendingId && ids.length === 0) return;
|
|
7985
8328
|
if (pendingId && ids.includes(pendingId)) {
|
|
7986
|
-
setOverviewSelectedScreenIds(
|
|
7987
|
-
|
|
7988
|
-
(layerId) => fileIds.has(layerId) || layerId === pendingId,
|
|
7989
|
-
),
|
|
8329
|
+
setOverviewSelectedScreenIds((current) =>
|
|
8330
|
+
sameStringIds(current, nextIds) ? current : nextIds,
|
|
7990
8331
|
);
|
|
7991
8332
|
if (fileIds.has(pendingId)) {
|
|
7992
8333
|
pendingOverviewScreenSelectionRef.current = null;
|
|
@@ -7999,11 +8340,11 @@ export default function DesignEditor() {
|
|
|
7999
8340
|
clearPendingOverviewLayerSelectionTimer();
|
|
8000
8341
|
setCreatedOverviewLayerSelection(null);
|
|
8001
8342
|
}
|
|
8002
|
-
setOverviewSelectedScreenIds(
|
|
8003
|
-
|
|
8343
|
+
setOverviewSelectedScreenIds((current) =>
|
|
8344
|
+
sameStringIds(current, nextIds) ? current : nextIds,
|
|
8004
8345
|
);
|
|
8005
8346
|
},
|
|
8006
|
-
[clearPendingOverviewLayerSelectionTimer, files],
|
|
8347
|
+
[boardFileId, clearPendingOverviewLayerSelectionTimer, files],
|
|
8007
8348
|
);
|
|
8008
8349
|
|
|
8009
8350
|
const shouldPreserveBlockedOverviewLayerSelectionRef = useRef<
|
|
@@ -8469,14 +8810,14 @@ export default function DesignEditor() {
|
|
|
8469
8810
|
(screenId: string, info: ElementInfo, intent?: ElementSelectionIntent) => {
|
|
8470
8811
|
const pendingLayerId = pendingOverviewLayerSelectionRef.current;
|
|
8471
8812
|
const pendingScreenId = pendingOverviewScreenSelectionRef.current;
|
|
8472
|
-
const createdSelection = createdOverviewLayerSelection;
|
|
8473
|
-
if (createdSelection?.screenId === screenId) {
|
|
8474
|
-
return;
|
|
8475
|
-
}
|
|
8476
8813
|
if (
|
|
8477
|
-
(
|
|
8478
|
-
|
|
8479
|
-
|
|
8814
|
+
shouldIgnoreOverviewLayerCreationEcho({
|
|
8815
|
+
pendingLayerId,
|
|
8816
|
+
pendingScreenId,
|
|
8817
|
+
screenId,
|
|
8818
|
+
info,
|
|
8819
|
+
event: "select",
|
|
8820
|
+
})
|
|
8480
8821
|
) {
|
|
8481
8822
|
return;
|
|
8482
8823
|
}
|
|
@@ -8535,7 +8876,6 @@ export default function DesignEditor() {
|
|
|
8535
8876
|
},
|
|
8536
8877
|
[
|
|
8537
8878
|
clearPendingOverviewLayerSelectionTimer,
|
|
8538
|
-
createdOverviewLayerSelection,
|
|
8539
8879
|
focusDesignInspectorForSelection,
|
|
8540
8880
|
getCodeLayerProjectionForScreen,
|
|
8541
8881
|
selectedLayerIdsState,
|
|
@@ -8546,13 +8886,13 @@ export default function DesignEditor() {
|
|
|
8546
8886
|
(screenId: string) => {
|
|
8547
8887
|
const pendingLayerId = pendingOverviewLayerSelectionRef.current;
|
|
8548
8888
|
const pendingScreenId = pendingOverviewScreenSelectionRef.current;
|
|
8549
|
-
const createdSelection = createdOverviewLayerSelection;
|
|
8550
|
-
if (createdSelection?.screenId === screenId) {
|
|
8551
|
-
return;
|
|
8552
|
-
}
|
|
8553
8889
|
if (
|
|
8554
|
-
(
|
|
8555
|
-
|
|
8890
|
+
shouldIgnoreOverviewLayerCreationEcho({
|
|
8891
|
+
pendingLayerId,
|
|
8892
|
+
pendingScreenId,
|
|
8893
|
+
screenId,
|
|
8894
|
+
event: "clear",
|
|
8895
|
+
})
|
|
8556
8896
|
) {
|
|
8557
8897
|
return;
|
|
8558
8898
|
}
|
|
@@ -8574,7 +8914,7 @@ export default function DesignEditor() {
|
|
|
8574
8914
|
setActiveTool("move");
|
|
8575
8915
|
setMode("edit");
|
|
8576
8916
|
},
|
|
8577
|
-
[clearPendingOverviewLayerSelectionTimer
|
|
8917
|
+
[clearPendingOverviewLayerSelectionTimer],
|
|
8578
8918
|
);
|
|
8579
8919
|
|
|
8580
8920
|
const handleElementSelect = useCallback(
|
|
@@ -9252,12 +9592,15 @@ export default function DesignEditor() {
|
|
|
9252
9592
|
styles: Record<string, string>,
|
|
9253
9593
|
elementInfo?: ElementInfo,
|
|
9254
9594
|
) => {
|
|
9255
|
-
|
|
9256
|
-
|
|
9595
|
+
if (!activeFile?.id) return;
|
|
9596
|
+
recordPendingVisualStyleEdit(
|
|
9597
|
+
activeFile.id,
|
|
9598
|
+
selector,
|
|
9599
|
+
styles,
|
|
9257
9600
|
elementInfo,
|
|
9258
|
-
|
|
9601
|
+
);
|
|
9259
9602
|
},
|
|
9260
|
-
[
|
|
9603
|
+
[activeFile?.id, recordPendingVisualStyleEdit],
|
|
9261
9604
|
);
|
|
9262
9605
|
|
|
9263
9606
|
const handleVisualStructureChange = useCallback(
|
|
@@ -9270,6 +9613,9 @@ export default function DesignEditor() {
|
|
|
9270
9613
|
sourceId?: string;
|
|
9271
9614
|
anchorSourceId?: string;
|
|
9272
9615
|
requestId?: string;
|
|
9616
|
+
dropMode?: "flow-insert" | "absolute-container";
|
|
9617
|
+
sourceRect?: { x: number; y: number; width: number; height: number };
|
|
9618
|
+
anchorRect?: { x: number; y: number; width: number; height: number };
|
|
9273
9619
|
},
|
|
9274
9620
|
) => {
|
|
9275
9621
|
if (!canEditDesign) return false;
|
|
@@ -9319,12 +9665,29 @@ export default function DesignEditor() {
|
|
|
9319
9665
|
(node) => node.id === patch.result.after?.nodeId,
|
|
9320
9666
|
)?.dataAttributes["data-agent-native-node-id"]
|
|
9321
9667
|
: undefined);
|
|
9668
|
+
const absoluteContainerOffset =
|
|
9669
|
+
details?.dropMode === "absolute-container" &&
|
|
9670
|
+
details.sourceRect &&
|
|
9671
|
+
details.anchorRect
|
|
9672
|
+
? {
|
|
9673
|
+
x: details.sourceRect.x - details.anchorRect.x,
|
|
9674
|
+
y: details.sourceRect.y - details.anchorRect.y,
|
|
9675
|
+
}
|
|
9676
|
+
: null;
|
|
9322
9677
|
const nextContent =
|
|
9323
9678
|
isAbsoluteCodeLayerNode(targetNode) && movedNodeAttrId
|
|
9324
|
-
?
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9679
|
+
? details?.dropMode === "absolute-container"
|
|
9680
|
+
? absoluteContainerOffset
|
|
9681
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
9682
|
+
patch.content,
|
|
9683
|
+
movedNodeAttrId,
|
|
9684
|
+
absoluteContainerOffset,
|
|
9685
|
+
)
|
|
9686
|
+
: patch.content
|
|
9687
|
+
: removeAbsolutePositioningFromNodeInHtml(
|
|
9688
|
+
patch.content,
|
|
9689
|
+
movedNodeAttrId,
|
|
9690
|
+
)
|
|
9328
9691
|
: patch.content;
|
|
9329
9692
|
const nextProjection = buildCodeLayerProjection(nextContent);
|
|
9330
9693
|
const movedNode =
|
|
@@ -9554,76 +9917,9 @@ export default function DesignEditor() {
|
|
|
9554
9917
|
styles: Record<string, string>,
|
|
9555
9918
|
elementInfo?: ElementInfo,
|
|
9556
9919
|
) => {
|
|
9557
|
-
|
|
9558
|
-
handleVisualStyleChange(selector, styles, elementInfo);
|
|
9559
|
-
return;
|
|
9560
|
-
}
|
|
9561
|
-
if (!canEditDesign) return;
|
|
9562
|
-
const entries = Object.entries(styles).filter(
|
|
9563
|
-
([, value]) => value !== undefined,
|
|
9564
|
-
);
|
|
9565
|
-
if (entries.length === 0) return;
|
|
9566
|
-
const liveSnapshot = liveScreenSnapshotsById[screenId];
|
|
9567
|
-
const baseContent = liveSnapshot?.html ?? getScreenContent(screenId);
|
|
9568
|
-
const projection = buildCodeLayerProjection(baseContent);
|
|
9569
|
-
const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
|
|
9570
|
-
const targetNode = targetInfo
|
|
9571
|
-
? resolveCodeLayerNodeFromElementInfo(projection, targetInfo)
|
|
9572
|
-
: resolveCodeLayerNodeFromBridge(projection, selector);
|
|
9573
|
-
const fallbackContent = applyInlineStylesToHtml(baseContent, selector, {
|
|
9574
|
-
...Object.fromEntries(entries),
|
|
9575
|
-
});
|
|
9576
|
-
const stylePatch = entries.reduce<{
|
|
9577
|
-
content: string;
|
|
9578
|
-
failed: string | null;
|
|
9579
|
-
}>(
|
|
9580
|
-
(current, [property, value]) => {
|
|
9581
|
-
if (current.failed) return current;
|
|
9582
|
-
const patch = applyVisualEdit(current.content, {
|
|
9583
|
-
kind: "style",
|
|
9584
|
-
target: targetNode ? { nodeId: targetNode.id } : { selector },
|
|
9585
|
-
property,
|
|
9586
|
-
value,
|
|
9587
|
-
});
|
|
9588
|
-
if (patch.result.status !== "applied") {
|
|
9589
|
-
return {
|
|
9590
|
-
content: current.content,
|
|
9591
|
-
failed: codeLayerPatchMessage(
|
|
9592
|
-
patch.result.message,
|
|
9593
|
-
t("designEditor.patchProof.selectorMissing"),
|
|
9594
|
-
),
|
|
9595
|
-
};
|
|
9596
|
-
}
|
|
9597
|
-
return { content: patch.content, failed: null };
|
|
9598
|
-
},
|
|
9599
|
-
{ content: baseContent, failed: null },
|
|
9600
|
-
);
|
|
9601
|
-
const nextContent = stylePatch.failed
|
|
9602
|
-
? fallbackContent
|
|
9603
|
-
: stylePatch.content;
|
|
9604
|
-
if (!nextContent) {
|
|
9605
|
-
toast.error(
|
|
9606
|
-
stylePatch.failed ?? t("designEditor.patchProof.selectorMissing"),
|
|
9607
|
-
{ duration: 4000 },
|
|
9608
|
-
);
|
|
9609
|
-
return;
|
|
9610
|
-
}
|
|
9611
|
-
if (liveSnapshot) {
|
|
9612
|
-
updateLiveScreenSnapshotContent(screenId, nextContent);
|
|
9613
|
-
return;
|
|
9614
|
-
}
|
|
9615
|
-
applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
|
|
9920
|
+
recordPendingVisualStyleEdit(screenId, selector, styles, elementInfo);
|
|
9616
9921
|
},
|
|
9617
|
-
[
|
|
9618
|
-
activeFile?.id,
|
|
9619
|
-
applyFileContentUpdate,
|
|
9620
|
-
canEditDesign,
|
|
9621
|
-
getScreenContent,
|
|
9622
|
-
handleVisualStyleChange,
|
|
9623
|
-
liveScreenSnapshotsById,
|
|
9624
|
-
t,
|
|
9625
|
-
updateLiveScreenSnapshotContent,
|
|
9626
|
-
],
|
|
9922
|
+
[recordPendingVisualStyleEdit],
|
|
9627
9923
|
);
|
|
9628
9924
|
|
|
9629
9925
|
const handleScreenVisualStructureChange = useCallback(
|
|
@@ -9637,6 +9933,9 @@ export default function DesignEditor() {
|
|
|
9637
9933
|
sourceId?: string;
|
|
9638
9934
|
anchorSourceId?: string;
|
|
9639
9935
|
requestId?: string;
|
|
9936
|
+
dropMode?: "flow-insert" | "absolute-container";
|
|
9937
|
+
sourceRect?: { x: number; y: number; width: number; height: number };
|
|
9938
|
+
anchorRect?: { x: number; y: number; width: number; height: number };
|
|
9640
9939
|
},
|
|
9641
9940
|
) => {
|
|
9642
9941
|
if (screenId === activeFile?.id) {
|
|
@@ -9687,15 +9986,51 @@ export default function DesignEditor() {
|
|
|
9687
9986
|
);
|
|
9688
9987
|
return false;
|
|
9689
9988
|
}
|
|
9690
|
-
|
|
9691
|
-
|
|
9989
|
+
const movedNodeAttrId =
|
|
9990
|
+
targetNode?.dataAttributes["data-agent-native-node-id"] ??
|
|
9991
|
+
details?.sourceId ??
|
|
9992
|
+
elementInfo?.sourceId ??
|
|
9692
9993
|
(patch.result.after?.nodeId
|
|
9693
9994
|
? patch.projection.nodes.find(
|
|
9694
9995
|
(node) => node.id === patch.result.after?.nodeId,
|
|
9996
|
+
)?.dataAttributes["data-agent-native-node-id"]
|
|
9997
|
+
: undefined);
|
|
9998
|
+
const absoluteContainerOffset =
|
|
9999
|
+
details?.dropMode === "absolute-container" &&
|
|
10000
|
+
details.sourceRect &&
|
|
10001
|
+
details.anchorRect
|
|
10002
|
+
? {
|
|
10003
|
+
x: details.sourceRect.x - details.anchorRect.x,
|
|
10004
|
+
y: details.sourceRect.y - details.anchorRect.y,
|
|
10005
|
+
}
|
|
10006
|
+
: null;
|
|
10007
|
+
const nextContent =
|
|
10008
|
+
isAbsoluteCodeLayerNode(targetNode) && movedNodeAttrId
|
|
10009
|
+
? details?.dropMode === "absolute-container"
|
|
10010
|
+
? absoluteContainerOffset
|
|
10011
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
10012
|
+
patch.content,
|
|
10013
|
+
movedNodeAttrId,
|
|
10014
|
+
absoluteContainerOffset,
|
|
10015
|
+
)
|
|
10016
|
+
: patch.content
|
|
10017
|
+
: removeAbsolutePositioningFromNodeInHtml(
|
|
10018
|
+
patch.content,
|
|
10019
|
+
movedNodeAttrId,
|
|
10020
|
+
)
|
|
10021
|
+
: patch.content;
|
|
10022
|
+
const nextProjection = buildCodeLayerProjection(nextContent);
|
|
10023
|
+
applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
|
|
10024
|
+
const movedNode =
|
|
10025
|
+
(movedNodeAttrId
|
|
10026
|
+
? nextProjection.nodes.find(
|
|
10027
|
+
(node) =>
|
|
10028
|
+
node.dataAttributes["data-agent-native-node-id"] ===
|
|
10029
|
+
movedNodeAttrId,
|
|
9695
10030
|
)
|
|
9696
10031
|
: null) ??
|
|
9697
10032
|
resolveCodeLayerNodeFromBridge(
|
|
9698
|
-
|
|
10033
|
+
nextProjection,
|
|
9699
10034
|
selector,
|
|
9700
10035
|
details?.sourceId ??
|
|
9701
10036
|
elementInfo?.sourceId ??
|
|
@@ -9925,11 +10260,18 @@ export default function DesignEditor() {
|
|
|
9925
10260
|
candidate.dataAttributes["data-agent-native-node-id"] === layerId,
|
|
9926
10261
|
);
|
|
9927
10262
|
if (!node?.source) continue;
|
|
10263
|
+
const portableStyleSnapshot =
|
|
10264
|
+
selectedElementLayerId &&
|
|
10265
|
+
node.id === selectedElementLayerId &&
|
|
10266
|
+
selectedElement?.portableStyleSnapshot
|
|
10267
|
+
? selectedElement.portableStyleSnapshot
|
|
10268
|
+
: undefined;
|
|
9928
10269
|
snapshots.push({
|
|
9929
10270
|
html: content.slice(node.source.start, node.source.end),
|
|
9930
10271
|
rootNodeId:
|
|
9931
10272
|
node.dataAttributes["data-agent-native-node-id"] ?? node.id,
|
|
9932
10273
|
sourceFileId: file.id,
|
|
10274
|
+
portableStyleSnapshot,
|
|
9933
10275
|
node,
|
|
9934
10276
|
sourceIndex: node.source.start,
|
|
9935
10277
|
tree,
|
|
@@ -9956,6 +10298,7 @@ export default function DesignEditor() {
|
|
|
9956
10298
|
selectedElement.sourceId ??
|
|
9957
10299
|
selectedElement.id,
|
|
9958
10300
|
sourceFileId: activeFile.id,
|
|
10301
|
+
portableStyleSnapshot: selectedElement.portableStyleSnapshot,
|
|
9959
10302
|
node,
|
|
9960
10303
|
sourceIndex: node.source?.start ?? Number.MAX_SAFE_INTEGER,
|
|
9961
10304
|
tree,
|
|
@@ -10020,19 +10363,31 @@ export default function DesignEditor() {
|
|
|
10020
10363
|
)
|
|
10021
10364
|
.filter((node): node is CodeLayerNode => Boolean(node));
|
|
10022
10365
|
if (insertedNodes.length === 0) return;
|
|
10366
|
+
const lastNode = insertedNodes[insertedNodes.length - 1];
|
|
10367
|
+
if (lastNode) {
|
|
10368
|
+
pendingOverviewScreenSelectionRef.current =
|
|
10369
|
+
screenId === boardFileId ? null : screenId;
|
|
10370
|
+
pendingOverviewLayerSelectionRef.current = lastNode.id;
|
|
10371
|
+
clearPendingOverviewLayerSelectionTimer();
|
|
10372
|
+
setCreatedOverviewLayerSelection({
|
|
10373
|
+
screenId,
|
|
10374
|
+
layerId: lastNode.id,
|
|
10375
|
+
});
|
|
10376
|
+
}
|
|
10023
10377
|
setActiveFileId(screenId);
|
|
10024
10378
|
setSelectedLayerIdsState(insertedNodes.map((node) => node.id));
|
|
10025
|
-
const lastNode = insertedNodes[insertedNodes.length - 1];
|
|
10026
10379
|
setSelectedElement(
|
|
10027
10380
|
lastNode ? elementInfoFromCodeLayerNode(lastNode) : null,
|
|
10028
10381
|
);
|
|
10029
10382
|
setActiveTool("move");
|
|
10030
10383
|
setMode("edit");
|
|
10031
10384
|
if (viewModeRef.current === "overview") {
|
|
10032
|
-
setOverviewSelectedScreenIds(
|
|
10385
|
+
setOverviewSelectedScreenIds(
|
|
10386
|
+
screenId === boardFileId ? [] : [screenId],
|
|
10387
|
+
);
|
|
10033
10388
|
}
|
|
10034
10389
|
},
|
|
10035
|
-
[],
|
|
10390
|
+
[boardFileId, clearPendingOverviewLayerSelectionTimer],
|
|
10036
10391
|
);
|
|
10037
10392
|
|
|
10038
10393
|
const handleCopySelection = useCallback(async () => {
|
|
@@ -10040,6 +10395,7 @@ export default function DesignEditor() {
|
|
|
10040
10395
|
html: snapshot.html,
|
|
10041
10396
|
rootNodeId: snapshot.rootNodeId,
|
|
10042
10397
|
sourceFileId: snapshot.sourceFileId,
|
|
10398
|
+
portableStyleSnapshot: snapshot.portableStyleSnapshot,
|
|
10043
10399
|
}));
|
|
10044
10400
|
if (entries.length === 0) return;
|
|
10045
10401
|
const clipboardText = entries.map((entry) => entry.html).join("\n");
|
|
@@ -10057,9 +10413,32 @@ export default function DesignEditor() {
|
|
|
10057
10413
|
const handlePasteSelection = useCallback(
|
|
10058
10414
|
(position?: { x: number; y: number }) => {
|
|
10059
10415
|
const entries = getCanvasClipboardEntries();
|
|
10060
|
-
|
|
10061
|
-
|
|
10416
|
+
const targetFileId =
|
|
10417
|
+
viewModeRef.current === "overview" && position && boardFileId
|
|
10418
|
+
? boardFileId
|
|
10419
|
+
: activeFile?.id;
|
|
10420
|
+
if (!targetFileId || !canEditDesign || entries.length === 0) return;
|
|
10421
|
+
const targetIsBoard = targetFileId === boardFileId;
|
|
10422
|
+
const baseContent =
|
|
10423
|
+
targetFileId === activeFile?.id
|
|
10424
|
+
? getFreshActiveContent()
|
|
10425
|
+
: (getScreenContent(targetFileId) ?? "");
|
|
10426
|
+
if (!baseContent && targetFileId !== boardFileId) return;
|
|
10062
10427
|
const layerHtmls = entries.map((entry) => entry.html);
|
|
10428
|
+
const styleSnapshots = targetIsBoard
|
|
10429
|
+
? entries.map((entry) => entry.portableStyleSnapshot)
|
|
10430
|
+
: undefined;
|
|
10431
|
+
const applyPasteContentUpdate = (nextContent: string) => {
|
|
10432
|
+
if (targetFileId === activeFile?.id) {
|
|
10433
|
+
applyLocalContentUpdate(nextContent, {
|
|
10434
|
+
forcePreviewFullDocument: true,
|
|
10435
|
+
});
|
|
10436
|
+
return;
|
|
10437
|
+
}
|
|
10438
|
+
applyFileContentUpdate(targetFileId, nextContent, {
|
|
10439
|
+
forcePreviewFullDocument: true,
|
|
10440
|
+
});
|
|
10441
|
+
};
|
|
10063
10442
|
|
|
10064
10443
|
// B7 fix: when an element is selected and no explicit canvas position was
|
|
10065
10444
|
// given, insert the clone as an in-flow sibling right AFTER the selected
|
|
@@ -10067,20 +10446,19 @@ export default function DesignEditor() {
|
|
|
10067
10446
|
// in normal document flow instead of being an absolutely-positioned body
|
|
10068
10447
|
// child. Fall back to the old position-based clone when nothing is
|
|
10069
10448
|
// selected or a "Paste here" position is provided.
|
|
10070
|
-
if (!position && selectedElement?.selector) {
|
|
10449
|
+
if (!position && !targetIsBoard && selectedElement?.selector) {
|
|
10071
10450
|
const selector = selectedCanvasSelector ?? selectedElement.selector;
|
|
10072
10451
|
const result = insertClonedHtmlLayers(baseContent, layerHtmls, {
|
|
10073
10452
|
targetSelectors: [selector],
|
|
10074
10453
|
placement: "after",
|
|
10075
10454
|
stripRootPosition: true,
|
|
10455
|
+
styleSnapshots,
|
|
10076
10456
|
});
|
|
10077
10457
|
if (result) {
|
|
10078
10458
|
pasteCascadeRef.current += 1;
|
|
10079
|
-
|
|
10080
|
-
forcePreviewFullDocument: true,
|
|
10081
|
-
});
|
|
10459
|
+
applyPasteContentUpdate(result.content);
|
|
10082
10460
|
selectInsertedLayers(
|
|
10083
|
-
|
|
10461
|
+
targetFileId,
|
|
10084
10462
|
result.content,
|
|
10085
10463
|
result.rootNodeIds,
|
|
10086
10464
|
);
|
|
@@ -10127,20 +10505,22 @@ export default function DesignEditor() {
|
|
|
10127
10505
|
});
|
|
10128
10506
|
const result = insertClonedHtmlLayers(baseContent, layerHtmls, {
|
|
10129
10507
|
positions,
|
|
10508
|
+
styleSnapshots,
|
|
10130
10509
|
});
|
|
10131
10510
|
if (!result) return;
|
|
10132
10511
|
if (!position) pasteCascadeRef.current += 1;
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
});
|
|
10136
|
-
selectInsertedLayers(activeFile.id, result.content, result.rootNodeIds);
|
|
10512
|
+
applyPasteContentUpdate(result.content);
|
|
10513
|
+
selectInsertedLayers(targetFileId, result.content, result.rootNodeIds);
|
|
10137
10514
|
},
|
|
10138
10515
|
[
|
|
10139
10516
|
activeFile,
|
|
10517
|
+
applyFileContentUpdate,
|
|
10140
10518
|
applyLocalContentUpdate,
|
|
10519
|
+
boardFileId,
|
|
10141
10520
|
canEditDesign,
|
|
10142
10521
|
getCanvasClipboardEntries,
|
|
10143
10522
|
getFreshActiveContent,
|
|
10523
|
+
getScreenContent,
|
|
10144
10524
|
selectInsertedLayers,
|
|
10145
10525
|
selectedCanvasSelector,
|
|
10146
10526
|
selectedElement,
|
|
@@ -10153,6 +10533,7 @@ export default function DesignEditor() {
|
|
|
10153
10533
|
const baseContent = getFreshActiveContent();
|
|
10154
10534
|
if (selectedElement?.boundingRect) {
|
|
10155
10535
|
const { x, y } = selectedElement.boundingRect;
|
|
10536
|
+
const targetIsBoard = activeFile.id === boardFileId;
|
|
10156
10537
|
const result = insertClonedHtmlLayers(
|
|
10157
10538
|
baseContent,
|
|
10158
10539
|
entries.map((entry) => entry.html),
|
|
@@ -10161,6 +10542,9 @@ export default function DesignEditor() {
|
|
|
10161
10542
|
x: x + index * 16,
|
|
10162
10543
|
y: y + index * 16,
|
|
10163
10544
|
})),
|
|
10545
|
+
styleSnapshots: targetIsBoard
|
|
10546
|
+
? entries.map((entry) => entry.portableStyleSnapshot)
|
|
10547
|
+
: undefined,
|
|
10164
10548
|
},
|
|
10165
10549
|
);
|
|
10166
10550
|
if (!result) return;
|
|
@@ -10174,6 +10558,7 @@ export default function DesignEditor() {
|
|
|
10174
10558
|
}, [
|
|
10175
10559
|
activeFile,
|
|
10176
10560
|
applyLocalContentUpdate,
|
|
10561
|
+
boardFileId,
|
|
10177
10562
|
getCanvasClipboardEntries,
|
|
10178
10563
|
getFreshActiveContent,
|
|
10179
10564
|
handlePasteSelection,
|
|
@@ -10502,9 +10887,9 @@ export default function DesignEditor() {
|
|
|
10502
10887
|
* Handle a primitive being drag-dropped onto another primitive in the
|
|
10503
10888
|
* MultiScreenCanvas overview (CONTRACT: onPrimitiveReparent prop).
|
|
10504
10889
|
*
|
|
10505
|
-
* Same-screen: applies a moveNode intent then
|
|
10506
|
-
* absolute
|
|
10507
|
-
*
|
|
10890
|
+
* Same-screen: applies a moveNode intent then rebases the moved node's
|
|
10891
|
+
* absolute coordinates relative to the target rectangle. Cross-screen uses
|
|
10892
|
+
* moveNodeBetweenDocuments and persists both files.
|
|
10508
10893
|
*/
|
|
10509
10894
|
const handleOverviewPrimitiveReparent = useCallback(
|
|
10510
10895
|
({
|
|
@@ -10544,27 +10929,38 @@ export default function DesignEditor() {
|
|
|
10544
10929
|
return;
|
|
10545
10930
|
}
|
|
10546
10931
|
|
|
10547
|
-
// 2. Strip absolute positioning from the moved node so it flows naturally.
|
|
10548
|
-
// Use removeAbsolutePositioningFromNodeInHtml (DOM-based) because the
|
|
10549
|
-
// applyVisualEdit substrate rejects empty-string values in isSafeStyleValue,
|
|
10550
|
-
// making applyVisualEdit({kind:"style",value:""}) a silent no-op.
|
|
10551
10932
|
const movedNodeAttrId =
|
|
10552
10933
|
movePatch.projection.nodes.find(
|
|
10553
10934
|
(n) =>
|
|
10554
10935
|
n.dataAttributes["data-agent-native-node-id"] === sourceNodeId ||
|
|
10555
10936
|
n.id === sourceNodeId,
|
|
10556
10937
|
)?.dataAttributes["data-agent-native-node-id"] ?? sourceNodeId;
|
|
10557
|
-
const
|
|
10558
|
-
|
|
10559
|
-
|
|
10938
|
+
const sourcePosition = getAbsolutePositioningForNodeInHtml(
|
|
10939
|
+
baseContent,
|
|
10940
|
+
sourceNodeId,
|
|
10941
|
+
);
|
|
10942
|
+
const targetPosition = getAbsolutePositioningForNodeInHtml(
|
|
10943
|
+
baseContent,
|
|
10944
|
+
targetNodeId,
|
|
10560
10945
|
);
|
|
10946
|
+
const nextContent =
|
|
10947
|
+
sourcePosition && targetPosition
|
|
10948
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
10949
|
+
movePatch.content,
|
|
10950
|
+
movedNodeAttrId,
|
|
10951
|
+
{
|
|
10952
|
+
x: sourcePosition.x - targetPosition.x,
|
|
10953
|
+
y: sourcePosition.y - targetPosition.y,
|
|
10954
|
+
},
|
|
10955
|
+
)
|
|
10956
|
+
: movePatch.content;
|
|
10561
10957
|
|
|
10562
|
-
applyFileContentUpdate(sourceScreenId,
|
|
10958
|
+
applyFileContentUpdate(sourceScreenId, nextContent, {
|
|
10563
10959
|
skipPreview: true,
|
|
10564
10960
|
});
|
|
10565
10961
|
|
|
10566
10962
|
// Re-select the moved node.
|
|
10567
|
-
const nextProjection = buildCodeLayerProjection(
|
|
10963
|
+
const nextProjection = buildCodeLayerProjection(nextContent);
|
|
10568
10964
|
const movedNodeAfter = nextProjection.nodes.find(
|
|
10569
10965
|
(n) =>
|
|
10570
10966
|
n.dataAttributes["data-agent-native-node-id"] === sourceNodeId ||
|
|
@@ -10616,18 +11012,26 @@ export default function DesignEditor() {
|
|
|
10616
11012
|
return;
|
|
10617
11013
|
}
|
|
10618
11014
|
|
|
10619
|
-
// Strip absolute positioning from the moved node in the destination.
|
|
10620
|
-
// Use removeAbsolutePositioningFromNodeInHtml (DOM-based) because
|
|
10621
|
-
// applyVisualEdit({kind:"style",value:""}) is a silent no-op (the
|
|
10622
|
-
// substrate rejects empty-string values in isSafeStyleValue). Use
|
|
10623
|
-
// result.movedNodeId (the final id in destHtml, which may differ from
|
|
10624
|
-
// nodeAttrId when a collision triggered an id re-stamp) so the strip and
|
|
10625
|
-
// re-selection always find the correct element.
|
|
10626
11015
|
const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
|
|
10627
|
-
const
|
|
10628
|
-
|
|
10629
|
-
|
|
11016
|
+
const sourcePosition = getAbsolutePositioningForNodeInHtml(
|
|
11017
|
+
sourceContent,
|
|
11018
|
+
nodeAttrId,
|
|
10630
11019
|
);
|
|
11020
|
+
const targetPosition = getAbsolutePositioningForNodeInHtml(
|
|
11021
|
+
destContent,
|
|
11022
|
+
anchorAttrId,
|
|
11023
|
+
);
|
|
11024
|
+
const nextDestContent =
|
|
11025
|
+
sourcePosition && targetPosition
|
|
11026
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
11027
|
+
result.destHtml,
|
|
11028
|
+
destNodeAttrId,
|
|
11029
|
+
{
|
|
11030
|
+
x: sourcePosition.x - targetPosition.x,
|
|
11031
|
+
y: sourcePosition.y - targetPosition.y,
|
|
11032
|
+
},
|
|
11033
|
+
)
|
|
11034
|
+
: result.destHtml;
|
|
10631
11035
|
|
|
10632
11036
|
recordContentHistoryEntry({
|
|
10633
11037
|
changes: [
|
|
@@ -10639,7 +11043,7 @@ export default function DesignEditor() {
|
|
|
10639
11043
|
{
|
|
10640
11044
|
fileId: targetScreenId,
|
|
10641
11045
|
before: destContent,
|
|
10642
|
-
after:
|
|
11046
|
+
after: nextDestContent,
|
|
10643
11047
|
},
|
|
10644
11048
|
],
|
|
10645
11049
|
});
|
|
@@ -10648,13 +11052,13 @@ export default function DesignEditor() {
|
|
|
10648
11052
|
recordHistory: false,
|
|
10649
11053
|
refreshPreview: true,
|
|
10650
11054
|
});
|
|
10651
|
-
applyFileContentUpdate(targetScreenId,
|
|
11055
|
+
applyFileContentUpdate(targetScreenId, nextDestContent, {
|
|
10652
11056
|
recordHistory: false,
|
|
10653
11057
|
refreshPreview: true,
|
|
10654
11058
|
});
|
|
10655
11059
|
|
|
10656
11060
|
// Re-select the moved node in the destination.
|
|
10657
|
-
const finalProjection = buildCodeLayerProjection(
|
|
11061
|
+
const finalProjection = buildCodeLayerProjection(nextDestContent);
|
|
10658
11062
|
const movedNodeFinal = finalProjection.nodes.find(
|
|
10659
11063
|
(n) => n.dataAttributes["data-agent-native-node-id"] === destNodeAttrId,
|
|
10660
11064
|
);
|
|
@@ -10695,8 +11099,11 @@ export default function DesignEditor() {
|
|
|
10695
11099
|
targetScreenId,
|
|
10696
11100
|
targetAnchorNodeId,
|
|
10697
11101
|
targetAnchorPlacement,
|
|
11102
|
+
targetDropMode,
|
|
11103
|
+
targetAnchorRect,
|
|
10698
11104
|
targetLocalPoint,
|
|
10699
11105
|
sourcePointerOffset,
|
|
11106
|
+
styleSnapshot,
|
|
10700
11107
|
}: {
|
|
10701
11108
|
sourceSelector: string;
|
|
10702
11109
|
sourceNodeId?: string;
|
|
@@ -10704,9 +11111,17 @@ export default function DesignEditor() {
|
|
|
10704
11111
|
targetScreenId: string;
|
|
10705
11112
|
targetAnchorNodeId?: string;
|
|
10706
11113
|
targetAnchorPlacement?: "before" | "after" | "inside";
|
|
11114
|
+
targetDropMode?: "flow-insert" | "absolute-container";
|
|
11115
|
+
targetAnchorRect?: {
|
|
11116
|
+
left: number;
|
|
11117
|
+
top: number;
|
|
11118
|
+
width: number;
|
|
11119
|
+
height: number;
|
|
11120
|
+
};
|
|
10707
11121
|
targetCanvasPoint?: { x: number; y: number };
|
|
10708
11122
|
targetLocalPoint?: { x: number; y: number };
|
|
10709
11123
|
sourcePointerOffset?: { x: number; y: number };
|
|
11124
|
+
styleSnapshot?: PortableStyleSnapshot;
|
|
10710
11125
|
}) => {
|
|
10711
11126
|
if (!canEditDesign) return;
|
|
10712
11127
|
if (sourceScreenId === targetScreenId) return;
|
|
@@ -10772,19 +11187,39 @@ export default function DesignEditor() {
|
|
|
10772
11187
|
// there is no anchor, preserve absolute mode and rebase left/top to the
|
|
10773
11188
|
// release point so screen↔board moves behave like Figma absolute layers.
|
|
10774
11189
|
const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
|
|
11190
|
+
const stylePreservedDest =
|
|
11191
|
+
targetScreenId === boardFileId
|
|
11192
|
+
? applyPortableStyleSnapshotToHtml(
|
|
11193
|
+
result.destHtml,
|
|
11194
|
+
destNodeAttrId,
|
|
11195
|
+
styleSnapshot,
|
|
11196
|
+
)
|
|
11197
|
+
: result.destHtml;
|
|
10775
11198
|
const nextDestContent = targetAnchorAttrId
|
|
10776
|
-
?
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
11199
|
+
? targetDropMode === "absolute-container"
|
|
11200
|
+
? targetLocalPoint && targetAnchorRect
|
|
11201
|
+
? setAbsolutePositioningForNodeInHtml(
|
|
11202
|
+
stylePreservedDest,
|
|
11203
|
+
destNodeAttrId,
|
|
11204
|
+
{
|
|
11205
|
+
x: targetLocalPoint.x - targetAnchorRect.left,
|
|
11206
|
+
y: targetLocalPoint.y - targetAnchorRect.top,
|
|
11207
|
+
},
|
|
11208
|
+
sourcePointerOffset,
|
|
11209
|
+
)
|
|
11210
|
+
: stylePreservedDest
|
|
11211
|
+
: removeAbsolutePositioningFromNodeInHtml(
|
|
11212
|
+
stylePreservedDest,
|
|
11213
|
+
destNodeAttrId,
|
|
11214
|
+
)
|
|
10780
11215
|
: targetLocalPoint
|
|
10781
11216
|
? setAbsolutePositioningForNodeInHtml(
|
|
10782
|
-
|
|
11217
|
+
stylePreservedDest,
|
|
10783
11218
|
destNodeAttrId,
|
|
10784
11219
|
targetLocalPoint,
|
|
10785
11220
|
sourcePointerOffset,
|
|
10786
11221
|
)
|
|
10787
|
-
:
|
|
11222
|
+
: stylePreservedDest;
|
|
10788
11223
|
|
|
10789
11224
|
recordContentHistoryEntry({
|
|
10790
11225
|
changes: [
|
|
@@ -10812,19 +11247,34 @@ export default function DesignEditor() {
|
|
|
10812
11247
|
|
|
10813
11248
|
// Switch active screen to the target and select the moved node; viewMode
|
|
10814
11249
|
// stays "overview" (no setViewMode call).
|
|
11250
|
+
pendingOverviewScreenSelectionRef.current =
|
|
11251
|
+
targetScreenId === boardFileId ? null : targetScreenId;
|
|
11252
|
+
pendingOverviewLayerSelectionRef.current = destNodeAttrId;
|
|
11253
|
+
clearPendingOverviewLayerSelectionTimer();
|
|
10815
11254
|
setActiveFileId(targetScreenId);
|
|
10816
11255
|
const finalProjection = buildCodeLayerProjection(nextDestContent);
|
|
10817
11256
|
const movedNodeFinal = finalProjection.nodes.find(
|
|
10818
11257
|
(n) => n.dataAttributes["data-agent-native-node-id"] === destNodeAttrId,
|
|
10819
11258
|
);
|
|
10820
11259
|
if (movedNodeFinal) {
|
|
11260
|
+
setCreatedOverviewLayerSelection({
|
|
11261
|
+
screenId: targetScreenId,
|
|
11262
|
+
layerId: movedNodeFinal.id,
|
|
11263
|
+
});
|
|
10821
11264
|
setSelectedLayerIdsState([movedNodeFinal.id]);
|
|
10822
11265
|
setSelectedElement(elementInfoFromCodeLayerNode(movedNodeFinal));
|
|
11266
|
+
if (viewModeRef.current === "overview") {
|
|
11267
|
+
setOverviewSelectedScreenIds(
|
|
11268
|
+
targetScreenId === boardFileId ? [] : [targetScreenId],
|
|
11269
|
+
);
|
|
11270
|
+
}
|
|
10823
11271
|
}
|
|
10824
11272
|
},
|
|
10825
11273
|
[
|
|
10826
11274
|
applyFileContentUpdate,
|
|
11275
|
+
boardFileId,
|
|
10827
11276
|
canEditDesign,
|
|
11277
|
+
clearPendingOverviewLayerSelectionTimer,
|
|
10828
11278
|
getScreenContent,
|
|
10829
11279
|
recordContentHistoryEntry,
|
|
10830
11280
|
t,
|
|
@@ -11707,6 +12157,7 @@ export default function DesignEditor() {
|
|
|
11707
12157
|
]);
|
|
11708
12158
|
|
|
11709
12159
|
const handleEscapeHotkey = useCallback(() => {
|
|
12160
|
+
if (cancelActiveEditorDrag()) return;
|
|
11710
12161
|
if (
|
|
11711
12162
|
shouldEscapeToOverview({
|
|
11712
12163
|
activeTool,
|
|
@@ -11730,6 +12181,7 @@ export default function DesignEditor() {
|
|
|
11730
12181
|
setMode("edit");
|
|
11731
12182
|
}, [
|
|
11732
12183
|
activeTool,
|
|
12184
|
+
cancelActiveEditorDrag,
|
|
11733
12185
|
drawMode,
|
|
11734
12186
|
enterOverviewFromZoom,
|
|
11735
12187
|
mode,
|
|
@@ -12106,6 +12558,84 @@ export default function DesignEditor() {
|
|
|
12106
12558
|
}
|
|
12107
12559
|
}, [ensureCodingHandoff, getCodingHandoffClipboardText, t]);
|
|
12108
12560
|
|
|
12561
|
+
const hasPendingVisualStyleEdits = pendingVisualStyleEdits.length > 0;
|
|
12562
|
+
useBeforeUnload(
|
|
12563
|
+
useCallback(
|
|
12564
|
+
(event: BeforeUnloadEvent) => {
|
|
12565
|
+
if (!hasPendingVisualStyleEdits) return;
|
|
12566
|
+
event.preventDefault();
|
|
12567
|
+
event.returnValue = "";
|
|
12568
|
+
},
|
|
12569
|
+
[hasPendingVisualStyleEdits],
|
|
12570
|
+
),
|
|
12571
|
+
{ capture: true },
|
|
12572
|
+
);
|
|
12573
|
+
const pendingVisualStyleNavigationBlocker = useBlocker(
|
|
12574
|
+
useCallback(
|
|
12575
|
+
({ currentLocation, nextLocation }) =>
|
|
12576
|
+
shouldBlockPendingVisualStyleNavigation({
|
|
12577
|
+
hasPendingVisualStyleEdits,
|
|
12578
|
+
currentPathname: currentLocation.pathname,
|
|
12579
|
+
nextPathname: nextLocation.pathname,
|
|
12580
|
+
}),
|
|
12581
|
+
[hasPendingVisualStyleEdits],
|
|
12582
|
+
),
|
|
12583
|
+
);
|
|
12584
|
+
const pendingVisualStyleWarningOpen =
|
|
12585
|
+
pendingVisualStyleNavigationBlocker.state === "blocked";
|
|
12586
|
+
const handleStayOnPendingVisualStyleNavigation = useCallback(() => {
|
|
12587
|
+
if (pendingVisualStyleNavigationBlocker.state !== "blocked") return;
|
|
12588
|
+
pendingVisualStyleNavigationBlocker.reset();
|
|
12589
|
+
}, [pendingVisualStyleNavigationBlocker]);
|
|
12590
|
+
const handleDiscardPendingVisualStylesAndNavigate = useCallback(() => {
|
|
12591
|
+
if (pendingVisualStyleNavigationBlocker.state !== "blocked") return;
|
|
12592
|
+
setPendingVisualStyleEdits([]);
|
|
12593
|
+
pendingVisualStyleNavigationBlocker.proceed();
|
|
12594
|
+
}, [pendingVisualStyleNavigationBlocker]);
|
|
12595
|
+
|
|
12596
|
+
const pendingVisualStylePropertyCount = useMemo(
|
|
12597
|
+
() => getPendingVisualStylePropertyCount(pendingVisualStyleEdits),
|
|
12598
|
+
[pendingVisualStyleEdits],
|
|
12599
|
+
);
|
|
12600
|
+
const pendingVisualStylePrompt = useMemo(
|
|
12601
|
+
() =>
|
|
12602
|
+
formatPendingVisualStylePrompt({
|
|
12603
|
+
designId: id,
|
|
12604
|
+
designTitle: design?.title,
|
|
12605
|
+
activeFileId: activeFile?.id,
|
|
12606
|
+
activeFilename: activeFile?.filename,
|
|
12607
|
+
edits: pendingVisualStyleEdits,
|
|
12608
|
+
}),
|
|
12609
|
+
[
|
|
12610
|
+
activeFile?.filename,
|
|
12611
|
+
activeFile?.id,
|
|
12612
|
+
design?.title,
|
|
12613
|
+
id,
|
|
12614
|
+
pendingVisualStyleEdits,
|
|
12615
|
+
],
|
|
12616
|
+
);
|
|
12617
|
+
const handleApplyPendingVisualStylesWithAgent = useCallback(() => {
|
|
12618
|
+
if (pendingVisualStyleEdits.length === 0) return;
|
|
12619
|
+
sendToDesignAgentChat({
|
|
12620
|
+
message: t("designEditor.pendingVisualStyles.agentMessage"),
|
|
12621
|
+
context: pendingVisualStylePrompt,
|
|
12622
|
+
submit: true,
|
|
12623
|
+
openSidebar: true,
|
|
12624
|
+
});
|
|
12625
|
+
setPendingVisualStyleEdits([]);
|
|
12626
|
+
setActiveLeftPanel("agent");
|
|
12627
|
+
toast.success(t("designEditor.pendingVisualStyles.sentToast"));
|
|
12628
|
+
}, [pendingVisualStyleEdits.length, pendingVisualStylePrompt, t]);
|
|
12629
|
+
const handleCopyPendingVisualStylePrompt = useCallback(async () => {
|
|
12630
|
+
if (pendingVisualStyleEdits.length === 0) return;
|
|
12631
|
+
try {
|
|
12632
|
+
await navigator.clipboard.writeText(pendingVisualStylePrompt);
|
|
12633
|
+
toast.success(t("designEditor.pendingVisualStyles.copiedToast"));
|
|
12634
|
+
} catch {
|
|
12635
|
+
toast.error(t("designEditor.toasts.clipboardBlocked"));
|
|
12636
|
+
}
|
|
12637
|
+
}, [pendingVisualStyleEdits.length, pendingVisualStylePrompt, t]);
|
|
12638
|
+
|
|
12109
12639
|
const triggerBlobDownload = useCallback((blob: Blob, filename: string) => {
|
|
12110
12640
|
const url = URL.createObjectURL(blob);
|
|
12111
12641
|
const a = document.createElement("a");
|
|
@@ -15445,6 +15975,7 @@ ${serializedHtml}
|
|
|
15445
15975
|
t("chat.suggestionMobile"),
|
|
15446
15976
|
]}
|
|
15447
15977
|
scope={designChatScope}
|
|
15978
|
+
showScopeBadge={false}
|
|
15448
15979
|
browserTabId={browserTabId}
|
|
15449
15980
|
/>
|
|
15450
15981
|
</div>
|
|
@@ -15665,6 +16196,49 @@ ${serializedHtml}
|
|
|
15665
16196
|
}}
|
|
15666
16197
|
/>
|
|
15667
16198
|
)}
|
|
16199
|
+
{pendingVisualStyleEdits.length > 0 ? (
|
|
16200
|
+
<div className="pointer-events-none absolute bottom-5 right-5 z-[70] flex items-end">
|
|
16201
|
+
<DropdownMenu>
|
|
16202
|
+
<DropdownMenuTrigger asChild>
|
|
16203
|
+
<Button
|
|
16204
|
+
className="pointer-events-auto h-11 cursor-pointer rounded-md bg-blue-500 px-4 text-sm font-semibold text-white shadow-[0_18px_40px_-20px_rgba(37,99,235,0.9)] hover:bg-blue-400 focus-visible:ring-blue-400"
|
|
16205
|
+
aria-label={t(
|
|
16206
|
+
"designEditor.pendingVisualStyles.applyAria",
|
|
16207
|
+
)}
|
|
16208
|
+
>
|
|
16209
|
+
<IconBrush className="h-4 w-4" />
|
|
16210
|
+
{t("designEditor.pendingVisualStyles.applyButton")}
|
|
16211
|
+
<span className="rounded bg-white/20 px-1.5 py-0.5 text-xs font-semibold text-white">
|
|
16212
|
+
{pendingVisualStylePropertyCount}
|
|
16213
|
+
</span>
|
|
16214
|
+
<IconChevronDown className="h-4 w-4" />
|
|
16215
|
+
</Button>
|
|
16216
|
+
</DropdownMenuTrigger>
|
|
16217
|
+
<DropdownMenuContent
|
|
16218
|
+
align="end"
|
|
16219
|
+
className="design-editor-app-menu-content w-64"
|
|
16220
|
+
>
|
|
16221
|
+
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
|
16222
|
+
{t("designEditor.pendingVisualStyles.previewLabel")}
|
|
16223
|
+
</DropdownMenuLabel>
|
|
16224
|
+
<DropdownMenuItem
|
|
16225
|
+
onClick={handleApplyPendingVisualStylesWithAgent}
|
|
16226
|
+
>
|
|
16227
|
+
<IconMessage className="mr-2 h-4 w-4" />
|
|
16228
|
+
{t(
|
|
16229
|
+
"designEditor.pendingVisualStyles.applyWithAgent",
|
|
16230
|
+
)}
|
|
16231
|
+
</DropdownMenuItem>
|
|
16232
|
+
<DropdownMenuItem
|
|
16233
|
+
onClick={handleCopyPendingVisualStylePrompt}
|
|
16234
|
+
>
|
|
16235
|
+
<IconClipboard className="mr-2 h-4 w-4" />
|
|
16236
|
+
{t("designEditor.pendingVisualStyles.copyPrompt")}
|
|
16237
|
+
</DropdownMenuItem>
|
|
16238
|
+
</DropdownMenuContent>
|
|
16239
|
+
</DropdownMenu>
|
|
16240
|
+
</div>
|
|
16241
|
+
) : null}
|
|
15668
16242
|
{viewMode === "overview" ? (
|
|
15669
16243
|
<MultiScreenCanvas
|
|
15670
16244
|
screens={overviewScreens}
|
|
@@ -16019,6 +16593,9 @@ ${serializedHtml}
|
|
|
16019
16593
|
onElementHover={(info) =>
|
|
16020
16594
|
handleScreenElementHover(screen.id, info)
|
|
16021
16595
|
}
|
|
16596
|
+
onEditorDragStateChange={
|
|
16597
|
+
handleEditorDragStateChange
|
|
16598
|
+
}
|
|
16022
16599
|
onClearSelection={() =>
|
|
16023
16600
|
handleScreenElementClear(screen.id)
|
|
16024
16601
|
}
|
|
@@ -16139,6 +16716,7 @@ ${serializedHtml}
|
|
|
16139
16716
|
onElementSelect={handleElementSelect}
|
|
16140
16717
|
onElementMarqueeSelect={handleElementMarqueeSelect}
|
|
16141
16718
|
onElementHover={handleElementHover}
|
|
16719
|
+
onEditorDragStateChange={handleEditorDragStateChange}
|
|
16142
16720
|
onClearSelection={() => {
|
|
16143
16721
|
setSelectedElement(null);
|
|
16144
16722
|
setHoveredElement(null);
|
|
@@ -16399,6 +16977,37 @@ ${serializedHtml}
|
|
|
16399
16977
|
) : null}
|
|
16400
16978
|
</div>
|
|
16401
16979
|
|
|
16980
|
+
<AlertDialog open={pendingVisualStyleWarningOpen}>
|
|
16981
|
+
<AlertDialogContent>
|
|
16982
|
+
<AlertDialogHeader>
|
|
16983
|
+
<AlertDialogTitle>
|
|
16984
|
+
{t("designEditor.pendingVisualStyles.leaveTitle")}
|
|
16985
|
+
</AlertDialogTitle>
|
|
16986
|
+
<AlertDialogDescription>
|
|
16987
|
+
{t(
|
|
16988
|
+
pendingVisualStylePropertyCount === 1
|
|
16989
|
+
? "designEditor.pendingVisualStyles.leaveDescriptionOne"
|
|
16990
|
+
: "designEditor.pendingVisualStyles.leaveDescriptionOther",
|
|
16991
|
+
{ count: pendingVisualStylePropertyCount },
|
|
16992
|
+
)}
|
|
16993
|
+
</AlertDialogDescription>
|
|
16994
|
+
</AlertDialogHeader>
|
|
16995
|
+
<AlertDialogFooter>
|
|
16996
|
+
<AlertDialogCancel
|
|
16997
|
+
onClick={handleStayOnPendingVisualStyleNavigation}
|
|
16998
|
+
>
|
|
16999
|
+
{t("designEditor.pendingVisualStyles.stay")}
|
|
17000
|
+
</AlertDialogCancel>
|
|
17001
|
+
<AlertDialogAction
|
|
17002
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
17003
|
+
onClick={handleDiscardPendingVisualStylesAndNavigate}
|
|
17004
|
+
>
|
|
17005
|
+
{t("designEditor.pendingVisualStyles.leave")}
|
|
17006
|
+
</AlertDialogAction>
|
|
17007
|
+
</AlertDialogFooter>
|
|
17008
|
+
</AlertDialogContent>
|
|
17009
|
+
</AlertDialog>
|
|
17010
|
+
|
|
16402
17011
|
{/* Motion dock (§6.3) — bottom timeline mounted while opening, open, or
|
|
16403
17012
|
closing. Canvas remains visible above.
|
|
16404
17013
|
Preview-only scrubbing fires a motion-preview postMessage to the
|