@agent-native/core 0.83.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 +28 -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/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/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/app/components/editor/BubbleToolbar.tsx +5 -5
- package/corpus/templates/content/app/global.css +6 -5
- package/corpus/templates/content/changelog/2026-06-30-editor-floating-toolbars-now-use-theme-aware-colors-in-light.md +6 -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 +345 -14
- 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 +1038 -150
- 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 +1129 -155
- 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/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 +1428 -384
- 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-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/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/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- 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/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,12 +1193,19 @@ 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);
|
|
752
1210
|
const crossScreenParentDragCleanupRef = useRef<(() => void) | null>(null);
|
|
753
1211
|
/** Board-space point from the last cross-screen-drag "move" message. */
|
|
@@ -771,6 +1229,21 @@ export function MultiScreenCanvas({
|
|
|
771
1229
|
const [chromeSettling, setChromeSettling] = useState(false);
|
|
772
1230
|
const previousPreviewDeviceFrameRef = useRef(previewDeviceFrame);
|
|
773
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
|
+
|
|
774
1247
|
const getResolvedMetadata = useCallback(
|
|
775
1248
|
(screen: ScreenFile) =>
|
|
776
1249
|
resolveScreenMetadata(
|
|
@@ -1145,6 +1618,32 @@ export function MultiScreenCanvas({
|
|
|
1145
1618
|
useEffect(() => {
|
|
1146
1619
|
if (!onCrossScreenElementDrop) return;
|
|
1147
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
|
+
|
|
1148
1647
|
const stopParentCrossScreenDrag = () => {
|
|
1149
1648
|
crossScreenParentDragCleanupRef.current?.();
|
|
1150
1649
|
crossScreenParentDragCleanupRef.current = null;
|
|
@@ -1152,115 +1651,228 @@ export function MultiScreenCanvas({
|
|
|
1152
1651
|
|
|
1153
1652
|
const clearCrossScreenDrag = () => {
|
|
1154
1653
|
stopParentCrossScreenDrag();
|
|
1654
|
+
clearCrossScreenPreviewGuide();
|
|
1155
1655
|
setCrossScreenGhost(null);
|
|
1156
1656
|
setCrossScreenTarget(null);
|
|
1657
|
+
setCrossScreenSourceIsBoard(false);
|
|
1658
|
+
clearCrossScreenDropGuide();
|
|
1157
1659
|
crossScreenTargetRef.current = null;
|
|
1158
1660
|
crossScreenDragMsgRef.current = null;
|
|
1159
1661
|
};
|
|
1160
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
|
+
|
|
1161
1792
|
const updateCrossScreenTargetFromBoardPoint = (
|
|
1162
1793
|
boardPoint: Point,
|
|
1163
1794
|
sourceScreenId: string,
|
|
1164
1795
|
) => {
|
|
1165
1796
|
crossScreenLastBoardPointRef.current = boardPoint;
|
|
1166
|
-
|
|
1797
|
+
const sourceIsBoard = sourceScreenId === boardFileId;
|
|
1798
|
+
setCrossScreenSourceIsBoard(sourceIsBoard);
|
|
1799
|
+
setCrossScreenGhost(
|
|
1800
|
+
sourceIsBoard ? null : { boardX: boardPoint.x, boardY: boardPoint.y },
|
|
1801
|
+
);
|
|
1167
1802
|
const target = getFrameEntryAtPoint(boardPoint);
|
|
1168
1803
|
if (target && target.id !== sourceScreenId) {
|
|
1169
1804
|
const nextTarget = { id: target.id, geometry: target.geometry };
|
|
1805
|
+
if (crossScreenTargetRef.current?.id !== nextTarget.id) {
|
|
1806
|
+
clearCrossScreenPreviewGuide();
|
|
1807
|
+
}
|
|
1170
1808
|
crossScreenTargetRef.current = nextTarget;
|
|
1171
1809
|
setCrossScreenTarget(nextTarget);
|
|
1810
|
+
requestCrossScreenDropGuide(nextTarget, boardPoint, sourceIsBoard);
|
|
1172
1811
|
} else {
|
|
1812
|
+
clearCrossScreenPreviewGuide();
|
|
1173
1813
|
crossScreenTargetRef.current = null;
|
|
1174
1814
|
setCrossScreenTarget(null);
|
|
1815
|
+
clearCrossScreenDropGuide();
|
|
1175
1816
|
}
|
|
1176
1817
|
};
|
|
1177
1818
|
|
|
1178
1819
|
const finalizeCrossScreenDrop = (
|
|
1179
1820
|
sourceScreenId: string,
|
|
1180
1821
|
candidate: CrossScreenDragTarget | null,
|
|
1181
|
-
payload: {
|
|
1822
|
+
payload: {
|
|
1823
|
+
selector: string;
|
|
1824
|
+
sourceId?: string;
|
|
1825
|
+
sourcePointerOffset?: Point;
|
|
1826
|
+
},
|
|
1182
1827
|
lastBoardPoint: Point | null,
|
|
1183
1828
|
) => {
|
|
1184
1829
|
clearCrossScreenDrag();
|
|
1185
1830
|
crossScreenLastBoardPointRef.current = null;
|
|
1186
1831
|
const hasIdentifier = !!(payload.selector || payload.sourceId);
|
|
1187
|
-
if (!
|
|
1188
|
-
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
if (!targetScreen) return Promise.resolve({});
|
|
1198
|
-
const targetGeometry = candidate.geometry;
|
|
1199
|
-
const targetIframe =
|
|
1200
|
-
surfaceRef.current?.querySelector<HTMLIFrameElement>(
|
|
1201
|
-
`[data-screen-iframe-id="${CSS.escape(candidate.id)}"]`,
|
|
1202
|
-
);
|
|
1203
|
-
const targetContentWindow = targetIframe?.contentWindow;
|
|
1204
|
-
if (!targetContentWindow) return Promise.resolve({});
|
|
1205
|
-
const targetViewportWidth =
|
|
1206
|
-
targetIframe.clientWidth || getResolvedMetadata(targetScreen).width;
|
|
1207
|
-
const targetViewportHeight =
|
|
1208
|
-
targetIframe.clientHeight || getResolvedMetadata(targetScreen).height;
|
|
1209
|
-
const scaleX = targetViewportWidth / Math.max(1, targetGeometry.width);
|
|
1210
|
-
const scaleY =
|
|
1211
|
-
targetViewportHeight / Math.max(1, targetGeometry.height);
|
|
1212
|
-
const localX = (lastBoardPoint.x - targetGeometry.x) * scaleX;
|
|
1213
|
-
const localY = (lastBoardPoint.y - targetGeometry.y) * scaleY;
|
|
1214
|
-
|
|
1215
|
-
const correlationId = `hit-${Date.now()}-${Math.random()
|
|
1216
|
-
.toString(36)
|
|
1217
|
-
.slice(2, 6)}`;
|
|
1218
|
-
|
|
1219
|
-
return new Promise((resolve) => {
|
|
1220
|
-
const timer = window.setTimeout(() => {
|
|
1221
|
-
window.removeEventListener("message", hitListener);
|
|
1222
|
-
resolve({});
|
|
1223
|
-
}, 50);
|
|
1224
|
-
|
|
1225
|
-
const hitListener = (ev: MessageEvent) => {
|
|
1226
|
-
if (
|
|
1227
|
-
!ev.data ||
|
|
1228
|
-
ev.data.type !== "agent-native:hit-test-result" ||
|
|
1229
|
-
ev.data.correlationId !== correlationId
|
|
1230
|
-
) {
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
window.clearTimeout(timer);
|
|
1234
|
-
window.removeEventListener("message", hitListener);
|
|
1235
|
-
resolve({
|
|
1236
|
-
anchorNodeId: ev.data.anchorNodeId ?? undefined,
|
|
1237
|
-
placement: ev.data.placement ?? undefined,
|
|
1238
|
-
});
|
|
1239
|
-
};
|
|
1240
|
-
window.addEventListener("message", hitListener);
|
|
1241
|
-
|
|
1242
|
-
targetContentWindow.postMessage(
|
|
1243
|
-
{
|
|
1244
|
-
type: "agent-native:hit-test",
|
|
1245
|
-
correlationId,
|
|
1246
|
-
x: localX,
|
|
1247
|
-
y: localY,
|
|
1248
|
-
},
|
|
1249
|
-
"*",
|
|
1250
|
-
);
|
|
1251
|
-
});
|
|
1252
|
-
};
|
|
1253
|
-
|
|
1254
|
-
void runHitTest().then(({ anchorNodeId, placement }) => {
|
|
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) {
|
|
1255
1842
|
onCrossScreenElementDropRef.current?.({
|
|
1256
1843
|
sourceSelector: payload.selector,
|
|
1257
1844
|
sourceNodeId: payload.sourceId,
|
|
1258
1845
|
sourceScreenId,
|
|
1259
|
-
targetScreenId:
|
|
1260
|
-
|
|
1261
|
-
|
|
1846
|
+
targetScreenId: targetCandidate.id,
|
|
1847
|
+
targetCanvasPoint: lastBoardPoint,
|
|
1848
|
+
targetLocalPoint: lastBoardPoint,
|
|
1849
|
+
sourcePointerOffset: payload.sourcePointerOffset,
|
|
1262
1850
|
});
|
|
1263
|
-
|
|
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
|
+
);
|
|
1264
1876
|
};
|
|
1265
1877
|
|
|
1266
1878
|
const handleMessage = (event: MessageEvent) => {
|
|
@@ -1277,6 +1889,8 @@ export function MultiScreenCanvas({
|
|
|
1277
1889
|
iframeY?: number;
|
|
1278
1890
|
viewportW?: number;
|
|
1279
1891
|
viewportH?: number;
|
|
1892
|
+
elementRect?: CrossScreenDragElementRect;
|
|
1893
|
+
pointerOffset?: Point;
|
|
1280
1894
|
};
|
|
1281
1895
|
|
|
1282
1896
|
if (msg.phase === "cancel") {
|
|
@@ -1301,9 +1915,13 @@ export function MultiScreenCanvas({
|
|
|
1301
1915
|
}
|
|
1302
1916
|
|
|
1303
1917
|
if (msg.phase === "start") {
|
|
1918
|
+
setCrossScreenSourceIsBoard(sourceScreenId === boardFileId);
|
|
1304
1919
|
crossScreenDragMsgRef.current = {
|
|
1305
1920
|
selector: msg.selector ?? "",
|
|
1306
1921
|
sourceId: msg.sourceId,
|
|
1922
|
+
sourcePointerOffset: isFinitePoint(msg.pointerOffset)
|
|
1923
|
+
? msg.pointerOffset
|
|
1924
|
+
: undefined,
|
|
1307
1925
|
};
|
|
1308
1926
|
stopParentCrossScreenDrag();
|
|
1309
1927
|
const restorePreviewPointerEvents = mutePreviewIframePointerEvents(
|
|
@@ -1326,6 +1944,9 @@ export function MultiScreenCanvas({
|
|
|
1326
1944
|
const payload = crossScreenDragMsgRef.current ?? {
|
|
1327
1945
|
selector: msg.selector ?? "",
|
|
1328
1946
|
sourceId: msg.sourceId,
|
|
1947
|
+
sourcePointerOffset: isFinitePoint(msg.pointerOffset)
|
|
1948
|
+
? msg.pointerOffset
|
|
1949
|
+
: undefined,
|
|
1329
1950
|
};
|
|
1330
1951
|
const lastBoardPoint = crossScreenLastBoardPointRef.current;
|
|
1331
1952
|
finalizeCrossScreenDrop(
|
|
@@ -1372,19 +1993,30 @@ export function MultiScreenCanvas({
|
|
|
1372
1993
|
crossScreenDragMsgRef.current = {
|
|
1373
1994
|
selector: selector ?? "",
|
|
1374
1995
|
sourceId,
|
|
1996
|
+
sourcePointerOffset: isFinitePoint(msg.pointerOffset)
|
|
1997
|
+
? msg.pointerOffset
|
|
1998
|
+
: (crossScreenDragMsgRef.current?.sourcePointerOffset ?? undefined),
|
|
1375
1999
|
};
|
|
1376
2000
|
|
|
1377
|
-
|
|
1378
|
-
if (
|
|
2001
|
+
const pointerInsideSourceIframe =
|
|
1379
2002
|
iframeX >= 0 &&
|
|
1380
2003
|
iframeY >= 0 &&
|
|
1381
2004
|
iframeX <= viewportW &&
|
|
1382
|
-
iframeY <= viewportH
|
|
1383
|
-
|
|
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();
|
|
1384
2014
|
setCrossScreenGhost(null);
|
|
1385
2015
|
setCrossScreenTarget(null);
|
|
2016
|
+
setCrossScreenSourceIsBoard(false);
|
|
1386
2017
|
crossScreenTargetRef.current = null;
|
|
1387
2018
|
crossScreenLastBoardPointRef.current = null;
|
|
2019
|
+
clearCrossScreenDropGuide();
|
|
1388
2020
|
return;
|
|
1389
2021
|
}
|
|
1390
2022
|
|
|
@@ -1428,6 +2060,9 @@ export function MultiScreenCanvas({
|
|
|
1428
2060
|
const payload = crossScreenDragMsgRef.current ?? {
|
|
1429
2061
|
selector: msg.selector ?? "",
|
|
1430
2062
|
sourceId: msg.sourceId,
|
|
2063
|
+
sourcePointerOffset: isFinitePoint(msg.pointerOffset)
|
|
2064
|
+
? msg.pointerOffset
|
|
2065
|
+
: undefined,
|
|
1431
2066
|
};
|
|
1432
2067
|
const lastBoardPoint = crossScreenLastBoardPointRef.current;
|
|
1433
2068
|
finalizeCrossScreenDrop(
|
|
@@ -1542,6 +2177,110 @@ export function MultiScreenCanvas({
|
|
|
1542
2177
|
[],
|
|
1543
2178
|
);
|
|
1544
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
|
+
|
|
1545
2284
|
const scheduleFeedbackClear = useCallback(() => {
|
|
1546
2285
|
if (feedbackTimerRef.current !== null) {
|
|
1547
2286
|
window.clearTimeout(feedbackTimerRef.current);
|
|
@@ -1735,6 +2474,30 @@ export function MultiScreenCanvas({
|
|
|
1735
2474
|
e.preventDefault();
|
|
1736
2475
|
e.stopPropagation();
|
|
1737
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
|
+
};
|
|
1738
2501
|
dragState.current = {
|
|
1739
2502
|
type: "marquee",
|
|
1740
2503
|
originClient: { x: e.clientX, y: e.clientY },
|
|
@@ -1748,14 +2511,25 @@ export function MultiScreenCanvas({
|
|
|
1748
2511
|
if (!e.shiftKey) {
|
|
1749
2512
|
updateSelectedIds(() => []);
|
|
1750
2513
|
updateSelectedDraftIds(() => []);
|
|
2514
|
+
onLayerMarqueeSelectionChange?.([], {
|
|
2515
|
+
source: "marquee",
|
|
2516
|
+
additive: false,
|
|
2517
|
+
shiftKey: false,
|
|
2518
|
+
});
|
|
1751
2519
|
}
|
|
1752
2520
|
setIsDragging(true);
|
|
2521
|
+
void collectLayerMarqueeCandidates().then((candidates) => {
|
|
2522
|
+
if (dragState.current?.type !== "marquee") return;
|
|
2523
|
+
layerCandidates = candidates;
|
|
2524
|
+
reportLayerSelection(latestRect);
|
|
2525
|
+
});
|
|
1753
2526
|
|
|
1754
2527
|
const handleMouseMove = (ev: MouseEvent) => {
|
|
1755
2528
|
const state = dragState.current;
|
|
1756
2529
|
if (!state || state.type !== "marquee") return;
|
|
1757
2530
|
const nextPoint = getCanvasPoint(ev.clientX, ev.clientY);
|
|
1758
2531
|
const rect = normalizeRectFromPoints(state.originCanvas, nextPoint);
|
|
2532
|
+
latestRect = rect;
|
|
1759
2533
|
if (
|
|
1760
2534
|
!state.hasMoved &&
|
|
1761
2535
|
Math.hypot(
|
|
@@ -1798,6 +2572,7 @@ export function MultiScreenCanvas({
|
|
|
1798
2572
|
? dedupeIds([...state.baseSelectedDraftIds, ...hitDraftIds])
|
|
1799
2573
|
: hitDraftIds,
|
|
1800
2574
|
);
|
|
2575
|
+
reportLayerSelection(rect);
|
|
1801
2576
|
};
|
|
1802
2577
|
|
|
1803
2578
|
const handleMouseUp = () => {
|
|
@@ -1805,6 +2580,11 @@ export function MultiScreenCanvas({
|
|
|
1805
2580
|
if (state?.type === "marquee" && !state.hasMoved && !state.additive) {
|
|
1806
2581
|
updateSelectedIds(() => []);
|
|
1807
2582
|
updateSelectedDraftIds(() => []);
|
|
2583
|
+
onLayerMarqueeSelectionChange?.([], {
|
|
2584
|
+
source: "marquee",
|
|
2585
|
+
additive: false,
|
|
2586
|
+
shiftKey: false,
|
|
2587
|
+
});
|
|
1808
2588
|
}
|
|
1809
2589
|
finishDrag();
|
|
1810
2590
|
};
|
|
@@ -1812,11 +2592,13 @@ export function MultiScreenCanvas({
|
|
|
1812
2592
|
installDragListeners(handleMouseMove, handleMouseUp);
|
|
1813
2593
|
},
|
|
1814
2594
|
[
|
|
2595
|
+
collectLayerMarqueeCandidates,
|
|
1815
2596
|
finishDrag,
|
|
1816
2597
|
getCanvasPoint,
|
|
1817
2598
|
getCurrentDraftEntries,
|
|
1818
2599
|
getCurrentFrameEntries,
|
|
1819
2600
|
installDragListeners,
|
|
2601
|
+
onLayerMarqueeSelectionChange,
|
|
1820
2602
|
updateSelectedDraftIds,
|
|
1821
2603
|
updateSelectedIds,
|
|
1822
2604
|
],
|
|
@@ -2355,7 +3137,6 @@ export function MultiScreenCanvas({
|
|
|
2355
3137
|
points,
|
|
2356
3138
|
moved: releaseMoved,
|
|
2357
3139
|
toolProps,
|
|
2358
|
-
fallbackText: t("designEditor.tools.text"),
|
|
2359
3140
|
});
|
|
2360
3141
|
commitDraftPrimitive(nextDraft, state.originFrameId);
|
|
2361
3142
|
if (activeTool === undefined) {
|
|
@@ -2383,7 +3164,6 @@ export function MultiScreenCanvas({
|
|
|
2383
3164
|
installDragListeners,
|
|
2384
3165
|
onActiveToolChange,
|
|
2385
3166
|
onCreateScreenFrame,
|
|
2386
|
-
t,
|
|
2387
3167
|
toolProps,
|
|
2388
3168
|
],
|
|
2389
3169
|
);
|
|
@@ -2464,14 +3244,6 @@ export function MultiScreenCanvas({
|
|
|
2464
3244
|
}),
|
|
2465
3245
|
);
|
|
2466
3246
|
setAlignmentGuides(snap.guides);
|
|
2467
|
-
const primary = state.originDrafts[state.primaryId].geometry;
|
|
2468
|
-
showTransformFeedback(
|
|
2469
|
-
`X ${Math.round(primary.x + dx + snap.dx)} Y ${Math.round(
|
|
2470
|
-
primary.y + dy + snap.dy,
|
|
2471
|
-
)}`,
|
|
2472
|
-
ev.clientX,
|
|
2473
|
-
ev.clientY,
|
|
2474
|
-
);
|
|
2475
3247
|
|
|
2476
3248
|
// Primitive drop-into-container detection: check if the dragged draft
|
|
2477
3249
|
// is hovering over a committed container primitive on any screen.
|
|
@@ -2574,7 +3346,6 @@ export function MultiScreenCanvas({
|
|
|
2574
3346
|
getCurrentCanvasEntries,
|
|
2575
3347
|
installDragListeners,
|
|
2576
3348
|
persistDraftPrimitive,
|
|
2577
|
-
showTransformFeedback,
|
|
2578
3349
|
updateDraftPrimitives,
|
|
2579
3350
|
updatePrimitiveDropTarget,
|
|
2580
3351
|
updateSelectedDraftIds,
|
|
@@ -2816,14 +3587,6 @@ export function MultiScreenCanvas({
|
|
|
2816
3587
|
return next;
|
|
2817
3588
|
});
|
|
2818
3589
|
setAlignmentGuides(snap.guides);
|
|
2819
|
-
const primary = state.originFrames[state.primaryId];
|
|
2820
|
-
showTransformFeedback(
|
|
2821
|
-
`X ${Math.round(primary.x + dx + snap.dx)} Y ${Math.round(
|
|
2822
|
-
primary.y + dy + snap.dy,
|
|
2823
|
-
)}`,
|
|
2824
|
-
ev.clientX,
|
|
2825
|
-
ev.clientY,
|
|
2826
|
-
);
|
|
2827
3590
|
|
|
2828
3591
|
// When all dragged ids are committed primitive nodeIds (not screen
|
|
2829
3592
|
// frames), check for a container primitive drop target to highlight.
|
|
@@ -2889,7 +3652,6 @@ export function MultiScreenCanvas({
|
|
|
2889
3652
|
getCurrentFrameEntries,
|
|
2890
3653
|
installDragListeners,
|
|
2891
3654
|
onPick,
|
|
2892
|
-
showTransformFeedback,
|
|
2893
3655
|
updateFrameGeometry,
|
|
2894
3656
|
updatePrimitiveDropTarget,
|
|
2895
3657
|
updateSelectedDraftIds,
|
|
@@ -3287,6 +4049,7 @@ export function MultiScreenCanvas({
|
|
|
3287
4049
|
|
|
3288
4050
|
const handleMouseDown = useCallback(
|
|
3289
4051
|
(e: React.MouseEvent) => {
|
|
4052
|
+
claimKeyboardFocus();
|
|
3290
4053
|
// Clear any stale pick-suppression left over from a prior resize/rotate/move
|
|
3291
4054
|
// gesture that never received its trailing frame click — otherwise it would
|
|
3292
4055
|
// silently swallow this unrelated interaction.
|
|
@@ -3321,6 +4084,7 @@ export function MultiScreenCanvas({
|
|
|
3321
4084
|
beginMarquee,
|
|
3322
4085
|
beginPan,
|
|
3323
4086
|
beginPenNodeCreation,
|
|
4087
|
+
claimKeyboardFocus,
|
|
3324
4088
|
localActiveTool,
|
|
3325
4089
|
],
|
|
3326
4090
|
);
|
|
@@ -3817,6 +4581,10 @@ export function MultiScreenCanvas({
|
|
|
3817
4581
|
const penActive = effectiveTool === "pen";
|
|
3818
4582
|
const creationToolActive = Boolean(getDraftCreationTool(effectiveTool));
|
|
3819
4583
|
const canvasGestureActive = isDragging || isPanning;
|
|
4584
|
+
const boardSurfaceInteractive = shouldBoardSurfaceCapturePointerEvents({
|
|
4585
|
+
tool: effectiveTool,
|
|
4586
|
+
gestureActive: canvasGestureActive,
|
|
4587
|
+
});
|
|
3820
4588
|
const displayedPenPath = penGesturePreview
|
|
3821
4589
|
? penGesturePreview
|
|
3822
4590
|
: activePenPath && penPointer && activePenPath.nodes.length > 0
|
|
@@ -3884,10 +4652,14 @@ export function MultiScreenCanvas({
|
|
|
3884
4652
|
selectedDraftEntries.length === 1 && !selectedDraftGroupBounds
|
|
3885
4653
|
? selectedDraftEntries[0]
|
|
3886
4654
|
: null;
|
|
4655
|
+
const rootSelectedEntryCount =
|
|
4656
|
+
selectedFrameEntries.length + selectedDraftEntries.length;
|
|
4657
|
+
const showPassiveRootSelectionBoxes = rootSelectedEntryCount > 1;
|
|
3887
4658
|
return (
|
|
3888
4659
|
<div
|
|
3889
4660
|
ref={surfaceRef}
|
|
3890
|
-
|
|
4661
|
+
tabIndex={-1}
|
|
4662
|
+
className="relative h-full w-full select-none overflow-hidden outline-none"
|
|
3891
4663
|
onMouseDownCapture={handleMouseDown}
|
|
3892
4664
|
onMouseMove={handleMouseMove}
|
|
3893
4665
|
style={{ cursor: surfaceCursor, touchAction: "none" }}
|
|
@@ -3924,6 +4696,7 @@ export function MultiScreenCanvas({
|
|
|
3924
4696
|
>
|
|
3925
4697
|
{boardFileId &&
|
|
3926
4698
|
boardFileContent !== undefined &&
|
|
4699
|
+
hasBoardSurfaceContent(boardFileContent) &&
|
|
3927
4700
|
(() => {
|
|
3928
4701
|
const boardGeo = boardFrameGeometry ?? {
|
|
3929
4702
|
x: 0,
|
|
@@ -3931,40 +4704,87 @@ export function MultiScreenCanvas({
|
|
|
3931
4704
|
width: 8192,
|
|
3932
4705
|
height: 8192,
|
|
3933
4706
|
};
|
|
3934
|
-
|
|
3935
|
-
const
|
|
3936
|
-
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);
|
|
3937
4716
|
return (
|
|
3938
4717
|
// Overflow-hidden wrapper so the board iframe never bleeds outside
|
|
3939
4718
|
// its declared logical surface. z-index 0 keeps it below screen
|
|
3940
4719
|
// iframes (which have their own stacking context above this).
|
|
3941
4720
|
<div
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
height: boardH,
|
|
3948
|
-
overflow: "hidden",
|
|
3949
|
-
pointerEvents: "auto",
|
|
3950
|
-
// Board sits behind all screen iframes.
|
|
3951
|
-
zIndex: 0,
|
|
3952
|
-
}}
|
|
4721
|
+
className="[&_.design-canvas-iframe-wrapper]:shadow-none [&_.design-canvas-iframe-wrapper]:ring-0"
|
|
4722
|
+
style={getBoardSurfaceLayerStyle({
|
|
4723
|
+
geometry: boardGeo,
|
|
4724
|
+
interactive: boardSurfaceInteractive,
|
|
4725
|
+
})}
|
|
3953
4726
|
>
|
|
3954
4727
|
<DesignCanvas
|
|
3955
|
-
content={
|
|
3956
|
-
contentKey={
|
|
4728
|
+
content={boardRenderContent}
|
|
4729
|
+
contentKey={boardContentKey}
|
|
4730
|
+
screenId={boardFileId}
|
|
3957
4731
|
zoom={100}
|
|
3958
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}
|
|
3959
4746
|
editMode={boardEditMode}
|
|
3960
4747
|
interactMode={false}
|
|
3961
|
-
|
|
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}
|
|
3962
4776
|
onElementSelect={onBoardElementSelect ?? (() => {})}
|
|
4777
|
+
onElementMarqueeSelect={onBoardElementMarqueeSelect}
|
|
3963
4778
|
onElementHover={onBoardElementHover ?? (() => {})}
|
|
4779
|
+
onClearSelection={onBoardElementClear}
|
|
4780
|
+
onIframeHotkey={onBoardIframeHotkey}
|
|
4781
|
+
onIframeContextMenu={onBoardIframeContextMenu}
|
|
3964
4782
|
onVisualStructureChange={onBoardVisualStructureChange}
|
|
3965
4783
|
onVisualStyleChange={onBoardVisualStyleChange}
|
|
3966
4784
|
onVisualDuplicateChange={onBoardVisualDuplicateChange}
|
|
3967
4785
|
onTextContentChange={onBoardTextContentChange}
|
|
4786
|
+
onTextEditingStateChange={onBoardTextEditingStateChange}
|
|
4787
|
+
onElementDblClickText={onBoardElementDblClickText}
|
|
3968
4788
|
tweakValues={{}}
|
|
3969
4789
|
/>
|
|
3970
4790
|
</div>
|
|
@@ -4044,6 +4864,28 @@ export function MultiScreenCanvas({
|
|
|
4044
4864
|
/>
|
|
4045
4865
|
) : null}
|
|
4046
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
|
+
|
|
4047
4889
|
{displayedPenPath ? (
|
|
4048
4890
|
<PenPathOverlay path={displayedPenPath} closeHover={penCloseHover} />
|
|
4049
4891
|
) : null}
|
|
@@ -4181,7 +5023,7 @@ export function MultiScreenCanvas({
|
|
|
4181
5023
|
}}
|
|
4182
5024
|
>
|
|
4183
5025
|
<div className="flex h-full w-full items-start justify-between rounded-lg bg-muted/20 p-2">
|
|
4184
|
-
<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">
|
|
4185
5027
|
{duplicatePreview.display}
|
|
4186
5028
|
</span>
|
|
4187
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">
|
|
@@ -4199,37 +5041,29 @@ export function MultiScreenCanvas({
|
|
|
4199
5041
|
{transformBadge ? (
|
|
4200
5042
|
<div
|
|
4201
5043
|
data-transform-badge
|
|
4202
|
-
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"
|
|
4203
5045
|
style={{ left: transformBadge.x, top: transformBadge.y }}
|
|
4204
5046
|
>
|
|
4205
5047
|
{transformBadge.text}
|
|
4206
5048
|
</div>
|
|
4207
5049
|
) : null}
|
|
4208
5050
|
|
|
4209
|
-
{
|
|
4210
|
-
Uses the same style as the primitiveDropTarget overlay (2px accent border
|
|
4211
|
-
+ 14% accent fill) so it is visually consistent. */}
|
|
4212
|
-
{crossScreenTarget ? (
|
|
5051
|
+
{crossScreenDropGuide ? (
|
|
4213
5052
|
<span
|
|
4214
|
-
data-cross-screen-drop-
|
|
4215
|
-
className="pointer-events-none absolute z-
|
|
4216
|
-
style={{
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
pan.y + (SURFACE_PADDING + crossScreenTarget.geometry.y) * scale,
|
|
4222
|
-
width: Math.max(1, crossScreenTarget.geometry.width * scale),
|
|
4223
|
-
height: Math.max(1, crossScreenTarget.geometry.height * scale),
|
|
4224
|
-
border: "2px solid var(--design-editor-accent-color)",
|
|
4225
|
-
background:
|
|
4226
|
-
"color-mix(in srgb, var(--design-editor-accent-color) 14%, transparent)",
|
|
4227
|
-
}}
|
|
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
|
+
})}
|
|
4228
5060
|
/>
|
|
4229
5061
|
) : null}
|
|
4230
5062
|
|
|
4231
5063
|
{/* Cross-screen element drag: small ghost following the board-space cursor. */}
|
|
4232
|
-
{crossScreenGhost
|
|
5064
|
+
{crossScreenGhost &&
|
|
5065
|
+
!crossScreenSourceIsBoard &&
|
|
5066
|
+
!crossScreenDropGuide ? (
|
|
4233
5067
|
<span
|
|
4234
5068
|
data-cross-screen-drag-ghost
|
|
4235
5069
|
className="pointer-events-none absolute z-50 rounded border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-accent-color)]/20 shadow"
|
|
@@ -4778,7 +5612,7 @@ const Screen = memo(function Screen({
|
|
|
4778
5612
|
<span
|
|
4779
5613
|
data-frame-title
|
|
4780
5614
|
className={cn(
|
|
4781
|
-
"min-w-0 truncate text-[11px] font-medium",
|
|
5615
|
+
"min-w-0 truncate !text-[11px] font-medium",
|
|
4782
5616
|
emphasized
|
|
4783
5617
|
? "text-[var(--design-editor-accent-color)]"
|
|
4784
5618
|
: activeOrEmphasized
|
|
@@ -5124,7 +5958,7 @@ function BreakpointPreviewRow({
|
|
|
5124
5958
|
/>
|
|
5125
5959
|
<span
|
|
5126
5960
|
className={cn(
|
|
5127
|
-
"truncate text-[11px] font-medium",
|
|
5961
|
+
"truncate !text-[11px] font-medium",
|
|
5128
5962
|
isActive
|
|
5129
5963
|
? "text-[var(--design-editor-accent-color)]"
|
|
5130
5964
|
: "text-muted-foreground",
|
|
@@ -5232,16 +6066,63 @@ function GroupSelectionBox({
|
|
|
5232
6066
|
chromeScale={chromeScale}
|
|
5233
6067
|
chromeSettling={chromeSettling}
|
|
5234
6068
|
showRotate={false}
|
|
6069
|
+
filled
|
|
5235
6070
|
onStartResize={onStartResize}
|
|
5236
6071
|
onStartRotate={() => {}}
|
|
5237
6072
|
/>
|
|
5238
6073
|
);
|
|
5239
6074
|
}
|
|
5240
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
|
+
|
|
5241
6121
|
function SelectionBox({
|
|
5242
6122
|
geometry,
|
|
5243
6123
|
chromeScale,
|
|
5244
6124
|
chromeSettling,
|
|
6125
|
+
filled = false,
|
|
5245
6126
|
showRotate = true,
|
|
5246
6127
|
onStartResize,
|
|
5247
6128
|
onStartRotate,
|
|
@@ -5249,6 +6130,7 @@ function SelectionBox({
|
|
|
5249
6130
|
geometry: FrameGeometry;
|
|
5250
6131
|
chromeScale: number;
|
|
5251
6132
|
chromeSettling: boolean;
|
|
6133
|
+
filled?: boolean;
|
|
5252
6134
|
showRotate?: boolean;
|
|
5253
6135
|
onStartResize: (handle: ResizeHandle, e: React.MouseEvent) => void;
|
|
5254
6136
|
onStartRotate: (e: React.MouseEvent) => void;
|
|
@@ -5263,6 +6145,9 @@ function SelectionBox({
|
|
|
5263
6145
|
top: SURFACE_PADDING + geometry.y,
|
|
5264
6146
|
width: geometry.width,
|
|
5265
6147
|
height: geometry.height,
|
|
6148
|
+
background: filled
|
|
6149
|
+
? "var(--design-editor-selection-color)"
|
|
6150
|
+
: "transparent",
|
|
5266
6151
|
borderRadius: 13 * chromeScale,
|
|
5267
6152
|
borderWidth: 1.5 * chromeScale,
|
|
5268
6153
|
transition: getSelectionBoxTransition(chromeSettling),
|
|
@@ -5905,7 +6790,6 @@ function createDraftPrimitive({
|
|
|
5905
6790
|
points,
|
|
5906
6791
|
moved,
|
|
5907
6792
|
toolProps,
|
|
5908
|
-
fallbackText,
|
|
5909
6793
|
}: DraftPrimitiveInput): DraftPrimitive {
|
|
5910
6794
|
const id = createDraftId(tool);
|
|
5911
6795
|
const geometry = moved
|
|
@@ -5937,7 +6821,7 @@ function createDraftPrimitive({
|
|
|
5937
6821
|
id,
|
|
5938
6822
|
kind: "text",
|
|
5939
6823
|
geometry,
|
|
5940
|
-
text: toolProps?.text ??
|
|
6824
|
+
text: toolProps?.text ?? "",
|
|
5941
6825
|
fill: toolProps?.fill,
|
|
5942
6826
|
stroke: toolProps?.stroke,
|
|
5943
6827
|
autoSize: !moved,
|
|
@@ -6463,17 +7347,21 @@ export function parsePrimitivesFromScreen(
|
|
|
6463
7347
|
// Validity: must have non-zero size
|
|
6464
7348
|
if (width <= 0 || height <= 0) return;
|
|
6465
7349
|
|
|
6466
|
-
//
|
|
6467
|
-
//
|
|
6468
|
-
//
|
|
6469
|
-
// - NOT a text primitive (but text divs could be containers in principle;
|
|
6470
|
-
// 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.
|
|
6471
7353
|
const isDiv = tag === "div";
|
|
6472
7354
|
const isEllipse =
|
|
6473
7355
|
style.borderRadius === "50%" ||
|
|
6474
7356
|
style.borderRadius === "50% 50% 50% 50%";
|
|
6475
7357
|
const isTextAutoSize = style.display === "inline-block";
|
|
6476
|
-
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;
|
|
6477
7365
|
|
|
6478
7366
|
result.push({
|
|
6479
7367
|
nodeId,
|