@agent-native/core 0.80.11 → 0.81.1
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 +53 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/AgentPanel.tsx +33 -1
- package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
- package/corpus/core/src/client/chat-view-transition.ts +33 -2
- package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
- package/corpus/core/src/db/client.ts +29 -2
- package/corpus/core/src/provider-api/index.ts +29 -0
- package/corpus/core/src/server/core-routes-plugin.ts +58 -20
- package/corpus/core/src/styles/agent-native.css +54 -0
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
- package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
- package/corpus/templates/analytics/app/global.css +8 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
- package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
- package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
- package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/app/global.css +15 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +90 -16
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
- package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
- package/corpus/templates/design/AGENTS.md +27 -0
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
- package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
- package/corpus/templates/design/actions/capture-design-state.ts +27 -4
- package/corpus/templates/design/actions/connect-localhost.ts +59 -22
- package/corpus/templates/design/actions/export-zip.ts +23 -8
- package/corpus/templates/design/actions/generate-design.ts +53 -1
- package/corpus/templates/design/actions/get-component-details.ts +5 -19
- package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
- package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
- package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
- package/corpus/templates/design/actions/navigate.ts +18 -3
- package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
- package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
- package/corpus/templates/design/actions/provider-api-request.ts +3 -3
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
- package/corpus/templates/design/actions/run-design-audit.ts +53 -4
- package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
- package/corpus/templates/design/actions/view-screen.ts +1 -1
- package/corpus/templates/design/actions/write-local-file.ts +213 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
- package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
- package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
- package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
- package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
- package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
- package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
- package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/global.css +7 -0
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/design/app/i18n-data.ts +358 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
- package/corpus/templates/design/app/pages/Index.tsx +5 -1
- package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +168 -65
- package/corpus/templates/design/package.json +3 -1
- package/corpus/templates/design/server/db/schema.ts +23 -0
- package/corpus/templates/design/server/handlers/uploads.ts +22 -1
- package/corpus/templates/design/server/lib/provider-api.ts +7 -4
- package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
- package/corpus/templates/design/server/plugins/db.ts +39 -0
- package/corpus/templates/design/server/register-secrets.ts +38 -0
- package/corpus/templates/design/shared/board-file.ts +228 -0
- package/corpus/templates/design/shared/board-objects.ts +288 -0
- package/corpus/templates/design/shared/code-layer.ts +52 -0
- package/corpus/templates/design/shared/motion-compiler.ts +58 -7
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/tsconfig.json +2 -1
- package/corpus/templates/slides/AGENTS.md +9 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
- package/corpus/templates/slides/app/vite-env.d.ts +18 -0
- package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/vite.config.ts +1 -0
- package/dist/cli/design-connect.d.ts +25 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +357 -18
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +18 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +1 -0
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +5 -5
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat-view-transition.d.ts +6 -0
- package/dist/client/chat-view-transition.d.ts.map +1 -1
- package/dist/client/chat-view-transition.js +23 -2
- package/dist/client/chat-view-transition.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +3 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +11 -4
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -3
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +20 -6
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +28 -2
- package/dist/db/client.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +8 -8
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/index.d.ts +5 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -0
- package/dist/provider-api/index.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +25 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +43 -18
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +54 -0
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,6 +11,14 @@ import {
|
|
|
11
11
|
type CanvasPin,
|
|
12
12
|
} from "@/components/visual-editor";
|
|
13
13
|
|
|
14
|
+
import { editorChromeBridgeScript } from "../../../.generated/bridge/editor-chrome.generated";
|
|
15
|
+
import { embeddedWheelBridgeScript } from "../../../.generated/bridge/embedded-wheel.generated";
|
|
16
|
+
import { hitTestBridgeScript } from "../../../.generated/bridge/hit-test.generated";
|
|
17
|
+
import { motionPreviewBridgeScript } from "../../../.generated/bridge/motion-preview.generated";
|
|
18
|
+
import { navBridgeScript } from "../../../.generated/bridge/nav.generated";
|
|
19
|
+
import { shaderFillPreviewBridgeScript } from "../../../.generated/bridge/shader-fill-preview.generated";
|
|
20
|
+
import { tweakBridgeScript } from "../../../.generated/bridge/tweak.generated";
|
|
21
|
+
import { zoomBridgeScript } from "../../../.generated/bridge/zoom.generated";
|
|
14
22
|
import { isTrustedCanvasBridgeMessage } from "./bridge-security";
|
|
15
23
|
import { DeviceFrame } from "./DeviceFrame";
|
|
16
24
|
import type { ElementInfo, DeviceFrameType } from "./types";
|
|
@@ -70,3316 +78,180 @@ export interface MotionTrackWire {
|
|
|
70
78
|
* MotionDock's scrubbing preview works in ALL editor modes without writing
|
|
71
79
|
* anything to the DB, Yjs state, or source files.
|
|
72
80
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* Seek all loaded tracks to normalised position t ∈ [0, 1] and apply
|
|
82
|
-
* the interpolated CSS property values as inline styles on the matching
|
|
83
|
-
* [data-agent-native-node-id="…"] elements. Never writes to storage.
|
|
84
|
-
*
|
|
85
|
-
* { type: 'motion-preview-clear' }
|
|
86
|
-
* Remove all motion-preview inline-style overrides and the in-memory
|
|
87
|
-
* track list. Called when the dock is closed or the timeline is
|
|
88
|
-
* discarded.
|
|
89
|
-
*
|
|
90
|
-
* Easing is deliberately simple: linear interpolation between keyframe
|
|
91
|
-
* values (CSS handles easing when the compiled CSS is actually applied;
|
|
92
|
-
* preview is a live visualisation, not a perfect recreation of the final
|
|
93
|
-
* animation).
|
|
94
|
-
*/
|
|
95
|
-
const MOTION_PREVIEW_BRIDGE_SCRIPT = `
|
|
96
|
-
<script data-agent-native-motion-preview-bridge>
|
|
97
|
-
(function() {
|
|
98
|
-
// Track list loaded by 'motion-load-tracks'.
|
|
99
|
-
var loadedTracks = [];
|
|
100
|
-
// Map of nodeId -> [property, ...] we have touched, for cleanup.
|
|
101
|
-
var touchedProps = {};
|
|
102
|
-
// Map of nodeId -> property -> original inline style value.
|
|
103
|
-
var originalInlineValues = {};
|
|
104
|
-
|
|
105
|
-
function lerp(a, b, ratio) {
|
|
106
|
-
var numA = parseFloat(a);
|
|
107
|
-
var numB = parseFloat(b);
|
|
108
|
-
if (Number.isFinite(numA) && Number.isFinite(numB)) {
|
|
109
|
-
var suffix = a.replace(/^-?[\\d.]+/, '') || b.replace(/^-?[\\d.]+/, '');
|
|
110
|
-
return (numA + (numB - numA) * ratio).toFixed(4).replace(/\\.?0+$/, '') + suffix;
|
|
111
|
-
}
|
|
112
|
-
// Non-numeric: snap to b after the midpoint.
|
|
113
|
-
return ratio < 0.5 ? a : b;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function interpolate(keyframes, t) {
|
|
117
|
-
if (!keyframes || keyframes.length === 0) return '';
|
|
118
|
-
if (keyframes.length === 1) return keyframes[0].value;
|
|
119
|
-
// Find surrounding keyframes.
|
|
120
|
-
var prev = keyframes[0];
|
|
121
|
-
var next = keyframes[keyframes.length - 1];
|
|
122
|
-
for (var i = 0; i < keyframes.length - 1; i++) {
|
|
123
|
-
if (t >= keyframes[i].t && t <= keyframes[i + 1].t) {
|
|
124
|
-
prev = keyframes[i];
|
|
125
|
-
next = keyframes[i + 1];
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
var span = next.t - prev.t;
|
|
130
|
-
if (span <= 0) return prev.value;
|
|
131
|
-
var ratio = Math.max(0, Math.min(1, (t - prev.t) / span));
|
|
132
|
-
return lerp(prev.value, next.value, ratio);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function applyPreview(t) {
|
|
136
|
-
for (var i = 0; i < loadedTracks.length; i++) {
|
|
137
|
-
var track = loadedTracks[i];
|
|
138
|
-
var el = document.querySelector('[data-agent-native-node-id="' + track.targetNodeId + '"]');
|
|
139
|
-
if (!el) continue;
|
|
140
|
-
var value = interpolate(track.keyframes, t);
|
|
141
|
-
if (value === '') continue;
|
|
142
|
-
if (!originalInlineValues[track.targetNodeId]) originalInlineValues[track.targetNodeId] = {};
|
|
143
|
-
if (!(track.property in originalInlineValues[track.targetNodeId])) {
|
|
144
|
-
originalInlineValues[track.targetNodeId][track.property] = el.style[track.property] || '';
|
|
145
|
-
}
|
|
146
|
-
el.style[track.property] = value;
|
|
147
|
-
if (!touchedProps[track.targetNodeId]) touchedProps[track.targetNodeId] = [];
|
|
148
|
-
if (touchedProps[track.targetNodeId].indexOf(track.property) === -1) {
|
|
149
|
-
touchedProps[track.targetNodeId].push(track.property);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function clearPreview() {
|
|
155
|
-
var nodeIds = Object.keys(touchedProps);
|
|
156
|
-
for (var i = 0; i < nodeIds.length; i++) {
|
|
157
|
-
var el = document.querySelector('[data-agent-native-node-id="' + nodeIds[i] + '"]');
|
|
158
|
-
if (!el) continue;
|
|
159
|
-
var props = touchedProps[nodeIds[i]];
|
|
160
|
-
for (var j = 0; j < props.length; j++) {
|
|
161
|
-
var originals = originalInlineValues[nodeIds[i]] || {};
|
|
162
|
-
el.style[props[j]] = Object.prototype.hasOwnProperty.call(originals, props[j])
|
|
163
|
-
? originals[props[j]]
|
|
164
|
-
: '';
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
touchedProps = {};
|
|
168
|
-
originalInlineValues = {};
|
|
169
|
-
loadedTracks = [];
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
window.addEventListener('message', function(e) {
|
|
173
|
-
if (e.source !== window.parent) return;
|
|
174
|
-
if (!e.data || typeof e.data.type !== 'string') return;
|
|
175
|
-
if (e.data.type === 'motion-load-tracks') {
|
|
176
|
-
clearPreview();
|
|
177
|
-
loadedTracks = Array.isArray(e.data.tracks) ? e.data.tracks : [];
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
if (e.data.type === 'motion-preview') {
|
|
181
|
-
var t = Number(e.data.t);
|
|
182
|
-
if (!Number.isFinite(t)) return;
|
|
183
|
-
t = Math.max(0, Math.min(1, t));
|
|
184
|
-
applyPreview(t);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (e.data.type === 'motion-preview-clear') {
|
|
188
|
-
clearPreview();
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
})();
|
|
193
|
-
</script>
|
|
194
|
-
`;
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Shader-fill preview bridge. ALWAYS injected alongside the other bridge
|
|
198
|
-
* scripts so the parent can apply a CSS gradient approximation of a shader
|
|
199
|
-
* fill to the currently-selected element **without** persisting anything.
|
|
200
|
-
*
|
|
201
|
-
* Protocol (parent → iframe):
|
|
202
|
-
*
|
|
203
|
-
* { type: 'shader-fill-preview', selector, nodeId, css }
|
|
204
|
-
* Apply `css` as the `background` inline style on the first element that
|
|
205
|
-
* matches `selector` (preferred) or `[data-agent-native-node-id="nodeId"]`.
|
|
206
|
-
* When both are absent, targets `document.body`. Stores the previous
|
|
207
|
-
* background value so it can be restored on clear.
|
|
208
|
-
* Preview-only — never writes to DB, Yjs, or source files.
|
|
209
|
-
*
|
|
210
|
-
* { type: 'shader-fill-preview-clear' }
|
|
211
|
-
* Remove the applied background override and restore the previous value.
|
|
212
|
-
* Called when the user discards the preview or switches selections.
|
|
213
|
-
*/
|
|
214
|
-
const SHADER_FILL_PREVIEW_BRIDGE_SCRIPT = `
|
|
215
|
-
<script data-agent-native-shader-fill-preview-bridge>
|
|
216
|
-
(function() {
|
|
217
|
-
// Track the element we patched and its original background so we can undo.
|
|
218
|
-
var patchedEl = null;
|
|
219
|
-
var originalBackground = '';
|
|
220
|
-
|
|
221
|
-
function resolveTarget(selector, nodeId) {
|
|
222
|
-
if (selector) {
|
|
223
|
-
try {
|
|
224
|
-
var hit = document.querySelector(selector);
|
|
225
|
-
if (hit) return hit;
|
|
226
|
-
} catch (_err) {}
|
|
227
|
-
}
|
|
228
|
-
if (nodeId) {
|
|
229
|
-
var byId = document.querySelector('[data-agent-native-node-id="' + nodeId.replace(/"/g, '\\\\"') + '"]');
|
|
230
|
-
if (byId) return byId;
|
|
231
|
-
}
|
|
232
|
-
return document.body;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function applyPreview(selector, nodeId, css) {
|
|
236
|
-
// Clear any prior patch first so we don't stack patches.
|
|
237
|
-
clearPreview();
|
|
238
|
-
var el = resolveTarget(selector, nodeId);
|
|
239
|
-
if (!el) return;
|
|
240
|
-
originalBackground = el.style.background || '';
|
|
241
|
-
el.style.background = css || '';
|
|
242
|
-
patchedEl = el;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function clearPreview() {
|
|
246
|
-
if (!patchedEl) return;
|
|
247
|
-
patchedEl.style.background = originalBackground;
|
|
248
|
-
patchedEl = null;
|
|
249
|
-
originalBackground = '';
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
window.addEventListener('message', function(e) {
|
|
253
|
-
if (e.source !== window.parent) return;
|
|
254
|
-
if (!e.data || typeof e.data.type !== 'string') return;
|
|
255
|
-
if (e.data.type === 'shader-fill-preview') {
|
|
256
|
-
var selector = typeof e.data.selector === 'string' ? e.data.selector : '';
|
|
257
|
-
var nodeId = typeof e.data.nodeId === 'string' ? e.data.nodeId : '';
|
|
258
|
-
var css = typeof e.data.css === 'string' ? e.data.css : '';
|
|
259
|
-
applyPreview(selector, nodeId, css);
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
if (e.data.type === 'shader-fill-preview-clear') {
|
|
263
|
-
clearPreview();
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
})();
|
|
268
|
-
</script>
|
|
269
|
-
`;
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Tweak-bridge script. ALWAYS injected so the parent's postMessage
|
|
273
|
-
* (`tweak-values`) can update CSS custom properties on the iframe's :root
|
|
274
|
-
* regardless of which editor mode is active. Without this the tweak panel
|
|
275
|
-
* silently no-ops in the default Comment mode.
|
|
276
|
-
*/
|
|
277
|
-
const TWEAK_BRIDGE_SCRIPT = `
|
|
278
|
-
<script data-agent-native-tweak-bridge>
|
|
279
|
-
(function() {
|
|
280
|
-
window.addEventListener('message', function(e) {
|
|
281
|
-
if (e.source !== window.parent) return;
|
|
282
|
-
if (!e.data || e.data.type !== 'tweak-values') return;
|
|
283
|
-
var root = document.documentElement;
|
|
284
|
-
var vals = e.data.values || {};
|
|
285
|
-
Object.keys(vals).forEach(function(k) {
|
|
286
|
-
root.style.setProperty(k, vals[k]);
|
|
287
|
-
});
|
|
288
|
-
});
|
|
289
|
-
})();
|
|
290
|
-
</script>
|
|
291
|
-
`;
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Pinch-zoom bridge: forwards trackpad pinch / Cmd-Ctrl+scroll wheel events
|
|
295
|
-
* from inside the iframe to the parent window. Wheel events don't naturally
|
|
296
|
-
* bubble out of an iframe, so without this the user can only pinch in the
|
|
297
|
-
* empty area around the canvas, not over the design itself.
|
|
298
|
-
*/
|
|
299
|
-
const ZOOM_BRIDGE_SCRIPT = `
|
|
300
|
-
<script data-agent-native-zoom-bridge>
|
|
301
|
-
(function() {
|
|
302
|
-
// Attach to documentElement (not window/document) so { passive: false }
|
|
303
|
-
// is honored consistently and the browser doesn't natively pinch-zoom the
|
|
304
|
-
// iframe's own document alongside the parent's zoom.
|
|
305
|
-
var target = document.documentElement || document.body || document;
|
|
306
|
-
function onWheel(e) {
|
|
307
|
-
if (!(e.ctrlKey || e.metaKey)) return;
|
|
308
|
-
e.preventDefault();
|
|
309
|
-
try {
|
|
310
|
-
window.parent.postMessage({
|
|
311
|
-
type: 'pinch-zoom-wheel',
|
|
312
|
-
deltaY: e.deltaY,
|
|
313
|
-
clientX: e.clientX,
|
|
314
|
-
clientY: e.clientY,
|
|
315
|
-
}, '*');
|
|
316
|
-
} catch (err) {}
|
|
317
|
-
}
|
|
318
|
-
target.addEventListener('wheel', onWheel, { passive: false, capture: true });
|
|
319
|
-
})();
|
|
320
|
-
</script>
|
|
321
|
-
`;
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Embedded overview bridge. A screen preview is a real iframe, so normal wheel
|
|
325
|
-
* events never bubble to the overview canvas underneath. In embedded mode we
|
|
326
|
-
* forward a bounded wheel payload to the parent so the existing canvas wheel
|
|
327
|
-
* handler can pan/zoom exactly as if the pointer were over empty canvas.
|
|
328
|
-
*/
|
|
329
|
-
const EMBEDDED_WHEEL_BRIDGE_SCRIPT = `
|
|
330
|
-
<script data-agent-native-embedded-wheel-bridge>
|
|
331
|
-
(function() {
|
|
332
|
-
var enabled = __EMBEDDED_WHEEL_FORWARDING_ENABLED__;
|
|
333
|
-
if (!enabled) return;
|
|
334
|
-
function clamp(value, limit) {
|
|
335
|
-
var number = Number(value) || 0;
|
|
336
|
-
if (number > limit) return limit;
|
|
337
|
-
if (number < -limit) return -limit;
|
|
338
|
-
return number;
|
|
339
|
-
}
|
|
340
|
-
function onWheel(e) {
|
|
341
|
-
e.preventDefault();
|
|
342
|
-
e.stopPropagation();
|
|
343
|
-
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
|
|
344
|
-
try {
|
|
345
|
-
window.parent.postMessage({
|
|
346
|
-
type: 'embedded-canvas-wheel',
|
|
347
|
-
deltaX: clamp(e.deltaX, 240),
|
|
348
|
-
deltaY: clamp(e.deltaY, 240),
|
|
349
|
-
deltaZ: clamp(e.deltaZ, 240),
|
|
350
|
-
deltaMode: e.deltaMode,
|
|
351
|
-
clientX: e.clientX,
|
|
352
|
-
clientY: e.clientY,
|
|
353
|
-
ctrlKey: !!e.ctrlKey,
|
|
354
|
-
metaKey: !!e.metaKey,
|
|
355
|
-
shiftKey: !!e.shiftKey,
|
|
356
|
-
altKey: !!e.altKey,
|
|
357
|
-
}, '*');
|
|
358
|
-
} catch (err) {}
|
|
359
|
-
}
|
|
360
|
-
var target = document.documentElement || document.body || document;
|
|
361
|
-
target.addEventListener('wheel', onWheel, { passive: false, capture: true });
|
|
362
|
-
})();
|
|
363
|
-
</script>
|
|
364
|
-
`;
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Navigation bridge. ALWAYS injected. A prototype lives in a `srcdoc` iframe,
|
|
368
|
-
* so a plain `<a href="/pricing">` resolves the relative URL against the PARENT
|
|
369
|
-
* app document and navigates the iframe to the Design app itself ("Design not
|
|
370
|
-
* found"), nuking the prototype. We intercept link clicks + relative form
|
|
371
|
-
* submits and route them to the parent instead:
|
|
372
|
-
* - in-page anchors (`#...`) and `javascript:`/`@click` handlers: left alone
|
|
373
|
-
* - external `http(s)`/`//` links: opened in a new tab by the parent
|
|
374
|
-
* - internal/relative links (or an explicit `data-screen`): asked to switch
|
|
375
|
-
* to the matching screen in a multi-screen design; otherwise a no-op so the
|
|
376
|
-
* prototype never blows itself away.
|
|
377
|
-
*/
|
|
378
|
-
const NAV_BRIDGE_SCRIPT = `
|
|
379
|
-
<script data-agent-native-nav-bridge>
|
|
380
|
-
(function() {
|
|
381
|
-
function classify(href) {
|
|
382
|
-
var h = (href || '').trim();
|
|
383
|
-
if (!h) return null;
|
|
384
|
-
var lower = h.toLowerCase();
|
|
385
|
-
if (lower.charAt(0) === '#') return null;
|
|
386
|
-
if (lower.indexOf('javascript:') === 0) return null;
|
|
387
|
-
if (lower.indexOf('mailto:') === 0 || lower.indexOf('tel:') === 0) {
|
|
388
|
-
return { external: true, href: h };
|
|
389
|
-
}
|
|
390
|
-
if (/^https?:\\/\\//i.test(h) || /^\\/\\//.test(h)) {
|
|
391
|
-
return { external: true, href: h };
|
|
392
|
-
}
|
|
393
|
-
var screen = h.replace(/^\\.?\\//, '').split(/[?#]/)[0];
|
|
394
|
-
return { external: false, href: h, screen: screen };
|
|
395
|
-
}
|
|
396
|
-
document.addEventListener('click', function(e) {
|
|
397
|
-
var t = e.target;
|
|
398
|
-
if (!t || !t.closest) return;
|
|
399
|
-
var a = t.closest('a[href], [data-screen]');
|
|
400
|
-
if (!a) return;
|
|
401
|
-
var ds = a.getAttribute && a.getAttribute('data-screen');
|
|
402
|
-
// In-page anchors ('#...') and empty hrefs must be handled in-document.
|
|
403
|
-
// A srcdoc document resolves '#'/'' against the PARENT app URL, so the
|
|
404
|
-
// browser's default action would navigate the iframe to the app itself.
|
|
405
|
-
if (!ds) {
|
|
406
|
-
var rawHref = a.getAttribute('href');
|
|
407
|
-
if (rawHref != null) {
|
|
408
|
-
var hh = rawHref.trim();
|
|
409
|
-
if (hh === '' || hh.charAt(0) === '#') {
|
|
410
|
-
e.preventDefault();
|
|
411
|
-
var fid = hh.charAt(0) === '#' ? hh.slice(1) : '';
|
|
412
|
-
var tgt = fid ? document.getElementById(fid) : null;
|
|
413
|
-
if (tgt && tgt.scrollIntoView) {
|
|
414
|
-
tgt.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
415
|
-
} else {
|
|
416
|
-
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
417
|
-
}
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
var info = ds
|
|
423
|
-
? { external: false, href: ds, screen: ds.replace(/^\\.?\\//, '').split(/[?#]/)[0] }
|
|
424
|
-
: classify(a.getAttribute('href'));
|
|
425
|
-
if (!info) return;
|
|
426
|
-
if (info.external) {
|
|
427
|
-
// Open external links in a new tab from the iframe itself (the sandbox
|
|
428
|
-
// grants allow-popups), bound to this real user click. We deliberately do
|
|
429
|
-
// NOT round-trip through the parent: a parent window.open() driven by
|
|
430
|
-
// postMessage would let any script in here spawn popups without a gesture.
|
|
431
|
-
try {
|
|
432
|
-
a.setAttribute('target', '_blank');
|
|
433
|
-
a.setAttribute('rel', 'noopener noreferrer');
|
|
434
|
-
} catch (err) {}
|
|
435
|
-
return; // allow the native click to proceed
|
|
436
|
-
}
|
|
437
|
-
e.preventDefault();
|
|
438
|
-
try {
|
|
439
|
-
window.parent.postMessage({
|
|
440
|
-
type: 'prototype-navigate',
|
|
441
|
-
href: info.href,
|
|
442
|
-
screen: info.screen || '',
|
|
443
|
-
}, '*');
|
|
444
|
-
} catch (err) {}
|
|
445
|
-
}, true);
|
|
446
|
-
document.addEventListener('submit', function(e) {
|
|
447
|
-
var f = e.target;
|
|
448
|
-
if (!f || f.tagName !== 'FORM') return;
|
|
449
|
-
var action = f.getAttribute('action') || '';
|
|
450
|
-
if (/^https?:\\/\\//i.test(action)) return;
|
|
451
|
-
e.preventDefault();
|
|
452
|
-
}, true);
|
|
453
|
-
})();
|
|
454
|
-
</script>
|
|
455
|
-
`;
|
|
456
|
-
|
|
457
|
-
const EDITOR_BRIDGE_VAR_NAMES = [
|
|
458
|
-
"--design-editor-accent-color",
|
|
459
|
-
"--design-editor-accent-hover-color",
|
|
460
|
-
"--design-editor-selection-color",
|
|
461
|
-
"--design-editor-accent-strong-color",
|
|
462
|
-
"--design-editor-accent-contrast-color",
|
|
463
|
-
"--design-editor-measure-color",
|
|
464
|
-
"--background",
|
|
465
|
-
"--foreground",
|
|
466
|
-
"--border",
|
|
467
|
-
];
|
|
468
|
-
|
|
469
|
-
function readEditorBridgeThemeVars(): Record<string, string> {
|
|
470
|
-
if (typeof window === "undefined") return {};
|
|
471
|
-
const styles = window.getComputedStyle(document.documentElement);
|
|
472
|
-
return Object.fromEntries(
|
|
473
|
-
EDITOR_BRIDGE_VAR_NAMES.map((name) => [
|
|
474
|
-
name,
|
|
475
|
-
styles.getPropertyValue(name).trim(),
|
|
476
|
-
]).filter(([, value]) => value.length > 0),
|
|
477
|
-
);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
function createEditorBridgeThemeScript(vars: Record<string, string>) {
|
|
481
|
-
const serializedVars = JSON.stringify(vars).replace(/</g, "\\u003c");
|
|
482
|
-
return `
|
|
483
|
-
<script data-agent-native-editor-theme>
|
|
484
|
-
(function() {
|
|
485
|
-
var vars = ${serializedVars};
|
|
486
|
-
var root = document.documentElement;
|
|
487
|
-
Object.keys(vars).forEach(function(name) {
|
|
488
|
-
root.style.setProperty(name, vars[name]);
|
|
489
|
-
});
|
|
490
|
-
})();
|
|
491
|
-
</script>
|
|
492
|
-
`;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Editor chrome bridge: blocks native iframe app interaction outside Interact
|
|
497
|
-
* mode and replaces it with element hover/selection overlays. Double-click text
|
|
498
|
-
* editing is enabled only while the editor is specifically in Edit mode.
|
|
499
|
-
*/
|
|
500
|
-
const EDITOR_CHROME_BRIDGE_SCRIPT = `
|
|
501
|
-
<script data-agent-native-editor-chrome-bridge>
|
|
502
|
-
(function() {
|
|
503
|
-
var readOnly = __READ_ONLY__;
|
|
504
|
-
var textEditingEnabled = !readOnly && __TEXT_EDITING_ENABLED__;
|
|
505
|
-
var scaleToolEnabled = false;
|
|
506
|
-
var editorChromeScaleX = Math.max(0.05, Number(__EDITOR_CHROME_SCALE_X__) || 1);
|
|
507
|
-
var editorChromeScaleY = Math.max(0.05, Number(__EDITOR_CHROME_SCALE_Y__) || editorChromeScaleX);
|
|
508
|
-
|
|
509
|
-
// Ease the constant-size selection chrome to its new size when overview zoom
|
|
510
|
-
// settles (parent posts set-editor-chrome-scale), matching the canvas chrome.
|
|
511
|
-
// Only chrome-scale-driven props animate; the overlay's live position is excluded.
|
|
512
|
-
(function () {
|
|
513
|
-
var chromeTransitionStyle = document.createElement('style');
|
|
514
|
-
chromeTransitionStyle.textContent =
|
|
515
|
-
'[data-agent-native-edit-overlay="selection"]{transition:border-width 150ms ease-out}' +
|
|
516
|
-
'[data-agent-native-edge-handle],[data-agent-native-edit-handle],[data-agent-native-rotate-handle]{transition:width 150ms ease-out,height 150ms ease-out,border-width 150ms ease-out,top 150ms ease-out,bottom 150ms ease-out,left 150ms ease-out,right 150ms ease-out}' +
|
|
517
|
-
'[data-agent-native-spacing-line]{position:absolute;display:none;pointer-events:none;border-radius:999px}' +
|
|
518
|
-
'[data-agent-native-spacing-region]{position:absolute;display:none;box-sizing:border-box;pointer-events:auto;background-size:6px 6px}' +
|
|
519
|
-
'[data-agent-native-spacing-region][data-orientation="vertical"]{cursor:ew-resize}' +
|
|
520
|
-
'[data-agent-native-spacing-region][data-orientation="horizontal"]{cursor:ns-resize}';
|
|
521
|
-
(document.head || document.documentElement).appendChild(chromeTransitionStyle);
|
|
522
|
-
})();
|
|
523
|
-
|
|
524
|
-
function chromeScaleX() {
|
|
525
|
-
return 1 / Math.max(0.05, editorChromeScaleX);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function chromeScaleY() {
|
|
529
|
-
return 1 / Math.max(0.05, editorChromeScaleY);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function chromeLineScale() {
|
|
533
|
-
return 1 / Math.max(0.05, Math.max(editorChromeScaleX, editorChromeScaleY));
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function syncEditorChromeScaleVars() {
|
|
537
|
-
document.documentElement.style.setProperty('--agent-native-editor-chrome-scale-x', String(chromeScaleX()));
|
|
538
|
-
document.documentElement.style.setProperty('--agent-native-editor-chrome-scale-y', String(chromeScaleY()));
|
|
539
|
-
document.documentElement.style.setProperty('--agent-native-editor-chrome-line-scale', String(chromeLineScale()));
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function escapeIdent(value) {
|
|
543
|
-
if (window.CSS && typeof window.CSS.escape === 'function') {
|
|
544
|
-
return window.CSS.escape(value);
|
|
545
|
-
}
|
|
546
|
-
return String(value).replace(/[^a-zA-Z0-9_-]/g, '\\\\$&');
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function escapeAttribute(value) {
|
|
550
|
-
return String(value).replace(/\\\\/g, '\\\\\\\\').replace(/"/g, '\\\\"');
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function attributeSelector(el, name) {
|
|
554
|
-
var value = el && el.getAttribute && el.getAttribute(name);
|
|
555
|
-
return value ? '[' + name + '="' + escapeAttribute(value) + '"]' : '';
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function classSelectorSuffix(el, maxCount) {
|
|
559
|
-
if (!el || !el.classList) return '';
|
|
560
|
-
return Array.prototype.slice.call(el.classList, 0, maxCount)
|
|
561
|
-
.map(function(token) { return '.' + escapeIdent(token); })
|
|
562
|
-
.join('');
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function selectorPart(el) {
|
|
566
|
-
if (!el || !el.tagName) return '';
|
|
567
|
-
var stableSelector =
|
|
568
|
-
attributeSelector(el, 'data-agent-native-node-id') ||
|
|
569
|
-
attributeSelector(el, 'data-code-layer-id') ||
|
|
570
|
-
attributeSelector(el, 'data-layer-id') ||
|
|
571
|
-
attributeSelector(el, 'data-builder-id') ||
|
|
572
|
-
attributeSelector(el, 'data-loc');
|
|
573
|
-
if (stableSelector) return el.tagName.toLowerCase() + stableSelector;
|
|
574
|
-
if (el.id) return '#' + escapeIdent(el.id);
|
|
575
|
-
var part = el.tagName.toLowerCase() + (stableSelector || classSelectorSuffix(el, 2));
|
|
576
|
-
var parent = el.parentElement;
|
|
577
|
-
if (parent) {
|
|
578
|
-
var sameTag = Array.prototype.filter.call(
|
|
579
|
-
parent.children,
|
|
580
|
-
function(child) { return child.tagName === el.tagName; }
|
|
581
|
-
);
|
|
582
|
-
if (sameTag.length > 1) {
|
|
583
|
-
part += ':nth-of-type(' + (sameTag.indexOf(el) + 1) + ')';
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
return part;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
function selectorPath(el, stopEl) {
|
|
590
|
-
var parts = [];
|
|
591
|
-
var node = el;
|
|
592
|
-
while (node && node.nodeType === 1) {
|
|
593
|
-
if (node !== stopEl) parts.unshift(selectorPart(node));
|
|
594
|
-
if (node === stopEl) break;
|
|
595
|
-
node = node.parentElement;
|
|
596
|
-
}
|
|
597
|
-
return parts.slice(-5).join(' > ');
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function getSourceId(el) {
|
|
601
|
-
if (!el || !el.getAttribute) return '';
|
|
602
|
-
return (
|
|
603
|
-
el.getAttribute('data-agent-native-node-id') ||
|
|
604
|
-
el.getAttribute('data-code-layer-id') ||
|
|
605
|
-
el.getAttribute('data-layer-id') ||
|
|
606
|
-
el.getAttribute('data-builder-id') ||
|
|
607
|
-
el.getAttribute('data-loc') ||
|
|
608
|
-
el.id ||
|
|
609
|
-
''
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
function isDocumentRootElement(el) {
|
|
614
|
-
return el === document.body || el === document.documentElement;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
function closestStableSourceElement(el) {
|
|
618
|
-
if (!el || !el.closest) return null;
|
|
619
|
-
var stable = el.closest('[data-agent-native-node-id],[data-code-layer-id],[data-layer-id],[data-builder-id],[data-loc]');
|
|
620
|
-
if (!stable || isDocumentRootElement(stable)) return null;
|
|
621
|
-
return stable;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function hasStableOwnSource(el) {
|
|
625
|
-
return !!(
|
|
626
|
-
el &&
|
|
627
|
-
!isDocumentRootElement(el) &&
|
|
628
|
-
getSourceId(el)
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function selectionTargetForHit(hit) {
|
|
633
|
-
if (!hit || isDocumentRootElement(hit)) return hit;
|
|
634
|
-
if (selectedEl && hit !== selectedEl && selectedEl.contains(hit)) return hit;
|
|
635
|
-
if (hasStableOwnSource(hit)) return hit;
|
|
636
|
-
return closestStableSourceElement(hit) || hit;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function freshRuntimeNodeId(prefix) {
|
|
640
|
-
var random = '';
|
|
641
|
-
try {
|
|
642
|
-
if (window.crypto && window.crypto.getRandomValues) {
|
|
643
|
-
var bytes = new Uint32Array(2);
|
|
644
|
-
window.crypto.getRandomValues(bytes);
|
|
645
|
-
random = Array.prototype.map.call(bytes, function(part) {
|
|
646
|
-
return part.toString(36);
|
|
647
|
-
}).join('');
|
|
648
|
-
}
|
|
649
|
-
} catch (_err) {}
|
|
650
|
-
if (!random) random = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
651
|
-
return 'an-' + String(prefix || 'copy') + '-' + random;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function resetRuntimeStableIds(root) {
|
|
655
|
-
if (!root || !root.querySelectorAll) return;
|
|
656
|
-
var nodes = [root].concat(Array.prototype.slice.call(root.querySelectorAll('[data-agent-native-node-id]')));
|
|
657
|
-
nodes.forEach(function(node, index) {
|
|
658
|
-
if (node && node.setAttribute) {
|
|
659
|
-
node.setAttribute('data-agent-native-node-id', freshRuntimeNodeId(index === 0 ? 'copy' : 'copy-child'));
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
function getSelector(el) {
|
|
665
|
-
var stableOwnSelector =
|
|
666
|
-
attributeSelector(el, 'data-agent-native-node-id') ||
|
|
667
|
-
attributeSelector(el, 'data-code-layer-id') ||
|
|
668
|
-
attributeSelector(el, 'data-layer-id') ||
|
|
669
|
-
attributeSelector(el, 'data-builder-id') ||
|
|
670
|
-
attributeSelector(el, 'data-loc');
|
|
671
|
-
if (stableOwnSelector) return stableOwnSelector;
|
|
672
|
-
|
|
673
|
-
if (el.id) return '#' + escapeIdent(el.id);
|
|
674
|
-
var stableAncestor = closestStableSourceElement(el);
|
|
675
|
-
if (stableAncestor && stableAncestor !== el) {
|
|
676
|
-
var stableAncestorSelector = selectorPart(stableAncestor);
|
|
677
|
-
if (stableAncestorSelector) {
|
|
678
|
-
var descendantPath = selectorPath(el, stableAncestor);
|
|
679
|
-
var descendantParts = descendantPath ? descendantPath.split(' > ') : [];
|
|
680
|
-
if (descendantParts.length) {
|
|
681
|
-
return stableAncestorSelector + ' > ' + descendantParts.join(' > ');
|
|
682
|
-
}
|
|
683
|
-
return stableAncestorSelector;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
return selectorPath(el);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function getElementInfo(el) {
|
|
691
|
-
var cs = window.getComputedStyle(el);
|
|
692
|
-
var rect = el.getBoundingClientRect();
|
|
693
|
-
var parentStyles = el.parentElement
|
|
694
|
-
? window.getComputedStyle(el.parentElement)
|
|
695
|
-
: null;
|
|
696
|
-
var parentDisplay = parentStyles ? parentStyles.display : undefined;
|
|
697
|
-
var sourceBacked = hasStableOwnSource(el) || !!closestStableSourceElement(el);
|
|
698
|
-
var sourceId = sourceBacked ? (getSourceId(el) || getSelector(el)) : '';
|
|
699
|
-
var parentLayout = parentStyles
|
|
700
|
-
? {
|
|
701
|
-
display: parentStyles.display,
|
|
702
|
-
flexDirection: parentStyles.flexDirection,
|
|
703
|
-
alignItems: parentStyles.alignItems,
|
|
704
|
-
justifyContent: parentStyles.justifyContent,
|
|
705
|
-
gap: parentStyles.gap,
|
|
706
|
-
gridTemplateColumns: parentStyles.gridTemplateColumns,
|
|
707
|
-
gridTemplateRows: parentStyles.gridTemplateRows,
|
|
708
|
-
position: parentStyles.position,
|
|
709
|
-
}
|
|
710
|
-
: undefined;
|
|
711
|
-
var capabilities = sourceBacked
|
|
712
|
-
? [
|
|
713
|
-
{
|
|
714
|
-
kind: 'deterministic-style-edit',
|
|
715
|
-
label: 'deterministic-style-edit',
|
|
716
|
-
confidence: 0.92,
|
|
717
|
-
reason: 'Inline style can be patched and replayed through HMR/collab.',
|
|
718
|
-
},
|
|
719
|
-
]
|
|
720
|
-
: [
|
|
721
|
-
{
|
|
722
|
-
kind: 'unsupported',
|
|
723
|
-
label: 'runtime-only-element',
|
|
724
|
-
confidence: 0.3,
|
|
725
|
-
reason: 'This runtime node is not anchored to a source code layer.',
|
|
726
|
-
},
|
|
727
|
-
];
|
|
728
|
-
if (sourceBacked && el.classList && el.classList.length > 0) {
|
|
729
|
-
capabilities.push({
|
|
730
|
-
kind: 'deterministic-class-edit',
|
|
731
|
-
label: 'deterministic-class-edit',
|
|
732
|
-
confidence: 0.78,
|
|
733
|
-
reason: 'Class tokens are visible on the selected element.',
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
if (sourceBacked && (parentDisplay === 'flex' || parentDisplay === 'inline-flex' || parentDisplay === 'grid' || parentDisplay === 'inline-grid')) {
|
|
737
|
-
capabilities.push({
|
|
738
|
-
kind: 'agent-structural-edit',
|
|
739
|
-
label: 'agent-structural-edit',
|
|
740
|
-
confidence: 0.54,
|
|
741
|
-
reason: 'Parent layout context decides whether movement means gap, order, alignment, or wrapper structure.',
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
// --- provenance: read source-location attributes when present ---
|
|
745
|
-
// These are emitted by connected apps via @vitejs/plugin-react jsxDEV or a
|
|
746
|
-
// Babel source plugin. Cross-origin localhost iframes cannot be read (CSP /
|
|
747
|
-
// same-origin policy), so this will be undefined in that case — expected.
|
|
748
|
-
var provenance = undefined;
|
|
749
|
-
var dataSourceFile = el.getAttribute('data-source-file');
|
|
750
|
-
var dataSourceLine = el.getAttribute('data-source-line');
|
|
751
|
-
var dataSourceColumn = el.getAttribute('data-source-column');
|
|
752
|
-
var dataComponentName = el.getAttribute('data-component-name');
|
|
753
|
-
var dataLoc = el.getAttribute('data-loc');
|
|
754
|
-
// data-loc may encode "file:line:col" (Babel source plugin convention).
|
|
755
|
-
// Only parse it when data-source-file is absent, to avoid double-reads.
|
|
756
|
-
if (!dataSourceFile && dataLoc) {
|
|
757
|
-
var lastColonIndex = dataLoc.lastIndexOf(':');
|
|
758
|
-
var lastPart = lastColonIndex >= 0 ? dataLoc.slice(lastColonIndex + 1) : '';
|
|
759
|
-
if (lastColonIndex >= 0 && /^\\d+$/.test(lastPart)) {
|
|
760
|
-
var beforeLastPart = dataLoc.slice(0, lastColonIndex);
|
|
761
|
-
var previousColonIndex = beforeLastPart.lastIndexOf(':');
|
|
762
|
-
var previousPart = previousColonIndex >= 0 ? beforeLastPart.slice(previousColonIndex + 1) : '';
|
|
763
|
-
var hasColumn = /^\\d+$/.test(previousPart);
|
|
764
|
-
dataSourceFile = hasColumn ? beforeLastPart.slice(0, previousColonIndex) : beforeLastPart;
|
|
765
|
-
dataSourceLine = hasColumn ? previousPart : lastPart;
|
|
766
|
-
if (hasColumn) dataSourceColumn = lastPart;
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
if (dataSourceFile || dataSourceLine || dataSourceColumn || dataComponentName) {
|
|
770
|
-
provenance = {};
|
|
771
|
-
if (dataSourceFile) provenance.sourceFile = dataSourceFile;
|
|
772
|
-
if (dataSourceLine) { var ln = parseInt(dataSourceLine, 10); if (!isNaN(ln)) provenance.line = ln; }
|
|
773
|
-
if (dataSourceColumn) { var col = parseInt(dataSourceColumn, 10); if (!isNaN(col)) provenance.column = col; }
|
|
774
|
-
if (dataComponentName) provenance.component = dataComponentName;
|
|
775
|
-
}
|
|
776
|
-
return {
|
|
777
|
-
tagName: el.tagName.toLowerCase(),
|
|
778
|
-
id: el.id || undefined,
|
|
779
|
-
sourceId: sourceId,
|
|
780
|
-
selector: getSelector(el),
|
|
781
|
-
classes: Array.from(el.classList),
|
|
782
|
-
computedStyles: {
|
|
783
|
-
color: cs.color,
|
|
784
|
-
backgroundColor: cs.backgroundColor,
|
|
785
|
-
backgroundImage: cs.backgroundImage,
|
|
786
|
-
backgroundBlendMode: cs.backgroundBlendMode,
|
|
787
|
-
fontSize: cs.fontSize,
|
|
788
|
-
fontFamily: cs.fontFamily,
|
|
789
|
-
fontWeight: cs.fontWeight,
|
|
790
|
-
lineHeight: cs.lineHeight,
|
|
791
|
-
letterSpacing: cs.letterSpacing,
|
|
792
|
-
textAlign: cs.textAlign,
|
|
793
|
-
display: cs.display,
|
|
794
|
-
overflow: cs.overflow,
|
|
795
|
-
flexDirection: cs.flexDirection,
|
|
796
|
-
justifyContent: cs.justifyContent,
|
|
797
|
-
alignItems: cs.alignItems,
|
|
798
|
-
alignSelf: cs.alignSelf,
|
|
799
|
-
flexGrow: cs.flexGrow,
|
|
800
|
-
flexShrink: cs.flexShrink,
|
|
801
|
-
flexBasis: cs.flexBasis,
|
|
802
|
-
order: cs.order,
|
|
803
|
-
gridColumn: cs.gridColumn,
|
|
804
|
-
gridRow: cs.gridRow,
|
|
805
|
-
position: cs.position,
|
|
806
|
-
top: cs.top,
|
|
807
|
-
right: cs.right,
|
|
808
|
-
bottom: cs.bottom,
|
|
809
|
-
left: cs.left,
|
|
810
|
-
gap: cs.gap,
|
|
811
|
-
width: cs.width,
|
|
812
|
-
height: cs.height,
|
|
813
|
-
opacity: cs.opacity,
|
|
814
|
-
paddingTop: cs.paddingTop,
|
|
815
|
-
paddingRight: cs.paddingRight,
|
|
816
|
-
paddingBottom: cs.paddingBottom,
|
|
817
|
-
paddingLeft: cs.paddingLeft,
|
|
818
|
-
marginTop: cs.marginTop,
|
|
819
|
-
marginRight: cs.marginRight,
|
|
820
|
-
marginBottom: cs.marginBottom,
|
|
821
|
-
marginLeft: cs.marginLeft,
|
|
822
|
-
borderWidth: cs.borderWidth,
|
|
823
|
-
borderStyle: cs.borderStyle,
|
|
824
|
-
borderColor: cs.borderColor,
|
|
825
|
-
borderRadius: cs.borderRadius,
|
|
826
|
-
borderTopLeftRadius: cs.borderTopLeftRadius,
|
|
827
|
-
borderTopRightRadius: cs.borderTopRightRadius,
|
|
828
|
-
borderBottomRightRadius: cs.borderBottomRightRadius,
|
|
829
|
-
borderBottomLeftRadius: cs.borderBottomLeftRadius,
|
|
830
|
-
outlineWidth: cs.outlineWidth,
|
|
831
|
-
outlineStyle: cs.outlineStyle,
|
|
832
|
-
outlineColor: cs.outlineColor,
|
|
833
|
-
outlineOffset: cs.outlineOffset,
|
|
834
|
-
boxShadow: cs.boxShadow,
|
|
835
|
-
textShadow: cs.textShadow,
|
|
836
|
-
filter: cs.filter,
|
|
837
|
-
mixBlendMode: cs.mixBlendMode,
|
|
838
|
-
zIndex: cs.zIndex,
|
|
839
|
-
},
|
|
840
|
-
boundingRect: { x: rect.x + (window.scrollX || window.pageXOffset || 0), y: rect.y + (window.scrollY || window.pageYOffset || 0), width: rect.width, height: rect.height },
|
|
841
|
-
textContent: el.textContent ? el.textContent.slice(0, 200) : undefined,
|
|
842
|
-
htmlContent: el.innerHTML && el.innerHTML !== el.textContent ? el.innerHTML.slice(0, 4000) : undefined,
|
|
843
|
-
isFlexContainer: cs.display === 'flex' || cs.display === 'inline-flex',
|
|
844
|
-
isFlexChild: parentDisplay === 'flex' || parentDisplay === 'inline-flex',
|
|
845
|
-
parentDisplay: parentDisplay,
|
|
846
|
-
parentLayout: parentLayout,
|
|
847
|
-
editCapabilities: capabilities,
|
|
848
|
-
confidence: capabilities.reduce(function(best, item) {
|
|
849
|
-
return Math.max(best, item.confidence || 0);
|
|
850
|
-
}, 0),
|
|
851
|
-
provenance: provenance,
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
var shieldOverlay = document.createElement('div');
|
|
856
|
-
shieldOverlay.setAttribute('data-agent-native-edit-overlay', 'shield');
|
|
857
|
-
shieldOverlay.style.cssText = 'position:fixed;inset:0;z-index:99990;background:transparent;pointer-events:auto;touch-action:none;cursor:default;';
|
|
858
|
-
document.body.appendChild(shieldOverlay);
|
|
859
|
-
|
|
860
|
-
var highlightOverlay = document.createElement('div');
|
|
861
|
-
highlightOverlay.setAttribute('data-agent-native-edit-overlay', 'highlight');
|
|
862
|
-
highlightOverlay.style.cssText = 'position:fixed;pointer-events:none;z-index:99997;border:1.5px solid var(--design-editor-accent-color);background:transparent;display:none;box-sizing:border-box;';
|
|
863
|
-
document.body.appendChild(highlightOverlay);
|
|
864
|
-
|
|
865
|
-
var selectionOverlay = document.createElement('div');
|
|
866
|
-
selectionOverlay.setAttribute('data-agent-native-edit-overlay', 'selection');
|
|
867
|
-
selectionOverlay.style.cssText = 'position:fixed;pointer-events:none;z-index:99998;border:1.5px solid var(--design-editor-accent-color);background:transparent;display:none;box-sizing:border-box;cursor:default;';
|
|
868
|
-
['n','e','s','w'].forEach(function(pos) {
|
|
869
|
-
var edge = document.createElement('span');
|
|
870
|
-
edge.setAttribute('data-agent-native-edge-handle', pos);
|
|
871
|
-
var cursor = pos === 'n' || pos === 's' ? 'ns-resize' : 'ew-resize';
|
|
872
|
-
edge.style.cssText = 'position:absolute;pointer-events:auto;cursor:' + cursor + ';background:transparent;';
|
|
873
|
-
if (pos === 'n') {
|
|
874
|
-
edge.style.left = '0';
|
|
875
|
-
edge.style.right = '0';
|
|
876
|
-
edge.style.top = '-5px';
|
|
877
|
-
edge.style.height = '10px';
|
|
878
|
-
}
|
|
879
|
-
if (pos === 's') {
|
|
880
|
-
edge.style.left = '0';
|
|
881
|
-
edge.style.right = '0';
|
|
882
|
-
edge.style.bottom = '-5px';
|
|
883
|
-
edge.style.height = '10px';
|
|
884
|
-
}
|
|
885
|
-
if (pos === 'e') {
|
|
886
|
-
edge.style.top = '0';
|
|
887
|
-
edge.style.bottom = '0';
|
|
888
|
-
edge.style.right = '-5px';
|
|
889
|
-
edge.style.width = '10px';
|
|
890
|
-
}
|
|
891
|
-
if (pos === 'w') {
|
|
892
|
-
edge.style.top = '0';
|
|
893
|
-
edge.style.bottom = '0';
|
|
894
|
-
edge.style.left = '-5px';
|
|
895
|
-
edge.style.width = '10px';
|
|
896
|
-
}
|
|
897
|
-
selectionOverlay.appendChild(edge);
|
|
898
|
-
});
|
|
899
|
-
['nw','ne','se','sw'].forEach(function(pos) {
|
|
900
|
-
var handle = document.createElement('span');
|
|
901
|
-
handle.setAttribute('data-agent-native-edit-handle', pos);
|
|
902
|
-
var cursor = pos === 'n' || pos === 's' ? 'ns-resize' : pos === 'e' || pos === 'w' ? 'ew-resize' : pos === 'nw' || pos === 'se' ? 'nwse-resize' : 'nesw-resize';
|
|
903
|
-
handle.style.cssText = 'position:absolute;z-index:1;width:7px;height:7px;border:1px solid var(--design-editor-accent-color);background:var(--design-editor-accent-contrast-color);box-sizing:border-box;border-radius:1px;pointer-events:auto;cursor:' + cursor + ';';
|
|
904
|
-
if (pos.indexOf('n') !== -1) handle.style.top = '-4px';
|
|
905
|
-
if (pos.indexOf('s') !== -1) handle.style.bottom = '-4px';
|
|
906
|
-
if (pos.indexOf('w') !== -1) handle.style.left = '-4px';
|
|
907
|
-
if (pos.indexOf('e') !== -1) handle.style.right = '-4px';
|
|
908
|
-
if (pos === 'n' || pos === 's') {
|
|
909
|
-
handle.style.left = '50%';
|
|
910
|
-
handle.style.transform = 'translateX(-50%)';
|
|
911
|
-
}
|
|
912
|
-
if (pos === 'e' || pos === 'w') {
|
|
913
|
-
handle.style.top = '50%';
|
|
914
|
-
handle.style.transform = 'translateY(-50%)';
|
|
915
|
-
}
|
|
916
|
-
selectionOverlay.appendChild(handle);
|
|
917
|
-
});
|
|
918
|
-
['nw','ne','se','sw'].forEach(function(pos) {
|
|
919
|
-
var rotate = document.createElement('span');
|
|
920
|
-
rotate.setAttribute('data-agent-native-rotate-handle', pos);
|
|
921
|
-
rotate.style.cssText = 'position:absolute;width:18px;height:18px;border-radius:999px;pointer-events:auto;cursor:grab;';
|
|
922
|
-
if (pos.indexOf('n') !== -1) rotate.style.top = '-26px';
|
|
923
|
-
if (pos.indexOf('s') !== -1) rotate.style.bottom = '-26px';
|
|
924
|
-
if (pos.indexOf('w') !== -1) rotate.style.left = '-26px';
|
|
925
|
-
if (pos.indexOf('e') !== -1) rotate.style.right = '-26px';
|
|
926
|
-
selectionOverlay.appendChild(rotate);
|
|
927
|
-
});
|
|
928
|
-
var spacingOverlay = document.createElement('div');
|
|
929
|
-
spacingOverlay.setAttribute('data-agent-native-spacing-overlay', '');
|
|
930
|
-
spacingOverlay.style.cssText = 'position:absolute;inset:0;display:none;pointer-events:none;';
|
|
931
|
-
selectionOverlay.appendChild(spacingOverlay);
|
|
932
|
-
document.body.appendChild(selectionOverlay);
|
|
933
|
-
|
|
934
|
-
var transformBadge = document.createElement('div');
|
|
935
|
-
transformBadge.setAttribute('data-agent-native-transform-badge', '');
|
|
936
|
-
transformBadge.style.cssText = 'position:fixed;z-index:100000;display:none;pointer-events:none;border:1px solid hsl(var(--border));border-radius:4px;background:hsl(var(--background) / 0.96);color:hsl(var(--foreground));font:11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;padding:3px 5px;box-shadow:0 8px 20px color-mix(in srgb, hsl(var(--foreground)) 16%, transparent);';
|
|
937
|
-
document.body.appendChild(transformBadge);
|
|
938
|
-
|
|
939
|
-
var spacingBadge = document.createElement('div');
|
|
940
|
-
spacingBadge.setAttribute('data-agent-native-spacing-badge', '');
|
|
941
|
-
spacingBadge.style.cssText = 'position:fixed;z-index:100000;display:none;pointer-events:none;border-radius:3px;color:white;font:10px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:700;padding:2px 4px;box-shadow:0 4px 14px rgba(0,0,0,0.18);';
|
|
942
|
-
document.body.appendChild(spacingBadge);
|
|
943
|
-
|
|
944
|
-
var insertionGuide = document.createElement('div');
|
|
945
|
-
insertionGuide.setAttribute('data-agent-native-insertion-guide', '');
|
|
946
|
-
insertionGuide.style.cssText = 'position:fixed;z-index:100000;display:none;pointer-events:none;background:var(--design-editor-accent-color);border-radius:999px;box-shadow:0 0 0 1px var(--design-editor-accent-color);';
|
|
947
|
-
document.body.appendChild(insertionGuide);
|
|
948
|
-
|
|
949
|
-
var measurementOverlay = document.createElement('div');
|
|
950
|
-
measurementOverlay.setAttribute('data-agent-native-measurement-overlay', '');
|
|
951
|
-
measurementOverlay.style.cssText = 'position:fixed;inset:0;z-index:100001;display:none;pointer-events:none;color:var(--design-editor-measure-color);font:11px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;';
|
|
952
|
-
document.body.appendChild(measurementOverlay);
|
|
953
|
-
|
|
954
|
-
// Component-instance tag: a small pill that floats above the selection
|
|
955
|
-
// outline whenever the selected element carries a data-agent-native-component
|
|
956
|
-
// attribute. Clicking it sends a 'component-source-jump' message to the
|
|
957
|
-
// parent so the editor can invoke open-component-source.
|
|
958
|
-
var componentTagOverlay = document.createElement('div');
|
|
959
|
-
componentTagOverlay.setAttribute('data-agent-native-edit-overlay', 'component-tag');
|
|
960
|
-
componentTagOverlay.style.cssText = [
|
|
961
|
-
'position:fixed',
|
|
962
|
-
'z-index:100002',
|
|
963
|
-
'display:none',
|
|
964
|
-
'pointer-events:auto',
|
|
965
|
-
'cursor:pointer',
|
|
966
|
-
'padding:2px 6px',
|
|
967
|
-
'border-radius:4px',
|
|
968
|
-
'font:11px/1.6 ui-sans-serif,system-ui,sans-serif',
|
|
969
|
-
'white-space:nowrap',
|
|
970
|
-
'user-select:none',
|
|
971
|
-
'-webkit-user-select:none',
|
|
972
|
-
'background:var(--design-editor-accent-color)',
|
|
973
|
-
'color:var(--design-editor-accent-contrast-color)',
|
|
974
|
-
'box-shadow:0 1px 4px color-mix(in srgb,var(--design-editor-accent-color) 40%,transparent)',
|
|
975
|
-
'border:1px solid color-mix(in srgb,var(--design-editor-accent-strong-color) 60%,transparent)',
|
|
976
|
-
'outline:2px solid transparent',
|
|
977
|
-
'transition:opacity 0.1s',
|
|
978
|
-
].join(';') + ';';
|
|
979
|
-
document.body.appendChild(componentTagOverlay);
|
|
980
|
-
|
|
981
|
-
componentTagOverlay.addEventListener('click', function(e) {
|
|
982
|
-
e.stopPropagation();
|
|
983
|
-
e.preventDefault();
|
|
984
|
-
var nodeId = componentTagOverlay.getAttribute('data-component-node-id') || '';
|
|
985
|
-
var componentName = componentTagOverlay.getAttribute('data-component-name') || '';
|
|
986
|
-
if (!nodeId || !componentName) return;
|
|
987
|
-
try {
|
|
988
|
-
window.parent.postMessage({
|
|
989
|
-
type: 'component-source-jump',
|
|
990
|
-
nodeId: nodeId,
|
|
991
|
-
componentName: componentName,
|
|
992
|
-
}, '*');
|
|
993
|
-
} catch (_err) {}
|
|
994
|
-
});
|
|
995
|
-
|
|
996
|
-
function updateComponentTag(el) {
|
|
997
|
-
if (!el) {
|
|
998
|
-
clearComponentTag();
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
var compName = el.getAttribute && el.getAttribute('data-agent-native-component');
|
|
1002
|
-
if (!compName) {
|
|
1003
|
-
clearComponentTag();
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
var nodeId = (
|
|
1007
|
-
el.getAttribute('data-agent-native-node-id') ||
|
|
1008
|
-
el.getAttribute('data-code-layer-id') ||
|
|
1009
|
-
el.getAttribute('data-layer-id') ||
|
|
1010
|
-
el.id ||
|
|
1011
|
-
''
|
|
1012
|
-
);
|
|
1013
|
-
componentTagOverlay.textContent = compName + ' →';
|
|
1014
|
-
componentTagOverlay.setAttribute('data-component-node-id', nodeId);
|
|
1015
|
-
componentTagOverlay.setAttribute('data-component-name', compName);
|
|
1016
|
-
|
|
1017
|
-
var rect = el.getBoundingClientRect();
|
|
1018
|
-
var tagHeight = 22;
|
|
1019
|
-
var tagTop = rect.top - tagHeight - 4;
|
|
1020
|
-
if (tagTop < 4) tagTop = rect.top + 4;
|
|
1021
|
-
componentTagOverlay.style.display = 'block';
|
|
1022
|
-
componentTagOverlay.style.left = rect.left + 'px';
|
|
1023
|
-
componentTagOverlay.style.top = tagTop + 'px';
|
|
1024
|
-
// Accent outline on the selection overlay to distinguish component roots.
|
|
1025
|
-
selectionOverlay.style.outline = '2px solid var(--design-editor-accent-strong-color)';
|
|
1026
|
-
selectionOverlay.style.outlineOffset = '2px';
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
function clearComponentTag() {
|
|
1030
|
-
componentTagOverlay.style.display = 'none';
|
|
1031
|
-
componentTagOverlay.removeAttribute('data-component-node-id');
|
|
1032
|
-
componentTagOverlay.removeAttribute('data-component-name');
|
|
1033
|
-
selectionOverlay.style.outline = '';
|
|
1034
|
-
selectionOverlay.style.outlineOffset = '';
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
var selectedEl = null;
|
|
1038
|
-
var hoveredEl = null;
|
|
1039
|
-
var activeTextEditEl = null;
|
|
1040
|
-
var textEditPointerState = null;
|
|
1041
|
-
var pendingStructureMove = null;
|
|
1042
|
-
var pendingShieldDrag = null;
|
|
1043
|
-
var suppressNextShieldClick = false;
|
|
1044
|
-
var suppressNextShieldClickTimer = null;
|
|
1045
|
-
var selectedSpacingHovered = false;
|
|
1046
|
-
var hoveredSpacingHandleKey = '';
|
|
1047
|
-
var spacingHandleStateByKey = {};
|
|
1048
|
-
var spacingHandleNodesByKey = {};
|
|
1049
|
-
var spacingDrag = null;
|
|
1050
|
-
var lockedSelectors = [];
|
|
1051
|
-
var hiddenSelectors = [];
|
|
1052
|
-
|
|
1053
|
-
function clearRuntimeSelection() {
|
|
1054
|
-
selectedEl = null;
|
|
1055
|
-
hoveredEl = null;
|
|
1056
|
-
selectedSpacingHovered = false;
|
|
1057
|
-
hoveredSpacingHandleKey = '';
|
|
1058
|
-
spacingDrag = null;
|
|
1059
|
-
selectionOverlay.style.display = 'none';
|
|
1060
|
-
highlightOverlay.style.display = 'none';
|
|
1061
|
-
hideSpacingOverlay();
|
|
1062
|
-
hideMeasurements();
|
|
1063
|
-
clearComponentTag();
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
function matchesSelectorList(el, selectors) {
|
|
1067
|
-
if (!el || !selectors || selectors.length === 0) return false;
|
|
1068
|
-
for (var i = 0; i < selectors.length; i += 1) {
|
|
1069
|
-
try {
|
|
1070
|
-
if (el.matches(selectors[i]) || el.closest(selectors[i])) return true;
|
|
1071
|
-
} catch (_err) {}
|
|
1072
|
-
}
|
|
1073
|
-
return false;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
function matchesExactSelectorList(el, selectors) {
|
|
1077
|
-
if (!el || !selectors || selectors.length === 0) return false;
|
|
1078
|
-
for (var i = 0; i < selectors.length; i += 1) {
|
|
1079
|
-
try {
|
|
1080
|
-
if (el.matches(selectors[i])) return true;
|
|
1081
|
-
} catch (_err) {}
|
|
1082
|
-
}
|
|
1083
|
-
return false;
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
function isLayerInteractionBlocked(el) {
|
|
1087
|
-
if (!el) return false;
|
|
1088
|
-
if (el.closest && el.closest('[data-agent-native-locked="true"], [data-agent-native-hidden="true"]')) {
|
|
1089
|
-
return true;
|
|
1090
|
-
}
|
|
1091
|
-
return matchesSelectorList(el, lockedSelectors) || matchesSelectorList(el, hiddenSelectors);
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
function applyHiddenSelectors() {
|
|
1095
|
-
document.querySelectorAll('[data-agent-native-runtime-hidden]').forEach(function(el) {
|
|
1096
|
-
var previous = el.getAttribute('data-agent-native-previous-display');
|
|
1097
|
-
if (previous === null) {
|
|
1098
|
-
el.style.removeProperty('display');
|
|
1099
|
-
} else {
|
|
1100
|
-
el.style.display = previous;
|
|
1101
|
-
}
|
|
1102
|
-
el.removeAttribute('data-agent-native-runtime-hidden');
|
|
1103
|
-
el.removeAttribute('data-agent-native-previous-display');
|
|
1104
|
-
});
|
|
1105
|
-
hiddenSelectors.forEach(function(selector) {
|
|
1106
|
-
try {
|
|
1107
|
-
document.querySelectorAll(selector).forEach(function(el) {
|
|
1108
|
-
if (!el.hasAttribute('data-agent-native-runtime-hidden')) {
|
|
1109
|
-
el.setAttribute('data-agent-native-previous-display', el.style.display || '');
|
|
1110
|
-
}
|
|
1111
|
-
el.setAttribute('data-agent-native-runtime-hidden', 'true');
|
|
1112
|
-
el.style.display = 'none';
|
|
1113
|
-
});
|
|
1114
|
-
} catch (_err) {}
|
|
1115
|
-
});
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
function replaceRuntimeDocument(html, preferredSelector, selectorCandidates) {
|
|
1119
|
-
if (typeof html !== 'string') return;
|
|
1120
|
-
if (activeTextEditEl) {
|
|
1121
|
-
applyHiddenSelectors();
|
|
1122
|
-
refreshOverlays();
|
|
1123
|
-
return;
|
|
1124
|
-
}
|
|
1125
|
-
var parser = new DOMParser();
|
|
1126
|
-
var nextDoc = parser.parseFromString(html, 'text/html');
|
|
1127
|
-
if (!nextDoc || !nextDoc.body) return;
|
|
1128
|
-
|
|
1129
|
-
var persistentNodes = Array.prototype.slice.call(
|
|
1130
|
-
document.querySelectorAll('[data-agent-native-edit-overlay]'),
|
|
1131
|
-
);
|
|
1132
|
-
var activeSelector = preferredSelector || (selectedEl ? getSelector(selectedEl) : '');
|
|
1133
|
-
var activeCandidates = [];
|
|
1134
|
-
if (Array.isArray(selectorCandidates)) {
|
|
1135
|
-
selectorCandidates.forEach(function(selector) {
|
|
1136
|
-
if (typeof selector === 'string' && selector && activeCandidates.indexOf(selector) === -1) {
|
|
1137
|
-
activeCandidates.push(selector);
|
|
1138
|
-
}
|
|
1139
|
-
});
|
|
1140
|
-
}
|
|
1141
|
-
if (activeSelector && activeCandidates.indexOf(activeSelector) === -1) {
|
|
1142
|
-
activeCandidates.push(activeSelector);
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
var nextHeadHtml = nextDoc.head ? nextDoc.head.innerHTML : '';
|
|
1146
|
-
if (nextHeadHtml === document.head.innerHTML && activeCandidates.length > 0) {
|
|
1147
|
-
var currentMatch = null;
|
|
1148
|
-
var nextMatch = null;
|
|
1149
|
-
var matchedSelector = '';
|
|
1150
|
-
var fallbackCurrentMatch = null;
|
|
1151
|
-
var fallbackSelector = '';
|
|
1152
|
-
for (var matchIndex = 0; matchIndex < activeCandidates.length; matchIndex += 1) {
|
|
1153
|
-
try {
|
|
1154
|
-
var currentCandidate = document.querySelector(activeCandidates[matchIndex]);
|
|
1155
|
-
var nextCandidate = nextDoc.querySelector(activeCandidates[matchIndex]);
|
|
1156
|
-
if (currentCandidate && !fallbackCurrentMatch) {
|
|
1157
|
-
fallbackCurrentMatch = currentCandidate;
|
|
1158
|
-
fallbackSelector = activeCandidates[matchIndex];
|
|
1159
|
-
}
|
|
1160
|
-
if (currentCandidate && nextCandidate) {
|
|
1161
|
-
currentMatch = currentCandidate;
|
|
1162
|
-
nextMatch = nextCandidate;
|
|
1163
|
-
matchedSelector = activeCandidates[matchIndex];
|
|
1164
|
-
break;
|
|
1165
|
-
}
|
|
1166
|
-
} catch (_err) {
|
|
1167
|
-
// Keep trying later aliases; bridge selectors can differ between
|
|
1168
|
-
// runtime and DOMParser passes.
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
if (!currentMatch && fallbackCurrentMatch) {
|
|
1172
|
-
currentMatch = fallbackCurrentMatch;
|
|
1173
|
-
matchedSelector = fallbackSelector;
|
|
1174
|
-
}
|
|
1175
|
-
if (
|
|
1176
|
-
currentMatch &&
|
|
1177
|
-
currentMatch !== document.body &&
|
|
1178
|
-
currentMatch !== document.documentElement &&
|
|
1179
|
-
!isOverlayElement(currentMatch)
|
|
1180
|
-
) {
|
|
1181
|
-
if (nextMatch) {
|
|
1182
|
-
currentMatch.replaceWith(document.importNode(nextMatch, true));
|
|
1183
|
-
} else if (currentMatch !== document.body && currentMatch !== document.documentElement) {
|
|
1184
|
-
currentMatch.parentElement && currentMatch.parentElement.removeChild(currentMatch);
|
|
1185
|
-
}
|
|
1186
|
-
applyHiddenSelectors();
|
|
1187
|
-
selectedEl = null;
|
|
1188
|
-
if (nextMatch) {
|
|
1189
|
-
try {
|
|
1190
|
-
selectedEl = document.querySelector(matchedSelector);
|
|
1191
|
-
} catch (_err) {}
|
|
1192
|
-
}
|
|
1193
|
-
hoveredEl = null;
|
|
1194
|
-
if (selectedEl && !isLayerInteractionBlocked(selectedEl)) {
|
|
1195
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
1196
|
-
window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
|
|
1197
|
-
} else {
|
|
1198
|
-
selectionOverlay.style.display = 'none';
|
|
1199
|
-
}
|
|
1200
|
-
highlightOverlay.style.display = 'none';
|
|
1201
|
-
hideMeasurements();
|
|
1202
|
-
refreshOverlays();
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
if (document.head.innerHTML !== nextHeadHtml) {
|
|
1207
|
-
document.head.innerHTML = nextHeadHtml;
|
|
1208
|
-
}
|
|
1209
|
-
Array.prototype.slice.call(document.body.attributes).forEach(function(attribute) {
|
|
1210
|
-
document.body.removeAttribute(attribute.name);
|
|
1211
|
-
});
|
|
1212
|
-
Array.prototype.slice.call(nextDoc.body.attributes).forEach(function(attribute) {
|
|
1213
|
-
document.body.setAttribute(attribute.name, attribute.value);
|
|
1214
|
-
});
|
|
1215
|
-
document.body.innerHTML = nextDoc.body.innerHTML;
|
|
1216
|
-
persistentNodes.forEach(function(node) {
|
|
1217
|
-
document.body.appendChild(node);
|
|
1218
|
-
});
|
|
1219
|
-
applyHiddenSelectors();
|
|
1220
|
-
|
|
1221
|
-
selectedEl = null;
|
|
1222
|
-
hoveredEl = null;
|
|
1223
|
-
for (var i = 0; i < activeCandidates.length && !selectedEl; i += 1) {
|
|
1224
|
-
try {
|
|
1225
|
-
var match = document.querySelector(activeCandidates[i]);
|
|
1226
|
-
// Skip the editor's own injected overlay chrome and re-anchor to a
|
|
1227
|
-
// source-backed element. A stale positional candidate like
|
|
1228
|
-
// body > div:nth-of-type(6) can otherwise re-match an overlay div
|
|
1229
|
-
// (the only direct div children of body at runtime), which then has
|
|
1230
|
-
// no code-layer node and fails every edit.
|
|
1231
|
-
if (match && !isLayerInteractionBlocked(match) && !isOverlayElement(match)) {
|
|
1232
|
-
selectedEl = selectionTargetForHit(match) || match;
|
|
1233
|
-
}
|
|
1234
|
-
} catch (_err) {}
|
|
1235
|
-
}
|
|
1236
|
-
if (selectedEl) {
|
|
1237
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
1238
|
-
window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
|
|
1239
|
-
} else {
|
|
1240
|
-
selectionOverlay.style.display = 'none';
|
|
1241
|
-
}
|
|
1242
|
-
highlightOverlay.style.display = 'none';
|
|
1243
|
-
hideMeasurements();
|
|
1244
|
-
refreshOverlays();
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
function hideSpacingOverlay() {
|
|
1248
|
-
spacingOverlay.style.display = 'none';
|
|
1249
|
-
spacingOverlay.innerHTML = '';
|
|
1250
|
-
spacingHandleStateByKey = {};
|
|
1251
|
-
spacingHandleNodesByKey = {};
|
|
1252
|
-
if (!spacingDrag) spacingBadge.style.display = 'none';
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
function visibleLayoutChildren(el) {
|
|
1256
|
-
if (!el || !el.children) return [];
|
|
1257
|
-
return Array.prototype.slice.call(el.children).filter(function(child) {
|
|
1258
|
-
if (!child || child.nodeType !== 1 || isOverlayElement(child) || isLayerInteractionBlocked(child)) return false;
|
|
1259
|
-
var cs = window.getComputedStyle(child);
|
|
1260
|
-
if (cs.display === 'none' || cs.visibility === 'hidden' || cs.position === 'fixed') return false;
|
|
1261
|
-
var rect = child.getBoundingClientRect();
|
|
1262
|
-
return rect.width > 0 && rect.height > 0;
|
|
1263
|
-
});
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
function spacingColor(kind) {
|
|
1267
|
-
return kind === 'gap' ? '#ff4fd8' : 'var(--design-editor-accent-color)';
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
function spacingFill(kind, orientation) {
|
|
1271
|
-
var tint = kind === 'gap' ? 'rgba(255, 79, 216, 0.28)' : 'rgba(46, 168, 255, 0.24)';
|
|
1272
|
-
var stripe = kind === 'gap' ? 'rgba(255, 79, 216, 0.58)' : 'rgba(46, 168, 255, 0.52)';
|
|
1273
|
-
var angle = orientation === 'vertical' ? '135deg' : '45deg';
|
|
1274
|
-
return 'repeating-linear-gradient(' + angle + ', ' + stripe + ' 0 1px, ' + tint + ' 1px 4px, transparent 4px 7px)';
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
function clampSpacingValue(value) {
|
|
1278
|
-
var rounded = Math.round(value);
|
|
1279
|
-
if (!Number.isFinite(rounded)) return 0;
|
|
1280
|
-
return Math.max(0, Math.min(999, rounded));
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
function makeSpacingHandle(config) {
|
|
1284
|
-
var region = config.region;
|
|
1285
|
-
if (!region || region.width <= 0 || region.height <= 0) return null;
|
|
1286
|
-
return {
|
|
1287
|
-
key: config.key,
|
|
1288
|
-
groupKey: config.groupKey || config.key,
|
|
1289
|
-
kind: config.kind,
|
|
1290
|
-
property: config.property,
|
|
1291
|
-
oppositeProperty: config.oppositeProperty || '',
|
|
1292
|
-
side: config.side || '',
|
|
1293
|
-
orientation: config.orientation,
|
|
1294
|
-
value: clampSpacingValue(config.value),
|
|
1295
|
-
region: {
|
|
1296
|
-
x: Math.round(region.x),
|
|
1297
|
-
y: Math.round(region.y),
|
|
1298
|
-
width: Math.max(1, Math.round(region.width)),
|
|
1299
|
-
height: Math.max(1, Math.round(region.height)),
|
|
1300
|
-
},
|
|
1301
|
-
line: config.line,
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
function childLocalRect(child, containerRect) {
|
|
1306
|
-
var rect = child.getBoundingClientRect();
|
|
1307
|
-
return {
|
|
1308
|
-
left: rect.left - containerRect.left,
|
|
1309
|
-
top: rect.top - containerRect.top,
|
|
1310
|
-
right: rect.right - containerRect.left,
|
|
1311
|
-
bottom: rect.bottom - containerRect.top,
|
|
1312
|
-
width: rect.width,
|
|
1313
|
-
height: rect.height,
|
|
1314
|
-
};
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
function childRectsOverlap(a, b, axis) {
|
|
1318
|
-
if (axis === 'x') {
|
|
1319
|
-
return Math.max(a.top, b.top) < Math.min(a.bottom, b.bottom);
|
|
1320
|
-
}
|
|
1321
|
-
return Math.max(a.left, b.left) < Math.min(a.right, b.right);
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
function buildPaddingSpacingHandles(el, rect, cs) {
|
|
1325
|
-
var handles = [];
|
|
1326
|
-
var borderTop = readPx(cs.borderTopWidth);
|
|
1327
|
-
var borderRight = readPx(cs.borderRightWidth);
|
|
1328
|
-
var borderBottom = readPx(cs.borderBottomWidth);
|
|
1329
|
-
var borderLeft = readPx(cs.borderLeftWidth);
|
|
1330
|
-
var paddingTop = readPx(cs.paddingTop);
|
|
1331
|
-
var paddingRight = readPx(cs.paddingRight);
|
|
1332
|
-
var paddingBottom = readPx(cs.paddingBottom);
|
|
1333
|
-
var paddingLeft = readPx(cs.paddingLeft);
|
|
1334
|
-
var sx = chromeScaleX();
|
|
1335
|
-
var sy = chromeScaleY();
|
|
1336
|
-
var line = Math.max(1, chromeLineScale());
|
|
1337
|
-
var hLineWidth = Math.max(8, Math.min(24, rect.width * 0.18)) * sx;
|
|
1338
|
-
var vLineHeight = Math.max(8, Math.min(24, rect.height * 0.18)) * sy;
|
|
1339
|
-
var innerLeft = borderLeft;
|
|
1340
|
-
var innerTop = borderTop;
|
|
1341
|
-
var innerWidth = Math.max(1, rect.width - borderLeft - borderRight);
|
|
1342
|
-
var innerHeight = Math.max(1, rect.height - borderTop - borderBottom);
|
|
1343
|
-
if (paddingTop > 0) {
|
|
1344
|
-
handles.push(makeSpacingHandle({
|
|
1345
|
-
key: 'padding:top',
|
|
1346
|
-
kind: 'padding',
|
|
1347
|
-
property: 'paddingTop',
|
|
1348
|
-
oppositeProperty: 'paddingBottom',
|
|
1349
|
-
side: 'top',
|
|
1350
|
-
orientation: 'horizontal',
|
|
1351
|
-
value: paddingTop,
|
|
1352
|
-
region: { x: innerLeft, y: innerTop, width: innerWidth, height: paddingTop },
|
|
1353
|
-
line: {
|
|
1354
|
-
x: rect.width / 2 - hLineWidth / 2,
|
|
1355
|
-
y: innerTop + paddingTop / 2 - line / 2,
|
|
1356
|
-
width: hLineWidth,
|
|
1357
|
-
height: line,
|
|
1358
|
-
},
|
|
1359
|
-
}));
|
|
1360
|
-
}
|
|
1361
|
-
if (paddingBottom > 0) {
|
|
1362
|
-
handles.push(makeSpacingHandle({
|
|
1363
|
-
key: 'padding:bottom',
|
|
1364
|
-
kind: 'padding',
|
|
1365
|
-
property: 'paddingBottom',
|
|
1366
|
-
oppositeProperty: 'paddingTop',
|
|
1367
|
-
side: 'bottom',
|
|
1368
|
-
orientation: 'horizontal',
|
|
1369
|
-
value: paddingBottom,
|
|
1370
|
-
region: { x: innerLeft, y: rect.height - borderBottom - paddingBottom, width: innerWidth, height: paddingBottom },
|
|
1371
|
-
line: {
|
|
1372
|
-
x: rect.width / 2 - hLineWidth / 2,
|
|
1373
|
-
y: rect.height - borderBottom - paddingBottom / 2 - line / 2,
|
|
1374
|
-
width: hLineWidth,
|
|
1375
|
-
height: line,
|
|
1376
|
-
},
|
|
1377
|
-
}));
|
|
1378
|
-
}
|
|
1379
|
-
if (paddingLeft > 0) {
|
|
1380
|
-
handles.push(makeSpacingHandle({
|
|
1381
|
-
key: 'padding:left',
|
|
1382
|
-
kind: 'padding',
|
|
1383
|
-
property: 'paddingLeft',
|
|
1384
|
-
oppositeProperty: 'paddingRight',
|
|
1385
|
-
side: 'left',
|
|
1386
|
-
orientation: 'vertical',
|
|
1387
|
-
value: paddingLeft,
|
|
1388
|
-
region: { x: innerLeft, y: innerTop, width: paddingLeft, height: innerHeight },
|
|
1389
|
-
line: {
|
|
1390
|
-
x: innerLeft + paddingLeft / 2 - line / 2,
|
|
1391
|
-
y: rect.height / 2 - vLineHeight / 2,
|
|
1392
|
-
width: line,
|
|
1393
|
-
height: vLineHeight,
|
|
1394
|
-
},
|
|
1395
|
-
}));
|
|
1396
|
-
}
|
|
1397
|
-
if (paddingRight > 0) {
|
|
1398
|
-
handles.push(makeSpacingHandle({
|
|
1399
|
-
key: 'padding:right',
|
|
1400
|
-
kind: 'padding',
|
|
1401
|
-
property: 'paddingRight',
|
|
1402
|
-
oppositeProperty: 'paddingLeft',
|
|
1403
|
-
side: 'right',
|
|
1404
|
-
orientation: 'vertical',
|
|
1405
|
-
value: paddingRight,
|
|
1406
|
-
region: { x: rect.width - borderRight - paddingRight, y: innerTop, width: paddingRight, height: innerHeight },
|
|
1407
|
-
line: {
|
|
1408
|
-
x: rect.width - borderRight - paddingRight / 2 - line / 2,
|
|
1409
|
-
y: rect.height / 2 - vLineHeight / 2,
|
|
1410
|
-
width: line,
|
|
1411
|
-
height: vLineHeight,
|
|
1412
|
-
},
|
|
1413
|
-
}));
|
|
1414
|
-
}
|
|
1415
|
-
return handles.filter(Boolean);
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
function buildGapSpacingHandles(el, rect, cs) {
|
|
1419
|
-
var children = visibleLayoutChildren(el);
|
|
1420
|
-
if (children.length < 2) return [];
|
|
1421
|
-
var handles = [];
|
|
1422
|
-
var sx = chromeScaleX();
|
|
1423
|
-
var sy = chromeScaleY();
|
|
1424
|
-
var line = Math.max(1, chromeLineScale());
|
|
1425
|
-
var hLineWidth = 10 * sx;
|
|
1426
|
-
var vLineHeight = 10 * sy;
|
|
1427
|
-
var isFlex = cs.display === 'flex' || cs.display === 'inline-flex';
|
|
1428
|
-
var isGrid = cs.display === 'grid' || cs.display === 'inline-grid';
|
|
1429
|
-
if (!isFlex && !isGrid) return handles;
|
|
1430
|
-
var primaryAxis = isFlex && cs.flexDirection && cs.flexDirection.indexOf('column') === 0 ? 'y' : 'x';
|
|
1431
|
-
var childRects = children.map(function(child) {
|
|
1432
|
-
return childLocalRect(child, rect);
|
|
1433
|
-
});
|
|
1434
|
-
|
|
1435
|
-
function addAxisGaps(axis, property, groupKey) {
|
|
1436
|
-
var cssGap = readPx(cs[property]);
|
|
1437
|
-
if (cssGap <= 0) return;
|
|
1438
|
-
var sorted = childRects.slice().sort(function(a, b) {
|
|
1439
|
-
return axis === 'x' ? a.left - b.left : a.top - b.top;
|
|
1440
|
-
});
|
|
1441
|
-
var count = 0;
|
|
1442
|
-
for (var i = 0; i < sorted.length - 1; i += 1) {
|
|
1443
|
-
var a = sorted[i];
|
|
1444
|
-
var b = sorted[i + 1];
|
|
1445
|
-
if (!childRectsOverlap(a, b, axis)) continue;
|
|
1446
|
-
var gap = axis === 'x' ? b.left - a.right : b.top - a.bottom;
|
|
1447
|
-
if (gap <= 1) continue;
|
|
1448
|
-
if (axis === 'x') {
|
|
1449
|
-
var top = Math.max(a.top, b.top);
|
|
1450
|
-
var bottom = Math.min(a.bottom, b.bottom);
|
|
1451
|
-
var height = Math.max(1, bottom - top);
|
|
1452
|
-
handles.push(makeSpacingHandle({
|
|
1453
|
-
key: groupKey + ':' + count,
|
|
1454
|
-
groupKey: groupKey,
|
|
1455
|
-
kind: 'gap',
|
|
1456
|
-
property: property,
|
|
1457
|
-
orientation: 'vertical',
|
|
1458
|
-
value: cssGap,
|
|
1459
|
-
region: { x: a.right, y: top, width: gap, height: height },
|
|
1460
|
-
line: {
|
|
1461
|
-
x: a.right + gap / 2 - line / 2,
|
|
1462
|
-
y: top + height / 2 - vLineHeight / 2,
|
|
1463
|
-
width: line,
|
|
1464
|
-
height: vLineHeight,
|
|
1465
|
-
},
|
|
1466
|
-
}));
|
|
1467
|
-
} else {
|
|
1468
|
-
var left = Math.max(a.left, b.left);
|
|
1469
|
-
var right = Math.min(a.right, b.right);
|
|
1470
|
-
var width = Math.max(1, right - left);
|
|
1471
|
-
handles.push(makeSpacingHandle({
|
|
1472
|
-
key: groupKey + ':' + count,
|
|
1473
|
-
groupKey: groupKey,
|
|
1474
|
-
kind: 'gap',
|
|
1475
|
-
property: property,
|
|
1476
|
-
orientation: 'horizontal',
|
|
1477
|
-
value: cssGap,
|
|
1478
|
-
region: { x: left, y: a.bottom, width: width, height: gap },
|
|
1479
|
-
line: {
|
|
1480
|
-
x: left + width / 2 - hLineWidth / 2,
|
|
1481
|
-
y: a.bottom + gap / 2 - line / 2,
|
|
1482
|
-
width: hLineWidth,
|
|
1483
|
-
height: line,
|
|
1484
|
-
},
|
|
1485
|
-
}));
|
|
1486
|
-
}
|
|
1487
|
-
count += 1;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
if (primaryAxis === 'x') {
|
|
1492
|
-
addAxisGaps('x', 'columnGap', 'gap:column');
|
|
1493
|
-
if (isGrid) addAxisGaps('y', 'rowGap', 'gap:row');
|
|
1494
|
-
} else {
|
|
1495
|
-
addAxisGaps('y', 'rowGap', 'gap:row');
|
|
1496
|
-
if (isGrid) addAxisGaps('x', 'columnGap', 'gap:column');
|
|
1497
|
-
}
|
|
1498
|
-
return handles.filter(Boolean);
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
function buildSpacingHandles(el) {
|
|
1502
|
-
if (!el || !document.documentElement.contains(el)) return [];
|
|
1503
|
-
if (Math.abs(currentRotation(el)) > 0.01) return [];
|
|
1504
|
-
var children = visibleLayoutChildren(el);
|
|
1505
|
-
if (children.length === 0) return [];
|
|
1506
|
-
var rect = el.getBoundingClientRect();
|
|
1507
|
-
if (rect.width <= 0 || rect.height <= 0) return [];
|
|
1508
|
-
var cs = window.getComputedStyle(el);
|
|
1509
|
-
return buildPaddingSpacingHandles(el, rect, cs).concat(buildGapSpacingHandles(el, rect, cs));
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
function showSpacingBadgeForHandle(handle, value) {
|
|
1513
|
-
if (!selectedEl || !handle) {
|
|
1514
|
-
spacingBadge.style.display = 'none';
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
1517
|
-
var rect = selectedEl.getBoundingClientRect();
|
|
1518
|
-
var x = rect.left + handle.region.x + handle.region.width / 2;
|
|
1519
|
-
var y = rect.top + handle.region.y + handle.region.height / 2;
|
|
1520
|
-
spacingBadge.textContent = String(clampSpacingValue(value));
|
|
1521
|
-
spacingBadge.style.display = 'block';
|
|
1522
|
-
spacingBadge.style.background = spacingColor(handle.kind);
|
|
1523
|
-
spacingBadge.style.left = x + 'px';
|
|
1524
|
-
spacingBadge.style.top = y + 'px';
|
|
1525
|
-
spacingBadge.style.transform = 'translate(-50%, -50%)';
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
function renderSpacingHandle(handle, activeGroupKey) {
|
|
1529
|
-
if (!handle) return;
|
|
1530
|
-
spacingHandleStateByKey[handle.key] = handle;
|
|
1531
|
-
var highlighted = Boolean(activeGroupKey && handle.groupKey === activeGroupKey);
|
|
1532
|
-
var lineNode = document.createElement('span');
|
|
1533
|
-
lineNode.setAttribute('data-agent-native-spacing-line', handle.kind);
|
|
1534
|
-
lineNode.style.display = 'block';
|
|
1535
|
-
lineNode.style.left = handle.line.x + 'px';
|
|
1536
|
-
lineNode.style.top = handle.line.y + 'px';
|
|
1537
|
-
lineNode.style.width = Math.max(1, handle.line.width) + 'px';
|
|
1538
|
-
lineNode.style.height = Math.max(1, handle.line.height) + 'px';
|
|
1539
|
-
lineNode.style.background = spacingColor(handle.kind);
|
|
1540
|
-
spacingOverlay.appendChild(lineNode);
|
|
1541
|
-
|
|
1542
|
-
var regionNode = document.createElement('span');
|
|
1543
|
-
regionNode.setAttribute('data-agent-native-spacing-region', handle.kind);
|
|
1544
|
-
regionNode.setAttribute('data-orientation', handle.orientation);
|
|
1545
|
-
regionNode.setAttribute('data-spacing-key', handle.key);
|
|
1546
|
-
regionNode.style.display = 'block';
|
|
1547
|
-
regionNode.style.left = handle.region.x + 'px';
|
|
1548
|
-
regionNode.style.top = handle.region.y + 'px';
|
|
1549
|
-
regionNode.style.width = handle.region.width + 'px';
|
|
1550
|
-
regionNode.style.height = handle.region.height + 'px';
|
|
1551
|
-
regionNode.style.background = highlighted ? spacingFill(handle.kind, handle.orientation) : 'transparent';
|
|
1552
|
-
regionNode.style.outline = highlighted ? '1px solid ' + spacingColor(handle.kind) : '0';
|
|
1553
|
-
regionNode.style.outlineOffset = '-1px';
|
|
1554
|
-
regionNode.addEventListener('mouseenter', function() {
|
|
1555
|
-
hoveredSpacingHandleKey = handle.key;
|
|
1556
|
-
selectedSpacingHovered = true;
|
|
1557
|
-
updateSpacingOverlay(selectedEl);
|
|
1558
|
-
});
|
|
1559
|
-
regionNode.addEventListener('mouseleave', function() {
|
|
1560
|
-
if (spacingDrag) return;
|
|
1561
|
-
hoveredSpacingHandleKey = '';
|
|
1562
|
-
updateSpacingOverlay(selectedEl);
|
|
1563
|
-
});
|
|
1564
|
-
regionNode.addEventListener('mousedown', function(event) {
|
|
1565
|
-
startSpacingDrag(handle.key, event);
|
|
1566
|
-
}, true);
|
|
1567
|
-
spacingHandleNodesByKey[handle.key] = regionNode;
|
|
1568
|
-
spacingOverlay.appendChild(regionNode);
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
function updateSpacingOverlay(el) {
|
|
1572
|
-
if (el && el !== selectedEl) {
|
|
1573
|
-
hideSpacingOverlay();
|
|
1574
|
-
return;
|
|
1575
|
-
}
|
|
1576
|
-
if (!selectedEl || !document.documentElement.contains(selectedEl)) {
|
|
1577
|
-
hideSpacingOverlay();
|
|
1578
|
-
return;
|
|
1579
|
-
}
|
|
1580
|
-
if (!selectedSpacingHovered && !hoveredSpacingHandleKey && !spacingDrag) {
|
|
1581
|
-
hideSpacingOverlay();
|
|
1582
|
-
return;
|
|
1583
|
-
}
|
|
1584
|
-
var handles = buildSpacingHandles(selectedEl);
|
|
1585
|
-
if (handles.length === 0) {
|
|
1586
|
-
hideSpacingOverlay();
|
|
1587
|
-
return;
|
|
1588
|
-
}
|
|
1589
|
-
spacingOverlay.style.display = 'block';
|
|
1590
|
-
spacingOverlay.innerHTML = '';
|
|
1591
|
-
spacingHandleStateByKey = {};
|
|
1592
|
-
spacingHandleNodesByKey = {};
|
|
1593
|
-
var activeHandle =
|
|
1594
|
-
(spacingDrag && spacingDrag.handle) ||
|
|
1595
|
-
handles.find(function(handle) { return handle.key === hoveredSpacingHandleKey; }) ||
|
|
1596
|
-
null;
|
|
1597
|
-
var activeGroupKey = activeHandle ? activeHandle.groupKey : '';
|
|
1598
|
-
handles.forEach(function(handle) {
|
|
1599
|
-
renderSpacingHandle(handle, activeGroupKey);
|
|
1600
|
-
});
|
|
1601
|
-
if (activeHandle) {
|
|
1602
|
-
showSpacingBadgeForHandle(activeHandle, spacingDrag ? spacingDrag.currentValue : activeHandle.value);
|
|
1603
|
-
} else {
|
|
1604
|
-
spacingBadge.style.display = 'none';
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
function applyEditorChromeScale() {
|
|
1609
|
-
syncEditorChromeScaleVars();
|
|
1610
|
-
var sx = chromeScaleX();
|
|
1611
|
-
var sy = chromeScaleY();
|
|
1612
|
-
var line = chromeLineScale();
|
|
1613
|
-
highlightOverlay.style.borderWidth = (1.5 * line) + 'px';
|
|
1614
|
-
selectionOverlay.style.borderWidth = (1.5 * line) + 'px';
|
|
1615
|
-
if (selectedEl) updateSpacingOverlay(selectedEl);
|
|
1616
|
-
|
|
1617
|
-
selectionOverlay.querySelectorAll('[data-agent-native-edge-handle]').forEach(function(edge) {
|
|
1618
|
-
var pos = edge.getAttribute('data-agent-native-edge-handle');
|
|
1619
|
-
if (pos === 'n' || pos === 's') {
|
|
1620
|
-
edge.style.height = (10 * sy) + 'px';
|
|
1621
|
-
edge.style[pos === 'n' ? 'top' : 'bottom'] = (-5 * sy) + 'px';
|
|
1622
|
-
}
|
|
1623
|
-
if (pos === 'e' || pos === 'w') {
|
|
1624
|
-
edge.style.width = (10 * sx) + 'px';
|
|
1625
|
-
edge.style[pos === 'w' ? 'left' : 'right'] = (-5 * sx) + 'px';
|
|
1626
|
-
}
|
|
1627
|
-
});
|
|
1628
|
-
|
|
1629
|
-
selectionOverlay.querySelectorAll('[data-agent-native-edit-handle]').forEach(function(handle) {
|
|
1630
|
-
var pos = handle.getAttribute('data-agent-native-edit-handle') || '';
|
|
1631
|
-
handle.style.width = (7 * sx) + 'px';
|
|
1632
|
-
handle.style.height = (7 * sy) + 'px';
|
|
1633
|
-
handle.style.borderWidth = Math.max(1, 1 * line) + 'px';
|
|
1634
|
-
if (pos.indexOf('n') !== -1) handle.style.top = (-4 * sy) + 'px';
|
|
1635
|
-
if (pos.indexOf('s') !== -1) handle.style.bottom = (-4 * sy) + 'px';
|
|
1636
|
-
if (pos.indexOf('w') !== -1) handle.style.left = (-4 * sx) + 'px';
|
|
1637
|
-
if (pos.indexOf('e') !== -1) handle.style.right = (-4 * sx) + 'px';
|
|
1638
|
-
});
|
|
1639
|
-
|
|
1640
|
-
selectionOverlay.querySelectorAll('[data-agent-native-rotate-handle]').forEach(function(handle) {
|
|
1641
|
-
var pos = handle.getAttribute('data-agent-native-rotate-handle') || '';
|
|
1642
|
-
handle.style.width = (18 * sx) + 'px';
|
|
1643
|
-
handle.style.height = (18 * sy) + 'px';
|
|
1644
|
-
if (pos.indexOf('n') !== -1) handle.style.top = (-26 * sy) + 'px';
|
|
1645
|
-
if (pos.indexOf('s') !== -1) handle.style.bottom = (-26 * sy) + 'px';
|
|
1646
|
-
if (pos.indexOf('w') !== -1) handle.style.left = (-26 * sx) + 'px';
|
|
1647
|
-
if (pos.indexOf('e') !== -1) handle.style.right = (-26 * sx) + 'px';
|
|
1648
|
-
});
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
function positionOverlay(overlay, el) {
|
|
1652
|
-
if (!el || !document.documentElement.contains(el)) {
|
|
1653
|
-
overlay.style.display = 'none';
|
|
1654
|
-
if (overlay === selectionOverlay) clearComponentTag();
|
|
1655
|
-
return;
|
|
1656
|
-
}
|
|
1657
|
-
// For the selection overlay, prefer the CSS box + rotation transform so
|
|
1658
|
-
// the handles hug the rotated element rather than its inflated AABB.
|
|
1659
|
-
if (overlay === selectionOverlay) {
|
|
1660
|
-
var elCs = window.getComputedStyle(el);
|
|
1661
|
-
var elLeft = readFinitePx(el.style.left || elCs.left);
|
|
1662
|
-
var elTop = readFinitePx(el.style.top || elCs.top);
|
|
1663
|
-
var elW = readFinitePx(el.style.width || elCs.width);
|
|
1664
|
-
var elH = readFinitePx(el.style.height || elCs.height);
|
|
1665
|
-
var elRot = currentRotation(el);
|
|
1666
|
-
var canUseLocalBox = Math.abs(elRot) > 0.01 && elLeft !== null && elTop !== null && elW !== null && elH !== null;
|
|
1667
|
-
// Convert element-local left/top to viewport coords by walking to the
|
|
1668
|
-
// nearest positioned ancestor (same reference frame as getBoundingClientRect).
|
|
1669
|
-
if (canUseLocalBox) {
|
|
1670
|
-
var parentRect = (el.offsetParent || document.documentElement).getBoundingClientRect();
|
|
1671
|
-
overlay.style.display = 'block';
|
|
1672
|
-
overlay.style.left = (parentRect.left + elLeft) + 'px';
|
|
1673
|
-
overlay.style.top = (parentRect.top + elTop) + 'px';
|
|
1674
|
-
overlay.style.width = elW + 'px';
|
|
1675
|
-
overlay.style.height = elH + 'px';
|
|
1676
|
-
overlay.style.transform = 'rotate(' + elRot + 'deg)';
|
|
1677
|
-
overlay.style.transformOrigin = '0 0';
|
|
1678
|
-
updateSpacingOverlay(el);
|
|
1679
|
-
updateComponentTag(el);
|
|
1680
|
-
return;
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
var rect = el.getBoundingClientRect();
|
|
1684
|
-
overlay.style.display = 'block';
|
|
1685
|
-
overlay.style.top = rect.top + 'px';
|
|
1686
|
-
overlay.style.left = rect.left + 'px';
|
|
1687
|
-
overlay.style.width = rect.width + 'px';
|
|
1688
|
-
overlay.style.height = rect.height + 'px';
|
|
1689
|
-
overlay.style.transform = '';
|
|
1690
|
-
if (overlay === selectionOverlay) {
|
|
1691
|
-
updateSpacingOverlay(el);
|
|
1692
|
-
updateComponentTag(el);
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
function refreshOverlays() {
|
|
1697
|
-
if (hoveredEl && hoveredEl !== selectedEl) {
|
|
1698
|
-
positionOverlay(highlightOverlay, hoveredEl);
|
|
1699
|
-
} else {
|
|
1700
|
-
highlightOverlay.style.display = 'none';
|
|
1701
|
-
}
|
|
1702
|
-
if (selectedEl) positionOverlay(selectionOverlay, selectedEl);
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
function hideMeasurements() {
|
|
1706
|
-
measurementOverlay.style.display = 'none';
|
|
1707
|
-
measurementOverlay.innerHTML = '';
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
function addMeasurementLine(x1, y1, x2, y2, label) {
|
|
1711
|
-
var horizontal = Math.abs(x2 - x1) >= Math.abs(y2 - y1);
|
|
1712
|
-
var line = document.createElement('div');
|
|
1713
|
-
var labelEl = document.createElement('div');
|
|
1714
|
-
if (horizontal) {
|
|
1715
|
-
var left = Math.min(x1, x2);
|
|
1716
|
-
var width = Math.max(1, Math.abs(x2 - x1));
|
|
1717
|
-
line.style.cssText = 'position:fixed;left:' + left + 'px;top:' + y1 + 'px;width:' + width + 'px;border-top:1px dashed var(--design-editor-measure-color);';
|
|
1718
|
-
labelEl.style.cssText = 'position:fixed;left:' + (left + width / 2) + 'px;top:' + (y1 - 9) + 'px;transform:translateX(-50%);border-radius:3px;background:var(--design-editor-measure-color);color:white;padding:1px 4px;';
|
|
1719
|
-
} else {
|
|
1720
|
-
var top = Math.min(y1, y2);
|
|
1721
|
-
var height = Math.max(1, Math.abs(y2 - y1));
|
|
1722
|
-
line.style.cssText = 'position:fixed;left:' + x1 + 'px;top:' + top + 'px;height:' + height + 'px;border-left:1px dashed var(--design-editor-measure-color);';
|
|
1723
|
-
labelEl.style.cssText = 'position:fixed;left:' + (x1 + 5) + 'px;top:' + (top + height / 2) + 'px;transform:translateY(-50%);border-radius:3px;background:var(--design-editor-measure-color);color:white;padding:1px 4px;';
|
|
1724
|
-
}
|
|
1725
|
-
labelEl.textContent = label;
|
|
1726
|
-
measurementOverlay.appendChild(line);
|
|
1727
|
-
measurementOverlay.appendChild(labelEl);
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
function showMeasurements(a, b) {
|
|
1731
|
-
if (!a || !b || a === b) {
|
|
1732
|
-
hideMeasurements();
|
|
1733
|
-
return;
|
|
1734
|
-
}
|
|
1735
|
-
var selectedRect = a.getBoundingClientRect();
|
|
1736
|
-
var hoverRect = b.getBoundingClientRect();
|
|
1737
|
-
measurementOverlay.innerHTML = '';
|
|
1738
|
-
measurementOverlay.style.display = 'block';
|
|
1739
|
-
|
|
1740
|
-
if (hoverRect.right <= selectedRect.left) {
|
|
1741
|
-
var yLeft = Math.max(hoverRect.top, Math.min(hoverRect.bottom, selectedRect.top + selectedRect.height / 2));
|
|
1742
|
-
addMeasurementLine(hoverRect.right, yLeft, selectedRect.left, yLeft, Math.round(selectedRect.left - hoverRect.right) + 'px');
|
|
1743
|
-
return;
|
|
1744
|
-
}
|
|
1745
|
-
if (selectedRect.right <= hoverRect.left) {
|
|
1746
|
-
var yRight = Math.max(selectedRect.top, Math.min(selectedRect.bottom, hoverRect.top + hoverRect.height / 2));
|
|
1747
|
-
addMeasurementLine(selectedRect.right, yRight, hoverRect.left, yRight, Math.round(hoverRect.left - selectedRect.right) + 'px');
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
if (hoverRect.bottom <= selectedRect.top) {
|
|
1751
|
-
var xTop = Math.max(hoverRect.left, Math.min(hoverRect.right, selectedRect.left + selectedRect.width / 2));
|
|
1752
|
-
addMeasurementLine(xTop, hoverRect.bottom, xTop, selectedRect.top, Math.round(selectedRect.top - hoverRect.bottom) + 'px');
|
|
1753
|
-
return;
|
|
1754
|
-
}
|
|
1755
|
-
if (selectedRect.bottom <= hoverRect.top) {
|
|
1756
|
-
var xBottom = Math.max(selectedRect.left, Math.min(selectedRect.right, hoverRect.left + hoverRect.width / 2));
|
|
1757
|
-
addMeasurementLine(xBottom, selectedRect.bottom, xBottom, hoverRect.top, Math.round(hoverRect.top - selectedRect.bottom) + 'px');
|
|
1758
|
-
return;
|
|
1759
|
-
}
|
|
1760
|
-
addMeasurementLine(
|
|
1761
|
-
selectedRect.left + selectedRect.width / 2,
|
|
1762
|
-
selectedRect.top + selectedRect.height / 2,
|
|
1763
|
-
hoverRect.left + hoverRect.width / 2,
|
|
1764
|
-
hoverRect.top + hoverRect.height / 2,
|
|
1765
|
-
Math.round(Math.hypot(
|
|
1766
|
-
hoverRect.left + hoverRect.width / 2 - (selectedRect.left + selectedRect.width / 2),
|
|
1767
|
-
hoverRect.top + hoverRect.height / 2 - (selectedRect.top + selectedRect.height / 2)
|
|
1768
|
-
)) + 'px'
|
|
1769
|
-
);
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
function dragEventNames(e) {
|
|
1773
|
-
var pointerGesture = e && e.type && e.type.indexOf('pointer') === 0;
|
|
1774
|
-
return pointerGesture
|
|
1775
|
-
? { move: 'pointermove', up: 'pointerup' }
|
|
1776
|
-
: { move: 'mousemove', up: 'mouseup' };
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
function elementFromEditorPoint(clientX, clientY) {
|
|
1780
|
-
var shieldPointerEvents = shieldOverlay.style.pointerEvents;
|
|
1781
|
-
var selectionPointerEvents = selectionOverlay.style.pointerEvents;
|
|
1782
|
-
var highlightPointerEvents = highlightOverlay.style.pointerEvents;
|
|
1783
|
-
shieldOverlay.style.pointerEvents = 'none';
|
|
1784
|
-
selectionOverlay.style.pointerEvents = 'none';
|
|
1785
|
-
highlightOverlay.style.pointerEvents = 'none';
|
|
1786
|
-
var target = document.elementFromPoint(clientX, clientY);
|
|
1787
|
-
shieldOverlay.style.pointerEvents = shieldPointerEvents;
|
|
1788
|
-
selectionOverlay.style.pointerEvents = selectionPointerEvents;
|
|
1789
|
-
highlightOverlay.style.pointerEvents = highlightPointerEvents;
|
|
1790
|
-
if (!target || target.nodeType !== 1) return null;
|
|
1791
|
-
if (isLayerInteractionBlocked(target)) return null;
|
|
1792
|
-
return target;
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
function stopNativeInteraction(e) {
|
|
1796
|
-
e.preventDefault();
|
|
1797
|
-
e.stopPropagation();
|
|
1798
|
-
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
function isEditorTypingTarget(target) {
|
|
1802
|
-
if (!target || !target.closest) return false;
|
|
1803
|
-
return !!target.closest('input, textarea, select, [contenteditable], [role="textbox"], [data-agent-native-text-editing]');
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
function shouldForwardDesignHotkey(e) {
|
|
1807
|
-
if (activeTextEditEl || isEditorTypingTarget(e.target) || e.isComposing) return false;
|
|
1808
|
-
var key = e.key;
|
|
1809
|
-
var normalized = key && key.length === 1 ? key.toLowerCase() : key;
|
|
1810
|
-
var primary = e.metaKey || e.ctrlKey;
|
|
1811
|
-
if (key === 'Escape' || key === 'Enter') return true;
|
|
1812
|
-
// Forward Tab only when an element is actively selected so the iframe does
|
|
1813
|
-
// not intercept Tab when the user is tabbing through browser UI with nothing
|
|
1814
|
-
// selected (preserves native keyboard accessibility).
|
|
1815
|
-
if (key === 'Tab') return !!selectedEl;
|
|
1816
|
-
if (key === 'Delete' || key === 'Backspace') return !primary;
|
|
1817
|
-
if (/^Arrow/.test(key || '')) return !e.altKey;
|
|
1818
|
-
if (primary) {
|
|
1819
|
-
return ['z','y','a','x','c','v','d','g','=','+','-','0',']','['].indexOf(normalized) !== -1 ||
|
|
1820
|
-
e.code === 'Digit1' ||
|
|
1821
|
-
e.code === 'Digit2' ||
|
|
1822
|
-
key === '1' ||
|
|
1823
|
-
key === '2';
|
|
1824
|
-
}
|
|
1825
|
-
if (e.shiftKey && (e.code === 'Digit1' || e.code === 'Digit2' || key === '1' || key === '2')) return true;
|
|
1826
|
-
return !e.altKey && !e.shiftKey && ['v','f','r','t','p','h','c','k'].indexOf(normalized) !== -1;
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
function blurActiveTextEditor() {
|
|
1830
|
-
var active = document.activeElement;
|
|
1831
|
-
if (
|
|
1832
|
-
active &&
|
|
1833
|
-
active.closest &&
|
|
1834
|
-
active.closest('[data-agent-native-text-editing]') &&
|
|
1835
|
-
typeof active.blur === 'function'
|
|
1836
|
-
) {
|
|
1837
|
-
active.blur();
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
function setTextEditingPointerPassthrough(enabled) {
|
|
1842
|
-
if (enabled) {
|
|
1843
|
-
if (!textEditPointerState) {
|
|
1844
|
-
textEditPointerState = {
|
|
1845
|
-
shield: shieldOverlay.style.pointerEvents,
|
|
1846
|
-
selection: selectionOverlay.style.pointerEvents,
|
|
1847
|
-
highlight: highlightOverlay.style.pointerEvents,
|
|
1848
|
-
};
|
|
1849
|
-
}
|
|
1850
|
-
shieldOverlay.style.pointerEvents = 'none';
|
|
1851
|
-
selectionOverlay.style.pointerEvents = 'none';
|
|
1852
|
-
highlightOverlay.style.pointerEvents = 'none';
|
|
1853
|
-
return;
|
|
1854
|
-
}
|
|
1855
|
-
if (!textEditPointerState) return;
|
|
1856
|
-
shieldOverlay.style.pointerEvents = textEditPointerState.shield;
|
|
1857
|
-
selectionOverlay.style.pointerEvents = textEditPointerState.selection;
|
|
1858
|
-
highlightOverlay.style.pointerEvents = textEditPointerState.highlight;
|
|
1859
|
-
textEditPointerState = null;
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
function hasTextContent(el) {
|
|
1863
|
-
return !!(el && el.textContent && el.textContent.trim().length > 0);
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
function isInlineEditableDescendant(el) {
|
|
1867
|
-
if (!el || !el.tagName) return false;
|
|
1868
|
-
// Allowlist covers inline markup AND common block-level text containers
|
|
1869
|
-
// (p, h1-h6, li, etc.) so that paragraphs with inline markup like
|
|
1870
|
-
// <p>Hello <strong>world</strong></p> can be double-click edited.
|
|
1871
|
-
return [
|
|
1872
|
-
// Inline formatting
|
|
1873
|
-
'a',
|
|
1874
|
-
'abbr',
|
|
1875
|
-
'b',
|
|
1876
|
-
'br',
|
|
1877
|
-
'cite',
|
|
1878
|
-
'code',
|
|
1879
|
-
'em',
|
|
1880
|
-
'i',
|
|
1881
|
-
'mark',
|
|
1882
|
-
'small',
|
|
1883
|
-
'span',
|
|
1884
|
-
'strong',
|
|
1885
|
-
'sub',
|
|
1886
|
-
'sup',
|
|
1887
|
-
'time',
|
|
1888
|
-
'u',
|
|
1889
|
-
'wbr',
|
|
1890
|
-
// Block-level text containers
|
|
1891
|
-
'p',
|
|
1892
|
-
'h1',
|
|
1893
|
-
'h2',
|
|
1894
|
-
'h3',
|
|
1895
|
-
'h4',
|
|
1896
|
-
'h5',
|
|
1897
|
-
'h6',
|
|
1898
|
-
'li',
|
|
1899
|
-
'ul',
|
|
1900
|
-
'ol',
|
|
1901
|
-
'dl',
|
|
1902
|
-
'dt',
|
|
1903
|
-
'dd',
|
|
1904
|
-
'label',
|
|
1905
|
-
'caption',
|
|
1906
|
-
'td',
|
|
1907
|
-
'th'
|
|
1908
|
-
].indexOf(el.tagName.toLowerCase()) !== -1;
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
function hasOnlyInlineEditableChildren(el) {
|
|
1912
|
-
if (!el || !hasTextContent(el)) return false;
|
|
1913
|
-
var descendants = el.querySelectorAll ? el.querySelectorAll('*') : [];
|
|
1914
|
-
for (var i = 0; i < descendants.length; i += 1) {
|
|
1915
|
-
if (!isInlineEditableDescendant(descendants[i])) return false;
|
|
1916
|
-
}
|
|
1917
|
-
return true;
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
function findTextEditTarget(hit) {
|
|
1921
|
-
if (!hit || hit.nodeType !== 1 || hit === document.body || hit === document.documentElement) return null;
|
|
1922
|
-
var selectedContainsHit = selectedEl && selectedEl.contains && selectedEl.contains(hit);
|
|
1923
|
-
if (selectedContainsHit && hasOnlyInlineEditableChildren(selectedEl)) return selectedEl;
|
|
1924
|
-
|
|
1925
|
-
var candidate = null;
|
|
1926
|
-
var node = hit;
|
|
1927
|
-
while (node && node.nodeType === 1 && node !== document.body && node !== document.documentElement) {
|
|
1928
|
-
if (hasOnlyInlineEditableChildren(node)) {
|
|
1929
|
-
candidate = node;
|
|
1930
|
-
}
|
|
1931
|
-
if (selectedEl && node === selectedEl) break;
|
|
1932
|
-
node = node.parentElement;
|
|
1933
|
-
}
|
|
1934
|
-
// Return null (not the raw hit) when no text-editable ancestor is found.
|
|
1935
|
-
// Falling back to the raw hit element makes non-text nodes like <img> or
|
|
1936
|
-
// <canvas> contenteditable, which leaves the editor in a broken state.
|
|
1937
|
-
return candidate || null;
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
|
-
function selectElementAtEvent(e) {
|
|
1941
|
-
stopNativeInteraction(e);
|
|
1942
|
-
blurActiveTextEditor();
|
|
1943
|
-
if (suppressNextShieldClick) {
|
|
1944
|
-
suppressNextShieldClick = false;
|
|
1945
|
-
if (suppressNextShieldClickTimer !== null) {
|
|
1946
|
-
clearTimeout(suppressNextShieldClickTimer);
|
|
1947
|
-
suppressNextShieldClickTimer = null;
|
|
1948
|
-
}
|
|
1949
|
-
return;
|
|
1950
|
-
}
|
|
1951
|
-
// Suppress the first click in a double-click sequence — the dblclick
|
|
1952
|
-
// handler (beginTextEditingFromEvent) will fire immediately after and a
|
|
1953
|
-
// spurious element-select would cause inspector flicker.
|
|
1954
|
-
if (e.detail >= 2) return;
|
|
1955
|
-
var target = elementFromEditorPoint(e.clientX, e.clientY);
|
|
1956
|
-
if (!target || target === document.body || target === document.documentElement) {
|
|
1957
|
-
// Click on empty canvas: clear the current selection (matches Figma).
|
|
1958
|
-
clearRuntimeSelection();
|
|
1959
|
-
window.parent.postMessage({ type: 'clear-selection' }, '*');
|
|
1960
|
-
return;
|
|
1961
|
-
}
|
|
1962
|
-
selectedSpacingHovered = false;
|
|
1963
|
-
hoveredSpacingHandleKey = '';
|
|
1964
|
-
selectedEl = selectionTargetForHit(target);
|
|
1965
|
-
if (!selectedEl || isLayerInteractionBlocked(selectedEl)) {
|
|
1966
|
-
selectedEl = null;
|
|
1967
|
-
selectionOverlay.style.display = 'none';
|
|
1968
|
-
clearComponentTag();
|
|
1969
|
-
return;
|
|
1970
|
-
}
|
|
1971
|
-
var info = getElementInfo(selectedEl);
|
|
1972
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
1973
|
-
window.parent.postMessage({ type: 'element-select', payload: info }, '*');
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
function suppressNextShieldClickBriefly() {
|
|
1977
|
-
suppressNextShieldClick = true;
|
|
1978
|
-
if (suppressNextShieldClickTimer !== null) {
|
|
1979
|
-
clearTimeout(suppressNextShieldClickTimer);
|
|
1980
|
-
}
|
|
1981
|
-
suppressNextShieldClickTimer = setTimeout(function() {
|
|
1982
|
-
suppressNextShieldClick = false;
|
|
1983
|
-
suppressNextShieldClickTimer = null;
|
|
1984
|
-
}, 250);
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
function openContextMenuAtEvent(e) {
|
|
1988
|
-
stopNativeInteraction(e);
|
|
1989
|
-
blurActiveTextEditor();
|
|
1990
|
-
var target = elementFromEditorPoint(e.clientX, e.clientY);
|
|
1991
|
-
var info = null;
|
|
1992
|
-
if (target) {
|
|
1993
|
-
selectedSpacingHovered = false;
|
|
1994
|
-
hoveredSpacingHandleKey = '';
|
|
1995
|
-
selectedEl = selectionTargetForHit(target);
|
|
1996
|
-
if (selectedEl && !isLayerInteractionBlocked(selectedEl)) {
|
|
1997
|
-
info = getElementInfo(selectedEl);
|
|
1998
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
1999
|
-
window.parent.postMessage({ type: 'element-select', payload: info }, '*');
|
|
2000
|
-
} else {
|
|
2001
|
-
selectedEl = null;
|
|
2002
|
-
selectionOverlay.style.display = 'none';
|
|
2003
|
-
clearComponentTag();
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
window.parent.postMessage({
|
|
2007
|
-
type: 'element-contextmenu',
|
|
2008
|
-
clientX: e.clientX,
|
|
2009
|
-
clientY: e.clientY,
|
|
2010
|
-
payload: info
|
|
2011
|
-
}, '*');
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
function findRuntimeTarget(selector, selectorCandidates) {
|
|
2015
|
-
var candidates = [];
|
|
2016
|
-
if (Array.isArray(selectorCandidates)) {
|
|
2017
|
-
selectorCandidates.forEach(function(candidate) {
|
|
2018
|
-
if (typeof candidate === 'string' && candidate && candidates.indexOf(candidate) === -1) {
|
|
2019
|
-
candidates.push(candidate);
|
|
2020
|
-
}
|
|
2021
|
-
});
|
|
2022
|
-
}
|
|
2023
|
-
if (selector && candidates.indexOf(selector) === -1) candidates.push(selector);
|
|
2024
|
-
if (
|
|
2025
|
-
selectedEl &&
|
|
2026
|
-
document.documentElement.contains(selectedEl) &&
|
|
2027
|
-
(candidates.length === 0 || matchesExactSelectorList(selectedEl, candidates))
|
|
2028
|
-
) {
|
|
2029
|
-
return selectedEl;
|
|
2030
|
-
}
|
|
2031
|
-
for (var i = 0; i < candidates.length; i += 1) {
|
|
2032
|
-
try {
|
|
2033
|
-
var match = document.querySelector(candidates[i]);
|
|
2034
|
-
if (match && !isLayerInteractionBlocked(match)) return match;
|
|
2035
|
-
} catch (_err) {}
|
|
2036
|
-
}
|
|
2037
|
-
return null;
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
function removeRuntimeTarget(selector, selectorCandidates) {
|
|
2041
|
-
var target = findRuntimeTarget(selector, selectorCandidates);
|
|
2042
|
-
if (!target || target === document.body || target === document.documentElement) return false;
|
|
2043
|
-
if (target.parentElement) target.parentElement.removeChild(target);
|
|
2044
|
-
if (selectedEl === target || !document.documentElement.contains(selectedEl)) {
|
|
2045
|
-
selectedEl = null;
|
|
2046
|
-
selectionOverlay.style.display = 'none';
|
|
2047
|
-
}
|
|
2048
|
-
hoveredEl = null;
|
|
2049
|
-
highlightOverlay.style.display = 'none';
|
|
2050
|
-
hideMeasurements();
|
|
2051
|
-
refreshOverlays();
|
|
2052
|
-
return true;
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
function readPx(value) {
|
|
2056
|
-
var num = parseFloat(value);
|
|
2057
|
-
return Number.isFinite(num) ? num : 0;
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
function readFinitePx(value) {
|
|
2061
|
-
if (!value || value === 'auto') return null;
|
|
2062
|
-
var num = parseFloat(value);
|
|
2063
|
-
return Number.isFinite(num) ? num : null;
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
function currentRotation(el) {
|
|
2067
|
-
var transform = el.style.transform || window.getComputedStyle(el).transform || '';
|
|
2068
|
-
var match = transform.match(/rotate\\((-?\\d+(?:\\.\\d+)?)deg\\)/);
|
|
2069
|
-
if (match) return parseFloat(match[1]) || 0;
|
|
2070
|
-
if (transform && transform !== 'none' && window.DOMMatrixReadOnly) {
|
|
2071
|
-
try {
|
|
2072
|
-
var matrix = new DOMMatrixReadOnly(transform);
|
|
2073
|
-
return Math.round(Math.atan2(matrix.b, matrix.a) * 180 / Math.PI);
|
|
2074
|
-
} catch (err) {}
|
|
2075
|
-
}
|
|
2076
|
-
return 0;
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
function mergeRotation(el, degrees) {
|
|
2080
|
-
var inline = el.style.transform || '';
|
|
2081
|
-
var next = inline.match(/rotate\\((-?\\d+(?:\\.\\d+)?)deg\\)/)
|
|
2082
|
-
? inline.replace(/rotate\\((-?\\d+(?:\\.\\d+)?)deg\\)/, 'rotate(' + degrees + 'deg)')
|
|
2083
|
-
: (inline && inline !== 'none' ? inline + ' ' : '') + 'rotate(' + degrees + 'deg)';
|
|
2084
|
-
return next.trim();
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
function ensurePositionable(el) {
|
|
2088
|
-
var cs = window.getComputedStyle(el);
|
|
2089
|
-
if (cs.position === 'static') {
|
|
2090
|
-
el.style.position = 'relative';
|
|
2091
|
-
if (!el.style.left) el.style.left = '0px';
|
|
2092
|
-
if (!el.style.top) el.style.top = '0px';
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
function postVisualStyleChange(styles) {
|
|
2097
|
-
if (!selectedEl) return;
|
|
2098
|
-
window.parent.postMessage({
|
|
2099
|
-
type: 'visual-style-change',
|
|
2100
|
-
selector: getSelector(selectedEl),
|
|
2101
|
-
styles: styles,
|
|
2102
|
-
payload: getElementInfo(selectedEl),
|
|
2103
|
-
}, '*');
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
function spacingValueFromPointer(handle, originValue, startX, startY, clientX, clientY) {
|
|
2107
|
-
var delta =
|
|
2108
|
-
handle.orientation === 'vertical' ? clientX - startX : clientY - startY;
|
|
2109
|
-
if (handle.kind === 'padding' && (handle.side === 'right' || handle.side === 'bottom')) {
|
|
2110
|
-
delta = -delta;
|
|
2111
|
-
}
|
|
2112
|
-
return clampSpacingValue(originValue + delta);
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
function applySpacingDragValue(target, handle, value, mirrorOpposite) {
|
|
2116
|
-
if (!target || !handle) return;
|
|
2117
|
-
target.style[handle.property] = value + 'px';
|
|
2118
|
-
if (handle.kind === 'padding' && mirrorOpposite && handle.oppositeProperty) {
|
|
2119
|
-
target.style[handle.oppositeProperty] = value + 'px';
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
function startSpacingDrag(key, e) {
|
|
2124
|
-
var handle = spacingHandleStateByKey[key];
|
|
2125
|
-
if (!selectedEl || !handle || isLayerInteractionBlocked(selectedEl)) return;
|
|
2126
|
-
e.preventDefault();
|
|
2127
|
-
e.stopPropagation();
|
|
2128
|
-
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
|
|
2129
|
-
var events = dragEventNames(e);
|
|
2130
|
-
var dragEl = selectedEl;
|
|
2131
|
-
var originValue = handle.value;
|
|
2132
|
-
var startX = e.clientX;
|
|
2133
|
-
var startY = e.clientY;
|
|
2134
|
-
hoveredSpacingHandleKey = key;
|
|
2135
|
-
selectedSpacingHovered = true;
|
|
2136
|
-
spacingDrag = {
|
|
2137
|
-
handle: handle,
|
|
2138
|
-
currentValue: originValue,
|
|
2139
|
-
mirrorOpposite: !!e.altKey,
|
|
2140
|
-
};
|
|
2141
|
-
showSpacingBadgeForHandle(handle, originValue);
|
|
2142
|
-
|
|
2143
|
-
function onMove(ev) {
|
|
2144
|
-
if (!dragEl || !document.documentElement.contains(dragEl)) return;
|
|
2145
|
-
var nextValue = spacingValueFromPointer(handle, originValue, startX, startY, ev.clientX, ev.clientY);
|
|
2146
|
-
spacingDrag = {
|
|
2147
|
-
handle: handle,
|
|
2148
|
-
currentValue: nextValue,
|
|
2149
|
-
mirrorOpposite: !!ev.altKey,
|
|
2150
|
-
};
|
|
2151
|
-
applySpacingDragValue(dragEl, handle, nextValue, !!ev.altKey);
|
|
2152
|
-
positionOverlay(selectionOverlay, dragEl);
|
|
2153
|
-
showSpacingBadgeForHandle(handle, nextValue);
|
|
2154
|
-
}
|
|
2155
|
-
|
|
2156
|
-
function onUp(ev) {
|
|
2157
|
-
document.removeEventListener(events.move, onMove, true);
|
|
2158
|
-
document.removeEventListener(events.up, onUp, true);
|
|
2159
|
-
if (!dragEl || !document.documentElement.contains(dragEl)) {
|
|
2160
|
-
spacingDrag = null;
|
|
2161
|
-
return;
|
|
2162
|
-
}
|
|
2163
|
-
var finalValue = spacingDrag ? spacingDrag.currentValue : originValue;
|
|
2164
|
-
var mirrorOpposite = spacingDrag ? spacingDrag.mirrorOpposite : !!ev.altKey;
|
|
2165
|
-
applySpacingDragValue(dragEl, handle, finalValue, mirrorOpposite);
|
|
2166
|
-
selectedEl = dragEl;
|
|
2167
|
-
spacingDrag = null;
|
|
2168
|
-
var styles = {};
|
|
2169
|
-
styles[handle.property] = finalValue + 'px';
|
|
2170
|
-
if (handle.kind === 'padding' && mirrorOpposite && handle.oppositeProperty) {
|
|
2171
|
-
styles[handle.oppositeProperty] = finalValue + 'px';
|
|
2172
|
-
}
|
|
2173
|
-
postVisualStyleChange(styles);
|
|
2174
|
-
positionOverlay(selectionOverlay, dragEl);
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
document.addEventListener(events.move, onMove, true);
|
|
2178
|
-
document.addEventListener(events.up, onUp, true);
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
function postTextContentChange(el, value, html) {
|
|
2182
|
-
window.parent.postMessage({
|
|
2183
|
-
type: 'text-content-change',
|
|
2184
|
-
selector: getSelector(el),
|
|
2185
|
-
value: value,
|
|
2186
|
-
html: html,
|
|
2187
|
-
payload: getElementInfo(el),
|
|
2188
|
-
}, '*');
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
function postTextEditingState(el, active) {
|
|
2192
|
-
var selection = window.getSelection && window.getSelection();
|
|
2193
|
-
window.parent.postMessage({
|
|
2194
|
-
type: 'text-editing-state',
|
|
2195
|
-
active: !!active,
|
|
2196
|
-
selector: el ? getSelector(el) : '',
|
|
2197
|
-
hasRange: !!(active && selection && selection.rangeCount > 0 && !selection.isCollapsed && selectionBelongsToElement(selection, el)),
|
|
2198
|
-
}, '*');
|
|
2199
|
-
}
|
|
2200
|
-
|
|
2201
|
-
function insertPlainTextAtSelection(text) {
|
|
2202
|
-
if (!text) return;
|
|
2203
|
-
if (document.queryCommandSupported && document.queryCommandSupported('insertText')) {
|
|
2204
|
-
document.execCommand('insertText', false, text);
|
|
2205
|
-
return;
|
|
2206
|
-
}
|
|
2207
|
-
var selection = window.getSelection && window.getSelection();
|
|
2208
|
-
if (!selection || selection.rangeCount === 0) return;
|
|
2209
|
-
var range = selection.getRangeAt(0);
|
|
2210
|
-
range.deleteContents();
|
|
2211
|
-
var textNode = document.createTextNode(text);
|
|
2212
|
-
range.insertNode(textNode);
|
|
2213
|
-
range.setStartAfter(textNode);
|
|
2214
|
-
range.setEndAfter(textNode);
|
|
2215
|
-
selection.removeAllRanges();
|
|
2216
|
-
selection.addRange(range);
|
|
2217
|
-
}
|
|
2218
|
-
|
|
2219
|
-
function selectionBelongsToElement(selection, el) {
|
|
2220
|
-
if (!selection || !el || selection.rangeCount === 0) return false;
|
|
2221
|
-
var range = selection.getRangeAt(0);
|
|
2222
|
-
var ancestor = range.commonAncestorContainer;
|
|
2223
|
-
var ancestorEl = ancestor && ancestor.nodeType === 1 ? ancestor : ancestor && ancestor.parentElement;
|
|
2224
|
-
return !!(ancestorEl && (ancestorEl === el || el.contains(ancestorEl)));
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
|
-
function normalizeCssPropertyName(property) {
|
|
2228
|
-
var prop = String(property || '').trim();
|
|
2229
|
-
if (!prop) return '';
|
|
2230
|
-
if (prop.indexOf('--') === 0) return prop;
|
|
2231
|
-
return prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
function applyInlineStyleProperty(el, property, value) {
|
|
2235
|
-
if (!el || !property) return false;
|
|
2236
|
-
var cssProperty = normalizeCssPropertyName(property);
|
|
2237
|
-
if (!cssProperty) return false;
|
|
2238
|
-
el.style.setProperty(cssProperty, String(value));
|
|
2239
|
-
return true;
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
function applyTextRangeStyle(property, value) {
|
|
2243
|
-
if (!activeTextEditEl || !property) return false;
|
|
2244
|
-
var selection = window.getSelection && window.getSelection();
|
|
2245
|
-
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return false;
|
|
2246
|
-
if (!selectionBelongsToElement(selection, activeTextEditEl)) return false;
|
|
2247
|
-
var range = selection.getRangeAt(0);
|
|
2248
|
-
var span = document.createElement('span');
|
|
2249
|
-
applyInlineStyleProperty(span, property, value);
|
|
2250
|
-
if (!span.getAttribute('style')) return false;
|
|
2251
|
-
try {
|
|
2252
|
-
range.surroundContents(span);
|
|
2253
|
-
} catch (err) {
|
|
2254
|
-
var contents = range.extractContents();
|
|
2255
|
-
span.appendChild(contents);
|
|
2256
|
-
range.insertNode(span);
|
|
2257
|
-
}
|
|
2258
|
-
selection.removeAllRanges();
|
|
2259
|
-
var nextRange = document.createRange();
|
|
2260
|
-
nextRange.selectNodeContents(span);
|
|
2261
|
-
selection.addRange(nextRange);
|
|
2262
|
-
return true;
|
|
2263
|
-
}
|
|
2264
|
-
|
|
2265
|
-
function showTransformBadge(text, clientX, clientY) {
|
|
2266
|
-
transformBadge.textContent = text;
|
|
2267
|
-
transformBadge.style.display = 'block';
|
|
2268
|
-
transformBadge.style.left = clientX + 12 + 'px';
|
|
2269
|
-
transformBadge.style.top = clientY + 12 + 'px';
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
|
-
function hideTransformBadge() {
|
|
2273
|
-
transformBadge.style.display = 'none';
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
function hideInsertionGuide() {
|
|
2277
|
-
insertionGuide.style.display = 'none';
|
|
2278
|
-
}
|
|
2279
|
-
|
|
2280
|
-
function isOverlayElement(el) {
|
|
2281
|
-
// Use closest() so that children of overlay elements (e.g. spacing-region
|
|
2282
|
-
// spans inside selectionOverlay that have pointer-events:auto via a CSS rule
|
|
2283
|
-
// and can therefore still be returned by elementFromPoint even when the
|
|
2284
|
-
// parent overlay has pointer-events:none set inline) are also treated as
|
|
2285
|
-
// overlay elements and never used as drag-drop anchor targets.
|
|
2286
|
-
return Boolean(
|
|
2287
|
-
el && el.closest && el.closest('[data-agent-native-edit-overlay]')
|
|
2288
|
-
);
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
function draggableElementChildren(parent) {
|
|
2292
|
-
return Array.prototype.slice.call(parent.children).filter(function(child) {
|
|
2293
|
-
return child.nodeType === 1 && !isOverlayElement(child) && !isLayerInteractionBlocked(child);
|
|
2294
|
-
});
|
|
2295
|
-
}
|
|
2296
|
-
|
|
2297
|
-
function isFlowReorderCandidate(el) {
|
|
2298
|
-
if (!el || !el.parentElement) return false;
|
|
2299
|
-
if (el === document.body || el === document.documentElement) return false;
|
|
2300
|
-
var cs = window.getComputedStyle(el);
|
|
2301
|
-
if (cs.position === 'absolute' || cs.position === 'fixed') return false;
|
|
2302
|
-
return true;
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
// keep in sync with EditPanel.tsx CONTAINER_TAGS/LEAF_TAGS (~line 1679)
|
|
2306
|
-
var BRIDGE_CONTAINER_TAGS = ['div', 'section', 'main', 'header', 'footer', 'nav', 'article', 'aside', 'form', 'ul', 'ol', 'figure', 'fieldset', 'details', 'dialog', 'blockquote', 'table', 'tbody', 'thead', 'tr'];
|
|
2307
|
-
var BRIDGE_LEAF_TAGS = ['img', 'video', 'picture', 'audio', 'canvas', 'svg', 'path', 'input', 'textarea', 'select', 'br', 'hr', 'iframe'];
|
|
2308
|
-
var BRIDGE_TEXT_TAGS = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'a', 'strong', 'em', 'label', 'li'];
|
|
2309
|
-
|
|
2310
|
-
function isContainerDropTarget(el) {
|
|
2311
|
-
if (!el || el === document.documentElement) return false;
|
|
2312
|
-
if (isOverlayElement(el) || isLayerInteractionBlocked(el)) return false;
|
|
2313
|
-
if (el === document.body) return true;
|
|
2314
|
-
var tag = (el.tagName || '').toLowerCase();
|
|
2315
|
-
// Reject leaf/text tags — they cannot accept children
|
|
2316
|
-
if (BRIDGE_LEAF_TAGS.indexOf(tag) !== -1 || BRIDGE_TEXT_TAGS.indexOf(tag) !== -1) return false;
|
|
2317
|
-
var cs = window.getComputedStyle(el);
|
|
2318
|
-
if (
|
|
2319
|
-
cs.display === 'flex' ||
|
|
2320
|
-
cs.display === 'inline-flex' ||
|
|
2321
|
-
cs.display === 'grid' ||
|
|
2322
|
-
cs.display === 'inline-grid'
|
|
2323
|
-
) {
|
|
2324
|
-
return true;
|
|
2325
|
-
}
|
|
2326
|
-
return BRIDGE_CONTAINER_TAGS.indexOf(tag) !== -1;
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
function edgePlacementForRect(rect, axis, clientX, clientY) {
|
|
2330
|
-
var size = axis === 'x' ? rect.width : rect.height;
|
|
2331
|
-
if (!size) return null;
|
|
2332
|
-
var offset = axis === 'x' ? clientX - rect.left : clientY - rect.top;
|
|
2333
|
-
if (offset < size * 0.22) return 'before';
|
|
2334
|
-
if (offset > size * 0.78) return 'after';
|
|
2335
|
-
return null;
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
function parentFlowAxis(parent) {
|
|
2339
|
-
var cs = window.getComputedStyle(parent);
|
|
2340
|
-
if (cs.display === 'flex' || cs.display === 'inline-flex') {
|
|
2341
|
-
var isRow = cs.flexDirection && cs.flexDirection.indexOf('row') === 0;
|
|
2342
|
-
// Wrapping row containers need Y-axis awareness for inter-row targeting;
|
|
2343
|
-
// fall back to column-axis insertion so the heuristic picks the right row.
|
|
2344
|
-
var wraps = cs.flexWrap === 'wrap' || cs.flexWrap === 'wrap-reverse';
|
|
2345
|
-
if (isRow && !wraps) return 'x';
|
|
2346
|
-
return 'y';
|
|
2347
|
-
}
|
|
2348
|
-
if (cs.display === 'grid' || cs.display === 'inline-grid') {
|
|
2349
|
-
var cols = (cs.gridTemplateColumns || '').split(' ').filter(Boolean).length;
|
|
2350
|
-
return cols > 1 ? 'x' : 'y';
|
|
2351
|
-
}
|
|
2352
|
-
return 'y';
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2355
|
-
function reorderTargetForPoint(el, clientX, clientY) {
|
|
2356
|
-
if (!el || !el.parentElement) return null;
|
|
2357
|
-
var hit = elementFromEditorPoint(clientX, clientY);
|
|
2358
|
-
if (
|
|
2359
|
-
hit &&
|
|
2360
|
-
hit !== document.documentElement &&
|
|
2361
|
-
hit !== el &&
|
|
2362
|
-
!el.contains(hit) &&
|
|
2363
|
-
!isOverlayElement(hit)
|
|
2364
|
-
) {
|
|
2365
|
-
if (isContainerDropTarget(hit)) {
|
|
2366
|
-
var containerRect = hit.getBoundingClientRect();
|
|
2367
|
-
var edgeAxis = hit.parentElement ? parentFlowAxis(hit.parentElement) : parentFlowAxis(hit);
|
|
2368
|
-
var edgePlacement = edgePlacementForRect(containerRect, edgeAxis, clientX, clientY);
|
|
2369
|
-
if (!edgePlacement) {
|
|
2370
|
-
return { anchor: hit, placement: 'inside', axis: parentFlowAxis(hit) };
|
|
2371
|
-
}
|
|
2372
|
-
return { anchor: hit, placement: edgePlacement, axis: edgeAxis };
|
|
2373
|
-
}
|
|
2374
|
-
var hitParent = hit.parentElement;
|
|
2375
|
-
if (hitParent) {
|
|
2376
|
-
var hitAxis = parentFlowAxis(hitParent);
|
|
2377
|
-
var hitRect = hit.getBoundingClientRect();
|
|
2378
|
-
var hitCenter = hitAxis === 'x'
|
|
2379
|
-
? hitRect.left + hitRect.width / 2
|
|
2380
|
-
: hitRect.top + hitRect.height / 2;
|
|
2381
|
-
var hitPointer = hitAxis === 'x' ? clientX : clientY;
|
|
2382
|
-
return {
|
|
2383
|
-
anchor: hit,
|
|
2384
|
-
placement: hitPointer < hitCenter ? 'before' : 'after',
|
|
2385
|
-
axis: hitAxis,
|
|
2386
|
-
};
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
var parent = el.parentElement;
|
|
2390
|
-
var axis = parentFlowAxis(parent);
|
|
2391
|
-
var siblings = draggableElementChildren(parent).filter(function(child) {
|
|
2392
|
-
return child !== el;
|
|
2393
|
-
});
|
|
2394
|
-
if (!siblings.length) return null;
|
|
2395
|
-
var beforeTarget = null;
|
|
2396
|
-
for (var i = 0; i < siblings.length; i += 1) {
|
|
2397
|
-
var rect = siblings[i].getBoundingClientRect();
|
|
2398
|
-
var center = axis === 'x' ? rect.left + rect.width / 2 : rect.top + rect.height / 2;
|
|
2399
|
-
var pointer = axis === 'x' ? clientX : clientY;
|
|
2400
|
-
if (pointer < center) {
|
|
2401
|
-
beforeTarget = siblings[i];
|
|
2402
|
-
break;
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2405
|
-
var anchor = beforeTarget || siblings[siblings.length - 1];
|
|
2406
|
-
var placement = beforeTarget ? 'before' : 'after';
|
|
2407
|
-
return { anchor: anchor, placement: placement, axis: axis };
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
function showInsertionGuideFor(target) {
|
|
2411
|
-
if (!target || !target.anchor) {
|
|
2412
|
-
hideInsertionGuide();
|
|
2413
|
-
return;
|
|
2414
|
-
}
|
|
2415
|
-
var rect = target.anchor.getBoundingClientRect();
|
|
2416
|
-
insertionGuide.style.display = 'block';
|
|
2417
|
-
insertionGuide.style.background = 'var(--design-editor-accent-color)';
|
|
2418
|
-
insertionGuide.style.border = '0';
|
|
2419
|
-
insertionGuide.style.borderRadius = '999px';
|
|
2420
|
-
insertionGuide.style.boxShadow = '0 0 0 1px var(--design-editor-accent-color)';
|
|
2421
|
-
if (target.placement === 'inside') {
|
|
2422
|
-
insertionGuide.style.left = rect.left + 'px';
|
|
2423
|
-
insertionGuide.style.top = rect.top + 'px';
|
|
2424
|
-
insertionGuide.style.width = rect.width + 'px';
|
|
2425
|
-
insertionGuide.style.height = rect.height + 'px';
|
|
2426
|
-
insertionGuide.style.background = 'color-mix(in srgb, var(--design-editor-accent-color) 14%, transparent)';
|
|
2427
|
-
insertionGuide.style.border = '2px solid var(--design-editor-accent-color)';
|
|
2428
|
-
insertionGuide.style.borderRadius = '2px';
|
|
2429
|
-
insertionGuide.style.boxShadow = 'none';
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
if (target.axis === 'x') {
|
|
2433
|
-
var x = target.placement === 'before' ? rect.left : rect.right;
|
|
2434
|
-
insertionGuide.style.left = x + 'px';
|
|
2435
|
-
insertionGuide.style.top = rect.top + 'px';
|
|
2436
|
-
insertionGuide.style.width = '2px';
|
|
2437
|
-
insertionGuide.style.height = rect.height + 'px';
|
|
2438
|
-
} else {
|
|
2439
|
-
var y = target.placement === 'before' ? rect.top : rect.bottom;
|
|
2440
|
-
insertionGuide.style.left = rect.left + 'px';
|
|
2441
|
-
insertionGuide.style.top = y + 'px';
|
|
2442
|
-
insertionGuide.style.width = rect.width + 'px';
|
|
2443
|
-
insertionGuide.style.height = '2px';
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
81
|
+
* Source: app/components/design/bridge/motion-preview.bridge.ts
|
|
82
|
+
* Compiled: .generated/bridge/motion-preview.generated.ts (run bridge/codegen.ts to update)
|
|
83
|
+
*/
|
|
84
|
+
const MOTION_PREVIEW_BRIDGE_SCRIPT = `
|
|
85
|
+
<script data-agent-native-motion-preview-bridge>
|
|
86
|
+
${motionPreviewBridgeScript}
|
|
87
|
+
</script>
|
|
88
|
+
`;
|
|
2446
89
|
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Shader-fill preview bridge. ALWAYS injected alongside the other bridge
|
|
92
|
+
* scripts so the parent can apply a CSS gradient approximation of a shader
|
|
93
|
+
* fill to the currently-selected element without persisting anything.
|
|
94
|
+
*
|
|
95
|
+
* Source: app/components/design/bridge/shader-fill-preview.bridge.ts
|
|
96
|
+
* Compiled: .generated/bridge/shader-fill-preview.generated.ts (run bridge/codegen.ts to update)
|
|
97
|
+
*/
|
|
98
|
+
const SHADER_FILL_PREVIEW_BRIDGE_SCRIPT = `
|
|
99
|
+
<script data-agent-native-shader-fill-preview-bridge>
|
|
100
|
+
${shaderFillPreviewBridgeScript}
|
|
101
|
+
</script>
|
|
102
|
+
`;
|
|
2460
103
|
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
}, '*');
|
|
2475
|
-
}
|
|
104
|
+
/**
|
|
105
|
+
* Tweak bridge. ALWAYS injected so the parent's postMessage (`tweak-values`)
|
|
106
|
+
* can update CSS custom properties on the iframe's :root regardless of which
|
|
107
|
+
* editor mode is active.
|
|
108
|
+
*
|
|
109
|
+
* Source: app/components/design/bridge/tweak.bridge.ts
|
|
110
|
+
* Compiled: .generated/bridge/tweak.generated.ts (run bridge/codegen.ts to update)
|
|
111
|
+
*/
|
|
112
|
+
const TWEAK_BRIDGE_SCRIPT = `
|
|
113
|
+
<script data-agent-native-tweak-bridge>
|
|
114
|
+
${tweakBridgeScript}
|
|
115
|
+
</script>
|
|
116
|
+
`;
|
|
2476
117
|
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
}
|
|
118
|
+
/**
|
|
119
|
+
* Pinch-zoom bridge: forwards trackpad pinch / Cmd-Ctrl+scroll wheel events
|
|
120
|
+
* from inside the iframe to the parent window.
|
|
121
|
+
*
|
|
122
|
+
* Source: app/components/design/bridge/zoom.bridge.ts
|
|
123
|
+
* Compiled: .generated/bridge/zoom.generated.ts (run bridge/codegen.ts to update)
|
|
124
|
+
*/
|
|
125
|
+
const ZOOM_BRIDGE_SCRIPT = `
|
|
126
|
+
<script data-agent-native-zoom-bridge>
|
|
127
|
+
${zoomBridgeScript}
|
|
128
|
+
</script>
|
|
129
|
+
`;
|
|
2490
130
|
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
selectedEl.parentElement.insertBefore(clone, selectedEl.nextSibling);
|
|
2504
|
-
selectedEl = clone;
|
|
2505
|
-
duplicatedForDrag = true;
|
|
2506
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
2507
|
-
window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
|
|
2508
|
-
}
|
|
2509
|
-
if (isFlowReorderCandidate(selectedEl)) {
|
|
2510
|
-
// Snapshot the element being reordered so a concurrent select-element or
|
|
2511
|
-
// clear-selection postMessage cannot mutate the wrong element mid-drag.
|
|
2512
|
-
var reorderEl = selectedEl;
|
|
2513
|
-
var currentTarget = reorderTargetForPoint(reorderEl, e.clientX, e.clientY);
|
|
2514
|
-
showInsertionGuideFor(currentTarget);
|
|
2515
|
-
// Cross-screen drag state: true when the pointer is outside this iframe's
|
|
2516
|
-
// viewport bounds. The host frame renders the ghost + highlight and owns
|
|
2517
|
-
// the drop when this is true; the bridge suppresses its in-iframe reorder.
|
|
2518
|
-
var pointerOutsideIframe = false;
|
|
2519
|
-
var reorderSelector = getSelector(reorderEl);
|
|
2520
|
-
var reorderSourceId = getSourceId(reorderEl);
|
|
2521
|
-
function onReorderMove(ev) {
|
|
2522
|
-
var vw = window.innerWidth;
|
|
2523
|
-
var vh = window.innerHeight;
|
|
2524
|
-
var cx = ev.clientX;
|
|
2525
|
-
var cy = ev.clientY;
|
|
2526
|
-
var outside = cx < 0 || cy < 0 || cx > vw || cy > vh;
|
|
2527
|
-
pointerOutsideIframe = outside;
|
|
2528
|
-
// Always notify the host frame so it can track the cursor position,
|
|
2529
|
-
// render the ghost, and highlight the target screen.
|
|
2530
|
-
window.parent.postMessage({
|
|
2531
|
-
type: 'agent-native:cross-screen-drag',
|
|
2532
|
-
phase: 'move',
|
|
2533
|
-
selector: reorderSelector,
|
|
2534
|
-
sourceId: reorderSourceId,
|
|
2535
|
-
iframeX: cx,
|
|
2536
|
-
iframeY: cy,
|
|
2537
|
-
viewportW: vw,
|
|
2538
|
-
viewportH: vh,
|
|
2539
|
-
}, '*');
|
|
2540
|
-
if (outside) {
|
|
2541
|
-
// Cursor left this iframe — hide the in-iframe insertion guide so
|
|
2542
|
-
// it does not render while the host shows a cross-screen drop target.
|
|
2543
|
-
hideInsertionGuide();
|
|
2544
|
-
showTransformBadge('Move layer', cx, cy);
|
|
2545
|
-
} else {
|
|
2546
|
-
// Cursor is inside this iframe — use existing in-iframe behavior.
|
|
2547
|
-
currentTarget = reorderTargetForPoint(reorderEl, cx, cy);
|
|
2548
|
-
showInsertionGuideFor(currentTarget);
|
|
2549
|
-
showTransformBadge(currentTarget ? 'Move layer' : 'Move', cx, cy);
|
|
2550
|
-
}
|
|
2551
|
-
}
|
|
2552
|
-
function onReorderEscape() {
|
|
2553
|
-
document.removeEventListener(events.move, onReorderMove, true);
|
|
2554
|
-
document.removeEventListener(events.up, onReorderUp, true);
|
|
2555
|
-
document.removeEventListener('keydown', onReorderKeyDown, true);
|
|
2556
|
-
hideTransformBadge();
|
|
2557
|
-
hideInsertionGuide();
|
|
2558
|
-
window.parent.postMessage({ type: 'agent-native:cross-screen-drag', phase: 'cancel' }, '*');
|
|
2559
|
-
// Revert any clone that was inserted for alt-drag.
|
|
2560
|
-
if (duplicatedForDrag && reorderEl && reorderEl !== originalSelectedEl) {
|
|
2561
|
-
if (reorderEl.parentElement) reorderEl.parentElement.removeChild(reorderEl);
|
|
2562
|
-
selectedEl = originalSelectedEl;
|
|
2563
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
2564
|
-
window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
|
-
function onReorderKeyDown(ev) {
|
|
2568
|
-
if (ev.key === 'Escape') {
|
|
2569
|
-
ev.preventDefault();
|
|
2570
|
-
ev.stopPropagation();
|
|
2571
|
-
if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();
|
|
2572
|
-
onReorderEscape();
|
|
2573
|
-
}
|
|
2574
|
-
}
|
|
2575
|
-
function onReorderUp(ev) {
|
|
2576
|
-
document.removeEventListener(events.move, onReorderMove, true);
|
|
2577
|
-
document.removeEventListener(events.up, onReorderUp, true);
|
|
2578
|
-
document.removeEventListener('keydown', onReorderKeyDown, true);
|
|
2579
|
-
hideTransformBadge();
|
|
2580
|
-
hideInsertionGuide();
|
|
2581
|
-
var vw = window.innerWidth;
|
|
2582
|
-
var vh = window.innerHeight;
|
|
2583
|
-
var cx = ev ? ev.clientX : 0;
|
|
2584
|
-
var cy = ev ? ev.clientY : 0;
|
|
2585
|
-
var outsideOnDrop = cx < 0 || cy < 0 || cx > vw || cy > vh;
|
|
2586
|
-
// Post the end message so the host can finalize a cross-screen drop.
|
|
2587
|
-
window.parent.postMessage({
|
|
2588
|
-
type: 'agent-native:cross-screen-drag',
|
|
2589
|
-
phase: 'end',
|
|
2590
|
-
selector: reorderSelector,
|
|
2591
|
-
sourceId: reorderSourceId,
|
|
2592
|
-
iframeX: cx,
|
|
2593
|
-
iframeY: cy,
|
|
2594
|
-
viewportW: vw,
|
|
2595
|
-
viewportH: vh,
|
|
2596
|
-
}, '*');
|
|
2597
|
-
// When the pointer is outside this iframe at release, the host owns the
|
|
2598
|
-
// move (cross-screen drop). Do NOT apply the in-iframe reorder so we
|
|
2599
|
-
// avoid a ghost element left in screen A's DOM.
|
|
2600
|
-
// Use outsideOnDrop only — pointerOutsideIframe is stale when the user
|
|
2601
|
-
// briefly exits the iframe and re-enters before releasing. The host
|
|
2602
|
-
// already clears cross-screen state on re-entry so checking the
|
|
2603
|
-
// momentary excursion flag here would wrongly drop the element nowhere.
|
|
2604
|
-
if (outsideOnDrop) return;
|
|
2605
|
-
if (!currentTarget) {
|
|
2606
|
-
// No valid drop target — clean up the clone if one was inserted so
|
|
2607
|
-
// no ghost element is left in the DOM.
|
|
2608
|
-
if (duplicatedForDrag && reorderEl && reorderEl !== originalSelectedEl) {
|
|
2609
|
-
if (reorderEl.parentElement) reorderEl.parentElement.removeChild(reorderEl);
|
|
2610
|
-
selectedEl = originalSelectedEl;
|
|
2611
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
2612
|
-
window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
|
|
2613
|
-
}
|
|
2614
|
-
return;
|
|
2615
|
-
}
|
|
2616
|
-
if (duplicatedForDrag) {
|
|
2617
|
-
applyRuntimeReorder(reorderEl, currentTarget);
|
|
2618
|
-
postVisualDuplicateChange(originalSelectedEl, reorderEl, currentTarget);
|
|
2619
|
-
} else {
|
|
2620
|
-
// Capture the pre-drag DOM anchor so we can revert if the parent
|
|
2621
|
-
// reports applied===false on the structure-ack.
|
|
2622
|
-
var prevParent = reorderEl.parentElement;
|
|
2623
|
-
var prevNextSibling = reorderEl.nextSibling;
|
|
2624
|
-
// Optimistically apply the reorder in the DOM for immediate
|
|
2625
|
-
// visual feedback; the visual-structure-ack handler will confirm
|
|
2626
|
-
// or revert once the parent processes the change.
|
|
2627
|
-
applyRuntimeReorder(reorderEl, currentTarget);
|
|
2628
|
-
postVisualStructureChange(reorderEl, currentTarget, { prevParent: prevParent, prevNextSibling: prevNextSibling });
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
document.addEventListener(events.move, onReorderMove, true);
|
|
2632
|
-
document.addEventListener(events.up, onReorderUp, true);
|
|
2633
|
-
document.addEventListener('keydown', onReorderKeyDown, true);
|
|
2634
|
-
return;
|
|
2635
|
-
}
|
|
2636
|
-
ensurePositionable(selectedEl);
|
|
2637
|
-
var cs = window.getComputedStyle(selectedEl);
|
|
2638
|
-
var originLeft = readPx(selectedEl.style.left || cs.left);
|
|
2639
|
-
var originTop = readPx(selectedEl.style.top || cs.top);
|
|
2640
|
-
var startX = e.clientX;
|
|
2641
|
-
var startY = e.clientY;
|
|
2642
|
-
// Snapshot the element being moved so that a concurrent select-element or
|
|
2643
|
-
// clear-selection postMessage cannot swap selectedEl mid-drag and cause
|
|
2644
|
-
// mutations on the wrong element or a null-deref in onUp.
|
|
2645
|
-
var dragEl = selectedEl;
|
|
2646
|
-
var moved = false;
|
|
2647
|
-
var DRAG_THRESHOLD = 3;
|
|
2648
|
-
function onMove(ev) {
|
|
2649
|
-
if (!moved && Math.hypot(ev.clientX - startX, ev.clientY - startY) > DRAG_THRESHOLD) {
|
|
2650
|
-
moved = true;
|
|
2651
|
-
}
|
|
2652
|
-
var nextLeft = originLeft + ev.clientX - startX;
|
|
2653
|
-
var nextTop = originTop + ev.clientY - startY;
|
|
2654
|
-
dragEl.style.left = Math.round(nextLeft) + 'px';
|
|
2655
|
-
dragEl.style.top = Math.round(nextTop) + 'px';
|
|
2656
|
-
showTransformBadge('X ' + Math.round(nextLeft) + ' Y ' + Math.round(nextTop), ev.clientX, ev.clientY);
|
|
2657
|
-
refreshOverlays();
|
|
2658
|
-
}
|
|
2659
|
-
function onUp() {
|
|
2660
|
-
document.removeEventListener(events.move, onMove, true);
|
|
2661
|
-
document.removeEventListener(events.up, onUp, true);
|
|
2662
|
-
hideTransformBadge();
|
|
2663
|
-
if (!dragEl) return;
|
|
2664
|
-
if (duplicatedForDrag && !moved) {
|
|
2665
|
-
// Alt-click with no real drag — remove the premature clone and restore the original selection.
|
|
2666
|
-
if (dragEl.parentElement) dragEl.parentElement.removeChild(dragEl);
|
|
2667
|
-
selectedEl = originalSelectedEl;
|
|
2668
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
2669
|
-
window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
|
|
2670
|
-
return;
|
|
2671
|
-
}
|
|
2672
|
-
if (duplicatedForDrag) {
|
|
2673
|
-
postVisualDuplicateChange(originalSelectedEl, dragEl);
|
|
2674
|
-
} else {
|
|
2675
|
-
window.parent.postMessage({
|
|
2676
|
-
type: 'visual-style-change',
|
|
2677
|
-
selector: getSelector(dragEl),
|
|
2678
|
-
styles: {
|
|
2679
|
-
position: dragEl.style.position,
|
|
2680
|
-
left: dragEl.style.left,
|
|
2681
|
-
top: dragEl.style.top,
|
|
2682
|
-
},
|
|
2683
|
-
payload: getElementInfo(dragEl),
|
|
2684
|
-
}, '*');
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2687
|
-
document.addEventListener(events.move, onMove, true);
|
|
2688
|
-
document.addEventListener(events.up, onUp, true);
|
|
2689
|
-
}
|
|
131
|
+
/**
|
|
132
|
+
* Embedded overview bridge. Forwards wheel events from embedded screen iframes
|
|
133
|
+
* to the parent canvas handler so pan/zoom works over design content.
|
|
134
|
+
*
|
|
135
|
+
* Source: app/components/design/bridge/embedded-wheel.bridge.ts
|
|
136
|
+
* Compiled: .generated/bridge/embedded-wheel.generated.ts (run bridge/codegen.ts to update)
|
|
137
|
+
*/
|
|
138
|
+
const EMBEDDED_WHEEL_BRIDGE_SCRIPT = `
|
|
139
|
+
<script data-agent-native-embedded-wheel-bridge>
|
|
140
|
+
${embeddedWheelBridgeScript}
|
|
141
|
+
</script>
|
|
142
|
+
`;
|
|
2690
143
|
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
left: readPx(selectedEl.style.left || cs.left),
|
|
2705
|
-
top: readPx(selectedEl.style.top || cs.top),
|
|
2706
|
-
width: originW,
|
|
2707
|
-
height: originH,
|
|
2708
|
-
ratio: originW / Math.max(1, originH),
|
|
2709
|
-
};
|
|
2710
|
-
var startX = e.clientX;
|
|
2711
|
-
var startY = e.clientY;
|
|
2712
|
-
// Snapshot the element so a concurrent clear-selection postMessage cannot
|
|
2713
|
-
// cause a null-deref in onMove/onUp.
|
|
2714
|
-
var resizeEl = selectedEl;
|
|
2715
|
-
// Capture the element rotation once at drag-start so per-move projection is
|
|
2716
|
-
// cheap and consistent even if the transform changes during the drag.
|
|
2717
|
-
var resizeTheta = currentRotation(resizeEl) * Math.PI / 180;
|
|
2718
|
-
function nextRect(ev) {
|
|
2719
|
-
var screenDx = ev.clientX - startX;
|
|
2720
|
-
var screenDy = ev.clientY - startY;
|
|
2721
|
-
// Project the screen-space pointer delta into the element's local
|
|
2722
|
-
// (un-rotated) coordinate frame so handles behave relative to the
|
|
2723
|
-
// visible rotated box rather than screen axes.
|
|
2724
|
-
var cosT = Math.cos(resizeTheta);
|
|
2725
|
-
var sinT = Math.sin(resizeTheta);
|
|
2726
|
-
var dx = screenDx * cosT + screenDy * sinT;
|
|
2727
|
-
var dy = -screenDx * sinT + screenDy * cosT;
|
|
2728
|
-
var left = origin.left;
|
|
2729
|
-
var top = origin.top;
|
|
2730
|
-
var width = origin.width;
|
|
2731
|
-
var height = origin.height;
|
|
2732
|
-
if (handle.indexOf('w') !== -1) {
|
|
2733
|
-
left = origin.left + dx;
|
|
2734
|
-
width = origin.width - dx;
|
|
2735
|
-
}
|
|
2736
|
-
if (handle.indexOf('e') !== -1) width = origin.width + dx;
|
|
2737
|
-
if (handle.indexOf('n') !== -1) {
|
|
2738
|
-
top = origin.top + dy;
|
|
2739
|
-
height = origin.height - dy;
|
|
2740
|
-
}
|
|
2741
|
-
if (handle.indexOf('s') !== -1) height = origin.height + dy;
|
|
2742
|
-
// Apply Shift / scaleToolEnabled aspect-ratio lock BEFORE the min-size
|
|
2743
|
-
// clamp so the ratio is computed from unclamped values (bug fix).
|
|
2744
|
-
if (ev.shiftKey) {
|
|
2745
|
-
// Shift locks aspect ratio for ALL 8 handles (corners and edges).
|
|
2746
|
-
if (handle === 'e' || handle === 'w') {
|
|
2747
|
-
height = width / origin.ratio;
|
|
2748
|
-
} else if (handle === 'n' || handle === 's') {
|
|
2749
|
-
width = height * origin.ratio;
|
|
2750
|
-
} else if (handle.length === 2) {
|
|
2751
|
-
if (Math.abs(dx) > Math.abs(dy)) height = width / origin.ratio;
|
|
2752
|
-
else width = height * origin.ratio;
|
|
2753
|
-
}
|
|
2754
|
-
}
|
|
2755
|
-
if (scaleToolEnabled) {
|
|
2756
|
-
// Scale tool: enforce aspect ratio on all 8 handles, not just corners.
|
|
2757
|
-
if (handle === 'e' || handle === 'w') {
|
|
2758
|
-
height = width / origin.ratio;
|
|
2759
|
-
} else if (handle === 'n' || handle === 's') {
|
|
2760
|
-
width = height * origin.ratio;
|
|
2761
|
-
} else if (handle.length === 2) {
|
|
2762
|
-
if (Math.abs(dx) > Math.abs(dy)) height = width / origin.ratio;
|
|
2763
|
-
else width = height * origin.ratio;
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
// Clamp to minimum size.
|
|
2767
|
-
var clampedW = Math.max(8, width);
|
|
2768
|
-
var clampedH = Math.max(8, height);
|
|
2769
|
-
// After clamping, re-apply the ratio if Shift or scale tool is active so
|
|
2770
|
-
// the clamped dimension doesn't silently break the locked aspect ratio.
|
|
2771
|
-
if (ev.shiftKey || scaleToolEnabled) {
|
|
2772
|
-
if (clampedW !== width) {
|
|
2773
|
-
// Width was clamped; re-derive height from the clamped width.
|
|
2774
|
-
clampedH = Math.max(8, clampedW / origin.ratio);
|
|
2775
|
-
} else if (clampedH !== height) {
|
|
2776
|
-
// Height was clamped; re-derive width from the clamped height.
|
|
2777
|
-
clampedW = Math.max(8, clampedH * origin.ratio);
|
|
2778
|
-
}
|
|
2779
|
-
}
|
|
2780
|
-
width = clampedW;
|
|
2781
|
-
height = clampedH;
|
|
2782
|
-
// Re-anchor the pinned edge for w/n handles after aspect-ratio lock and
|
|
2783
|
-
// clamping so the opposite (e/s) edge stays fixed regardless of whether
|
|
2784
|
-
// the dimension change was driven by raw dx/dy or by the ratio lock.
|
|
2785
|
-
if (handle.indexOf('w') !== -1) left = origin.left + (origin.width - width);
|
|
2786
|
-
if (handle.indexOf('n') !== -1) top = origin.top + (origin.height - height);
|
|
2787
|
-
if (ev.altKey) {
|
|
2788
|
-
if (handle.indexOf('w') !== -1 || handle.indexOf('e') !== -1) left = origin.left - (width - origin.width) / 2;
|
|
2789
|
-
if (handle.indexOf('n') !== -1 || handle.indexOf('s') !== -1) top = origin.top - (height - origin.height) / 2;
|
|
2790
|
-
}
|
|
2791
|
-
return { left: left, top: top, width: width, height: height };
|
|
2792
|
-
}
|
|
2793
|
-
function onMove(ev) {
|
|
2794
|
-
if (!resizeEl) return;
|
|
2795
|
-
var rect = nextRect(ev);
|
|
2796
|
-
resizeEl.style.left = Math.round(rect.left) + 'px';
|
|
2797
|
-
resizeEl.style.top = Math.round(rect.top) + 'px';
|
|
2798
|
-
resizeEl.style.width = Math.round(rect.width) + 'px';
|
|
2799
|
-
resizeEl.style.height = Math.round(rect.height) + 'px';
|
|
2800
|
-
showTransformBadge(Math.round(rect.width) + ' x ' + Math.round(rect.height), ev.clientX, ev.clientY);
|
|
2801
|
-
refreshOverlays();
|
|
2802
|
-
}
|
|
2803
|
-
function onUp() {
|
|
2804
|
-
document.removeEventListener('mousemove', onMove, true);
|
|
2805
|
-
document.removeEventListener('mouseup', onUp, true);
|
|
2806
|
-
hideTransformBadge();
|
|
2807
|
-
if (!resizeEl) return;
|
|
2808
|
-
window.parent.postMessage({
|
|
2809
|
-
type: 'visual-style-change',
|
|
2810
|
-
selector: getSelector(resizeEl),
|
|
2811
|
-
styles: {
|
|
2812
|
-
position: resizeEl.style.position,
|
|
2813
|
-
left: resizeEl.style.left,
|
|
2814
|
-
top: resizeEl.style.top,
|
|
2815
|
-
width: resizeEl.style.width,
|
|
2816
|
-
height: resizeEl.style.height,
|
|
2817
|
-
},
|
|
2818
|
-
payload: getElementInfo(resizeEl),
|
|
2819
|
-
}, '*');
|
|
2820
|
-
}
|
|
2821
|
-
document.addEventListener('mousemove', onMove, true);
|
|
2822
|
-
document.addEventListener('mouseup', onUp, true);
|
|
2823
|
-
}
|
|
144
|
+
/**
|
|
145
|
+
* Navigation bridge. ALWAYS injected. Intercepts link clicks and relative form
|
|
146
|
+
* submits inside prototype iframes so they route to the parent instead of
|
|
147
|
+
* navigating the iframe to the Design app itself.
|
|
148
|
+
*
|
|
149
|
+
* Source: app/components/design/bridge/nav.bridge.ts
|
|
150
|
+
* Compiled: .generated/bridge/nav.generated.ts (run bridge/codegen.ts to update)
|
|
151
|
+
*/
|
|
152
|
+
const NAV_BRIDGE_SCRIPT = `
|
|
153
|
+
<script data-agent-native-nav-bridge>
|
|
154
|
+
${navBridgeScript}
|
|
155
|
+
</script>
|
|
156
|
+
`;
|
|
2824
157
|
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
document.removeEventListener('mouseup', onUp, true);
|
|
2852
|
-
hideTransformBadge();
|
|
2853
|
-
if (!rotateEl) return;
|
|
2854
|
-
window.parent.postMessage({
|
|
2855
|
-
type: 'visual-style-change',
|
|
2856
|
-
selector: getSelector(rotateEl),
|
|
2857
|
-
styles: { transform: rotateEl.style.transform },
|
|
2858
|
-
payload: getElementInfo(rotateEl),
|
|
2859
|
-
}, '*');
|
|
2860
|
-
}
|
|
2861
|
-
document.addEventListener('mousemove', onMove, true);
|
|
2862
|
-
document.addEventListener('mouseup', onUp, true);
|
|
2863
|
-
}
|
|
158
|
+
/**
|
|
159
|
+
* Lightweight hit-test bridge: injected into every screen srcdoc iframe so
|
|
160
|
+
* MultiScreenCanvas can ask "what container is under this point?" during a
|
|
161
|
+
* cross-screen drag without needing to synthesise DOM events.
|
|
162
|
+
*
|
|
163
|
+
* Protocol (parent → iframe via postMessage):
|
|
164
|
+
* { type: 'agent-native:hit-test', correlationId: string, x: number, y: number }
|
|
165
|
+
* where x/y are in this iframe's viewport coordinate space.
|
|
166
|
+
*
|
|
167
|
+
* Reply (iframe → window.parent):
|
|
168
|
+
* { type: 'agent-native:hit-test-result', correlationId: string,
|
|
169
|
+
* anchorNodeId: string, placement: 'before'|'after'|'inside' }
|
|
170
|
+
*
|
|
171
|
+
* Reads DOM only — no mutations, no event interception. The container-drop and
|
|
172
|
+
* placement logic is intentionally kept in sync with the corresponding helpers
|
|
173
|
+
* inside editor-chrome.bridge.ts (search for "// keep in sync with
|
|
174
|
+
* hit-test.bridge.ts" comments there).
|
|
175
|
+
*
|
|
176
|
+
* Source: app/components/design/bridge/hit-test.bridge.ts
|
|
177
|
+
* Compiled: .generated/bridge/hit-test.generated.ts (run bridge/codegen.ts to update)
|
|
178
|
+
*/
|
|
179
|
+
export const LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT = `
|
|
180
|
+
<script data-agent-native-hit-test-bridge>
|
|
181
|
+
${hitTestBridgeScript}
|
|
182
|
+
</script>
|
|
183
|
+
`;
|
|
2864
184
|
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Append the LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT into a complete HTML string.
|
|
187
|
+
* Injects before </body> when present, before </html> as a fallback, or appends
|
|
188
|
+
* at the end. This is the seam MultiScreenCanvas uses to add the hit-test
|
|
189
|
+
* responder to each screen srcdoc without rebuilding the entire document.
|
|
190
|
+
*/
|
|
191
|
+
export function appendHitTestResponder(html: string): string {
|
|
192
|
+
if (html.includes("</body>")) {
|
|
193
|
+
return html.replace(
|
|
194
|
+
"</body>", // i18n-ignore generated iframe HTML marker
|
|
195
|
+
LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT + "</body>", // i18n-ignore generated iframe HTML injection
|
|
196
|
+
);
|
|
2870
197
|
}
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
var hit = elementFromEditorPoint(e.clientX, e.clientY);
|
|
2877
|
-
if (!hit || hit === document.body || hit === document.documentElement) return;
|
|
2878
|
-
var dragTarget =
|
|
2879
|
-
selectedEl &&
|
|
2880
|
-
document.documentElement.contains(selectedEl) &&
|
|
2881
|
-
selectedEl.contains(hit)
|
|
2882
|
-
? selectedEl
|
|
2883
|
-
: selectionTargetForHit(hit);
|
|
2884
|
-
if (
|
|
2885
|
-
!dragTarget ||
|
|
2886
|
-
dragTarget === document.body ||
|
|
2887
|
-
dragTarget === document.documentElement ||
|
|
2888
|
-
isLayerInteractionBlocked(dragTarget)
|
|
2889
|
-
) {
|
|
2890
|
-
return;
|
|
2891
|
-
}
|
|
2892
|
-
var startX = e.clientX;
|
|
2893
|
-
var startY = e.clientY;
|
|
2894
|
-
var didStartDrag = false;
|
|
2895
|
-
function selectDragTarget() {
|
|
2896
|
-
selectedEl = dragTarget;
|
|
2897
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
2898
|
-
window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
|
|
2899
|
-
}
|
|
2900
|
-
function onMove(ev) {
|
|
2901
|
-
if (Math.hypot(ev.clientX - startX, ev.clientY - startY) <= 3) return;
|
|
2902
|
-
clearPendingShieldDrag();
|
|
2903
|
-
didStartDrag = true;
|
|
2904
|
-
selectDragTarget();
|
|
2905
|
-
suppressNextShieldClickBriefly();
|
|
2906
|
-
startMove(ev);
|
|
2907
|
-
}
|
|
2908
|
-
function onUp(ev) {
|
|
2909
|
-
clearPendingShieldDrag();
|
|
2910
|
-
if (didStartDrag) return;
|
|
2911
|
-
if (ev) stopNativeInteraction(ev);
|
|
2912
|
-
selectDragTarget();
|
|
2913
|
-
suppressNextShieldClickBriefly();
|
|
2914
|
-
}
|
|
2915
|
-
clearPendingShieldDrag();
|
|
2916
|
-
pendingShieldDrag = { move: events.move, up: events.up, onMove: onMove, onUp: onUp };
|
|
2917
|
-
document.addEventListener(events.move, onMove, true);
|
|
2918
|
-
document.addEventListener(events.up, onUp, true);
|
|
198
|
+
if (html.includes("</html>")) {
|
|
199
|
+
return html.replace(
|
|
200
|
+
"</html>", // i18n-ignore generated iframe HTML marker
|
|
201
|
+
LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT + "</html>", // i18n-ignore generated iframe HTML injection
|
|
202
|
+
);
|
|
2919
203
|
}
|
|
204
|
+
return html + LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT;
|
|
205
|
+
}
|
|
2920
206
|
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
startResize(resizeHandle, e);
|
|
2933
|
-
return;
|
|
2934
|
-
}
|
|
2935
|
-
var rotateHandle = e.target && e.target.getAttribute && e.target.getAttribute('data-agent-native-rotate-handle');
|
|
2936
|
-
if (rotateHandle) {
|
|
2937
|
-
startRotate(e);
|
|
2938
|
-
return;
|
|
2939
|
-
}
|
|
2940
|
-
startMove(e);
|
|
2941
|
-
}, true);
|
|
2942
|
-
|
|
2943
|
-
shieldOverlay.addEventListener('pointerdown', beginPotentialShieldDrag, true);
|
|
2944
|
-
|
|
2945
|
-
['pointerdown','pointerup','mousedown','mouseup','auxclick'].forEach(function(type) {
|
|
2946
|
-
shieldOverlay.addEventListener(type, stopNativeInteraction, true);
|
|
2947
|
-
});
|
|
207
|
+
const EDITOR_BRIDGE_VAR_NAMES = [
|
|
208
|
+
"--design-editor-accent-color",
|
|
209
|
+
"--design-editor-accent-hover-color",
|
|
210
|
+
"--design-editor-selection-color",
|
|
211
|
+
"--design-editor-accent-strong-color",
|
|
212
|
+
"--design-editor-accent-contrast-color",
|
|
213
|
+
"--design-editor-measure-color",
|
|
214
|
+
"--background",
|
|
215
|
+
"--foreground",
|
|
216
|
+
"--border",
|
|
217
|
+
];
|
|
2948
218
|
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
219
|
+
function readEditorBridgeThemeVars(): Record<string, string> {
|
|
220
|
+
if (typeof window === "undefined") return {};
|
|
221
|
+
const styles = window.getComputedStyle(document.documentElement);
|
|
222
|
+
return Object.fromEntries(
|
|
223
|
+
EDITOR_BRIDGE_VAR_NAMES.map((name) => [
|
|
224
|
+
name,
|
|
225
|
+
styles.getPropertyValue(name).trim(),
|
|
226
|
+
]).filter(([, value]) => value.length > 0),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
2955
229
|
|
|
2956
|
-
|
|
2957
|
-
|
|
230
|
+
function createEditorBridgeThemeScript(vars: Record<string, string>) {
|
|
231
|
+
const serializedVars = JSON.stringify(vars).replace(/</g, "\\u003c");
|
|
232
|
+
return `
|
|
233
|
+
<script data-agent-native-editor-theme>
|
|
234
|
+
(function() {
|
|
235
|
+
var vars = ${serializedVars};
|
|
236
|
+
var root = document.documentElement;
|
|
237
|
+
Object.keys(vars).forEach(function(name) {
|
|
238
|
+
root.style.setProperty(name, vars[name]);
|
|
2958
239
|
});
|
|
2959
|
-
|
|
2960
|
-
shieldOverlay.addEventListener('click', selectElementAtEvent, true);
|
|
2961
|
-
shieldOverlay.addEventListener('contextmenu', openContextMenuAtEvent, true);
|
|
2962
|
-
selectionOverlay.addEventListener('contextmenu', openContextMenuAtEvent, true);
|
|
2963
|
-
document.addEventListener('contextmenu', function(e) {
|
|
2964
|
-
if (isOverlayElement(e.target)) return;
|
|
2965
|
-
openContextMenuAtEvent(e);
|
|
2966
|
-
}, true);
|
|
2967
|
-
|
|
2968
|
-
document.addEventListener('keydown', function(e) {
|
|
2969
|
-
if (!shouldForwardDesignHotkey(e)) return;
|
|
2970
|
-
stopNativeInteraction(e);
|
|
2971
|
-
if (e.key === 'Escape') clearRuntimeSelection();
|
|
2972
|
-
window.parent.postMessage({
|
|
2973
|
-
type: 'design-hotkey',
|
|
2974
|
-
key: e.key,
|
|
2975
|
-
code: e.code,
|
|
2976
|
-
metaKey: !!e.metaKey,
|
|
2977
|
-
ctrlKey: !!e.ctrlKey,
|
|
2978
|
-
shiftKey: !!e.shiftKey,
|
|
2979
|
-
altKey: !!e.altKey,
|
|
2980
|
-
repeat: !!e.repeat
|
|
2981
|
-
}, '*');
|
|
2982
|
-
}, true);
|
|
2983
|
-
|
|
2984
|
-
function placeTextCaretFromPoint(target, clientX, clientY) {
|
|
2985
|
-
try {
|
|
2986
|
-
var range = null;
|
|
2987
|
-
if (document.caretRangeFromPoint) {
|
|
2988
|
-
range = document.caretRangeFromPoint(clientX, clientY);
|
|
2989
|
-
} else if (document.caretPositionFromPoint) {
|
|
2990
|
-
var position = document.caretPositionFromPoint(clientX, clientY);
|
|
2991
|
-
if (position) {
|
|
2992
|
-
range = document.createRange();
|
|
2993
|
-
range.setStart(position.offsetNode, position.offset);
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
|
-
if (!range) {
|
|
2997
|
-
range = document.createRange();
|
|
2998
|
-
range.selectNodeContents(target);
|
|
2999
|
-
range.collapse(false);
|
|
3000
|
-
}
|
|
3001
|
-
var selection = window.getSelection();
|
|
3002
|
-
selection.removeAllRanges();
|
|
3003
|
-
selection.addRange(range);
|
|
3004
|
-
} catch (err) {
|
|
3005
|
-
try {
|
|
3006
|
-
var fallbackRange = document.createRange();
|
|
3007
|
-
fallbackRange.selectNodeContents(target);
|
|
3008
|
-
fallbackRange.collapse(false);
|
|
3009
|
-
var fallbackSelection = window.getSelection();
|
|
3010
|
-
fallbackSelection.removeAllRanges();
|
|
3011
|
-
fallbackSelection.addRange(fallbackRange);
|
|
3012
|
-
} catch (_err) {}
|
|
3013
|
-
}
|
|
3014
|
-
}
|
|
3015
|
-
|
|
3016
|
-
function beginTextEditingFromEvent(e) {
|
|
3017
|
-
if (activeTextEditEl && e.target && activeTextEditEl.contains(e.target)) return;
|
|
3018
|
-
if (!textEditingEnabled) {
|
|
3019
|
-
stopNativeInteraction(e);
|
|
3020
|
-
return;
|
|
3021
|
-
}
|
|
3022
|
-
stopNativeInteraction(e);
|
|
3023
|
-
var target = findTextEditTarget(elementFromEditorPoint(e.clientX, e.clientY));
|
|
3024
|
-
if (!target || target.nodeType !== 1) return;
|
|
3025
|
-
// Anchor the selection identity to the nearest source-backed element. Text
|
|
3026
|
-
// editing still operates on the actual target text node, but a later
|
|
3027
|
-
// style edit posts from selectedEl, so it must point at a patchable
|
|
3028
|
-
// code-layer node rather than a runtime-only descendant (which would emit a
|
|
3029
|
-
// brittle body > div:nth-of-type(...) selector that never resolves).
|
|
3030
|
-
selectedEl = selectionTargetForHit(target) || target;
|
|
3031
|
-
var originalText = target.textContent || '';
|
|
3032
|
-
var originalHtml = target.innerHTML || '';
|
|
3033
|
-
var committed = false;
|
|
3034
|
-
activeTextEditEl = target;
|
|
3035
|
-
target.setAttribute('contenteditable', 'true');
|
|
3036
|
-
target.setAttribute('data-agent-native-text-editing', 'true');
|
|
3037
|
-
target.style.cursor = 'text';
|
|
3038
|
-
target.style.outline = '1.5px solid var(--design-editor-accent-color)';
|
|
3039
|
-
target.style.outlineOffset = '2px';
|
|
3040
|
-
setTextEditingPointerPassthrough(true);
|
|
3041
|
-
positionOverlay(selectionOverlay, target);
|
|
3042
|
-
window.parent.postMessage({ type: 'element-select', payload: getElementInfo(target) }, '*');
|
|
3043
|
-
window.parent.postMessage({ type: 'element-dblclick-text', payload: getElementInfo(target) }, '*');
|
|
3044
|
-
postTextEditingState(target, true);
|
|
3045
|
-
|
|
3046
|
-
function finish(commit) {
|
|
3047
|
-
if (committed) return;
|
|
3048
|
-
committed = true;
|
|
3049
|
-
target.removeEventListener('blur', onBlur, true);
|
|
3050
|
-
target.removeEventListener('keydown', onKeyDown, true);
|
|
3051
|
-
target.removeEventListener('paste', onPaste, true);
|
|
3052
|
-
target.removeEventListener('keyup', onSelectionChange, true);
|
|
3053
|
-
target.removeEventListener('mouseup', onSelectionChange, true);
|
|
3054
|
-
document.removeEventListener('selectionchange', onSelectionChange);
|
|
3055
|
-
target.removeAttribute('contenteditable');
|
|
3056
|
-
target.removeAttribute('data-agent-native-text-editing');
|
|
3057
|
-
target.style.cursor = '';
|
|
3058
|
-
target.style.outline = '';
|
|
3059
|
-
target.style.outlineOffset = '';
|
|
3060
|
-
setTextEditingPointerPassthrough(false);
|
|
3061
|
-
if (activeTextEditEl === target) activeTextEditEl = null;
|
|
3062
|
-
postTextEditingState(target, false);
|
|
3063
|
-
if (!commit) {
|
|
3064
|
-
target.innerHTML = originalHtml;
|
|
3065
|
-
refreshOverlays();
|
|
3066
|
-
return;
|
|
3067
|
-
}
|
|
3068
|
-
var next = target.textContent || '';
|
|
3069
|
-
var nextHtml = target.innerHTML || '';
|
|
3070
|
-
refreshOverlays();
|
|
3071
|
-
if (next !== originalText || nextHtml !== originalHtml) {
|
|
3072
|
-
postTextContentChange(target, next, nextHtml);
|
|
3073
|
-
}
|
|
3074
|
-
}
|
|
3075
|
-
|
|
3076
|
-
function onBlur() {
|
|
3077
|
-
finish(true);
|
|
3078
|
-
}
|
|
3079
|
-
|
|
3080
|
-
function onKeyDown(ev) {
|
|
3081
|
-
if (ev.key === 'Escape') {
|
|
3082
|
-
ev.preventDefault();
|
|
3083
|
-
finish(true);
|
|
3084
|
-
target.blur();
|
|
3085
|
-
return;
|
|
3086
|
-
}
|
|
3087
|
-
if (ev.key === 'Enter' && !ev.shiftKey) {
|
|
3088
|
-
ev.preventDefault();
|
|
3089
|
-
finish(true);
|
|
3090
|
-
target.blur();
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
|
|
3094
|
-
function onPaste(ev) {
|
|
3095
|
-
ev.preventDefault();
|
|
3096
|
-
insertPlainTextAtSelection((ev.clipboardData && ev.clipboardData.getData('text/plain')) || '');
|
|
3097
|
-
}
|
|
3098
|
-
|
|
3099
|
-
function onSelectionChange() {
|
|
3100
|
-
postTextEditingState(target, true);
|
|
3101
|
-
}
|
|
3102
|
-
|
|
3103
|
-
target.addEventListener('blur', onBlur, true);
|
|
3104
|
-
target.addEventListener('keydown', onKeyDown, true);
|
|
3105
|
-
target.addEventListener('paste', onPaste, true);
|
|
3106
|
-
target.addEventListener('keyup', onSelectionChange, true);
|
|
3107
|
-
target.addEventListener('mouseup', onSelectionChange, true);
|
|
3108
|
-
document.addEventListener('selectionchange', onSelectionChange);
|
|
3109
|
-
target.focus();
|
|
3110
|
-
placeTextCaretFromPoint(target, e.clientX, e.clientY);
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
shieldOverlay.addEventListener('dblclick', beginTextEditingFromEvent, true);
|
|
3114
|
-
selectionOverlay.addEventListener('dblclick', beginTextEditingFromEvent, true);
|
|
3115
|
-
document.addEventListener('dblclick', function(e) {
|
|
3116
|
-
if (isOverlayElement(e.target)) return;
|
|
3117
|
-
beginTextEditingFromEvent(e);
|
|
3118
|
-
}, true);
|
|
3119
|
-
|
|
3120
|
-
shieldOverlay.addEventListener('pointermove', function(e) {
|
|
3121
|
-
stopNativeInteraction(e);
|
|
3122
|
-
hoveredEl = elementFromEditorPoint(e.clientX, e.clientY);
|
|
3123
|
-
if (!hoveredEl) {
|
|
3124
|
-
highlightOverlay.style.display = 'none';
|
|
3125
|
-
if (!spacingDrag) {
|
|
3126
|
-
selectedSpacingHovered = false;
|
|
3127
|
-
hoveredSpacingHandleKey = '';
|
|
3128
|
-
updateSpacingOverlay(selectedEl);
|
|
3129
|
-
}
|
|
3130
|
-
hideMeasurements();
|
|
3131
|
-
return;
|
|
3132
|
-
}
|
|
3133
|
-
if (hoveredEl && hoveredEl.closest('[data-agent-native-text-editing]')) return;
|
|
3134
|
-
if (!spacingDrag) {
|
|
3135
|
-
selectedSpacingHovered = Boolean(
|
|
3136
|
-
selectedEl &&
|
|
3137
|
-
hoveredEl &&
|
|
3138
|
-
(hoveredEl === selectedEl ||
|
|
3139
|
-
(selectedEl.contains && selectedEl.contains(hoveredEl))),
|
|
3140
|
-
);
|
|
3141
|
-
if (!selectedSpacingHovered) hoveredSpacingHandleKey = '';
|
|
3142
|
-
updateSpacingOverlay(selectedEl);
|
|
3143
|
-
}
|
|
3144
|
-
if (hoveredEl === selectedEl) {
|
|
3145
|
-
highlightOverlay.style.display = 'none';
|
|
3146
|
-
} else {
|
|
3147
|
-
positionOverlay(highlightOverlay, hoveredEl);
|
|
3148
|
-
}
|
|
3149
|
-
if (e.altKey && selectedEl && hoveredEl && selectedEl !== hoveredEl) {
|
|
3150
|
-
showMeasurements(selectedEl, hoveredEl);
|
|
3151
|
-
} else {
|
|
3152
|
-
hideMeasurements();
|
|
3153
|
-
}
|
|
3154
|
-
var info = getElementInfo(hoveredEl);
|
|
3155
|
-
window.parent.postMessage({ type: 'element-hover', payload: info }, '*');
|
|
3156
|
-
}, true);
|
|
3157
|
-
|
|
3158
|
-
shieldOverlay.addEventListener('pointerleave', function(e) {
|
|
3159
|
-
stopNativeInteraction(e);
|
|
3160
|
-
hoveredEl = null;
|
|
3161
|
-
if (!spacingDrag) {
|
|
3162
|
-
selectedSpacingHovered = false;
|
|
3163
|
-
hoveredSpacingHandleKey = '';
|
|
3164
|
-
updateSpacingOverlay(selectedEl);
|
|
3165
|
-
}
|
|
3166
|
-
highlightOverlay.style.display = 'none';
|
|
3167
|
-
hideMeasurements();
|
|
3168
|
-
window.parent.postMessage({ type: 'element-hover', payload: null }, '*');
|
|
3169
|
-
}, true);
|
|
3170
|
-
|
|
3171
|
-
window.addEventListener('keyup', function(e) {
|
|
3172
|
-
if (e.key === 'Alt') hideMeasurements();
|
|
3173
|
-
}, true);
|
|
3174
|
-
|
|
3175
|
-
window.addEventListener('message', function(e) {
|
|
3176
|
-
if (e.source !== window.parent) return;
|
|
3177
|
-
if (!e.data) return;
|
|
3178
|
-
if (e.data.payload && typeof e.data.payload === 'object') {
|
|
3179
|
-
Object.keys(e.data.payload).forEach(function(key) {
|
|
3180
|
-
if (e.data[key] === undefined) e.data[key] = e.data.payload[key];
|
|
3181
|
-
});
|
|
3182
|
-
}
|
|
3183
|
-
if (e.data.type === 'set-editor-chrome-scale') {
|
|
3184
|
-
// Live-update the constant-size chrome scale WITHOUT rebuilding srcdoc.
|
|
3185
|
-
// Rebuilding srcdoc reloads the iframe and flashes the content white.
|
|
3186
|
-
editorChromeScaleX = Math.max(0.05, Number(e.data.scaleX) || 1);
|
|
3187
|
-
editorChromeScaleY = Math.max(0.05, Number(e.data.scaleY) || editorChromeScaleX);
|
|
3188
|
-
applyEditorChromeScale();
|
|
3189
|
-
if (selectedEl || hoveredEl) refreshOverlays();
|
|
3190
|
-
return;
|
|
3191
|
-
}
|
|
3192
|
-
if (e.data.type === 'scale-tool-mode') {
|
|
3193
|
-
scaleToolEnabled = !!e.data.enabled;
|
|
3194
|
-
return;
|
|
3195
|
-
}
|
|
3196
|
-
if (e.data.type === 'clear-selection') {
|
|
3197
|
-
clearRuntimeSelection();
|
|
3198
|
-
return;
|
|
3199
|
-
}
|
|
3200
|
-
if (e.data.type === 'select-element') {
|
|
3201
|
-
var candidates = [];
|
|
3202
|
-
if (Array.isArray(e.data.selectorCandidates)) {
|
|
3203
|
-
e.data.selectorCandidates.forEach(function(selector) {
|
|
3204
|
-
if (typeof selector === 'string' && selector && candidates.indexOf(selector) === -1) {
|
|
3205
|
-
candidates.push(selector);
|
|
3206
|
-
}
|
|
3207
|
-
});
|
|
3208
|
-
}
|
|
3209
|
-
if (e.data.selector && candidates.indexOf(String(e.data.selector)) === -1) {
|
|
3210
|
-
candidates.push(String(e.data.selector));
|
|
3211
|
-
}
|
|
3212
|
-
var target =
|
|
3213
|
-
selectedEl &&
|
|
3214
|
-
document.documentElement.contains(selectedEl) &&
|
|
3215
|
-
matchesExactSelectorList(selectedEl, candidates)
|
|
3216
|
-
? selectedEl
|
|
3217
|
-
: null;
|
|
3218
|
-
for (var i = 0; i < candidates.length && !target; i += 1) {
|
|
3219
|
-
try {
|
|
3220
|
-
var matches = document.querySelectorAll(candidates[i]);
|
|
3221
|
-
for (var j = 0; j < matches.length; j += 1) {
|
|
3222
|
-
if (!isLayerInteractionBlocked(matches[j])) {
|
|
3223
|
-
target = matches[j];
|
|
3224
|
-
break;
|
|
3225
|
-
}
|
|
3226
|
-
}
|
|
3227
|
-
} catch (_err) {}
|
|
3228
|
-
}
|
|
3229
|
-
if (!target) return;
|
|
3230
|
-
selectedSpacingHovered = false;
|
|
3231
|
-
hoveredSpacingHandleKey = '';
|
|
3232
|
-
selectedEl = target;
|
|
3233
|
-
positionOverlay(selectionOverlay, target);
|
|
3234
|
-
if (hoveredEl === selectedEl) highlightOverlay.style.display = 'none';
|
|
3235
|
-
return;
|
|
3236
|
-
}
|
|
3237
|
-
if (e.data.type === 'hover-element') {
|
|
3238
|
-
var hoverCandidates = [];
|
|
3239
|
-
if (Array.isArray(e.data.selectorCandidates)) {
|
|
3240
|
-
e.data.selectorCandidates.forEach(function(selector) {
|
|
3241
|
-
if (typeof selector === 'string' && selector && hoverCandidates.indexOf(selector) === -1) {
|
|
3242
|
-
hoverCandidates.push(selector);
|
|
3243
|
-
}
|
|
3244
|
-
});
|
|
3245
|
-
}
|
|
3246
|
-
if (e.data.selector && hoverCandidates.indexOf(String(e.data.selector)) === -1) {
|
|
3247
|
-
hoverCandidates.push(String(e.data.selector));
|
|
3248
|
-
}
|
|
3249
|
-
if (hoverCandidates.length === 0) {
|
|
3250
|
-
hoveredEl = null;
|
|
3251
|
-
highlightOverlay.style.display = 'none';
|
|
3252
|
-
hideMeasurements();
|
|
3253
|
-
return;
|
|
3254
|
-
}
|
|
3255
|
-
var hoverTarget = findRuntimeTarget(String(e.data.selector || ''), hoverCandidates);
|
|
3256
|
-
hoveredEl = hoverTarget;
|
|
3257
|
-
if (hoveredEl && !isLayerInteractionBlocked(hoveredEl) && hoveredEl !== selectedEl) {
|
|
3258
|
-
positionOverlay(highlightOverlay, hoveredEl);
|
|
3259
|
-
} else {
|
|
3260
|
-
highlightOverlay.style.display = 'none';
|
|
3261
|
-
hideMeasurements();
|
|
3262
|
-
}
|
|
3263
|
-
return;
|
|
3264
|
-
}
|
|
3265
|
-
if (e.data.type === 'layer-states') {
|
|
3266
|
-
lockedSelectors = Array.isArray(e.data.lockedSelectors) ? e.data.lockedSelectors.filter(function(item) { return typeof item === 'string'; }) : [];
|
|
3267
|
-
hiddenSelectors = Array.isArray(e.data.hiddenSelectors) ? e.data.hiddenSelectors.filter(function(item) { return typeof item === 'string'; }) : [];
|
|
3268
|
-
if (selectedEl && isLayerInteractionBlocked(selectedEl)) {
|
|
3269
|
-
selectedEl = null;
|
|
3270
|
-
selectionOverlay.style.display = 'none';
|
|
3271
|
-
clearComponentTag();
|
|
3272
|
-
}
|
|
3273
|
-
if (hoveredEl && isLayerInteractionBlocked(hoveredEl)) {
|
|
3274
|
-
hoveredEl = null;
|
|
3275
|
-
highlightOverlay.style.display = 'none';
|
|
3276
|
-
}
|
|
3277
|
-
applyHiddenSelectors();
|
|
3278
|
-
return;
|
|
3279
|
-
}
|
|
3280
|
-
if (e.data.type === 'visual-structure-ack') {
|
|
3281
|
-
if (!pendingStructureMove || e.data.requestId !== pendingStructureMove.requestId) return;
|
|
3282
|
-
var move = pendingStructureMove;
|
|
3283
|
-
pendingStructureMove = null;
|
|
3284
|
-
if (e.data.applied) {
|
|
3285
|
-
if (move.el && move.el.isConnected) {
|
|
3286
|
-
applyRuntimeReorder(move.el, move.target);
|
|
3287
|
-
selectedEl = move.el;
|
|
3288
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
3289
|
-
window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
|
|
3290
|
-
}
|
|
3291
|
-
} else {
|
|
3292
|
-
// Revert the optimistic reorder to its pre-drag position.
|
|
3293
|
-
if (move.el && move.el.isConnected && move.origin && move.origin.prevParent && move.origin.prevParent.isConnected) {
|
|
3294
|
-
move.origin.prevParent.insertBefore(move.el, move.origin.prevNextSibling);
|
|
3295
|
-
selectedEl = move.el;
|
|
3296
|
-
positionOverlay(selectionOverlay, selectedEl);
|
|
3297
|
-
window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
|
|
3298
|
-
}
|
|
3299
|
-
}
|
|
3300
|
-
return;
|
|
3301
|
-
}
|
|
3302
|
-
if (e.data.type === 'replace-document-content') {
|
|
3303
|
-
replaceRuntimeDocument(
|
|
3304
|
-
e.data.content,
|
|
3305
|
-
e.data.forceFullDocument ? '' : e.data.selectedSelector,
|
|
3306
|
-
e.data.forceFullDocument ? [] : e.data.selectorCandidates
|
|
3307
|
-
);
|
|
3308
|
-
return;
|
|
3309
|
-
}
|
|
3310
|
-
if (e.data.type === 'delete-element') {
|
|
3311
|
-
removeRuntimeTarget(e.data.selector, e.data.selectorCandidates);
|
|
3312
|
-
return;
|
|
3313
|
-
}
|
|
3314
|
-
if (e.data.type !== 'style-change') return;
|
|
3315
|
-
var sel = e.data.selector;
|
|
3316
|
-
var prop = e.data.property;
|
|
3317
|
-
var val = e.data.value;
|
|
3318
|
-
var candidatesForStyle = Array.isArray(e.data.selectorCandidates) ? e.data.selectorCandidates : [];
|
|
3319
|
-
if (sel && candidatesForStyle.indexOf(String(sel)) === -1) candidatesForStyle.push(String(sel));
|
|
3320
|
-
if (e.data.nodeId) {
|
|
3321
|
-
candidatesForStyle.push('[data-agent-native-node-id="' + String(e.data.nodeId).replace(/"/g, '\\\\"') + '"]');
|
|
3322
|
-
}
|
|
3323
|
-
var el = findRuntimeTarget(String(sel || ''), candidatesForStyle);
|
|
3324
|
-
if (prop && activeTextEditEl && el === activeTextEditEl && applyTextRangeStyle(prop, val)) {
|
|
3325
|
-
postTextContentChange(activeTextEditEl, activeTextEditEl.textContent || '', activeTextEditEl.innerHTML || '');
|
|
3326
|
-
refreshOverlays();
|
|
3327
|
-
return;
|
|
3328
|
-
}
|
|
3329
|
-
if (!el) return;
|
|
3330
|
-
var didPatchDom = false;
|
|
3331
|
-
var attributeOverrides = e.data.attributeOverrides;
|
|
3332
|
-
if (attributeOverrides && typeof attributeOverrides === 'object' && !Array.isArray(attributeOverrides)) {
|
|
3333
|
-
Object.keys(attributeOverrides).forEach(function(name) {
|
|
3334
|
-
if (!/^(?!on)[a-zA-Z][a-zA-Z0-9:_.-]*$/i.test(name)) return;
|
|
3335
|
-
var nextValue = attributeOverrides[name];
|
|
3336
|
-
if (nextValue === null || nextValue === undefined || nextValue === false) {
|
|
3337
|
-
el.removeAttribute(name);
|
|
3338
|
-
} else {
|
|
3339
|
-
el.setAttribute(name, String(nextValue));
|
|
3340
|
-
}
|
|
3341
|
-
didPatchDom = true;
|
|
3342
|
-
});
|
|
3343
|
-
}
|
|
3344
|
-
var classEdit = e.data.classEdit;
|
|
3345
|
-
if (classEdit && typeof classEdit === 'object') {
|
|
3346
|
-
var currentClass = (el.getAttribute('class') || '').trim().split(/\\s+/).filter(Boolean);
|
|
3347
|
-
var nextClass = currentClass.slice();
|
|
3348
|
-
if (classEdit.operation === 'replace' && classEdit.from && classEdit.to) {
|
|
3349
|
-
var replaced = false;
|
|
3350
|
-
nextClass = currentClass.map(function(token) {
|
|
3351
|
-
if (token === classEdit.from) {
|
|
3352
|
-
replaced = true;
|
|
3353
|
-
return String(classEdit.to);
|
|
3354
|
-
}
|
|
3355
|
-
return token;
|
|
3356
|
-
});
|
|
3357
|
-
if (!replaced && nextClass.indexOf(String(classEdit.to)) === -1) nextClass.push(String(classEdit.to));
|
|
3358
|
-
didPatchDom = true;
|
|
3359
|
-
} else if (classEdit.operation === 'add' && classEdit.className) {
|
|
3360
|
-
if (nextClass.indexOf(String(classEdit.className)) === -1) nextClass.push(String(classEdit.className));
|
|
3361
|
-
didPatchDom = true;
|
|
3362
|
-
} else if (classEdit.operation === 'remove' && classEdit.className) {
|
|
3363
|
-
nextClass = currentClass.filter(function(token) { return token !== String(classEdit.className); });
|
|
3364
|
-
didPatchDom = true;
|
|
3365
|
-
}
|
|
3366
|
-
if (didPatchDom) el.setAttribute('class', nextClass.join(' '));
|
|
3367
|
-
}
|
|
3368
|
-
if (prop && typeof prop === 'string') {
|
|
3369
|
-
applyInlineStyleProperty(el, prop, val);
|
|
3370
|
-
didPatchDom = true;
|
|
3371
|
-
}
|
|
3372
|
-
if (didPatchDom) {
|
|
3373
|
-
refreshOverlays();
|
|
3374
|
-
}
|
|
3375
|
-
});
|
|
3376
|
-
|
|
3377
|
-
window.addEventListener('scroll', refreshOverlays, true);
|
|
3378
|
-
window.addEventListener('resize', refreshOverlays);
|
|
3379
|
-
applyEditorChromeScale();
|
|
3380
240
|
})();
|
|
3381
241
|
</script>
|
|
3382
242
|
`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Editor chrome bridge: blocks native iframe app interaction outside Interact
|
|
247
|
+
* mode and replaces it with element hover/selection overlays. Double-click text
|
|
248
|
+
* editing is enabled only while the editor is specifically in Edit mode.
|
|
249
|
+
*/
|
|
250
|
+
const EDITOR_CHROME_BRIDGE_SCRIPT = `
|
|
251
|
+
<script data-agent-native-editor-chrome-bridge>
|
|
252
|
+
${editorChromeBridgeScript}
|
|
253
|
+
</script>
|
|
254
|
+
`;
|
|
3383
255
|
|
|
3384
256
|
interface DesignCanvasProps {
|
|
3385
257
|
content: string;
|
|
@@ -3702,21 +574,26 @@ export function DesignCanvas({
|
|
|
3702
574
|
});
|
|
3703
575
|
|
|
3704
576
|
// Build the srcdoc. The tweak bridge ALWAYS goes in so the panel works
|
|
3705
|
-
// outside Edit mode. The editor chrome bridge is omitted for Interact
|
|
3706
|
-
//
|
|
577
|
+
// outside Edit mode. The editor chrome bridge is omitted for Interact mode
|
|
578
|
+
// so preview/app users can interact with the app normally.
|
|
579
|
+
//
|
|
580
|
+
// readOnly is intentionally NOT a dep here: the bridge is injected whenever
|
|
581
|
+
// !interactMode and the initial __READ_ONLY__ placeholder is baked from the
|
|
582
|
+
// *first* render only. After that, live readOnly changes flow through the
|
|
583
|
+
// set-read-only postMessage (see the useEffect below) so switching the active
|
|
584
|
+
// surface (board ↔ screen) never rebuilds srcdoc / reloads the iframe.
|
|
3707
585
|
const srcdoc = useMemo(() => {
|
|
3708
586
|
if (externalPreviewUrl) return undefined;
|
|
3709
|
-
const editorChromeBridge =
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
)
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
.replace("__EDITOR_CHROME_SCALE_Y__", String(editorChromeScaleY));
|
|
587
|
+
const editorChromeBridge = interactMode
|
|
588
|
+
? ""
|
|
589
|
+
: createEditorBridgeThemeScript(readEditorBridgeThemeVars()) +
|
|
590
|
+
EDITOR_CHROME_BRIDGE_SCRIPT.replace(
|
|
591
|
+
"__READ_ONLY__",
|
|
592
|
+
readOnly ? "true" : "false",
|
|
593
|
+
)
|
|
594
|
+
.replace("__TEXT_EDITING_ENABLED__", editMode ? "true" : "false")
|
|
595
|
+
.replace("__EDITOR_CHROME_SCALE_X__", String(editorChromeScaleX))
|
|
596
|
+
.replace("__EDITOR_CHROME_SCALE_Y__", String(editorChromeScaleY));
|
|
3720
597
|
const embeddedWheelBridge = EMBEDDED_WHEEL_BRIDGE_SCRIPT.replace(
|
|
3721
598
|
"__EMBEDDED_WHEEL_FORWARDING_ENABLED__",
|
|
3722
599
|
isEmbeddedFrame ? "true" : "false",
|
|
@@ -3741,13 +618,13 @@ export function DesignCanvas({
|
|
|
3741
618
|
// baked chrome scale. Live zoom updates flow through the set-editor-chrome-scale
|
|
3742
619
|
// postMessage above. Including them here rebuilds srcdoc on every zoom commit,
|
|
3743
620
|
// which reloads the iframe and flashes the screen content white.
|
|
621
|
+
// readOnly is intentionally NOT a dep: live changes flow through set-read-only.
|
|
3744
622
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3745
623
|
}, [
|
|
3746
624
|
editMode,
|
|
3747
625
|
externalPreviewUrl,
|
|
3748
626
|
interactMode,
|
|
3749
627
|
isEmbeddedFrame,
|
|
3750
|
-
readOnly,
|
|
3751
628
|
renderedContent,
|
|
3752
629
|
]);
|
|
3753
630
|
|
|
@@ -4186,6 +1063,44 @@ export function DesignCanvas({
|
|
|
4186
1063
|
);
|
|
4187
1064
|
}, [editorChromeScaleX, editorChromeScaleY]);
|
|
4188
1065
|
|
|
1066
|
+
// Sync readOnly to the bridge IN-PLACE via postMessage so switching the active
|
|
1067
|
+
// surface (board ↔ screen) does not rebuild srcdoc / reload the iframe.
|
|
1068
|
+
// The initial baked __READ_ONLY__ placeholder covers first paint; subsequent
|
|
1069
|
+
// changes arrive here. Also re-send on iframe load so the bridge is in sync
|
|
1070
|
+
// after any content-key-driven reload.
|
|
1071
|
+
const readOnlyRef = useRef(readOnly);
|
|
1072
|
+
readOnlyRef.current = readOnly;
|
|
1073
|
+
useEffect(() => {
|
|
1074
|
+
const iframe = iframeRef.current;
|
|
1075
|
+
if (!iframe) return;
|
|
1076
|
+
function sendReadOnly() {
|
|
1077
|
+
iframe!.contentWindow?.postMessage(
|
|
1078
|
+
{ type: "set-read-only", readOnly: readOnlyRef.current },
|
|
1079
|
+
"*",
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
sendReadOnly();
|
|
1083
|
+
iframe.addEventListener("load", sendReadOnly);
|
|
1084
|
+
return () => iframe.removeEventListener("load", sendReadOnly);
|
|
1085
|
+
// Only re-run when readOnly changes; iframe identity is stable.
|
|
1086
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1087
|
+
}, [readOnly]);
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* Trigger immediate text-editing mode for a specific node inside the iframe,
|
|
1091
|
+
* identified by its data-agent-native-node-id value. Call this after
|
|
1092
|
+
* programmatically creating a TEXT primitive so the user can type right away
|
|
1093
|
+
* without a second click.
|
|
1094
|
+
*
|
|
1095
|
+
* Posts { type: 'begin-text-edit', nodeId } to the iframe bridge.
|
|
1096
|
+
* The bridge enters the same contenteditable text-editing path used on dblclick.
|
|
1097
|
+
*/
|
|
1098
|
+
const beginTextEdit = useCallback((nodeId: string) => {
|
|
1099
|
+
const iframe = iframeRef.current;
|
|
1100
|
+
if (!iframe?.contentWindow || !nodeId) return;
|
|
1101
|
+
iframe.contentWindow.postMessage({ type: "begin-text-edit", nodeId }, "*");
|
|
1102
|
+
}, []);
|
|
1103
|
+
|
|
4189
1104
|
const sendStyleChange = useCallback(
|
|
4190
1105
|
(
|
|
4191
1106
|
selector: string,
|
|
@@ -4312,6 +1227,9 @@ export function DesignCanvas({
|
|
|
4312
1227
|
(window as any).__designCanvasSendShaderFillPreview = sendShaderFillPreview;
|
|
4313
1228
|
(window as any).__designCanvasClearShaderFillPreview =
|
|
4314
1229
|
clearShaderFillPreview;
|
|
1230
|
+
// Imperative text-edit entry — call after creating a TEXT primitive so the
|
|
1231
|
+
// user can type immediately without a second click.
|
|
1232
|
+
(window as any).__designCanvasBeginTextEdit = beginTextEdit;
|
|
4315
1233
|
return () => {
|
|
4316
1234
|
// Identity-guard each delete so a stale unmounting instance never clobbers
|
|
4317
1235
|
// a freshly mounted instance's bridge during a remount race.
|
|
@@ -4350,6 +1268,9 @@ export function DesignCanvas({
|
|
|
4350
1268
|
) {
|
|
4351
1269
|
delete (window as any).__designCanvasClearShaderFillPreview;
|
|
4352
1270
|
}
|
|
1271
|
+
if ((window as any).__designCanvasBeginTextEdit === beginTextEdit) {
|
|
1272
|
+
delete (window as any).__designCanvasBeginTextEdit;
|
|
1273
|
+
}
|
|
4353
1274
|
};
|
|
4354
1275
|
}, [
|
|
4355
1276
|
deleteRuntimeElement,
|
|
@@ -4360,6 +1281,7 @@ export function DesignCanvas({
|
|
|
4360
1281
|
clearMotionPreview,
|
|
4361
1282
|
sendShaderFillPreview,
|
|
4362
1283
|
clearShaderFillPreview,
|
|
1284
|
+
beginTextEdit,
|
|
4363
1285
|
]);
|
|
4364
1286
|
|
|
4365
1287
|
// Device dimensions match real-world devices. iframes are replaced elements
|