@agent-native/core 0.82.0 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +34 -0
- package/corpus/core/README.md +2 -2
- package/corpus/core/docs/content/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/ar-SA/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/de-DE/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/es-ES/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/fr-FR/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/hi-IN/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/ja-JP/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/ko-KR/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/pt-BR/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-CN/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/template-chat.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-TW/faq.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/template-chat.mdx +1 -1
- package/corpus/core/docs/content/template-chat.mdx +1 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builtin.ts +1 -1
- package/corpus/core/src/agent/engine/output-tokens.ts +1 -1
- package/corpus/core/src/agent/harness/acp-adapter.ts +28 -4
- package/corpus/core/src/agent/harness/ai-sdk-adapter.ts +3 -0
- package/corpus/core/src/agent/harness/translate.ts +3 -0
- package/corpus/core/src/agent/harness/types.ts +1 -0
- package/corpus/core/src/agent/model-config.ts +34 -13
- package/corpus/core/src/agent/production-agent.ts +87 -10
- package/corpus/core/src/agent/thread-data-builder.ts +29 -4
- package/corpus/core/src/agent/tool-call-journal.ts +109 -48
- package/corpus/core/src/agent/types.ts +6 -1
- package/corpus/core/src/cli/design-connect.ts +106 -0
- package/corpus/core/src/cli/gateway-helpers.ts +17 -0
- package/corpus/core/src/cli/recap.ts +0 -2
- package/corpus/core/src/cli/skills.ts +54 -16
- package/corpus/core/src/cli/workspace-dev.ts +29 -5
- package/corpus/core/src/client/AssistantChat.tsx +89 -42
- package/corpus/core/src/client/CommandMenu.tsx +34 -12
- package/corpus/core/src/client/DefaultSpinner.tsx +8 -1
- package/corpus/core/src/client/chat/message-components.tsx +49 -1
- package/corpus/core/src/client/chat/repo-helpers.ts +71 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +15 -5
- package/corpus/core/src/client/components/ApiKeySettings.tsx +8 -8
- package/corpus/core/src/client/components/CodeRequiredDialog.tsx +26 -15
- package/corpus/core/src/client/components/MissingKeyCard.tsx +13 -6
- package/corpus/core/src/client/composer/TiptapComposer.tsx +4 -3
- package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +0 -1
- package/corpus/core/src/client/guided-questions.tsx +21 -27
- package/corpus/core/src/client/integrations/IntegrationCard.tsx +1 -1
- package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +1 -1
- package/corpus/core/src/client/onboarding/OnboardingBanner.tsx +2 -2
- package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +23 -19
- package/corpus/core/src/client/resources/ResourceEditor.tsx +18 -10
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +5 -1
- package/corpus/core/src/client/settings/SettingsPanel.tsx +8 -3
- package/corpus/core/src/client/settings/useBuilderStatus.ts +8 -41
- package/corpus/core/src/client/useProductionAgent.ts +6 -2
- package/corpus/core/src/deploy/build.ts +1 -1
- package/corpus/core/src/observability/traces.ts +3 -3
- package/corpus/core/src/onboarding/default-steps.ts +1 -1
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +177 -81
- package/corpus/core/src/server/core-routes-plugin.ts +7 -1
- package/corpus/core/src/server/open-route.ts +59 -40
- package/corpus/core/src/shared/reasoning-effort.ts +2 -0
- package/corpus/core/src/sharing/access.ts +22 -2
- package/corpus/core/src/sharing/registry.ts +18 -0
- package/corpus/core/src/styles/agent-native.css +104 -20
- package/corpus/core/src/styles/rich-markdown-editor.css +11 -10
- package/corpus/templates/analytics/actions/hubspot-deals.ts +64 -4
- package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +10 -10
- package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +10 -10
- package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +6 -5
- package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +9 -9
- package/corpus/templates/analytics/app/global.css +4 -0
- package/corpus/templates/analytics/app/lib/chart-theme.ts +13 -0
- package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +22 -24
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-charts-now-use-theme-aware-tooltip-and-grid-colors.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-fixed-ask-composer-spacing-so-the-chat-field-has-a-comfortab.md +6 -0
- package/corpus/templates/calendar/AGENTS.md +4 -0
- package/corpus/templates/calendar/actions/connect-google-calendar.ts +80 -0
- package/corpus/templates/calendar/changelog/2026-06-30-calendar-now-gives-the-agent-a-safe-google-connection-link-i.md +6 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/content/.env.local.example +6 -0
- package/corpus/templates/content/AGENTS.md +10 -1
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +14 -1
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +43 -0
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +79 -7
- package/corpus/templates/content/actions/_database-source-utils.ts +987 -22
- package/corpus/templates/content/actions/_database-utils.ts +41 -0
- package/corpus/templates/content/actions/_local-file-documents.ts +74 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +33 -8
- package/corpus/templates/content/actions/change-content-database-source-role.ts +14 -0
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +3 -0
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +71 -3
- package/corpus/templates/content/actions/list-content-databases.ts +41 -33
- package/corpus/templates/content/actions/process-builder-body-hydration.ts +47 -0
- package/corpus/templates/content/actions/update-document.ts +93 -0
- package/corpus/templates/content/app/blocks/SourceComponentBlock.tsx +277 -0
- package/corpus/templates/content/app/blocks/contentBlockRegistry.tsx +2 -0
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +5 -5
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +45 -4
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +5 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +92 -0
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +5 -3
- package/corpus/templates/content/app/components/editor/extensions/registryBlocks.ts +41 -0
- package/corpus/templates/content/app/components/editor/registrySlashItems.ts +4 -0
- package/corpus/templates/content/app/global.css +16 -8
- package/corpus/templates/content/app/hooks/use-content-database.ts +24 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +24 -0
- package/corpus/templates/content/app/i18n-data.ts +212 -0
- package/corpus/templates/content/app/lib/content-command-search.ts +64 -0
- package/corpus/templates/content/app/root.tsx +236 -8
- package/corpus/templates/content/changelog/2026-06-29-builder-cms-sources-now-sync-article-bodies-through-content.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-builder-article-media-now-render-as-images-and-embeds-when-r.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-builder-source-components-now-render-as-preserved-preview-bl.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-cmd-k-search-now-opens-from-the-editor-and-finds-real-content.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-editor-floating-toolbars-now-use-theme-aware-colors-in-light.md +6 -0
- package/corpus/templates/content/package.json +3 -1
- package/corpus/templates/content/scripts/check-native-deps.mjs +57 -0
- package/corpus/templates/content/scripts/dev-database.mjs +83 -0
- package/corpus/templates/content/scripts/seed-demo-user.mjs +107 -0
- package/corpus/templates/content/server/db/schema.ts +27 -0
- package/corpus/templates/content/server/plugins/db.ts +33 -0
- package/corpus/templates/content/shared/api.ts +45 -0
- package/corpus/templates/content/shared/builder-mdx.ts +905 -5
- package/corpus/templates/content/shared/nfm-registry.ts +2 -0
- package/corpus/templates/content/shared/source-component-block.ts +141 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +6 -1
- package/corpus/templates/design/actions/add-localhost-screens.ts +63 -8
- package/corpus/templates/design/actions/apply-motion-edit.ts +12 -46
- package/corpus/templates/design/actions/list-design-native-assets.ts +2 -1
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +49 -1
- package/corpus/templates/design/actions/navigate.ts +3 -12
- package/corpus/templates/design/actions/open-visual-edit.ts +341 -0
- package/corpus/templates/design/actions/view-screen.ts +1 -1
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +1 -1
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +350 -15
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +71 -91
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +25 -27
- package/corpus/templates/design/app/components/design/DeviceFrame.tsx +1 -1
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +2 -2
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1111 -570
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +162 -32
- package/corpus/templates/design/app/components/design/MotionDock.tsx +268 -240
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1193 -207
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +1 -1
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +81 -102
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +6 -6
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +11 -11
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +9 -7
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1225 -154
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +130 -37
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +24 -11
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +184 -162
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +137 -79
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +13 -13
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +8 -8
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +2 -2
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +4 -4
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +3 -3
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -7
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +14 -11
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +4 -4
- package/corpus/templates/design/app/components/design/inspector/index.ts +2 -0
- package/corpus/templates/design/app/components/design/types.ts +19 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +61 -78
- package/corpus/templates/design/app/components/layout/Header.tsx +0 -1
- package/corpus/templates/design/app/components/layout/Layout.tsx +3 -2
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +0 -2
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +5 -4
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +2 -2
- package/corpus/templates/design/app/global.css +56 -0
- package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -5
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +6 -7
- package/corpus/templates/design/app/hooks/use-question-flow.ts +3 -2
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +6 -4
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +6 -3
- package/corpus/templates/design/app/i18n/zh-TW.ts +5 -14
- package/corpus/templates/design/app/i18n-data.ts +63 -188
- package/corpus/templates/design/app/lib/agent-chat.ts +13 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +1755 -401
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +9 -5
- package/corpus/templates/design/app/pages/DesignSystems.tsx +2 -2
- package/corpus/templates/design/app/pages/Index.tsx +49 -26
- package/corpus/templates/design/app/pages/NotFound.tsx +25 -8
- package/corpus/templates/design/app/pages/VisualEdit.tsx +17 -5
- package/corpus/templates/design/app/routes/examples.tsx +1 -7
- package/corpus/templates/design/app/routes/templates.tsx +7 -4
- package/corpus/templates/design/changelog/2026-06-30-board-shapes-and-auto-layout-controls-are-clearer.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-canvas-editing-now-keeps-undo-redo-and-cross-screen-layer-mo.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-selections-now-use-purple-component-chrome-and-sho.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-constraints-now-stay-tucked-into-a-compact-inspector-preview.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-creating-a-component-now-opens-in-a-compact-inspector-popove.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-cards-no-longer-show-legacy-project-type-badges.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-chat-now-keeps-new-turns-in-the-visible-design-agent-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-editor-panels-now-keep-their-compact-text-sizing-cons.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-no-longer-shows-the-connected-local-code-warning-bann.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-opens-to-the-files-panel-unless-a-generation-is-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-opens-with-a-cleaner-empty-canvas-and-loading-preview.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-tools-use-simpler-less-decorative-icons.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-dragging-absolute-elements-no-longer-shows-coordinate-labels.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-all-screens-opening-too-zoomed-in-by-default.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-the-public-design-assets-sidebar-so-native-components-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-the-screen-overview-board-so-empty-board-space-no-long.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-full-view-screens-can-now-scroll-while-remaining-editable.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-inspector-element-headers-show-the-selected-html-tag-and-the.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-inspector-inputs-stay-compact-across-desktop-breakpoints.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-layers-can-be-multi-selected-from-the-canvas-and-edited-toge.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-local-visual-edit-links-can-edit-localhost-frames-without-si.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-localhost-visual-edit-frames-can-now-be-selected-and-edited.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-marquee-selection-now-works-inside-screens-multi-selected-la.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-is-now-available-from-the-persistent-left-rail-and-th.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-timeline-collapse-and-reopen-animations-feel-smoother.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-board-shapes-now-start-with-neutral-gray-styling.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-designs-open-into-a-steadier-loading-state-before-the-ed.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-designs-show-a-loading-state-during-handoff-and-keep-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-public-visual-edit-design-links-now-open-directly-instead-of.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-removed-the-confusing-states-section-from-the-design-editor-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-removed-the-templates-gallery-from-design-navigation.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-stays-visible-as-soon-as-editable-designs-load.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-signed-out-save-and-share-controls-use-simpler-labels-and-op.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-text-added-on-the-canvas-now-focuses-cleanly-with-the-right-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-is-simpler-and-its-bottom.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-now-follows-the-current-t.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-child-layout-controls-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-the-ask-ai-section-abov.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-review-panel-no-longer-shows-a-separate-inline-au.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-empty-designs-page-now-focuses-on-a-single-primary-new-d.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-layers-panel-header-stays-as-layers-while-multiple-items.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-missing-design-screen-now-uses-a-neutral-back-button.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-now-closes-without-squeezing-the-canvas-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-new-design-connect-ai-setup-now-lines-up-with-the-compos.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-screen-overview-board-now-blends-into-the-canvas-and-new.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-drops-now-show-screen-guides.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-keep-a-transparent-backdrop-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-select-reliably-from-all-screens.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-edit-can-open-and-refresh-localhost-screens-in-one-au.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +15 -4
- package/corpus/templates/design/server/db/index.ts +52 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/design/server/plugins/auth.ts +1 -0
- package/corpus/templates/design/server/plugins/core-routes.ts +4 -0
- package/corpus/templates/design/shared/board-file.ts +380 -5
- package/corpus/templates/design/shared/board-objects.ts +2 -2
- package/corpus/templates/design/shared/code-layer.ts +50 -6
- package/corpus/templates/design/shared/motion-compiler.ts +27 -0
- package/corpus/templates/macros/app/components/DailyProgress.tsx +41 -37
- package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
- package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
- package/corpus/templates/macros/app/components/VoiceDictation.tsx +1 -1
- package/corpus/templates/macros/app/components/WeeklyCaloriesChart.tsx +1 -1
- package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
- package/corpus/templates/macros/app/components/WeightTracker.tsx +1 -1
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -1
- package/corpus/templates/macros/app/routes/_index.tsx +5 -5
- package/corpus/templates/macros/app/routes/analytics.tsx +1 -1
- package/corpus/templates/macros/changelog/2026-06-30-dashboard-loading-and-chart-surfaces-now-use-theme-aware-col.md +6 -0
- package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +14 -11
- package/corpus/templates/mail/app/global.css +3 -2
- package/corpus/templates/mail/changelog/2026-06-30-compose-floating-toolbars-now-use-theme-aware-colors-in-ligh.md +6 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +2 -2
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +3 -3
- package/corpus/templates/slides/app/global.css +10 -0
- package/corpus/templates/slides/app/pages/Index.tsx +55 -51
- package/corpus/templates/slides/changelog/2026-06-30-deck-thumbnails-collapse-cleanly-to-one-column-when-the-chat.md +6 -0
- package/dist/agent/engine/anthropic-engine.d.ts +2 -2
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts +2 -2
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builtin.js +1 -1
- package/dist/agent/engine/builtin.js.map +1 -1
- package/dist/agent/engine/output-tokens.js +1 -1
- package/dist/agent/engine/output-tokens.js.map +1 -1
- package/dist/agent/harness/acp-adapter.d.ts +4 -1
- package/dist/agent/harness/acp-adapter.d.ts.map +1 -1
- package/dist/agent/harness/acp-adapter.js +19 -2
- package/dist/agent/harness/acp-adapter.js.map +1 -1
- package/dist/agent/harness/ai-sdk-adapter.d.ts.map +1 -1
- package/dist/agent/harness/ai-sdk-adapter.js +3 -0
- package/dist/agent/harness/ai-sdk-adapter.js.map +1 -1
- package/dist/agent/harness/translate.d.ts.map +1 -1
- package/dist/agent/harness/translate.js +3 -0
- package/dist/agent/harness/translate.js.map +1 -1
- package/dist/agent/harness/types.d.ts +1 -0
- package/dist/agent/harness/types.d.ts.map +1 -1
- package/dist/agent/harness/types.js.map +1 -1
- package/dist/agent/model-config.d.ts +21 -20
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +31 -13
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +85 -10
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +27 -6
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/agent/tool-call-journal.d.ts +11 -9
- package/dist/agent/tool-call-journal.d.ts.map +1 -1
- package/dist/agent/tool-call-journal.js +92 -40
- package/dist/agent/tool-call-journal.js.map +1 -1
- package/dist/agent/types.d.ts +5 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +84 -0
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/gateway-helpers.d.ts +4 -0
- package/dist/cli/gateway-helpers.d.ts.map +1 -1
- package/dist/cli/gateway-helpers.js +9 -0
- package/dist/cli/gateway-helpers.js.map +1 -1
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +0 -2
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +54 -16
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/workspace-dev.d.ts.map +1 -1
- package/dist/cli/workspace-dev.js +33 -6
- package/dist/cli/workspace-dev.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +72 -26
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/CommandMenu.d.ts +6 -2
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +25 -14
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/DefaultSpinner.d.ts.map +1 -1
- package/dist/client/DefaultSpinner.js +8 -1
- package/dist/client/DefaultSpinner.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +7 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +37 -1
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/repo-helpers.d.ts +1 -0
- package/dist/client/chat/repo-helpers.d.ts.map +1 -1
- package/dist/client/chat/repo-helpers.js +59 -0
- package/dist/client/chat/repo-helpers.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +4 -4
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/components/ApiKeySettings.js +8 -8
- package/dist/client/components/ApiKeySettings.js.map +1 -1
- package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
- package/dist/client/components/CodeRequiredDialog.js +24 -15
- package/dist/client/components/CodeRequiredDialog.js.map +1 -1
- package/dist/client/components/MissingKeyCard.d.ts.map +1 -1
- package/dist/client/components/MissingKeyCard.js +11 -6
- package/dist/client/components/MissingKeyCard.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +4 -3
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +6 -6
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/integrations/IntegrationCard.js +1 -1
- package/dist/client/integrations/IntegrationCard.js.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.js +1 -1
- package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
- package/dist/client/onboarding/OnboardingBanner.js +2 -2
- package/dist/client/onboarding/OnboardingBanner.js.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.js +23 -19
- package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
- package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
- package/dist/client/resources/ResourceEditor.js +13 -10
- package/dist/client/resources/ResourceEditor.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +2 -1
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +8 -3
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +6 -38
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/useProductionAgent.d.ts +2 -1
- package/dist/client/useProductionAgent.d.ts.map +1 -1
- package/dist/client/useProductionAgent.js.map +1 -1
- package/dist/deploy/build.js +1 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js.map +1 -1
- package/dist/onboarding/default-steps.js +1 -1
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +3 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +134 -72
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +3 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +4 -1
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/open-route.d.ts +9 -0
- package/dist/server/open-route.d.ts.map +1 -1
- package/dist/server/open-route.js +48 -38
- package/dist/server/open-route.js.map +1 -1
- package/dist/shared/reasoning-effort.js +3 -0
- package/dist/shared/reasoning-effort.js.map +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +18 -2
- package/dist/sharing/access.js.map +1 -1
- package/dist/sharing/registry.d.ts +10 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +1 -1
- package/dist/styles/agent-native.css +104 -20
- package/dist/styles/rich-markdown-editor.css +11 -10
- package/docs/content/cloneable-saas.mdx +2 -2
- package/docs/content/faq.mdx +1 -1
- package/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
- package/docs/content/locales/ar-SA/faq.mdx +1 -1
- package/docs/content/locales/ar-SA/template-chat.mdx +1 -1
- package/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
- package/docs/content/locales/de-DE/faq.mdx +1 -1
- package/docs/content/locales/de-DE/template-chat.mdx +1 -1
- package/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
- package/docs/content/locales/es-ES/faq.mdx +1 -1
- package/docs/content/locales/es-ES/template-chat.mdx +1 -1
- package/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
- package/docs/content/locales/fr-FR/faq.mdx +1 -1
- package/docs/content/locales/fr-FR/template-chat.mdx +1 -1
- package/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
- package/docs/content/locales/hi-IN/faq.mdx +1 -1
- package/docs/content/locales/hi-IN/template-chat.mdx +1 -1
- package/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
- package/docs/content/locales/ja-JP/faq.mdx +1 -1
- package/docs/content/locales/ja-JP/template-chat.mdx +1 -1
- package/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
- package/docs/content/locales/ko-KR/faq.mdx +1 -1
- package/docs/content/locales/ko-KR/template-chat.mdx +1 -1
- package/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
- package/docs/content/locales/pt-BR/faq.mdx +1 -1
- package/docs/content/locales/pt-BR/template-chat.mdx +1 -1
- package/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
- package/docs/content/locales/zh-CN/faq.mdx +1 -1
- package/docs/content/locales/zh-CN/template-chat.mdx +1 -1
- package/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
- package/docs/content/locales/zh-TW/faq.mdx +1 -1
- package/docs/content/locales/zh-TW/template-chat.mdx +1 -1
- package/docs/content/template-chat.mdx +1 -1
- package/package.json +1 -1
- package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +0 -157
- package/corpus/templates/design/app/lib/design-templates.ts +0 -1305
- package/corpus/templates/design/app/pages/Templates.tsx +0 -279
|
@@ -52,11 +52,14 @@ import {
|
|
|
52
52
|
DesignCanvas,
|
|
53
53
|
LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT,
|
|
54
54
|
appendHitTestResponder,
|
|
55
|
+
type IframeContextMenuPayload,
|
|
56
|
+
type IframeHotkeyPayload,
|
|
55
57
|
} from "./DesignCanvas";
|
|
56
58
|
import {
|
|
57
59
|
DEVICE_FRAME_VIEWPORTS,
|
|
58
60
|
type DeviceFrameType,
|
|
59
61
|
type ElementInfo,
|
|
62
|
+
type ElementSelectionIntent,
|
|
60
63
|
} from "./types";
|
|
61
64
|
|
|
62
65
|
// Re-export so consumers of MultiScreenCanvas can use the same script without
|
|
@@ -78,6 +81,7 @@ interface ScreenFile {
|
|
|
78
81
|
height?: number;
|
|
79
82
|
url?: string;
|
|
80
83
|
previewUrl?: string;
|
|
84
|
+
bridgeUrl?: string;
|
|
81
85
|
/**
|
|
82
86
|
* When set, renders multiple side-by-side breakpoint frames (mobile-first,
|
|
83
87
|
* §6.4). Each entry is a pixel width; the active breakpoint determines the
|
|
@@ -90,7 +94,7 @@ interface ScreenFile {
|
|
|
90
94
|
|
|
91
95
|
type ScreenSourceType = "localhost" | "fusion" | "inline";
|
|
92
96
|
type ScreenPreviewState = "live" | "snapshot" | "preview";
|
|
93
|
-
type MultiScreenCanvasTool =
|
|
97
|
+
export type MultiScreenCanvasTool =
|
|
94
98
|
| "move"
|
|
95
99
|
| "frame"
|
|
96
100
|
| "rect"
|
|
@@ -142,6 +146,7 @@ interface ScreenMetadata {
|
|
|
142
146
|
height?: number;
|
|
143
147
|
url?: string;
|
|
144
148
|
previewUrl?: string;
|
|
149
|
+
bridgeUrl?: string;
|
|
145
150
|
}
|
|
146
151
|
|
|
147
152
|
interface DuplicateRequest {
|
|
@@ -213,6 +218,11 @@ interface MultiScreenCanvasProps {
|
|
|
213
218
|
widthPx: number | undefined,
|
|
214
219
|
) => void;
|
|
215
220
|
onSelectionChange?: (selectedIds: string[]) => void;
|
|
221
|
+
onLayerMarqueeSelectionChange?: (
|
|
222
|
+
selection: CanvasLayerMarqueeSelection[],
|
|
223
|
+
intent: ElementSelectionIntent,
|
|
224
|
+
) => void;
|
|
225
|
+
selectedLayerSelectorGroupsByScreen?: Record<string, string[][]>;
|
|
216
226
|
/**
|
|
217
227
|
* Called when the user drags an element out of the active screen's iframe
|
|
218
228
|
* and drops it onto a different screen. The bridge in the source iframe
|
|
@@ -231,6 +241,12 @@ interface MultiScreenCanvasProps {
|
|
|
231
241
|
targetAnchorNodeId?: string;
|
|
232
242
|
/** DOM insertion placement relative to the anchor node. */
|
|
233
243
|
targetAnchorPlacement?: "before" | "after" | "inside";
|
|
244
|
+
/** Final drop point in logical overview canvas coordinates. */
|
|
245
|
+
targetCanvasPoint?: Point;
|
|
246
|
+
/** Final drop point in the destination iframe/content coordinate space. */
|
|
247
|
+
targetLocalPoint?: Point;
|
|
248
|
+
/** Pointer offset from the dragged element's top-left in source iframe px. */
|
|
249
|
+
sourcePointerOffset?: Point;
|
|
234
250
|
}) => void;
|
|
235
251
|
// ── Board file (new model) ───────────────────────────────────────────────
|
|
236
252
|
/**
|
|
@@ -264,16 +280,51 @@ interface MultiScreenCanvasProps {
|
|
|
264
280
|
* Pass `canEditDesign` from DesignEditor. Defaults to false.
|
|
265
281
|
*/
|
|
266
282
|
boardEditMode?: boolean;
|
|
283
|
+
/**
|
|
284
|
+
* When true the board is the active surface (activeFileId === boardFileId),
|
|
285
|
+
* so the board <DesignCanvas> owns the global window runtime bridge
|
|
286
|
+
* (`registerRuntimeBridge={boardIsActive}`). This mirrors how the active
|
|
287
|
+
* screen owns the bridge in single/overview mode: at most one surface
|
|
288
|
+
* registers the global `window.__designCanvas*` helpers at a time
|
|
289
|
+
* (active screen XOR active board, since `activeFileId` is exclusive), so
|
|
290
|
+
* in-place ops — delete removal, begin-text-edit — reach the board exactly
|
|
291
|
+
* like a screen. DesignEditor passes `activeFileId === boardFileId`.
|
|
292
|
+
* Defaults to false.
|
|
293
|
+
*/
|
|
294
|
+
boardIsActive?: boolean;
|
|
267
295
|
/**
|
|
268
296
|
* Called when the user selects an element on the board surface.
|
|
269
297
|
* DesignEditor should set boardFileId as the active file and push the
|
|
270
298
|
* selection to the inspector.
|
|
271
299
|
*/
|
|
272
|
-
onBoardElementSelect?: (
|
|
300
|
+
onBoardElementSelect?: (
|
|
301
|
+
info: ElementInfo,
|
|
302
|
+
intent?: ElementSelectionIntent,
|
|
303
|
+
) => void;
|
|
304
|
+
onBoardElementMarqueeSelect?: (
|
|
305
|
+
infos: ElementInfo[],
|
|
306
|
+
intent?: ElementSelectionIntent,
|
|
307
|
+
) => void;
|
|
273
308
|
/**
|
|
274
309
|
* Called when the user hovers an element on the board surface.
|
|
275
310
|
*/
|
|
276
311
|
onBoardElementHover?: (info: ElementInfo | null) => void;
|
|
312
|
+
onBoardElementClear?: () => void;
|
|
313
|
+
onBoardElementDblClickText?: (info: ElementInfo) => void;
|
|
314
|
+
onBoardIframeHotkey?: (event: IframeHotkeyPayload) => void;
|
|
315
|
+
onBoardIframeContextMenu?: (event: IframeContextMenuPayload) => void;
|
|
316
|
+
onBoardTextEditingStateChange?: (state: {
|
|
317
|
+
active: boolean;
|
|
318
|
+
selector?: string;
|
|
319
|
+
hasRange?: boolean;
|
|
320
|
+
}) => void;
|
|
321
|
+
boardClearSelectionRequest?: number;
|
|
322
|
+
boardSelectedSelector?: string | null;
|
|
323
|
+
boardSelectedSelectorCandidates?: string[];
|
|
324
|
+
boardHoveredSelector?: string | null;
|
|
325
|
+
boardHoveredSelectorCandidates?: string[];
|
|
326
|
+
boardLockedSelectors?: string[];
|
|
327
|
+
boardHiddenSelectors?: string[];
|
|
277
328
|
/**
|
|
278
329
|
* Called when a drag / reorder / reparent / drop-into-container or delete
|
|
279
330
|
* occurs on a board element. Target file is boardFileId.
|
|
@@ -391,6 +442,245 @@ export function isDirectScreenHoverTarget(
|
|
|
391
442
|
return !!element && !element.closest("[data-screen-content]");
|
|
392
443
|
}
|
|
393
444
|
|
|
445
|
+
export function shouldBoardSurfaceCapturePointerEvents(args: {
|
|
446
|
+
tool: MultiScreenCanvasTool | string;
|
|
447
|
+
gestureActive?: boolean;
|
|
448
|
+
}) {
|
|
449
|
+
if (args.gestureActive) return false;
|
|
450
|
+
const tool = normalizeCanvasTool(args.tool as MultiScreenCanvasTool);
|
|
451
|
+
return (
|
|
452
|
+
!getDraftCreationTool(tool) &&
|
|
453
|
+
tool !== "hand" &&
|
|
454
|
+
tool !== "comment" &&
|
|
455
|
+
tool !== "draw"
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export function getBoardSurfaceLayerStyle(args: {
|
|
460
|
+
geometry: FrameGeometry;
|
|
461
|
+
interactive: boolean;
|
|
462
|
+
}): CSSProperties {
|
|
463
|
+
return {
|
|
464
|
+
position: "absolute",
|
|
465
|
+
left: SURFACE_PADDING + args.geometry.x,
|
|
466
|
+
top: SURFACE_PADDING + args.geometry.y,
|
|
467
|
+
width: args.geometry.width,
|
|
468
|
+
height: args.geometry.height,
|
|
469
|
+
overflow: "hidden",
|
|
470
|
+
pointerEvents: args.interactive ? "auto" : "none",
|
|
471
|
+
background: "transparent",
|
|
472
|
+
zIndex: 0,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function hasBoardSurfaceContent(html: string | undefined) {
|
|
477
|
+
if (!html) return false;
|
|
478
|
+
const bodyMatch = html.match(/<body\b[^>]*>([\s\S]*?)<\/body>/i);
|
|
479
|
+
const content = bodyMatch?.[1] ?? html;
|
|
480
|
+
return content.replace(/<!--[\s\S]*?-->/g, "").trim().length > 0;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const BOARD_SURFACE_RENDER_STYLE = `<style data-agent-native-board-surface-render>html,body{background:transparent!important;background-color:transparent!important;background-image:none!important;}body{margin:0!important;position:relative;overflow:visible;}body>:not([data-agent-native-node-id]):not(style):not(script),body>[data-agent-native-node-id]:not([data-an-primitive]):has([data-agent-native-node-id]),body>[data-agent-native-node-id="body"],body>[data-agent-native-node-id="Body"],body>[data-agent-native-layer-name="body"],body>[data-agent-native-layer-name="Body"],body>[data-agent-native-layer-name="<body>"]{background:transparent!important;background-color:transparent!important;background-image:none!important;box-shadow:none!important;}[data-agent-native-board-backdrop-candidate="true"]{display:none!important;pointer-events:none!important;}</style>`;
|
|
484
|
+
const BOARD_SURFACE_BACKGROUND = "hsl(0 0% 10%)";
|
|
485
|
+
|
|
486
|
+
const BOARD_SURFACE_BACKDROP_MIN_EDGE_PX = 2400;
|
|
487
|
+
const BOARD_SURFACE_BACKDROP_MIN_AREA_PX = 8_000_000;
|
|
488
|
+
const HTML_VOID_TAGS = new Set([
|
|
489
|
+
"area",
|
|
490
|
+
"base",
|
|
491
|
+
"br",
|
|
492
|
+
"col",
|
|
493
|
+
"embed",
|
|
494
|
+
"hr",
|
|
495
|
+
"img",
|
|
496
|
+
"input",
|
|
497
|
+
"link",
|
|
498
|
+
"meta",
|
|
499
|
+
"param",
|
|
500
|
+
"source",
|
|
501
|
+
"track",
|
|
502
|
+
"wbr",
|
|
503
|
+
]);
|
|
504
|
+
const HTML_TAG_RE = /<!--[\s\S]*?-->|<\/?([a-zA-Z][\w:-]*)([^<>]*?)\/?>/g;
|
|
505
|
+
|
|
506
|
+
function getHtmlAttributeValue(tag: string, name: string) {
|
|
507
|
+
const match = tag.match(
|
|
508
|
+
new RegExp(
|
|
509
|
+
`\\s${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>]+))`,
|
|
510
|
+
"i",
|
|
511
|
+
),
|
|
512
|
+
);
|
|
513
|
+
return match?.[1] ?? match?.[2] ?? match?.[3] ?? "";
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function getCssDeclarationValue(style: string, name: string) {
|
|
517
|
+
const match = style.match(
|
|
518
|
+
new RegExp(`(?:^|;)\\s*${name}\\s*:\\s*([^;]+)`, "i"),
|
|
519
|
+
);
|
|
520
|
+
return match?.[1]?.trim() ?? "";
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function getCssPixelValue(style: string, name: string) {
|
|
524
|
+
const value = getCssDeclarationValue(style, name);
|
|
525
|
+
const match = value.match(/^(-?\d+(?:\.\d+)?)px$/i);
|
|
526
|
+
if (!match?.[1]) return null;
|
|
527
|
+
const parsed = Number(match[1]);
|
|
528
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function parseCssColor(value: string) {
|
|
532
|
+
const trimmed = value.trim().toLowerCase();
|
|
533
|
+
if (!trimmed || trimmed === "transparent") return null;
|
|
534
|
+
const rgb = trimmed.match(
|
|
535
|
+
/^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(\d+(?:\.\d+)?))?\s*\)$/,
|
|
536
|
+
);
|
|
537
|
+
if (rgb?.[1] && rgb[2] && rgb[3]) {
|
|
538
|
+
const alpha = rgb[4] === undefined ? 1 : Number(rgb[4]);
|
|
539
|
+
if (alpha <= 0) return null;
|
|
540
|
+
return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])] as const;
|
|
541
|
+
}
|
|
542
|
+
const hex = trimmed.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
|
|
543
|
+
if (!hex?.[1]) return null;
|
|
544
|
+
const raw = hex[1];
|
|
545
|
+
const full =
|
|
546
|
+
raw.length === 3
|
|
547
|
+
? raw
|
|
548
|
+
.split("")
|
|
549
|
+
.map((part) => part + part)
|
|
550
|
+
.join("")
|
|
551
|
+
: raw;
|
|
552
|
+
return [
|
|
553
|
+
Number.parseInt(full.slice(0, 2), 16),
|
|
554
|
+
Number.parseInt(full.slice(2, 4), 16),
|
|
555
|
+
Number.parseInt(full.slice(4, 6), 16),
|
|
556
|
+
] as const;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function isNeutralBackdropColor(value: string) {
|
|
560
|
+
const color = parseCssColor(value);
|
|
561
|
+
if (!color) return false;
|
|
562
|
+
const max = Math.max(...color);
|
|
563
|
+
const min = Math.min(...color);
|
|
564
|
+
return min >= 180 && max - min <= 24;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function isAccidentalBoardBackdropTag(tag: string) {
|
|
568
|
+
if (
|
|
569
|
+
getHtmlAttributeValue(tag, "data-agent-native-board-backdrop-candidate")
|
|
570
|
+
) {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
const primitive = getHtmlAttributeValue(
|
|
574
|
+
tag,
|
|
575
|
+
"data-an-primitive",
|
|
576
|
+
).toLowerCase();
|
|
577
|
+
if (primitive !== "rectangle" && primitive !== "rect") return false;
|
|
578
|
+
const style = getHtmlAttributeValue(tag, "style");
|
|
579
|
+
if (!style) return false;
|
|
580
|
+
const width = getCssPixelValue(style, "width");
|
|
581
|
+
const height = getCssPixelValue(style, "height");
|
|
582
|
+
if (width === null || height === null) return false;
|
|
583
|
+
if (
|
|
584
|
+
width < BOARD_SURFACE_BACKDROP_MIN_EDGE_PX ||
|
|
585
|
+
height < BOARD_SURFACE_BACKDROP_MIN_EDGE_PX ||
|
|
586
|
+
width * height < BOARD_SURFACE_BACKDROP_MIN_AREA_PX
|
|
587
|
+
) {
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
const background =
|
|
591
|
+
getCssDeclarationValue(style, "background-color") ||
|
|
592
|
+
getCssDeclarationValue(style, "background");
|
|
593
|
+
return isNeutralBackdropColor(background);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function markAccidentalBoardBackdropCandidates(html: string) {
|
|
597
|
+
return html.replace(HTML_TAG_RE, (tag: string, tagName?: string) => {
|
|
598
|
+
if (!tagName || tag.startsWith("</")) return tag;
|
|
599
|
+
if (!isAccidentalBoardBackdropTag(tag)) return tag;
|
|
600
|
+
return tag.replace(
|
|
601
|
+
/\/?>$/,
|
|
602
|
+
(ending) => ` data-agent-native-board-backdrop-candidate="true"${ending}`,
|
|
603
|
+
);
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function findLastHtmlStackTagIndex(
|
|
608
|
+
stack: Array<{ tagName: string; nodeId: string }>,
|
|
609
|
+
tagName: string,
|
|
610
|
+
) {
|
|
611
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
612
|
+
if (stack[i]?.tagName === tagName) return i;
|
|
613
|
+
}
|
|
614
|
+
return -1;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function getCurrentLayerParentNodeId(
|
|
618
|
+
stack: Array<{ tagName: string; nodeId: string }>,
|
|
619
|
+
) {
|
|
620
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
621
|
+
const nodeId = stack[i]?.nodeId;
|
|
622
|
+
if (nodeId) return nodeId;
|
|
623
|
+
}
|
|
624
|
+
return "body";
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export function getBoardSurfaceRenderContent(html: string) {
|
|
628
|
+
if (!html) return html;
|
|
629
|
+
const renderHtml = markAccidentalBoardBackdropCandidates(html);
|
|
630
|
+
if (renderHtml.includes("data-agent-native-board-surface-render")) {
|
|
631
|
+
return renderHtml;
|
|
632
|
+
}
|
|
633
|
+
if (/<\/head>/i.test(html)) {
|
|
634
|
+
return renderHtml.replace(
|
|
635
|
+
/<\/head>/i,
|
|
636
|
+
`${BOARD_SURFACE_RENDER_STYLE}</head>`,
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
if (/<body\b/i.test(html)) {
|
|
640
|
+
return renderHtml.replace(/<body\b/i, `${BOARD_SURFACE_RENDER_STYLE}<body`);
|
|
641
|
+
}
|
|
642
|
+
return `${BOARD_SURFACE_RENDER_STYLE}${renderHtml}`;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export function getBoardContentLayerSignature(html: string) {
|
|
646
|
+
const layers: string[] = [];
|
|
647
|
+
const stack: Array<{ tagName: string; nodeId: string }> = [];
|
|
648
|
+
const childCountsByParent = new Map<string, number>();
|
|
649
|
+
|
|
650
|
+
for (const match of html.matchAll(HTML_TAG_RE)) {
|
|
651
|
+
const token = match[0];
|
|
652
|
+
const tagName = match[1]?.toLowerCase();
|
|
653
|
+
if (!tagName) continue;
|
|
654
|
+
|
|
655
|
+
if (token.startsWith("</")) {
|
|
656
|
+
const index = findLastHtmlStackTagIndex(stack, tagName);
|
|
657
|
+
if (index >= 0) stack.splice(index);
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const nodeId = getHtmlAttributeValue(token, "data-agent-native-node-id");
|
|
662
|
+
if (nodeId) {
|
|
663
|
+
const parentNodeId = getCurrentLayerParentNodeId(stack);
|
|
664
|
+
const childIndex = childCountsByParent.get(parentNodeId) ?? 0;
|
|
665
|
+
childCountsByParent.set(parentNodeId, childIndex + 1);
|
|
666
|
+
layers.push(`${nodeId}<${parentNodeId}#${childIndex}`);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const selfClosing = token.endsWith("/>") || HTML_VOID_TAGS.has(tagName);
|
|
670
|
+
if (!selfClosing) stack.push({ tagName, nodeId });
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return `${layers.length}:${hashString(layers.join("\n"))}`;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export function getBoardContentKey(args: {
|
|
677
|
+
boardFileId: string;
|
|
678
|
+
boardFileContent: string;
|
|
679
|
+
boardIsActive: boolean;
|
|
680
|
+
}) {
|
|
681
|
+
return `${args.boardFileId}:layers:${getBoardContentLayerSignature(args.boardFileContent)}`;
|
|
682
|
+
}
|
|
683
|
+
|
|
394
684
|
function getChromeHandleTransition(chromeSettling: boolean) {
|
|
395
685
|
return chromeSettling
|
|
396
686
|
? CHROME_HANDLE_SETTLE_TRANSITION
|
|
@@ -454,6 +744,152 @@ export interface Point {
|
|
|
454
744
|
y: number;
|
|
455
745
|
}
|
|
456
746
|
|
|
747
|
+
export type CrossScreenDropPlacement = "before" | "after" | "inside";
|
|
748
|
+
export type CrossScreenDropAxis = "x" | "y";
|
|
749
|
+
|
|
750
|
+
export interface CrossScreenHitTestAnchorRect {
|
|
751
|
+
left: number;
|
|
752
|
+
top: number;
|
|
753
|
+
width: number;
|
|
754
|
+
height: number;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export interface CrossScreenHitTestResult {
|
|
758
|
+
anchorNodeId?: string;
|
|
759
|
+
placement?: CrossScreenDropPlacement;
|
|
760
|
+
axis?: CrossScreenDropAxis;
|
|
761
|
+
anchorRect?: CrossScreenHitTestAnchorRect;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
interface CrossScreenDragElementRect {
|
|
765
|
+
left: number;
|
|
766
|
+
top: number;
|
|
767
|
+
width: number;
|
|
768
|
+
height: number;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function isFinitePoint(value: unknown): value is Point {
|
|
772
|
+
if (!value || typeof value !== "object") return false;
|
|
773
|
+
const point = value as Record<string, unknown>;
|
|
774
|
+
return Number.isFinite(point.x) && Number.isFinite(point.y);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
interface CanvasLayerMarqueeCandidate {
|
|
778
|
+
screenId: string;
|
|
779
|
+
info: ElementInfo;
|
|
780
|
+
geometry: FrameGeometry;
|
|
781
|
+
frameGeometry: FrameGeometry;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
export interface CanvasLayerMarqueeSelection {
|
|
785
|
+
screenId: string;
|
|
786
|
+
info: ElementInfo;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
export interface CrossScreenDropGuide {
|
|
790
|
+
placement: CrossScreenDropPlacement;
|
|
791
|
+
axis: CrossScreenDropAxis;
|
|
792
|
+
boardRect: FrameGeometry;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function isCrossScreenDropPlacement(
|
|
796
|
+
value: unknown,
|
|
797
|
+
): value is CrossScreenDropPlacement {
|
|
798
|
+
return value === "before" || value === "after" || value === "inside";
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function isCrossScreenDropAxis(value: unknown): value is CrossScreenDropAxis {
|
|
802
|
+
return value === "x" || value === "y";
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function isCrossScreenHitTestAnchorRect(
|
|
806
|
+
value: unknown,
|
|
807
|
+
): value is CrossScreenHitTestAnchorRect {
|
|
808
|
+
if (!value || typeof value !== "object") return false;
|
|
809
|
+
const rect = value as Record<string, unknown>;
|
|
810
|
+
return (
|
|
811
|
+
Number.isFinite(rect.left) &&
|
|
812
|
+
Number.isFinite(rect.top) &&
|
|
813
|
+
Number.isFinite(rect.width) &&
|
|
814
|
+
Number.isFinite(rect.height)
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
export function getCrossScreenDropGuideForHitTest(args: {
|
|
819
|
+
hit: CrossScreenHitTestResult;
|
|
820
|
+
targetGeometry: FrameGeometry;
|
|
821
|
+
targetMetadata: { width: number; height: number };
|
|
822
|
+
}): CrossScreenDropGuide | null {
|
|
823
|
+
const rect = args.hit.anchorRect;
|
|
824
|
+
if (!rect) return null;
|
|
825
|
+
const placement = args.hit.placement ?? "inside";
|
|
826
|
+
const axis = args.hit.axis ?? "y";
|
|
827
|
+
const scaleX =
|
|
828
|
+
args.targetGeometry.width / Math.max(1, args.targetMetadata.width);
|
|
829
|
+
const scaleY =
|
|
830
|
+
args.targetGeometry.height / Math.max(1, args.targetMetadata.height);
|
|
831
|
+
return {
|
|
832
|
+
placement,
|
|
833
|
+
axis,
|
|
834
|
+
boardRect: {
|
|
835
|
+
x: args.targetGeometry.x + rect.left * scaleX,
|
|
836
|
+
y: args.targetGeometry.y + rect.top * scaleY,
|
|
837
|
+
width: Math.max(1, rect.width * scaleX),
|
|
838
|
+
height: Math.max(1, rect.height * scaleY),
|
|
839
|
+
},
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
export function getCrossScreenDropGuideStyle(args: {
|
|
844
|
+
guide: CrossScreenDropGuide;
|
|
845
|
+
pan: Point;
|
|
846
|
+
scale: number;
|
|
847
|
+
}): CSSProperties {
|
|
848
|
+
const { boardRect, placement, axis } = args.guide;
|
|
849
|
+
const left = args.pan.x + (SURFACE_PADDING + boardRect.x) * args.scale;
|
|
850
|
+
const top = args.pan.y + (SURFACE_PADDING + boardRect.y) * args.scale;
|
|
851
|
+
const width = Math.max(1, boardRect.width * args.scale);
|
|
852
|
+
const height = Math.max(1, boardRect.height * args.scale);
|
|
853
|
+
|
|
854
|
+
if (placement === "inside") {
|
|
855
|
+
return {
|
|
856
|
+
left,
|
|
857
|
+
top,
|
|
858
|
+
width,
|
|
859
|
+
height,
|
|
860
|
+
border: "2px solid var(--design-editor-accent-color)",
|
|
861
|
+
background:
|
|
862
|
+
"color-mix(in srgb, var(--design-editor-accent-color) 14%, transparent)",
|
|
863
|
+
borderRadius: 2,
|
|
864
|
+
boxShadow: "none",
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (axis === "x") {
|
|
869
|
+
const x = placement === "before" ? left : left + width;
|
|
870
|
+
return {
|
|
871
|
+
left: x - 1,
|
|
872
|
+
top,
|
|
873
|
+
width: 2,
|
|
874
|
+
height: Math.max(8, height),
|
|
875
|
+
background: "var(--design-editor-accent-color)",
|
|
876
|
+
borderRadius: 999,
|
|
877
|
+
boxShadow: "0 0 0 1px var(--design-editor-accent-color)",
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const y = placement === "before" ? top : top + height;
|
|
882
|
+
return {
|
|
883
|
+
left,
|
|
884
|
+
top: y - 1,
|
|
885
|
+
width: Math.max(8, width),
|
|
886
|
+
height: 2,
|
|
887
|
+
background: "var(--design-editor-accent-color)",
|
|
888
|
+
borderRadius: 999,
|
|
889
|
+
boxShadow: "0 0 0 1px var(--design-editor-accent-color)",
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
457
893
|
export type DraftPrimitiveKind =
|
|
458
894
|
| "frame"
|
|
459
895
|
| "rectangle"
|
|
@@ -498,7 +934,6 @@ interface DraftPrimitiveInput {
|
|
|
498
934
|
points?: Point[];
|
|
499
935
|
moved: boolean;
|
|
500
936
|
toolProps?: CanvasToolProps;
|
|
501
|
-
fallbackText: string;
|
|
502
937
|
}
|
|
503
938
|
|
|
504
939
|
interface MarqueeRect {
|
|
@@ -664,14 +1099,30 @@ export function MultiScreenCanvas({
|
|
|
664
1099
|
onAddBreakpoint,
|
|
665
1100
|
onActiveBreakpointChange,
|
|
666
1101
|
onSelectionChange,
|
|
1102
|
+
onLayerMarqueeSelectionChange,
|
|
1103
|
+
selectedLayerSelectorGroupsByScreen = {},
|
|
667
1104
|
onCrossScreenElementDrop,
|
|
668
1105
|
boardFileId,
|
|
669
1106
|
boardFileContent,
|
|
670
1107
|
boardFrameGeometry,
|
|
671
1108
|
onBoardDrawPrimitive,
|
|
672
1109
|
boardEditMode = false,
|
|
1110
|
+
boardIsActive = false,
|
|
673
1111
|
onBoardElementSelect,
|
|
1112
|
+
onBoardElementMarqueeSelect,
|
|
674
1113
|
onBoardElementHover,
|
|
1114
|
+
onBoardElementClear,
|
|
1115
|
+
onBoardElementDblClickText,
|
|
1116
|
+
onBoardIframeHotkey,
|
|
1117
|
+
onBoardIframeContextMenu,
|
|
1118
|
+
onBoardTextEditingStateChange,
|
|
1119
|
+
boardClearSelectionRequest,
|
|
1120
|
+
boardSelectedSelector,
|
|
1121
|
+
boardSelectedSelectorCandidates,
|
|
1122
|
+
boardHoveredSelector,
|
|
1123
|
+
boardHoveredSelectorCandidates,
|
|
1124
|
+
boardLockedSelectors,
|
|
1125
|
+
boardHiddenSelectors,
|
|
675
1126
|
onBoardVisualStructureChange,
|
|
676
1127
|
onBoardVisualStyleChange,
|
|
677
1128
|
onBoardVisualDuplicateChange,
|
|
@@ -742,13 +1193,21 @@ export function MultiScreenCanvas({
|
|
|
742
1193
|
useState<CrossScreenDragGhost | null>(null);
|
|
743
1194
|
const [crossScreenTarget, setCrossScreenTarget] =
|
|
744
1195
|
useState<CrossScreenDragTarget | null>(null);
|
|
1196
|
+
const [crossScreenDropGuide, setCrossScreenDropGuide] =
|
|
1197
|
+
useState<CrossScreenDropGuide | null>(null);
|
|
1198
|
+
const [crossScreenSourceIsBoard, setCrossScreenSourceIsBoard] =
|
|
1199
|
+
useState(false);
|
|
745
1200
|
/** Ref kept in sync with state so the message handler can read without closures. */
|
|
746
1201
|
const crossScreenTargetRef = useRef<CrossScreenDragTarget | null>(null);
|
|
1202
|
+
const crossScreenHitTestSeqRef = useRef(0);
|
|
1203
|
+
const crossScreenPreviewTargetIdRef = useRef<string | null>(null);
|
|
747
1204
|
/** The most-recent drag message payload — kept for use in the "end" handler. */
|
|
748
1205
|
const crossScreenDragMsgRef = useRef<{
|
|
749
1206
|
selector: string;
|
|
750
1207
|
sourceId?: string;
|
|
1208
|
+
sourcePointerOffset?: Point;
|
|
751
1209
|
} | null>(null);
|
|
1210
|
+
const crossScreenParentDragCleanupRef = useRef<(() => void) | null>(null);
|
|
752
1211
|
/** Board-space point from the last cross-screen-drag "move" message. */
|
|
753
1212
|
const crossScreenLastBoardPointRef = useRef<{ x: number; y: number } | null>(
|
|
754
1213
|
null,
|
|
@@ -770,6 +1229,21 @@ export function MultiScreenCanvas({
|
|
|
770
1229
|
const [chromeSettling, setChromeSettling] = useState(false);
|
|
771
1230
|
const previousPreviewDeviceFrameRef = useRef(previewDeviceFrame);
|
|
772
1231
|
|
|
1232
|
+
const claimKeyboardFocus = useCallback(() => {
|
|
1233
|
+
const surface = surfaceRef.current;
|
|
1234
|
+
if (!surface) return;
|
|
1235
|
+
const active = document.activeElement;
|
|
1236
|
+
if (
|
|
1237
|
+
active instanceof HTMLElement &&
|
|
1238
|
+
active !== surface &&
|
|
1239
|
+
!surface.contains(active) &&
|
|
1240
|
+
isEditableHotkeyTarget(active)
|
|
1241
|
+
) {
|
|
1242
|
+
active.blur();
|
|
1243
|
+
}
|
|
1244
|
+
surface.focus({ preventScroll: true });
|
|
1245
|
+
}, []);
|
|
1246
|
+
|
|
773
1247
|
const getResolvedMetadata = useCallback(
|
|
774
1248
|
(screen: ScreenFile) =>
|
|
775
1249
|
resolveScreenMetadata(
|
|
@@ -1144,26 +1618,279 @@ export function MultiScreenCanvas({
|
|
|
1144
1618
|
useEffect(() => {
|
|
1145
1619
|
if (!onCrossScreenElementDrop) return;
|
|
1146
1620
|
|
|
1621
|
+
const clearCrossScreenDropGuide = () => {
|
|
1622
|
+
crossScreenHitTestSeqRef.current += 1;
|
|
1623
|
+
setCrossScreenDropGuide(null);
|
|
1624
|
+
};
|
|
1625
|
+
|
|
1626
|
+
const postHitTestPreviewClear = (targetId: string | null | undefined) => {
|
|
1627
|
+
if (!targetId) return;
|
|
1628
|
+
const targetIframe = surfaceRef.current?.querySelector<HTMLIFrameElement>(
|
|
1629
|
+
`[data-screen-iframe-id="${CSS.escape(targetId)}"]`,
|
|
1630
|
+
);
|
|
1631
|
+
targetIframe?.contentWindow?.postMessage(
|
|
1632
|
+
{ type: "agent-native:hit-test-preview-clear" },
|
|
1633
|
+
"*",
|
|
1634
|
+
);
|
|
1635
|
+
};
|
|
1636
|
+
|
|
1637
|
+
const clearCrossScreenPreviewGuide = (
|
|
1638
|
+
targetId?: string | null | undefined,
|
|
1639
|
+
) => {
|
|
1640
|
+
const id = targetId ?? crossScreenPreviewTargetIdRef.current;
|
|
1641
|
+
postHitTestPreviewClear(id);
|
|
1642
|
+
if (!targetId || targetId === crossScreenPreviewTargetIdRef.current) {
|
|
1643
|
+
crossScreenPreviewTargetIdRef.current = null;
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
const stopParentCrossScreenDrag = () => {
|
|
1648
|
+
crossScreenParentDragCleanupRef.current?.();
|
|
1649
|
+
crossScreenParentDragCleanupRef.current = null;
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1147
1652
|
const clearCrossScreenDrag = () => {
|
|
1653
|
+
stopParentCrossScreenDrag();
|
|
1654
|
+
clearCrossScreenPreviewGuide();
|
|
1148
1655
|
setCrossScreenGhost(null);
|
|
1149
1656
|
setCrossScreenTarget(null);
|
|
1657
|
+
setCrossScreenSourceIsBoard(false);
|
|
1658
|
+
clearCrossScreenDropGuide();
|
|
1150
1659
|
crossScreenTargetRef.current = null;
|
|
1151
1660
|
crossScreenDragMsgRef.current = null;
|
|
1152
1661
|
};
|
|
1153
1662
|
|
|
1663
|
+
const runHitTest = (
|
|
1664
|
+
candidate: CrossScreenDragTarget,
|
|
1665
|
+
boardPoint: Point,
|
|
1666
|
+
options: { preview?: boolean } = {},
|
|
1667
|
+
): Promise<CrossScreenHitTestResult> => {
|
|
1668
|
+
const targetScreen = screensRef.current.find(
|
|
1669
|
+
(s) => s.id === candidate.id,
|
|
1670
|
+
);
|
|
1671
|
+
if (!targetScreen) return Promise.resolve({});
|
|
1672
|
+
const targetGeometry = candidate.geometry;
|
|
1673
|
+
const targetIframe = surfaceRef.current?.querySelector<HTMLIFrameElement>(
|
|
1674
|
+
`[data-screen-iframe-id="${CSS.escape(candidate.id)}"]`,
|
|
1675
|
+
);
|
|
1676
|
+
const targetContentWindow = targetIframe?.contentWindow;
|
|
1677
|
+
if (!targetContentWindow) return Promise.resolve({});
|
|
1678
|
+
const targetViewportWidth =
|
|
1679
|
+
targetIframe.clientWidth || getResolvedMetadata(targetScreen).width;
|
|
1680
|
+
const targetViewportHeight =
|
|
1681
|
+
targetIframe.clientHeight || getResolvedMetadata(targetScreen).height;
|
|
1682
|
+
const scaleX = targetViewportWidth / Math.max(1, targetGeometry.width);
|
|
1683
|
+
const scaleY = targetViewportHeight / Math.max(1, targetGeometry.height);
|
|
1684
|
+
const localX = (boardPoint.x - targetGeometry.x) * scaleX;
|
|
1685
|
+
const localY = (boardPoint.y - targetGeometry.y) * scaleY;
|
|
1686
|
+
|
|
1687
|
+
const correlationId = `hit-${Date.now()}-${Math.random()
|
|
1688
|
+
.toString(36)
|
|
1689
|
+
.slice(2, 6)}`;
|
|
1690
|
+
|
|
1691
|
+
return new Promise((resolve) => {
|
|
1692
|
+
const timer = window.setTimeout(() => {
|
|
1693
|
+
window.removeEventListener("message", hitListener);
|
|
1694
|
+
resolve({});
|
|
1695
|
+
}, 50);
|
|
1696
|
+
|
|
1697
|
+
const hitListener = (ev: MessageEvent) => {
|
|
1698
|
+
if (
|
|
1699
|
+
!ev.data ||
|
|
1700
|
+
ev.data.type !== "agent-native:hit-test-result" ||
|
|
1701
|
+
ev.data.correlationId !== correlationId
|
|
1702
|
+
) {
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
window.clearTimeout(timer);
|
|
1706
|
+
window.removeEventListener("message", hitListener);
|
|
1707
|
+
resolve({
|
|
1708
|
+
anchorNodeId:
|
|
1709
|
+
typeof ev.data.anchorNodeId === "string"
|
|
1710
|
+
? ev.data.anchorNodeId
|
|
1711
|
+
: undefined,
|
|
1712
|
+
placement: isCrossScreenDropPlacement(ev.data.placement)
|
|
1713
|
+
? ev.data.placement
|
|
1714
|
+
: undefined,
|
|
1715
|
+
axis: isCrossScreenDropAxis(ev.data.axis)
|
|
1716
|
+
? ev.data.axis
|
|
1717
|
+
: undefined,
|
|
1718
|
+
anchorRect: isCrossScreenHitTestAnchorRect(ev.data.anchorRect)
|
|
1719
|
+
? ev.data.anchorRect
|
|
1720
|
+
: undefined,
|
|
1721
|
+
});
|
|
1722
|
+
};
|
|
1723
|
+
window.addEventListener("message", hitListener);
|
|
1724
|
+
|
|
1725
|
+
targetContentWindow.postMessage(
|
|
1726
|
+
{
|
|
1727
|
+
type: "agent-native:hit-test",
|
|
1728
|
+
correlationId,
|
|
1729
|
+
x: localX,
|
|
1730
|
+
y: localY,
|
|
1731
|
+
preview: options.preview === true,
|
|
1732
|
+
},
|
|
1733
|
+
"*",
|
|
1734
|
+
);
|
|
1735
|
+
if (options.preview) {
|
|
1736
|
+
crossScreenPreviewTargetIdRef.current = candidate.id;
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
};
|
|
1740
|
+
|
|
1741
|
+
const getTargetLocalPoint = (
|
|
1742
|
+
candidate: CrossScreenDragTarget,
|
|
1743
|
+
boardPoint: Point,
|
|
1744
|
+
): Point | null => {
|
|
1745
|
+
if (candidate.id === boardFileId) return boardPoint;
|
|
1746
|
+
const targetScreen = screensRef.current.find(
|
|
1747
|
+
(s) => s.id === candidate.id,
|
|
1748
|
+
);
|
|
1749
|
+
if (!targetScreen) return null;
|
|
1750
|
+
const targetIframe = surfaceRef.current?.querySelector<HTMLIFrameElement>(
|
|
1751
|
+
`[data-screen-iframe-id="${CSS.escape(candidate.id)}"]`,
|
|
1752
|
+
);
|
|
1753
|
+
const targetViewportWidth =
|
|
1754
|
+
targetIframe?.clientWidth || getResolvedMetadata(targetScreen).width;
|
|
1755
|
+
const targetViewportHeight =
|
|
1756
|
+
targetIframe?.clientHeight || getResolvedMetadata(targetScreen).height;
|
|
1757
|
+
const scaleX =
|
|
1758
|
+
targetViewportWidth / Math.max(1, candidate.geometry.width);
|
|
1759
|
+
const scaleY =
|
|
1760
|
+
targetViewportHeight / Math.max(1, candidate.geometry.height);
|
|
1761
|
+
return {
|
|
1762
|
+
x: (boardPoint.x - candidate.geometry.x) * scaleX,
|
|
1763
|
+
y: (boardPoint.y - candidate.geometry.y) * scaleY,
|
|
1764
|
+
};
|
|
1765
|
+
};
|
|
1766
|
+
|
|
1767
|
+
const requestCrossScreenDropGuide = (
|
|
1768
|
+
candidate: CrossScreenDragTarget,
|
|
1769
|
+
boardPoint: Point,
|
|
1770
|
+
sourceIsBoard: boolean,
|
|
1771
|
+
) => {
|
|
1772
|
+
const requestSeq = ++crossScreenHitTestSeqRef.current;
|
|
1773
|
+
void runHitTest(candidate, boardPoint, { preview: sourceIsBoard }).then(
|
|
1774
|
+
(hit) => {
|
|
1775
|
+
if (crossScreenHitTestSeqRef.current !== requestSeq) return;
|
|
1776
|
+
if (crossScreenTargetRef.current?.id !== candidate.id) return;
|
|
1777
|
+
const targetScreen = screensRef.current.find(
|
|
1778
|
+
(s) => s.id === candidate.id,
|
|
1779
|
+
);
|
|
1780
|
+
const guide = targetScreen
|
|
1781
|
+
? getCrossScreenDropGuideForHitTest({
|
|
1782
|
+
hit,
|
|
1783
|
+
targetGeometry: candidate.geometry,
|
|
1784
|
+
targetMetadata: getResolvedMetadata(targetScreen),
|
|
1785
|
+
})
|
|
1786
|
+
: null;
|
|
1787
|
+
setCrossScreenDropGuide(sourceIsBoard ? null : guide);
|
|
1788
|
+
},
|
|
1789
|
+
);
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
const updateCrossScreenTargetFromBoardPoint = (
|
|
1793
|
+
boardPoint: Point,
|
|
1794
|
+
sourceScreenId: string,
|
|
1795
|
+
) => {
|
|
1796
|
+
crossScreenLastBoardPointRef.current = boardPoint;
|
|
1797
|
+
const sourceIsBoard = sourceScreenId === boardFileId;
|
|
1798
|
+
setCrossScreenSourceIsBoard(sourceIsBoard);
|
|
1799
|
+
setCrossScreenGhost(
|
|
1800
|
+
sourceIsBoard ? null : { boardX: boardPoint.x, boardY: boardPoint.y },
|
|
1801
|
+
);
|
|
1802
|
+
const target = getFrameEntryAtPoint(boardPoint);
|
|
1803
|
+
if (target && target.id !== sourceScreenId) {
|
|
1804
|
+
const nextTarget = { id: target.id, geometry: target.geometry };
|
|
1805
|
+
if (crossScreenTargetRef.current?.id !== nextTarget.id) {
|
|
1806
|
+
clearCrossScreenPreviewGuide();
|
|
1807
|
+
}
|
|
1808
|
+
crossScreenTargetRef.current = nextTarget;
|
|
1809
|
+
setCrossScreenTarget(nextTarget);
|
|
1810
|
+
requestCrossScreenDropGuide(nextTarget, boardPoint, sourceIsBoard);
|
|
1811
|
+
} else {
|
|
1812
|
+
clearCrossScreenPreviewGuide();
|
|
1813
|
+
crossScreenTargetRef.current = null;
|
|
1814
|
+
setCrossScreenTarget(null);
|
|
1815
|
+
clearCrossScreenDropGuide();
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
|
|
1819
|
+
const finalizeCrossScreenDrop = (
|
|
1820
|
+
sourceScreenId: string,
|
|
1821
|
+
candidate: CrossScreenDragTarget | null,
|
|
1822
|
+
payload: {
|
|
1823
|
+
selector: string;
|
|
1824
|
+
sourceId?: string;
|
|
1825
|
+
sourcePointerOffset?: Point;
|
|
1826
|
+
},
|
|
1827
|
+
lastBoardPoint: Point | null,
|
|
1828
|
+
) => {
|
|
1829
|
+
clearCrossScreenDrag();
|
|
1830
|
+
crossScreenLastBoardPointRef.current = null;
|
|
1831
|
+
const hasIdentifier = !!(payload.selector || payload.sourceId);
|
|
1832
|
+
if (!hasIdentifier || !sourceScreenId) return;
|
|
1833
|
+
if (!lastBoardPoint) return;
|
|
1834
|
+
const targetCandidate =
|
|
1835
|
+
candidate ??
|
|
1836
|
+
(boardFileId && sourceScreenId !== boardFileId && boardFrameGeometry
|
|
1837
|
+
? { id: boardFileId, geometry: boardFrameGeometry }
|
|
1838
|
+
: null);
|
|
1839
|
+
if (!targetCandidate) return;
|
|
1840
|
+
|
|
1841
|
+
if (targetCandidate.id === boardFileId) {
|
|
1842
|
+
onCrossScreenElementDropRef.current?.({
|
|
1843
|
+
sourceSelector: payload.selector,
|
|
1844
|
+
sourceNodeId: payload.sourceId,
|
|
1845
|
+
sourceScreenId,
|
|
1846
|
+
targetScreenId: targetCandidate.id,
|
|
1847
|
+
targetCanvasPoint: lastBoardPoint,
|
|
1848
|
+
targetLocalPoint: lastBoardPoint,
|
|
1849
|
+
sourcePointerOffset: payload.sourcePointerOffset,
|
|
1850
|
+
});
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
void runHitTest(targetCandidate, lastBoardPoint).then(
|
|
1855
|
+
({ anchorNodeId, placement }) => {
|
|
1856
|
+
const targetAnchorPlacement = isCrossScreenDropPlacement(placement)
|
|
1857
|
+
? placement
|
|
1858
|
+
: undefined;
|
|
1859
|
+
const targetLocalPoint = getTargetLocalPoint(
|
|
1860
|
+
targetCandidate,
|
|
1861
|
+
lastBoardPoint,
|
|
1862
|
+
);
|
|
1863
|
+
onCrossScreenElementDropRef.current?.({
|
|
1864
|
+
sourceSelector: payload.selector,
|
|
1865
|
+
sourceNodeId: payload.sourceId,
|
|
1866
|
+
sourceScreenId,
|
|
1867
|
+
targetScreenId: targetCandidate.id,
|
|
1868
|
+
targetAnchorNodeId: anchorNodeId,
|
|
1869
|
+
targetAnchorPlacement,
|
|
1870
|
+
targetCanvasPoint: lastBoardPoint,
|
|
1871
|
+
targetLocalPoint: targetLocalPoint ?? undefined,
|
|
1872
|
+
sourcePointerOffset: payload.sourcePointerOffset,
|
|
1873
|
+
});
|
|
1874
|
+
},
|
|
1875
|
+
);
|
|
1876
|
+
};
|
|
1877
|
+
|
|
1154
1878
|
const handleMessage = (event: MessageEvent) => {
|
|
1155
1879
|
if (!event.data || event.data.type !== "agent-native:cross-screen-drag") {
|
|
1156
1880
|
return;
|
|
1157
1881
|
}
|
|
1158
1882
|
const msg = event.data as {
|
|
1159
1883
|
type: string;
|
|
1160
|
-
phase: "move" | "end" | "cancel";
|
|
1884
|
+
phase: "start" | "move" | "end" | "cancel";
|
|
1885
|
+
screenId?: string;
|
|
1161
1886
|
selector?: string;
|
|
1162
1887
|
sourceId?: string;
|
|
1163
1888
|
iframeX?: number;
|
|
1164
1889
|
iframeY?: number;
|
|
1165
1890
|
viewportW?: number;
|
|
1166
1891
|
viewportH?: number;
|
|
1892
|
+
elementRect?: CrossScreenDragElementRect;
|
|
1893
|
+
pointerOffset?: Point;
|
|
1167
1894
|
};
|
|
1168
1895
|
|
|
1169
1896
|
if (msg.phase === "cancel") {
|
|
@@ -1171,9 +1898,13 @@ export function MultiScreenCanvas({
|
|
|
1171
1898
|
return;
|
|
1172
1899
|
}
|
|
1173
1900
|
|
|
1174
|
-
//
|
|
1175
|
-
// a
|
|
1176
|
-
const sourceScreenId =
|
|
1901
|
+
// Prefer the iframe-supplied source id. In overview, activeId can point
|
|
1902
|
+
// at a different screen than the layer currently being dragged.
|
|
1903
|
+
const sourceScreenId =
|
|
1904
|
+
msg.screenId &&
|
|
1905
|
+
(msg.screenId === boardFileId || frameGeometryRef.current[msg.screenId])
|
|
1906
|
+
? msg.screenId
|
|
1907
|
+
: activeId;
|
|
1177
1908
|
if (!sourceScreenId) {
|
|
1178
1909
|
// Always clear visual state (ghost + highlight) when we have no active
|
|
1179
1910
|
// screen to attribute the drag to — regardless of the phase. Without
|
|
@@ -1183,6 +1914,69 @@ export function MultiScreenCanvas({
|
|
|
1183
1914
|
return;
|
|
1184
1915
|
}
|
|
1185
1916
|
|
|
1917
|
+
if (msg.phase === "start") {
|
|
1918
|
+
setCrossScreenSourceIsBoard(sourceScreenId === boardFileId);
|
|
1919
|
+
crossScreenDragMsgRef.current = {
|
|
1920
|
+
selector: msg.selector ?? "",
|
|
1921
|
+
sourceId: msg.sourceId,
|
|
1922
|
+
sourcePointerOffset: isFinitePoint(msg.pointerOffset)
|
|
1923
|
+
? msg.pointerOffset
|
|
1924
|
+
: undefined,
|
|
1925
|
+
};
|
|
1926
|
+
stopParentCrossScreenDrag();
|
|
1927
|
+
const restorePreviewPointerEvents = mutePreviewIframePointerEvents(
|
|
1928
|
+
surfaceRef.current,
|
|
1929
|
+
);
|
|
1930
|
+
let didCleanup = false;
|
|
1931
|
+
const activateParentDrag = (ev: MouseEvent) => {
|
|
1932
|
+
ev.preventDefault();
|
|
1933
|
+
updateCrossScreenTargetFromBoardPoint(
|
|
1934
|
+
getCanvasPoint(ev.clientX, ev.clientY),
|
|
1935
|
+
sourceScreenId,
|
|
1936
|
+
);
|
|
1937
|
+
};
|
|
1938
|
+
const handleParentMouseMove = (ev: MouseEvent) => {
|
|
1939
|
+
activateParentDrag(ev);
|
|
1940
|
+
};
|
|
1941
|
+
const handleParentMouseUp = (ev: MouseEvent) => {
|
|
1942
|
+
activateParentDrag(ev);
|
|
1943
|
+
const candidate = crossScreenTargetRef.current;
|
|
1944
|
+
const payload = crossScreenDragMsgRef.current ?? {
|
|
1945
|
+
selector: msg.selector ?? "",
|
|
1946
|
+
sourceId: msg.sourceId,
|
|
1947
|
+
sourcePointerOffset: isFinitePoint(msg.pointerOffset)
|
|
1948
|
+
? msg.pointerOffset
|
|
1949
|
+
: undefined,
|
|
1950
|
+
};
|
|
1951
|
+
const lastBoardPoint = crossScreenLastBoardPointRef.current;
|
|
1952
|
+
finalizeCrossScreenDrop(
|
|
1953
|
+
sourceScreenId,
|
|
1954
|
+
candidate,
|
|
1955
|
+
payload,
|
|
1956
|
+
lastBoardPoint,
|
|
1957
|
+
);
|
|
1958
|
+
};
|
|
1959
|
+
const handleParentWindowBlur = () => {
|
|
1960
|
+
clearCrossScreenDrag();
|
|
1961
|
+
};
|
|
1962
|
+
const cleanup = () => {
|
|
1963
|
+
if (didCleanup) return;
|
|
1964
|
+
didCleanup = true;
|
|
1965
|
+
window.removeEventListener("mousemove", handleParentMouseMove, true);
|
|
1966
|
+
window.removeEventListener("mouseup", handleParentMouseUp, true);
|
|
1967
|
+
window.removeEventListener("blur", handleParentWindowBlur, true);
|
|
1968
|
+
restorePreviewPointerEvents();
|
|
1969
|
+
if (crossScreenParentDragCleanupRef.current === cleanup) {
|
|
1970
|
+
crossScreenParentDragCleanupRef.current = null;
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
crossScreenParentDragCleanupRef.current = cleanup;
|
|
1974
|
+
window.addEventListener("mousemove", handleParentMouseMove, true);
|
|
1975
|
+
window.addEventListener("mouseup", handleParentMouseUp, true);
|
|
1976
|
+
window.addEventListener("blur", handleParentWindowBlur, true);
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1186
1980
|
if (msg.phase === "move") {
|
|
1187
1981
|
const { iframeX, iframeY, viewportW, viewportH, selector, sourceId } =
|
|
1188
1982
|
msg;
|
|
@@ -1199,23 +1993,36 @@ export function MultiScreenCanvas({
|
|
|
1199
1993
|
crossScreenDragMsgRef.current = {
|
|
1200
1994
|
selector: selector ?? "",
|
|
1201
1995
|
sourceId,
|
|
1996
|
+
sourcePointerOffset: isFinitePoint(msg.pointerOffset)
|
|
1997
|
+
? msg.pointerOffset
|
|
1998
|
+
: (crossScreenDragMsgRef.current?.sourcePointerOffset ?? undefined),
|
|
1202
1999
|
};
|
|
1203
2000
|
|
|
1204
|
-
|
|
1205
|
-
if (
|
|
2001
|
+
const pointerInsideSourceIframe =
|
|
1206
2002
|
iframeX >= 0 &&
|
|
1207
2003
|
iframeY >= 0 &&
|
|
1208
2004
|
iframeX <= viewportW &&
|
|
1209
|
-
iframeY <= viewportH
|
|
1210
|
-
|
|
1211
|
-
|
|
2005
|
+
iframeY <= viewportH;
|
|
2006
|
+
const sourceIsBoard = sourceScreenId === boardFileId;
|
|
2007
|
+
// Regular screen iframes are finite artboards, so an in-bounds pointer
|
|
2008
|
+
// means the source bridge should keep handling the drag. The board
|
|
2009
|
+
// iframe is different: it spans the whole overview canvas, including
|
|
2010
|
+
// every screen frame, so board-origin drags must still be checked
|
|
2011
|
+
// against screen drop targets while technically "inside" the source.
|
|
2012
|
+
if (pointerInsideSourceIframe && !sourceIsBoard) {
|
|
2013
|
+
clearCrossScreenPreviewGuide();
|
|
2014
|
+
setCrossScreenGhost(null);
|
|
2015
|
+
setCrossScreenTarget(null);
|
|
2016
|
+
setCrossScreenSourceIsBoard(false);
|
|
2017
|
+
crossScreenTargetRef.current = null;
|
|
2018
|
+
crossScreenLastBoardPointRef.current = null;
|
|
2019
|
+
clearCrossScreenDropGuide();
|
|
1212
2020
|
return;
|
|
1213
2021
|
}
|
|
1214
2022
|
|
|
1215
|
-
// Translate iframe coords → board coords using
|
|
1216
|
-
//
|
|
1217
|
-
//
|
|
1218
|
-
// boardY = frame.y + iframeY * (frame.height / metadata.height)
|
|
2023
|
+
// Translate iframe coords → board coords using the live embedded
|
|
2024
|
+
// viewport from the bridge. In overview, the iframe viewport may be the
|
|
2025
|
+
// frame geometry rather than the screen metadata width.
|
|
1219
2026
|
|
|
1220
2027
|
let boardX: number;
|
|
1221
2028
|
let boardY: number;
|
|
@@ -1234,31 +2041,13 @@ export function MultiScreenCanvas({
|
|
|
1234
2041
|
clearCrossScreenDrag();
|
|
1235
2042
|
return;
|
|
1236
2043
|
}
|
|
1237
|
-
const
|
|
1238
|
-
const
|
|
1239
|
-
sourceGeometry.width / Math.max(1, sourceMetadata.width);
|
|
1240
|
-
const scaleY =
|
|
1241
|
-
sourceGeometry.height / Math.max(1, sourceMetadata.height);
|
|
2044
|
+
const scaleX = sourceGeometry.width / Math.max(1, viewportW);
|
|
2045
|
+
const scaleY = sourceGeometry.height / Math.max(1, viewportH);
|
|
1242
2046
|
boardX = sourceGeometry.x + iframeX * scaleX;
|
|
1243
2047
|
boardY = sourceGeometry.y + iframeY * scaleY;
|
|
1244
2048
|
}
|
|
1245
2049
|
const boardPoint = { x: boardX, y: boardY };
|
|
1246
|
-
|
|
1247
|
-
// Remember the latest board-space position for use in the "end" handler.
|
|
1248
|
-
crossScreenLastBoardPointRef.current = boardPoint;
|
|
1249
|
-
|
|
1250
|
-
setCrossScreenGhost({ boardX, boardY });
|
|
1251
|
-
|
|
1252
|
-
// Find which screen frame the board point falls in.
|
|
1253
|
-
const target = getFrameEntryAtPoint(boardPoint);
|
|
1254
|
-
if (target && target.id !== sourceScreenId) {
|
|
1255
|
-
const nextTarget = { id: target.id, geometry: target.geometry };
|
|
1256
|
-
crossScreenTargetRef.current = nextTarget;
|
|
1257
|
-
setCrossScreenTarget(nextTarget);
|
|
1258
|
-
} else {
|
|
1259
|
-
crossScreenTargetRef.current = null;
|
|
1260
|
-
setCrossScreenTarget(null);
|
|
1261
|
-
}
|
|
2050
|
+
updateCrossScreenTargetFromBoardPoint(boardPoint, sourceScreenId);
|
|
1262
2051
|
return;
|
|
1263
2052
|
}
|
|
1264
2053
|
|
|
@@ -1271,99 +2060,23 @@ export function MultiScreenCanvas({
|
|
|
1271
2060
|
const payload = crossScreenDragMsgRef.current ?? {
|
|
1272
2061
|
selector: msg.selector ?? "",
|
|
1273
2062
|
sourceId: msg.sourceId,
|
|
2063
|
+
sourcePointerOffset: isFinitePoint(msg.pointerOffset)
|
|
2064
|
+
? msg.pointerOffset
|
|
2065
|
+
: undefined,
|
|
1274
2066
|
};
|
|
1275
2067
|
const lastBoardPoint = crossScreenLastBoardPointRef.current;
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
const hasIdentifier = !!(payload.selector || payload.sourceId);
|
|
1283
|
-
if (candidate && hasIdentifier && sourceScreenId) {
|
|
1284
|
-
// Translate the board-space drop point to the target iframe's local
|
|
1285
|
-
// coordinate space, then run a lightweight hit-test (postMessage +
|
|
1286
|
-
// 50 ms timeout) to find the deepest container at the drop site.
|
|
1287
|
-
const runHitTest = (): Promise<{
|
|
1288
|
-
anchorNodeId?: string;
|
|
1289
|
-
placement?: "before" | "after" | "inside";
|
|
1290
|
-
}> => {
|
|
1291
|
-
if (!lastBoardPoint) return Promise.resolve({});
|
|
1292
|
-
const targetScreen = screensRef.current.find(
|
|
1293
|
-
(s) => s.id === candidate.id,
|
|
1294
|
-
);
|
|
1295
|
-
if (!targetScreen) return Promise.resolve({});
|
|
1296
|
-
const targetGeometry = candidate.geometry;
|
|
1297
|
-
const targetMetadata = getResolvedMetadata(targetScreen);
|
|
1298
|
-
// Board → target iframe local coords
|
|
1299
|
-
const scaleX =
|
|
1300
|
-
targetMetadata.width / Math.max(1, targetGeometry.width);
|
|
1301
|
-
const scaleY =
|
|
1302
|
-
targetMetadata.height / Math.max(1, targetGeometry.height);
|
|
1303
|
-
const localX = (lastBoardPoint.x - targetGeometry.x) * scaleX;
|
|
1304
|
-
const localY = (lastBoardPoint.y - targetGeometry.y) * scaleY;
|
|
1305
|
-
|
|
1306
|
-
const targetIframe =
|
|
1307
|
-
surfaceRef.current?.querySelector<HTMLIFrameElement>(
|
|
1308
|
-
`[data-screen-iframe-id="${CSS.escape(candidate.id)}"]`,
|
|
1309
|
-
);
|
|
1310
|
-
const targetContentWindow = targetIframe?.contentWindow;
|
|
1311
|
-
if (!targetContentWindow) return Promise.resolve({});
|
|
1312
|
-
|
|
1313
|
-
const correlationId = `hit-${Date.now()}-${Math.random()
|
|
1314
|
-
.toString(36)
|
|
1315
|
-
.slice(2, 6)}`;
|
|
1316
|
-
|
|
1317
|
-
return new Promise((resolve) => {
|
|
1318
|
-
const timer = window.setTimeout(() => {
|
|
1319
|
-
window.removeEventListener("message", hitListener);
|
|
1320
|
-
resolve({});
|
|
1321
|
-
}, 50);
|
|
1322
|
-
|
|
1323
|
-
const hitListener = (ev: MessageEvent) => {
|
|
1324
|
-
if (
|
|
1325
|
-
!ev.data ||
|
|
1326
|
-
ev.data.type !== "agent-native:hit-test-result" ||
|
|
1327
|
-
ev.data.correlationId !== correlationId
|
|
1328
|
-
)
|
|
1329
|
-
return;
|
|
1330
|
-
window.clearTimeout(timer);
|
|
1331
|
-
window.removeEventListener("message", hitListener);
|
|
1332
|
-
resolve({
|
|
1333
|
-
anchorNodeId: ev.data.anchorNodeId ?? undefined,
|
|
1334
|
-
placement: ev.data.placement ?? undefined,
|
|
1335
|
-
});
|
|
1336
|
-
};
|
|
1337
|
-
window.addEventListener("message", hitListener);
|
|
1338
|
-
|
|
1339
|
-
targetContentWindow.postMessage(
|
|
1340
|
-
{
|
|
1341
|
-
type: "agent-native:hit-test",
|
|
1342
|
-
correlationId,
|
|
1343
|
-
x: localX,
|
|
1344
|
-
y: localY,
|
|
1345
|
-
},
|
|
1346
|
-
"*",
|
|
1347
|
-
);
|
|
1348
|
-
});
|
|
1349
|
-
};
|
|
1350
|
-
|
|
1351
|
-
void runHitTest().then(({ anchorNodeId, placement }) => {
|
|
1352
|
-
onCrossScreenElementDropRef.current?.({
|
|
1353
|
-
sourceSelector: payload.selector,
|
|
1354
|
-
sourceNodeId: payload.sourceId,
|
|
1355
|
-
sourceScreenId,
|
|
1356
|
-
targetScreenId: candidate.id,
|
|
1357
|
-
targetAnchorNodeId: anchorNodeId,
|
|
1358
|
-
targetAnchorPlacement: placement,
|
|
1359
|
-
});
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
2068
|
+
finalizeCrossScreenDrop(
|
|
2069
|
+
sourceScreenId,
|
|
2070
|
+
candidate,
|
|
2071
|
+
payload,
|
|
2072
|
+
lastBoardPoint,
|
|
2073
|
+
);
|
|
1362
2074
|
}
|
|
1363
2075
|
};
|
|
1364
2076
|
|
|
1365
2077
|
window.addEventListener("message", handleMessage);
|
|
1366
2078
|
return () => {
|
|
2079
|
+
stopParentCrossScreenDrag();
|
|
1367
2080
|
window.removeEventListener("message", handleMessage);
|
|
1368
2081
|
};
|
|
1369
2082
|
}, [
|
|
@@ -1371,6 +2084,7 @@ export function MultiScreenCanvas({
|
|
|
1371
2084
|
boardFileId,
|
|
1372
2085
|
boardFrameGeometry,
|
|
1373
2086
|
getFrameEntryAtPoint,
|
|
2087
|
+
getCanvasPoint,
|
|
1374
2088
|
getResolvedMetadata,
|
|
1375
2089
|
onCrossScreenElementDrop,
|
|
1376
2090
|
]);
|
|
@@ -1463,6 +2177,110 @@ export function MultiScreenCanvas({
|
|
|
1463
2177
|
[],
|
|
1464
2178
|
);
|
|
1465
2179
|
|
|
2180
|
+
const requestSelectableElementInfos = useCallback(
|
|
2181
|
+
(screenId: string): Promise<ElementInfo[]> => {
|
|
2182
|
+
const targetIframe = surfaceRef.current?.querySelector<HTMLIFrameElement>(
|
|
2183
|
+
`[data-screen-iframe-id="${CSS.escape(screenId)}"]`,
|
|
2184
|
+
);
|
|
2185
|
+
const targetContentWindow = targetIframe?.contentWindow;
|
|
2186
|
+
if (!targetContentWindow) return Promise.resolve([]);
|
|
2187
|
+
const correlationId = `rects-${Date.now()}-${Math.random()
|
|
2188
|
+
.toString(36)
|
|
2189
|
+
.slice(2, 6)}`;
|
|
2190
|
+
return new Promise((resolve) => {
|
|
2191
|
+
const timer = window.setTimeout(() => {
|
|
2192
|
+
window.removeEventListener("message", listener);
|
|
2193
|
+
resolve([]);
|
|
2194
|
+
}, 80);
|
|
2195
|
+
const listener = (event: MessageEvent) => {
|
|
2196
|
+
if (
|
|
2197
|
+
!event.data ||
|
|
2198
|
+
event.data.type !== "agent-native:selectable-rects-result" ||
|
|
2199
|
+
event.data.correlationId !== correlationId
|
|
2200
|
+
) {
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
window.clearTimeout(timer);
|
|
2204
|
+
window.removeEventListener("message", listener);
|
|
2205
|
+
const payload: unknown[] = Array.isArray(event.data.payload)
|
|
2206
|
+
? event.data.payload
|
|
2207
|
+
: [];
|
|
2208
|
+
resolve(
|
|
2209
|
+
payload.filter((item): item is ElementInfo => {
|
|
2210
|
+
if (!item || typeof item !== "object") return false;
|
|
2211
|
+
const candidate = item as Partial<ElementInfo>;
|
|
2212
|
+
return (
|
|
2213
|
+
typeof candidate.tagName === "string" &&
|
|
2214
|
+
!!candidate.boundingRect &&
|
|
2215
|
+
typeof candidate.boundingRect.width === "number" &&
|
|
2216
|
+
typeof candidate.boundingRect.height === "number"
|
|
2217
|
+
);
|
|
2218
|
+
}),
|
|
2219
|
+
);
|
|
2220
|
+
};
|
|
2221
|
+
window.addEventListener("message", listener);
|
|
2222
|
+
targetContentWindow.postMessage(
|
|
2223
|
+
{ type: "agent-native:collect-selectable-rects", correlationId },
|
|
2224
|
+
"*",
|
|
2225
|
+
);
|
|
2226
|
+
});
|
|
2227
|
+
},
|
|
2228
|
+
[],
|
|
2229
|
+
);
|
|
2230
|
+
|
|
2231
|
+
const collectLayerMarqueeCandidates = useCallback(async () => {
|
|
2232
|
+
const frameCandidates = await Promise.all(
|
|
2233
|
+
getCurrentFrameEntries().map(async (entry) => {
|
|
2234
|
+
const screen = screensRef.current.find((item) => item.id === entry.id);
|
|
2235
|
+
if (!screen) return [] as CanvasLayerMarqueeCandidate[];
|
|
2236
|
+
const iframe = surfaceRef.current?.querySelector<HTMLIFrameElement>(
|
|
2237
|
+
`[data-screen-iframe-id="${CSS.escape(entry.id)}"]`,
|
|
2238
|
+
);
|
|
2239
|
+
const metadata = getResolvedMetadata(screen);
|
|
2240
|
+
const viewportWidth = iframe?.clientWidth || metadata.width;
|
|
2241
|
+
const viewportHeight = iframe?.clientHeight || metadata.height;
|
|
2242
|
+
const scaleX = entry.geometry.width / Math.max(1, viewportWidth);
|
|
2243
|
+
const scaleY = entry.geometry.height / Math.max(1, viewportHeight);
|
|
2244
|
+
const infos = await requestSelectableElementInfos(entry.id);
|
|
2245
|
+
return infos.map((info) => ({
|
|
2246
|
+
screenId: entry.id,
|
|
2247
|
+
info,
|
|
2248
|
+
geometry: {
|
|
2249
|
+
x: entry.geometry.x + info.boundingRect.x * scaleX,
|
|
2250
|
+
y: entry.geometry.y + info.boundingRect.y * scaleY,
|
|
2251
|
+
width: info.boundingRect.width * scaleX,
|
|
2252
|
+
height: info.boundingRect.height * scaleY,
|
|
2253
|
+
},
|
|
2254
|
+
frameGeometry: entry.geometry,
|
|
2255
|
+
}));
|
|
2256
|
+
}),
|
|
2257
|
+
);
|
|
2258
|
+
const boardCandidates =
|
|
2259
|
+
boardFileId && boardFrameGeometry
|
|
2260
|
+
? await (async () => {
|
|
2261
|
+
const infos = await requestSelectableElementInfos(boardFileId);
|
|
2262
|
+
return infos.map((info) => ({
|
|
2263
|
+
screenId: boardFileId,
|
|
2264
|
+
info,
|
|
2265
|
+
geometry: {
|
|
2266
|
+
x: boardFrameGeometry.x + info.boundingRect.x,
|
|
2267
|
+
y: boardFrameGeometry.y + info.boundingRect.y,
|
|
2268
|
+
width: info.boundingRect.width,
|
|
2269
|
+
height: info.boundingRect.height,
|
|
2270
|
+
},
|
|
2271
|
+
frameGeometry: boardFrameGeometry,
|
|
2272
|
+
}));
|
|
2273
|
+
})()
|
|
2274
|
+
: [];
|
|
2275
|
+
return [...frameCandidates.flat(), ...boardCandidates];
|
|
2276
|
+
}, [
|
|
2277
|
+
boardFileId,
|
|
2278
|
+
boardFrameGeometry,
|
|
2279
|
+
getCurrentFrameEntries,
|
|
2280
|
+
getResolvedMetadata,
|
|
2281
|
+
requestSelectableElementInfos,
|
|
2282
|
+
]);
|
|
2283
|
+
|
|
1466
2284
|
const scheduleFeedbackClear = useCallback(() => {
|
|
1467
2285
|
if (feedbackTimerRef.current !== null) {
|
|
1468
2286
|
window.clearTimeout(feedbackTimerRef.current);
|
|
@@ -1656,6 +2474,30 @@ export function MultiScreenCanvas({
|
|
|
1656
2474
|
e.preventDefault();
|
|
1657
2475
|
e.stopPropagation();
|
|
1658
2476
|
const originCanvas = getCanvasPoint(e.clientX, e.clientY);
|
|
2477
|
+
let latestRect = normalizeRectFromPoints(originCanvas, originCanvas);
|
|
2478
|
+
let layerCandidates: CanvasLayerMarqueeCandidate[] = [];
|
|
2479
|
+
const reportLayerSelection = (rect: MarqueeRect) => {
|
|
2480
|
+
const state = dragState.current;
|
|
2481
|
+
if (!state || state.type !== "marquee") return;
|
|
2482
|
+
const selection = layerCandidates
|
|
2483
|
+
.filter((candidate) =>
|
|
2484
|
+
rotatedRectIntersects(
|
|
2485
|
+
rect,
|
|
2486
|
+
getSelectableBounds(candidate.geometry),
|
|
2487
|
+
getFrameCenter(candidate.frameGeometry),
|
|
2488
|
+
candidate.frameGeometry.rotation ?? 0,
|
|
2489
|
+
),
|
|
2490
|
+
)
|
|
2491
|
+
.map((candidate) => ({
|
|
2492
|
+
screenId: candidate.screenId,
|
|
2493
|
+
info: candidate.info,
|
|
2494
|
+
}));
|
|
2495
|
+
onLayerMarqueeSelectionChange?.(selection, {
|
|
2496
|
+
source: "marquee",
|
|
2497
|
+
additive: state.additive,
|
|
2498
|
+
shiftKey: state.additive,
|
|
2499
|
+
});
|
|
2500
|
+
};
|
|
1659
2501
|
dragState.current = {
|
|
1660
2502
|
type: "marquee",
|
|
1661
2503
|
originClient: { x: e.clientX, y: e.clientY },
|
|
@@ -1669,14 +2511,25 @@ export function MultiScreenCanvas({
|
|
|
1669
2511
|
if (!e.shiftKey) {
|
|
1670
2512
|
updateSelectedIds(() => []);
|
|
1671
2513
|
updateSelectedDraftIds(() => []);
|
|
2514
|
+
onLayerMarqueeSelectionChange?.([], {
|
|
2515
|
+
source: "marquee",
|
|
2516
|
+
additive: false,
|
|
2517
|
+
shiftKey: false,
|
|
2518
|
+
});
|
|
1672
2519
|
}
|
|
1673
2520
|
setIsDragging(true);
|
|
2521
|
+
void collectLayerMarqueeCandidates().then((candidates) => {
|
|
2522
|
+
if (dragState.current?.type !== "marquee") return;
|
|
2523
|
+
layerCandidates = candidates;
|
|
2524
|
+
reportLayerSelection(latestRect);
|
|
2525
|
+
});
|
|
1674
2526
|
|
|
1675
2527
|
const handleMouseMove = (ev: MouseEvent) => {
|
|
1676
2528
|
const state = dragState.current;
|
|
1677
2529
|
if (!state || state.type !== "marquee") return;
|
|
1678
2530
|
const nextPoint = getCanvasPoint(ev.clientX, ev.clientY);
|
|
1679
2531
|
const rect = normalizeRectFromPoints(state.originCanvas, nextPoint);
|
|
2532
|
+
latestRect = rect;
|
|
1680
2533
|
if (
|
|
1681
2534
|
!state.hasMoved &&
|
|
1682
2535
|
Math.hypot(
|
|
@@ -1719,6 +2572,7 @@ export function MultiScreenCanvas({
|
|
|
1719
2572
|
? dedupeIds([...state.baseSelectedDraftIds, ...hitDraftIds])
|
|
1720
2573
|
: hitDraftIds,
|
|
1721
2574
|
);
|
|
2575
|
+
reportLayerSelection(rect);
|
|
1722
2576
|
};
|
|
1723
2577
|
|
|
1724
2578
|
const handleMouseUp = () => {
|
|
@@ -1726,6 +2580,11 @@ export function MultiScreenCanvas({
|
|
|
1726
2580
|
if (state?.type === "marquee" && !state.hasMoved && !state.additive) {
|
|
1727
2581
|
updateSelectedIds(() => []);
|
|
1728
2582
|
updateSelectedDraftIds(() => []);
|
|
2583
|
+
onLayerMarqueeSelectionChange?.([], {
|
|
2584
|
+
source: "marquee",
|
|
2585
|
+
additive: false,
|
|
2586
|
+
shiftKey: false,
|
|
2587
|
+
});
|
|
1729
2588
|
}
|
|
1730
2589
|
finishDrag();
|
|
1731
2590
|
};
|
|
@@ -1733,11 +2592,13 @@ export function MultiScreenCanvas({
|
|
|
1733
2592
|
installDragListeners(handleMouseMove, handleMouseUp);
|
|
1734
2593
|
},
|
|
1735
2594
|
[
|
|
2595
|
+
collectLayerMarqueeCandidates,
|
|
1736
2596
|
finishDrag,
|
|
1737
2597
|
getCanvasPoint,
|
|
1738
2598
|
getCurrentDraftEntries,
|
|
1739
2599
|
getCurrentFrameEntries,
|
|
1740
2600
|
installDragListeners,
|
|
2601
|
+
onLayerMarqueeSelectionChange,
|
|
1741
2602
|
updateSelectedDraftIds,
|
|
1742
2603
|
updateSelectedIds,
|
|
1743
2604
|
],
|
|
@@ -1940,6 +2801,7 @@ export function MultiScreenCanvas({
|
|
|
1940
2801
|
}, [frameGeometry, retryPersistedDraftPrimitives, screens]);
|
|
1941
2802
|
|
|
1942
2803
|
const clearActivePenPath = useCallback(() => {
|
|
2804
|
+
activePenPathRef.current = null;
|
|
1943
2805
|
setActivePenPath(null);
|
|
1944
2806
|
setPenGesturePreview(null);
|
|
1945
2807
|
setPenPointer(null);
|
|
@@ -1965,6 +2827,25 @@ export function MultiScreenCanvas({
|
|
|
1965
2827
|
[clearActivePenPath, commitDraftPrimitive, onActiveToolChange, toolProps],
|
|
1966
2828
|
);
|
|
1967
2829
|
|
|
2830
|
+
const undoActivePenPathSegment = useCallback(() => {
|
|
2831
|
+
const path = activePenPathRef.current;
|
|
2832
|
+
if (!path) return false;
|
|
2833
|
+
|
|
2834
|
+
const remainingNodes = path.nodes.slice(0, -1);
|
|
2835
|
+
if (remainingNodes.length === 0) {
|
|
2836
|
+
clearActivePenPath();
|
|
2837
|
+
return true;
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
const nextPath: PenPath = { nodes: remainingNodes, closed: false };
|
|
2841
|
+
activePenPathRef.current = nextPath;
|
|
2842
|
+
setActivePenPath(nextPath);
|
|
2843
|
+
setPenGesturePreview(null);
|
|
2844
|
+
setPenPointer(null);
|
|
2845
|
+
setPenCloseHover(false);
|
|
2846
|
+
return true;
|
|
2847
|
+
}, [clearActivePenPath]);
|
|
2848
|
+
|
|
1968
2849
|
const getPenAnchorPoint = useCallback(
|
|
1969
2850
|
(
|
|
1970
2851
|
clientX: number,
|
|
@@ -2256,7 +3137,6 @@ export function MultiScreenCanvas({
|
|
|
2256
3137
|
points,
|
|
2257
3138
|
moved: releaseMoved,
|
|
2258
3139
|
toolProps,
|
|
2259
|
-
fallbackText: t("designEditor.tools.text"),
|
|
2260
3140
|
});
|
|
2261
3141
|
commitDraftPrimitive(nextDraft, state.originFrameId);
|
|
2262
3142
|
if (activeTool === undefined) {
|
|
@@ -2284,7 +3164,6 @@ export function MultiScreenCanvas({
|
|
|
2284
3164
|
installDragListeners,
|
|
2285
3165
|
onActiveToolChange,
|
|
2286
3166
|
onCreateScreenFrame,
|
|
2287
|
-
t,
|
|
2288
3167
|
toolProps,
|
|
2289
3168
|
],
|
|
2290
3169
|
);
|
|
@@ -2365,14 +3244,6 @@ export function MultiScreenCanvas({
|
|
|
2365
3244
|
}),
|
|
2366
3245
|
);
|
|
2367
3246
|
setAlignmentGuides(snap.guides);
|
|
2368
|
-
const primary = state.originDrafts[state.primaryId].geometry;
|
|
2369
|
-
showTransformFeedback(
|
|
2370
|
-
`X ${Math.round(primary.x + dx + snap.dx)} Y ${Math.round(
|
|
2371
|
-
primary.y + dy + snap.dy,
|
|
2372
|
-
)}`,
|
|
2373
|
-
ev.clientX,
|
|
2374
|
-
ev.clientY,
|
|
2375
|
-
);
|
|
2376
3247
|
|
|
2377
3248
|
// Primitive drop-into-container detection: check if the dragged draft
|
|
2378
3249
|
// is hovering over a committed container primitive on any screen.
|
|
@@ -2475,7 +3346,6 @@ export function MultiScreenCanvas({
|
|
|
2475
3346
|
getCurrentCanvasEntries,
|
|
2476
3347
|
installDragListeners,
|
|
2477
3348
|
persistDraftPrimitive,
|
|
2478
|
-
showTransformFeedback,
|
|
2479
3349
|
updateDraftPrimitives,
|
|
2480
3350
|
updatePrimitiveDropTarget,
|
|
2481
3351
|
updateSelectedDraftIds,
|
|
@@ -2717,14 +3587,6 @@ export function MultiScreenCanvas({
|
|
|
2717
3587
|
return next;
|
|
2718
3588
|
});
|
|
2719
3589
|
setAlignmentGuides(snap.guides);
|
|
2720
|
-
const primary = state.originFrames[state.primaryId];
|
|
2721
|
-
showTransformFeedback(
|
|
2722
|
-
`X ${Math.round(primary.x + dx + snap.dx)} Y ${Math.round(
|
|
2723
|
-
primary.y + dy + snap.dy,
|
|
2724
|
-
)}`,
|
|
2725
|
-
ev.clientX,
|
|
2726
|
-
ev.clientY,
|
|
2727
|
-
);
|
|
2728
3590
|
|
|
2729
3591
|
// When all dragged ids are committed primitive nodeIds (not screen
|
|
2730
3592
|
// frames), check for a container primitive drop target to highlight.
|
|
@@ -2790,7 +3652,6 @@ export function MultiScreenCanvas({
|
|
|
2790
3652
|
getCurrentFrameEntries,
|
|
2791
3653
|
installDragListeners,
|
|
2792
3654
|
onPick,
|
|
2793
|
-
showTransformFeedback,
|
|
2794
3655
|
updateFrameGeometry,
|
|
2795
3656
|
updatePrimitiveDropTarget,
|
|
2796
3657
|
updateSelectedDraftIds,
|
|
@@ -3188,6 +4049,7 @@ export function MultiScreenCanvas({
|
|
|
3188
4049
|
|
|
3189
4050
|
const handleMouseDown = useCallback(
|
|
3190
4051
|
(e: React.MouseEvent) => {
|
|
4052
|
+
claimKeyboardFocus();
|
|
3191
4053
|
// Clear any stale pick-suppression left over from a prior resize/rotate/move
|
|
3192
4054
|
// gesture that never received its trailing frame click — otherwise it would
|
|
3193
4055
|
// silently swallow this unrelated interaction.
|
|
@@ -3222,6 +4084,7 @@ export function MultiScreenCanvas({
|
|
|
3222
4084
|
beginMarquee,
|
|
3223
4085
|
beginPan,
|
|
3224
4086
|
beginPenNodeCreation,
|
|
4087
|
+
claimKeyboardFocus,
|
|
3225
4088
|
localActiveTool,
|
|
3226
4089
|
],
|
|
3227
4090
|
);
|
|
@@ -3615,15 +4478,21 @@ export function MultiScreenCanvas({
|
|
|
3615
4478
|
useEffect(() => {
|
|
3616
4479
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
3617
4480
|
const path = activePenPathRef.current;
|
|
3618
|
-
if (
|
|
3619
|
-
!path ||
|
|
3620
|
-
event.metaKey ||
|
|
3621
|
-
event.ctrlKey ||
|
|
3622
|
-
isEditableHotkeyTarget(event.target)
|
|
3623
|
-
) {
|
|
4481
|
+
if (!path || isEditableHotkeyTarget(event.target)) {
|
|
3624
4482
|
return;
|
|
3625
4483
|
}
|
|
3626
4484
|
|
|
4485
|
+
const primaryKey = event.metaKey || event.ctrlKey;
|
|
4486
|
+
if (primaryKey && !event.shiftKey && event.key.toLowerCase() === "z") {
|
|
4487
|
+
event.preventDefault();
|
|
4488
|
+
event.stopPropagation();
|
|
4489
|
+
event.stopImmediatePropagation();
|
|
4490
|
+
undoActivePenPathSegment();
|
|
4491
|
+
return;
|
|
4492
|
+
}
|
|
4493
|
+
|
|
4494
|
+
if (primaryKey) return;
|
|
4495
|
+
|
|
3627
4496
|
if (event.key === "Enter") {
|
|
3628
4497
|
event.preventDefault();
|
|
3629
4498
|
event.stopPropagation();
|
|
@@ -3644,14 +4513,7 @@ export function MultiScreenCanvas({
|
|
|
3644
4513
|
event.preventDefault();
|
|
3645
4514
|
event.stopPropagation();
|
|
3646
4515
|
event.stopImmediatePropagation();
|
|
3647
|
-
|
|
3648
|
-
if (remainingNodes.length === 0) {
|
|
3649
|
-
clearActivePenPath();
|
|
3650
|
-
return;
|
|
3651
|
-
}
|
|
3652
|
-
setActivePenPath({ nodes: remainingNodes, closed: false });
|
|
3653
|
-
setPenPointer(null);
|
|
3654
|
-
setPenCloseHover(false);
|
|
4516
|
+
undoActivePenPathSegment();
|
|
3655
4517
|
}
|
|
3656
4518
|
};
|
|
3657
4519
|
|
|
@@ -3659,7 +4521,7 @@ export function MultiScreenCanvas({
|
|
|
3659
4521
|
return () => {
|
|
3660
4522
|
window.removeEventListener("keydown", handleKeyDown, true);
|
|
3661
4523
|
};
|
|
3662
|
-
}, [clearActivePenPath, finishPenPath]);
|
|
4524
|
+
}, [clearActivePenPath, finishPenPath, undoActivePenPathSegment]);
|
|
3663
4525
|
|
|
3664
4526
|
useEffect(() => {
|
|
3665
4527
|
const tool = normalizeCanvasTool(activeTool ?? localActiveTool);
|
|
@@ -3719,6 +4581,10 @@ export function MultiScreenCanvas({
|
|
|
3719
4581
|
const penActive = effectiveTool === "pen";
|
|
3720
4582
|
const creationToolActive = Boolean(getDraftCreationTool(effectiveTool));
|
|
3721
4583
|
const canvasGestureActive = isDragging || isPanning;
|
|
4584
|
+
const boardSurfaceInteractive = shouldBoardSurfaceCapturePointerEvents({
|
|
4585
|
+
tool: effectiveTool,
|
|
4586
|
+
gestureActive: canvasGestureActive,
|
|
4587
|
+
});
|
|
3722
4588
|
const displayedPenPath = penGesturePreview
|
|
3723
4589
|
? penGesturePreview
|
|
3724
4590
|
: activePenPath && penPointer && activePenPath.nodes.length > 0
|
|
@@ -3786,10 +4652,14 @@ export function MultiScreenCanvas({
|
|
|
3786
4652
|
selectedDraftEntries.length === 1 && !selectedDraftGroupBounds
|
|
3787
4653
|
? selectedDraftEntries[0]
|
|
3788
4654
|
: null;
|
|
4655
|
+
const rootSelectedEntryCount =
|
|
4656
|
+
selectedFrameEntries.length + selectedDraftEntries.length;
|
|
4657
|
+
const showPassiveRootSelectionBoxes = rootSelectedEntryCount > 1;
|
|
3789
4658
|
return (
|
|
3790
4659
|
<div
|
|
3791
4660
|
ref={surfaceRef}
|
|
3792
|
-
|
|
4661
|
+
tabIndex={-1}
|
|
4662
|
+
className="relative h-full w-full select-none overflow-hidden outline-none"
|
|
3793
4663
|
onMouseDownCapture={handleMouseDown}
|
|
3794
4664
|
onMouseMove={handleMouseMove}
|
|
3795
4665
|
style={{ cursor: surfaceCursor, touchAction: "none" }}
|
|
@@ -3826,6 +4696,7 @@ export function MultiScreenCanvas({
|
|
|
3826
4696
|
>
|
|
3827
4697
|
{boardFileId &&
|
|
3828
4698
|
boardFileContent !== undefined &&
|
|
4699
|
+
hasBoardSurfaceContent(boardFileContent) &&
|
|
3829
4700
|
(() => {
|
|
3830
4701
|
const boardGeo = boardFrameGeometry ?? {
|
|
3831
4702
|
x: 0,
|
|
@@ -3833,40 +4704,87 @@ export function MultiScreenCanvas({
|
|
|
3833
4704
|
width: 8192,
|
|
3834
4705
|
height: 8192,
|
|
3835
4706
|
};
|
|
3836
|
-
|
|
3837
|
-
const
|
|
3838
|
-
const
|
|
4707
|
+
const boardW = boardGeo.width;
|
|
4708
|
+
const boardH = boardGeo.height;
|
|
4709
|
+
const boardContentKey = getBoardContentKey({
|
|
4710
|
+
boardFileId,
|
|
4711
|
+
boardFileContent,
|
|
4712
|
+
boardIsActive,
|
|
4713
|
+
});
|
|
4714
|
+
const boardRenderContent =
|
|
4715
|
+
getBoardSurfaceRenderContent(boardFileContent);
|
|
3839
4716
|
return (
|
|
3840
4717
|
// Overflow-hidden wrapper so the board iframe never bleeds outside
|
|
3841
4718
|
// its declared logical surface. z-index 0 keeps it below screen
|
|
3842
4719
|
// iframes (which have their own stacking context above this).
|
|
3843
4720
|
<div
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
height: boardH,
|
|
3850
|
-
overflow: "hidden",
|
|
3851
|
-
pointerEvents: "auto",
|
|
3852
|
-
// Board sits behind all screen iframes.
|
|
3853
|
-
zIndex: 0,
|
|
3854
|
-
}}
|
|
4721
|
+
className="[&_.design-canvas-iframe-wrapper]:shadow-none [&_.design-canvas-iframe-wrapper]:ring-0"
|
|
4722
|
+
style={getBoardSurfaceLayerStyle({
|
|
4723
|
+
geometry: boardGeo,
|
|
4724
|
+
interactive: boardSurfaceInteractive,
|
|
4725
|
+
})}
|
|
3855
4726
|
>
|
|
3856
4727
|
<DesignCanvas
|
|
3857
|
-
content={
|
|
3858
|
-
contentKey={
|
|
4728
|
+
content={boardRenderContent}
|
|
4729
|
+
contentKey={boardContentKey}
|
|
4730
|
+
screenId={boardFileId}
|
|
3859
4731
|
zoom={100}
|
|
3860
4732
|
deviceFrame="none"
|
|
4733
|
+
boardSurface
|
|
4734
|
+
embeddedFrameBackground={BOARD_SURFACE_BACKGROUND}
|
|
4735
|
+
embeddedFrame={{
|
|
4736
|
+
viewportWidth: Math.max(1, Math.round(boardW)),
|
|
4737
|
+
viewportHeight: Math.max(1, Math.round(boardH)),
|
|
4738
|
+
displayWidth: Math.max(1, Math.round(boardW)),
|
|
4739
|
+
displayHeight: Math.max(1, Math.round(boardH)),
|
|
4740
|
+
fluid: true,
|
|
4741
|
+
contentOffsetX: -boardGeo.x,
|
|
4742
|
+
contentOffsetY: -boardGeo.y,
|
|
4743
|
+
}}
|
|
4744
|
+
editorChromeScaleX={canvasZoom / 100}
|
|
4745
|
+
editorChromeScaleY={canvasZoom / 100}
|
|
3861
4746
|
editMode={boardEditMode}
|
|
3862
4747
|
interactMode={false}
|
|
3863
|
-
|
|
4748
|
+
scaleMode={boardIsActive && effectiveTool === "scale"}
|
|
4749
|
+
clearSelectionRequest={boardClearSelectionRequest}
|
|
4750
|
+
selectedSelector={
|
|
4751
|
+
boardIsActive ? (boardSelectedSelector ?? null) : null
|
|
4752
|
+
}
|
|
4753
|
+
selectedSelectorCandidates={
|
|
4754
|
+
boardIsActive ? (boardSelectedSelectorCandidates ?? []) : []
|
|
4755
|
+
}
|
|
4756
|
+
selectedSelectorGroups={
|
|
4757
|
+
selectedLayerSelectorGroupsByScreen[boardFileId] ?? []
|
|
4758
|
+
}
|
|
4759
|
+
hoveredSelector={boardHoveredSelector ?? null}
|
|
4760
|
+
hoveredSelectorCandidates={
|
|
4761
|
+
boardHoveredSelectorCandidates ?? []
|
|
4762
|
+
}
|
|
4763
|
+
lockedSelectors={boardLockedSelectors ?? []}
|
|
4764
|
+
hiddenSelectors={boardHiddenSelectors ?? []}
|
|
4765
|
+
// Board owns the global window runtime bridge only when it is
|
|
4766
|
+
// the active surface (activeFileId === boardFileId). This is
|
|
4767
|
+
// the XOR counterpart to the active screen's
|
|
4768
|
+
// registerRuntimeBridge={screenIsActive}: since activeFileId
|
|
4769
|
+
// is exclusive, the board and any screen can never both
|
|
4770
|
+
// register at once, so window.__designCanvas* in-place ops
|
|
4771
|
+
// (delete removal, begin-text-edit) target the board exactly
|
|
4772
|
+
// like a screen. editMode stays always-editable so a board
|
|
4773
|
+
// element can still be clicked to select it before the board
|
|
4774
|
+
// becomes active.
|
|
4775
|
+
registerRuntimeBridge={boardIsActive}
|
|
3864
4776
|
onElementSelect={onBoardElementSelect ?? (() => {})}
|
|
4777
|
+
onElementMarqueeSelect={onBoardElementMarqueeSelect}
|
|
3865
4778
|
onElementHover={onBoardElementHover ?? (() => {})}
|
|
4779
|
+
onClearSelection={onBoardElementClear}
|
|
4780
|
+
onIframeHotkey={onBoardIframeHotkey}
|
|
4781
|
+
onIframeContextMenu={onBoardIframeContextMenu}
|
|
3866
4782
|
onVisualStructureChange={onBoardVisualStructureChange}
|
|
3867
4783
|
onVisualStyleChange={onBoardVisualStyleChange}
|
|
3868
4784
|
onVisualDuplicateChange={onBoardVisualDuplicateChange}
|
|
3869
4785
|
onTextContentChange={onBoardTextContentChange}
|
|
4786
|
+
onTextEditingStateChange={onBoardTextEditingStateChange}
|
|
4787
|
+
onElementDblClickText={onBoardElementDblClickText}
|
|
3870
4788
|
tweakValues={{}}
|
|
3871
4789
|
/>
|
|
3872
4790
|
</div>
|
|
@@ -3946,6 +4864,28 @@ export function MultiScreenCanvas({
|
|
|
3946
4864
|
/>
|
|
3947
4865
|
) : null}
|
|
3948
4866
|
|
|
4867
|
+
{showPassiveRootSelectionBoxes
|
|
4868
|
+
? selectedFrameEntries.map((entry) => (
|
|
4869
|
+
<PassiveSelectionBox
|
|
4870
|
+
key={`selected-frame-${entry.id}`}
|
|
4871
|
+
geometry={entry.geometry}
|
|
4872
|
+
chromeScale={chromeScale}
|
|
4873
|
+
chromeSettling={chromeSettling}
|
|
4874
|
+
/>
|
|
4875
|
+
))
|
|
4876
|
+
: null}
|
|
4877
|
+
|
|
4878
|
+
{showPassiveRootSelectionBoxes
|
|
4879
|
+
? selectedDraftEntries.map((entry) => (
|
|
4880
|
+
<PassiveSelectionBox
|
|
4881
|
+
key={`selected-draft-${entry.id}`}
|
|
4882
|
+
geometry={entry.geometry}
|
|
4883
|
+
chromeScale={chromeScale}
|
|
4884
|
+
chromeSettling={chromeSettling}
|
|
4885
|
+
/>
|
|
4886
|
+
))
|
|
4887
|
+
: null}
|
|
4888
|
+
|
|
3949
4889
|
{displayedPenPath ? (
|
|
3950
4890
|
<PenPathOverlay path={displayedPenPath} closeHover={penCloseHover} />
|
|
3951
4891
|
) : null}
|
|
@@ -4083,7 +5023,7 @@ export function MultiScreenCanvas({
|
|
|
4083
5023
|
}}
|
|
4084
5024
|
>
|
|
4085
5025
|
<div className="flex h-full w-full items-start justify-between rounded-lg bg-muted/20 p-2">
|
|
4086
|
-
<span className="max-w-[190px] truncate text-[11px] font-medium text-foreground">
|
|
5026
|
+
<span className="max-w-[190px] truncate !text-[11px] font-medium text-foreground">
|
|
4087
5027
|
{duplicatePreview.display}
|
|
4088
5028
|
</span>
|
|
4089
5029
|
<span className="flex items-center gap-1 rounded-md border border-border bg-background px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground shadow-sm">
|
|
@@ -4101,37 +5041,29 @@ export function MultiScreenCanvas({
|
|
|
4101
5041
|
{transformBadge ? (
|
|
4102
5042
|
<div
|
|
4103
5043
|
data-transform-badge
|
|
4104
|
-
className="pointer-events-none fixed z-50 rounded border border-border bg-background/95 px-1.5 py-0.5 font-mono text-[11px] leading-5 text-foreground shadow-lg backdrop-blur"
|
|
5044
|
+
className="pointer-events-none fixed z-50 rounded border border-border bg-background/95 px-1.5 py-0.5 font-mono !text-[11px] leading-5 text-foreground shadow-lg backdrop-blur"
|
|
4105
5045
|
style={{ left: transformBadge.x, top: transformBadge.y }}
|
|
4106
5046
|
>
|
|
4107
5047
|
{transformBadge.text}
|
|
4108
5048
|
</div>
|
|
4109
5049
|
) : null}
|
|
4110
5050
|
|
|
4111
|
-
{
|
|
4112
|
-
Uses the same style as the primitiveDropTarget overlay (2px accent border
|
|
4113
|
-
+ 14% accent fill) so it is visually consistent. */}
|
|
4114
|
-
{crossScreenTarget ? (
|
|
5051
|
+
{crossScreenDropGuide ? (
|
|
4115
5052
|
<span
|
|
4116
|
-
data-cross-screen-drop-
|
|
4117
|
-
className="pointer-events-none absolute z-
|
|
4118
|
-
style={{
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
pan.y + (SURFACE_PADDING + crossScreenTarget.geometry.y) * scale,
|
|
4124
|
-
width: Math.max(1, crossScreenTarget.geometry.width * scale),
|
|
4125
|
-
height: Math.max(1, crossScreenTarget.geometry.height * scale),
|
|
4126
|
-
border: "2px solid var(--design-editor-accent-color)",
|
|
4127
|
-
background:
|
|
4128
|
-
"color-mix(in srgb, var(--design-editor-accent-color) 14%, transparent)",
|
|
4129
|
-
}}
|
|
5053
|
+
data-cross-screen-drop-guide
|
|
5054
|
+
className="pointer-events-none absolute z-50 rounded-sm shadow-[0_0_0_1px_var(--design-editor-accent-contrast-color)]"
|
|
5055
|
+
style={getCrossScreenDropGuideStyle({
|
|
5056
|
+
guide: crossScreenDropGuide,
|
|
5057
|
+
pan,
|
|
5058
|
+
scale,
|
|
5059
|
+
})}
|
|
4130
5060
|
/>
|
|
4131
5061
|
) : null}
|
|
4132
5062
|
|
|
4133
5063
|
{/* Cross-screen element drag: small ghost following the board-space cursor. */}
|
|
4134
|
-
{crossScreenGhost
|
|
5064
|
+
{crossScreenGhost &&
|
|
5065
|
+
!crossScreenSourceIsBoard &&
|
|
5066
|
+
!crossScreenDropGuide ? (
|
|
4135
5067
|
<span
|
|
4136
5068
|
data-cross-screen-drag-ghost
|
|
4137
5069
|
className="pointer-events-none absolute z-50 rounded border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-accent-color)]/20 shadow"
|
|
@@ -4680,7 +5612,7 @@ const Screen = memo(function Screen({
|
|
|
4680
5612
|
<span
|
|
4681
5613
|
data-frame-title
|
|
4682
5614
|
className={cn(
|
|
4683
|
-
"min-w-0 truncate text-[11px] font-medium",
|
|
5615
|
+
"min-w-0 truncate !text-[11px] font-medium",
|
|
4684
5616
|
emphasized
|
|
4685
5617
|
? "text-[var(--design-editor-accent-color)]"
|
|
4686
5618
|
: activeOrEmphasized
|
|
@@ -5026,7 +5958,7 @@ function BreakpointPreviewRow({
|
|
|
5026
5958
|
/>
|
|
5027
5959
|
<span
|
|
5028
5960
|
className={cn(
|
|
5029
|
-
"truncate text-[11px] font-medium",
|
|
5961
|
+
"truncate !text-[11px] font-medium",
|
|
5030
5962
|
isActive
|
|
5031
5963
|
? "text-[var(--design-editor-accent-color)]"
|
|
5032
5964
|
: "text-muted-foreground",
|
|
@@ -5134,16 +6066,63 @@ function GroupSelectionBox({
|
|
|
5134
6066
|
chromeScale={chromeScale}
|
|
5135
6067
|
chromeSettling={chromeSettling}
|
|
5136
6068
|
showRotate={false}
|
|
6069
|
+
filled
|
|
5137
6070
|
onStartResize={onStartResize}
|
|
5138
6071
|
onStartRotate={() => {}}
|
|
5139
6072
|
/>
|
|
5140
6073
|
);
|
|
5141
6074
|
}
|
|
5142
6075
|
|
|
6076
|
+
function PassiveSelectionBox({
|
|
6077
|
+
geometry,
|
|
6078
|
+
chromeScale,
|
|
6079
|
+
chromeSettling,
|
|
6080
|
+
}: {
|
|
6081
|
+
geometry: FrameGeometry;
|
|
6082
|
+
chromeScale: number;
|
|
6083
|
+
chromeSettling: boolean;
|
|
6084
|
+
}) {
|
|
6085
|
+
return (
|
|
6086
|
+
<div
|
|
6087
|
+
data-passive-frame-selection-box
|
|
6088
|
+
className="pointer-events-none absolute border border-[var(--design-editor-accent-color)]"
|
|
6089
|
+
style={{
|
|
6090
|
+
left: SURFACE_PADDING + geometry.x,
|
|
6091
|
+
top: SURFACE_PADDING + geometry.y,
|
|
6092
|
+
width: geometry.width,
|
|
6093
|
+
height: geometry.height,
|
|
6094
|
+
borderRadius: 13 * chromeScale,
|
|
6095
|
+
borderWidth: 1.5 * chromeScale,
|
|
6096
|
+
transition: getSelectionBoxTransition(chromeSettling),
|
|
6097
|
+
transform: geometry.rotation
|
|
6098
|
+
? `rotate(${geometry.rotation}deg)`
|
|
6099
|
+
: undefined,
|
|
6100
|
+
transformOrigin: `${geometry.width / 2}px ${geometry.height / 2}px`,
|
|
6101
|
+
zIndex: 999_999,
|
|
6102
|
+
}}
|
|
6103
|
+
>
|
|
6104
|
+
{CORNER_RESIZE_HANDLE_CONFIGS.map((config) => (
|
|
6105
|
+
<span
|
|
6106
|
+
key={config.handle}
|
|
6107
|
+
data-passive-resize-handle={config.handle}
|
|
6108
|
+
className="pointer-events-none absolute z-20 rounded-[2px] border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-accent-contrast-color)] shadow"
|
|
6109
|
+
style={cornerHandleStyle(
|
|
6110
|
+
config.handle,
|
|
6111
|
+
config.cursor,
|
|
6112
|
+
chromeScale,
|
|
6113
|
+
chromeSettling,
|
|
6114
|
+
)}
|
|
6115
|
+
/>
|
|
6116
|
+
))}
|
|
6117
|
+
</div>
|
|
6118
|
+
);
|
|
6119
|
+
}
|
|
6120
|
+
|
|
5143
6121
|
function SelectionBox({
|
|
5144
6122
|
geometry,
|
|
5145
6123
|
chromeScale,
|
|
5146
6124
|
chromeSettling,
|
|
6125
|
+
filled = false,
|
|
5147
6126
|
showRotate = true,
|
|
5148
6127
|
onStartResize,
|
|
5149
6128
|
onStartRotate,
|
|
@@ -5151,6 +6130,7 @@ function SelectionBox({
|
|
|
5151
6130
|
geometry: FrameGeometry;
|
|
5152
6131
|
chromeScale: number;
|
|
5153
6132
|
chromeSettling: boolean;
|
|
6133
|
+
filled?: boolean;
|
|
5154
6134
|
showRotate?: boolean;
|
|
5155
6135
|
onStartResize: (handle: ResizeHandle, e: React.MouseEvent) => void;
|
|
5156
6136
|
onStartRotate: (e: React.MouseEvent) => void;
|
|
@@ -5165,6 +6145,9 @@ function SelectionBox({
|
|
|
5165
6145
|
top: SURFACE_PADDING + geometry.y,
|
|
5166
6146
|
width: geometry.width,
|
|
5167
6147
|
height: geometry.height,
|
|
6148
|
+
background: filled
|
|
6149
|
+
? "var(--design-editor-selection-color)"
|
|
6150
|
+
: "transparent",
|
|
5168
6151
|
borderRadius: 13 * chromeScale,
|
|
5169
6152
|
borderWidth: 1.5 * chromeScale,
|
|
5170
6153
|
transition: getSelectionBoxTransition(chromeSettling),
|
|
@@ -5807,7 +6790,6 @@ function createDraftPrimitive({
|
|
|
5807
6790
|
points,
|
|
5808
6791
|
moved,
|
|
5809
6792
|
toolProps,
|
|
5810
|
-
fallbackText,
|
|
5811
6793
|
}: DraftPrimitiveInput): DraftPrimitive {
|
|
5812
6794
|
const id = createDraftId(tool);
|
|
5813
6795
|
const geometry = moved
|
|
@@ -5839,7 +6821,7 @@ function createDraftPrimitive({
|
|
|
5839
6821
|
id,
|
|
5840
6822
|
kind: "text",
|
|
5841
6823
|
geometry,
|
|
5842
|
-
text: toolProps?.text ??
|
|
6824
|
+
text: toolProps?.text ?? "",
|
|
5843
6825
|
fill: toolProps?.fill,
|
|
5844
6826
|
stroke: toolProps?.stroke,
|
|
5845
6827
|
autoSize: !moved,
|
|
@@ -6365,17 +7347,21 @@ export function parsePrimitivesFromScreen(
|
|
|
6365
7347
|
// Validity: must have non-zero size
|
|
6366
7348
|
if (width <= 0 || height <= 0) return;
|
|
6367
7349
|
|
|
6368
|
-
//
|
|
6369
|
-
//
|
|
6370
|
-
//
|
|
6371
|
-
// - NOT a text primitive (but text divs could be containers in principle;
|
|
6372
|
-
// we exclude them by checking for display:inline-block autoSize pattern)
|
|
7350
|
+
// Only explicit auto-layout primitives should accept drops. Plain absolute
|
|
7351
|
+
// rectangles are visual layers, not flow containers; treating every div as a
|
|
7352
|
+
// container made top-level rectangles disappear into each other on drag.
|
|
6373
7353
|
const isDiv = tag === "div";
|
|
6374
7354
|
const isEllipse =
|
|
6375
7355
|
style.borderRadius === "50%" ||
|
|
6376
7356
|
style.borderRadius === "50% 50% 50% 50%";
|
|
6377
7357
|
const isTextAutoSize = style.display === "inline-block";
|
|
6378
|
-
const
|
|
7358
|
+
const isAutoLayout =
|
|
7359
|
+
style.display === "flex" ||
|
|
7360
|
+
style.display === "inline-flex" ||
|
|
7361
|
+
style.display === "grid" ||
|
|
7362
|
+
style.display === "inline-grid";
|
|
7363
|
+
const isContainer =
|
|
7364
|
+
isDiv && !isEllipse && !isTextAutoSize && isAutoLayout;
|
|
6379
7365
|
|
|
6380
7366
|
result.push({
|
|
6381
7367
|
nodeId,
|