@agent-native/core 0.80.10 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +53 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +108 -15
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/AssistantChat.tsx +123 -26
- 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/sse-event-processor.ts +9 -0
- 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/.agents/skills/image-generation/SKILL.md +9 -2
- package/corpus/templates/assets/AGENTS.md +4 -0
- package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
- package/corpus/templates/assets/actions/generate-image.ts +118 -67
- package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -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/i18n/zh-TW.ts +7 -0
- package/corpus/templates/assets/app/i18n-data.ts +61 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +233 -52
- package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/assets/server/lib/generation.ts +135 -11
- package/corpus/templates/assets/shared/api.ts +4 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
- 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 +76 -25
- package/corpus/templates/design/actions/apply-design-state.ts +17 -4
- package/corpus/templates/design/actions/apply-motion-edit.ts +76 -115
- 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/delete-design.ts +20 -0
- 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 +21 -26
- 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 +18 -5
- 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-design-states.ts +19 -1
- 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/open-component-source.ts +7 -2
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +32 -26
- package/corpus/templates/design/actions/preview-shader-fill.ts +9 -11
- 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 +74 -10
- 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 +244 -3197
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +795 -289
- package/corpus/templates/design/app/components/design/EditPanel.tsx +635 -234
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +124 -32
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +244 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +491 -56
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +55 -67
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +38 -63
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +264 -8
- 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 +22 -0
- package/corpus/templates/design/app/i18n-data.ts +369 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +3024 -616
- 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-token-edits-now-stay-visible-in-previews-and-token-li.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/global-setup.ts +94 -2
- 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/component-model.ts +35 -0
- package/corpus/templates/design/shared/motion-compiler.ts +137 -15
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
- 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/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +94 -10
- package/dist/agent/production-agent.js.map +1 -1
- 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/AssistantChat.d.ts +14 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +115 -22
- package/dist/client/AssistantChat.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/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/sse-event-processor.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/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- 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/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- 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
|
@@ -0,0 +1,4037 @@
|
|
|
1
|
+
// @ts-nocheck -- extracted from the inline editor bridge; typed cleanup follows after the migration lands.
|
|
2
|
+
/**
|
|
3
|
+
* Editor chrome bridge — injected into the live-edit canvas iframe.
|
|
4
|
+
*
|
|
5
|
+
* This file is the TypeScript source for the editor chrome bridge that was
|
|
6
|
+
* previously inlined as a template literal in DesignCanvas.tsx. It compiles
|
|
7
|
+
* to a self-contained IIFE string via bridge/codegen.ts.
|
|
8
|
+
*
|
|
9
|
+
* Runtime placeholders (replaced by DesignCanvas.tsx before injection):
|
|
10
|
+
* __READ_ONLY__ — boolean literal "true"/"false"
|
|
11
|
+
* __TEXT_EDITING_ENABLED__ — boolean literal "true"/"false"
|
|
12
|
+
* __EDITOR_CHROME_SCALE_X__ — number string, e.g. "1.5"
|
|
13
|
+
* __EDITOR_CHROME_SCALE_Y__ — number string, e.g. "1.5"
|
|
14
|
+
*
|
|
15
|
+
* Rules:
|
|
16
|
+
* • No import/require of any module (DOM globals only).
|
|
17
|
+
* • No references to outer/module scope (the code runs inside an iframe).
|
|
18
|
+
* • Wrap everything in a self-executing IIFE.
|
|
19
|
+
*
|
|
20
|
+
* keep in sync with hit-test.bridge.ts for the shared container/axis/placement
|
|
21
|
+
* helpers (search for "// keep in sync" comments).
|
|
22
|
+
*/
|
|
23
|
+
declare var __READ_ONLY__: boolean;
|
|
24
|
+
declare var __TEXT_EDITING_ENABLED__: boolean;
|
|
25
|
+
declare var __EDITOR_CHROME_SCALE_X__: string;
|
|
26
|
+
declare var __EDITOR_CHROME_SCALE_Y__: string;
|
|
27
|
+
|
|
28
|
+
(function () {
|
|
29
|
+
var readOnly = __READ_ONLY__;
|
|
30
|
+
var textEditingEnabled = !readOnly && __TEXT_EDITING_ENABLED__;
|
|
31
|
+
var scaleToolEnabled = false;
|
|
32
|
+
var editorChromeScaleX = Math.max(
|
|
33
|
+
0.05,
|
|
34
|
+
Number(__EDITOR_CHROME_SCALE_X__) || 1,
|
|
35
|
+
);
|
|
36
|
+
var editorChromeScaleY = Math.max(
|
|
37
|
+
0.05,
|
|
38
|
+
Number(__EDITOR_CHROME_SCALE_Y__) || editorChromeScaleX,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Ease the constant-size selection chrome to its new size when overview zoom
|
|
42
|
+
// settles (parent posts set-editor-chrome-scale), matching the canvas chrome.
|
|
43
|
+
// Only chrome-scale-driven props animate; the overlay's live position is excluded.
|
|
44
|
+
(function () {
|
|
45
|
+
var chromeTransitionStyle = document.createElement("style");
|
|
46
|
+
chromeTransitionStyle.textContent =
|
|
47
|
+
'[data-agent-native-edit-overlay="selection"]{transition:border-width 150ms ease-out}' +
|
|
48
|
+
"[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}" +
|
|
49
|
+
"[data-agent-native-spacing-line]{position:absolute;display:none;pointer-events:none;border-radius:999px}" +
|
|
50
|
+
"[data-agent-native-spacing-region]{position:absolute;display:none;box-sizing:border-box;pointer-events:auto;background-size:6px 6px}" +
|
|
51
|
+
'[data-agent-native-spacing-region][data-orientation="vertical"]{cursor:ew-resize}' +
|
|
52
|
+
'[data-agent-native-spacing-region][data-orientation="horizontal"]{cursor:ns-resize}';
|
|
53
|
+
(document.head || document.documentElement).appendChild(
|
|
54
|
+
chromeTransitionStyle,
|
|
55
|
+
);
|
|
56
|
+
})();
|
|
57
|
+
|
|
58
|
+
function chromeScaleX(): number {
|
|
59
|
+
return 1 / Math.max(0.05, editorChromeScaleX);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function chromeScaleY(): number {
|
|
63
|
+
return 1 / Math.max(0.05, editorChromeScaleY);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function chromeLineScale(): number {
|
|
67
|
+
return 1 / Math.max(0.05, Math.max(editorChromeScaleX, editorChromeScaleY));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function syncEditorChromeScaleVars(): void {
|
|
71
|
+
document.documentElement.style.setProperty(
|
|
72
|
+
"--agent-native-editor-chrome-scale-x",
|
|
73
|
+
String(chromeScaleX()),
|
|
74
|
+
);
|
|
75
|
+
document.documentElement.style.setProperty(
|
|
76
|
+
"--agent-native-editor-chrome-scale-y",
|
|
77
|
+
String(chromeScaleY()),
|
|
78
|
+
);
|
|
79
|
+
document.documentElement.style.setProperty(
|
|
80
|
+
"--agent-native-editor-chrome-line-scale",
|
|
81
|
+
String(chromeLineScale()),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function escapeIdent(value: unknown): string {
|
|
86
|
+
if (window.CSS && typeof window.CSS.escape === "function") {
|
|
87
|
+
return window.CSS.escape(String(value));
|
|
88
|
+
}
|
|
89
|
+
return String(value).replace(/[^a-zA-Z0-9_-]/g, "\\$&");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function escapeAttribute(value: unknown): string {
|
|
93
|
+
return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function attributeSelector(el: Element | null, name: string): string {
|
|
97
|
+
var value = el && el.getAttribute && el.getAttribute(name);
|
|
98
|
+
return value ? "[" + name + '="' + escapeAttribute(value) + '"]' : "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function classSelectorSuffix(el: Element | null, maxCount: number): string {
|
|
102
|
+
if (!el || !el.classList) return "";
|
|
103
|
+
return Array.prototype.slice
|
|
104
|
+
.call(el.classList, 0, maxCount)
|
|
105
|
+
.map(function (token) {
|
|
106
|
+
return "." + escapeIdent(token);
|
|
107
|
+
})
|
|
108
|
+
.join("");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function selectorPart(el: Element | null): string {
|
|
112
|
+
if (!el || !el.tagName) return "";
|
|
113
|
+
var stableSelector =
|
|
114
|
+
attributeSelector(el, "data-agent-native-node-id") ||
|
|
115
|
+
attributeSelector(el, "data-code-layer-id") ||
|
|
116
|
+
attributeSelector(el, "data-layer-id") ||
|
|
117
|
+
attributeSelector(el, "data-builder-id") ||
|
|
118
|
+
attributeSelector(el, "data-loc");
|
|
119
|
+
if (stableSelector) return el.tagName.toLowerCase() + stableSelector;
|
|
120
|
+
if (el.id) return "#" + escapeIdent(el.id);
|
|
121
|
+
var part =
|
|
122
|
+
el.tagName.toLowerCase() + (stableSelector || classSelectorSuffix(el, 2));
|
|
123
|
+
var parent = el.parentElement;
|
|
124
|
+
if (parent) {
|
|
125
|
+
var sameTag = Array.prototype.filter.call(
|
|
126
|
+
parent.children,
|
|
127
|
+
function (child) {
|
|
128
|
+
return child.tagName === el.tagName;
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
if (sameTag.length > 1) {
|
|
132
|
+
part += ":nth-of-type(" + (sameTag.indexOf(el) + 1) + ")";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return part;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function selectorPath(el: Element | null, stopEl?: Element | null): string {
|
|
139
|
+
var parts = [];
|
|
140
|
+
var node = el;
|
|
141
|
+
while (node && node.nodeType === 1) {
|
|
142
|
+
if (node !== stopEl) parts.unshift(selectorPart(node));
|
|
143
|
+
if (node === stopEl) break;
|
|
144
|
+
node = node.parentElement;
|
|
145
|
+
}
|
|
146
|
+
return parts.slice(-5).join(" > ");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getSourceId(el: Element | null): string {
|
|
150
|
+
if (!el || !el.getAttribute) return "";
|
|
151
|
+
return (
|
|
152
|
+
el.getAttribute("data-agent-native-node-id") ||
|
|
153
|
+
el.getAttribute("data-code-layer-id") ||
|
|
154
|
+
el.getAttribute("data-layer-id") ||
|
|
155
|
+
el.getAttribute("data-builder-id") ||
|
|
156
|
+
el.getAttribute("data-loc") ||
|
|
157
|
+
el.id ||
|
|
158
|
+
""
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isDocumentRootElement(el: Element | null): boolean {
|
|
163
|
+
return el === document.body || el === document.documentElement;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function closestStableSourceElement(el: Element | null): Element | null {
|
|
167
|
+
if (!el || !el.closest) return null;
|
|
168
|
+
var stable = el.closest(
|
|
169
|
+
"[data-agent-native-node-id],[data-code-layer-id],[data-layer-id],[data-builder-id],[data-loc]",
|
|
170
|
+
);
|
|
171
|
+
if (!stable || isDocumentRootElement(stable)) return null;
|
|
172
|
+
return stable;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function hasStableOwnSource(el: Element | null): boolean {
|
|
176
|
+
return !!(el && !isDocumentRootElement(el) && getSourceId(el));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function selectionTargetForHit(hit: Element | null): Element | null {
|
|
180
|
+
if (!hit || isDocumentRootElement(hit)) return hit;
|
|
181
|
+
if (selectedEl && hit !== selectedEl && selectedEl.contains(hit))
|
|
182
|
+
return hit;
|
|
183
|
+
if (hasStableOwnSource(hit)) return hit;
|
|
184
|
+
return closestStableSourceElement(hit) || hit;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function freshRuntimeNodeId(prefix: string): string {
|
|
188
|
+
var random = "";
|
|
189
|
+
try {
|
|
190
|
+
if (window.crypto && window.crypto.getRandomValues) {
|
|
191
|
+
var bytes = new Uint32Array(2);
|
|
192
|
+
window.crypto.getRandomValues(bytes);
|
|
193
|
+
random = Array.prototype.map
|
|
194
|
+
.call(bytes, function (part: number) {
|
|
195
|
+
return part.toString(36);
|
|
196
|
+
})
|
|
197
|
+
.join("");
|
|
198
|
+
}
|
|
199
|
+
} catch (_err) {}
|
|
200
|
+
if (!random)
|
|
201
|
+
random = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
202
|
+
return "an-" + String(prefix || "copy") + "-" + random;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function resetRuntimeStableIds(root: Element | null): void {
|
|
206
|
+
if (!root || !root.querySelectorAll) return;
|
|
207
|
+
var nodes = [root].concat(
|
|
208
|
+
Array.prototype.slice.call(
|
|
209
|
+
root.querySelectorAll("[data-agent-native-node-id]"),
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
nodes.forEach(function (node, index) {
|
|
213
|
+
if (node && node.setAttribute) {
|
|
214
|
+
node.setAttribute(
|
|
215
|
+
"data-agent-native-node-id",
|
|
216
|
+
freshRuntimeNodeId(index === 0 ? "copy" : "copy-child"),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getSelector(el: Element | null): string {
|
|
223
|
+
if (!el) return "";
|
|
224
|
+
var stableOwnSelector =
|
|
225
|
+
attributeSelector(el, "data-agent-native-node-id") ||
|
|
226
|
+
attributeSelector(el, "data-code-layer-id") ||
|
|
227
|
+
attributeSelector(el, "data-layer-id") ||
|
|
228
|
+
attributeSelector(el, "data-builder-id") ||
|
|
229
|
+
attributeSelector(el, "data-loc");
|
|
230
|
+
if (stableOwnSelector) return stableOwnSelector;
|
|
231
|
+
|
|
232
|
+
if (el.id) return "#" + escapeIdent(el.id);
|
|
233
|
+
var stableAncestor = closestStableSourceElement(el);
|
|
234
|
+
if (stableAncestor && stableAncestor !== el) {
|
|
235
|
+
var stableAncestorSelector = selectorPart(stableAncestor);
|
|
236
|
+
if (stableAncestorSelector) {
|
|
237
|
+
var descendantPath = selectorPath(el, stableAncestor);
|
|
238
|
+
var descendantParts = descendantPath ? descendantPath.split(" > ") : [];
|
|
239
|
+
if (descendantParts.length) {
|
|
240
|
+
return stableAncestorSelector + " > " + descendantParts.join(" > ");
|
|
241
|
+
}
|
|
242
|
+
return stableAncestorSelector;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return selectorPath(el);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getElementInfo(el: Element): unknown {
|
|
250
|
+
var cs = window.getComputedStyle(el);
|
|
251
|
+
var rect = el.getBoundingClientRect();
|
|
252
|
+
var parentStyles = el.parentElement
|
|
253
|
+
? window.getComputedStyle(el.parentElement)
|
|
254
|
+
: null;
|
|
255
|
+
var parentDisplay = parentStyles ? parentStyles.display : undefined;
|
|
256
|
+
var sourceBacked =
|
|
257
|
+
hasStableOwnSource(el) || !!closestStableSourceElement(el);
|
|
258
|
+
var sourceId = sourceBacked ? getSourceId(el) || getSelector(el) : "";
|
|
259
|
+
var parentLayout = parentStyles
|
|
260
|
+
? {
|
|
261
|
+
display: parentStyles.display,
|
|
262
|
+
flexDirection: parentStyles.flexDirection,
|
|
263
|
+
alignItems: parentStyles.alignItems,
|
|
264
|
+
justifyContent: parentStyles.justifyContent,
|
|
265
|
+
gap: parentStyles.gap,
|
|
266
|
+
gridTemplateColumns: parentStyles.gridTemplateColumns,
|
|
267
|
+
gridTemplateRows: parentStyles.gridTemplateRows,
|
|
268
|
+
position: parentStyles.position,
|
|
269
|
+
}
|
|
270
|
+
: undefined;
|
|
271
|
+
var capabilities = sourceBacked
|
|
272
|
+
? [
|
|
273
|
+
{
|
|
274
|
+
kind: "deterministic-style-edit",
|
|
275
|
+
label: "deterministic-style-edit",
|
|
276
|
+
confidence: 0.92,
|
|
277
|
+
reason:
|
|
278
|
+
"Inline style can be patched and replayed through HMR/collab.",
|
|
279
|
+
},
|
|
280
|
+
]
|
|
281
|
+
: [
|
|
282
|
+
{
|
|
283
|
+
kind: "unsupported",
|
|
284
|
+
label: "runtime-only-element",
|
|
285
|
+
confidence: 0.3,
|
|
286
|
+
reason: "This runtime node is not anchored to a source code layer.",
|
|
287
|
+
},
|
|
288
|
+
];
|
|
289
|
+
if (sourceBacked && el.classList && el.classList.length > 0) {
|
|
290
|
+
capabilities.push({
|
|
291
|
+
kind: "deterministic-class-edit",
|
|
292
|
+
label: "deterministic-class-edit",
|
|
293
|
+
confidence: 0.78,
|
|
294
|
+
reason: "Class tokens are visible on the selected element.",
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
if (
|
|
298
|
+
sourceBacked &&
|
|
299
|
+
(parentDisplay === "flex" ||
|
|
300
|
+
parentDisplay === "inline-flex" ||
|
|
301
|
+
parentDisplay === "grid" ||
|
|
302
|
+
parentDisplay === "inline-grid")
|
|
303
|
+
) {
|
|
304
|
+
capabilities.push({
|
|
305
|
+
kind: "agent-structural-edit",
|
|
306
|
+
label: "agent-structural-edit",
|
|
307
|
+
confidence: 0.54,
|
|
308
|
+
reason:
|
|
309
|
+
"Parent layout context decides whether movement means gap, order, alignment, or wrapper structure.",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
// --- provenance: read source-location attributes when present ---
|
|
313
|
+
// These are emitted by connected apps via @vitejs/plugin-react jsxDEV or a
|
|
314
|
+
// Babel source plugin. Cross-origin localhost iframes cannot be read (CSP /
|
|
315
|
+
// same-origin policy), so this will be undefined in that case — expected.
|
|
316
|
+
var provenance:
|
|
317
|
+
| {
|
|
318
|
+
sourceFile?: string;
|
|
319
|
+
line?: number;
|
|
320
|
+
column?: number;
|
|
321
|
+
component?: string;
|
|
322
|
+
}
|
|
323
|
+
| undefined = undefined;
|
|
324
|
+
var dataSourceFile = el.getAttribute("data-source-file");
|
|
325
|
+
var dataSourceLine = el.getAttribute("data-source-line");
|
|
326
|
+
var dataSourceColumn = el.getAttribute("data-source-column");
|
|
327
|
+
var dataComponentName = el.getAttribute("data-component-name");
|
|
328
|
+
var dataLoc = el.getAttribute("data-loc");
|
|
329
|
+
// data-loc may encode "file:line:col" (Babel source plugin convention).
|
|
330
|
+
// Only parse it when data-source-file is absent, to avoid double-reads.
|
|
331
|
+
if (!dataSourceFile && dataLoc) {
|
|
332
|
+
var lastColonIndex = dataLoc.lastIndexOf(":");
|
|
333
|
+
var lastPart =
|
|
334
|
+
lastColonIndex >= 0 ? dataLoc.slice(lastColonIndex + 1) : "";
|
|
335
|
+
if (lastColonIndex >= 0 && /^\d+$/.test(lastPart)) {
|
|
336
|
+
var beforeLastPart = dataLoc.slice(0, lastColonIndex);
|
|
337
|
+
var previousColonIndex = beforeLastPart.lastIndexOf(":");
|
|
338
|
+
var previousPart =
|
|
339
|
+
previousColonIndex >= 0
|
|
340
|
+
? beforeLastPart.slice(previousColonIndex + 1)
|
|
341
|
+
: "";
|
|
342
|
+
var hasColumn = /^\d+$/.test(previousPart);
|
|
343
|
+
dataSourceFile = hasColumn
|
|
344
|
+
? beforeLastPart.slice(0, previousColonIndex)
|
|
345
|
+
: beforeLastPart;
|
|
346
|
+
dataSourceLine = hasColumn ? previousPart : lastPart;
|
|
347
|
+
if (hasColumn) dataSourceColumn = lastPart;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (
|
|
351
|
+
dataSourceFile ||
|
|
352
|
+
dataSourceLine ||
|
|
353
|
+
dataSourceColumn ||
|
|
354
|
+
dataComponentName
|
|
355
|
+
) {
|
|
356
|
+
provenance = {};
|
|
357
|
+
if (dataSourceFile) provenance.sourceFile = dataSourceFile;
|
|
358
|
+
if (dataSourceLine) {
|
|
359
|
+
var ln = parseInt(dataSourceLine, 10);
|
|
360
|
+
if (!isNaN(ln)) provenance.line = ln;
|
|
361
|
+
}
|
|
362
|
+
if (dataSourceColumn) {
|
|
363
|
+
var col = parseInt(dataSourceColumn, 10);
|
|
364
|
+
if (!isNaN(col)) provenance.column = col;
|
|
365
|
+
}
|
|
366
|
+
if (dataComponentName) provenance.component = dataComponentName;
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
tagName: el.tagName.toLowerCase(),
|
|
370
|
+
id: el.id || undefined,
|
|
371
|
+
sourceId: sourceId,
|
|
372
|
+
selector: getSelector(el),
|
|
373
|
+
classes: Array.from(el.classList),
|
|
374
|
+
computedStyles: {
|
|
375
|
+
color: cs.color,
|
|
376
|
+
backgroundColor: cs.backgroundColor,
|
|
377
|
+
backgroundImage: cs.backgroundImage,
|
|
378
|
+
backgroundPosition: cs.backgroundPosition,
|
|
379
|
+
backgroundRepeat: cs.backgroundRepeat,
|
|
380
|
+
backgroundSize: cs.backgroundSize,
|
|
381
|
+
backgroundBlendMode: cs.backgroundBlendMode,
|
|
382
|
+
fontSize: cs.fontSize,
|
|
383
|
+
fontFamily: cs.fontFamily,
|
|
384
|
+
fontWeight: cs.fontWeight,
|
|
385
|
+
lineHeight: cs.lineHeight,
|
|
386
|
+
letterSpacing: cs.letterSpacing,
|
|
387
|
+
textAlign: cs.textAlign,
|
|
388
|
+
display: cs.display,
|
|
389
|
+
overflow: cs.overflow,
|
|
390
|
+
flexDirection: cs.flexDirection,
|
|
391
|
+
justifyContent: cs.justifyContent,
|
|
392
|
+
alignItems: cs.alignItems,
|
|
393
|
+
alignSelf: cs.alignSelf,
|
|
394
|
+
flexGrow: cs.flexGrow,
|
|
395
|
+
flexShrink: cs.flexShrink,
|
|
396
|
+
flexBasis: cs.flexBasis,
|
|
397
|
+
order: cs.order,
|
|
398
|
+
gridColumn: cs.gridColumn,
|
|
399
|
+
gridRow: cs.gridRow,
|
|
400
|
+
position: cs.position,
|
|
401
|
+
top: cs.top,
|
|
402
|
+
right: cs.right,
|
|
403
|
+
bottom: cs.bottom,
|
|
404
|
+
left: cs.left,
|
|
405
|
+
gap: cs.gap,
|
|
406
|
+
width: cs.width,
|
|
407
|
+
height: cs.height,
|
|
408
|
+
opacity: cs.opacity,
|
|
409
|
+
paddingTop: cs.paddingTop,
|
|
410
|
+
paddingRight: cs.paddingRight,
|
|
411
|
+
paddingBottom: cs.paddingBottom,
|
|
412
|
+
paddingLeft: cs.paddingLeft,
|
|
413
|
+
marginTop: cs.marginTop,
|
|
414
|
+
marginRight: cs.marginRight,
|
|
415
|
+
marginBottom: cs.marginBottom,
|
|
416
|
+
marginLeft: cs.marginLeft,
|
|
417
|
+
borderWidth: cs.borderWidth,
|
|
418
|
+
borderStyle: cs.borderStyle,
|
|
419
|
+
borderColor: cs.borderColor,
|
|
420
|
+
borderRadius: cs.borderRadius,
|
|
421
|
+
borderTopLeftRadius: cs.borderTopLeftRadius,
|
|
422
|
+
borderTopRightRadius: cs.borderTopRightRadius,
|
|
423
|
+
borderBottomRightRadius: cs.borderBottomRightRadius,
|
|
424
|
+
borderBottomLeftRadius: cs.borderBottomLeftRadius,
|
|
425
|
+
outlineWidth: cs.outlineWidth,
|
|
426
|
+
outlineStyle: cs.outlineStyle,
|
|
427
|
+
outlineColor: cs.outlineColor,
|
|
428
|
+
outlineOffset: cs.outlineOffset,
|
|
429
|
+
boxShadow: cs.boxShadow,
|
|
430
|
+
textShadow: cs.textShadow,
|
|
431
|
+
filter: cs.filter,
|
|
432
|
+
mixBlendMode: cs.mixBlendMode,
|
|
433
|
+
zIndex: cs.zIndex,
|
|
434
|
+
},
|
|
435
|
+
boundingRect: {
|
|
436
|
+
x: rect.x + (window.scrollX || window.pageXOffset || 0),
|
|
437
|
+
y: rect.y + (window.scrollY || window.pageYOffset || 0),
|
|
438
|
+
width: rect.width,
|
|
439
|
+
height: rect.height,
|
|
440
|
+
},
|
|
441
|
+
textContent: el.textContent ? el.textContent.slice(0, 200) : undefined,
|
|
442
|
+
htmlContent:
|
|
443
|
+
el.innerHTML && el.innerHTML !== el.textContent
|
|
444
|
+
? el.innerHTML.slice(0, 4000)
|
|
445
|
+
: undefined,
|
|
446
|
+
isFlexContainer: cs.display === "flex" || cs.display === "inline-flex",
|
|
447
|
+
isFlexChild: parentDisplay === "flex" || parentDisplay === "inline-flex",
|
|
448
|
+
parentDisplay: parentDisplay,
|
|
449
|
+
parentLayout: parentLayout,
|
|
450
|
+
editCapabilities: capabilities,
|
|
451
|
+
confidence: capabilities.reduce(function (best, item) {
|
|
452
|
+
return Math.max(best, item.confidence || 0);
|
|
453
|
+
}, 0),
|
|
454
|
+
provenance: provenance,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
var shieldOverlay = document.createElement("div");
|
|
459
|
+
shieldOverlay.setAttribute("data-agent-native-edit-overlay", "shield");
|
|
460
|
+
shieldOverlay.style.cssText =
|
|
461
|
+
"position:fixed;inset:0;z-index:99990;background:transparent;pointer-events:auto;touch-action:none;cursor:default;";
|
|
462
|
+
document.body.appendChild(shieldOverlay);
|
|
463
|
+
|
|
464
|
+
var highlightOverlay = document.createElement("div");
|
|
465
|
+
highlightOverlay.setAttribute("data-agent-native-edit-overlay", "highlight");
|
|
466
|
+
highlightOverlay.style.cssText =
|
|
467
|
+
"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;";
|
|
468
|
+
document.body.appendChild(highlightOverlay);
|
|
469
|
+
|
|
470
|
+
var selectionOverlay = document.createElement("div");
|
|
471
|
+
selectionOverlay.setAttribute("data-agent-native-edit-overlay", "selection");
|
|
472
|
+
selectionOverlay.style.cssText =
|
|
473
|
+
"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;";
|
|
474
|
+
["n", "e", "s", "w"].forEach(function (pos) {
|
|
475
|
+
var edge = document.createElement("span");
|
|
476
|
+
edge.setAttribute("data-agent-native-edge-handle", pos);
|
|
477
|
+
var cursor = pos === "n" || pos === "s" ? "ns-resize" : "ew-resize";
|
|
478
|
+
edge.style.cssText =
|
|
479
|
+
"position:absolute;pointer-events:auto;cursor:" +
|
|
480
|
+
cursor +
|
|
481
|
+
";background:transparent;";
|
|
482
|
+
if (pos === "n") {
|
|
483
|
+
edge.style.left = "0";
|
|
484
|
+
edge.style.right = "0";
|
|
485
|
+
edge.style.top = "-5px";
|
|
486
|
+
edge.style.height = "10px";
|
|
487
|
+
}
|
|
488
|
+
if (pos === "s") {
|
|
489
|
+
edge.style.left = "0";
|
|
490
|
+
edge.style.right = "0";
|
|
491
|
+
edge.style.bottom = "-5px";
|
|
492
|
+
edge.style.height = "10px";
|
|
493
|
+
}
|
|
494
|
+
if (pos === "e") {
|
|
495
|
+
edge.style.top = "0";
|
|
496
|
+
edge.style.bottom = "0";
|
|
497
|
+
edge.style.right = "-5px";
|
|
498
|
+
edge.style.width = "10px";
|
|
499
|
+
}
|
|
500
|
+
if (pos === "w") {
|
|
501
|
+
edge.style.top = "0";
|
|
502
|
+
edge.style.bottom = "0";
|
|
503
|
+
edge.style.left = "-5px";
|
|
504
|
+
edge.style.width = "10px";
|
|
505
|
+
}
|
|
506
|
+
selectionOverlay.appendChild(edge);
|
|
507
|
+
});
|
|
508
|
+
["nw", "ne", "se", "sw"].forEach(function (pos) {
|
|
509
|
+
var handle = document.createElement("span");
|
|
510
|
+
handle.setAttribute("data-agent-native-edit-handle", pos);
|
|
511
|
+
var cursor =
|
|
512
|
+
pos === "n" || pos === "s"
|
|
513
|
+
? "ns-resize"
|
|
514
|
+
: pos === "e" || pos === "w"
|
|
515
|
+
? "ew-resize"
|
|
516
|
+
: pos === "nw" || pos === "se"
|
|
517
|
+
? "nwse-resize"
|
|
518
|
+
: "nesw-resize";
|
|
519
|
+
handle.style.cssText =
|
|
520
|
+
"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:" +
|
|
521
|
+
cursor +
|
|
522
|
+
";";
|
|
523
|
+
if (pos.indexOf("n") !== -1) handle.style.top = "-4px";
|
|
524
|
+
if (pos.indexOf("s") !== -1) handle.style.bottom = "-4px";
|
|
525
|
+
if (pos.indexOf("w") !== -1) handle.style.left = "-4px";
|
|
526
|
+
if (pos.indexOf("e") !== -1) handle.style.right = "-4px";
|
|
527
|
+
if (pos === "n" || pos === "s") {
|
|
528
|
+
handle.style.left = "50%";
|
|
529
|
+
handle.style.transform = "translateX(-50%)";
|
|
530
|
+
}
|
|
531
|
+
if (pos === "e" || pos === "w") {
|
|
532
|
+
handle.style.top = "50%";
|
|
533
|
+
handle.style.transform = "translateY(-50%)";
|
|
534
|
+
}
|
|
535
|
+
selectionOverlay.appendChild(handle);
|
|
536
|
+
});
|
|
537
|
+
["nw", "ne", "se", "sw"].forEach(function (pos) {
|
|
538
|
+
var rotate = document.createElement("span");
|
|
539
|
+
rotate.setAttribute("data-agent-native-rotate-handle", pos);
|
|
540
|
+
rotate.style.cssText =
|
|
541
|
+
"position:absolute;width:18px;height:18px;border-radius:999px;pointer-events:auto;cursor:grab;";
|
|
542
|
+
if (pos.indexOf("n") !== -1) rotate.style.top = "-26px";
|
|
543
|
+
if (pos.indexOf("s") !== -1) rotate.style.bottom = "-26px";
|
|
544
|
+
if (pos.indexOf("w") !== -1) rotate.style.left = "-26px";
|
|
545
|
+
if (pos.indexOf("e") !== -1) rotate.style.right = "-26px";
|
|
546
|
+
selectionOverlay.appendChild(rotate);
|
|
547
|
+
});
|
|
548
|
+
var spacingOverlay = document.createElement("div");
|
|
549
|
+
spacingOverlay.setAttribute("data-agent-native-spacing-overlay", "");
|
|
550
|
+
spacingOverlay.style.cssText =
|
|
551
|
+
"position:absolute;inset:0;display:none;pointer-events:none;";
|
|
552
|
+
selectionOverlay.appendChild(spacingOverlay);
|
|
553
|
+
document.body.appendChild(selectionOverlay);
|
|
554
|
+
|
|
555
|
+
var transformBadge = document.createElement("div");
|
|
556
|
+
transformBadge.setAttribute("data-agent-native-transform-badge", "");
|
|
557
|
+
transformBadge.style.cssText =
|
|
558
|
+
"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);";
|
|
559
|
+
document.body.appendChild(transformBadge);
|
|
560
|
+
|
|
561
|
+
var spacingBadge = document.createElement("div");
|
|
562
|
+
spacingBadge.setAttribute("data-agent-native-spacing-badge", "");
|
|
563
|
+
spacingBadge.style.cssText =
|
|
564
|
+
"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);";
|
|
565
|
+
document.body.appendChild(spacingBadge);
|
|
566
|
+
|
|
567
|
+
var insertionGuide = document.createElement("div");
|
|
568
|
+
insertionGuide.setAttribute("data-agent-native-insertion-guide", "");
|
|
569
|
+
insertionGuide.style.cssText =
|
|
570
|
+
"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);";
|
|
571
|
+
document.body.appendChild(insertionGuide);
|
|
572
|
+
|
|
573
|
+
var measurementOverlay = document.createElement("div");
|
|
574
|
+
measurementOverlay.setAttribute("data-agent-native-measurement-overlay", "");
|
|
575
|
+
measurementOverlay.style.cssText =
|
|
576
|
+
"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;";
|
|
577
|
+
document.body.appendChild(measurementOverlay);
|
|
578
|
+
|
|
579
|
+
// Component-instance tag: a small pill that floats above the selection
|
|
580
|
+
// outline whenever the selected element carries a data-agent-native-component
|
|
581
|
+
// attribute. Clicking it sends a 'component-source-jump' message to the
|
|
582
|
+
// parent so the editor can invoke open-component-source.
|
|
583
|
+
var componentTagOverlay = document.createElement("div");
|
|
584
|
+
componentTagOverlay.setAttribute(
|
|
585
|
+
"data-agent-native-edit-overlay",
|
|
586
|
+
"component-tag",
|
|
587
|
+
);
|
|
588
|
+
componentTagOverlay.style.cssText =
|
|
589
|
+
[
|
|
590
|
+
"position:fixed",
|
|
591
|
+
"z-index:100002",
|
|
592
|
+
"display:none",
|
|
593
|
+
"pointer-events:auto",
|
|
594
|
+
"cursor:pointer",
|
|
595
|
+
"padding:2px 6px",
|
|
596
|
+
"border-radius:4px",
|
|
597
|
+
"font:11px/1.6 ui-sans-serif,system-ui,sans-serif",
|
|
598
|
+
"white-space:nowrap",
|
|
599
|
+
"user-select:none",
|
|
600
|
+
"-webkit-user-select:none",
|
|
601
|
+
"background:var(--design-editor-accent-color)",
|
|
602
|
+
"color:var(--design-editor-accent-contrast-color)",
|
|
603
|
+
"box-shadow:0 1px 4px color-mix(in srgb,var(--design-editor-accent-color) 40%,transparent)",
|
|
604
|
+
"border:1px solid color-mix(in srgb,var(--design-editor-accent-strong-color) 60%,transparent)",
|
|
605
|
+
"outline:2px solid transparent",
|
|
606
|
+
"transition:opacity 0.1s",
|
|
607
|
+
].join(";") + ";";
|
|
608
|
+
document.body.appendChild(componentTagOverlay);
|
|
609
|
+
|
|
610
|
+
componentTagOverlay.addEventListener("click", function (e) {
|
|
611
|
+
e.stopPropagation();
|
|
612
|
+
e.preventDefault();
|
|
613
|
+
var nodeId =
|
|
614
|
+
componentTagOverlay.getAttribute("data-component-node-id") || "";
|
|
615
|
+
var componentName =
|
|
616
|
+
componentTagOverlay.getAttribute("data-component-name") || "";
|
|
617
|
+
if (!nodeId || !componentName) return;
|
|
618
|
+
try {
|
|
619
|
+
(window.parent as Window).postMessage(
|
|
620
|
+
{
|
|
621
|
+
type: "component-source-jump",
|
|
622
|
+
nodeId: nodeId,
|
|
623
|
+
componentName: componentName,
|
|
624
|
+
},
|
|
625
|
+
"*",
|
|
626
|
+
);
|
|
627
|
+
} catch (_err) {}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
function updateComponentTag(el: Element | null): void {
|
|
631
|
+
if (!el) {
|
|
632
|
+
clearComponentTag();
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
var compName =
|
|
636
|
+
el.getAttribute && el.getAttribute("data-agent-native-component");
|
|
637
|
+
if (!compName) {
|
|
638
|
+
clearComponentTag();
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
var nodeId =
|
|
642
|
+
el.getAttribute("data-agent-native-node-id") ||
|
|
643
|
+
el.getAttribute("data-code-layer-id") ||
|
|
644
|
+
el.getAttribute("data-layer-id") ||
|
|
645
|
+
el.id ||
|
|
646
|
+
"";
|
|
647
|
+
componentTagOverlay.textContent = compName + " →";
|
|
648
|
+
componentTagOverlay.setAttribute("data-component-node-id", nodeId);
|
|
649
|
+
componentTagOverlay.setAttribute("data-component-name", compName);
|
|
650
|
+
|
|
651
|
+
var rect = el.getBoundingClientRect();
|
|
652
|
+
var tagHeight = 22;
|
|
653
|
+
var tagTop = rect.top - tagHeight - 4;
|
|
654
|
+
if (tagTop < 4) tagTop = rect.top + 4;
|
|
655
|
+
componentTagOverlay.style.display = "block";
|
|
656
|
+
componentTagOverlay.style.left = rect.left + "px";
|
|
657
|
+
componentTagOverlay.style.top = tagTop + "px";
|
|
658
|
+
// Accent outline on the selection overlay to distinguish component roots.
|
|
659
|
+
selectionOverlay.style.outline =
|
|
660
|
+
"2px solid var(--design-editor-accent-strong-color)";
|
|
661
|
+
selectionOverlay.style.outlineOffset = "2px";
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function clearComponentTag(): void {
|
|
665
|
+
componentTagOverlay.style.display = "none";
|
|
666
|
+
componentTagOverlay.removeAttribute("data-component-node-id");
|
|
667
|
+
componentTagOverlay.removeAttribute("data-component-name");
|
|
668
|
+
selectionOverlay.style.outline = "";
|
|
669
|
+
selectionOverlay.style.outlineOffset = "";
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
var selectedEl: Element | null = null;
|
|
673
|
+
var hoveredEl: Element | null = null;
|
|
674
|
+
var activeTextEditEl: HTMLElement | null = null;
|
|
675
|
+
var textEditPointerState: {
|
|
676
|
+
shield: string;
|
|
677
|
+
selection: string;
|
|
678
|
+
highlight: string;
|
|
679
|
+
} | null = null;
|
|
680
|
+
var pendingStructureMoves: Record<
|
|
681
|
+
string,
|
|
682
|
+
{
|
|
683
|
+
requestId: string;
|
|
684
|
+
el: Element;
|
|
685
|
+
target: { anchor: Element; placement: string; axis?: string };
|
|
686
|
+
origin: { prevParent: Element; prevNextSibling: Node | null } | null;
|
|
687
|
+
}
|
|
688
|
+
> = {};
|
|
689
|
+
var pendingShieldDrag: {
|
|
690
|
+
el: Element;
|
|
691
|
+
startX: number;
|
|
692
|
+
startY: number;
|
|
693
|
+
lastX: number;
|
|
694
|
+
lastY: number;
|
|
695
|
+
requestId: string;
|
|
696
|
+
offsetX: number;
|
|
697
|
+
offsetY: number;
|
|
698
|
+
originalPointerEvents: string;
|
|
699
|
+
lastReorder: { anchor: Element; placement: string; axis?: string } | null;
|
|
700
|
+
} | null = null;
|
|
701
|
+
var suppressNextShieldClick = false;
|
|
702
|
+
var suppressNextShieldClickTimer: ReturnType<typeof setTimeout> | null = null;
|
|
703
|
+
var selectedSpacingHovered = false;
|
|
704
|
+
var hoveredSpacingHandleKey = "";
|
|
705
|
+
var spacingHandleStateByKey: Record<string, { value: number }> = {};
|
|
706
|
+
var spacingHandleNodesByKey: Record<string, Element> = {};
|
|
707
|
+
var spacingOverlayRenderKey = "";
|
|
708
|
+
var spacingDrag: {
|
|
709
|
+
key: string;
|
|
710
|
+
groupKey: string;
|
|
711
|
+
property: string;
|
|
712
|
+
oppositeProperty: string;
|
|
713
|
+
side: string;
|
|
714
|
+
orientation: string;
|
|
715
|
+
baseValue: number;
|
|
716
|
+
baseOppositeValue: number;
|
|
717
|
+
startX: number;
|
|
718
|
+
startY: number;
|
|
719
|
+
el: Element;
|
|
720
|
+
} | null = null;
|
|
721
|
+
var lockedSelectors: string[] = [];
|
|
722
|
+
var hiddenSelectors: string[] = [];
|
|
723
|
+
var lastEditorPointWasBlocked = false;
|
|
724
|
+
|
|
725
|
+
function clearRuntimeSelection(): void {
|
|
726
|
+
selectedEl = null;
|
|
727
|
+
hoveredEl = null;
|
|
728
|
+
selectedSpacingHovered = false;
|
|
729
|
+
hoveredSpacingHandleKey = "";
|
|
730
|
+
spacingDrag = null;
|
|
731
|
+
selectionOverlay.style.display = "none";
|
|
732
|
+
highlightOverlay.style.display = "none";
|
|
733
|
+
hideSpacingOverlay();
|
|
734
|
+
hideMeasurements();
|
|
735
|
+
clearComponentTag();
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function matchesSelectorList(
|
|
739
|
+
el: Element | null,
|
|
740
|
+
selectors: string[],
|
|
741
|
+
): boolean {
|
|
742
|
+
if (!el || !selectors || selectors.length === 0) return false;
|
|
743
|
+
for (var i = 0; i < selectors.length; i += 1) {
|
|
744
|
+
try {
|
|
745
|
+
if (el.matches(selectors[i]) || el.closest(selectors[i])) return true;
|
|
746
|
+
} catch (_err) {}
|
|
747
|
+
}
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function matchesExactSelectorList(
|
|
752
|
+
el: Element | null,
|
|
753
|
+
selectors: string[],
|
|
754
|
+
): boolean {
|
|
755
|
+
if (!el || !selectors || selectors.length === 0) return false;
|
|
756
|
+
for (var i = 0; i < selectors.length; i += 1) {
|
|
757
|
+
try {
|
|
758
|
+
if (el.matches(selectors[i])) return true;
|
|
759
|
+
} catch (_err) {}
|
|
760
|
+
}
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function isLayerInteractionBlocked(el: Element | null): boolean {
|
|
765
|
+
if (!el) return false;
|
|
766
|
+
if (
|
|
767
|
+
el.closest &&
|
|
768
|
+
el.closest(
|
|
769
|
+
'[data-agent-native-locked="true"], [data-agent-native-hidden="true"]',
|
|
770
|
+
)
|
|
771
|
+
) {
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
return (
|
|
775
|
+
matchesSelectorList(el, lockedSelectors) ||
|
|
776
|
+
matchesSelectorList(el, hiddenSelectors)
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function applyHiddenSelectors(): void {
|
|
781
|
+
document
|
|
782
|
+
.querySelectorAll("[data-agent-native-runtime-hidden]")
|
|
783
|
+
.forEach(function (el: HTMLElement) {
|
|
784
|
+
var previous = el.getAttribute("data-agent-native-previous-display");
|
|
785
|
+
if (previous === null) {
|
|
786
|
+
el.style.removeProperty("display");
|
|
787
|
+
} else {
|
|
788
|
+
el.style.display = previous;
|
|
789
|
+
}
|
|
790
|
+
el.removeAttribute("data-agent-native-runtime-hidden");
|
|
791
|
+
el.removeAttribute("data-agent-native-previous-display");
|
|
792
|
+
});
|
|
793
|
+
hiddenSelectors.forEach(function (selector) {
|
|
794
|
+
try {
|
|
795
|
+
document.querySelectorAll(selector).forEach(function (el) {
|
|
796
|
+
if (!el.hasAttribute("data-agent-native-runtime-hidden")) {
|
|
797
|
+
el.setAttribute(
|
|
798
|
+
"data-agent-native-previous-display",
|
|
799
|
+
el.style.display || "",
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
el.setAttribute("data-agent-native-runtime-hidden", "true");
|
|
803
|
+
el.style.display = "none";
|
|
804
|
+
});
|
|
805
|
+
} catch (_err) {}
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function replaceRuntimeDocument(
|
|
810
|
+
html: string,
|
|
811
|
+
preferredSelector: string,
|
|
812
|
+
selectorCandidates: string[],
|
|
813
|
+
): void {
|
|
814
|
+
if (typeof html !== "string") return;
|
|
815
|
+
if (activeTextEditEl) {
|
|
816
|
+
applyHiddenSelectors();
|
|
817
|
+
refreshOverlays();
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
var parser = new DOMParser();
|
|
821
|
+
var nextDoc = parser.parseFromString(html, "text/html");
|
|
822
|
+
if (!nextDoc || !nextDoc.body) return;
|
|
823
|
+
|
|
824
|
+
var persistentNodes = Array.prototype.slice.call(
|
|
825
|
+
document.querySelectorAll("[data-agent-native-edit-overlay]"),
|
|
826
|
+
);
|
|
827
|
+
var activeSelector =
|
|
828
|
+
preferredSelector || (selectedEl ? getSelector(selectedEl) : "");
|
|
829
|
+
var activeCandidates: string[] = [];
|
|
830
|
+
if (Array.isArray(selectorCandidates)) {
|
|
831
|
+
selectorCandidates.forEach(function (selector) {
|
|
832
|
+
if (
|
|
833
|
+
typeof selector === "string" &&
|
|
834
|
+
selector &&
|
|
835
|
+
activeCandidates.indexOf(selector) === -1
|
|
836
|
+
) {
|
|
837
|
+
activeCandidates.push(selector);
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
if (activeSelector && activeCandidates.indexOf(activeSelector) === -1) {
|
|
842
|
+
activeCandidates.push(activeSelector);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
var nextHeadHtml = nextDoc.head ? nextDoc.head.innerHTML : "";
|
|
846
|
+
if (
|
|
847
|
+
nextHeadHtml === document.head.innerHTML &&
|
|
848
|
+
activeCandidates.length > 0
|
|
849
|
+
) {
|
|
850
|
+
var currentMatch = null;
|
|
851
|
+
var nextMatch = null;
|
|
852
|
+
var matchedSelector = "";
|
|
853
|
+
var fallbackCurrentMatch = null;
|
|
854
|
+
var fallbackSelector = "";
|
|
855
|
+
for (
|
|
856
|
+
var matchIndex = 0;
|
|
857
|
+
matchIndex < activeCandidates.length;
|
|
858
|
+
matchIndex += 1
|
|
859
|
+
) {
|
|
860
|
+
try {
|
|
861
|
+
var currentCandidate = document.querySelector(
|
|
862
|
+
activeCandidates[matchIndex],
|
|
863
|
+
);
|
|
864
|
+
var nextCandidate = nextDoc.querySelector(
|
|
865
|
+
activeCandidates[matchIndex],
|
|
866
|
+
);
|
|
867
|
+
if (currentCandidate && !fallbackCurrentMatch) {
|
|
868
|
+
fallbackCurrentMatch = currentCandidate;
|
|
869
|
+
fallbackSelector = activeCandidates[matchIndex];
|
|
870
|
+
}
|
|
871
|
+
if (currentCandidate && nextCandidate) {
|
|
872
|
+
currentMatch = currentCandidate;
|
|
873
|
+
nextMatch = nextCandidate;
|
|
874
|
+
matchedSelector = activeCandidates[matchIndex];
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
} catch (_err) {
|
|
878
|
+
// Keep trying later aliases; bridge selectors can differ between
|
|
879
|
+
// runtime and DOMParser passes.
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (!currentMatch && fallbackCurrentMatch) {
|
|
883
|
+
currentMatch = fallbackCurrentMatch;
|
|
884
|
+
matchedSelector = fallbackSelector;
|
|
885
|
+
}
|
|
886
|
+
if (
|
|
887
|
+
currentMatch &&
|
|
888
|
+
currentMatch !== document.body &&
|
|
889
|
+
currentMatch !== document.documentElement &&
|
|
890
|
+
!isOverlayElement(currentMatch)
|
|
891
|
+
) {
|
|
892
|
+
if (nextMatch) {
|
|
893
|
+
currentMatch.replaceWith(document.importNode(nextMatch, true));
|
|
894
|
+
} else if (
|
|
895
|
+
currentMatch !== document.body &&
|
|
896
|
+
currentMatch !== document.documentElement
|
|
897
|
+
) {
|
|
898
|
+
if (
|
|
899
|
+
currentMatch.parentNode &&
|
|
900
|
+
currentMatch.parentNode.contains(currentMatch)
|
|
901
|
+
) {
|
|
902
|
+
currentMatch.remove();
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
applyHiddenSelectors();
|
|
906
|
+
selectedEl = null;
|
|
907
|
+
if (nextMatch) {
|
|
908
|
+
try {
|
|
909
|
+
selectedEl = document.querySelector(matchedSelector);
|
|
910
|
+
} catch (_err) {}
|
|
911
|
+
}
|
|
912
|
+
hoveredEl = null;
|
|
913
|
+
if (selectedEl && !isLayerInteractionBlocked(selectedEl)) {
|
|
914
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
915
|
+
(window.parent as Window).postMessage(
|
|
916
|
+
{ type: "element-select", payload: getElementInfo(selectedEl) },
|
|
917
|
+
"*",
|
|
918
|
+
);
|
|
919
|
+
} else {
|
|
920
|
+
selectionOverlay.style.display = "none";
|
|
921
|
+
}
|
|
922
|
+
highlightOverlay.style.display = "none";
|
|
923
|
+
hideMeasurements();
|
|
924
|
+
refreshOverlays();
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (document.head.innerHTML !== nextHeadHtml) {
|
|
929
|
+
document.head.innerHTML = nextHeadHtml;
|
|
930
|
+
}
|
|
931
|
+
Array.prototype.slice.call(document.body.attributes).forEach(function (
|
|
932
|
+
attribute: Attr,
|
|
933
|
+
) {
|
|
934
|
+
document.body.removeAttribute(attribute.name);
|
|
935
|
+
});
|
|
936
|
+
Array.prototype.slice.call(nextDoc.body.attributes).forEach(function (
|
|
937
|
+
attribute: Attr,
|
|
938
|
+
) {
|
|
939
|
+
document.body.setAttribute(attribute.name, attribute.value);
|
|
940
|
+
});
|
|
941
|
+
document.body.innerHTML = nextDoc.body.innerHTML;
|
|
942
|
+
persistentNodes.forEach(function (node) {
|
|
943
|
+
document.body.appendChild(node);
|
|
944
|
+
});
|
|
945
|
+
applyHiddenSelectors();
|
|
946
|
+
|
|
947
|
+
selectedEl = null;
|
|
948
|
+
hoveredEl = null;
|
|
949
|
+
for (var i = 0; i < activeCandidates.length && !selectedEl; i += 1) {
|
|
950
|
+
try {
|
|
951
|
+
var match = document.querySelector(activeCandidates[i]);
|
|
952
|
+
// Skip the editor's own injected overlay chrome and re-anchor to a
|
|
953
|
+
// source-backed element. A stale positional candidate like
|
|
954
|
+
// body > div:nth-of-type(6) can otherwise re-match an overlay div
|
|
955
|
+
// (the only direct div children of body at runtime), which then has
|
|
956
|
+
// no code-layer node and fails every edit.
|
|
957
|
+
if (
|
|
958
|
+
match &&
|
|
959
|
+
!isLayerInteractionBlocked(match) &&
|
|
960
|
+
!isOverlayElement(match)
|
|
961
|
+
) {
|
|
962
|
+
selectedEl = selectionTargetForHit(match) || match;
|
|
963
|
+
}
|
|
964
|
+
} catch (_err) {}
|
|
965
|
+
}
|
|
966
|
+
if (selectedEl) {
|
|
967
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
968
|
+
(window.parent as Window).postMessage(
|
|
969
|
+
{ type: "element-select", payload: getElementInfo(selectedEl) },
|
|
970
|
+
"*",
|
|
971
|
+
);
|
|
972
|
+
} else {
|
|
973
|
+
selectionOverlay.style.display = "none";
|
|
974
|
+
}
|
|
975
|
+
highlightOverlay.style.display = "none";
|
|
976
|
+
hideMeasurements();
|
|
977
|
+
refreshOverlays();
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function hideSpacingOverlay(): void {
|
|
981
|
+
spacingOverlay.style.display = "none";
|
|
982
|
+
spacingOverlay.innerHTML = "";
|
|
983
|
+
spacingHandleStateByKey = {};
|
|
984
|
+
spacingHandleNodesByKey = {};
|
|
985
|
+
spacingOverlayRenderKey = "";
|
|
986
|
+
if (!spacingDrag) spacingBadge.style.display = "none";
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function visibleLayoutChildren(el: Element | null): Element[] {
|
|
990
|
+
if (!el || !el.children) return [];
|
|
991
|
+
return Array.prototype.slice.call(el.children).filter(function (child) {
|
|
992
|
+
if (
|
|
993
|
+
!child ||
|
|
994
|
+
child.nodeType !== 1 ||
|
|
995
|
+
isOverlayElement(child) ||
|
|
996
|
+
isLayerInteractionBlocked(child)
|
|
997
|
+
)
|
|
998
|
+
return false;
|
|
999
|
+
var cs = window.getComputedStyle(child);
|
|
1000
|
+
if (
|
|
1001
|
+
cs.display === "none" ||
|
|
1002
|
+
cs.visibility === "hidden" ||
|
|
1003
|
+
cs.position === "fixed"
|
|
1004
|
+
)
|
|
1005
|
+
return false;
|
|
1006
|
+
var rect = child.getBoundingClientRect();
|
|
1007
|
+
return rect.width > 0 && rect.height > 0;
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function spacingColor(kind: string): string {
|
|
1012
|
+
return kind === "gap" ? "#ff4fd8" : "var(--design-editor-accent-color)";
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function spacingFill(kind: string, orientation: string): string {
|
|
1016
|
+
var tint =
|
|
1017
|
+
kind === "gap" ? "rgba(255, 79, 216, 0.28)" : "rgba(46, 168, 255, 0.24)";
|
|
1018
|
+
var stripe =
|
|
1019
|
+
kind === "gap" ? "rgba(255, 79, 216, 0.58)" : "rgba(46, 168, 255, 0.52)";
|
|
1020
|
+
var angle = orientation === "vertical" ? "135deg" : "45deg";
|
|
1021
|
+
return (
|
|
1022
|
+
"repeating-linear-gradient(" +
|
|
1023
|
+
angle +
|
|
1024
|
+
", " +
|
|
1025
|
+
stripe +
|
|
1026
|
+
" 0 1px, " +
|
|
1027
|
+
tint +
|
|
1028
|
+
" 1px 4px, transparent 4px 7px)"
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function clampSpacingValue(value: number): number {
|
|
1033
|
+
var rounded = Math.round(value);
|
|
1034
|
+
if (!Number.isFinite(rounded)) return 0;
|
|
1035
|
+
return Math.max(0, Math.min(999, rounded));
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function makeSpacingHandle(config: {
|
|
1039
|
+
key: string;
|
|
1040
|
+
groupKey?: string;
|
|
1041
|
+
kind: string;
|
|
1042
|
+
property: string;
|
|
1043
|
+
oppositeProperty?: string;
|
|
1044
|
+
side?: string;
|
|
1045
|
+
orientation: string;
|
|
1046
|
+
value: number;
|
|
1047
|
+
region: { x: number; y: number; width: number; height: number };
|
|
1048
|
+
line?: unknown;
|
|
1049
|
+
}): unknown {
|
|
1050
|
+
var region = config.region;
|
|
1051
|
+
if (!region || region.width <= 0 || region.height <= 0) return null;
|
|
1052
|
+
return {
|
|
1053
|
+
key: config.key,
|
|
1054
|
+
groupKey: config.groupKey || config.key,
|
|
1055
|
+
kind: config.kind,
|
|
1056
|
+
property: config.property,
|
|
1057
|
+
oppositeProperty: config.oppositeProperty || "",
|
|
1058
|
+
side: config.side || "",
|
|
1059
|
+
orientation: config.orientation,
|
|
1060
|
+
value: clampSpacingValue(config.value),
|
|
1061
|
+
region: {
|
|
1062
|
+
x: Math.round(region.x),
|
|
1063
|
+
y: Math.round(region.y),
|
|
1064
|
+
width: Math.max(1, Math.round(region.width)),
|
|
1065
|
+
height: Math.max(1, Math.round(region.height)),
|
|
1066
|
+
},
|
|
1067
|
+
line: config.line,
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function childLocalRect(
|
|
1072
|
+
child: Element,
|
|
1073
|
+
containerRect: DOMRect | { left: number; top: number },
|
|
1074
|
+
): {
|
|
1075
|
+
left: number;
|
|
1076
|
+
top: number;
|
|
1077
|
+
right: number;
|
|
1078
|
+
bottom: number;
|
|
1079
|
+
width: number;
|
|
1080
|
+
height: number;
|
|
1081
|
+
} {
|
|
1082
|
+
var rect = child.getBoundingClientRect();
|
|
1083
|
+
return {
|
|
1084
|
+
left: rect.left - containerRect.left,
|
|
1085
|
+
top: rect.top - containerRect.top,
|
|
1086
|
+
right: rect.right - containerRect.left,
|
|
1087
|
+
bottom: rect.bottom - containerRect.top,
|
|
1088
|
+
width: rect.width,
|
|
1089
|
+
height: rect.height,
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function childRectsOverlap(
|
|
1094
|
+
a: { top: number; bottom: number; left: number; right: number },
|
|
1095
|
+
b: { top: number; bottom: number; left: number; right: number },
|
|
1096
|
+
axis: string,
|
|
1097
|
+
): boolean {
|
|
1098
|
+
if (axis === "x") {
|
|
1099
|
+
return Math.max(a.top, b.top) < Math.min(a.bottom, b.bottom);
|
|
1100
|
+
}
|
|
1101
|
+
return Math.max(a.left, b.left) < Math.min(a.right, b.right);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function buildPaddingSpacingHandles(
|
|
1105
|
+
el: Element,
|
|
1106
|
+
rect: DOMRect,
|
|
1107
|
+
cs: CSSStyleDeclaration,
|
|
1108
|
+
): unknown[] {
|
|
1109
|
+
var handles = [];
|
|
1110
|
+
var borderTop = readPx(cs.borderTopWidth);
|
|
1111
|
+
var borderRight = readPx(cs.borderRightWidth);
|
|
1112
|
+
var borderBottom = readPx(cs.borderBottomWidth);
|
|
1113
|
+
var borderLeft = readPx(cs.borderLeftWidth);
|
|
1114
|
+
var paddingTop = readPx(cs.paddingTop);
|
|
1115
|
+
var paddingRight = readPx(cs.paddingRight);
|
|
1116
|
+
var paddingBottom = readPx(cs.paddingBottom);
|
|
1117
|
+
var paddingLeft = readPx(cs.paddingLeft);
|
|
1118
|
+
var sx = chromeScaleX();
|
|
1119
|
+
var sy = chromeScaleY();
|
|
1120
|
+
var line = Math.max(1, chromeLineScale());
|
|
1121
|
+
var hLineWidth = Math.max(6, Math.min(18, rect.width * 0.12)) * sx;
|
|
1122
|
+
var vLineHeight = Math.max(6, Math.min(18, rect.height * 0.12)) * sy;
|
|
1123
|
+
var innerLeft = borderLeft;
|
|
1124
|
+
var innerTop = borderTop;
|
|
1125
|
+
var innerWidth = Math.max(1, rect.width - borderLeft - borderRight);
|
|
1126
|
+
var innerHeight = Math.max(1, rect.height - borderTop - borderBottom);
|
|
1127
|
+
if (paddingTop > 0) {
|
|
1128
|
+
handles.push(
|
|
1129
|
+
makeSpacingHandle({
|
|
1130
|
+
key: "padding:top",
|
|
1131
|
+
kind: "padding",
|
|
1132
|
+
property: "paddingTop",
|
|
1133
|
+
oppositeProperty: "paddingBottom",
|
|
1134
|
+
side: "top",
|
|
1135
|
+
orientation: "horizontal",
|
|
1136
|
+
value: paddingTop,
|
|
1137
|
+
region: {
|
|
1138
|
+
x: innerLeft,
|
|
1139
|
+
y: innerTop,
|
|
1140
|
+
width: innerWidth,
|
|
1141
|
+
height: paddingTop,
|
|
1142
|
+
},
|
|
1143
|
+
line: {
|
|
1144
|
+
x: rect.width / 2 - hLineWidth / 2,
|
|
1145
|
+
y: innerTop + paddingTop / 2 - line / 2,
|
|
1146
|
+
width: hLineWidth,
|
|
1147
|
+
height: line,
|
|
1148
|
+
},
|
|
1149
|
+
}),
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
if (paddingBottom > 0) {
|
|
1153
|
+
handles.push(
|
|
1154
|
+
makeSpacingHandle({
|
|
1155
|
+
key: "padding:bottom",
|
|
1156
|
+
kind: "padding",
|
|
1157
|
+
property: "paddingBottom",
|
|
1158
|
+
oppositeProperty: "paddingTop",
|
|
1159
|
+
side: "bottom",
|
|
1160
|
+
orientation: "horizontal",
|
|
1161
|
+
value: paddingBottom,
|
|
1162
|
+
region: {
|
|
1163
|
+
x: innerLeft,
|
|
1164
|
+
y: rect.height - borderBottom - paddingBottom,
|
|
1165
|
+
width: innerWidth,
|
|
1166
|
+
height: paddingBottom,
|
|
1167
|
+
},
|
|
1168
|
+
line: {
|
|
1169
|
+
x: rect.width / 2 - hLineWidth / 2,
|
|
1170
|
+
y: rect.height - borderBottom - paddingBottom / 2 - line / 2,
|
|
1171
|
+
width: hLineWidth,
|
|
1172
|
+
height: line,
|
|
1173
|
+
},
|
|
1174
|
+
}),
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
if (paddingLeft > 0) {
|
|
1178
|
+
handles.push(
|
|
1179
|
+
makeSpacingHandle({
|
|
1180
|
+
key: "padding:left",
|
|
1181
|
+
kind: "padding",
|
|
1182
|
+
property: "paddingLeft",
|
|
1183
|
+
oppositeProperty: "paddingRight",
|
|
1184
|
+
side: "left",
|
|
1185
|
+
orientation: "vertical",
|
|
1186
|
+
value: paddingLeft,
|
|
1187
|
+
region: {
|
|
1188
|
+
x: innerLeft,
|
|
1189
|
+
y: innerTop,
|
|
1190
|
+
width: paddingLeft,
|
|
1191
|
+
height: innerHeight,
|
|
1192
|
+
},
|
|
1193
|
+
line: {
|
|
1194
|
+
x: innerLeft + paddingLeft / 2 - line / 2,
|
|
1195
|
+
y: rect.height / 2 - vLineHeight / 2,
|
|
1196
|
+
width: line,
|
|
1197
|
+
height: vLineHeight,
|
|
1198
|
+
},
|
|
1199
|
+
}),
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
if (paddingRight > 0) {
|
|
1203
|
+
handles.push(
|
|
1204
|
+
makeSpacingHandle({
|
|
1205
|
+
key: "padding:right",
|
|
1206
|
+
kind: "padding",
|
|
1207
|
+
property: "paddingRight",
|
|
1208
|
+
oppositeProperty: "paddingLeft",
|
|
1209
|
+
side: "right",
|
|
1210
|
+
orientation: "vertical",
|
|
1211
|
+
value: paddingRight,
|
|
1212
|
+
region: {
|
|
1213
|
+
x: rect.width - borderRight - paddingRight,
|
|
1214
|
+
y: innerTop,
|
|
1215
|
+
width: paddingRight,
|
|
1216
|
+
height: innerHeight,
|
|
1217
|
+
},
|
|
1218
|
+
line: {
|
|
1219
|
+
x: rect.width - borderRight - paddingRight / 2 - line / 2,
|
|
1220
|
+
y: rect.height / 2 - vLineHeight / 2,
|
|
1221
|
+
width: line,
|
|
1222
|
+
height: vLineHeight,
|
|
1223
|
+
},
|
|
1224
|
+
}),
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
return handles.filter(Boolean);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function buildGapSpacingHandles(
|
|
1231
|
+
el: Element,
|
|
1232
|
+
rect: DOMRect,
|
|
1233
|
+
cs: CSSStyleDeclaration,
|
|
1234
|
+
): unknown[] {
|
|
1235
|
+
var children = visibleLayoutChildren(el);
|
|
1236
|
+
if (children.length < 2) return [];
|
|
1237
|
+
var handles = [];
|
|
1238
|
+
var sx = chromeScaleX();
|
|
1239
|
+
var sy = chromeScaleY();
|
|
1240
|
+
var line = Math.max(1, chromeLineScale());
|
|
1241
|
+
var hLineWidth = 8 * sx;
|
|
1242
|
+
var vLineHeight = 8 * sy;
|
|
1243
|
+
var isFlex = cs.display === "flex" || cs.display === "inline-flex";
|
|
1244
|
+
var isGrid = cs.display === "grid" || cs.display === "inline-grid";
|
|
1245
|
+
if (!isFlex && !isGrid) return handles;
|
|
1246
|
+
var primaryAxis =
|
|
1247
|
+
isFlex && cs.flexDirection && cs.flexDirection.indexOf("column") === 0
|
|
1248
|
+
? "y"
|
|
1249
|
+
: "x";
|
|
1250
|
+
var childRects = children.map(function (child) {
|
|
1251
|
+
return childLocalRect(child, rect);
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
function addAxisGaps(axis, property, groupKey) {
|
|
1255
|
+
var cssGap = readPx(cs[property]);
|
|
1256
|
+
if (cssGap <= 0) return;
|
|
1257
|
+
var sorted = childRects.slice().sort(function (a, b) {
|
|
1258
|
+
return axis === "x" ? a.left - b.left : a.top - b.top;
|
|
1259
|
+
});
|
|
1260
|
+
var count = 0;
|
|
1261
|
+
for (var i = 0; i < sorted.length - 1; i += 1) {
|
|
1262
|
+
var a = sorted[i];
|
|
1263
|
+
var b = sorted[i + 1];
|
|
1264
|
+
if (!childRectsOverlap(a, b, axis)) continue;
|
|
1265
|
+
var gap = axis === "x" ? b.left - a.right : b.top - a.bottom;
|
|
1266
|
+
if (gap <= 1) continue;
|
|
1267
|
+
if (axis === "x") {
|
|
1268
|
+
var top = Math.max(a.top, b.top);
|
|
1269
|
+
var bottom = Math.min(a.bottom, b.bottom);
|
|
1270
|
+
var height = Math.max(1, bottom - top);
|
|
1271
|
+
handles.push(
|
|
1272
|
+
makeSpacingHandle({
|
|
1273
|
+
key: groupKey + ":" + count,
|
|
1274
|
+
groupKey: groupKey,
|
|
1275
|
+
kind: "gap",
|
|
1276
|
+
property: property,
|
|
1277
|
+
orientation: "vertical",
|
|
1278
|
+
value: cssGap,
|
|
1279
|
+
region: { x: a.right, y: top, width: gap, height: height },
|
|
1280
|
+
line: {
|
|
1281
|
+
x: a.right + gap / 2 - line / 2,
|
|
1282
|
+
y: top + height / 2 - vLineHeight / 2,
|
|
1283
|
+
width: line,
|
|
1284
|
+
height: vLineHeight,
|
|
1285
|
+
},
|
|
1286
|
+
}),
|
|
1287
|
+
);
|
|
1288
|
+
} else {
|
|
1289
|
+
var left = Math.max(a.left, b.left);
|
|
1290
|
+
var right = Math.min(a.right, b.right);
|
|
1291
|
+
var width = Math.max(1, right - left);
|
|
1292
|
+
handles.push(
|
|
1293
|
+
makeSpacingHandle({
|
|
1294
|
+
key: groupKey + ":" + count,
|
|
1295
|
+
groupKey: groupKey,
|
|
1296
|
+
kind: "gap",
|
|
1297
|
+
property: property,
|
|
1298
|
+
orientation: "horizontal",
|
|
1299
|
+
value: cssGap,
|
|
1300
|
+
region: { x: left, y: a.bottom, width: width, height: gap },
|
|
1301
|
+
line: {
|
|
1302
|
+
x: left + width / 2 - hLineWidth / 2,
|
|
1303
|
+
y: a.bottom + gap / 2 - line / 2,
|
|
1304
|
+
width: hLineWidth,
|
|
1305
|
+
height: line,
|
|
1306
|
+
},
|
|
1307
|
+
}),
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
count += 1;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
if (primaryAxis === "x") {
|
|
1315
|
+
addAxisGaps("x", "columnGap", "gap:column");
|
|
1316
|
+
if (isGrid) addAxisGaps("y", "rowGap", "gap:row");
|
|
1317
|
+
} else {
|
|
1318
|
+
addAxisGaps("y", "rowGap", "gap:row");
|
|
1319
|
+
if (isGrid) addAxisGaps("x", "columnGap", "gap:column");
|
|
1320
|
+
}
|
|
1321
|
+
return handles.filter(Boolean);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function buildSpacingHandles(el: Element | null): ({
|
|
1325
|
+
key: string;
|
|
1326
|
+
groupKey: string;
|
|
1327
|
+
kind: string;
|
|
1328
|
+
property: string;
|
|
1329
|
+
oppositeProperty: string;
|
|
1330
|
+
side: string;
|
|
1331
|
+
orientation: string;
|
|
1332
|
+
value: number;
|
|
1333
|
+
region: { x: number; y: number; width: number; height: number };
|
|
1334
|
+
line: { x: number; y: number; width: number; height: number } | undefined;
|
|
1335
|
+
} | null)[] {
|
|
1336
|
+
if (!el || !document.documentElement.contains(el)) return [];
|
|
1337
|
+
if (Math.abs(currentRotation(el)) > 0.01) return [];
|
|
1338
|
+
var children = visibleLayoutChildren(el);
|
|
1339
|
+
if (children.length === 0) return [];
|
|
1340
|
+
var rect = el.getBoundingClientRect();
|
|
1341
|
+
if (rect.width <= 0 || rect.height <= 0) return [];
|
|
1342
|
+
var cs = window.getComputedStyle(el);
|
|
1343
|
+
return buildPaddingSpacingHandles(el, rect, cs).concat(
|
|
1344
|
+
buildGapSpacingHandles(el, rect, cs),
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
function showSpacingBadgeForHandle(
|
|
1349
|
+
handle: {
|
|
1350
|
+
key: string;
|
|
1351
|
+
groupKey: string;
|
|
1352
|
+
kind: string;
|
|
1353
|
+
property: string;
|
|
1354
|
+
oppositeProperty: string;
|
|
1355
|
+
side: string;
|
|
1356
|
+
orientation: string;
|
|
1357
|
+
value: number;
|
|
1358
|
+
region: { x: number; y: number; width: number; height: number };
|
|
1359
|
+
line: { x: number; y: number; width: number; height: number } | undefined;
|
|
1360
|
+
} | null,
|
|
1361
|
+
value: number,
|
|
1362
|
+
): void {
|
|
1363
|
+
if (!selectedEl || !handle) {
|
|
1364
|
+
spacingBadge.style.display = "none";
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
var rect = selectedEl.getBoundingClientRect();
|
|
1368
|
+
var x = rect.left + handle.region.x + handle.region.width / 2;
|
|
1369
|
+
var y = rect.top + handle.region.y + handle.region.height / 2;
|
|
1370
|
+
spacingBadge.textContent = String(clampSpacingValue(value));
|
|
1371
|
+
spacingBadge.style.display = "block";
|
|
1372
|
+
spacingBadge.style.background = spacingColor(handle.kind);
|
|
1373
|
+
spacingBadge.style.left = x + "px";
|
|
1374
|
+
spacingBadge.style.top = y + "px";
|
|
1375
|
+
spacingBadge.style.transform = "translate(-50%, -50%)";
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
function renderSpacingHandle(
|
|
1379
|
+
handle: {
|
|
1380
|
+
key: string;
|
|
1381
|
+
groupKey: string;
|
|
1382
|
+
kind: string;
|
|
1383
|
+
property: string;
|
|
1384
|
+
oppositeProperty: string;
|
|
1385
|
+
side: string;
|
|
1386
|
+
orientation: string;
|
|
1387
|
+
value: number;
|
|
1388
|
+
region: { x: number; y: number; width: number; height: number };
|
|
1389
|
+
line: { x: number; y: number; width: number; height: number } | undefined;
|
|
1390
|
+
} | null,
|
|
1391
|
+
activeGroupKey: string,
|
|
1392
|
+
): void {
|
|
1393
|
+
if (!handle) return;
|
|
1394
|
+
spacingHandleStateByKey[handle.key] = handle;
|
|
1395
|
+
var highlighted = Boolean(
|
|
1396
|
+
activeGroupKey && handle.groupKey === activeGroupKey,
|
|
1397
|
+
);
|
|
1398
|
+
var lineNode = document.createElement("span");
|
|
1399
|
+
lineNode.setAttribute("data-agent-native-spacing-line", handle.kind);
|
|
1400
|
+
lineNode.style.display = "block";
|
|
1401
|
+
lineNode.style.left = handle.line.x + "px";
|
|
1402
|
+
lineNode.style.top = handle.line.y + "px";
|
|
1403
|
+
lineNode.style.width = Math.max(1, handle.line.width) + "px";
|
|
1404
|
+
lineNode.style.height = Math.max(1, handle.line.height) + "px";
|
|
1405
|
+
lineNode.style.background = spacingColor(handle.kind);
|
|
1406
|
+
spacingOverlay.appendChild(lineNode);
|
|
1407
|
+
|
|
1408
|
+
var regionNode = document.createElement("span");
|
|
1409
|
+
regionNode.setAttribute("data-agent-native-spacing-region", handle.kind);
|
|
1410
|
+
regionNode.setAttribute("data-orientation", handle.orientation);
|
|
1411
|
+
regionNode.setAttribute("data-spacing-key", handle.key);
|
|
1412
|
+
regionNode.style.display = "block";
|
|
1413
|
+
regionNode.style.left = handle.region.x + "px";
|
|
1414
|
+
regionNode.style.top = handle.region.y + "px";
|
|
1415
|
+
regionNode.style.width = handle.region.width + "px";
|
|
1416
|
+
regionNode.style.height = handle.region.height + "px";
|
|
1417
|
+
regionNode.style.background = highlighted
|
|
1418
|
+
? spacingFill(handle.kind, handle.orientation)
|
|
1419
|
+
: "transparent";
|
|
1420
|
+
regionNode.style.outline = highlighted
|
|
1421
|
+
? "1px solid " + spacingColor(handle.kind)
|
|
1422
|
+
: "0";
|
|
1423
|
+
regionNode.style.outlineOffset = "-1px";
|
|
1424
|
+
regionNode.addEventListener(
|
|
1425
|
+
"mousedown",
|
|
1426
|
+
function (event) {
|
|
1427
|
+
startSpacingDrag(handle.key, event);
|
|
1428
|
+
},
|
|
1429
|
+
true,
|
|
1430
|
+
);
|
|
1431
|
+
spacingHandleNodesByKey[handle.key] = regionNode;
|
|
1432
|
+
spacingOverlay.appendChild(regionNode);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function updateSpacingOverlay(el: Element | null): void {
|
|
1436
|
+
if (el && el !== selectedEl) {
|
|
1437
|
+
hideSpacingOverlay();
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
if (!selectedEl || !document.documentElement.contains(selectedEl)) {
|
|
1441
|
+
hideSpacingOverlay();
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
if (!selectedSpacingHovered && !hoveredSpacingHandleKey && !spacingDrag) {
|
|
1445
|
+
hideSpacingOverlay();
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
var handles = buildSpacingHandles(selectedEl);
|
|
1449
|
+
if (handles.length === 0) {
|
|
1450
|
+
hideSpacingOverlay();
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
var activeHandle =
|
|
1454
|
+
(spacingDrag && spacingDrag.handle) ||
|
|
1455
|
+
handles.find(function (handle) {
|
|
1456
|
+
return handle.key === hoveredSpacingHandleKey;
|
|
1457
|
+
}) ||
|
|
1458
|
+
null;
|
|
1459
|
+
var activeGroupKey = activeHandle ? activeHandle.groupKey : "";
|
|
1460
|
+
var nextRenderKey =
|
|
1461
|
+
activeGroupKey +
|
|
1462
|
+
"|" +
|
|
1463
|
+
handles
|
|
1464
|
+
.map(function (handle) {
|
|
1465
|
+
return [
|
|
1466
|
+
handle.key,
|
|
1467
|
+
handle.value,
|
|
1468
|
+
handle.region.x,
|
|
1469
|
+
handle.region.y,
|
|
1470
|
+
handle.region.width,
|
|
1471
|
+
handle.region.height,
|
|
1472
|
+
handle.line.x,
|
|
1473
|
+
handle.line.y,
|
|
1474
|
+
handle.line.width,
|
|
1475
|
+
handle.line.height,
|
|
1476
|
+
].join(",");
|
|
1477
|
+
})
|
|
1478
|
+
.join("|");
|
|
1479
|
+
if (
|
|
1480
|
+
spacingOverlay.style.display === "block" &&
|
|
1481
|
+
spacingOverlayRenderKey === nextRenderKey
|
|
1482
|
+
) {
|
|
1483
|
+
if (activeHandle) {
|
|
1484
|
+
showSpacingBadgeForHandle(
|
|
1485
|
+
activeHandle,
|
|
1486
|
+
spacingDrag ? spacingDrag.currentValue : activeHandle.value,
|
|
1487
|
+
);
|
|
1488
|
+
} else {
|
|
1489
|
+
spacingBadge.style.display = "none";
|
|
1490
|
+
}
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
spacingOverlayRenderKey = nextRenderKey;
|
|
1494
|
+
spacingOverlay.style.display = "block";
|
|
1495
|
+
spacingOverlay.innerHTML = "";
|
|
1496
|
+
spacingHandleStateByKey = {};
|
|
1497
|
+
spacingHandleNodesByKey = {};
|
|
1498
|
+
handles.forEach(function (handle) {
|
|
1499
|
+
renderSpacingHandle(handle, activeGroupKey);
|
|
1500
|
+
});
|
|
1501
|
+
if (activeHandle) {
|
|
1502
|
+
showSpacingBadgeForHandle(
|
|
1503
|
+
activeHandle,
|
|
1504
|
+
spacingDrag ? spacingDrag.currentValue : activeHandle.value,
|
|
1505
|
+
);
|
|
1506
|
+
} else {
|
|
1507
|
+
spacingBadge.style.display = "none";
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function handleSpacingOverlayPointerMove(e: PointerEvent): void {
|
|
1512
|
+
if (spacingDrag) return;
|
|
1513
|
+
var target =
|
|
1514
|
+
e.target && e.target.closest
|
|
1515
|
+
? e.target.closest("[data-agent-native-spacing-region]")
|
|
1516
|
+
: null;
|
|
1517
|
+
var spacingKey =
|
|
1518
|
+
target && target.getAttribute
|
|
1519
|
+
? target.getAttribute("data-spacing-key")
|
|
1520
|
+
: "";
|
|
1521
|
+
if (!spacingKey) return;
|
|
1522
|
+
stopNativeInteraction(e);
|
|
1523
|
+
selectedSpacingHovered = true;
|
|
1524
|
+
if (hoveredSpacingHandleKey !== spacingKey) {
|
|
1525
|
+
hoveredSpacingHandleKey = spacingKey;
|
|
1526
|
+
updateSpacingOverlay(selectedEl);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
function spacingRegionFromPoint(
|
|
1531
|
+
clientX: number,
|
|
1532
|
+
clientY: number,
|
|
1533
|
+
): Element | null {
|
|
1534
|
+
var targets = document.elementsFromPoint
|
|
1535
|
+
? document.elementsFromPoint(clientX, clientY)
|
|
1536
|
+
: [document.elementFromPoint(clientX, clientY)];
|
|
1537
|
+
for (var i = 0; i < targets.length; i += 1) {
|
|
1538
|
+
var target = targets[i];
|
|
1539
|
+
if (!target || target.nodeType !== 1 || !target.closest) continue;
|
|
1540
|
+
var region = target.closest("[data-agent-native-spacing-region]");
|
|
1541
|
+
if (region) return region;
|
|
1542
|
+
}
|
|
1543
|
+
return null;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function selectedSpacingSurfaceContainsPoint(
|
|
1547
|
+
clientX: number,
|
|
1548
|
+
clientY: number,
|
|
1549
|
+
): boolean {
|
|
1550
|
+
if (!selectedEl || !document.documentElement.contains(selectedEl))
|
|
1551
|
+
return false;
|
|
1552
|
+
var region = spacingRegionFromPoint(clientX, clientY);
|
|
1553
|
+
if (region) {
|
|
1554
|
+
var spacingKey = region.getAttribute
|
|
1555
|
+
? region.getAttribute("data-spacing-key")
|
|
1556
|
+
: "";
|
|
1557
|
+
if (spacingKey) hoveredSpacingHandleKey = spacingKey;
|
|
1558
|
+
selectedSpacingHovered = true;
|
|
1559
|
+
return true;
|
|
1560
|
+
}
|
|
1561
|
+
var hit = elementFromEditorPoint(clientX, clientY);
|
|
1562
|
+
if (
|
|
1563
|
+
hit &&
|
|
1564
|
+
(hit === selectedEl || (selectedEl.contains && selectedEl.contains(hit)))
|
|
1565
|
+
) {
|
|
1566
|
+
selectedSpacingHovered = true;
|
|
1567
|
+
return true;
|
|
1568
|
+
}
|
|
1569
|
+
return false;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
function shouldKeepSpacingOverlayForLeave(e: PointerEvent): boolean {
|
|
1573
|
+
if (spacingDrag) return true;
|
|
1574
|
+
if (e.relatedTarget && isOverlayElement(e.relatedTarget)) return true;
|
|
1575
|
+
if (Number.isFinite(e.clientX) && Number.isFinite(e.clientY)) {
|
|
1576
|
+
return selectedSpacingSurfaceContainsPoint(e.clientX, e.clientY);
|
|
1577
|
+
}
|
|
1578
|
+
return false;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
function applyEditorChromeScale() {
|
|
1582
|
+
syncEditorChromeScaleVars();
|
|
1583
|
+
var sx = chromeScaleX();
|
|
1584
|
+
var sy = chromeScaleY();
|
|
1585
|
+
var line = chromeLineScale();
|
|
1586
|
+
highlightOverlay.style.borderWidth = 1.5 * line + "px";
|
|
1587
|
+
selectionOverlay.style.borderWidth = 1.5 * line + "px";
|
|
1588
|
+
if (selectedEl) updateSpacingOverlay(selectedEl);
|
|
1589
|
+
|
|
1590
|
+
selectionOverlay
|
|
1591
|
+
.querySelectorAll("[data-agent-native-edge-handle]")
|
|
1592
|
+
.forEach(function (edge) {
|
|
1593
|
+
var pos = edge.getAttribute("data-agent-native-edge-handle");
|
|
1594
|
+
if (pos === "n" || pos === "s") {
|
|
1595
|
+
edge.style.height = 10 * sy + "px";
|
|
1596
|
+
edge.style[pos === "n" ? "top" : "bottom"] = -5 * sy + "px";
|
|
1597
|
+
}
|
|
1598
|
+
if (pos === "e" || pos === "w") {
|
|
1599
|
+
edge.style.width = 10 * sx + "px";
|
|
1600
|
+
edge.style[pos === "w" ? "left" : "right"] = -5 * sx + "px";
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
selectionOverlay
|
|
1605
|
+
.querySelectorAll("[data-agent-native-edit-handle]")
|
|
1606
|
+
.forEach(function (handle) {
|
|
1607
|
+
var pos = handle.getAttribute("data-agent-native-edit-handle") || "";
|
|
1608
|
+
handle.style.width = 7 * sx + "px";
|
|
1609
|
+
handle.style.height = 7 * sy + "px";
|
|
1610
|
+
handle.style.borderWidth = Math.max(1, 1 * line) + "px";
|
|
1611
|
+
if (pos.indexOf("n") !== -1) handle.style.top = -4 * sy + "px";
|
|
1612
|
+
if (pos.indexOf("s") !== -1) handle.style.bottom = -4 * sy + "px";
|
|
1613
|
+
if (pos.indexOf("w") !== -1) handle.style.left = -4 * sx + "px";
|
|
1614
|
+
if (pos.indexOf("e") !== -1) handle.style.right = -4 * sx + "px";
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
selectionOverlay
|
|
1618
|
+
.querySelectorAll("[data-agent-native-rotate-handle]")
|
|
1619
|
+
.forEach(function (handle) {
|
|
1620
|
+
var pos = handle.getAttribute("data-agent-native-rotate-handle") || "";
|
|
1621
|
+
handle.style.width = 18 * sx + "px";
|
|
1622
|
+
handle.style.height = 18 * sy + "px";
|
|
1623
|
+
if (pos.indexOf("n") !== -1) handle.style.top = -26 * sy + "px";
|
|
1624
|
+
if (pos.indexOf("s") !== -1) handle.style.bottom = -26 * sy + "px";
|
|
1625
|
+
if (pos.indexOf("w") !== -1) handle.style.left = -26 * sx + "px";
|
|
1626
|
+
if (pos.indexOf("e") !== -1) handle.style.right = -26 * sx + "px";
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
function positionOverlay(overlay: HTMLElement, el: Element): void {
|
|
1631
|
+
if (!el || !document.documentElement.contains(el)) {
|
|
1632
|
+
overlay.style.display = "none";
|
|
1633
|
+
if (overlay === selectionOverlay) clearComponentTag();
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
// For the selection overlay, prefer the CSS box + rotation transform so
|
|
1637
|
+
// the handles hug the rotated element rather than its inflated AABB.
|
|
1638
|
+
if (overlay === selectionOverlay) {
|
|
1639
|
+
var elCs = window.getComputedStyle(el);
|
|
1640
|
+
var elLeft = readFinitePx(el.style.left || elCs.left);
|
|
1641
|
+
var elTop = readFinitePx(el.style.top || elCs.top);
|
|
1642
|
+
var elW = readFinitePx(el.style.width || elCs.width);
|
|
1643
|
+
var elH = readFinitePx(el.style.height || elCs.height);
|
|
1644
|
+
var elRot = currentRotation(el);
|
|
1645
|
+
var canUseLocalBox =
|
|
1646
|
+
Math.abs(elRot) > 0.01 &&
|
|
1647
|
+
elLeft !== null &&
|
|
1648
|
+
elTop !== null &&
|
|
1649
|
+
elW !== null &&
|
|
1650
|
+
elH !== null;
|
|
1651
|
+
// Convert element-local left/top to viewport coords by walking to the
|
|
1652
|
+
// nearest positioned ancestor (same reference frame as getBoundingClientRect).
|
|
1653
|
+
if (canUseLocalBox) {
|
|
1654
|
+
var parentRect = (
|
|
1655
|
+
(el as HTMLElement).offsetParent || document.documentElement
|
|
1656
|
+
).getBoundingClientRect();
|
|
1657
|
+
overlay.style.display = "block";
|
|
1658
|
+
overlay.style.left = parentRect.left + elLeft + "px";
|
|
1659
|
+
overlay.style.top = parentRect.top + elTop + "px";
|
|
1660
|
+
overlay.style.width = elW + "px";
|
|
1661
|
+
overlay.style.height = elH + "px";
|
|
1662
|
+
overlay.style.transform = "rotate(" + elRot + "deg)";
|
|
1663
|
+
overlay.style.transformOrigin = "0 0";
|
|
1664
|
+
updateSpacingOverlay(el);
|
|
1665
|
+
updateComponentTag(el);
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
var rect = el.getBoundingClientRect();
|
|
1670
|
+
overlay.style.display = "block";
|
|
1671
|
+
overlay.style.top = rect.top + "px";
|
|
1672
|
+
overlay.style.left = rect.left + "px";
|
|
1673
|
+
overlay.style.width = rect.width + "px";
|
|
1674
|
+
overlay.style.height = rect.height + "px";
|
|
1675
|
+
overlay.style.transform = "";
|
|
1676
|
+
if (overlay === selectionOverlay) {
|
|
1677
|
+
updateSpacingOverlay(el);
|
|
1678
|
+
updateComponentTag(el);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
function refreshOverlays(): void {
|
|
1683
|
+
if (hoveredEl && hoveredEl !== selectedEl) {
|
|
1684
|
+
positionOverlay(highlightOverlay, hoveredEl);
|
|
1685
|
+
} else {
|
|
1686
|
+
highlightOverlay.style.display = "none";
|
|
1687
|
+
}
|
|
1688
|
+
if (selectedEl) positionOverlay(selectionOverlay, selectedEl);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function hideMeasurements(): void {
|
|
1692
|
+
measurementOverlay.style.display = "none";
|
|
1693
|
+
measurementOverlay.innerHTML = "";
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
function addMeasurementLine(x1, y1, x2, y2, label) {
|
|
1697
|
+
var horizontal = Math.abs(x2 - x1) >= Math.abs(y2 - y1);
|
|
1698
|
+
var line = document.createElement("div");
|
|
1699
|
+
var labelEl = document.createElement("div");
|
|
1700
|
+
if (horizontal) {
|
|
1701
|
+
var left = Math.min(x1, x2);
|
|
1702
|
+
var width = Math.max(1, Math.abs(x2 - x1));
|
|
1703
|
+
line.style.cssText =
|
|
1704
|
+
"position:fixed;left:" +
|
|
1705
|
+
left +
|
|
1706
|
+
"px;top:" +
|
|
1707
|
+
y1 +
|
|
1708
|
+
"px;width:" +
|
|
1709
|
+
width +
|
|
1710
|
+
"px;border-top:1px dashed var(--design-editor-measure-color);";
|
|
1711
|
+
labelEl.style.cssText =
|
|
1712
|
+
"position:fixed;left:" +
|
|
1713
|
+
(left + width / 2) +
|
|
1714
|
+
"px;top:" +
|
|
1715
|
+
(y1 - 9) +
|
|
1716
|
+
"px;transform:translateX(-50%);border-radius:3px;background:var(--design-editor-measure-color);color:white;padding:1px 4px;";
|
|
1717
|
+
} else {
|
|
1718
|
+
var top = Math.min(y1, y2);
|
|
1719
|
+
var height = Math.max(1, Math.abs(y2 - y1));
|
|
1720
|
+
line.style.cssText =
|
|
1721
|
+
"position:fixed;left:" +
|
|
1722
|
+
x1 +
|
|
1723
|
+
"px;top:" +
|
|
1724
|
+
top +
|
|
1725
|
+
"px;height:" +
|
|
1726
|
+
height +
|
|
1727
|
+
"px;border-left:1px dashed var(--design-editor-measure-color);";
|
|
1728
|
+
labelEl.style.cssText =
|
|
1729
|
+
"position:fixed;left:" +
|
|
1730
|
+
(x1 + 5) +
|
|
1731
|
+
"px;top:" +
|
|
1732
|
+
(top + height / 2) +
|
|
1733
|
+
"px;transform:translateY(-50%);border-radius:3px;background:var(--design-editor-measure-color);color:white;padding:1px 4px;";
|
|
1734
|
+
}
|
|
1735
|
+
labelEl.textContent = label;
|
|
1736
|
+
measurementOverlay.appendChild(line);
|
|
1737
|
+
measurementOverlay.appendChild(labelEl);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
function showMeasurements(a, b) {
|
|
1741
|
+
if (!a || !b || a === b) {
|
|
1742
|
+
hideMeasurements();
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
var selectedRect = a.getBoundingClientRect();
|
|
1746
|
+
var hoverRect = b.getBoundingClientRect();
|
|
1747
|
+
measurementOverlay.innerHTML = "";
|
|
1748
|
+
measurementOverlay.style.display = "block";
|
|
1749
|
+
|
|
1750
|
+
if (hoverRect.right <= selectedRect.left) {
|
|
1751
|
+
var yLeft = Math.max(
|
|
1752
|
+
hoverRect.top,
|
|
1753
|
+
Math.min(hoverRect.bottom, selectedRect.top + selectedRect.height / 2),
|
|
1754
|
+
);
|
|
1755
|
+
addMeasurementLine(
|
|
1756
|
+
hoverRect.right,
|
|
1757
|
+
yLeft,
|
|
1758
|
+
selectedRect.left,
|
|
1759
|
+
yLeft,
|
|
1760
|
+
Math.round(selectedRect.left - hoverRect.right) + "px",
|
|
1761
|
+
);
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
if (selectedRect.right <= hoverRect.left) {
|
|
1765
|
+
var yRight = Math.max(
|
|
1766
|
+
selectedRect.top,
|
|
1767
|
+
Math.min(selectedRect.bottom, hoverRect.top + hoverRect.height / 2),
|
|
1768
|
+
);
|
|
1769
|
+
addMeasurementLine(
|
|
1770
|
+
selectedRect.right,
|
|
1771
|
+
yRight,
|
|
1772
|
+
hoverRect.left,
|
|
1773
|
+
yRight,
|
|
1774
|
+
Math.round(hoverRect.left - selectedRect.right) + "px",
|
|
1775
|
+
);
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
if (hoverRect.bottom <= selectedRect.top) {
|
|
1779
|
+
var xTop = Math.max(
|
|
1780
|
+
hoverRect.left,
|
|
1781
|
+
Math.min(hoverRect.right, selectedRect.left + selectedRect.width / 2),
|
|
1782
|
+
);
|
|
1783
|
+
addMeasurementLine(
|
|
1784
|
+
xTop,
|
|
1785
|
+
hoverRect.bottom,
|
|
1786
|
+
xTop,
|
|
1787
|
+
selectedRect.top,
|
|
1788
|
+
Math.round(selectedRect.top - hoverRect.bottom) + "px",
|
|
1789
|
+
);
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
if (selectedRect.bottom <= hoverRect.top) {
|
|
1793
|
+
var xBottom = Math.max(
|
|
1794
|
+
selectedRect.left,
|
|
1795
|
+
Math.min(selectedRect.right, hoverRect.left + hoverRect.width / 2),
|
|
1796
|
+
);
|
|
1797
|
+
addMeasurementLine(
|
|
1798
|
+
xBottom,
|
|
1799
|
+
selectedRect.bottom,
|
|
1800
|
+
xBottom,
|
|
1801
|
+
hoverRect.top,
|
|
1802
|
+
Math.round(hoverRect.top - selectedRect.bottom) + "px",
|
|
1803
|
+
);
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
addMeasurementLine(
|
|
1807
|
+
selectedRect.left + selectedRect.width / 2,
|
|
1808
|
+
selectedRect.top + selectedRect.height / 2,
|
|
1809
|
+
hoverRect.left + hoverRect.width / 2,
|
|
1810
|
+
hoverRect.top + hoverRect.height / 2,
|
|
1811
|
+
Math.round(
|
|
1812
|
+
Math.hypot(
|
|
1813
|
+
hoverRect.left +
|
|
1814
|
+
hoverRect.width / 2 -
|
|
1815
|
+
(selectedRect.left + selectedRect.width / 2),
|
|
1816
|
+
hoverRect.top +
|
|
1817
|
+
hoverRect.height / 2 -
|
|
1818
|
+
(selectedRect.top + selectedRect.height / 2),
|
|
1819
|
+
),
|
|
1820
|
+
) + "px",
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
function dragEventNames(e) {
|
|
1825
|
+
var pointerGesture = e && e.type && e.type.indexOf("pointer") === 0;
|
|
1826
|
+
return pointerGesture
|
|
1827
|
+
? { move: "pointermove", up: "pointerup" }
|
|
1828
|
+
: { move: "mousemove", up: "mouseup" };
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
function elementFromEditorPoint(
|
|
1832
|
+
clientX: number,
|
|
1833
|
+
clientY: number,
|
|
1834
|
+
): Element | null {
|
|
1835
|
+
lastEditorPointWasBlocked = false;
|
|
1836
|
+
var shieldPointerEvents = shieldOverlay.style.pointerEvents;
|
|
1837
|
+
var selectionPointerEvents = selectionOverlay.style.pointerEvents;
|
|
1838
|
+
var highlightPointerEvents = highlightOverlay.style.pointerEvents;
|
|
1839
|
+
shieldOverlay.style.pointerEvents = "none";
|
|
1840
|
+
selectionOverlay.style.pointerEvents = "none";
|
|
1841
|
+
highlightOverlay.style.pointerEvents = "none";
|
|
1842
|
+
var targets = document.elementsFromPoint
|
|
1843
|
+
? document.elementsFromPoint(clientX, clientY)
|
|
1844
|
+
: [document.elementFromPoint(clientX, clientY)];
|
|
1845
|
+
shieldOverlay.style.pointerEvents = shieldPointerEvents;
|
|
1846
|
+
selectionOverlay.style.pointerEvents = selectionPointerEvents;
|
|
1847
|
+
highlightOverlay.style.pointerEvents = highlightPointerEvents;
|
|
1848
|
+
for (var i = 0; i < targets.length; i += 1) {
|
|
1849
|
+
var target = targets[i];
|
|
1850
|
+
if (!target || target.nodeType !== 1) continue;
|
|
1851
|
+
if (isOverlayElement(target)) continue;
|
|
1852
|
+
if (isLayerInteractionBlocked(target)) {
|
|
1853
|
+
lastEditorPointWasBlocked = true;
|
|
1854
|
+
return null;
|
|
1855
|
+
}
|
|
1856
|
+
return target;
|
|
1857
|
+
}
|
|
1858
|
+
return null;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
function stopNativeInteraction(e: Event): void {
|
|
1862
|
+
e.preventDefault();
|
|
1863
|
+
e.stopPropagation();
|
|
1864
|
+
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
function isEditorTypingTarget(target) {
|
|
1868
|
+
if (!target || !target.closest) return false;
|
|
1869
|
+
return !!target.closest(
|
|
1870
|
+
'input, textarea, select, [contenteditable], [role="textbox"], [data-agent-native-text-editing]',
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
function shouldForwardDesignHotkey(e) {
|
|
1875
|
+
if (activeTextEditEl || isEditorTypingTarget(e.target) || e.isComposing)
|
|
1876
|
+
return false;
|
|
1877
|
+
var key = e.key;
|
|
1878
|
+
var normalized = key && key.length === 1 ? key.toLowerCase() : key;
|
|
1879
|
+
var primary = e.metaKey || e.ctrlKey;
|
|
1880
|
+
if (key === "Escape" || key === "Enter") return true;
|
|
1881
|
+
// Forward Tab only when an element is actively selected so the iframe does
|
|
1882
|
+
// not intercept Tab when the user is tabbing through browser UI with nothing
|
|
1883
|
+
// selected (preserves native keyboard accessibility).
|
|
1884
|
+
if (key === "Tab") return !!selectedEl;
|
|
1885
|
+
if (key === "Delete" || key === "Backspace") return !primary;
|
|
1886
|
+
if (/^Arrow/.test(key || "")) return !e.altKey;
|
|
1887
|
+
if (primary) {
|
|
1888
|
+
return (
|
|
1889
|
+
[
|
|
1890
|
+
"z",
|
|
1891
|
+
"y",
|
|
1892
|
+
"a",
|
|
1893
|
+
"x",
|
|
1894
|
+
"c",
|
|
1895
|
+
"v",
|
|
1896
|
+
"d",
|
|
1897
|
+
"g",
|
|
1898
|
+
"=",
|
|
1899
|
+
"+",
|
|
1900
|
+
"-",
|
|
1901
|
+
"0",
|
|
1902
|
+
"]",
|
|
1903
|
+
"[",
|
|
1904
|
+
].indexOf(normalized) !== -1 ||
|
|
1905
|
+
e.code === "Digit1" ||
|
|
1906
|
+
e.code === "Digit2" ||
|
|
1907
|
+
key === "1" ||
|
|
1908
|
+
key === "2"
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1911
|
+
if (
|
|
1912
|
+
e.shiftKey &&
|
|
1913
|
+
(e.code === "Digit1" || e.code === "Digit2" || key === "1" || key === "2")
|
|
1914
|
+
)
|
|
1915
|
+
return true;
|
|
1916
|
+
return (
|
|
1917
|
+
!e.altKey &&
|
|
1918
|
+
!e.shiftKey &&
|
|
1919
|
+
["v", "f", "r", "t", "p", "h", "c", "k"].indexOf(normalized) !== -1
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
function blurActiveTextEditor(): void {
|
|
1924
|
+
var active = document.activeElement;
|
|
1925
|
+
if (
|
|
1926
|
+
active &&
|
|
1927
|
+
active.closest &&
|
|
1928
|
+
active.closest("[data-agent-native-text-editing]") &&
|
|
1929
|
+
typeof active.blur === "function"
|
|
1930
|
+
) {
|
|
1931
|
+
active.blur();
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
function setTextEditingPointerPassthrough(enabled: boolean): void {
|
|
1936
|
+
if (enabled) {
|
|
1937
|
+
if (!textEditPointerState) {
|
|
1938
|
+
textEditPointerState = {
|
|
1939
|
+
shield: shieldOverlay.style.pointerEvents,
|
|
1940
|
+
selection: selectionOverlay.style.pointerEvents,
|
|
1941
|
+
highlight: highlightOverlay.style.pointerEvents,
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
shieldOverlay.style.pointerEvents = "none";
|
|
1945
|
+
selectionOverlay.style.pointerEvents = "none";
|
|
1946
|
+
highlightOverlay.style.pointerEvents = "none";
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
if (!textEditPointerState) return;
|
|
1950
|
+
shieldOverlay.style.pointerEvents = textEditPointerState.shield;
|
|
1951
|
+
selectionOverlay.style.pointerEvents = textEditPointerState.selection;
|
|
1952
|
+
highlightOverlay.style.pointerEvents = textEditPointerState.highlight;
|
|
1953
|
+
textEditPointerState = null;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
function hasTextContent(el: Element | null): boolean {
|
|
1957
|
+
return !!(el && el.textContent && el.textContent.trim().length > 0);
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
function isInlineEditableDescendant(el: Element | null): boolean {
|
|
1961
|
+
if (!el || !el.tagName) return false;
|
|
1962
|
+
// Allowlist covers inline markup AND common block-level text containers
|
|
1963
|
+
// (p, h1-h6, li, etc.) so that paragraphs with inline markup like
|
|
1964
|
+
// <p>Hello <strong>world</strong></p> can be double-click edited.
|
|
1965
|
+
return (
|
|
1966
|
+
[
|
|
1967
|
+
// Inline formatting
|
|
1968
|
+
"a",
|
|
1969
|
+
"abbr",
|
|
1970
|
+
"b",
|
|
1971
|
+
"br",
|
|
1972
|
+
"cite",
|
|
1973
|
+
"code",
|
|
1974
|
+
"em",
|
|
1975
|
+
"i",
|
|
1976
|
+
"mark",
|
|
1977
|
+
"small",
|
|
1978
|
+
"span",
|
|
1979
|
+
"strong",
|
|
1980
|
+
"sub",
|
|
1981
|
+
"sup",
|
|
1982
|
+
"time",
|
|
1983
|
+
"u",
|
|
1984
|
+
"wbr",
|
|
1985
|
+
// Block-level text containers
|
|
1986
|
+
"p",
|
|
1987
|
+
"h1",
|
|
1988
|
+
"h2",
|
|
1989
|
+
"h3",
|
|
1990
|
+
"h4",
|
|
1991
|
+
"h5",
|
|
1992
|
+
"h6",
|
|
1993
|
+
"li",
|
|
1994
|
+
"ul",
|
|
1995
|
+
"ol",
|
|
1996
|
+
"dl",
|
|
1997
|
+
"dt",
|
|
1998
|
+
"dd",
|
|
1999
|
+
"label",
|
|
2000
|
+
"caption",
|
|
2001
|
+
"td",
|
|
2002
|
+
"th",
|
|
2003
|
+
].indexOf(el.tagName.toLowerCase()) !== -1
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
function hasOnlyInlineEditableChildren(el) {
|
|
2008
|
+
if (!el || !hasTextContent(el)) return false;
|
|
2009
|
+
var descendants = el.querySelectorAll ? el.querySelectorAll("*") : [];
|
|
2010
|
+
for (var i = 0; i < descendants.length; i += 1) {
|
|
2011
|
+
if (!isInlineEditableDescendant(descendants[i])) return false;
|
|
2012
|
+
}
|
|
2013
|
+
return true;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
function findTextEditTarget(hit) {
|
|
2017
|
+
if (
|
|
2018
|
+
!hit ||
|
|
2019
|
+
hit.nodeType !== 1 ||
|
|
2020
|
+
hit === document.body ||
|
|
2021
|
+
hit === document.documentElement
|
|
2022
|
+
)
|
|
2023
|
+
return null;
|
|
2024
|
+
var selectedContainsHit =
|
|
2025
|
+
selectedEl && selectedEl.contains && selectedEl.contains(hit);
|
|
2026
|
+
if (selectedContainsHit && hasOnlyInlineEditableChildren(selectedEl))
|
|
2027
|
+
return selectedEl;
|
|
2028
|
+
|
|
2029
|
+
var candidate = null;
|
|
2030
|
+
var node = hit;
|
|
2031
|
+
while (
|
|
2032
|
+
node &&
|
|
2033
|
+
node.nodeType === 1 &&
|
|
2034
|
+
node !== document.body &&
|
|
2035
|
+
node !== document.documentElement
|
|
2036
|
+
) {
|
|
2037
|
+
if (hasOnlyInlineEditableChildren(node)) {
|
|
2038
|
+
candidate = node;
|
|
2039
|
+
}
|
|
2040
|
+
if (selectedEl && node === selectedEl) break;
|
|
2041
|
+
node = node.parentElement;
|
|
2042
|
+
}
|
|
2043
|
+
// Return null (not the raw hit) when no text-editable ancestor is found.
|
|
2044
|
+
// Falling back to the raw hit element makes non-text nodes like <img> or
|
|
2045
|
+
// <canvas> contenteditable, which leaves the editor in a broken state.
|
|
2046
|
+
return candidate || null;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
function selectElementAtEvent(e) {
|
|
2050
|
+
stopNativeInteraction(e);
|
|
2051
|
+
blurActiveTextEditor();
|
|
2052
|
+
if (suppressNextShieldClick) {
|
|
2053
|
+
suppressNextShieldClick = false;
|
|
2054
|
+
if (suppressNextShieldClickTimer !== null) {
|
|
2055
|
+
clearTimeout(suppressNextShieldClickTimer);
|
|
2056
|
+
suppressNextShieldClickTimer = null;
|
|
2057
|
+
}
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
// Suppress the first click in a double-click sequence — the dblclick
|
|
2061
|
+
// handler (beginTextEditingFromEvent) will fire immediately after and a
|
|
2062
|
+
// spurious element-select would cause inspector flicker.
|
|
2063
|
+
if (e.detail >= 2) return;
|
|
2064
|
+
var target = elementFromEditorPoint(e.clientX, e.clientY);
|
|
2065
|
+
if (!target && lastEditorPointWasBlocked) return;
|
|
2066
|
+
if (
|
|
2067
|
+
!target ||
|
|
2068
|
+
target === document.body ||
|
|
2069
|
+
target === document.documentElement
|
|
2070
|
+
) {
|
|
2071
|
+
// Click on empty canvas: clear the current selection (matches Figma).
|
|
2072
|
+
clearRuntimeSelection();
|
|
2073
|
+
(window.parent as Window).postMessage({ type: "clear-selection" }, "*");
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
selectedSpacingHovered = false;
|
|
2077
|
+
hoveredSpacingHandleKey = "";
|
|
2078
|
+
selectedEl = selectionTargetForHit(target);
|
|
2079
|
+
if (!selectedEl || isLayerInteractionBlocked(selectedEl)) {
|
|
2080
|
+
selectedEl = null;
|
|
2081
|
+
selectionOverlay.style.display = "none";
|
|
2082
|
+
clearComponentTag();
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
var info = getElementInfo(selectedEl);
|
|
2086
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
2087
|
+
(window.parent as Window).postMessage(
|
|
2088
|
+
{ type: "element-select", payload: info },
|
|
2089
|
+
"*",
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
function suppressNextShieldClickBriefly() {
|
|
2094
|
+
suppressNextShieldClick = true;
|
|
2095
|
+
if (suppressNextShieldClickTimer !== null) {
|
|
2096
|
+
clearTimeout(suppressNextShieldClickTimer);
|
|
2097
|
+
}
|
|
2098
|
+
suppressNextShieldClickTimer = setTimeout(function () {
|
|
2099
|
+
suppressNextShieldClick = false;
|
|
2100
|
+
suppressNextShieldClickTimer = null;
|
|
2101
|
+
}, 250);
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
function openContextMenuAtEvent(e) {
|
|
2105
|
+
stopNativeInteraction(e);
|
|
2106
|
+
blurActiveTextEditor();
|
|
2107
|
+
var target = elementFromEditorPoint(e.clientX, e.clientY);
|
|
2108
|
+
if (!target && lastEditorPointWasBlocked) return;
|
|
2109
|
+
var info = null;
|
|
2110
|
+
if (target) {
|
|
2111
|
+
selectedSpacingHovered = false;
|
|
2112
|
+
hoveredSpacingHandleKey = "";
|
|
2113
|
+
selectedEl = selectionTargetForHit(target);
|
|
2114
|
+
if (selectedEl && !isLayerInteractionBlocked(selectedEl)) {
|
|
2115
|
+
info = getElementInfo(selectedEl);
|
|
2116
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
2117
|
+
(window.parent as Window).postMessage(
|
|
2118
|
+
{ type: "element-select", payload: info },
|
|
2119
|
+
"*",
|
|
2120
|
+
);
|
|
2121
|
+
} else {
|
|
2122
|
+
selectedEl = null;
|
|
2123
|
+
selectionOverlay.style.display = "none";
|
|
2124
|
+
clearComponentTag();
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
(window.parent as Window).postMessage(
|
|
2128
|
+
{
|
|
2129
|
+
type: "element-contextmenu",
|
|
2130
|
+
clientX: e.clientX,
|
|
2131
|
+
clientY: e.clientY,
|
|
2132
|
+
payload: info,
|
|
2133
|
+
},
|
|
2134
|
+
"*",
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
function findRuntimeTarget(selector, selectorCandidates) {
|
|
2139
|
+
var candidates: string[] = [];
|
|
2140
|
+
if (Array.isArray(selectorCandidates)) {
|
|
2141
|
+
selectorCandidates.forEach(function (candidate) {
|
|
2142
|
+
if (
|
|
2143
|
+
typeof candidate === "string" &&
|
|
2144
|
+
candidate &&
|
|
2145
|
+
candidates.indexOf(candidate) === -1
|
|
2146
|
+
) {
|
|
2147
|
+
candidates.push(candidate);
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
if (selector && candidates.indexOf(selector) === -1)
|
|
2152
|
+
candidates.push(selector);
|
|
2153
|
+
if (
|
|
2154
|
+
selectedEl &&
|
|
2155
|
+
document.documentElement.contains(selectedEl) &&
|
|
2156
|
+
(candidates.length === 0 ||
|
|
2157
|
+
matchesExactSelectorList(selectedEl, candidates))
|
|
2158
|
+
) {
|
|
2159
|
+
return selectedEl;
|
|
2160
|
+
}
|
|
2161
|
+
for (var i = 0; i < candidates.length; i += 1) {
|
|
2162
|
+
try {
|
|
2163
|
+
var match = document.querySelector(candidates[i]);
|
|
2164
|
+
if (match && !isLayerInteractionBlocked(match)) return match;
|
|
2165
|
+
} catch (_err) {}
|
|
2166
|
+
}
|
|
2167
|
+
return null;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
function removeRuntimeTarget(selector, selectorCandidates) {
|
|
2171
|
+
var target = findRuntimeTarget(selector, selectorCandidates);
|
|
2172
|
+
if (
|
|
2173
|
+
!target ||
|
|
2174
|
+
target === document.body ||
|
|
2175
|
+
target === document.documentElement
|
|
2176
|
+
)
|
|
2177
|
+
return false;
|
|
2178
|
+
if (target.parentElement) target.parentElement.removeChild(target);
|
|
2179
|
+
if (
|
|
2180
|
+
selectedEl === target ||
|
|
2181
|
+
!document.documentElement.contains(selectedEl)
|
|
2182
|
+
) {
|
|
2183
|
+
selectedEl = null;
|
|
2184
|
+
selectionOverlay.style.display = "none";
|
|
2185
|
+
}
|
|
2186
|
+
hoveredEl = null;
|
|
2187
|
+
highlightOverlay.style.display = "none";
|
|
2188
|
+
hideMeasurements();
|
|
2189
|
+
refreshOverlays();
|
|
2190
|
+
return true;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
function readPx(value: string): number {
|
|
2194
|
+
var num = parseFloat(value);
|
|
2195
|
+
return Number.isFinite(num) ? num : 0;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
function readFinitePx(value) {
|
|
2199
|
+
if (!value || value === "auto") return null;
|
|
2200
|
+
var num = parseFloat(value);
|
|
2201
|
+
return Number.isFinite(num) ? num : null;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
function currentRotation(el) {
|
|
2205
|
+
var transform =
|
|
2206
|
+
el.style.transform || window.getComputedStyle(el).transform || "";
|
|
2207
|
+
var match = transform.match(/rotate\((-?\d+(?:\.\d+)?)deg\)/);
|
|
2208
|
+
if (match) return parseFloat(match[1]) || 0;
|
|
2209
|
+
if (transform && transform !== "none" && window.DOMMatrixReadOnly) {
|
|
2210
|
+
try {
|
|
2211
|
+
var matrix = new DOMMatrixReadOnly(transform);
|
|
2212
|
+
return Math.round((Math.atan2(matrix.b, matrix.a) * 180) / Math.PI);
|
|
2213
|
+
} catch (err) {}
|
|
2214
|
+
}
|
|
2215
|
+
return 0;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
function mergeRotation(el, degrees) {
|
|
2219
|
+
var inline = el.style.transform || "";
|
|
2220
|
+
var next = inline.match(/rotate\((-?\d+(?:\.\d+)?)deg\)/)
|
|
2221
|
+
? inline.replace(
|
|
2222
|
+
/rotate\((-?\d+(?:\.\d+)?)deg\)/,
|
|
2223
|
+
"rotate(" + degrees + "deg)",
|
|
2224
|
+
)
|
|
2225
|
+
: (inline && inline !== "none" ? inline + " " : "") +
|
|
2226
|
+
"rotate(" +
|
|
2227
|
+
degrees +
|
|
2228
|
+
"deg)";
|
|
2229
|
+
return next.trim();
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
function ensurePositionable(el) {
|
|
2233
|
+
var cs = window.getComputedStyle(el);
|
|
2234
|
+
if (cs.position === "static") {
|
|
2235
|
+
el.style.position = "relative";
|
|
2236
|
+
if (!el.style.left) el.style.left = "0px";
|
|
2237
|
+
if (!el.style.top) el.style.top = "0px";
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
function postVisualStyleChange(styles) {
|
|
2242
|
+
if (!selectedEl) return;
|
|
2243
|
+
(window.parent as Window).postMessage(
|
|
2244
|
+
{
|
|
2245
|
+
type: "visual-style-change",
|
|
2246
|
+
selector: getSelector(selectedEl),
|
|
2247
|
+
styles: styles,
|
|
2248
|
+
payload: getElementInfo(selectedEl),
|
|
2249
|
+
},
|
|
2250
|
+
"*",
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
function spacingValueFromPointer(
|
|
2255
|
+
handle,
|
|
2256
|
+
originValue,
|
|
2257
|
+
startX,
|
|
2258
|
+
startY,
|
|
2259
|
+
clientX,
|
|
2260
|
+
clientY,
|
|
2261
|
+
) {
|
|
2262
|
+
var delta =
|
|
2263
|
+
handle.orientation === "vertical" ? clientX - startX : clientY - startY;
|
|
2264
|
+
if (
|
|
2265
|
+
handle.kind === "padding" &&
|
|
2266
|
+
(handle.side === "right" || handle.side === "bottom")
|
|
2267
|
+
) {
|
|
2268
|
+
delta = -delta;
|
|
2269
|
+
}
|
|
2270
|
+
return clampSpacingValue(originValue + delta);
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
function applySpacingDragValue(
|
|
2274
|
+
target: Element,
|
|
2275
|
+
handle: {
|
|
2276
|
+
key: string;
|
|
2277
|
+
groupKey: string;
|
|
2278
|
+
kind: string;
|
|
2279
|
+
property: string;
|
|
2280
|
+
oppositeProperty: string;
|
|
2281
|
+
side: string;
|
|
2282
|
+
orientation: string;
|
|
2283
|
+
value: number;
|
|
2284
|
+
region: { x: number; y: number; width: number; height: number };
|
|
2285
|
+
line: { x: number; y: number; width: number; height: number } | undefined;
|
|
2286
|
+
} | null,
|
|
2287
|
+
value: number,
|
|
2288
|
+
mirrorOpposite: boolean,
|
|
2289
|
+
): void {
|
|
2290
|
+
if (!target || !handle) return;
|
|
2291
|
+
target.style[handle.property] = value + "px";
|
|
2292
|
+
if (
|
|
2293
|
+
handle.kind === "padding" &&
|
|
2294
|
+
mirrorOpposite &&
|
|
2295
|
+
handle.oppositeProperty
|
|
2296
|
+
) {
|
|
2297
|
+
target.style[handle.oppositeProperty] = value + "px";
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
function startSpacingDrag(key, e) {
|
|
2302
|
+
var handle = spacingHandleStateByKey[key];
|
|
2303
|
+
if (!selectedEl || !handle || isLayerInteractionBlocked(selectedEl)) return;
|
|
2304
|
+
e.preventDefault();
|
|
2305
|
+
e.stopPropagation();
|
|
2306
|
+
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
|
|
2307
|
+
var events = dragEventNames(e);
|
|
2308
|
+
var dragEl = selectedEl;
|
|
2309
|
+
var originValue = handle.value;
|
|
2310
|
+
var startX = e.clientX;
|
|
2311
|
+
var startY = e.clientY;
|
|
2312
|
+
hoveredSpacingHandleKey = key;
|
|
2313
|
+
selectedSpacingHovered = true;
|
|
2314
|
+
spacingDrag = {
|
|
2315
|
+
handle: handle,
|
|
2316
|
+
currentValue: originValue,
|
|
2317
|
+
mirrorOpposite: !!e.altKey,
|
|
2318
|
+
};
|
|
2319
|
+
showSpacingBadgeForHandle(handle, originValue);
|
|
2320
|
+
|
|
2321
|
+
function onMove(ev) {
|
|
2322
|
+
if (!dragEl || !document.documentElement.contains(dragEl)) return;
|
|
2323
|
+
var nextValue = spacingValueFromPointer(
|
|
2324
|
+
handle,
|
|
2325
|
+
originValue,
|
|
2326
|
+
startX,
|
|
2327
|
+
startY,
|
|
2328
|
+
ev.clientX,
|
|
2329
|
+
ev.clientY,
|
|
2330
|
+
);
|
|
2331
|
+
spacingDrag = {
|
|
2332
|
+
handle: handle,
|
|
2333
|
+
currentValue: nextValue,
|
|
2334
|
+
mirrorOpposite: !!ev.altKey,
|
|
2335
|
+
};
|
|
2336
|
+
applySpacingDragValue(dragEl, handle, nextValue, !!ev.altKey);
|
|
2337
|
+
positionOverlay(selectionOverlay, dragEl);
|
|
2338
|
+
showSpacingBadgeForHandle(handle, nextValue);
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
function onUp(ev) {
|
|
2342
|
+
document.removeEventListener(events.move, onMove, true);
|
|
2343
|
+
document.removeEventListener(events.up, onUp, true);
|
|
2344
|
+
if (!dragEl || !document.documentElement.contains(dragEl)) {
|
|
2345
|
+
spacingDrag = null;
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
var finalValue = spacingDrag ? spacingDrag.currentValue : originValue;
|
|
2349
|
+
var mirrorOpposite = spacingDrag
|
|
2350
|
+
? spacingDrag.mirrorOpposite
|
|
2351
|
+
: !!ev.altKey;
|
|
2352
|
+
applySpacingDragValue(dragEl, handle, finalValue, mirrorOpposite);
|
|
2353
|
+
selectedEl = dragEl;
|
|
2354
|
+
spacingDrag = null;
|
|
2355
|
+
var styles = {};
|
|
2356
|
+
styles[handle.property] = finalValue + "px";
|
|
2357
|
+
if (
|
|
2358
|
+
handle.kind === "padding" &&
|
|
2359
|
+
mirrorOpposite &&
|
|
2360
|
+
handle.oppositeProperty
|
|
2361
|
+
) {
|
|
2362
|
+
styles[handle.oppositeProperty] = finalValue + "px";
|
|
2363
|
+
}
|
|
2364
|
+
postVisualStyleChange(styles);
|
|
2365
|
+
positionOverlay(selectionOverlay, dragEl);
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
document.addEventListener(events.move, onMove, true);
|
|
2369
|
+
document.addEventListener(events.up, onUp, true);
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
function postTextContentChange(el, value, html) {
|
|
2373
|
+
(window.parent as Window).postMessage(
|
|
2374
|
+
{
|
|
2375
|
+
type: "text-content-change",
|
|
2376
|
+
selector: getSelector(el),
|
|
2377
|
+
value: value,
|
|
2378
|
+
html: html,
|
|
2379
|
+
payload: getElementInfo(el),
|
|
2380
|
+
},
|
|
2381
|
+
"*",
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
function postTextEditingState(el: Element | null, active: boolean): void {
|
|
2386
|
+
var selection: Selection | null = window.getSelection
|
|
2387
|
+
? window.getSelection()
|
|
2388
|
+
: null;
|
|
2389
|
+
(window.parent as Window).postMessage(
|
|
2390
|
+
{
|
|
2391
|
+
type: "text-editing-state",
|
|
2392
|
+
active: !!active,
|
|
2393
|
+
selector: el ? getSelector(el) : "",
|
|
2394
|
+
hasRange: !!(
|
|
2395
|
+
active &&
|
|
2396
|
+
selection &&
|
|
2397
|
+
selection.rangeCount > 0 &&
|
|
2398
|
+
!selection.isCollapsed &&
|
|
2399
|
+
selectionBelongsToElement(selection, el)
|
|
2400
|
+
),
|
|
2401
|
+
},
|
|
2402
|
+
"*",
|
|
2403
|
+
);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
function insertPlainTextAtSelection(text: string): void {
|
|
2407
|
+
if (!text) return;
|
|
2408
|
+
if (
|
|
2409
|
+
document.queryCommandSupported &&
|
|
2410
|
+
document.queryCommandSupported("insertText")
|
|
2411
|
+
) {
|
|
2412
|
+
document.execCommand("insertText", false, text);
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
var selection: Selection | null = window.getSelection
|
|
2416
|
+
? window.getSelection()
|
|
2417
|
+
: null;
|
|
2418
|
+
if (!selection || selection.rangeCount === 0) return;
|
|
2419
|
+
var range = selection.getRangeAt(0);
|
|
2420
|
+
range.deleteContents();
|
|
2421
|
+
var textNode = document.createTextNode(text);
|
|
2422
|
+
range.insertNode(textNode);
|
|
2423
|
+
range.setStartAfter(textNode);
|
|
2424
|
+
range.setEndAfter(textNode);
|
|
2425
|
+
selection.removeAllRanges();
|
|
2426
|
+
selection.addRange(range);
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
function selectionBelongsToElement(
|
|
2430
|
+
selection: Selection | null,
|
|
2431
|
+
el: Element | null,
|
|
2432
|
+
): boolean {
|
|
2433
|
+
if (!selection || !el || selection.rangeCount === 0) return false;
|
|
2434
|
+
var range = selection.getRangeAt(0);
|
|
2435
|
+
var ancestor = range.commonAncestorContainer;
|
|
2436
|
+
var ancestorEl =
|
|
2437
|
+
ancestor && ancestor.nodeType === 1
|
|
2438
|
+
? ancestor
|
|
2439
|
+
: ancestor && ancestor.parentElement;
|
|
2440
|
+
return !!(ancestorEl && (ancestorEl === el || el.contains(ancestorEl)));
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
function normalizeCssPropertyName(property: unknown): string {
|
|
2444
|
+
var prop = String(property || "").trim();
|
|
2445
|
+
if (!prop) return "";
|
|
2446
|
+
if (prop.indexOf("--") === 0) return prop;
|
|
2447
|
+
return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
function applyInlineStyleProperty(
|
|
2451
|
+
el: HTMLElement | null,
|
|
2452
|
+
property: unknown,
|
|
2453
|
+
value: unknown,
|
|
2454
|
+
): boolean {
|
|
2455
|
+
if (!el || !property) return false;
|
|
2456
|
+
var cssProperty = normalizeCssPropertyName(property);
|
|
2457
|
+
if (!cssProperty) return false;
|
|
2458
|
+
el.style.setProperty(cssProperty, String(value));
|
|
2459
|
+
return true;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
function applyTextRangeStyle(property: unknown, value: unknown): boolean {
|
|
2463
|
+
if (!activeTextEditEl || !property) return false;
|
|
2464
|
+
var selection: Selection | null = window.getSelection
|
|
2465
|
+
? window.getSelection()
|
|
2466
|
+
: null;
|
|
2467
|
+
if (!selection || selection.rangeCount === 0 || selection.isCollapsed)
|
|
2468
|
+
return false;
|
|
2469
|
+
if (!selectionBelongsToElement(selection, activeTextEditEl)) return false;
|
|
2470
|
+
var range = selection.getRangeAt(0);
|
|
2471
|
+
var span = document.createElement("span");
|
|
2472
|
+
applyInlineStyleProperty(span, property, value);
|
|
2473
|
+
if (!span.getAttribute("style")) return false;
|
|
2474
|
+
try {
|
|
2475
|
+
range.surroundContents(span);
|
|
2476
|
+
} catch (err) {
|
|
2477
|
+
var contents = range.extractContents();
|
|
2478
|
+
span.appendChild(contents);
|
|
2479
|
+
range.insertNode(span);
|
|
2480
|
+
}
|
|
2481
|
+
selection.removeAllRanges();
|
|
2482
|
+
var nextRange = document.createRange();
|
|
2483
|
+
nextRange.selectNodeContents(span);
|
|
2484
|
+
selection.addRange(nextRange);
|
|
2485
|
+
return true;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
function showTransformBadge(
|
|
2489
|
+
text: string,
|
|
2490
|
+
clientX: number,
|
|
2491
|
+
clientY: number,
|
|
2492
|
+
): void {
|
|
2493
|
+
transformBadge.textContent = text;
|
|
2494
|
+
transformBadge.style.display = "block";
|
|
2495
|
+
transformBadge.style.left = clientX + 12 + "px";
|
|
2496
|
+
transformBadge.style.top = clientY + 12 + "px";
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
function hideTransformBadge(): void {
|
|
2500
|
+
transformBadge.style.display = "none";
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
function hideInsertionGuide(): void {
|
|
2504
|
+
insertionGuide.style.display = "none";
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
function isOverlayElement(el: Element | null): boolean {
|
|
2508
|
+
// Use closest() so that children of overlay elements (e.g. spacing-region
|
|
2509
|
+
// spans inside selectionOverlay that have pointer-events:auto via a CSS rule
|
|
2510
|
+
// and can therefore still be returned by elementFromPoint even when the
|
|
2511
|
+
// parent overlay has pointer-events:none set inline) are also treated as
|
|
2512
|
+
// overlay elements and never used as drag-drop anchor targets.
|
|
2513
|
+
return Boolean(
|
|
2514
|
+
el && el.closest && el.closest("[data-agent-native-edit-overlay]"),
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
function draggableElementChildren(parent: Element): Element[] {
|
|
2519
|
+
return Array.prototype.slice.call(parent.children).filter(function (child) {
|
|
2520
|
+
return (
|
|
2521
|
+
child.nodeType === 1 &&
|
|
2522
|
+
!isOverlayElement(child) &&
|
|
2523
|
+
!isLayerInteractionBlocked(child)
|
|
2524
|
+
);
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
function isFlowReorderCandidate(el) {
|
|
2529
|
+
if (!el || !el.parentElement) return false;
|
|
2530
|
+
if (el === document.body || el === document.documentElement) return false;
|
|
2531
|
+
var cs = window.getComputedStyle(el);
|
|
2532
|
+
if (cs.position === "absolute" || cs.position === "fixed") return false;
|
|
2533
|
+
return true;
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// keep in sync with EditPanel.tsx CONTAINER_TAGS/LEAF_TAGS (~line 1679)
|
|
2537
|
+
var BRIDGE_CONTAINER_TAGS = [
|
|
2538
|
+
"div",
|
|
2539
|
+
"section",
|
|
2540
|
+
"main",
|
|
2541
|
+
"header",
|
|
2542
|
+
"footer",
|
|
2543
|
+
"nav",
|
|
2544
|
+
"article",
|
|
2545
|
+
"aside",
|
|
2546
|
+
"form",
|
|
2547
|
+
"ul",
|
|
2548
|
+
"ol",
|
|
2549
|
+
"figure",
|
|
2550
|
+
"fieldset",
|
|
2551
|
+
"details",
|
|
2552
|
+
"dialog",
|
|
2553
|
+
"blockquote",
|
|
2554
|
+
"table",
|
|
2555
|
+
"tbody",
|
|
2556
|
+
"thead",
|
|
2557
|
+
"tr",
|
|
2558
|
+
];
|
|
2559
|
+
var BRIDGE_LEAF_TAGS = [
|
|
2560
|
+
"img",
|
|
2561
|
+
"video",
|
|
2562
|
+
"picture",
|
|
2563
|
+
"audio",
|
|
2564
|
+
"canvas",
|
|
2565
|
+
"svg",
|
|
2566
|
+
"path",
|
|
2567
|
+
"input",
|
|
2568
|
+
"textarea",
|
|
2569
|
+
"select",
|
|
2570
|
+
"br",
|
|
2571
|
+
"hr",
|
|
2572
|
+
"iframe",
|
|
2573
|
+
];
|
|
2574
|
+
var BRIDGE_TEXT_TAGS = [
|
|
2575
|
+
"p",
|
|
2576
|
+
"h1",
|
|
2577
|
+
"h2",
|
|
2578
|
+
"h3",
|
|
2579
|
+
"h4",
|
|
2580
|
+
"h5",
|
|
2581
|
+
"h6",
|
|
2582
|
+
"span",
|
|
2583
|
+
"a",
|
|
2584
|
+
"strong",
|
|
2585
|
+
"em",
|
|
2586
|
+
"label",
|
|
2587
|
+
"li",
|
|
2588
|
+
];
|
|
2589
|
+
|
|
2590
|
+
function isContainerDropTarget(el: Element | null): boolean {
|
|
2591
|
+
if (!el || el === document.documentElement) return false;
|
|
2592
|
+
if (isOverlayElement(el) || isLayerInteractionBlocked(el)) return false;
|
|
2593
|
+
if (el === document.body) return true;
|
|
2594
|
+
var tag = (el.tagName || "").toLowerCase();
|
|
2595
|
+
// Reject leaf/text tags — they cannot accept children
|
|
2596
|
+
if (
|
|
2597
|
+
BRIDGE_LEAF_TAGS.indexOf(tag) !== -1 ||
|
|
2598
|
+
BRIDGE_TEXT_TAGS.indexOf(tag) !== -1
|
|
2599
|
+
)
|
|
2600
|
+
return false;
|
|
2601
|
+
var cs = window.getComputedStyle(el);
|
|
2602
|
+
if (
|
|
2603
|
+
cs.display === "flex" ||
|
|
2604
|
+
cs.display === "inline-flex" ||
|
|
2605
|
+
cs.display === "grid" ||
|
|
2606
|
+
cs.display === "inline-grid"
|
|
2607
|
+
) {
|
|
2608
|
+
return true;
|
|
2609
|
+
}
|
|
2610
|
+
return BRIDGE_CONTAINER_TAGS.indexOf(tag) !== -1;
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
function edgePlacementForRect(
|
|
2614
|
+
rect: DOMRect,
|
|
2615
|
+
axis: string,
|
|
2616
|
+
clientX: number,
|
|
2617
|
+
clientY: number,
|
|
2618
|
+
): string | null {
|
|
2619
|
+
var size = axis === "x" ? rect.width : rect.height;
|
|
2620
|
+
if (!size) return null;
|
|
2621
|
+
var offset = axis === "x" ? clientX - rect.left : clientY - rect.top;
|
|
2622
|
+
if (offset < size * 0.22) return "before";
|
|
2623
|
+
if (offset > size * 0.78) return "after";
|
|
2624
|
+
return null;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
function parentFlowAxis(parent: Element): string {
|
|
2628
|
+
var cs = window.getComputedStyle(parent);
|
|
2629
|
+
if (cs.display === "flex" || cs.display === "inline-flex") {
|
|
2630
|
+
var isRow = cs.flexDirection && cs.flexDirection.indexOf("row") === 0;
|
|
2631
|
+
// Wrapping row containers need Y-axis awareness for inter-row targeting;
|
|
2632
|
+
// fall back to column-axis insertion so the heuristic picks the right row.
|
|
2633
|
+
var wraps = cs.flexWrap === "wrap" || cs.flexWrap === "wrap-reverse";
|
|
2634
|
+
if (isRow && !wraps) return "x";
|
|
2635
|
+
return "y";
|
|
2636
|
+
}
|
|
2637
|
+
if (cs.display === "grid" || cs.display === "inline-grid") {
|
|
2638
|
+
var cols = (cs.gridTemplateColumns || "")
|
|
2639
|
+
.split(" ")
|
|
2640
|
+
.filter(Boolean).length;
|
|
2641
|
+
return cols > 1 ? "x" : "y";
|
|
2642
|
+
}
|
|
2643
|
+
return "y";
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
function reorderTargetForPoint(el, clientX, clientY) {
|
|
2647
|
+
if (!el || !el.parentElement) return null;
|
|
2648
|
+
var hit = elementFromEditorPoint(clientX, clientY);
|
|
2649
|
+
if (
|
|
2650
|
+
hit &&
|
|
2651
|
+
hit !== document.documentElement &&
|
|
2652
|
+
hit !== el &&
|
|
2653
|
+
!el.contains(hit) &&
|
|
2654
|
+
!isOverlayElement(hit)
|
|
2655
|
+
) {
|
|
2656
|
+
if (isContainerDropTarget(hit)) {
|
|
2657
|
+
var containerRect = hit.getBoundingClientRect();
|
|
2658
|
+
var edgeAxis = hit.parentElement
|
|
2659
|
+
? parentFlowAxis(hit.parentElement)
|
|
2660
|
+
: parentFlowAxis(hit);
|
|
2661
|
+
var edgePlacement = edgePlacementForRect(
|
|
2662
|
+
containerRect,
|
|
2663
|
+
edgeAxis,
|
|
2664
|
+
clientX,
|
|
2665
|
+
clientY,
|
|
2666
|
+
);
|
|
2667
|
+
if (!edgePlacement) {
|
|
2668
|
+
return {
|
|
2669
|
+
anchor: hit,
|
|
2670
|
+
placement: "inside",
|
|
2671
|
+
axis: parentFlowAxis(hit),
|
|
2672
|
+
};
|
|
2673
|
+
}
|
|
2674
|
+
return { anchor: hit, placement: edgePlacement, axis: edgeAxis };
|
|
2675
|
+
}
|
|
2676
|
+
var hitParent = hit.parentElement;
|
|
2677
|
+
if (hitParent) {
|
|
2678
|
+
var hitAxis = parentFlowAxis(hitParent);
|
|
2679
|
+
var hitRect = hit.getBoundingClientRect();
|
|
2680
|
+
var hitCenter =
|
|
2681
|
+
hitAxis === "x"
|
|
2682
|
+
? hitRect.left + hitRect.width / 2
|
|
2683
|
+
: hitRect.top + hitRect.height / 2;
|
|
2684
|
+
var hitPointer = hitAxis === "x" ? clientX : clientY;
|
|
2685
|
+
return {
|
|
2686
|
+
anchor: hit,
|
|
2687
|
+
placement: hitPointer < hitCenter ? "before" : "after",
|
|
2688
|
+
axis: hitAxis,
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
var parent = el.parentElement;
|
|
2693
|
+
var axis = parentFlowAxis(parent);
|
|
2694
|
+
var siblings = draggableElementChildren(parent).filter(function (child) {
|
|
2695
|
+
return child !== el;
|
|
2696
|
+
});
|
|
2697
|
+
if (!siblings.length) return null;
|
|
2698
|
+
var beforeTarget = null;
|
|
2699
|
+
for (var i = 0; i < siblings.length; i += 1) {
|
|
2700
|
+
var rect = siblings[i].getBoundingClientRect();
|
|
2701
|
+
var center =
|
|
2702
|
+
axis === "x" ? rect.left + rect.width / 2 : rect.top + rect.height / 2;
|
|
2703
|
+
var pointer = axis === "x" ? clientX : clientY;
|
|
2704
|
+
if (pointer < center) {
|
|
2705
|
+
beforeTarget = siblings[i];
|
|
2706
|
+
break;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
var anchor = beforeTarget || siblings[siblings.length - 1];
|
|
2710
|
+
var placement = beforeTarget ? "before" : "after";
|
|
2711
|
+
return { anchor: anchor, placement: placement, axis: axis };
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
function showInsertionGuideFor(target) {
|
|
2715
|
+
if (!target || !target.anchor) {
|
|
2716
|
+
hideInsertionGuide();
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
var rect = target.anchor.getBoundingClientRect();
|
|
2720
|
+
insertionGuide.style.display = "block";
|
|
2721
|
+
insertionGuide.style.background = "var(--design-editor-accent-color)";
|
|
2722
|
+
insertionGuide.style.border = "0";
|
|
2723
|
+
insertionGuide.style.borderRadius = "999px";
|
|
2724
|
+
insertionGuide.style.boxShadow =
|
|
2725
|
+
"0 0 0 1px var(--design-editor-accent-color)";
|
|
2726
|
+
if (target.placement === "inside") {
|
|
2727
|
+
insertionGuide.style.left = rect.left + "px";
|
|
2728
|
+
insertionGuide.style.top = rect.top + "px";
|
|
2729
|
+
insertionGuide.style.width = rect.width + "px";
|
|
2730
|
+
insertionGuide.style.height = rect.height + "px";
|
|
2731
|
+
insertionGuide.style.background =
|
|
2732
|
+
"color-mix(in srgb, var(--design-editor-accent-color) 14%, transparent)";
|
|
2733
|
+
insertionGuide.style.border =
|
|
2734
|
+
"2px solid var(--design-editor-accent-color)";
|
|
2735
|
+
insertionGuide.style.borderRadius = "2px";
|
|
2736
|
+
insertionGuide.style.boxShadow = "none";
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
if (target.axis === "x") {
|
|
2740
|
+
var x = target.placement === "before" ? rect.left : rect.right;
|
|
2741
|
+
insertionGuide.style.left = x + "px";
|
|
2742
|
+
insertionGuide.style.top = rect.top + "px";
|
|
2743
|
+
insertionGuide.style.width = "2px";
|
|
2744
|
+
insertionGuide.style.height = rect.height + "px";
|
|
2745
|
+
} else {
|
|
2746
|
+
var y = target.placement === "before" ? rect.top : rect.bottom;
|
|
2747
|
+
insertionGuide.style.left = rect.left + "px";
|
|
2748
|
+
insertionGuide.style.top = y + "px";
|
|
2749
|
+
insertionGuide.style.width = rect.width + "px";
|
|
2750
|
+
insertionGuide.style.height = "2px";
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
function applyRuntimeReorder(el, target) {
|
|
2755
|
+
if (!el || !target || !target.anchor || !target.anchor.parentElement)
|
|
2756
|
+
return;
|
|
2757
|
+
if (target.placement === "inside") {
|
|
2758
|
+
target.anchor.appendChild(el);
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
var parent = target.anchor.parentElement;
|
|
2762
|
+
if (target.placement === "before") {
|
|
2763
|
+
parent.insertBefore(el, target.anchor);
|
|
2764
|
+
} else {
|
|
2765
|
+
parent.insertBefore(el, target.anchor.nextSibling);
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
function postVisualStructureChange(el, target, origin) {
|
|
2770
|
+
if (!el || !target || !target.anchor) return;
|
|
2771
|
+
var requestId =
|
|
2772
|
+
"move-" + Date.now() + "-" + Math.random().toString(16).slice(2);
|
|
2773
|
+
pendingStructureMoves[requestId] = {
|
|
2774
|
+
requestId: requestId,
|
|
2775
|
+
el: el,
|
|
2776
|
+
target: target,
|
|
2777
|
+
origin: origin || null,
|
|
2778
|
+
};
|
|
2779
|
+
(window.parent as Window).postMessage(
|
|
2780
|
+
{
|
|
2781
|
+
type: "visual-structure-change",
|
|
2782
|
+
requestId: requestId,
|
|
2783
|
+
selector: getSelector(el),
|
|
2784
|
+
sourceId: getSourceId(el),
|
|
2785
|
+
anchorSelector: getSelector(target.anchor),
|
|
2786
|
+
anchorSourceId: getSourceId(target.anchor),
|
|
2787
|
+
placement: target.placement,
|
|
2788
|
+
payload: getElementInfo(el),
|
|
2789
|
+
},
|
|
2790
|
+
"*",
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
function postVisualDuplicateChange(originalEl, cloneEl, target) {
|
|
2795
|
+
if (!originalEl || !cloneEl) return;
|
|
2796
|
+
(window.parent as Window).postMessage(
|
|
2797
|
+
{
|
|
2798
|
+
type: "visual-duplicate-change",
|
|
2799
|
+
selector: getSelector(originalEl),
|
|
2800
|
+
sourceId: getSourceId(originalEl),
|
|
2801
|
+
anchorSelector:
|
|
2802
|
+
target && target.anchor ? getSelector(target.anchor) : "",
|
|
2803
|
+
anchorSourceId:
|
|
2804
|
+
target && target.anchor ? getSourceId(target.anchor) : "",
|
|
2805
|
+
placement: target && target.placement ? target.placement : "after",
|
|
2806
|
+
cloneHtml: cloneEl.outerHTML,
|
|
2807
|
+
payload: getElementInfo(cloneEl),
|
|
2808
|
+
},
|
|
2809
|
+
"*",
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
function startMove(e) {
|
|
2814
|
+
if (!selectedEl) return;
|
|
2815
|
+
if (isLayerInteractionBlocked(selectedEl)) return;
|
|
2816
|
+
e.preventDefault();
|
|
2817
|
+
e.stopPropagation();
|
|
2818
|
+
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
|
|
2819
|
+
var events = dragEventNames(e);
|
|
2820
|
+
var originalSelectedEl = selectedEl;
|
|
2821
|
+
var duplicatedForDrag = false;
|
|
2822
|
+
if (
|
|
2823
|
+
e.altKey &&
|
|
2824
|
+
selectedEl !== document.body &&
|
|
2825
|
+
selectedEl !== document.documentElement
|
|
2826
|
+
) {
|
|
2827
|
+
var clone = selectedEl.cloneNode(true);
|
|
2828
|
+
resetRuntimeStableIds(clone);
|
|
2829
|
+
selectedEl.parentElement.insertBefore(clone, selectedEl.nextSibling);
|
|
2830
|
+
selectedEl = clone;
|
|
2831
|
+
duplicatedForDrag = true;
|
|
2832
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
2833
|
+
(window.parent as Window).postMessage(
|
|
2834
|
+
{ type: "element-select", payload: getElementInfo(selectedEl) },
|
|
2835
|
+
"*",
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2838
|
+
if (isFlowReorderCandidate(selectedEl)) {
|
|
2839
|
+
// Snapshot the element being reordered so a concurrent select-element or
|
|
2840
|
+
// clear-selection postMessage cannot mutate the wrong element mid-drag.
|
|
2841
|
+
var reorderEl = selectedEl;
|
|
2842
|
+
var currentTarget = reorderTargetForPoint(
|
|
2843
|
+
reorderEl,
|
|
2844
|
+
e.clientX,
|
|
2845
|
+
e.clientY,
|
|
2846
|
+
);
|
|
2847
|
+
showInsertionGuideFor(currentTarget);
|
|
2848
|
+
// Cross-screen drag state: true when the pointer is outside this iframe's
|
|
2849
|
+
// viewport bounds. The host frame renders the ghost + highlight and owns
|
|
2850
|
+
// the drop when this is true; the bridge suppresses its in-iframe reorder.
|
|
2851
|
+
var pointerOutsideIframe = false;
|
|
2852
|
+
var reorderSelector = getSelector(reorderEl);
|
|
2853
|
+
var reorderSourceId = getSourceId(reorderEl);
|
|
2854
|
+
function onReorderMove(ev) {
|
|
2855
|
+
var vw = window.innerWidth;
|
|
2856
|
+
var vh = window.innerHeight;
|
|
2857
|
+
var cx = ev.clientX;
|
|
2858
|
+
var cy = ev.clientY;
|
|
2859
|
+
var outside = cx < 0 || cy < 0 || cx > vw || cy > vh;
|
|
2860
|
+
pointerOutsideIframe = outside;
|
|
2861
|
+
// Always notify the host frame so it can track the cursor position,
|
|
2862
|
+
// render the ghost, and highlight the target screen.
|
|
2863
|
+
(window.parent as Window).postMessage(
|
|
2864
|
+
{
|
|
2865
|
+
type: "agent-native:cross-screen-drag",
|
|
2866
|
+
phase: "move",
|
|
2867
|
+
selector: reorderSelector,
|
|
2868
|
+
sourceId: reorderSourceId,
|
|
2869
|
+
iframeX: cx,
|
|
2870
|
+
iframeY: cy,
|
|
2871
|
+
viewportW: vw,
|
|
2872
|
+
viewportH: vh,
|
|
2873
|
+
},
|
|
2874
|
+
"*",
|
|
2875
|
+
);
|
|
2876
|
+
if (outside) {
|
|
2877
|
+
// Cursor left this iframe — hide the in-iframe insertion guide so
|
|
2878
|
+
// it does not render while the host shows a cross-screen drop target.
|
|
2879
|
+
hideInsertionGuide();
|
|
2880
|
+
showTransformBadge("Move layer", cx, cy);
|
|
2881
|
+
} else {
|
|
2882
|
+
// Cursor is inside this iframe — use existing in-iframe behavior.
|
|
2883
|
+
currentTarget = reorderTargetForPoint(reorderEl, cx, cy);
|
|
2884
|
+
showInsertionGuideFor(currentTarget);
|
|
2885
|
+
showTransformBadge(currentTarget ? "Move layer" : "Move", cx, cy);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
function onReorderEscape() {
|
|
2889
|
+
document.removeEventListener(events.move, onReorderMove, true);
|
|
2890
|
+
document.removeEventListener(events.up, onReorderUp, true);
|
|
2891
|
+
document.removeEventListener("keydown", onReorderKeyDown, true);
|
|
2892
|
+
hideTransformBadge();
|
|
2893
|
+
hideInsertionGuide();
|
|
2894
|
+
(window.parent as Window).postMessage(
|
|
2895
|
+
{ type: "agent-native:cross-screen-drag", phase: "cancel" },
|
|
2896
|
+
"*",
|
|
2897
|
+
);
|
|
2898
|
+
// Revert any clone that was inserted for alt-drag.
|
|
2899
|
+
if (
|
|
2900
|
+
duplicatedForDrag &&
|
|
2901
|
+
reorderEl &&
|
|
2902
|
+
reorderEl !== originalSelectedEl
|
|
2903
|
+
) {
|
|
2904
|
+
if (reorderEl.parentElement)
|
|
2905
|
+
reorderEl.parentElement.removeChild(reorderEl);
|
|
2906
|
+
selectedEl = originalSelectedEl;
|
|
2907
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
2908
|
+
(window.parent as Window).postMessage(
|
|
2909
|
+
{ type: "element-select", payload: getElementInfo(selectedEl) },
|
|
2910
|
+
"*",
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
function onReorderKeyDown(ev) {
|
|
2915
|
+
if (ev.key === "Escape") {
|
|
2916
|
+
ev.preventDefault();
|
|
2917
|
+
ev.stopPropagation();
|
|
2918
|
+
if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();
|
|
2919
|
+
onReorderEscape();
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
function onReorderUp(ev) {
|
|
2923
|
+
document.removeEventListener(events.move, onReorderMove, true);
|
|
2924
|
+
document.removeEventListener(events.up, onReorderUp, true);
|
|
2925
|
+
document.removeEventListener("keydown", onReorderKeyDown, true);
|
|
2926
|
+
hideTransformBadge();
|
|
2927
|
+
hideInsertionGuide();
|
|
2928
|
+
var vw = window.innerWidth;
|
|
2929
|
+
var vh = window.innerHeight;
|
|
2930
|
+
var cx = ev ? ev.clientX : 0;
|
|
2931
|
+
var cy = ev ? ev.clientY : 0;
|
|
2932
|
+
var outsideOnDrop = cx < 0 || cy < 0 || cx > vw || cy > vh;
|
|
2933
|
+
// Post the end message so the host can finalize a cross-screen drop.
|
|
2934
|
+
(window.parent as Window).postMessage(
|
|
2935
|
+
{
|
|
2936
|
+
type: "agent-native:cross-screen-drag",
|
|
2937
|
+
phase: "end",
|
|
2938
|
+
selector: reorderSelector,
|
|
2939
|
+
sourceId: reorderSourceId,
|
|
2940
|
+
iframeX: cx,
|
|
2941
|
+
iframeY: cy,
|
|
2942
|
+
viewportW: vw,
|
|
2943
|
+
viewportH: vh,
|
|
2944
|
+
},
|
|
2945
|
+
"*",
|
|
2946
|
+
);
|
|
2947
|
+
// When the pointer is outside this iframe at release, the host owns the
|
|
2948
|
+
// move (cross-screen drop). Do NOT apply the in-iframe reorder so we
|
|
2949
|
+
// avoid a ghost element left in screen A's DOM.
|
|
2950
|
+
// Use outsideOnDrop only — pointerOutsideIframe is stale when the user
|
|
2951
|
+
// briefly exits the iframe and re-enters before releasing. The host
|
|
2952
|
+
// already clears cross-screen state on re-entry so checking the
|
|
2953
|
+
// momentary excursion flag here would wrongly drop the element nowhere.
|
|
2954
|
+
if (outsideOnDrop) return;
|
|
2955
|
+
if (!currentTarget) {
|
|
2956
|
+
// No valid drop target — clean up the clone if one was inserted so
|
|
2957
|
+
// no ghost element is left in the DOM.
|
|
2958
|
+
if (
|
|
2959
|
+
duplicatedForDrag &&
|
|
2960
|
+
reorderEl &&
|
|
2961
|
+
reorderEl !== originalSelectedEl
|
|
2962
|
+
) {
|
|
2963
|
+
if (reorderEl.parentElement)
|
|
2964
|
+
reorderEl.parentElement.removeChild(reorderEl);
|
|
2965
|
+
selectedEl = originalSelectedEl;
|
|
2966
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
2967
|
+
(window.parent as Window).postMessage(
|
|
2968
|
+
{ type: "element-select", payload: getElementInfo(selectedEl) },
|
|
2969
|
+
"*",
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2974
|
+
if (duplicatedForDrag) {
|
|
2975
|
+
applyRuntimeReorder(reorderEl, currentTarget);
|
|
2976
|
+
postVisualDuplicateChange(
|
|
2977
|
+
originalSelectedEl,
|
|
2978
|
+
reorderEl,
|
|
2979
|
+
currentTarget,
|
|
2980
|
+
);
|
|
2981
|
+
} else {
|
|
2982
|
+
// Capture the pre-drag DOM anchor so we can revert if the parent
|
|
2983
|
+
// reports applied===false on the structure-ack.
|
|
2984
|
+
var prevParent = reorderEl.parentElement;
|
|
2985
|
+
var prevNextSibling = reorderEl.nextSibling;
|
|
2986
|
+
// Optimistically apply the reorder in the DOM for immediate
|
|
2987
|
+
// visual feedback; the visual-structure-ack handler will confirm
|
|
2988
|
+
// or revert once the parent processes the change.
|
|
2989
|
+
applyRuntimeReorder(reorderEl, currentTarget);
|
|
2990
|
+
postVisualStructureChange(reorderEl, currentTarget, {
|
|
2991
|
+
prevParent: prevParent,
|
|
2992
|
+
prevNextSibling: prevNextSibling,
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
document.addEventListener(events.move, onReorderMove, true);
|
|
2997
|
+
document.addEventListener(events.up, onReorderUp, true);
|
|
2998
|
+
document.addEventListener("keydown", onReorderKeyDown, true);
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
ensurePositionable(selectedEl);
|
|
3002
|
+
var cs = window.getComputedStyle(selectedEl);
|
|
3003
|
+
var originLeft = readPx((selectedEl as HTMLElement).style.left || cs.left);
|
|
3004
|
+
var originTop = readPx(selectedEl.style.top || cs.top);
|
|
3005
|
+
var startX = e.clientX;
|
|
3006
|
+
var startY = e.clientY;
|
|
3007
|
+
// Snapshot the element being moved so that a concurrent select-element or
|
|
3008
|
+
// clear-selection postMessage cannot swap selectedEl mid-drag and cause
|
|
3009
|
+
// mutations on the wrong element or a null-deref in onUp.
|
|
3010
|
+
var dragEl = selectedEl;
|
|
3011
|
+
var moved = false;
|
|
3012
|
+
var DRAG_THRESHOLD = 3;
|
|
3013
|
+
function onMove(ev) {
|
|
3014
|
+
if (
|
|
3015
|
+
!moved &&
|
|
3016
|
+
Math.hypot(ev.clientX - startX, ev.clientY - startY) > DRAG_THRESHOLD
|
|
3017
|
+
) {
|
|
3018
|
+
moved = true;
|
|
3019
|
+
}
|
|
3020
|
+
var nextLeft = originLeft + ev.clientX - startX;
|
|
3021
|
+
var nextTop = originTop + ev.clientY - startY;
|
|
3022
|
+
(dragEl as HTMLElement).style.left = Math.round(nextLeft) + "px";
|
|
3023
|
+
(dragEl as HTMLElement).style.top = Math.round(nextTop) + "px";
|
|
3024
|
+
showTransformBadge(
|
|
3025
|
+
"X " + Math.round(nextLeft) + " Y " + Math.round(nextTop),
|
|
3026
|
+
ev.clientX,
|
|
3027
|
+
ev.clientY,
|
|
3028
|
+
);
|
|
3029
|
+
refreshOverlays();
|
|
3030
|
+
}
|
|
3031
|
+
function onUp() {
|
|
3032
|
+
document.removeEventListener(events.move, onMove, true);
|
|
3033
|
+
document.removeEventListener(events.up, onUp, true);
|
|
3034
|
+
hideTransformBadge();
|
|
3035
|
+
if (!dragEl) return;
|
|
3036
|
+
if (duplicatedForDrag && !moved) {
|
|
3037
|
+
// Alt-click with no real drag — remove the premature clone and restore the original selection.
|
|
3038
|
+
if (dragEl.parentElement) dragEl.parentElement.removeChild(dragEl);
|
|
3039
|
+
selectedEl = originalSelectedEl;
|
|
3040
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
3041
|
+
(window.parent as Window).postMessage(
|
|
3042
|
+
{ type: "element-select", payload: getElementInfo(selectedEl) },
|
|
3043
|
+
"*",
|
|
3044
|
+
);
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
if (duplicatedForDrag) {
|
|
3048
|
+
postVisualDuplicateChange(originalSelectedEl, dragEl);
|
|
3049
|
+
} else {
|
|
3050
|
+
(window.parent as Window).postMessage(
|
|
3051
|
+
{
|
|
3052
|
+
type: "visual-style-change",
|
|
3053
|
+
selector: getSelector(dragEl),
|
|
3054
|
+
styles: {
|
|
3055
|
+
position: (dragEl as HTMLElement).style.position,
|
|
3056
|
+
left: (dragEl as HTMLElement).style.left,
|
|
3057
|
+
top: (dragEl as HTMLElement).style.top,
|
|
3058
|
+
},
|
|
3059
|
+
payload: getElementInfo(dragEl),
|
|
3060
|
+
},
|
|
3061
|
+
"*",
|
|
3062
|
+
);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
document.addEventListener(events.move, onMove, true);
|
|
3066
|
+
document.addEventListener(events.up, onUp, true);
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
function startResize(handle, e) {
|
|
3070
|
+
if (!selectedEl) return;
|
|
3071
|
+
if (isLayerInteractionBlocked(selectedEl)) return;
|
|
3072
|
+
e.preventDefault();
|
|
3073
|
+
e.stopPropagation();
|
|
3074
|
+
var events = dragEventNames(e);
|
|
3075
|
+
ensurePositionable(selectedEl);
|
|
3076
|
+
var cs = window.getComputedStyle(selectedEl);
|
|
3077
|
+
// Bug fix: use CSS width/height (not getBoundingClientRect) for the resize
|
|
3078
|
+
// origin dimensions so that rotated elements don't use the inflated
|
|
3079
|
+
// axis-aligned bounding box as the starting size.
|
|
3080
|
+
var originW = readPx(selectedEl.style.width || cs.width);
|
|
3081
|
+
var originH = readPx(selectedEl.style.height || cs.height);
|
|
3082
|
+
var origin = {
|
|
3083
|
+
left: readPx(selectedEl.style.left || cs.left),
|
|
3084
|
+
top: readPx(selectedEl.style.top || cs.top),
|
|
3085
|
+
width: originW,
|
|
3086
|
+
height: originH,
|
|
3087
|
+
ratio: originW / Math.max(1, originH),
|
|
3088
|
+
};
|
|
3089
|
+
var startX = e.clientX;
|
|
3090
|
+
var startY = e.clientY;
|
|
3091
|
+
// Snapshot the element so a concurrent clear-selection postMessage cannot
|
|
3092
|
+
// cause a null-deref in onMove/onUp.
|
|
3093
|
+
var resizeEl = selectedEl;
|
|
3094
|
+
// Capture the element rotation once at drag-start so per-move projection is
|
|
3095
|
+
// cheap and consistent even if the transform changes during the drag.
|
|
3096
|
+
var resizeTheta = (currentRotation(resizeEl) * Math.PI) / 180;
|
|
3097
|
+
function nextRect(ev) {
|
|
3098
|
+
var screenDx = ev.clientX - startX;
|
|
3099
|
+
var screenDy = ev.clientY - startY;
|
|
3100
|
+
// Project the screen-space pointer delta into the element's local
|
|
3101
|
+
// (un-rotated) coordinate frame so handles behave relative to the
|
|
3102
|
+
// visible rotated box rather than screen axes.
|
|
3103
|
+
var cosT = Math.cos(resizeTheta);
|
|
3104
|
+
var sinT = Math.sin(resizeTheta);
|
|
3105
|
+
var dx = screenDx * cosT + screenDy * sinT;
|
|
3106
|
+
var dy = -screenDx * sinT + screenDy * cosT;
|
|
3107
|
+
var left = origin.left;
|
|
3108
|
+
var top = origin.top;
|
|
3109
|
+
var width = origin.width;
|
|
3110
|
+
var height = origin.height;
|
|
3111
|
+
if (handle.indexOf("w") !== -1) {
|
|
3112
|
+
left = origin.left + dx;
|
|
3113
|
+
width = origin.width - dx;
|
|
3114
|
+
}
|
|
3115
|
+
if (handle.indexOf("e") !== -1) width = origin.width + dx;
|
|
3116
|
+
if (handle.indexOf("n") !== -1) {
|
|
3117
|
+
top = origin.top + dy;
|
|
3118
|
+
height = origin.height - dy;
|
|
3119
|
+
}
|
|
3120
|
+
if (handle.indexOf("s") !== -1) height = origin.height + dy;
|
|
3121
|
+
// Apply Shift / scaleToolEnabled aspect-ratio lock BEFORE the min-size
|
|
3122
|
+
// clamp so the ratio is computed from unclamped values (bug fix).
|
|
3123
|
+
if (ev.shiftKey) {
|
|
3124
|
+
// Shift locks aspect ratio for ALL 8 handles (corners and edges).
|
|
3125
|
+
if (handle === "e" || handle === "w") {
|
|
3126
|
+
height = width / origin.ratio;
|
|
3127
|
+
} else if (handle === "n" || handle === "s") {
|
|
3128
|
+
width = height * origin.ratio;
|
|
3129
|
+
} else if (handle.length === 2) {
|
|
3130
|
+
if (Math.abs(dx) > Math.abs(dy)) height = width / origin.ratio;
|
|
3131
|
+
else width = height * origin.ratio;
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
if (scaleToolEnabled) {
|
|
3135
|
+
// Scale tool: enforce aspect ratio on all 8 handles, not just corners.
|
|
3136
|
+
if (handle === "e" || handle === "w") {
|
|
3137
|
+
height = width / origin.ratio;
|
|
3138
|
+
} else if (handle === "n" || handle === "s") {
|
|
3139
|
+
width = height * origin.ratio;
|
|
3140
|
+
} else if (handle.length === 2) {
|
|
3141
|
+
if (Math.abs(dx) > Math.abs(dy)) height = width / origin.ratio;
|
|
3142
|
+
else width = height * origin.ratio;
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
// Clamp to minimum size.
|
|
3146
|
+
var clampedW = Math.max(8, width);
|
|
3147
|
+
var clampedH = Math.max(8, height);
|
|
3148
|
+
// After clamping, re-apply the ratio if Shift or scale tool is active so
|
|
3149
|
+
// the clamped dimension doesn't silently break the locked aspect ratio.
|
|
3150
|
+
if (ev.shiftKey || scaleToolEnabled) {
|
|
3151
|
+
if (clampedW !== width) {
|
|
3152
|
+
// Width was clamped; re-derive height from the clamped width.
|
|
3153
|
+
clampedH = Math.max(8, clampedW / origin.ratio);
|
|
3154
|
+
} else if (clampedH !== height) {
|
|
3155
|
+
// Height was clamped; re-derive width from the clamped height.
|
|
3156
|
+
clampedW = Math.max(8, clampedH * origin.ratio);
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
width = clampedW;
|
|
3160
|
+
height = clampedH;
|
|
3161
|
+
// Re-anchor the pinned edge for w/n handles after aspect-ratio lock and
|
|
3162
|
+
// clamping so the opposite (e/s) edge stays fixed regardless of whether
|
|
3163
|
+
// the dimension change was driven by raw dx/dy or by the ratio lock.
|
|
3164
|
+
if (handle.indexOf("w") !== -1)
|
|
3165
|
+
left = origin.left + (origin.width - width);
|
|
3166
|
+
if (handle.indexOf("n") !== -1)
|
|
3167
|
+
top = origin.top + (origin.height - height);
|
|
3168
|
+
if (ev.altKey) {
|
|
3169
|
+
if (handle.indexOf("w") !== -1 || handle.indexOf("e") !== -1)
|
|
3170
|
+
left = origin.left - (width - origin.width) / 2;
|
|
3171
|
+
if (handle.indexOf("n") !== -1 || handle.indexOf("s") !== -1)
|
|
3172
|
+
top = origin.top - (height - origin.height) / 2;
|
|
3173
|
+
}
|
|
3174
|
+
return { left: left, top: top, width: width, height: height };
|
|
3175
|
+
}
|
|
3176
|
+
function onMove(ev) {
|
|
3177
|
+
if (!resizeEl) return;
|
|
3178
|
+
var rect = nextRect(ev);
|
|
3179
|
+
resizeEl.style.left = Math.round(rect.left) + "px";
|
|
3180
|
+
resizeEl.style.top = Math.round(rect.top) + "px";
|
|
3181
|
+
resizeEl.style.width = Math.round(rect.width) + "px";
|
|
3182
|
+
resizeEl.style.height = Math.round(rect.height) + "px";
|
|
3183
|
+
showTransformBadge(
|
|
3184
|
+
Math.round(rect.width) + " x " + Math.round(rect.height),
|
|
3185
|
+
ev.clientX,
|
|
3186
|
+
ev.clientY,
|
|
3187
|
+
);
|
|
3188
|
+
refreshOverlays();
|
|
3189
|
+
}
|
|
3190
|
+
function onUp() {
|
|
3191
|
+
document.removeEventListener(events.move, onMove, true);
|
|
3192
|
+
document.removeEventListener(events.up, onUp, true);
|
|
3193
|
+
hideTransformBadge();
|
|
3194
|
+
if (!resizeEl) return;
|
|
3195
|
+
(window.parent as Window).postMessage(
|
|
3196
|
+
{
|
|
3197
|
+
type: "visual-style-change",
|
|
3198
|
+
selector: getSelector(resizeEl),
|
|
3199
|
+
styles: {
|
|
3200
|
+
position: resizeEl.style.position,
|
|
3201
|
+
left: resizeEl.style.left,
|
|
3202
|
+
top: resizeEl.style.top,
|
|
3203
|
+
width: resizeEl.style.width,
|
|
3204
|
+
height: resizeEl.style.height,
|
|
3205
|
+
},
|
|
3206
|
+
payload: getElementInfo(resizeEl),
|
|
3207
|
+
},
|
|
3208
|
+
"*",
|
|
3209
|
+
);
|
|
3210
|
+
}
|
|
3211
|
+
document.addEventListener(events.move, onMove, true);
|
|
3212
|
+
document.addEventListener(events.up, onUp, true);
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
function startRotate(e) {
|
|
3216
|
+
if (!selectedEl) return;
|
|
3217
|
+
if (isLayerInteractionBlocked(selectedEl)) return;
|
|
3218
|
+
e.preventDefault();
|
|
3219
|
+
e.stopPropagation();
|
|
3220
|
+
var events = dragEventNames(e);
|
|
3221
|
+
// getBoundingClientRect is correct here — we only need the element center
|
|
3222
|
+
// for angle math, and the element's visual position is what we want.
|
|
3223
|
+
var rect = selectedEl.getBoundingClientRect();
|
|
3224
|
+
var center = {
|
|
3225
|
+
x: rect.left + rect.width / 2,
|
|
3226
|
+
y: rect.top + rect.height / 2,
|
|
3227
|
+
};
|
|
3228
|
+
var originAngle =
|
|
3229
|
+
(Math.atan2(e.clientY - center.y, e.clientX - center.x) * 180) / Math.PI;
|
|
3230
|
+
var originRotation = currentRotation(selectedEl);
|
|
3231
|
+
// Snapshot so a concurrent clear-selection postMessage cannot cause a
|
|
3232
|
+
// null-deref in onMove/onUp.
|
|
3233
|
+
var rotateEl = selectedEl;
|
|
3234
|
+
function onMove(ev) {
|
|
3235
|
+
if (!rotateEl) return;
|
|
3236
|
+
var pointerAngle =
|
|
3237
|
+
(Math.atan2(ev.clientY - center.y, ev.clientX - center.x) * 180) /
|
|
3238
|
+
Math.PI;
|
|
3239
|
+
var next = originRotation + pointerAngle - originAngle;
|
|
3240
|
+
if (ev.shiftKey) next = Math.round(next / 15) * 15;
|
|
3241
|
+
next = Math.round(next);
|
|
3242
|
+
rotateEl.style.transform = mergeRotation(rotateEl, next);
|
|
3243
|
+
showTransformBadge(next + "deg", ev.clientX, ev.clientY);
|
|
3244
|
+
refreshOverlays();
|
|
3245
|
+
}
|
|
3246
|
+
function onUp() {
|
|
3247
|
+
document.removeEventListener(events.move, onMove, true);
|
|
3248
|
+
document.removeEventListener(events.up, onUp, true);
|
|
3249
|
+
hideTransformBadge();
|
|
3250
|
+
if (!rotateEl) return;
|
|
3251
|
+
(window.parent as Window).postMessage(
|
|
3252
|
+
{
|
|
3253
|
+
type: "visual-style-change",
|
|
3254
|
+
selector: getSelector(rotateEl),
|
|
3255
|
+
styles: { transform: rotateEl.style.transform },
|
|
3256
|
+
payload: getElementInfo(rotateEl),
|
|
3257
|
+
},
|
|
3258
|
+
"*",
|
|
3259
|
+
);
|
|
3260
|
+
}
|
|
3261
|
+
document.addEventListener(events.move, onMove, true);
|
|
3262
|
+
document.addEventListener(events.up, onUp, true);
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
function clearPendingShieldDrag() {
|
|
3266
|
+
if (!pendingShieldDrag) return;
|
|
3267
|
+
document.removeEventListener(
|
|
3268
|
+
pendingShieldDrag.move,
|
|
3269
|
+
pendingShieldDrag.onMove,
|
|
3270
|
+
true,
|
|
3271
|
+
);
|
|
3272
|
+
document.removeEventListener(
|
|
3273
|
+
pendingShieldDrag.up,
|
|
3274
|
+
pendingShieldDrag.onUp,
|
|
3275
|
+
true,
|
|
3276
|
+
);
|
|
3277
|
+
pendingShieldDrag = null;
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
function beginPotentialShieldDrag(e) {
|
|
3281
|
+
stopNativeInteraction(e);
|
|
3282
|
+
if (e.button !== 0 || activeTextEditEl) return;
|
|
3283
|
+
var events = dragEventNames(e);
|
|
3284
|
+
var hit = elementFromEditorPoint(e.clientX, e.clientY);
|
|
3285
|
+
if (!hit || hit === document.body || hit === document.documentElement)
|
|
3286
|
+
return;
|
|
3287
|
+
var dragTarget =
|
|
3288
|
+
selectedEl &&
|
|
3289
|
+
document.documentElement.contains(selectedEl) &&
|
|
3290
|
+
selectedEl.contains(hit)
|
|
3291
|
+
? selectedEl
|
|
3292
|
+
: selectionTargetForHit(hit);
|
|
3293
|
+
var clickTarget = selectionTargetForHit(hit);
|
|
3294
|
+
if (
|
|
3295
|
+
!dragTarget ||
|
|
3296
|
+
dragTarget === document.body ||
|
|
3297
|
+
dragTarget === document.documentElement ||
|
|
3298
|
+
isLayerInteractionBlocked(dragTarget)
|
|
3299
|
+
) {
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
var startX = e.clientX;
|
|
3303
|
+
var startY = e.clientY;
|
|
3304
|
+
var didStartDrag = false;
|
|
3305
|
+
function selectTarget(target) {
|
|
3306
|
+
selectedEl = target;
|
|
3307
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
3308
|
+
(window.parent as Window).postMessage(
|
|
3309
|
+
{ type: "element-select", payload: getElementInfo(selectedEl) },
|
|
3310
|
+
"*",
|
|
3311
|
+
);
|
|
3312
|
+
}
|
|
3313
|
+
function onMove(ev) {
|
|
3314
|
+
if (Math.hypot(ev.clientX - startX, ev.clientY - startY) <= 3) return;
|
|
3315
|
+
clearPendingShieldDrag();
|
|
3316
|
+
didStartDrag = true;
|
|
3317
|
+
selectTarget(dragTarget);
|
|
3318
|
+
suppressNextShieldClickBriefly();
|
|
3319
|
+
startMove(ev);
|
|
3320
|
+
}
|
|
3321
|
+
function onUp(ev) {
|
|
3322
|
+
clearPendingShieldDrag();
|
|
3323
|
+
if (didStartDrag) return;
|
|
3324
|
+
if (ev) stopNativeInteraction(ev);
|
|
3325
|
+
selectTarget(clickTarget || dragTarget);
|
|
3326
|
+
suppressNextShieldClickBriefly();
|
|
3327
|
+
}
|
|
3328
|
+
clearPendingShieldDrag();
|
|
3329
|
+
pendingShieldDrag = {
|
|
3330
|
+
move: events.move,
|
|
3331
|
+
up: events.up,
|
|
3332
|
+
onMove: onMove,
|
|
3333
|
+
onUp: onUp,
|
|
3334
|
+
};
|
|
3335
|
+
document.addEventListener(events.move, onMove, true);
|
|
3336
|
+
document.addEventListener(events.up, onUp, true);
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
selectionOverlay.addEventListener(
|
|
3340
|
+
"mousedown",
|
|
3341
|
+
function (e) {
|
|
3342
|
+
var spacingKey =
|
|
3343
|
+
e.target &&
|
|
3344
|
+
e.target.getAttribute &&
|
|
3345
|
+
e.target.getAttribute("data-spacing-key");
|
|
3346
|
+
if (spacingKey) {
|
|
3347
|
+
startSpacingDrag(spacingKey, e);
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3350
|
+
var resizeHandle =
|
|
3351
|
+
e.target &&
|
|
3352
|
+
e.target.getAttribute &&
|
|
3353
|
+
e.target.getAttribute("data-agent-native-edit-handle");
|
|
3354
|
+
if (!resizeHandle && e.target && e.target.getAttribute) {
|
|
3355
|
+
resizeHandle = e.target.getAttribute("data-agent-native-edge-handle");
|
|
3356
|
+
}
|
|
3357
|
+
if (resizeHandle) {
|
|
3358
|
+
startResize(resizeHandle, e);
|
|
3359
|
+
return;
|
|
3360
|
+
}
|
|
3361
|
+
var rotateHandle =
|
|
3362
|
+
e.target &&
|
|
3363
|
+
e.target.getAttribute &&
|
|
3364
|
+
e.target.getAttribute("data-agent-native-rotate-handle");
|
|
3365
|
+
if (rotateHandle) {
|
|
3366
|
+
startRotate(e);
|
|
3367
|
+
return;
|
|
3368
|
+
}
|
|
3369
|
+
startMove(e);
|
|
3370
|
+
},
|
|
3371
|
+
true,
|
|
3372
|
+
);
|
|
3373
|
+
|
|
3374
|
+
shieldOverlay.addEventListener("pointerdown", beginPotentialShieldDrag, true);
|
|
3375
|
+
|
|
3376
|
+
["pointerdown", "pointerup", "mousedown", "mouseup", "auxclick"].forEach(
|
|
3377
|
+
function (type) {
|
|
3378
|
+
shieldOverlay.addEventListener(type, stopNativeInteraction, true);
|
|
3379
|
+
},
|
|
3380
|
+
);
|
|
3381
|
+
|
|
3382
|
+
function stopBlockedLayerInteraction(e) {
|
|
3383
|
+
if (isOverlayElement(e.target)) return;
|
|
3384
|
+
var target = e.target && e.target.nodeType === 1 ? e.target : null;
|
|
3385
|
+
if (!target || !isLayerInteractionBlocked(target)) return;
|
|
3386
|
+
stopNativeInteraction(e);
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
[
|
|
3390
|
+
"pointerdown",
|
|
3391
|
+
"pointerup",
|
|
3392
|
+
"mousedown",
|
|
3393
|
+
"mouseup",
|
|
3394
|
+
"click",
|
|
3395
|
+
"auxclick",
|
|
3396
|
+
].forEach(function (type) {
|
|
3397
|
+
document.addEventListener(type, stopBlockedLayerInteraction, true);
|
|
3398
|
+
});
|
|
3399
|
+
|
|
3400
|
+
shieldOverlay.addEventListener("click", selectElementAtEvent, true);
|
|
3401
|
+
shieldOverlay.addEventListener("contextmenu", openContextMenuAtEvent, true);
|
|
3402
|
+
selectionOverlay.addEventListener(
|
|
3403
|
+
"contextmenu",
|
|
3404
|
+
openContextMenuAtEvent,
|
|
3405
|
+
true,
|
|
3406
|
+
);
|
|
3407
|
+
document.addEventListener(
|
|
3408
|
+
"contextmenu",
|
|
3409
|
+
function (e) {
|
|
3410
|
+
if (isOverlayElement(e.target)) return;
|
|
3411
|
+
openContextMenuAtEvent(e);
|
|
3412
|
+
},
|
|
3413
|
+
true,
|
|
3414
|
+
);
|
|
3415
|
+
|
|
3416
|
+
document.addEventListener(
|
|
3417
|
+
"keydown",
|
|
3418
|
+
function (e) {
|
|
3419
|
+
if (!shouldForwardDesignHotkey(e)) return;
|
|
3420
|
+
stopNativeInteraction(e);
|
|
3421
|
+
if (e.key === "Escape") clearRuntimeSelection();
|
|
3422
|
+
(window.parent as Window).postMessage(
|
|
3423
|
+
{
|
|
3424
|
+
type: "design-hotkey",
|
|
3425
|
+
key: e.key,
|
|
3426
|
+
code: e.code,
|
|
3427
|
+
metaKey: !!e.metaKey,
|
|
3428
|
+
ctrlKey: !!e.ctrlKey,
|
|
3429
|
+
shiftKey: !!e.shiftKey,
|
|
3430
|
+
altKey: !!e.altKey,
|
|
3431
|
+
repeat: !!e.repeat,
|
|
3432
|
+
},
|
|
3433
|
+
"*",
|
|
3434
|
+
);
|
|
3435
|
+
},
|
|
3436
|
+
true,
|
|
3437
|
+
);
|
|
3438
|
+
|
|
3439
|
+
function placeTextCaretFromPoint(target, clientX, clientY) {
|
|
3440
|
+
try {
|
|
3441
|
+
var range = null;
|
|
3442
|
+
if (document.caretRangeFromPoint) {
|
|
3443
|
+
range = document.caretRangeFromPoint(clientX, clientY);
|
|
3444
|
+
} else if (document.caretPositionFromPoint) {
|
|
3445
|
+
var position = document.caretPositionFromPoint(clientX, clientY);
|
|
3446
|
+
if (position) {
|
|
3447
|
+
range = document.createRange();
|
|
3448
|
+
range.setStart(position.offsetNode, position.offset);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
if (!range) {
|
|
3452
|
+
range = document.createRange();
|
|
3453
|
+
range.selectNodeContents(target);
|
|
3454
|
+
range.collapse(false);
|
|
3455
|
+
}
|
|
3456
|
+
var selection = window.getSelection();
|
|
3457
|
+
selection.removeAllRanges();
|
|
3458
|
+
selection.addRange(range);
|
|
3459
|
+
} catch (err) {
|
|
3460
|
+
try {
|
|
3461
|
+
var fallbackRange = document.createRange();
|
|
3462
|
+
fallbackRange.selectNodeContents(target);
|
|
3463
|
+
fallbackRange.collapse(false);
|
|
3464
|
+
var fallbackSelection = window.getSelection();
|
|
3465
|
+
fallbackSelection.removeAllRanges();
|
|
3466
|
+
fallbackSelection.addRange(fallbackRange);
|
|
3467
|
+
} catch (_err) {}
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
function beginTextEditingFromEvent(e) {
|
|
3472
|
+
if (activeTextEditEl && e.target && activeTextEditEl.contains(e.target))
|
|
3473
|
+
return;
|
|
3474
|
+
if (!textEditingEnabled) {
|
|
3475
|
+
stopNativeInteraction(e);
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3478
|
+
stopNativeInteraction(e);
|
|
3479
|
+
var target = findTextEditTarget(
|
|
3480
|
+
elementFromEditorPoint(e.clientX, e.clientY),
|
|
3481
|
+
);
|
|
3482
|
+
if (!target || target.nodeType !== 1) return;
|
|
3483
|
+
// Anchor the selection identity to the nearest source-backed element. Text
|
|
3484
|
+
// editing still operates on the actual target text node, but a later
|
|
3485
|
+
// style edit posts from selectedEl, so it must point at a patchable
|
|
3486
|
+
// code-layer node rather than a runtime-only descendant (which would emit a
|
|
3487
|
+
// brittle body > div:nth-of-type(...) selector that never resolves).
|
|
3488
|
+
selectedEl = selectionTargetForHit(target) || target;
|
|
3489
|
+
var originalText = target.textContent || "";
|
|
3490
|
+
var originalHtml = target.innerHTML || "";
|
|
3491
|
+
var committed = false;
|
|
3492
|
+
activeTextEditEl = target;
|
|
3493
|
+
target.setAttribute("contenteditable", "true");
|
|
3494
|
+
target.setAttribute("data-agent-native-text-editing", "true");
|
|
3495
|
+
target.style.cursor = "text";
|
|
3496
|
+
target.style.outline = "1.5px solid var(--design-editor-accent-color)";
|
|
3497
|
+
target.style.outlineOffset = "2px";
|
|
3498
|
+
setTextEditingPointerPassthrough(true);
|
|
3499
|
+
positionOverlay(selectionOverlay, target);
|
|
3500
|
+
(window.parent as Window).postMessage(
|
|
3501
|
+
{ type: "element-select", payload: getElementInfo(target) },
|
|
3502
|
+
"*",
|
|
3503
|
+
);
|
|
3504
|
+
(window.parent as Window).postMessage(
|
|
3505
|
+
{ type: "element-dblclick-text", payload: getElementInfo(target) },
|
|
3506
|
+
"*",
|
|
3507
|
+
);
|
|
3508
|
+
postTextEditingState(target, true);
|
|
3509
|
+
|
|
3510
|
+
function finish(commit) {
|
|
3511
|
+
if (committed) return;
|
|
3512
|
+
committed = true;
|
|
3513
|
+
target.removeEventListener("blur", onBlur, true);
|
|
3514
|
+
target.removeEventListener("keydown", onKeyDown, true);
|
|
3515
|
+
target.removeEventListener("paste", onPaste, true);
|
|
3516
|
+
target.removeEventListener("keyup", onSelectionChange, true);
|
|
3517
|
+
target.removeEventListener("mouseup", onSelectionChange, true);
|
|
3518
|
+
document.removeEventListener("selectionchange", onSelectionChange);
|
|
3519
|
+
target.removeAttribute("contenteditable");
|
|
3520
|
+
target.removeAttribute("data-agent-native-text-editing");
|
|
3521
|
+
target.style.cursor = "";
|
|
3522
|
+
target.style.outline = "";
|
|
3523
|
+
target.style.outlineOffset = "";
|
|
3524
|
+
setTextEditingPointerPassthrough(false);
|
|
3525
|
+
if (activeTextEditEl === target) activeTextEditEl = null;
|
|
3526
|
+
postTextEditingState(target, false);
|
|
3527
|
+
if (!commit) {
|
|
3528
|
+
target.innerHTML = originalHtml;
|
|
3529
|
+
refreshOverlays();
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
var next = target.textContent || "";
|
|
3533
|
+
var nextHtml = target.innerHTML || "";
|
|
3534
|
+
refreshOverlays();
|
|
3535
|
+
if (next !== originalText || nextHtml !== originalHtml) {
|
|
3536
|
+
postTextContentChange(target, next, nextHtml);
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
function onBlur() {
|
|
3541
|
+
finish(true);
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
function onKeyDown(ev) {
|
|
3545
|
+
if (ev.key === "Escape") {
|
|
3546
|
+
ev.preventDefault();
|
|
3547
|
+
finish(false);
|
|
3548
|
+
target.blur();
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
if (ev.key === "Enter" && !ev.shiftKey) {
|
|
3552
|
+
ev.preventDefault();
|
|
3553
|
+
finish(true);
|
|
3554
|
+
target.blur();
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
function onPaste(ev) {
|
|
3559
|
+
ev.preventDefault();
|
|
3560
|
+
insertPlainTextAtSelection(
|
|
3561
|
+
(ev.clipboardData && ev.clipboardData.getData("text/plain")) || "",
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
function onSelectionChange() {
|
|
3566
|
+
postTextEditingState(target, true);
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
target.addEventListener("blur", onBlur, true);
|
|
3570
|
+
target.addEventListener("keydown", onKeyDown, true);
|
|
3571
|
+
target.addEventListener("paste", onPaste, true);
|
|
3572
|
+
target.addEventListener("keyup", onSelectionChange, true);
|
|
3573
|
+
target.addEventListener("mouseup", onSelectionChange, true);
|
|
3574
|
+
document.addEventListener("selectionchange", onSelectionChange);
|
|
3575
|
+
target.focus();
|
|
3576
|
+
placeTextCaretFromPoint(target, e.clientX, e.clientY);
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
shieldOverlay.addEventListener("dblclick", beginTextEditingFromEvent, true);
|
|
3580
|
+
selectionOverlay.addEventListener(
|
|
3581
|
+
"dblclick",
|
|
3582
|
+
beginTextEditingFromEvent,
|
|
3583
|
+
true,
|
|
3584
|
+
);
|
|
3585
|
+
document.addEventListener(
|
|
3586
|
+
"dblclick",
|
|
3587
|
+
function (e) {
|
|
3588
|
+
if (isOverlayElement(e.target)) return;
|
|
3589
|
+
beginTextEditingFromEvent(e);
|
|
3590
|
+
},
|
|
3591
|
+
true,
|
|
3592
|
+
);
|
|
3593
|
+
|
|
3594
|
+
shieldOverlay.addEventListener(
|
|
3595
|
+
"pointermove",
|
|
3596
|
+
function (e) {
|
|
3597
|
+
stopNativeInteraction(e);
|
|
3598
|
+
hoveredEl = elementFromEditorPoint(e.clientX, e.clientY);
|
|
3599
|
+
if (!hoveredEl) {
|
|
3600
|
+
highlightOverlay.style.display = "none";
|
|
3601
|
+
if (!spacingDrag) {
|
|
3602
|
+
selectedSpacingHovered = false;
|
|
3603
|
+
hoveredSpacingHandleKey = "";
|
|
3604
|
+
updateSpacingOverlay(selectedEl);
|
|
3605
|
+
}
|
|
3606
|
+
hideMeasurements();
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
if (hoveredEl && hoveredEl.closest("[data-agent-native-text-editing]"))
|
|
3610
|
+
return;
|
|
3611
|
+
if (!spacingDrag) {
|
|
3612
|
+
selectedSpacingHovered = Boolean(
|
|
3613
|
+
selectedEl &&
|
|
3614
|
+
hoveredEl &&
|
|
3615
|
+
(hoveredEl === selectedEl ||
|
|
3616
|
+
(selectedEl.contains && selectedEl.contains(hoveredEl))),
|
|
3617
|
+
);
|
|
3618
|
+
if (!selectedSpacingHovered) hoveredSpacingHandleKey = "";
|
|
3619
|
+
updateSpacingOverlay(selectedEl);
|
|
3620
|
+
}
|
|
3621
|
+
if (hoveredEl === selectedEl) {
|
|
3622
|
+
highlightOverlay.style.display = "none";
|
|
3623
|
+
} else {
|
|
3624
|
+
positionOverlay(highlightOverlay, hoveredEl);
|
|
3625
|
+
}
|
|
3626
|
+
if (e.altKey && selectedEl && hoveredEl && selectedEl !== hoveredEl) {
|
|
3627
|
+
showMeasurements(selectedEl, hoveredEl);
|
|
3628
|
+
} else {
|
|
3629
|
+
hideMeasurements();
|
|
3630
|
+
}
|
|
3631
|
+
var info = getElementInfo(hoveredEl);
|
|
3632
|
+
(window.parent as Window).postMessage(
|
|
3633
|
+
{ type: "element-hover", payload: info },
|
|
3634
|
+
"*",
|
|
3635
|
+
);
|
|
3636
|
+
},
|
|
3637
|
+
true,
|
|
3638
|
+
);
|
|
3639
|
+
|
|
3640
|
+
selectionOverlay.addEventListener(
|
|
3641
|
+
"pointermove",
|
|
3642
|
+
handleSpacingOverlayPointerMove,
|
|
3643
|
+
true,
|
|
3644
|
+
);
|
|
3645
|
+
|
|
3646
|
+
selectionOverlay.addEventListener(
|
|
3647
|
+
"pointerleave",
|
|
3648
|
+
function (e) {
|
|
3649
|
+
stopNativeInteraction(e);
|
|
3650
|
+
if (shouldKeepSpacingOverlayForLeave(e)) {
|
|
3651
|
+
updateSpacingOverlay(selectedEl);
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
if (!spacingDrag) {
|
|
3655
|
+
selectedSpacingHovered = false;
|
|
3656
|
+
hoveredSpacingHandleKey = "";
|
|
3657
|
+
updateSpacingOverlay(selectedEl);
|
|
3658
|
+
}
|
|
3659
|
+
},
|
|
3660
|
+
true,
|
|
3661
|
+
);
|
|
3662
|
+
|
|
3663
|
+
shieldOverlay.addEventListener(
|
|
3664
|
+
"pointerleave",
|
|
3665
|
+
function (e) {
|
|
3666
|
+
stopNativeInteraction(e);
|
|
3667
|
+
if (shouldKeepSpacingOverlayForLeave(e)) {
|
|
3668
|
+
updateSpacingOverlay(selectedEl);
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
hoveredEl = null;
|
|
3672
|
+
if (!spacingDrag) {
|
|
3673
|
+
selectedSpacingHovered = false;
|
|
3674
|
+
hoveredSpacingHandleKey = "";
|
|
3675
|
+
updateSpacingOverlay(selectedEl);
|
|
3676
|
+
}
|
|
3677
|
+
highlightOverlay.style.display = "none";
|
|
3678
|
+
hideMeasurements();
|
|
3679
|
+
(window.parent as Window).postMessage(
|
|
3680
|
+
{ type: "element-hover", payload: null },
|
|
3681
|
+
"*",
|
|
3682
|
+
);
|
|
3683
|
+
},
|
|
3684
|
+
true,
|
|
3685
|
+
);
|
|
3686
|
+
|
|
3687
|
+
window.addEventListener(
|
|
3688
|
+
"keyup",
|
|
3689
|
+
function (e) {
|
|
3690
|
+
if (e.key === "Alt") hideMeasurements();
|
|
3691
|
+
},
|
|
3692
|
+
true,
|
|
3693
|
+
);
|
|
3694
|
+
|
|
3695
|
+
window.addEventListener("message", function (e) {
|
|
3696
|
+
if (e.source !== window.parent) return;
|
|
3697
|
+
if (!e.data) return;
|
|
3698
|
+
if (e.data.payload && typeof e.data.payload === "object") {
|
|
3699
|
+
Object.keys(e.data.payload).forEach(function (key) {
|
|
3700
|
+
if (e.data[key] === undefined) e.data[key] = e.data.payload[key];
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
// set-read-only: toggle the bridge's readOnly state in-place without a reload.
|
|
3704
|
+
// When readOnly becomes true the shield/selection/drag/edit entry points are
|
|
3705
|
+
// gated so the surface is safe for background/inactive display use.
|
|
3706
|
+
if (e.data.type === "set-read-only") {
|
|
3707
|
+
var nextReadOnly = !!e.data.readOnly;
|
|
3708
|
+
if (readOnly === nextReadOnly) return;
|
|
3709
|
+
readOnly = nextReadOnly;
|
|
3710
|
+
textEditingEnabled = !readOnly && __TEXT_EDITING_ENABLED__;
|
|
3711
|
+
if (readOnly) {
|
|
3712
|
+
// Leave the text editor gracefully before going read-only.
|
|
3713
|
+
if (activeTextEditEl) {
|
|
3714
|
+
activeTextEditEl.blur();
|
|
3715
|
+
}
|
|
3716
|
+
clearRuntimeSelection();
|
|
3717
|
+
shieldOverlay.style.pointerEvents = "none";
|
|
3718
|
+
} else {
|
|
3719
|
+
shieldOverlay.style.pointerEvents = "auto";
|
|
3720
|
+
}
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
// begin-text-edit: enter text-editing mode for the element identified by
|
|
3724
|
+
// nodeId immediately (no double-click needed). Used after programmatic
|
|
3725
|
+
// text-element creation so the user can type right away and the autosize
|
|
3726
|
+
// CSS (width:max-content or similar) takes effect from the first keystroke.
|
|
3727
|
+
if (e.data.type === "begin-text-edit") {
|
|
3728
|
+
if (readOnly || !textEditingEnabled) return;
|
|
3729
|
+
var nodeId: string =
|
|
3730
|
+
typeof e.data.nodeId === "string" ? e.data.nodeId : "";
|
|
3731
|
+
if (!nodeId) return;
|
|
3732
|
+
var nodeTarget: Element | null = document.querySelector(
|
|
3733
|
+
'[data-agent-native-node-id="' +
|
|
3734
|
+
nodeId.replace(/\\/g, "\\\\").replace(/"/g, '\\"') +
|
|
3735
|
+
'"]',
|
|
3736
|
+
);
|
|
3737
|
+
if (!nodeTarget || nodeTarget.nodeType !== 1) return;
|
|
3738
|
+
// Resolve the actual editable text leaf (same logic as findTextEditTarget).
|
|
3739
|
+
var textTarget: HTMLElement | null =
|
|
3740
|
+
(findTextEditTarget(nodeTarget) as HTMLElement | null) ||
|
|
3741
|
+
(nodeTarget as HTMLElement);
|
|
3742
|
+
if (!textTarget || textTarget.nodeType !== 1) return;
|
|
3743
|
+
// If we are already editing this element, do nothing.
|
|
3744
|
+
if (activeTextEditEl && activeTextEditEl === textTarget) return;
|
|
3745
|
+
// Synthesise coordinates at the end of the element content so the caret
|
|
3746
|
+
// lands at the insertion point (right after any placeholder text).
|
|
3747
|
+
var bteRect = textTarget.getBoundingClientRect();
|
|
3748
|
+
var bteCenterX = bteRect.right - 2;
|
|
3749
|
+
var bteCenterY = bteRect.top + bteRect.height / 2;
|
|
3750
|
+
// Delegate to the canonical path so all state, events, and postMessages
|
|
3751
|
+
// stay consistent with a normal double-click text edit.
|
|
3752
|
+
beginTextEditingFromEvent({
|
|
3753
|
+
clientX: bteCenterX,
|
|
3754
|
+
clientY: bteCenterY,
|
|
3755
|
+
target: textTarget,
|
|
3756
|
+
preventDefault: function () {},
|
|
3757
|
+
stopPropagation: function () {},
|
|
3758
|
+
stopImmediatePropagation: function () {},
|
|
3759
|
+
} as unknown as MouseEvent);
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
if (e.data.type === "set-editor-chrome-scale") {
|
|
3763
|
+
// Live-update the constant-size chrome scale WITHOUT rebuilding srcdoc.
|
|
3764
|
+
// Rebuilding srcdoc reloads the iframe and flashes the content white.
|
|
3765
|
+
editorChromeScaleX = Math.max(0.05, Number(e.data.scaleX) || 1);
|
|
3766
|
+
editorChromeScaleY = Math.max(
|
|
3767
|
+
0.05,
|
|
3768
|
+
Number(e.data.scaleY) || editorChromeScaleX,
|
|
3769
|
+
);
|
|
3770
|
+
applyEditorChromeScale();
|
|
3771
|
+
if (selectedEl || hoveredEl) refreshOverlays();
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
3774
|
+
if (e.data.type === "scale-tool-mode") {
|
|
3775
|
+
scaleToolEnabled = !!e.data.enabled;
|
|
3776
|
+
return;
|
|
3777
|
+
}
|
|
3778
|
+
if (e.data.type === "clear-selection") {
|
|
3779
|
+
clearRuntimeSelection();
|
|
3780
|
+
return;
|
|
3781
|
+
}
|
|
3782
|
+
if (e.data.type === "select-element") {
|
|
3783
|
+
var candidates: string[] = [];
|
|
3784
|
+
if (Array.isArray(e.data.selectorCandidates)) {
|
|
3785
|
+
e.data.selectorCandidates.forEach(function (selector) {
|
|
3786
|
+
if (
|
|
3787
|
+
typeof selector === "string" &&
|
|
3788
|
+
selector &&
|
|
3789
|
+
candidates.indexOf(selector) === -1
|
|
3790
|
+
) {
|
|
3791
|
+
candidates.push(selector);
|
|
3792
|
+
}
|
|
3793
|
+
});
|
|
3794
|
+
}
|
|
3795
|
+
if (
|
|
3796
|
+
e.data.selector &&
|
|
3797
|
+
candidates.indexOf(String(e.data.selector)) === -1
|
|
3798
|
+
) {
|
|
3799
|
+
candidates.push(String(e.data.selector));
|
|
3800
|
+
}
|
|
3801
|
+
var target =
|
|
3802
|
+
selectedEl &&
|
|
3803
|
+
document.documentElement.contains(selectedEl) &&
|
|
3804
|
+
matchesExactSelectorList(selectedEl, candidates)
|
|
3805
|
+
? selectedEl
|
|
3806
|
+
: null;
|
|
3807
|
+
for (var i = 0; i < candidates.length && !target; i += 1) {
|
|
3808
|
+
try {
|
|
3809
|
+
var matches = document.querySelectorAll(candidates[i]);
|
|
3810
|
+
for (var j = 0; j < matches.length; j += 1) {
|
|
3811
|
+
if (!isLayerInteractionBlocked(matches[j])) {
|
|
3812
|
+
target = matches[j];
|
|
3813
|
+
break;
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
} catch (_err) {}
|
|
3817
|
+
}
|
|
3818
|
+
if (!target) return;
|
|
3819
|
+
selectedSpacingHovered = false;
|
|
3820
|
+
hoveredSpacingHandleKey = "";
|
|
3821
|
+
selectedEl = target;
|
|
3822
|
+
positionOverlay(selectionOverlay, target);
|
|
3823
|
+
if (hoveredEl === selectedEl) highlightOverlay.style.display = "none";
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3826
|
+
if (e.data.type === "hover-element") {
|
|
3827
|
+
var hoverCandidates: string[] = [];
|
|
3828
|
+
if (Array.isArray(e.data.selectorCandidates)) {
|
|
3829
|
+
e.data.selectorCandidates.forEach(function (selector) {
|
|
3830
|
+
if (
|
|
3831
|
+
typeof selector === "string" &&
|
|
3832
|
+
selector &&
|
|
3833
|
+
hoverCandidates.indexOf(selector) === -1
|
|
3834
|
+
) {
|
|
3835
|
+
hoverCandidates.push(selector);
|
|
3836
|
+
}
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
if (
|
|
3840
|
+
e.data.selector &&
|
|
3841
|
+
hoverCandidates.indexOf(String(e.data.selector)) === -1
|
|
3842
|
+
) {
|
|
3843
|
+
hoverCandidates.push(String(e.data.selector));
|
|
3844
|
+
}
|
|
3845
|
+
if (hoverCandidates.length === 0) {
|
|
3846
|
+
hoveredEl = null;
|
|
3847
|
+
highlightOverlay.style.display = "none";
|
|
3848
|
+
hideMeasurements();
|
|
3849
|
+
return;
|
|
3850
|
+
}
|
|
3851
|
+
var hoverTarget = findRuntimeTarget(
|
|
3852
|
+
String(e.data.selector || ""),
|
|
3853
|
+
hoverCandidates,
|
|
3854
|
+
);
|
|
3855
|
+
hoveredEl = hoverTarget;
|
|
3856
|
+
if (
|
|
3857
|
+
hoveredEl &&
|
|
3858
|
+
!isLayerInteractionBlocked(hoveredEl) &&
|
|
3859
|
+
hoveredEl !== selectedEl
|
|
3860
|
+
) {
|
|
3861
|
+
positionOverlay(highlightOverlay, hoveredEl);
|
|
3862
|
+
} else {
|
|
3863
|
+
highlightOverlay.style.display = "none";
|
|
3864
|
+
hideMeasurements();
|
|
3865
|
+
}
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
if (e.data.type === "layer-states") {
|
|
3869
|
+
lockedSelectors = Array.isArray(e.data.lockedSelectors)
|
|
3870
|
+
? e.data.lockedSelectors.filter(function (item) {
|
|
3871
|
+
return typeof item === "string";
|
|
3872
|
+
})
|
|
3873
|
+
: [];
|
|
3874
|
+
hiddenSelectors = Array.isArray(e.data.hiddenSelectors)
|
|
3875
|
+
? e.data.hiddenSelectors.filter(function (item) {
|
|
3876
|
+
return typeof item === "string";
|
|
3877
|
+
})
|
|
3878
|
+
: [];
|
|
3879
|
+
if (selectedEl && isLayerInteractionBlocked(selectedEl)) {
|
|
3880
|
+
selectedEl = null;
|
|
3881
|
+
selectionOverlay.style.display = "none";
|
|
3882
|
+
clearComponentTag();
|
|
3883
|
+
}
|
|
3884
|
+
if (hoveredEl && isLayerInteractionBlocked(hoveredEl)) {
|
|
3885
|
+
hoveredEl = null;
|
|
3886
|
+
highlightOverlay.style.display = "none";
|
|
3887
|
+
}
|
|
3888
|
+
applyHiddenSelectors();
|
|
3889
|
+
return;
|
|
3890
|
+
}
|
|
3891
|
+
if (e.data.type === "visual-structure-ack") {
|
|
3892
|
+
var move = pendingStructureMoves[e.data.requestId];
|
|
3893
|
+
if (!move) return;
|
|
3894
|
+
delete pendingStructureMoves[e.data.requestId];
|
|
3895
|
+
if (e.data.applied) {
|
|
3896
|
+
if (move.el && move.el.isConnected) {
|
|
3897
|
+
applyRuntimeReorder(move.el, move.target);
|
|
3898
|
+
selectedEl = move.el;
|
|
3899
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
3900
|
+
(window.parent as Window).postMessage(
|
|
3901
|
+
{ type: "element-select", payload: getElementInfo(selectedEl) },
|
|
3902
|
+
"*",
|
|
3903
|
+
);
|
|
3904
|
+
}
|
|
3905
|
+
} else {
|
|
3906
|
+
// Revert the optimistic reorder to its pre-drag position.
|
|
3907
|
+
if (
|
|
3908
|
+
move.el &&
|
|
3909
|
+
move.el.isConnected &&
|
|
3910
|
+
move.origin &&
|
|
3911
|
+
move.origin.prevParent &&
|
|
3912
|
+
move.origin.prevParent.isConnected
|
|
3913
|
+
) {
|
|
3914
|
+
move.origin.prevParent.insertBefore(
|
|
3915
|
+
move.el,
|
|
3916
|
+
move.origin.prevNextSibling,
|
|
3917
|
+
);
|
|
3918
|
+
selectedEl = move.el;
|
|
3919
|
+
positionOverlay(selectionOverlay, selectedEl);
|
|
3920
|
+
(window.parent as Window).postMessage(
|
|
3921
|
+
{ type: "element-select", payload: getElementInfo(selectedEl) },
|
|
3922
|
+
"*",
|
|
3923
|
+
);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
return;
|
|
3927
|
+
}
|
|
3928
|
+
if (e.data.type === "replace-document-content") {
|
|
3929
|
+
replaceRuntimeDocument(
|
|
3930
|
+
e.data.content,
|
|
3931
|
+
e.data.forceFullDocument ? "" : e.data.selectedSelector,
|
|
3932
|
+
e.data.forceFullDocument ? [] : e.data.selectorCandidates,
|
|
3933
|
+
);
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
if (e.data.type === "delete-element") {
|
|
3937
|
+
removeRuntimeTarget(e.data.selector, e.data.selectorCandidates);
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
if (e.data.type !== "style-change") return;
|
|
3941
|
+
var sel = e.data.selector;
|
|
3942
|
+
var prop = e.data.property;
|
|
3943
|
+
var val = e.data.value;
|
|
3944
|
+
var candidatesForStyle = Array.isArray(e.data.selectorCandidates)
|
|
3945
|
+
? e.data.selectorCandidates
|
|
3946
|
+
: [];
|
|
3947
|
+
if (sel && candidatesForStyle.indexOf(String(sel)) === -1)
|
|
3948
|
+
candidatesForStyle.push(String(sel));
|
|
3949
|
+
if (e.data.nodeId) {
|
|
3950
|
+
candidatesForStyle.push(
|
|
3951
|
+
'[data-agent-native-node-id="' +
|
|
3952
|
+
String(e.data.nodeId).replace(/"/g, '\\"') +
|
|
3953
|
+
'"]',
|
|
3954
|
+
);
|
|
3955
|
+
}
|
|
3956
|
+
var el = findRuntimeTarget(String(sel || ""), candidatesForStyle);
|
|
3957
|
+
if (
|
|
3958
|
+
prop &&
|
|
3959
|
+
activeTextEditEl &&
|
|
3960
|
+
el === activeTextEditEl &&
|
|
3961
|
+
applyTextRangeStyle(prop, val)
|
|
3962
|
+
) {
|
|
3963
|
+
postTextContentChange(
|
|
3964
|
+
activeTextEditEl,
|
|
3965
|
+
activeTextEditEl.textContent || "",
|
|
3966
|
+
activeTextEditEl.innerHTML || "",
|
|
3967
|
+
);
|
|
3968
|
+
refreshOverlays();
|
|
3969
|
+
return;
|
|
3970
|
+
}
|
|
3971
|
+
if (!el) return;
|
|
3972
|
+
var didPatchDom = false;
|
|
3973
|
+
var attributeOverrides = e.data.attributeOverrides;
|
|
3974
|
+
if (
|
|
3975
|
+
attributeOverrides &&
|
|
3976
|
+
typeof attributeOverrides === "object" &&
|
|
3977
|
+
!Array.isArray(attributeOverrides)
|
|
3978
|
+
) {
|
|
3979
|
+
Object.keys(attributeOverrides).forEach(function (name) {
|
|
3980
|
+
if (!/^(?!on)[a-zA-Z][a-zA-Z0-9:_.-]*$/i.test(name)) return;
|
|
3981
|
+
var nextValue = attributeOverrides[name];
|
|
3982
|
+
if (
|
|
3983
|
+
nextValue === null ||
|
|
3984
|
+
nextValue === undefined ||
|
|
3985
|
+
nextValue === false
|
|
3986
|
+
) {
|
|
3987
|
+
el.removeAttribute(name);
|
|
3988
|
+
} else {
|
|
3989
|
+
el.setAttribute(name, String(nextValue));
|
|
3990
|
+
}
|
|
3991
|
+
didPatchDom = true;
|
|
3992
|
+
});
|
|
3993
|
+
}
|
|
3994
|
+
var classEdit = e.data.classEdit;
|
|
3995
|
+
if (classEdit && typeof classEdit === "object") {
|
|
3996
|
+
var currentClass = (el.getAttribute("class") || "")
|
|
3997
|
+
.trim()
|
|
3998
|
+
.split(/\s+/)
|
|
3999
|
+
.filter(Boolean);
|
|
4000
|
+
var nextClass = currentClass.slice();
|
|
4001
|
+
if (classEdit.operation === "replace" && classEdit.from && classEdit.to) {
|
|
4002
|
+
var replaced = false;
|
|
4003
|
+
nextClass = currentClass.map(function (token) {
|
|
4004
|
+
if (token === classEdit.from) {
|
|
4005
|
+
replaced = true;
|
|
4006
|
+
return String(classEdit.to);
|
|
4007
|
+
}
|
|
4008
|
+
return token;
|
|
4009
|
+
});
|
|
4010
|
+
if (!replaced && nextClass.indexOf(String(classEdit.to)) === -1)
|
|
4011
|
+
nextClass.push(String(classEdit.to));
|
|
4012
|
+
didPatchDom = true;
|
|
4013
|
+
} else if (classEdit.operation === "add" && classEdit.className) {
|
|
4014
|
+
if (nextClass.indexOf(String(classEdit.className)) === -1)
|
|
4015
|
+
nextClass.push(String(classEdit.className));
|
|
4016
|
+
didPatchDom = true;
|
|
4017
|
+
} else if (classEdit.operation === "remove" && classEdit.className) {
|
|
4018
|
+
nextClass = currentClass.filter(function (token) {
|
|
4019
|
+
return token !== String(classEdit.className);
|
|
4020
|
+
});
|
|
4021
|
+
didPatchDom = true;
|
|
4022
|
+
}
|
|
4023
|
+
if (didPatchDom) el.setAttribute("class", nextClass.join(" "));
|
|
4024
|
+
}
|
|
4025
|
+
if (prop && typeof prop === "string") {
|
|
4026
|
+
applyInlineStyleProperty(el, prop, val);
|
|
4027
|
+
didPatchDom = true;
|
|
4028
|
+
}
|
|
4029
|
+
if (didPatchDom) {
|
|
4030
|
+
refreshOverlays();
|
|
4031
|
+
}
|
|
4032
|
+
});
|
|
4033
|
+
|
|
4034
|
+
window.addEventListener("scroll", refreshOverlays, true);
|
|
4035
|
+
window.addEventListener("resize", refreshOverlays);
|
|
4036
|
+
applyEditorChromeScale();
|
|
4037
|
+
})();
|