@agent-native/core 0.83.0 → 0.84.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +34 -0
- package/corpus/core/README.md +2 -2
- package/corpus/core/docs/content/cloneable-saas.mdx +2 -2
- package/corpus/core/docs/content/evals.mdx +34 -12
- 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/evals.mdx +7 -0
- 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/evals.mdx +8 -0
- 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/evals.mdx +8 -0
- 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/evals.mdx +8 -0
- 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/evals.mdx +8 -0
- 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/evals.mdx +7 -0
- 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/evals.mdx +7 -0
- 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/evals.mdx +8 -0
- 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/evals.mdx +7 -0
- 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/evals.mdx +7 -0
- 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/eval/define-eval.ts +4 -1
- package/corpus/core/src/eval/report.ts +23 -8
- package/corpus/core/src/eval/runner.ts +41 -6
- package/corpus/core/src/eval/types.ts +11 -0
- package/corpus/core/src/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/AGENTS.md +9 -2
- package/corpus/templates/content/actions/_database-row-batch.ts +230 -0
- package/corpus/templates/content/actions/_database-utils.ts +17 -9
- package/corpus/templates/content/actions/_notion-action-utils.ts +26 -0
- package/corpus/templates/content/actions/connect-notion-status.ts +13 -4
- package/corpus/templates/content/actions/create-and-link-notion-page.ts +23 -0
- package/corpus/templates/content/actions/delete-database-items.ts +55 -0
- package/corpus/templates/content/actions/delete-document.ts +4 -4
- package/corpus/templates/content/actions/disconnect-notion.ts +16 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +1 -1
- package/corpus/templates/content/actions/duplicate-database-items.ts +191 -0
- package/corpus/templates/content/actions/link-notion-page.ts +10 -7
- package/corpus/templates/content/actions/list-notion-links.ts +3 -4
- package/corpus/templates/content/actions/pull-notion-page.ts +7 -9
- package/corpus/templates/content/actions/push-notion-page.ts +7 -9
- package/corpus/templates/content/actions/refresh-notion-sync-status.ts +24 -0
- package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +23 -0
- package/corpus/templates/content/actions/search-notion-pages.ts +67 -0
- package/corpus/templates/content/actions/unlink-notion-page.ts +23 -0
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +5 -5
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +27 -34
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +16 -13
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +10 -10
- package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +1 -1
- package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +18 -9
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +27 -34
- package/corpus/templates/content/app/components/editor/database/navigation-state.ts +10 -4
- package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +32 -17
- package/corpus/templates/content/app/global.css +6 -5
- package/corpus/templates/content/app/hooks/use-content-database.ts +35 -0
- package/corpus/templates/content/app/hooks/use-notion.ts +108 -99
- package/corpus/templates/content/changelog/2026-06-30-editor-floating-toolbars-now-use-theme-aware-colors-in-light.md +6 -0
- package/corpus/templates/content/package.json +3 -0
- package/corpus/templates/content/parity/README.md +69 -0
- package/corpus/templates/content/parity/eval-scenarios.ts +118 -0
- package/corpus/templates/content/parity/exceptions.allowlist.ts +17 -0
- package/corpus/templates/content/parity/matrix.md +28 -0
- package/corpus/templates/content/parity/matrix.ts +606 -0
- package/corpus/templates/content/parity/matrix.types.ts +42 -0
- package/corpus/templates/content/parity/parity-evals.eval.ts +4 -0
- package/corpus/templates/content/parity/render-matrix.ts +90 -0
- package/corpus/templates/content/parity/scenario-to-eval.ts +57 -0
- package/corpus/templates/content/shared/api.ts +11 -0
- package/corpus/templates/design/.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/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.js +1 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/eval/define-eval.d.ts.map +1 -1
- package/dist/eval/define-eval.js +2 -1
- package/dist/eval/define-eval.js.map +1 -1
- package/dist/eval/report.d.ts.map +1 -1
- package/dist/eval/report.js +19 -4
- package/dist/eval/report.js.map +1 -1
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +38 -6
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval/types.d.ts +11 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/eval/types.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- 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/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +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/evals.mdx +34 -12
- package/docs/content/faq.mdx +1 -1
- package/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
- package/docs/content/locales/ar-SA/evals.mdx +7 -0
- 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/evals.mdx +8 -0
- 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/evals.mdx +8 -0
- 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/evals.mdx +8 -0
- 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/evals.mdx +8 -0
- 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/evals.mdx +7 -0
- 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/evals.mdx +7 -0
- 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/evals.mdx +8 -0
- 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/evals.mdx +7 -0
- 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/evals.mdx +7 -0
- 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/content/server/routes/api/notion/disconnect.post.ts +0 -12
- 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
|
@@ -17,16 +17,19 @@ import {
|
|
|
17
17
|
IconAlignJustified,
|
|
18
18
|
IconAlignLeft,
|
|
19
19
|
IconAlignRight,
|
|
20
|
+
IconAngle,
|
|
20
21
|
IconArrowAutofitHeight,
|
|
21
22
|
IconArrowAutofitWidth,
|
|
22
23
|
IconArrowRight,
|
|
23
24
|
IconBackground,
|
|
24
25
|
IconBlur,
|
|
26
|
+
IconBorderCorners,
|
|
27
|
+
IconBorderRadius,
|
|
25
28
|
IconBorderStyle,
|
|
26
29
|
IconBrush,
|
|
30
|
+
IconCheck,
|
|
27
31
|
IconCode,
|
|
28
32
|
IconComponents,
|
|
29
|
-
IconDeviceDesktop,
|
|
30
33
|
IconExternalLink,
|
|
31
34
|
IconDroplet,
|
|
32
35
|
IconEye,
|
|
@@ -34,11 +37,10 @@ import {
|
|
|
34
37
|
IconFlipHorizontal,
|
|
35
38
|
IconFlipVertical,
|
|
36
39
|
IconFrame,
|
|
40
|
+
IconGridDots,
|
|
37
41
|
IconLayoutDistributeHorizontal,
|
|
38
42
|
IconLayoutGrid,
|
|
39
43
|
IconLoader2,
|
|
40
|
-
IconShieldCheck,
|
|
41
|
-
IconSlice,
|
|
42
44
|
IconLayoutAlignBottom,
|
|
43
45
|
IconLayoutAlignCenter,
|
|
44
46
|
IconLayoutAlignLeft,
|
|
@@ -49,11 +51,14 @@ import {
|
|
|
49
51
|
IconLetterSpacing,
|
|
50
52
|
IconLineHeight,
|
|
51
53
|
IconLink,
|
|
52
|
-
IconLock,
|
|
53
|
-
IconMaximize,
|
|
54
54
|
IconMinus,
|
|
55
55
|
IconPhoto,
|
|
56
56
|
IconPlus,
|
|
57
|
+
IconRadiusBottomLeft,
|
|
58
|
+
IconRadiusBottomRight,
|
|
59
|
+
IconRadiusTopLeft,
|
|
60
|
+
IconRadiusTopRight,
|
|
61
|
+
IconRefresh,
|
|
57
62
|
IconShadow,
|
|
58
63
|
IconSquare,
|
|
59
64
|
IconTypography,
|
|
@@ -64,6 +69,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
|
|
64
69
|
import {
|
|
65
70
|
useCallback,
|
|
66
71
|
useEffect,
|
|
72
|
+
useMemo,
|
|
67
73
|
useRef,
|
|
68
74
|
useState,
|
|
69
75
|
type ReactNode,
|
|
@@ -72,14 +78,6 @@ import { useParams } from "react-router";
|
|
|
72
78
|
import { toast } from "sonner";
|
|
73
79
|
|
|
74
80
|
import { Button } from "@/components/ui/button";
|
|
75
|
-
import {
|
|
76
|
-
Dialog,
|
|
77
|
-
DialogContent,
|
|
78
|
-
DialogDescription,
|
|
79
|
-
DialogFooter,
|
|
80
|
-
DialogHeader,
|
|
81
|
-
DialogTitle,
|
|
82
|
-
} from "@/components/ui/dialog";
|
|
83
81
|
import {
|
|
84
82
|
DropdownMenu,
|
|
85
83
|
DropdownMenuContent,
|
|
@@ -112,6 +110,7 @@ import { cn } from "@/lib/utils";
|
|
|
112
110
|
|
|
113
111
|
import {
|
|
114
112
|
AutoLayoutMatrix,
|
|
113
|
+
ConstraintsPreview,
|
|
115
114
|
ConstraintsWidget,
|
|
116
115
|
ExportSettingsPanel,
|
|
117
116
|
DesignColorPicker,
|
|
@@ -133,18 +132,80 @@ import {
|
|
|
133
132
|
} from "./inspector";
|
|
134
133
|
import { IconLayoutSettings } from "./inspector/design-icons";
|
|
135
134
|
import type { DesignPaintType } from "./inspector/DesignColorPicker";
|
|
136
|
-
import { InspectorAiActions } from "./inspector/InspectorAiActions";
|
|
137
135
|
import { ReviewPanel } from "./ReviewPanel";
|
|
138
136
|
import type { ReviewPanelProps } from "./ReviewPanel";
|
|
139
|
-
import { StatesPanel } from "./StatesPanel";
|
|
140
137
|
import type { StatesPanelProps } from "./StatesPanel";
|
|
141
138
|
import { TweaksPanelContent } from "./TweaksPanel";
|
|
142
139
|
import type { ElementInfo } from "./types";
|
|
143
140
|
|
|
144
141
|
export type InspectorTab = "design" | "tweaks";
|
|
145
142
|
|
|
143
|
+
const MIXED_VALUE = "Mixed";
|
|
144
|
+
|
|
145
|
+
function isMixedValue(value: string | undefined): boolean {
|
|
146
|
+
return value === MIXED_VALUE;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function sameOrMixed(values: string[]): string {
|
|
150
|
+
if (values.length === 0) return "";
|
|
151
|
+
const first = values[0] ?? "";
|
|
152
|
+
return values.every((value) => value === first) ? first : MIXED_VALUE;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function mixedElementFromSelection(
|
|
156
|
+
elements: ElementInfo[],
|
|
157
|
+
): ElementInfo | null {
|
|
158
|
+
const base = elements[elements.length - 1];
|
|
159
|
+
if (!base) return null;
|
|
160
|
+
const styleKeys = new Set<string>();
|
|
161
|
+
elements.forEach((element) => {
|
|
162
|
+
Object.keys(element.computedStyles).forEach((key) => styleKeys.add(key));
|
|
163
|
+
});
|
|
164
|
+
const computedStyles = Object.fromEntries(
|
|
165
|
+
Array.from(styleKeys).map((key) => [
|
|
166
|
+
key,
|
|
167
|
+
sameOrMixed(elements.map((element) => element.computedStyles[key] ?? "")),
|
|
168
|
+
]),
|
|
169
|
+
);
|
|
170
|
+
const minX = Math.min(...elements.map((element) => element.boundingRect.x));
|
|
171
|
+
const minY = Math.min(...elements.map((element) => element.boundingRect.y));
|
|
172
|
+
const maxX = Math.max(
|
|
173
|
+
...elements.map(
|
|
174
|
+
(element) => element.boundingRect.x + element.boundingRect.width,
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
const maxY = Math.max(
|
|
178
|
+
...elements.map(
|
|
179
|
+
(element) => element.boundingRect.y + element.boundingRect.height,
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
return {
|
|
183
|
+
...base,
|
|
184
|
+
tagName: sameOrMixed(elements.map((element) => element.tagName)),
|
|
185
|
+
id: undefined,
|
|
186
|
+
sourceId: undefined,
|
|
187
|
+
selector: base.selector,
|
|
188
|
+
classes: [],
|
|
189
|
+
computedStyles,
|
|
190
|
+
boundingRect: {
|
|
191
|
+
x: minX,
|
|
192
|
+
y: minY,
|
|
193
|
+
width: maxX - minX,
|
|
194
|
+
height: maxY - minY,
|
|
195
|
+
},
|
|
196
|
+
textContent: sameOrMixed(
|
|
197
|
+
elements.map((element) => element.textContent ?? ""),
|
|
198
|
+
),
|
|
199
|
+
htmlContent: undefined,
|
|
200
|
+
childElementCount: undefined,
|
|
201
|
+
isFlexChild: elements.every((element) => element.isFlexChild),
|
|
202
|
+
isFlexContainer: elements.every((element) => element.isFlexContainer),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
146
206
|
interface EditPanelProps {
|
|
147
207
|
selectedElement: ElementInfo | null;
|
|
208
|
+
selectedElements?: ElementInfo[];
|
|
148
209
|
pageStyles?: Record<string, string>;
|
|
149
210
|
zoom?: number;
|
|
150
211
|
headerTrailing?: ReactNode;
|
|
@@ -157,17 +218,10 @@ interface EditPanelProps {
|
|
|
157
218
|
onRequestTweaks?: (anchor: HTMLElement) => void;
|
|
158
219
|
onStyleChange: (property: string, value: string) => void;
|
|
159
220
|
onStylesChange?: (styles: Record<string, string>) => void;
|
|
160
|
-
onAutoLayoutConvert?: (
|
|
161
|
-
targetNodeId: string,
|
|
162
|
-
opts?: { direction?: "row" | "column"; gap?: string },
|
|
163
|
-
) => void;
|
|
164
221
|
onExport?: (settings: ExportSettingsValue[]) => void;
|
|
165
222
|
exporting?: boolean;
|
|
166
|
-
|
|
167
|
-
/** Active file id — forwarded to InspectorAiActions for agent context. */
|
|
223
|
+
/** Active file id — used for component prop editing context. */
|
|
168
224
|
fileId?: string;
|
|
169
|
-
/** Active file name (e.g. "index.html") — forwarded to InspectorAiActions. */
|
|
170
|
-
filename?: string;
|
|
171
225
|
/** Latest active file HTML, used to compose rapid sequential source edits. */
|
|
172
226
|
activeContent?: string;
|
|
173
227
|
/** Server revision for activeContent. */
|
|
@@ -223,6 +277,8 @@ interface EditPanelProps {
|
|
|
223
277
|
* omitted the "Create component" button is disabled.
|
|
224
278
|
*/
|
|
225
279
|
onCreateComponent?: (name: string) => void;
|
|
280
|
+
/** True when the selected element is already represented as a component. */
|
|
281
|
+
selectedElementAlreadyComponent?: boolean;
|
|
226
282
|
/** Suggested default name for the create-component dialog. */
|
|
227
283
|
defaultComponentName?: string;
|
|
228
284
|
/** Code-inspection data for the "Inspect code" popover. */
|
|
@@ -236,6 +292,12 @@ interface EditPanelProps {
|
|
|
236
292
|
export interface InspectCodeData {
|
|
237
293
|
/** Outer HTML of the selected element (inline / Alpine source). */
|
|
238
294
|
html?: string | null;
|
|
295
|
+
/** Selected element tag, used when only runtime selection metadata exists. */
|
|
296
|
+
tagName?: string | null;
|
|
297
|
+
/** Selected element id, used for the runtime-metadata fallback preview. */
|
|
298
|
+
id?: string | null;
|
|
299
|
+
/** Selected element classes, used for the runtime-metadata fallback preview. */
|
|
300
|
+
classes?: string[];
|
|
239
301
|
/**
|
|
240
302
|
* Resolved source file for real-app sources (localhost / fusion), when the
|
|
241
303
|
* resolveNodeToFile capability is available.
|
|
@@ -250,6 +312,23 @@ export interface InspectCodeData {
|
|
|
250
312
|
} | null;
|
|
251
313
|
}
|
|
252
314
|
|
|
315
|
+
const VOID_HTML_TAGS = new Set([
|
|
316
|
+
"area",
|
|
317
|
+
"base",
|
|
318
|
+
"br",
|
|
319
|
+
"col",
|
|
320
|
+
"embed",
|
|
321
|
+
"hr",
|
|
322
|
+
"img",
|
|
323
|
+
"input",
|
|
324
|
+
"link",
|
|
325
|
+
"meta",
|
|
326
|
+
"param",
|
|
327
|
+
"source",
|
|
328
|
+
"track",
|
|
329
|
+
"wbr",
|
|
330
|
+
]);
|
|
331
|
+
|
|
253
332
|
/**
|
|
254
333
|
* Normalize a CSS length-ish value typed by the user. If the input is bare
|
|
255
334
|
* digits (e.g. "32" or "32.5"), append the default unit so it parses as a
|
|
@@ -301,12 +380,14 @@ function PropInput({
|
|
|
301
380
|
defaultUnit?: string;
|
|
302
381
|
}) {
|
|
303
382
|
const [draft, setDraft] = useState(value);
|
|
383
|
+
const mixed = isMixedValue(value);
|
|
304
384
|
|
|
305
385
|
useEffect(() => {
|
|
306
386
|
setDraft(value);
|
|
307
387
|
}, [value]);
|
|
308
388
|
|
|
309
389
|
const commit = () => {
|
|
390
|
+
if (isMixedValue(draft)) return;
|
|
310
391
|
if (defaultUnit === undefined) {
|
|
311
392
|
if (draft !== value) onChange(draft);
|
|
312
393
|
return;
|
|
@@ -327,6 +408,9 @@ function PropInput({
|
|
|
327
408
|
<Input
|
|
328
409
|
type={type}
|
|
329
410
|
value={draft}
|
|
411
|
+
onFocus={(e) => {
|
|
412
|
+
if (mixed) e.currentTarget.select();
|
|
413
|
+
}}
|
|
330
414
|
onChange={(e) => {
|
|
331
415
|
setDraft(e.target.value);
|
|
332
416
|
// For length fields, defer the live update until blur/Enter so that
|
|
@@ -344,7 +428,7 @@ function PropInput({
|
|
|
344
428
|
}
|
|
345
429
|
}}
|
|
346
430
|
placeholder={placeholder}
|
|
347
|
-
className="h-6 min-w-0 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-[11px] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
431
|
+
className="h-6 min-w-0 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)] md:!text-[11px]"
|
|
348
432
|
/>
|
|
349
433
|
</div>
|
|
350
434
|
);
|
|
@@ -681,6 +765,18 @@ function ColorInput({
|
|
|
681
765
|
setSelectedStopId("stop-0");
|
|
682
766
|
};
|
|
683
767
|
|
|
768
|
+
if (isMixedValue(value)) {
|
|
769
|
+
return (
|
|
770
|
+
<button
|
|
771
|
+
type="button"
|
|
772
|
+
className="flex h-6 w-full items-center rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left !text-[11px] text-muted-foreground"
|
|
773
|
+
onClick={() => onChange("#000000")}
|
|
774
|
+
>
|
|
775
|
+
{MIXED_VALUE}
|
|
776
|
+
</button>
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
684
780
|
return (
|
|
685
781
|
<DesignColorPicker
|
|
686
782
|
key={pickerKey}
|
|
@@ -995,7 +1091,7 @@ function PropSelect({
|
|
|
995
1091
|
<div className="flex items-center gap-1.5">
|
|
996
1092
|
<FieldLabel>{label}</FieldLabel>
|
|
997
1093
|
<Select value={value} onValueChange={onChange}>
|
|
998
|
-
<SelectTrigger className="h-6 min-w-0 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
1094
|
+
<SelectTrigger className="h-6 min-w-0 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
999
1095
|
<SelectValue />
|
|
1000
1096
|
</SelectTrigger>
|
|
1001
1097
|
<SelectContent>
|
|
@@ -1003,7 +1099,7 @@ function PropSelect({
|
|
|
1003
1099
|
<SelectItem
|
|
1004
1100
|
key={opt.value}
|
|
1005
1101
|
value={opt.value}
|
|
1006
|
-
className="text-[11px]"
|
|
1102
|
+
className="!text-[11px]"
|
|
1007
1103
|
>
|
|
1008
1104
|
{opt.label}
|
|
1009
1105
|
</SelectItem>
|
|
@@ -1043,7 +1139,7 @@ function PropSlider({
|
|
|
1043
1139
|
step={step}
|
|
1044
1140
|
className="flex-1"
|
|
1045
1141
|
/>
|
|
1046
|
-
<span className="w-12 text-right text-[11px] tabular-nums text-muted-foreground">
|
|
1142
|
+
<span className="w-12 text-right !text-[11px] tabular-nums text-muted-foreground">
|
|
1047
1143
|
{value}
|
|
1048
1144
|
{unit}
|
|
1049
1145
|
</span>
|
|
@@ -1083,7 +1179,7 @@ function PanelSection({
|
|
|
1083
1179
|
onClick={() => setCollapsed((c) => !c)}
|
|
1084
1180
|
aria-expanded={!collapsed}
|
|
1085
1181
|
>
|
|
1086
|
-
<h3 className="min-w-0 flex-1 truncate text-[11px] font-semibold text-foreground">
|
|
1182
|
+
<h3 className="min-w-0 flex-1 truncate !text-[11px] font-semibold text-foreground">
|
|
1087
1183
|
{title}
|
|
1088
1184
|
</h3>
|
|
1089
1185
|
</button>
|
|
@@ -1092,7 +1188,7 @@ function PanelSection({
|
|
|
1092
1188
|
) : null}
|
|
1093
1189
|
</div>
|
|
1094
1190
|
{!collapsed && children ? (
|
|
1095
|
-
<div className="space-y-1.5 px-3 pb-3 pt-0.5 text-[11px]">
|
|
1191
|
+
<div className="space-y-1.5 px-3 pb-3 pt-0.5 !text-[11px]">
|
|
1096
1192
|
{children}
|
|
1097
1193
|
</div>
|
|
1098
1194
|
) : null}
|
|
@@ -1102,7 +1198,7 @@ function PanelSection({
|
|
|
1102
1198
|
|
|
1103
1199
|
function FieldLabel({ children }: { children: ReactNode }) {
|
|
1104
1200
|
return (
|
|
1105
|
-
<Label className="w-[64px] shrink-0 text-[11px] font-medium text-muted-foreground">
|
|
1201
|
+
<Label className="w-[64px] shrink-0 !text-[11px] font-medium text-muted-foreground">
|
|
1106
1202
|
{children}
|
|
1107
1203
|
</Label>
|
|
1108
1204
|
);
|
|
@@ -1110,7 +1206,7 @@ function FieldLabel({ children }: { children: ReactNode }) {
|
|
|
1110
1206
|
|
|
1111
1207
|
function SubsectionLabel({ children }: { children: ReactNode }) {
|
|
1112
1208
|
return (
|
|
1113
|
-
<p className="text-[11px] font-medium text-muted-foreground">{children}</p>
|
|
1209
|
+
<p className="!text-[11px] font-medium text-muted-foreground">{children}</p>
|
|
1114
1210
|
);
|
|
1115
1211
|
}
|
|
1116
1212
|
|
|
@@ -1152,7 +1248,7 @@ function DesignSpacingControl({
|
|
|
1152
1248
|
return (
|
|
1153
1249
|
<div className="space-y-1.5">
|
|
1154
1250
|
<div className="flex items-center justify-between gap-1.5">
|
|
1155
|
-
<Label className="text-[11px] font-medium text-muted-foreground">
|
|
1251
|
+
<Label className="!text-[11px] font-medium text-muted-foreground">
|
|
1156
1252
|
{label}
|
|
1157
1253
|
</Label>
|
|
1158
1254
|
<Tooltip>
|
|
@@ -1515,6 +1611,7 @@ function ScrubStyleInput({
|
|
|
1515
1611
|
ariaLabel,
|
|
1516
1612
|
tooltipLabel,
|
|
1517
1613
|
hideIcon = true,
|
|
1614
|
+
icon,
|
|
1518
1615
|
}: {
|
|
1519
1616
|
label: string;
|
|
1520
1617
|
value: string;
|
|
@@ -1529,15 +1626,18 @@ function ScrubStyleInput({
|
|
|
1529
1626
|
hideIcon?: boolean;
|
|
1530
1627
|
ariaLabel?: string;
|
|
1531
1628
|
tooltipLabel?: string;
|
|
1629
|
+
icon?: (props: { className?: string }) => ReactNode;
|
|
1532
1630
|
}) {
|
|
1631
|
+
const mixed = isMixedValue(value);
|
|
1533
1632
|
return (
|
|
1534
1633
|
<ScrubInput
|
|
1535
1634
|
label={label}
|
|
1536
1635
|
ariaLabel={ariaLabel}
|
|
1537
1636
|
tooltipLabel={tooltipLabel}
|
|
1538
|
-
icon={hideIcon ? null :
|
|
1539
|
-
value={value ? parseNumericValue(value) : (placeholder ?? 0)}
|
|
1637
|
+
icon={hideIcon ? null : icon}
|
|
1638
|
+
value={mixed ? 0 : value ? parseNumericValue(value) : (placeholder ?? 0)}
|
|
1540
1639
|
onChange={onChange}
|
|
1640
|
+
mixed={mixed}
|
|
1541
1641
|
unit={unit}
|
|
1542
1642
|
min={min}
|
|
1543
1643
|
max={max}
|
|
@@ -1545,7 +1645,7 @@ function ScrubStyleInput({
|
|
|
1545
1645
|
precision={1}
|
|
1546
1646
|
className="gap-0"
|
|
1547
1647
|
labelClassName={cn(
|
|
1548
|
-
"h-6 w-7 justify-center gap-0 rounded-l-md border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-[11px] tabular-nums",
|
|
1648
|
+
"h-6 w-7 justify-center gap-0 rounded-l-md rounded-r-none border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] !text-[11px] tabular-nums",
|
|
1549
1649
|
labelClassName,
|
|
1550
1650
|
)}
|
|
1551
1651
|
inputClassName={cn(
|
|
@@ -1620,17 +1720,28 @@ function colorHasVisibleAlpha(value: string | undefined): boolean {
|
|
|
1620
1720
|
return parsed.a > 0;
|
|
1621
1721
|
}
|
|
1622
1722
|
|
|
1723
|
+
function normalizedElementTagName(tagName: string | null | undefined): string {
|
|
1724
|
+
return tagName?.trim().toLowerCase() || "element";
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1623
1727
|
function inspectorObjectTitle(element: ElementInfo): string {
|
|
1624
|
-
const
|
|
1728
|
+
const componentName = componentNameForElementInfo(element);
|
|
1729
|
+
if (componentName) return componentName;
|
|
1730
|
+
const tag = normalizedElementTagName(element.tagName);
|
|
1625
1731
|
if (TEXT_TAGS.has(tag)) return "Text";
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1732
|
+
return tag;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
function componentNameForElementInfo(
|
|
1736
|
+
element: ElementInfo | null | undefined,
|
|
1737
|
+
): string {
|
|
1738
|
+
return element?.componentName?.trim() ?? "";
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function elementIsComponentSelection(
|
|
1742
|
+
element: ElementInfo | null | undefined,
|
|
1743
|
+
): boolean {
|
|
1744
|
+
return componentNameForElementInfo(element).length > 0;
|
|
1634
1745
|
}
|
|
1635
1746
|
|
|
1636
1747
|
function displayLabel(value: string | undefined): string {
|
|
@@ -1741,7 +1852,7 @@ const LEAF_TAGS = new Set([
|
|
|
1741
1852
|
* children show the full Auto layout section the same way does.
|
|
1742
1853
|
*/
|
|
1743
1854
|
function isContainerElement(element: ElementInfo): boolean {
|
|
1744
|
-
if (element.isFlexContainer) return true;
|
|
1855
|
+
if (element.isFlexContainer || element.isGridContainer) return true;
|
|
1745
1856
|
const tag = (element.tagName || "").toLowerCase();
|
|
1746
1857
|
if (TEXT_TAGS.has(tag) || LEAF_TAGS.has(tag)) return false;
|
|
1747
1858
|
return CONTAINER_TAGS.has(tag);
|
|
@@ -1758,6 +1869,13 @@ function isParentGrid(element: ElementInfo): boolean {
|
|
|
1758
1869
|
return Boolean(element.parentDisplay?.toLowerCase().includes("grid"));
|
|
1759
1870
|
}
|
|
1760
1871
|
|
|
1872
|
+
function elementHasLayoutChildren(element: ElementInfo): boolean {
|
|
1873
|
+
if (typeof element.childElementCount === "number") {
|
|
1874
|
+
return element.childElementCount > 0;
|
|
1875
|
+
}
|
|
1876
|
+
return Boolean(element.htmlContent?.match(/<\s*[a-zA-Z][^>]*>/));
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1761
1879
|
function parentFlexDirection(element: ElementInfo): AutoLayoutSizingAxis {
|
|
1762
1880
|
return element.parentLayout?.flexDirection?.includes("column")
|
|
1763
1881
|
? "vertical"
|
|
@@ -1977,10 +2095,10 @@ function commitElementSizing(
|
|
|
1977
2095
|
}
|
|
1978
2096
|
|
|
1979
2097
|
/**
|
|
1980
|
-
*
|
|
1981
|
-
* selection into a reusable component via `onSubmit`.
|
|
2098
|
+
* Header-anchored popover that prompts for a component name, then promotes the
|
|
2099
|
+
* current selection into a reusable component via `onSubmit`.
|
|
1982
2100
|
*/
|
|
1983
|
-
function
|
|
2101
|
+
function CreateComponentPopover({
|
|
1984
2102
|
open,
|
|
1985
2103
|
onOpenChange,
|
|
1986
2104
|
defaultName,
|
|
@@ -1992,8 +2110,9 @@ function CreateComponentDialog({
|
|
|
1992
2110
|
onSubmit: (name: string) => void;
|
|
1993
2111
|
}) {
|
|
1994
2112
|
const [name, setName] = useState(defaultName);
|
|
2113
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
1995
2114
|
|
|
1996
|
-
// Reset the field to the freshest default each time the
|
|
2115
|
+
// Reset the field to the freshest default each time the popover opens.
|
|
1997
2116
|
useEffect(() => {
|
|
1998
2117
|
if (open) setName(defaultName);
|
|
1999
2118
|
}, [open, defaultName]);
|
|
@@ -2006,55 +2125,86 @@ function CreateComponentDialog({
|
|
|
2006
2125
|
};
|
|
2007
2126
|
|
|
2008
2127
|
return (
|
|
2009
|
-
<
|
|
2010
|
-
<
|
|
2011
|
-
<
|
|
2012
|
-
<
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
"
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
</DialogHeader>
|
|
2021
|
-
<div className="space-y-1.5">
|
|
2022
|
-
<Label
|
|
2023
|
-
htmlFor="create-component-name"
|
|
2024
|
-
className="text-[11px] font-medium text-muted-foreground"
|
|
2025
|
-
>
|
|
2026
|
-
{"Component name" /* i18n-ignore design inspector label */}
|
|
2027
|
-
</Label>
|
|
2028
|
-
<Input
|
|
2029
|
-
id="create-component-name"
|
|
2030
|
-
autoFocus
|
|
2031
|
-
value={name}
|
|
2032
|
-
onChange={(e) => setName(e.target.value)}
|
|
2033
|
-
onKeyDown={(e) => {
|
|
2034
|
-
if (e.key === "Enter") {
|
|
2035
|
-
e.preventDefault();
|
|
2036
|
-
commit();
|
|
2128
|
+
<Popover open={open} onOpenChange={onOpenChange}>
|
|
2129
|
+
<Tooltip>
|
|
2130
|
+
<TooltipTrigger asChild>
|
|
2131
|
+
<PopoverTrigger asChild>
|
|
2132
|
+
<Button
|
|
2133
|
+
type="button"
|
|
2134
|
+
variant="ghost"
|
|
2135
|
+
size="icon"
|
|
2136
|
+
className="size-6 shrink-0 cursor-pointer rounded-md text-muted-foreground hover:text-foreground"
|
|
2137
|
+
aria-label={
|
|
2138
|
+
"Create component" /* i18n-ignore design inspector action */
|
|
2037
2139
|
}
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2140
|
+
>
|
|
2141
|
+
<IconComponents className="size-3.5" />
|
|
2142
|
+
</Button>
|
|
2143
|
+
</PopoverTrigger>
|
|
2144
|
+
</TooltipTrigger>
|
|
2145
|
+
<TooltipContent>
|
|
2146
|
+
{"Create component" /* i18n-ignore design inspector action */}
|
|
2147
|
+
</TooltipContent>
|
|
2148
|
+
</Tooltip>
|
|
2149
|
+
<PopoverContent
|
|
2150
|
+
align="end"
|
|
2151
|
+
sideOffset={8}
|
|
2152
|
+
className="w-80 p-3 text-[12px]"
|
|
2153
|
+
onOpenAutoFocus={(event) => {
|
|
2154
|
+
event.preventDefault();
|
|
2155
|
+
window.requestAnimationFrame(() => inputRef.current?.select());
|
|
2156
|
+
}}
|
|
2157
|
+
>
|
|
2158
|
+
<form
|
|
2159
|
+
className="space-y-3"
|
|
2160
|
+
onSubmit={(event) => {
|
|
2161
|
+
event.preventDefault();
|
|
2162
|
+
commit();
|
|
2163
|
+
}}
|
|
2164
|
+
>
|
|
2165
|
+
<div className="space-y-1">
|
|
2166
|
+
<h3 className="text-[13px] font-semibold text-foreground">
|
|
2167
|
+
{"Create component" /* i18n-ignore design inspector action */}
|
|
2168
|
+
</h3>
|
|
2169
|
+
<p className="!text-[11px] leading-4 text-muted-foreground">
|
|
2170
|
+
{
|
|
2171
|
+
"Name this element so it becomes a reusable component. The agent can then extract props and replace repeated instances." /* i18n-ignore design inspector copy */
|
|
2172
|
+
}
|
|
2173
|
+
</p>
|
|
2174
|
+
</div>
|
|
2175
|
+
<div className="space-y-1.5">
|
|
2176
|
+
<Label
|
|
2177
|
+
htmlFor="create-component-name"
|
|
2178
|
+
className="!text-[11px] font-medium text-muted-foreground"
|
|
2179
|
+
>
|
|
2180
|
+
{"Component name" /* i18n-ignore design inspector label */}
|
|
2181
|
+
</Label>
|
|
2182
|
+
<Input
|
|
2183
|
+
ref={inputRef}
|
|
2184
|
+
id="create-component-name"
|
|
2185
|
+
value={name}
|
|
2186
|
+
onChange={(e) => setName(e.target.value)}
|
|
2187
|
+
placeholder={
|
|
2188
|
+
"PrimaryButton" /* i18n-ignore design inspector placeholder */
|
|
2189
|
+
}
|
|
2190
|
+
/>
|
|
2191
|
+
</div>
|
|
2192
|
+
<div className="flex justify-end gap-2">
|
|
2193
|
+
<Button
|
|
2194
|
+
type="button"
|
|
2195
|
+
variant="ghost"
|
|
2196
|
+
size="sm"
|
|
2197
|
+
onClick={() => onOpenChange(false)}
|
|
2198
|
+
>
|
|
2199
|
+
{"Cancel" /* i18n-ignore design inspector action */}
|
|
2200
|
+
</Button>
|
|
2201
|
+
<Button type="submit" size="sm" disabled={!name.trim()}>
|
|
2202
|
+
{"Create" /* i18n-ignore design inspector action */}
|
|
2203
|
+
</Button>
|
|
2204
|
+
</div>
|
|
2205
|
+
</form>
|
|
2206
|
+
</PopoverContent>
|
|
2207
|
+
</Popover>
|
|
2058
2208
|
);
|
|
2059
2209
|
}
|
|
2060
2210
|
|
|
@@ -2105,6 +2255,141 @@ export function truncateOpeningTag(openTag: string, max = 32): string {
|
|
|
2105
2255
|
);
|
|
2106
2256
|
}
|
|
2107
2257
|
|
|
2258
|
+
function tagNameFromOpeningTag(openTag: string): string | null {
|
|
2259
|
+
const match = /^<\/?\s*([a-zA-Z][\w:-]*)/.exec(openTag.trim());
|
|
2260
|
+
return match?.[1]?.toLowerCase() ?? null;
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
function isSelfClosingOpeningTag(openTag: string, tagName: string): boolean {
|
|
2264
|
+
return /\/>\s*$/.test(openTag) || VOID_HTML_TAGS.has(tagName);
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
function escapeHtmlAttribute(value: string): string {
|
|
2268
|
+
return value
|
|
2269
|
+
.replace(/&/g, "&")
|
|
2270
|
+
.replace(/"/g, """)
|
|
2271
|
+
.replace(/</g, "<")
|
|
2272
|
+
.replace(/>/g, ">");
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
function fallbackOpeningTag(
|
|
2276
|
+
data: Pick<InspectCodeData, "tagName" | "id" | "classes">,
|
|
2277
|
+
) {
|
|
2278
|
+
const tag = normalizedElementTagName(data.tagName);
|
|
2279
|
+
const attrs: string[] = [];
|
|
2280
|
+
const id = data.id?.trim();
|
|
2281
|
+
const classes = data.classes?.map((item) => item.trim()).filter(Boolean);
|
|
2282
|
+
if (id) attrs.push(`id="${escapeHtmlAttribute(id)}"`);
|
|
2283
|
+
if (classes?.length) {
|
|
2284
|
+
attrs.push(`class="${escapeHtmlAttribute(classes.join(" "))}"`);
|
|
2285
|
+
}
|
|
2286
|
+
return `<${tag}${attrs.length ? ` ${attrs.join(" ")}` : ""}>`;
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
export function elementHtmlPreview(
|
|
2290
|
+
data: Pick<InspectCodeData, "html" | "tagName" | "id" | "classes">,
|
|
2291
|
+
): string | null {
|
|
2292
|
+
const openingTag = openingTagOf(data.html);
|
|
2293
|
+
const hasFallbackMetadata = Boolean(
|
|
2294
|
+
data.tagName?.trim() ||
|
|
2295
|
+
data.id?.trim() ||
|
|
2296
|
+
data.classes?.some((item) => item.trim()),
|
|
2297
|
+
);
|
|
2298
|
+
if (!openingTag && !hasFallbackMetadata) return null;
|
|
2299
|
+
const previewOpeningTag = openingTag ?? fallbackOpeningTag(data);
|
|
2300
|
+
const tagName =
|
|
2301
|
+
tagNameFromOpeningTag(previewOpeningTag) ??
|
|
2302
|
+
normalizedElementTagName(data.tagName);
|
|
2303
|
+
if (isSelfClosingOpeningTag(previewOpeningTag, tagName)) {
|
|
2304
|
+
return previewOpeningTag;
|
|
2305
|
+
}
|
|
2306
|
+
return `${previewOpeningTag}\n ...\n</${tagName}>`;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
type HtmlTokenKind = "plain" | "punctuation" | "tag" | "attribute" | "value";
|
|
2310
|
+
|
|
2311
|
+
interface HtmlToken {
|
|
2312
|
+
text: string;
|
|
2313
|
+
kind: HtmlTokenKind;
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
function tokenizeHtmlAttributes(source: string): HtmlToken[] {
|
|
2317
|
+
const tokens: HtmlToken[] = [];
|
|
2318
|
+
const attrPattern =
|
|
2319
|
+
/(\s+)([^\s=/>]+)(?:\s*(=)\s*("[^"]*"|'[^']*'|[^\s"'=<>`]+))?/g;
|
|
2320
|
+
let cursor = 0;
|
|
2321
|
+
for (const match of source.matchAll(attrPattern)) {
|
|
2322
|
+
const index = match.index ?? 0;
|
|
2323
|
+
if (index > cursor) {
|
|
2324
|
+
tokens.push({ text: source.slice(cursor, index), kind: "plain" });
|
|
2325
|
+
}
|
|
2326
|
+
tokens.push({ text: match[1] ?? "", kind: "plain" });
|
|
2327
|
+
tokens.push({ text: match[2] ?? "", kind: "attribute" });
|
|
2328
|
+
if (match[3]) tokens.push({ text: match[3], kind: "punctuation" });
|
|
2329
|
+
if (match[4]) tokens.push({ text: match[4], kind: "value" });
|
|
2330
|
+
cursor = index + match[0].length;
|
|
2331
|
+
}
|
|
2332
|
+
if (cursor < source.length) {
|
|
2333
|
+
tokens.push({ text: source.slice(cursor), kind: "plain" });
|
|
2334
|
+
}
|
|
2335
|
+
return tokens;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
function tokenizeHtmlTag(source: string): HtmlToken[] {
|
|
2339
|
+
const match = /^(<\/?)([a-zA-Z][\w:-]*)([\s\S]*?)(\/?>)$/.exec(source);
|
|
2340
|
+
if (!match) return [{ text: source, kind: "plain" }];
|
|
2341
|
+
return [
|
|
2342
|
+
{ text: match[1] ?? "", kind: "punctuation" },
|
|
2343
|
+
{ text: match[2] ?? "", kind: "tag" },
|
|
2344
|
+
...tokenizeHtmlAttributes(match[3] ?? ""),
|
|
2345
|
+
{ text: match[4] ?? "", kind: "punctuation" },
|
|
2346
|
+
];
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
function tokenizeHtml(source: string): HtmlToken[] {
|
|
2350
|
+
const tokens: HtmlToken[] = [];
|
|
2351
|
+
const tagPattern = /<\/?[a-zA-Z][\w:-]*(?:"[^"]*"|'[^']*'|[^'">])*>/g;
|
|
2352
|
+
let cursor = 0;
|
|
2353
|
+
for (const match of source.matchAll(tagPattern)) {
|
|
2354
|
+
const index = match.index ?? 0;
|
|
2355
|
+
if (index > cursor) {
|
|
2356
|
+
tokens.push({ text: source.slice(cursor, index), kind: "plain" });
|
|
2357
|
+
}
|
|
2358
|
+
tokens.push(...tokenizeHtmlTag(match[0]));
|
|
2359
|
+
cursor = index + match[0].length;
|
|
2360
|
+
}
|
|
2361
|
+
if (cursor < source.length) {
|
|
2362
|
+
tokens.push({ text: source.slice(cursor), kind: "plain" });
|
|
2363
|
+
}
|
|
2364
|
+
return tokens;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
function htmlTokenClassName(kind: HtmlTokenKind): string {
|
|
2368
|
+
switch (kind) {
|
|
2369
|
+
case "punctuation":
|
|
2370
|
+
return "text-muted-foreground/70";
|
|
2371
|
+
case "tag":
|
|
2372
|
+
return "text-[var(--design-editor-accent-color)]";
|
|
2373
|
+
case "attribute":
|
|
2374
|
+
return "text-foreground/90";
|
|
2375
|
+
case "value":
|
|
2376
|
+
return "text-[var(--design-editor-measure-color)]";
|
|
2377
|
+
default:
|
|
2378
|
+
return "text-muted-foreground";
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
function highlightedHtml(source: string): ReactNode {
|
|
2383
|
+
return tokenizeHtml(source).map((token, index) => (
|
|
2384
|
+
<span
|
|
2385
|
+
key={`${index}:${token.kind}`}
|
|
2386
|
+
className={htmlTokenClassName(token.kind)}
|
|
2387
|
+
>
|
|
2388
|
+
{token.text}
|
|
2389
|
+
</span>
|
|
2390
|
+
));
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2108
2393
|
/**
|
|
2109
2394
|
* Parse the top-level `key: value` pairs from an Alpine `x-data` object literal
|
|
2110
2395
|
* (e.g. `{ variant: 'outline', size: 'lg', disabled: false }`).
|
|
@@ -2467,22 +2752,12 @@ export function isBooleanPropValue(value: string): boolean {
|
|
|
2467
2752
|
* TODO: replace the read-only <pre> with an inline Monaco editor for richer
|
|
2468
2753
|
* (editable) code inspection once the editor bundle is wired into the inspector.
|
|
2469
2754
|
*/
|
|
2470
|
-
function InspectCodePopover({
|
|
2471
|
-
trigger,
|
|
2472
|
-
data,
|
|
2473
|
-
}: {
|
|
2474
|
-
trigger: ReactNode;
|
|
2475
|
-
data: InspectCodeData;
|
|
2476
|
-
}) {
|
|
2755
|
+
function InspectCodePopover({ data }: { data: InspectCodeData }) {
|
|
2477
2756
|
const [copied, setCopied] = useState(false);
|
|
2478
2757
|
const html = data.html ?? "";
|
|
2479
2758
|
const source = data.sourceLocation ?? null;
|
|
2480
|
-
const snippet =
|
|
2481
|
-
|
|
2482
|
-
const openingTag = (() => {
|
|
2483
|
-
const raw = openingTagOf(html);
|
|
2484
|
-
return raw ? truncateOpeningTag(raw) : null;
|
|
2485
|
-
})();
|
|
2759
|
+
const snippet =
|
|
2760
|
+
elementHtmlPreview(data) ?? source?.snippet ?? (html.trim() || null);
|
|
2486
2761
|
|
|
2487
2762
|
const handleCopy = () => {
|
|
2488
2763
|
if (!snippet) return;
|
|
@@ -2499,8 +2774,27 @@ function InspectCodePopover({
|
|
|
2499
2774
|
|
|
2500
2775
|
return (
|
|
2501
2776
|
<Popover>
|
|
2502
|
-
<
|
|
2503
|
-
|
|
2777
|
+
<Tooltip>
|
|
2778
|
+
<TooltipTrigger asChild>
|
|
2779
|
+
<PopoverTrigger asChild>
|
|
2780
|
+
<Button
|
|
2781
|
+
type="button"
|
|
2782
|
+
variant="ghost"
|
|
2783
|
+
size="icon"
|
|
2784
|
+
className="size-6 cursor-pointer rounded-md text-muted-foreground hover:text-foreground"
|
|
2785
|
+
aria-label={
|
|
2786
|
+
"Inspect code" /* i18n-ignore design inspector action */
|
|
2787
|
+
}
|
|
2788
|
+
>
|
|
2789
|
+
<IconCode className="size-3.5" />
|
|
2790
|
+
</Button>
|
|
2791
|
+
</PopoverTrigger>
|
|
2792
|
+
</TooltipTrigger>
|
|
2793
|
+
<TooltipContent>
|
|
2794
|
+
{"Inspect code" /* i18n-ignore design inspector action */}
|
|
2795
|
+
</TooltipContent>
|
|
2796
|
+
</Tooltip>
|
|
2797
|
+
<PopoverContent align="end" className="w-80 space-y-2 p-2 !text-[11px]">
|
|
2504
2798
|
<div className="flex items-center justify-between gap-2">
|
|
2505
2799
|
<span className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70">
|
|
2506
2800
|
{"Inspect code" /* i18n-ignore design inspector label */}
|
|
@@ -2521,16 +2815,6 @@ function InspectCodePopover({
|
|
|
2521
2815
|
</Button>
|
|
2522
2816
|
</div>
|
|
2523
2817
|
|
|
2524
|
-
{/* At-a-glance opening tag (tag name + attributes) above the full HTML. */}
|
|
2525
|
-
{openingTag && (
|
|
2526
|
-
<code
|
|
2527
|
-
className="block truncate rounded bg-[var(--design-editor-control-bg)] px-2 py-1 font-mono text-[10px] text-foreground"
|
|
2528
|
-
title={openingTag}
|
|
2529
|
-
>
|
|
2530
|
-
{openingTag}
|
|
2531
|
-
</code>
|
|
2532
|
-
)}
|
|
2533
|
-
|
|
2534
2818
|
{source && (
|
|
2535
2819
|
<div
|
|
2536
2820
|
className="flex items-center gap-1 rounded bg-[var(--design-editor-control-bg)] px-2 py-1"
|
|
@@ -2546,7 +2830,7 @@ function InspectCodePopover({
|
|
|
2546
2830
|
|
|
2547
2831
|
{snippet ? (
|
|
2548
2832
|
<pre className="max-h-64 overflow-auto rounded bg-[var(--design-editor-control-bg)] p-2 font-mono text-[10px] leading-relaxed text-foreground">
|
|
2549
|
-
<code>{snippet}</code>
|
|
2833
|
+
<code>{highlightedHtml(snippet)}</code>
|
|
2550
2834
|
</pre>
|
|
2551
2835
|
) : (
|
|
2552
2836
|
<p className="px-1 py-2 text-muted-foreground">
|
|
@@ -2569,7 +2853,7 @@ function InspectCodePopover({
|
|
|
2569
2853
|
type="button"
|
|
2570
2854
|
variant="outline"
|
|
2571
2855
|
size="sm"
|
|
2572
|
-
className="h-7 w-full gap-1.5 text-[11px]"
|
|
2856
|
+
className="h-7 w-full gap-1.5 !text-[11px]"
|
|
2573
2857
|
>
|
|
2574
2858
|
<IconExternalLink className="size-3.5" />
|
|
2575
2859
|
{"Open in VS Code" /* i18n-ignore design inspector action */}
|
|
@@ -2582,7 +2866,8 @@ function InspectCodePopover({
|
|
|
2582
2866
|
}
|
|
2583
2867
|
|
|
2584
2868
|
function elementTypeIcon(element: ElementInfo) {
|
|
2585
|
-
|
|
2869
|
+
if (elementIsComponentSelection(element)) return IconComponents;
|
|
2870
|
+
const tag = normalizedElementTagName(element.tagName);
|
|
2586
2871
|
if (TEXT_TAGS.has(tag)) return IconTypography;
|
|
2587
2872
|
if (tag === "img" || tag === "video" || tag === "picture") return IconPhoto;
|
|
2588
2873
|
if (tag === "svg" || tag === "path") return IconVector;
|
|
@@ -2592,53 +2877,80 @@ function elementTypeIcon(element: ElementInfo) {
|
|
|
2592
2877
|
|
|
2593
2878
|
function SelectionHeader({
|
|
2594
2879
|
element,
|
|
2880
|
+
selectedCount = 0,
|
|
2595
2881
|
onCreateComponent,
|
|
2596
|
-
|
|
2882
|
+
createComponentOpen = false,
|
|
2883
|
+
onCreateComponentOpenChange,
|
|
2884
|
+
showCreateComponentAction = true,
|
|
2885
|
+
defaultComponentName = "Component",
|
|
2886
|
+
inspectCode,
|
|
2597
2887
|
}: {
|
|
2598
2888
|
element: ElementInfo | null;
|
|
2889
|
+
selectedCount?: number;
|
|
2599
2890
|
/** Promote the current selection into a reusable component. Omit/undefined to disable. */
|
|
2600
|
-
onCreateComponent?: () => void;
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2891
|
+
onCreateComponent?: (name: string) => void;
|
|
2892
|
+
createComponentOpen?: boolean;
|
|
2893
|
+
onCreateComponentOpenChange?: (open: boolean) => void;
|
|
2894
|
+
showCreateComponentAction?: boolean;
|
|
2895
|
+
defaultComponentName?: string;
|
|
2896
|
+
/** Data for the "Inspect code" popover. When omitted the button renders disabled. */
|
|
2897
|
+
inspectCode?: InspectCodeData;
|
|
2607
2898
|
}) {
|
|
2608
2899
|
if (!element) return null;
|
|
2609
2900
|
|
|
2610
|
-
const title =
|
|
2901
|
+
const title =
|
|
2902
|
+
selectedCount > 1
|
|
2903
|
+
? `${selectedCount} selected`
|
|
2904
|
+
: inspectorObjectTitle(element);
|
|
2611
2905
|
const TypeIcon = elementTypeIcon(element);
|
|
2612
|
-
|
|
2613
|
-
const inspectTrigger = (
|
|
2614
|
-
<SectionIconButton
|
|
2615
|
-
label={"Inspect code" /* i18n-ignore design inspector action */}
|
|
2616
|
-
disabled={!inspectCodePopover}
|
|
2617
|
-
>
|
|
2618
|
-
<IconCode className="size-3.5" />
|
|
2619
|
-
</SectionIconButton>
|
|
2620
|
-
);
|
|
2906
|
+
const isComponentSelection = elementIsComponentSelection(element);
|
|
2621
2907
|
|
|
2622
2908
|
return (
|
|
2623
2909
|
<div className="flex min-h-8 shrink-0 items-center justify-between gap-2 border-b border-border/90 px-3">
|
|
2624
2910
|
{/* Node-type label. Rename lives in the layers panel and device sizing
|
|
2625
2911
|
lives elsewhere, so this is a plain non-interactive label. */}
|
|
2626
2912
|
<div className="flex min-w-0 items-center gap-1.5 text-left text-[13px] font-semibold text-foreground">
|
|
2627
|
-
<TypeIcon
|
|
2913
|
+
<TypeIcon
|
|
2914
|
+
className={cn(
|
|
2915
|
+
"size-3.5 shrink-0",
|
|
2916
|
+
isComponentSelection
|
|
2917
|
+
? "text-[var(--design-editor-component-color)]"
|
|
2918
|
+
: "text-muted-foreground",
|
|
2919
|
+
)}
|
|
2920
|
+
/>
|
|
2628
2921
|
<span className="truncate">{title}</span>
|
|
2629
2922
|
</div>
|
|
2630
2923
|
{/* Right-aligned quick actions: create-component + dev inspect (</>) */}
|
|
2631
2924
|
<div className="flex shrink-0 items-center gap-0.5">
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2925
|
+
{showCreateComponentAction ? (
|
|
2926
|
+
onCreateComponent && onCreateComponentOpenChange ? (
|
|
2927
|
+
<CreateComponentPopover
|
|
2928
|
+
open={createComponentOpen}
|
|
2929
|
+
onOpenChange={onCreateComponentOpenChange}
|
|
2930
|
+
defaultName={defaultComponentName}
|
|
2931
|
+
onSubmit={onCreateComponent}
|
|
2932
|
+
/>
|
|
2933
|
+
) : (
|
|
2934
|
+
<SectionIconButton
|
|
2935
|
+
label={
|
|
2936
|
+
"Create component" /* i18n-ignore design inspector action */
|
|
2937
|
+
}
|
|
2938
|
+
disabled
|
|
2939
|
+
>
|
|
2940
|
+
<IconComponents className="size-3.5" />
|
|
2941
|
+
</SectionIconButton>
|
|
2942
|
+
)
|
|
2943
|
+
) : null}
|
|
2944
|
+
{inspectCode ? (
|
|
2945
|
+
<InspectCodePopover data={inspectCode} />
|
|
2946
|
+
) : (
|
|
2947
|
+
<SectionIconButton
|
|
2948
|
+
label={"Inspect code" /* i18n-ignore design inspector action */}
|
|
2949
|
+
disabled
|
|
2950
|
+
>
|
|
2951
|
+
<IconCode className="size-3.5" />
|
|
2952
|
+
</SectionIconButton>
|
|
2953
|
+
)}
|
|
2642
2954
|
</div>
|
|
2643
2955
|
</div>
|
|
2644
2956
|
);
|
|
@@ -2664,13 +2976,13 @@ function InspectorTabsHeader({
|
|
|
2664
2976
|
<TabsList className="h-7 justify-start gap-0.5 rounded-none bg-transparent p-0">
|
|
2665
2977
|
<TabsTrigger
|
|
2666
2978
|
value="design"
|
|
2667
|
-
className="h-6 rounded-md px-1.5 text-[11px] font-semibold text-muted-foreground shadow-none transition-colors hover:text-foreground data-[state=active]:bg-[var(--design-editor-panel-raised-bg)] data-[state=active]:text-foreground data-[state=active]:shadow-none"
|
|
2979
|
+
className="h-6 rounded-md px-1.5 !text-[11px] font-semibold text-muted-foreground shadow-none transition-colors hover:text-foreground data-[state=active]:bg-[var(--design-editor-panel-raised-bg)] data-[state=active]:text-foreground data-[state=active]:shadow-none"
|
|
2668
2980
|
>
|
|
2669
2981
|
{"Design" /* i18n-ignore design inspector tab */}
|
|
2670
2982
|
</TabsTrigger>
|
|
2671
2983
|
<TabsTrigger
|
|
2672
2984
|
value="tweaks"
|
|
2673
|
-
className="h-6 rounded-md px-1.5 text-[11px] font-semibold text-muted-foreground shadow-none transition-colors hover:text-foreground data-[state=active]:bg-[var(--design-editor-panel-raised-bg)] data-[state=active]:text-foreground data-[state=active]:shadow-none"
|
|
2985
|
+
className="h-6 rounded-md px-1.5 !text-[11px] font-semibold text-muted-foreground shadow-none transition-colors hover:text-foreground data-[state=active]:bg-[var(--design-editor-panel-raised-bg)] data-[state=active]:text-foreground data-[state=active]:shadow-none"
|
|
2674
2986
|
>
|
|
2675
2987
|
{t("designEditor.tweaks")}
|
|
2676
2988
|
</TabsTrigger>
|
|
@@ -2706,7 +3018,7 @@ function SectionIconButton({
|
|
|
2706
3018
|
variant="ghost"
|
|
2707
3019
|
size="icon"
|
|
2708
3020
|
className={cn(
|
|
2709
|
-
"size-6 cursor-pointer rounded-md text-muted-foreground hover:text-foreground disabled:cursor-not-allowed",
|
|
3021
|
+
"size-6 shrink-0 cursor-pointer rounded-md text-muted-foreground hover:text-foreground disabled:cursor-not-allowed",
|
|
2710
3022
|
className,
|
|
2711
3023
|
)}
|
|
2712
3024
|
disabled={disabled}
|
|
@@ -2888,23 +3200,23 @@ function TypographyDetailsPopover({
|
|
|
2888
3200
|
>
|
|
2889
3201
|
<div className="flex items-center gap-1 border-b border-[var(--design-editor-control-border)] p-2.5">
|
|
2890
3202
|
<div className="flex rounded-md bg-[var(--design-editor-control-bg)] p-0.5">
|
|
2891
|
-
<span className="rounded bg-[var(--design-editor-panel-raised-bg)] px-2.5 py-1 text-[11px] font-semibold text-foreground">
|
|
3203
|
+
<span className="rounded bg-[var(--design-editor-panel-raised-bg)] px-2.5 py-1 !text-[11px] font-semibold text-foreground">
|
|
2892
3204
|
{"Basics" /* i18n-ignore design typography details tab */}
|
|
2893
3205
|
</span>
|
|
2894
|
-
<span className="px-2.5 py-1 text-[11px] font-medium text-muted-foreground">
|
|
3206
|
+
<span className="px-2.5 py-1 !text-[11px] font-medium text-muted-foreground">
|
|
2895
3207
|
{"Details" /* i18n-ignore design typography details tab */}
|
|
2896
3208
|
</span>
|
|
2897
|
-
<span className="px-2.5 py-1 text-[11px] font-medium text-muted-foreground">
|
|
3209
|
+
<span className="px-2.5 py-1 !text-[11px] font-medium text-muted-foreground">
|
|
2898
3210
|
{"Variable" /* i18n-ignore design typography details tab */}
|
|
2899
3211
|
</span>
|
|
2900
3212
|
</div>
|
|
2901
3213
|
</div>
|
|
2902
|
-
<div className="space-y-3 p-4 text-[11px]">
|
|
3214
|
+
<div className="space-y-3 p-4 !text-[11px]">
|
|
2903
3215
|
<div className="flex h-20 items-center justify-center rounded-md bg-[var(--design-editor-control-bg)] text-[18px] text-muted-foreground/80">
|
|
2904
3216
|
{"Preview" /* i18n-ignore design typography details preview */}
|
|
2905
3217
|
</div>
|
|
2906
3218
|
<div className="flex items-center justify-between gap-3">
|
|
2907
|
-
<span className="text-[11px] font-medium text-muted-foreground">
|
|
3219
|
+
<span className="!text-[11px] font-medium text-muted-foreground">
|
|
2908
3220
|
{"Text box" /* i18n-ignore design typography details label */}
|
|
2909
3221
|
</span>
|
|
2910
3222
|
<TextResizeControls
|
|
@@ -2927,7 +3239,6 @@ function CornerRadiusControl({
|
|
|
2927
3239
|
}) {
|
|
2928
3240
|
const t = useT();
|
|
2929
3241
|
const independentCornersLabel = t("editPanel.labels.independentCorners");
|
|
2930
|
-
const radius = cssLengthNumber(styles.borderRadius || "0");
|
|
2931
3242
|
const corners = {
|
|
2932
3243
|
topLeft: cssLengthNumber(styles.borderTopLeftRadius || styles.borderRadius),
|
|
2933
3244
|
topRight: cssLengthNumber(
|
|
@@ -2940,110 +3251,238 @@ function CornerRadiusControl({
|
|
|
2940
3251
|
styles.borderBottomLeftRadius || styles.borderRadius,
|
|
2941
3252
|
),
|
|
2942
3253
|
};
|
|
3254
|
+
const cornersDiffer =
|
|
3255
|
+
corners.topLeft !== corners.topRight ||
|
|
3256
|
+
corners.topLeft !== corners.bottomRight ||
|
|
3257
|
+
corners.topLeft !== corners.bottomLeft;
|
|
3258
|
+
const [showIndependentCorners, setShowIndependentCorners] =
|
|
3259
|
+
useState(cornersDiffer);
|
|
3260
|
+
const radius = cornersDiffer
|
|
3261
|
+
? corners.topLeft
|
|
3262
|
+
: cssLengthNumber(styles.borderRadius || String(corners.topLeft));
|
|
3263
|
+
const commitRadius = (value: number) => {
|
|
3264
|
+
const next = `${Math.max(0, Math.round(value))}px`;
|
|
3265
|
+
onStyleChange("borderRadius", next);
|
|
3266
|
+
if (!showIndependentCorners) return;
|
|
3267
|
+
onStyleChange("borderTopLeftRadius", next);
|
|
3268
|
+
onStyleChange("borderTopRightRadius", next);
|
|
3269
|
+
onStyleChange("borderBottomRightRadius", next);
|
|
3270
|
+
onStyleChange("borderBottomLeftRadius", next);
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3273
|
+
useEffect(() => {
|
|
3274
|
+
if (cornersDiffer) setShowIndependentCorners(true);
|
|
3275
|
+
}, [cornersDiffer]);
|
|
2943
3276
|
|
|
2944
3277
|
return (
|
|
2945
|
-
|
|
2946
|
-
<
|
|
3278
|
+
<>
|
|
3279
|
+
<AppearanceScrubField
|
|
2947
3280
|
label={t("editPanel.labels.cornerRadius")}
|
|
3281
|
+
icon={IconBorderRadius}
|
|
2948
3282
|
value={radius}
|
|
2949
|
-
onChange={
|
|
2950
|
-
onStyleChange("borderRadius", `${Math.max(0, Math.round(value))}px`)
|
|
2951
|
-
}
|
|
2952
|
-
unit="px"
|
|
3283
|
+
onChange={commitRadius}
|
|
2953
3284
|
min={0}
|
|
2954
3285
|
precision={1}
|
|
2955
|
-
labelClassName="w-24"
|
|
2956
|
-
inputClassName="h-6"
|
|
2957
3286
|
/>
|
|
2958
|
-
<
|
|
2959
|
-
<
|
|
2960
|
-
<
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
3287
|
+
<Tooltip>
|
|
3288
|
+
<TooltipTrigger asChild>
|
|
3289
|
+
<Button
|
|
3290
|
+
type="button"
|
|
3291
|
+
variant="ghost"
|
|
3292
|
+
size="icon"
|
|
3293
|
+
className={cn(
|
|
3294
|
+
"size-6 rounded-md text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground",
|
|
3295
|
+
showIndependentCorners &&
|
|
3296
|
+
"bg-[var(--design-editor-accent-color)]/20 text-[var(--design-editor-accent-color)] hover:bg-[var(--design-editor-accent-color)]/20 hover:text-[var(--design-editor-accent-color)]",
|
|
3297
|
+
)}
|
|
3298
|
+
aria-label={independentCornersLabel}
|
|
3299
|
+
aria-pressed={showIndependentCorners}
|
|
3300
|
+
onClick={() => setShowIndependentCorners((value) => !value)}
|
|
3301
|
+
>
|
|
3302
|
+
<IconBorderCorners className="size-3.5" />
|
|
3303
|
+
</Button>
|
|
3304
|
+
</TooltipTrigger>
|
|
3305
|
+
<TooltipContent>{independentCornersLabel}</TooltipContent>
|
|
3306
|
+
</Tooltip>
|
|
3307
|
+
{showIndependentCorners ? (
|
|
3308
|
+
<>
|
|
3309
|
+
<AppearanceScrubField
|
|
3310
|
+
label={t("editPanel.labels.topLeft")}
|
|
3311
|
+
icon={IconRadiusTopLeft}
|
|
3312
|
+
value={corners.topLeft}
|
|
3313
|
+
onChange={(value) =>
|
|
3314
|
+
onStyleChange(
|
|
3315
|
+
"borderTopLeftRadius",
|
|
3316
|
+
`${Math.max(0, Math.round(value))}px`,
|
|
3317
|
+
)
|
|
3318
|
+
}
|
|
3319
|
+
min={0}
|
|
3320
|
+
precision={1}
|
|
3321
|
+
/>
|
|
3322
|
+
<AppearanceScrubField
|
|
3323
|
+
label={t("editPanel.labels.topRight")}
|
|
3324
|
+
icon={IconRadiusTopRight}
|
|
3325
|
+
value={corners.topRight}
|
|
3326
|
+
onChange={(value) =>
|
|
3327
|
+
onStyleChange(
|
|
3328
|
+
"borderTopRightRadius",
|
|
3329
|
+
`${Math.max(0, Math.round(value))}px`,
|
|
3330
|
+
)
|
|
3331
|
+
}
|
|
3332
|
+
min={0}
|
|
3333
|
+
precision={1}
|
|
3334
|
+
/>
|
|
3335
|
+
<span aria-hidden="true" />
|
|
3336
|
+
<AppearanceScrubField
|
|
3337
|
+
label={t("editPanel.labels.bottomLeft")}
|
|
3338
|
+
icon={IconRadiusBottomLeft}
|
|
3339
|
+
value={corners.bottomLeft}
|
|
3340
|
+
onChange={(value) =>
|
|
3341
|
+
onStyleChange(
|
|
3342
|
+
"borderBottomLeftRadius",
|
|
3343
|
+
`${Math.max(0, Math.round(value))}px`,
|
|
3344
|
+
)
|
|
3345
|
+
}
|
|
3346
|
+
min={0}
|
|
3347
|
+
precision={1}
|
|
3348
|
+
/>
|
|
3349
|
+
<AppearanceScrubField
|
|
3350
|
+
label={t("editPanel.labels.bottomRight")}
|
|
3351
|
+
icon={IconRadiusBottomRight}
|
|
3352
|
+
value={corners.bottomRight}
|
|
3353
|
+
onChange={(value) =>
|
|
3354
|
+
onStyleChange(
|
|
3355
|
+
"borderBottomRightRadius",
|
|
3356
|
+
`${Math.max(0, Math.round(value))}px`,
|
|
3357
|
+
)
|
|
3358
|
+
}
|
|
3359
|
+
min={0}
|
|
3360
|
+
precision={1}
|
|
3361
|
+
/>
|
|
3362
|
+
<span aria-hidden="true" />
|
|
3363
|
+
</>
|
|
3364
|
+
) : null}
|
|
3365
|
+
</>
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
function AppearanceScrubField({
|
|
3370
|
+
label,
|
|
3371
|
+
icon,
|
|
3372
|
+
value,
|
|
3373
|
+
onChange,
|
|
3374
|
+
mixed = false,
|
|
3375
|
+
min,
|
|
3376
|
+
max,
|
|
3377
|
+
step,
|
|
3378
|
+
unit,
|
|
3379
|
+
precision,
|
|
3380
|
+
}: {
|
|
3381
|
+
label: string;
|
|
3382
|
+
icon: (props: { className?: string }) => ReactNode;
|
|
3383
|
+
value: number;
|
|
3384
|
+
onChange: (value: number) => void;
|
|
3385
|
+
mixed?: boolean;
|
|
3386
|
+
min?: number;
|
|
3387
|
+
max?: number;
|
|
3388
|
+
step?: number;
|
|
3389
|
+
unit?: string;
|
|
3390
|
+
precision?: number;
|
|
3391
|
+
}) {
|
|
3392
|
+
return (
|
|
3393
|
+
<ScrubInput
|
|
3394
|
+
label={label}
|
|
3395
|
+
ariaLabel={label}
|
|
3396
|
+
icon={icon}
|
|
3397
|
+
value={value}
|
|
3398
|
+
onChange={onChange}
|
|
3399
|
+
mixed={mixed}
|
|
3400
|
+
min={min}
|
|
3401
|
+
max={max}
|
|
3402
|
+
step={step}
|
|
3403
|
+
unit={unit}
|
|
3404
|
+
precision={precision}
|
|
3405
|
+
className="min-w-0 gap-0"
|
|
3406
|
+
labelClassName="h-6 w-7 justify-center gap-0 rounded-l-md rounded-r-none border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-muted-foreground [&>span]:sr-only"
|
|
3407
|
+
inputClassName="h-6 min-w-0 rounded-l-none rounded-r-md border-[var(--design-editor-control-border)] border-l-0 bg-[var(--design-editor-control-bg)] px-0 text-left shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
3408
|
+
/>
|
|
3409
|
+
);
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
function BlendModeMenu({
|
|
3413
|
+
styles,
|
|
3414
|
+
onStyleChange,
|
|
3415
|
+
}: {
|
|
3416
|
+
styles: Record<string, string>;
|
|
3417
|
+
onStyleChange: (property: string, value: string) => void;
|
|
3418
|
+
}) {
|
|
3419
|
+
const [open, setOpen] = useState(false);
|
|
3420
|
+
const blendMode = optionValue(
|
|
3421
|
+
BLEND_MODE_OPTIONS,
|
|
3422
|
+
styles.mixBlendMode || "normal",
|
|
3423
|
+
"normal",
|
|
3424
|
+
);
|
|
3425
|
+
const selectedBlendMode =
|
|
3426
|
+
blendMode === "normal" && styles.isolation !== "isolate"
|
|
3427
|
+
? "pass-through"
|
|
3428
|
+
: blendMode;
|
|
3429
|
+
const options = [
|
|
3430
|
+
{
|
|
3431
|
+
value: "pass-through",
|
|
3432
|
+
label: "Pass through", // i18n-ignore design blend mode label
|
|
3433
|
+
},
|
|
3434
|
+
...BLEND_MODE_OPTIONS,
|
|
3435
|
+
] as const;
|
|
3436
|
+
const selectBlendMode = (value: (typeof options)[number]["value"]) => {
|
|
3437
|
+
if (value === "pass-through") {
|
|
3438
|
+
onStyleChange("mixBlendMode", "normal");
|
|
3439
|
+
onStyleChange("isolation", "auto");
|
|
3440
|
+
return;
|
|
3441
|
+
}
|
|
3442
|
+
onStyleChange("mixBlendMode", value);
|
|
3443
|
+
if (value === "normal") onStyleChange("isolation", "isolate");
|
|
3444
|
+
};
|
|
3445
|
+
|
|
3446
|
+
return (
|
|
3447
|
+
<DropdownMenu open={open} onOpenChange={setOpen}>
|
|
3448
|
+
<DropdownMenuTrigger asChild>
|
|
3449
|
+
<Button
|
|
3450
|
+
type="button"
|
|
3451
|
+
variant="ghost"
|
|
3452
|
+
size="icon"
|
|
3453
|
+
aria-label={"Blend mode" /* i18n-ignore design inspector action */}
|
|
3454
|
+
aria-pressed={open}
|
|
3455
|
+
className={cn(
|
|
3456
|
+
"size-6 cursor-pointer rounded-md text-muted-foreground hover:text-foreground",
|
|
3457
|
+
open &&
|
|
3458
|
+
"bg-[var(--design-editor-accent-color)]/20 text-[var(--design-editor-accent-color)] hover:text-[var(--design-editor-accent-color)]",
|
|
3459
|
+
)}
|
|
2980
3460
|
>
|
|
2981
|
-
<
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
`${Math.max(0, Math.round(value))}px`,
|
|
3007
|
-
)
|
|
3008
|
-
}
|
|
3009
|
-
unit="px"
|
|
3010
|
-
min={0}
|
|
3011
|
-
precision={1}
|
|
3012
|
-
inputClassName="h-6"
|
|
3013
|
-
/>
|
|
3014
|
-
<ScrubInput
|
|
3015
|
-
label={t("editPanel.labels.bottomLeft")}
|
|
3016
|
-
value={corners.bottomLeft}
|
|
3017
|
-
onChange={(value) =>
|
|
3018
|
-
onStyleChange(
|
|
3019
|
-
"borderBottomLeftRadius",
|
|
3020
|
-
`${Math.max(0, Math.round(value))}px`,
|
|
3021
|
-
)
|
|
3022
|
-
}
|
|
3023
|
-
unit="px"
|
|
3024
|
-
min={0}
|
|
3025
|
-
precision={1}
|
|
3026
|
-
inputClassName="h-6"
|
|
3027
|
-
/>
|
|
3028
|
-
<ScrubInput
|
|
3029
|
-
label={t("editPanel.labels.bottomRight")}
|
|
3030
|
-
value={corners.bottomRight}
|
|
3031
|
-
onChange={(value) =>
|
|
3032
|
-
onStyleChange(
|
|
3033
|
-
"borderBottomRightRadius",
|
|
3034
|
-
`${Math.max(0, Math.round(value))}px`,
|
|
3035
|
-
)
|
|
3036
|
-
}
|
|
3037
|
-
unit="px"
|
|
3038
|
-
min={0}
|
|
3039
|
-
precision={1}
|
|
3040
|
-
inputClassName="h-6"
|
|
3041
|
-
/>
|
|
3042
|
-
</div>
|
|
3043
|
-
</div>
|
|
3044
|
-
</PopoverContent>
|
|
3045
|
-
</Popover>
|
|
3046
|
-
</div>
|
|
3461
|
+
<IconDroplet className="size-3.5" />
|
|
3462
|
+
</Button>
|
|
3463
|
+
</DropdownMenuTrigger>
|
|
3464
|
+
<DropdownMenuContent
|
|
3465
|
+
side="left"
|
|
3466
|
+
align="start"
|
|
3467
|
+
sideOffset={8}
|
|
3468
|
+
className="z-[100010] w-48 rounded-xl border-[var(--design-editor-control-border)] bg-[var(--design-editor-panel-bg)] p-1 text-[13px] text-foreground shadow-2xl"
|
|
3469
|
+
>
|
|
3470
|
+
{options.map((option) => (
|
|
3471
|
+
<DropdownMenuItem
|
|
3472
|
+
key={option.value}
|
|
3473
|
+
className="flex h-9 cursor-pointer items-center gap-3 rounded-md px-3 text-[13px] focus:bg-[var(--design-editor-control-bg)]"
|
|
3474
|
+
onSelect={() => selectBlendMode(option.value)}
|
|
3475
|
+
>
|
|
3476
|
+
<span className="flex size-4 shrink-0 items-center justify-center">
|
|
3477
|
+
{selectedBlendMode === option.value ? (
|
|
3478
|
+
<IconCheck className="size-4" />
|
|
3479
|
+
) : null}
|
|
3480
|
+
</span>
|
|
3481
|
+
<span>{option.label}</span>
|
|
3482
|
+
</DropdownMenuItem>
|
|
3483
|
+
))}
|
|
3484
|
+
</DropdownMenuContent>
|
|
3485
|
+
</DropdownMenu>
|
|
3047
3486
|
);
|
|
3048
3487
|
}
|
|
3049
3488
|
|
|
@@ -3098,7 +3537,6 @@ function StrokeLayerControl({
|
|
|
3098
3537
|
/>
|
|
3099
3538
|
</div>
|
|
3100
3539
|
<SectionIconButton
|
|
3101
|
-
className="opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100"
|
|
3102
3540
|
label={
|
|
3103
3541
|
visible
|
|
3104
3542
|
? t("editPanel.labels.hideLayer")
|
|
@@ -3123,7 +3561,6 @@ function StrokeLayerControl({
|
|
|
3123
3561
|
)}
|
|
3124
3562
|
</SectionIconButton>
|
|
3125
3563
|
<SectionIconButton
|
|
3126
|
-
className="opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100"
|
|
3127
3564
|
label={t("editPanel.labels.removeLayer")}
|
|
3128
3565
|
onClick={onRemove}
|
|
3129
3566
|
>
|
|
@@ -3133,7 +3570,7 @@ function StrokeLayerControl({
|
|
|
3133
3570
|
{/* design stroke geometry: position + weight side by side */}
|
|
3134
3571
|
<div className="grid grid-cols-2 gap-1.5">
|
|
3135
3572
|
<Select value={position} onValueChange={movePosition}>
|
|
3136
|
-
<SelectTrigger className="h-6 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
3573
|
+
<SelectTrigger className="h-6 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
3137
3574
|
<SelectValue />
|
|
3138
3575
|
</SelectTrigger>
|
|
3139
3576
|
<SelectContent>
|
|
@@ -3141,7 +3578,7 @@ function StrokeLayerControl({
|
|
|
3141
3578
|
<SelectItem
|
|
3142
3579
|
key={option.value}
|
|
3143
3580
|
value={option.value}
|
|
3144
|
-
className="text-[11px]"
|
|
3581
|
+
className="!text-[11px]"
|
|
3145
3582
|
>
|
|
3146
3583
|
{option.label}
|
|
3147
3584
|
</SelectItem>
|
|
@@ -3163,7 +3600,7 @@ function StrokeLayerControl({
|
|
|
3163
3600
|
min={0}
|
|
3164
3601
|
precision={1}
|
|
3165
3602
|
className="gap-0"
|
|
3166
|
-
labelClassName="h-6 w-6 justify-center gap-0 rounded-l-md border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-[11px] [&>span]:hidden"
|
|
3603
|
+
labelClassName="h-6 w-6 justify-center gap-0 rounded-l-md rounded-r-none border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] !text-[11px] [&>span]:hidden"
|
|
3167
3604
|
inputClassName="h-6 rounded-l-none rounded-r-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
3168
3605
|
/>
|
|
3169
3606
|
</div>
|
|
@@ -3268,26 +3705,50 @@ function readBlurFilter(value: string | undefined): number {
|
|
|
3268
3705
|
return match ? Math.max(0, Number(match[1])) : 0;
|
|
3269
3706
|
}
|
|
3270
3707
|
|
|
3708
|
+
function hasBlurFilter(value: string | undefined): boolean {
|
|
3709
|
+
return /blur\(/.test(value || "");
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
function setBlurFilterValue(value: string | undefined, blur: number): string {
|
|
3713
|
+
const blurFn = `blur(${Math.max(0, Math.round(blur))}px)`;
|
|
3714
|
+
const existing = compactCssValue(value, "");
|
|
3715
|
+
return existing.includes("blur(")
|
|
3716
|
+
? existing.replace(/blur\([^)]*\)/, blurFn)
|
|
3717
|
+
: blurFn;
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3720
|
+
function shadowColorWithOpacity(color: string, opacity: number): string {
|
|
3721
|
+
const parsed = parseCssColor(color);
|
|
3722
|
+
return parsed
|
|
3723
|
+
? rgbaToCss(withColorOpacity(parsed, opacity))
|
|
3724
|
+
: opacity <= 0
|
|
3725
|
+
? "rgba(0, 0, 0, 0)"
|
|
3726
|
+
: color;
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3271
3729
|
function ShadowEffectRow({
|
|
3272
3730
|
layer,
|
|
3273
3731
|
index,
|
|
3274
3732
|
onChange,
|
|
3275
3733
|
onRemove,
|
|
3734
|
+
onToggleVisibility,
|
|
3276
3735
|
}: {
|
|
3277
3736
|
layer: ShadowLayer;
|
|
3278
3737
|
index: number;
|
|
3279
3738
|
onChange: (patch: Partial<ShadowLayer>) => void;
|
|
3280
3739
|
onRemove: () => void;
|
|
3740
|
+
onToggleVisibility: () => void;
|
|
3281
3741
|
}) {
|
|
3282
3742
|
const t = useT();
|
|
3743
|
+
const visible = colorHasVisibleAlpha(layer.color);
|
|
3283
3744
|
return (
|
|
3284
3745
|
<Popover>
|
|
3285
|
-
{/* design effect row: [swatch+label+x,y,blur trigger (flex-1)] [remove] */}
|
|
3746
|
+
{/* design effect row: [swatch+label+x,y,blur trigger (flex-1)] [eye] [remove] */}
|
|
3286
3747
|
<div className="group flex items-center gap-1.5">
|
|
3287
3748
|
<PopoverTrigger asChild>
|
|
3288
3749
|
<button
|
|
3289
3750
|
type="button"
|
|
3290
|
-
className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
|
|
3751
|
+
className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left !text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
|
|
3291
3752
|
>
|
|
3292
3753
|
<span
|
|
3293
3754
|
className="size-4 shrink-0 rounded-sm border border-[var(--design-editor-control-border)]"
|
|
@@ -3305,7 +3766,20 @@ function ShadowEffectRow({
|
|
|
3305
3766
|
</button>
|
|
3306
3767
|
</PopoverTrigger>
|
|
3307
3768
|
<SectionIconButton
|
|
3308
|
-
|
|
3769
|
+
label={
|
|
3770
|
+
visible
|
|
3771
|
+
? t("editPanel.labels.hideLayer")
|
|
3772
|
+
: t("editPanel.labels.showLayer")
|
|
3773
|
+
}
|
|
3774
|
+
onClick={onToggleVisibility}
|
|
3775
|
+
>
|
|
3776
|
+
{visible ? (
|
|
3777
|
+
<IconEye className="size-3.5" />
|
|
3778
|
+
) : (
|
|
3779
|
+
<IconEyeOff className="size-3.5" />
|
|
3780
|
+
)}
|
|
3781
|
+
</SectionIconButton>
|
|
3782
|
+
<SectionIconButton
|
|
3309
3783
|
label={t("editPanel.labels.removeLayer")}
|
|
3310
3784
|
onClick={onRemove}
|
|
3311
3785
|
>
|
|
@@ -3326,7 +3800,7 @@ function ShadowEffectRow({
|
|
|
3326
3800
|
<button
|
|
3327
3801
|
type="button"
|
|
3328
3802
|
className={cn(
|
|
3329
|
-
"rounded border px-2 py-1 text-[11px]",
|
|
3803
|
+
"rounded border px-2 py-1 !text-[11px]",
|
|
3330
3804
|
layer.inset
|
|
3331
3805
|
? "border-[var(--design-editor-accent-color)] bg-[var(--design-editor-selection-color)] text-foreground"
|
|
3332
3806
|
: "border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-muted-foreground",
|
|
@@ -3565,7 +4039,7 @@ function TypographyProperties({
|
|
|
3565
4039
|
>
|
|
3566
4040
|
<SelectTrigger
|
|
3567
4041
|
aria-label={t("editPanel.labels.font")}
|
|
3568
|
-
className="h-6 w-full rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]"
|
|
4042
|
+
className="h-6 w-full rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]"
|
|
3569
4043
|
>
|
|
3570
4044
|
<SelectValue />
|
|
3571
4045
|
</SelectTrigger>
|
|
@@ -3574,7 +4048,7 @@ function TypographyProperties({
|
|
|
3574
4048
|
<SelectItem
|
|
3575
4049
|
key={opt.value}
|
|
3576
4050
|
value={opt.value}
|
|
3577
|
-
className="text-[11px]"
|
|
4051
|
+
className="!text-[11px]"
|
|
3578
4052
|
>
|
|
3579
4053
|
{opt.label}
|
|
3580
4054
|
</SelectItem>
|
|
@@ -3589,7 +4063,7 @@ function TypographyProperties({
|
|
|
3589
4063
|
value={styles.fontWeight || "400"}
|
|
3590
4064
|
onValueChange={(v) => onStyleChange("fontWeight", v)}
|
|
3591
4065
|
>
|
|
3592
|
-
<SelectTrigger className="h-6 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
4066
|
+
<SelectTrigger className="h-6 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
3593
4067
|
<SelectValue />
|
|
3594
4068
|
</SelectTrigger>
|
|
3595
4069
|
<SelectContent>
|
|
@@ -3597,7 +4071,7 @@ function TypographyProperties({
|
|
|
3597
4071
|
<SelectItem
|
|
3598
4072
|
key={opt.value}
|
|
3599
4073
|
value={opt.value}
|
|
3600
|
-
className="text-[11px]"
|
|
4074
|
+
className="!text-[11px]"
|
|
3601
4075
|
>
|
|
3602
4076
|
{opt.label}
|
|
3603
4077
|
</SelectItem>
|
|
@@ -3616,7 +4090,7 @@ function TypographyProperties({
|
|
|
3616
4090
|
min={1}
|
|
3617
4091
|
precision={1}
|
|
3618
4092
|
className="gap-0"
|
|
3619
|
-
labelClassName="h-6 w-6 justify-center gap-0 rounded-l-md border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-[11px] [&>span]:hidden"
|
|
4093
|
+
labelClassName="h-6 w-6 justify-center gap-0 rounded-l-md rounded-r-none border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] !text-[11px] [&>span]:hidden"
|
|
3620
4094
|
inputClassName="h-6 rounded-l-none rounded-r-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
3621
4095
|
/>
|
|
3622
4096
|
</div>
|
|
@@ -3635,7 +4109,7 @@ function TypographyProperties({
|
|
|
3635
4109
|
step={0.1}
|
|
3636
4110
|
precision={2}
|
|
3637
4111
|
className="gap-0"
|
|
3638
|
-
labelClassName="h-6 w-6 justify-center gap-0 rounded-l-md border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-[11px] [&>span]:hidden"
|
|
4112
|
+
labelClassName="h-6 w-6 justify-center gap-0 rounded-l-md rounded-r-none border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] !text-[11px] [&>span]:hidden"
|
|
3639
4113
|
inputClassName="h-6 rounded-l-none rounded-r-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
3640
4114
|
/>
|
|
3641
4115
|
<ScrubInput
|
|
@@ -3649,7 +4123,7 @@ function TypographyProperties({
|
|
|
3649
4123
|
unit="px"
|
|
3650
4124
|
precision={1}
|
|
3651
4125
|
className="gap-0"
|
|
3652
|
-
labelClassName="h-6 w-6 justify-center gap-0 rounded-l-md border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-[11px] [&>span]:hidden"
|
|
4126
|
+
labelClassName="h-6 w-6 justify-center gap-0 rounded-l-md rounded-r-none border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] !text-[11px] [&>span]:hidden"
|
|
3653
4127
|
inputClassName="h-6 rounded-l-none rounded-r-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
3654
4128
|
/>
|
|
3655
4129
|
</div>
|
|
@@ -3725,15 +4199,10 @@ function FlexContainerControls({
|
|
|
3725
4199
|
element,
|
|
3726
4200
|
onStyleChange,
|
|
3727
4201
|
onStylesChange,
|
|
3728
|
-
onAutoLayoutConvert,
|
|
3729
4202
|
}: {
|
|
3730
4203
|
element: ElementInfo;
|
|
3731
4204
|
onStyleChange: (property: string, value: string) => void;
|
|
3732
4205
|
onStylesChange?: (styles: Record<string, string>) => void;
|
|
3733
|
-
onAutoLayoutConvert?: (
|
|
3734
|
-
targetNodeId: string,
|
|
3735
|
-
opts?: { direction?: "row" | "column"; gap?: string },
|
|
3736
|
-
) => void;
|
|
3737
4206
|
}) {
|
|
3738
4207
|
const t = useT();
|
|
3739
4208
|
const styles = element.computedStyles;
|
|
@@ -3743,7 +4212,11 @@ function FlexContainerControls({
|
|
|
3743
4212
|
// control can show the right state (normal vs horizontal/vertical/wrap)
|
|
3744
4213
|
// instead of an empty "add" affordance.
|
|
3745
4214
|
const display = (styles.display || "").toLowerCase();
|
|
3746
|
-
const isFlex = display.includes("flex");
|
|
4215
|
+
const isFlex = element.isFlexContainer || display.includes("flex");
|
|
4216
|
+
const displayMode: AutoLayoutMatrixValue["display"] = isFlex
|
|
4217
|
+
? "flex"
|
|
4218
|
+
: "block";
|
|
4219
|
+
const hasLayoutChildren = elementHasLayoutChildren(element);
|
|
3747
4220
|
const flexDirection: AutoLayoutMatrixValue["direction"] =
|
|
3748
4221
|
styles.flexDirection?.includes("column") ? "vertical" : "horizontal";
|
|
3749
4222
|
const mainGapAxis =
|
|
@@ -3772,29 +4245,6 @@ function FlexContainerControls({
|
|
|
3772
4245
|
onStyleChange("display", "block");
|
|
3773
4246
|
};
|
|
3774
4247
|
|
|
3775
|
-
/**
|
|
3776
|
-
* "Convert to auto layout" — enables flex on a non-flex container and strips
|
|
3777
|
-
* position:absolute from direct children (full reflow via the autoLayout
|
|
3778
|
-
* substrate intent). Falls back to a style-only patch when no substrate
|
|
3779
|
-
* handler is wired (e.g. read-only or non-editable contexts).
|
|
3780
|
-
*/
|
|
3781
|
-
const handleConvertToAutoLayout = () => {
|
|
3782
|
-
if (onAutoLayoutConvert && element.sourceId) {
|
|
3783
|
-
onAutoLayoutConvert(element.sourceId, { direction: "row", gap: "8px" });
|
|
3784
|
-
return;
|
|
3785
|
-
}
|
|
3786
|
-
// Fallback: set display:flex on the parent only (no child strip).
|
|
3787
|
-
commitStylePatch(
|
|
3788
|
-
{
|
|
3789
|
-
display: "flex",
|
|
3790
|
-
flexDirection: "row",
|
|
3791
|
-
gap: "8px",
|
|
3792
|
-
alignItems: "flex-start",
|
|
3793
|
-
},
|
|
3794
|
-
onStyleChange,
|
|
3795
|
-
onStylesChange,
|
|
3796
|
-
);
|
|
3797
|
-
};
|
|
3798
4248
|
const padding = {
|
|
3799
4249
|
top: parseNumericValue(styles.paddingTop || "0"),
|
|
3800
4250
|
right: parseNumericValue(styles.paddingRight || "0"),
|
|
@@ -3831,29 +4281,16 @@ function FlexContainerControls({
|
|
|
3831
4281
|
horizontal: cssElementSize(element, "horizontal"),
|
|
3832
4282
|
vertical: cssElementSize(element, "vertical"),
|
|
3833
4283
|
},
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
4284
|
+
mixedSize: {
|
|
4285
|
+
horizontal: isMixedValue(styles.width),
|
|
4286
|
+
vertical: isMixedValue(styles.height),
|
|
4287
|
+
},
|
|
4288
|
+
display: displayMode,
|
|
3839
4289
|
spaceBetween: styles.justifyContent === "space-between",
|
|
3840
4290
|
};
|
|
3841
4291
|
|
|
3842
4292
|
return (
|
|
3843
4293
|
<div className="space-y-2">
|
|
3844
|
-
{/* Convert to auto layout — shown when the container is not yet flex */}
|
|
3845
|
-
{!isFlex ? (
|
|
3846
|
-
<Button
|
|
3847
|
-
type="button"
|
|
3848
|
-
variant="outline"
|
|
3849
|
-
size="sm"
|
|
3850
|
-
className="h-7 w-full gap-1.5 text-[11px]"
|
|
3851
|
-
onClick={handleConvertToAutoLayout}
|
|
3852
|
-
>
|
|
3853
|
-
<IconLayoutSettings className="size-3.5 shrink-0" />
|
|
3854
|
-
{"Convert to auto layout" /* i18n-ignore design inspector action */}
|
|
3855
|
-
</Button>
|
|
3856
|
-
) : null}
|
|
3857
4294
|
<AutoLayoutMatrix
|
|
3858
4295
|
value={autoLayoutValue}
|
|
3859
4296
|
onDisplayChange={handleDisplayChange}
|
|
@@ -3938,6 +4375,7 @@ function FlexContainerControls({
|
|
|
3938
4375
|
onChildMinMaxChange={(axis, kind, val) =>
|
|
3939
4376
|
commitElementMinMax(axis, kind, val, onStyleChange)
|
|
3940
4377
|
}
|
|
4378
|
+
showChildLayoutControls={hasLayoutChildren}
|
|
3941
4379
|
/>
|
|
3942
4380
|
</div>
|
|
3943
4381
|
);
|
|
@@ -4040,15 +4478,10 @@ function LayoutContextProperties({
|
|
|
4040
4478
|
element,
|
|
4041
4479
|
onStyleChange,
|
|
4042
4480
|
onStylesChange,
|
|
4043
|
-
onAutoLayoutConvert,
|
|
4044
4481
|
}: {
|
|
4045
4482
|
element: ElementInfo;
|
|
4046
4483
|
onStyleChange: (property: string, value: string) => void;
|
|
4047
4484
|
onStylesChange?: (styles: Record<string, string>) => void;
|
|
4048
|
-
onAutoLayoutConvert?: (
|
|
4049
|
-
targetNodeId: string,
|
|
4050
|
-
opts?: { direction?: "row" | "column"; gap?: string },
|
|
4051
|
-
) => void;
|
|
4052
4485
|
}) {
|
|
4053
4486
|
const t = useT();
|
|
4054
4487
|
const flexChild = isParentFlex(element);
|
|
@@ -4084,6 +4517,7 @@ function LayoutContextProperties({
|
|
|
4084
4517
|
sizingAxis="horizontal"
|
|
4085
4518
|
value={inferElementSizing(element, "horizontal")}
|
|
4086
4519
|
resolvedSize={cssElementSize(element, "horizontal")}
|
|
4520
|
+
mixed={isMixedValue(element.computedStyles.width)}
|
|
4087
4521
|
minMax={readElementMinMax(element, "horizontal")}
|
|
4088
4522
|
options={availableSizing.horizontal ?? ["fixed"]}
|
|
4089
4523
|
disabled={false}
|
|
@@ -4106,6 +4540,7 @@ function LayoutContextProperties({
|
|
|
4106
4540
|
sizingAxis="vertical"
|
|
4107
4541
|
value={inferElementSizing(element, "vertical")}
|
|
4108
4542
|
resolvedSize={cssElementSize(element, "vertical")}
|
|
4543
|
+
mixed={isMixedValue(element.computedStyles.height)}
|
|
4109
4544
|
minMax={readElementMinMax(element, "vertical")}
|
|
4110
4545
|
options={availableSizing.vertical ?? ["fixed"]}
|
|
4111
4546
|
disabled={false}
|
|
@@ -4141,7 +4576,6 @@ function LayoutContextProperties({
|
|
|
4141
4576
|
element={element}
|
|
4142
4577
|
onStyleChange={onStyleChange}
|
|
4143
4578
|
onStylesChange={onStylesChange}
|
|
4144
|
-
onAutoLayoutConvert={onAutoLayoutConvert}
|
|
4145
4579
|
/>
|
|
4146
4580
|
{childControls}
|
|
4147
4581
|
</PanelSection>
|
|
@@ -4214,7 +4648,7 @@ function LayoutGuideProperties({
|
|
|
4214
4648
|
}
|
|
4215
4649
|
>
|
|
4216
4650
|
{active ? (
|
|
4217
|
-
<div className="flex items-center gap-2 rounded-md bg-[var(--design-editor-control-bg)] px-2 py-1.5 text-[11px] text-muted-foreground">
|
|
4651
|
+
<div className="flex items-center gap-2 rounded-md bg-[var(--design-editor-control-bg)] px-2 py-1.5 !text-[11px] text-muted-foreground">
|
|
4218
4652
|
<IconLayoutGrid className="size-3.5 shrink-0" />
|
|
4219
4653
|
<span className="min-w-0 flex-1 truncate text-foreground">
|
|
4220
4654
|
{"Columns" /* i18n-ignore design inspector label */}
|
|
@@ -4222,7 +4656,7 @@ function LayoutGuideProperties({
|
|
|
4222
4656
|
<span className="shrink-0 tabular-nums">12</span>
|
|
4223
4657
|
</div>
|
|
4224
4658
|
) : (
|
|
4225
|
-
<p className="text-[11px] text-muted-foreground">
|
|
4659
|
+
<p className="!text-[11px] text-muted-foreground">
|
|
4226
4660
|
{"No layout guides" /* i18n-ignore design inspector empty state */}
|
|
4227
4661
|
</p>
|
|
4228
4662
|
)}
|
|
@@ -4309,6 +4743,7 @@ function PositionLayoutProperties({
|
|
|
4309
4743
|
? "center"
|
|
4310
4744
|
: "top",
|
|
4311
4745
|
};
|
|
4746
|
+
const [constraintsExpanded, setConstraintsExpanded] = useState(false);
|
|
4312
4747
|
|
|
4313
4748
|
const handleConstraintsChange = useCallback(
|
|
4314
4749
|
(value: ConstraintsValue) => {
|
|
@@ -4469,7 +4904,7 @@ function PositionLayoutProperties({
|
|
|
4469
4904
|
|
|
4470
4905
|
<div className="space-y-1.5">
|
|
4471
4906
|
<SubsectionLabel>{t("editPanel.labels.position")}</SubsectionLabel>
|
|
4472
|
-
<div className="grid grid-cols-
|
|
4907
|
+
<div className="grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_1.75rem] gap-2">
|
|
4473
4908
|
<ScrubStyleInput
|
|
4474
4909
|
label="X"
|
|
4475
4910
|
ariaLabel="X-position"
|
|
@@ -4488,7 +4923,39 @@ function PositionLayoutProperties({
|
|
|
4488
4923
|
inputClassName="h-6"
|
|
4489
4924
|
onChange={(v) => onStyleChange("top", `${Math.round(v)}px`)}
|
|
4490
4925
|
/>
|
|
4926
|
+
<Tooltip>
|
|
4927
|
+
<TooltipTrigger asChild>
|
|
4928
|
+
<button
|
|
4929
|
+
type="button"
|
|
4930
|
+
aria-label={
|
|
4931
|
+
"Constraints" /* i18n-ignore design inspector action */
|
|
4932
|
+
}
|
|
4933
|
+
aria-pressed={constraintsExpanded}
|
|
4934
|
+
onClick={() => setConstraintsExpanded((expanded) => !expanded)}
|
|
4935
|
+
className={cn(
|
|
4936
|
+
"flex size-7 items-center justify-center rounded-md transition-colors",
|
|
4937
|
+
"hover:bg-[var(--design-editor-control-bg)] hover:text-foreground",
|
|
4938
|
+
"focus:outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
|
|
4939
|
+
constraintsExpanded
|
|
4940
|
+
? "bg-[var(--design-editor-selection-color)] text-[var(--design-editor-accent-color)] hover:text-[var(--design-editor-accent-color)]"
|
|
4941
|
+
: "text-muted-foreground",
|
|
4942
|
+
)}
|
|
4943
|
+
>
|
|
4944
|
+
<ConstraintsPreview value={constraintsValue} />
|
|
4945
|
+
</button>
|
|
4946
|
+
</TooltipTrigger>
|
|
4947
|
+
<TooltipContent>
|
|
4948
|
+
{"Constraints" /* i18n-ignore design inspector tooltip */}
|
|
4949
|
+
</TooltipContent>
|
|
4950
|
+
</Tooltip>
|
|
4491
4951
|
</div>
|
|
4952
|
+
{constraintsExpanded ? (
|
|
4953
|
+
<ConstraintsWidget
|
|
4954
|
+
value={constraintsValue}
|
|
4955
|
+
onChange={handleConstraintsChange}
|
|
4956
|
+
className="pt-1"
|
|
4957
|
+
/>
|
|
4958
|
+
) : null}
|
|
4492
4959
|
</div>
|
|
4493
4960
|
|
|
4494
4961
|
<div className="space-y-1.5">
|
|
@@ -4496,7 +4963,12 @@ function PositionLayoutProperties({
|
|
|
4496
4963
|
<div className="flex items-center gap-2">
|
|
4497
4964
|
<div className="min-w-0 flex-1">
|
|
4498
4965
|
<ScrubStyleInput
|
|
4499
|
-
label="
|
|
4966
|
+
label="Rotation"
|
|
4967
|
+
ariaLabel={t("editPanel.labels.rotation")}
|
|
4968
|
+
tooltipLabel={t("editPanel.labels.rotation")}
|
|
4969
|
+
hideIcon={false}
|
|
4970
|
+
icon={IconAngle}
|
|
4971
|
+
labelClassName="[&>span]:sr-only"
|
|
4500
4972
|
value={`${parseRotationValue(styles.transform)}deg`}
|
|
4501
4973
|
unit="deg"
|
|
4502
4974
|
inputClassName="h-6"
|
|
@@ -4530,13 +5002,6 @@ function PositionLayoutProperties({
|
|
|
4530
5002
|
</InspectorSegment>
|
|
4531
5003
|
</div>
|
|
4532
5004
|
</div>
|
|
4533
|
-
|
|
4534
|
-
{constrainedPosition ? (
|
|
4535
|
-
<ConstraintsWidget
|
|
4536
|
-
value={constraintsValue}
|
|
4537
|
-
onChange={handleConstraintsChange}
|
|
4538
|
-
/>
|
|
4539
|
-
) : null}
|
|
4540
5005
|
</PanelSection>
|
|
4541
5006
|
);
|
|
4542
5007
|
}
|
|
@@ -4569,6 +5034,12 @@ function FillProperties({
|
|
|
4569
5034
|
const backgroundPositionLayers = isTextElement
|
|
4570
5035
|
? []
|
|
4571
5036
|
: splitCssLayers(styles.backgroundPosition || "");
|
|
5037
|
+
const fillIsMixed =
|
|
5038
|
+
isMixedValue(fillValue) ||
|
|
5039
|
+
isMixedValue(styles.backgroundImage) ||
|
|
5040
|
+
isMixedValue(styles.backgroundSize) ||
|
|
5041
|
+
isMixedValue(styles.backgroundRepeat) ||
|
|
5042
|
+
isMixedValue(styles.backgroundPosition);
|
|
4572
5043
|
const hasBackgroundLayer = !isTextElement && backgroundLayers.length > 0;
|
|
4573
5044
|
const hasVisibleFill =
|
|
4574
5045
|
isTextElement || colorHasVisibleAlpha(fillValue) || hasBackgroundLayer;
|
|
@@ -4638,6 +5109,18 @@ function FillProperties({
|
|
|
4638
5109
|
<SectionIconButton
|
|
4639
5110
|
label={t("editPanel.labels.addLayer")}
|
|
4640
5111
|
onClick={() => {
|
|
5112
|
+
if (fillIsMixed) {
|
|
5113
|
+
commitStylePatch(
|
|
5114
|
+
{
|
|
5115
|
+
color: "#000000",
|
|
5116
|
+
backgroundColor: "#ffffff",
|
|
5117
|
+
backgroundImage: "none",
|
|
5118
|
+
},
|
|
5119
|
+
onStyleChange,
|
|
5120
|
+
onStylesChange,
|
|
5121
|
+
);
|
|
5122
|
+
return;
|
|
5123
|
+
}
|
|
4641
5124
|
if (isTextElement) {
|
|
4642
5125
|
onStyleChange(
|
|
4643
5126
|
"color",
|
|
@@ -4668,7 +5151,13 @@ function FillProperties({
|
|
|
4668
5151
|
</>
|
|
4669
5152
|
}
|
|
4670
5153
|
>
|
|
4671
|
-
{
|
|
5154
|
+
{fillIsMixed ? (
|
|
5155
|
+
<p className="px-1.5 py-2 !text-[11px] text-muted-foreground">
|
|
5156
|
+
{
|
|
5157
|
+
"Click + to replace mixed content" /* i18n-ignore figma mixed fill hint */
|
|
5158
|
+
}
|
|
5159
|
+
</p>
|
|
5160
|
+
) : hasVisibleFill ? (
|
|
4672
5161
|
<div className="space-y-1.5">
|
|
4673
5162
|
{isTextElement || colorHasVisibleAlpha(fillValue) ? (
|
|
4674
5163
|
/* design row: [swatch+hex trigger (flex-1)] [eye] [remove] */
|
|
@@ -4700,7 +5189,6 @@ function FillProperties({
|
|
|
4700
5189
|
/>
|
|
4701
5190
|
</div>
|
|
4702
5191
|
<SectionIconButton
|
|
4703
|
-
className="opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100"
|
|
4704
5192
|
label={
|
|
4705
5193
|
isHidden
|
|
4706
5194
|
? t("editPanel.labels.showLayer")
|
|
@@ -4716,7 +5204,6 @@ function FillProperties({
|
|
|
4716
5204
|
)}
|
|
4717
5205
|
</SectionIconButton>
|
|
4718
5206
|
<SectionIconButton
|
|
4719
|
-
className="opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100"
|
|
4720
5207
|
label={t("editPanel.labels.removeLayer")}
|
|
4721
5208
|
onClick={() => {
|
|
4722
5209
|
if (isTextElement) {
|
|
@@ -4783,7 +5270,7 @@ function FillProperties({
|
|
|
4783
5270
|
<PopoverTrigger asChild>
|
|
4784
5271
|
<button
|
|
4785
5272
|
type="button"
|
|
4786
|
-
className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
|
|
5273
|
+
className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left !text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
|
|
4787
5274
|
>
|
|
4788
5275
|
<span
|
|
4789
5276
|
className="size-4 shrink-0 rounded-sm border border-[var(--design-editor-control-border)]"
|
|
@@ -4848,7 +5335,6 @@ function FillProperties({
|
|
|
4848
5335
|
</PopoverContent>
|
|
4849
5336
|
</Popover>
|
|
4850
5337
|
<SectionIconButton
|
|
4851
|
-
className="opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100"
|
|
4852
5338
|
label={
|
|
4853
5339
|
opacity <= 0
|
|
4854
5340
|
? t("editPanel.labels.showLayer")
|
|
@@ -4928,7 +5414,6 @@ function FillProperties({
|
|
|
4928
5414
|
)}
|
|
4929
5415
|
</SectionIconButton>
|
|
4930
5416
|
<SectionIconButton
|
|
4931
|
-
className="opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100"
|
|
4932
5417
|
label={t("editPanel.labels.removeLayer")}
|
|
4933
5418
|
onClick={removeLayer}
|
|
4934
5419
|
>
|
|
@@ -4960,6 +5445,15 @@ function StrokeProperties({
|
|
|
4960
5445
|
styles.outlineWidth,
|
|
4961
5446
|
styles.outlineStyle,
|
|
4962
5447
|
);
|
|
5448
|
+
const strokeIsMixed = [
|
|
5449
|
+
styles.borderWidth,
|
|
5450
|
+
styles.borderStyle,
|
|
5451
|
+
styles.borderColor,
|
|
5452
|
+
styles.outlineWidth,
|
|
5453
|
+
styles.outlineStyle,
|
|
5454
|
+
styles.outlineColor,
|
|
5455
|
+
styles.outlineOffset,
|
|
5456
|
+
].some(isMixedValue);
|
|
4963
5457
|
// Render the row whenever a stroke has been configured (non-zero width),
|
|
4964
5458
|
// even when its style is "none" (hidden). This mirrors Figma's behavior where
|
|
4965
5459
|
// hidden stroke rows remain present so the user can re-show them via the eye icon.
|
|
@@ -4973,6 +5467,20 @@ function StrokeProperties({
|
|
|
4973
5467
|
<SectionIconButton
|
|
4974
5468
|
label={t("editPanel.labels.addLayer")}
|
|
4975
5469
|
onClick={() => {
|
|
5470
|
+
if (strokeIsMixed) {
|
|
5471
|
+
commitStylePatch(
|
|
5472
|
+
{
|
|
5473
|
+
borderWidth: "1px",
|
|
5474
|
+
borderStyle: "solid",
|
|
5475
|
+
borderColor: "#000000",
|
|
5476
|
+
outlineWidth: "0px",
|
|
5477
|
+
outlineStyle: "none",
|
|
5478
|
+
},
|
|
5479
|
+
onStyleChange,
|
|
5480
|
+
onStylesChange,
|
|
5481
|
+
);
|
|
5482
|
+
return;
|
|
5483
|
+
}
|
|
4976
5484
|
if (!borderVisible) {
|
|
4977
5485
|
const borderColor = cssColorOrFallback(
|
|
4978
5486
|
styles.borderColor || styles.color,
|
|
@@ -5029,7 +5537,13 @@ function StrokeProperties({
|
|
|
5029
5537
|
</SectionIconButton>
|
|
5030
5538
|
}
|
|
5031
5539
|
>
|
|
5032
|
-
{
|
|
5540
|
+
{strokeIsMixed ? (
|
|
5541
|
+
<p className="px-1.5 py-2 !text-[11px] text-muted-foreground">
|
|
5542
|
+
{
|
|
5543
|
+
"Click + to replace mixed content" /* i18n-ignore figma mixed stroke hint */
|
|
5544
|
+
}
|
|
5545
|
+
</p>
|
|
5546
|
+
) : borderExists ? (
|
|
5033
5547
|
<StrokeLayerControl
|
|
5034
5548
|
kind="border"
|
|
5035
5549
|
visible={borderVisible}
|
|
@@ -5085,15 +5599,6 @@ function AppearanceProperties({
|
|
|
5085
5599
|
title={t("root.commandAppearance")}
|
|
5086
5600
|
actions={
|
|
5087
5601
|
<>
|
|
5088
|
-
{/* Opacity / blend-mode affordance — matches the design editor's pill icon */}
|
|
5089
|
-
<SectionIconButton
|
|
5090
|
-
label={
|
|
5091
|
-
"Opacity & blend mode" /* i18n-ignore design inspector action */
|
|
5092
|
-
}
|
|
5093
|
-
>
|
|
5094
|
-
<IconSlice className="size-3.5" />
|
|
5095
|
-
</SectionIconButton>
|
|
5096
|
-
{/* Visibility toggle */}
|
|
5097
5602
|
<SectionIconToggle
|
|
5098
5603
|
label={
|
|
5099
5604
|
hidden
|
|
@@ -5111,55 +5616,36 @@ function AppearanceProperties({
|
|
|
5111
5616
|
<IconEye className="size-3.5" />
|
|
5112
5617
|
)}
|
|
5113
5618
|
</SectionIconToggle>
|
|
5114
|
-
{
|
|
5115
|
-
<SectionIconButton
|
|
5116
|
-
label={"Styles" /* i18n-ignore design inspector action */}
|
|
5117
|
-
>
|
|
5118
|
-
<IconDroplet className="size-3.5" />
|
|
5119
|
-
</SectionIconButton>
|
|
5619
|
+
<BlendModeMenu styles={styles} onStyleChange={onStyleChange} />
|
|
5120
5620
|
</>
|
|
5121
5621
|
}
|
|
5122
5622
|
>
|
|
5123
|
-
<div className="grid grid-cols-2 gap-
|
|
5124
|
-
<
|
|
5623
|
+
<div className="grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto] items-center gap-x-2 gap-y-1.5">
|
|
5624
|
+
<p className="min-w-0 truncate !text-[11px] font-medium text-muted-foreground">
|
|
5625
|
+
{t("editPanel.labels.opacity")}
|
|
5626
|
+
</p>
|
|
5627
|
+
<p className="min-w-0 truncate !text-[11px] font-medium text-muted-foreground">
|
|
5628
|
+
{t("editPanel.labels.cornerRadius")}
|
|
5629
|
+
</p>
|
|
5630
|
+
<span aria-hidden="true" />
|
|
5631
|
+
<AppearanceScrubField
|
|
5125
5632
|
label={t("editPanel.labels.opacity")}
|
|
5126
|
-
|
|
5633
|
+
icon={IconGridDots}
|
|
5634
|
+
value={
|
|
5635
|
+
isMixedValue(styles.opacity)
|
|
5636
|
+
? 0
|
|
5637
|
+
: parseNumericValue(styles.opacity || "1") * 100
|
|
5638
|
+
}
|
|
5127
5639
|
onChange={(v) => onStyleChange("opacity", String(v / 100))}
|
|
5640
|
+
mixed={isMixedValue(styles.opacity)}
|
|
5128
5641
|
min={0}
|
|
5129
5642
|
max={100}
|
|
5130
5643
|
step={1}
|
|
5131
5644
|
unit="%"
|
|
5132
5645
|
precision={1}
|
|
5133
|
-
labelClassName="w-0 overflow-hidden"
|
|
5134
|
-
inputClassName="h-6 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] shadow-none"
|
|
5135
5646
|
/>
|
|
5136
|
-
{
|
|
5137
|
-
<Select
|
|
5138
|
-
value={optionValue(
|
|
5139
|
-
BLEND_MODE_OPTIONS,
|
|
5140
|
-
styles.mixBlendMode || "normal",
|
|
5141
|
-
"normal",
|
|
5142
|
-
)}
|
|
5143
|
-
onValueChange={(value) => onStyleChange("mixBlendMode", value)}
|
|
5144
|
-
>
|
|
5145
|
-
<SelectTrigger className="h-6 min-w-0 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
5146
|
-
<SelectValue />
|
|
5147
|
-
</SelectTrigger>
|
|
5148
|
-
<SelectContent>
|
|
5149
|
-
{BLEND_MODE_OPTIONS.map((opt) => (
|
|
5150
|
-
<SelectItem
|
|
5151
|
-
key={opt.value}
|
|
5152
|
-
value={opt.value}
|
|
5153
|
-
className="text-[11px]"
|
|
5154
|
-
>
|
|
5155
|
-
{opt.label}
|
|
5156
|
-
</SelectItem>
|
|
5157
|
-
))}
|
|
5158
|
-
</SelectContent>
|
|
5159
|
-
</Select>
|
|
5647
|
+
<CornerRadiusControl styles={styles} onStyleChange={onStyleChange} />
|
|
5160
5648
|
</div>
|
|
5161
|
-
{/* M7: use CornerRadiusControl (with independent-corners toggle) instead of bare ScrubInput */}
|
|
5162
|
-
<CornerRadiusControl styles={styles} onStyleChange={onStyleChange} />
|
|
5163
5649
|
</PanelSection>
|
|
5164
5650
|
);
|
|
5165
5651
|
}
|
|
@@ -5176,11 +5662,19 @@ function EffectsProperties({
|
|
|
5176
5662
|
const t = useT();
|
|
5177
5663
|
const styles = element.computedStyles;
|
|
5178
5664
|
const blurValue = readBlurFilter(styles.filter);
|
|
5665
|
+
const filterHasBlur = hasBlurFilter(styles.filter);
|
|
5179
5666
|
// M5 · Background (backdrop) blur is a distinct design effect type, backed by
|
|
5180
5667
|
// CSS `backdrop-filter: blur()` (vs layer blur's `filter: blur()`).
|
|
5181
|
-
const
|
|
5182
|
-
styles.backdropFilter || styles.webkitBackdropFilter
|
|
5183
|
-
);
|
|
5668
|
+
const backdropFilterValue =
|
|
5669
|
+
styles.backdropFilter || styles.webkitBackdropFilter;
|
|
5670
|
+
const backdropFilterHasBlur = hasBlurFilter(backdropFilterValue);
|
|
5671
|
+
const backdropBlurValue = readBlurFilter(backdropFilterValue);
|
|
5672
|
+
const [hiddenEffectStash, setHiddenEffectStash] = useState<
|
|
5673
|
+
Record<string, string>
|
|
5674
|
+
>({});
|
|
5675
|
+
const effectStashKey = elementIdentityKey(element);
|
|
5676
|
+
const layerBlurStashKey = `${effectStashKey}:filter:blur`;
|
|
5677
|
+
const backdropBlurStashKey = `${effectStashKey}:backdrop-filter:blur`;
|
|
5184
5678
|
const shadowLayers = parseShadowLayers(styles.boxShadow);
|
|
5185
5679
|
const setShadowLayers = (layers: ShadowLayer[]) => {
|
|
5186
5680
|
const boxShadow = serializeShadowLayers(layers);
|
|
@@ -5213,21 +5707,21 @@ function EffectsProperties({
|
|
|
5213
5707
|
</DropdownMenuTrigger>
|
|
5214
5708
|
<DropdownMenuContent align="end" className="min-w-44">
|
|
5215
5709
|
<DropdownMenuItem
|
|
5216
|
-
className="gap-2 text-[11px]"
|
|
5710
|
+
className="gap-2 !text-[11px]"
|
|
5217
5711
|
onSelect={addDropShadow}
|
|
5218
5712
|
>
|
|
5219
5713
|
<IconShadow className="size-3.5" />
|
|
5220
5714
|
{t("editPanel.labels.dropShadow")}
|
|
5221
5715
|
</DropdownMenuItem>
|
|
5222
5716
|
<DropdownMenuItem
|
|
5223
|
-
className="gap-2 text-[11px]"
|
|
5717
|
+
className="gap-2 !text-[11px]"
|
|
5224
5718
|
onSelect={addLayerBlur}
|
|
5225
5719
|
>
|
|
5226
5720
|
<IconBlur className="size-3.5" />
|
|
5227
5721
|
{t("editPanel.labels.layerBlur")}
|
|
5228
5722
|
</DropdownMenuItem>
|
|
5229
5723
|
<DropdownMenuItem
|
|
5230
|
-
className="gap-2 text-[11px]"
|
|
5724
|
+
className="gap-2 !text-[11px]"
|
|
5231
5725
|
onSelect={addBackgroundBlur}
|
|
5232
5726
|
>
|
|
5233
5727
|
<IconBackground className="size-3.5" />
|
|
@@ -5252,6 +5746,41 @@ function EffectsProperties({
|
|
|
5252
5746
|
);
|
|
5253
5747
|
setShadowLayers(next);
|
|
5254
5748
|
}}
|
|
5749
|
+
onToggleVisibility={() => {
|
|
5750
|
+
const visible = colorHasVisibleAlpha(layer.color);
|
|
5751
|
+
const shadowStashKey = `${effectStashKey}:shadow:${layer.id}`;
|
|
5752
|
+
if (visible) {
|
|
5753
|
+
setHiddenEffectStash((prev) => ({
|
|
5754
|
+
...prev,
|
|
5755
|
+
[shadowStashKey]: layer.color,
|
|
5756
|
+
}));
|
|
5757
|
+
const next = shadowLayers.map((candidate) =>
|
|
5758
|
+
candidate.id === layer.id
|
|
5759
|
+
? {
|
|
5760
|
+
...candidate,
|
|
5761
|
+
color: shadowColorWithOpacity(candidate.color, 0),
|
|
5762
|
+
}
|
|
5763
|
+
: candidate,
|
|
5764
|
+
);
|
|
5765
|
+
setShadowLayers(next);
|
|
5766
|
+
return;
|
|
5767
|
+
}
|
|
5768
|
+
|
|
5769
|
+
const restored =
|
|
5770
|
+
hiddenEffectStash[shadowStashKey] ??
|
|
5771
|
+
shadowColorWithOpacity(layer.color, 25);
|
|
5772
|
+
setHiddenEffectStash((prev) => {
|
|
5773
|
+
const next = { ...prev };
|
|
5774
|
+
delete next[shadowStashKey];
|
|
5775
|
+
return next;
|
|
5776
|
+
});
|
|
5777
|
+
const next = shadowLayers.map((candidate) =>
|
|
5778
|
+
candidate.id === layer.id
|
|
5779
|
+
? { ...candidate, color: restored }
|
|
5780
|
+
: candidate,
|
|
5781
|
+
);
|
|
5782
|
+
setShadowLayers(next);
|
|
5783
|
+
}}
|
|
5255
5784
|
onRemove={() =>
|
|
5256
5785
|
setShadowLayers(
|
|
5257
5786
|
shadowLayers.filter((candidate) => candidate.id !== layer.id),
|
|
@@ -5261,14 +5790,14 @@ function EffectsProperties({
|
|
|
5261
5790
|
))}
|
|
5262
5791
|
</div>
|
|
5263
5792
|
) : null}
|
|
5264
|
-
{
|
|
5793
|
+
{filterHasBlur ? (
|
|
5265
5794
|
/* design effect row for layer blur: flat row matching shadow rows */
|
|
5266
5795
|
<Popover>
|
|
5267
5796
|
<div className="group flex items-center gap-1.5">
|
|
5268
5797
|
<PopoverTrigger asChild>
|
|
5269
5798
|
<button
|
|
5270
5799
|
type="button"
|
|
5271
|
-
className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
|
|
5800
|
+
className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left !text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
|
|
5272
5801
|
>
|
|
5273
5802
|
<span className="min-w-0 flex-1 truncate font-medium text-foreground">
|
|
5274
5803
|
{t("editPanel.labels.layerBlur")}
|
|
@@ -5279,10 +5808,45 @@ function EffectsProperties({
|
|
|
5279
5808
|
</button>
|
|
5280
5809
|
</PopoverTrigger>
|
|
5281
5810
|
<SectionIconButton
|
|
5282
|
-
|
|
5811
|
+
label={
|
|
5812
|
+
blurValue > 0
|
|
5813
|
+
? t("editPanel.labels.hideLayer")
|
|
5814
|
+
: t("editPanel.labels.showLayer")
|
|
5815
|
+
}
|
|
5816
|
+
onClick={() => {
|
|
5817
|
+
if (blurValue > 0) {
|
|
5818
|
+
setHiddenEffectStash((prev) => ({
|
|
5819
|
+
...prev,
|
|
5820
|
+
[layerBlurStashKey]: String(blurValue),
|
|
5821
|
+
}));
|
|
5822
|
+
onStyleChange("filter", setBlurFilterValue(styles.filter, 0));
|
|
5823
|
+
return;
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5826
|
+
const restored = Number(hiddenEffectStash[layerBlurStashKey]);
|
|
5827
|
+
const nextBlur =
|
|
5828
|
+
Number.isFinite(restored) && restored > 0 ? restored : 4;
|
|
5829
|
+
setHiddenEffectStash((prev) => {
|
|
5830
|
+
const next = { ...prev };
|
|
5831
|
+
delete next[layerBlurStashKey];
|
|
5832
|
+
return next;
|
|
5833
|
+
});
|
|
5834
|
+
onStyleChange(
|
|
5835
|
+
"filter",
|
|
5836
|
+
setBlurFilterValue(styles.filter, nextBlur),
|
|
5837
|
+
);
|
|
5838
|
+
}}
|
|
5839
|
+
>
|
|
5840
|
+
{blurValue > 0 ? (
|
|
5841
|
+
<IconEye className="size-3.5" />
|
|
5842
|
+
) : (
|
|
5843
|
+
<IconEyeOff className="size-3.5" />
|
|
5844
|
+
)}
|
|
5845
|
+
</SectionIconButton>
|
|
5846
|
+
<SectionIconButton
|
|
5283
5847
|
label={t("editPanel.labels.removeLayer")}
|
|
5284
5848
|
onClick={() => onStyleChange("filter", "none")}
|
|
5285
|
-
disabled={!
|
|
5849
|
+
disabled={!filterHasBlur}
|
|
5286
5850
|
>
|
|
5287
5851
|
<IconMinus className="size-3.5" />
|
|
5288
5852
|
</SectionIconButton>
|
|
@@ -5296,14 +5860,12 @@ function EffectsProperties({
|
|
|
5296
5860
|
<ScrubInput
|
|
5297
5861
|
label={t("editPanel.labels.blur")}
|
|
5298
5862
|
value={blurValue}
|
|
5299
|
-
onChange={(value) =>
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
onStyleChange("filter", next);
|
|
5306
|
-
}}
|
|
5863
|
+
onChange={(value) =>
|
|
5864
|
+
onStyleChange(
|
|
5865
|
+
"filter",
|
|
5866
|
+
setBlurFilterValue(styles.filter, value),
|
|
5867
|
+
)
|
|
5868
|
+
}
|
|
5307
5869
|
unit="px"
|
|
5308
5870
|
min={0}
|
|
5309
5871
|
precision={1}
|
|
@@ -5313,14 +5875,14 @@ function EffectsProperties({
|
|
|
5313
5875
|
</PopoverContent>
|
|
5314
5876
|
</Popover>
|
|
5315
5877
|
) : null}
|
|
5316
|
-
{
|
|
5878
|
+
{backdropFilterHasBlur ? (
|
|
5317
5879
|
/* M5 · Background (backdrop) blur effect row — mirrors the layer-blur row */
|
|
5318
5880
|
<Popover>
|
|
5319
5881
|
<div className="group flex items-center gap-1.5">
|
|
5320
5882
|
<PopoverTrigger asChild>
|
|
5321
5883
|
<button
|
|
5322
5884
|
type="button"
|
|
5323
|
-
className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
|
|
5885
|
+
className="flex h-6 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-left !text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)]"
|
|
5324
5886
|
>
|
|
5325
5887
|
<span className="min-w-0 flex-1 truncate font-medium text-foreground">
|
|
5326
5888
|
{"Background blur" /* i18n-ignore design effect type */}
|
|
@@ -5331,12 +5893,50 @@ function EffectsProperties({
|
|
|
5331
5893
|
</button>
|
|
5332
5894
|
</PopoverTrigger>
|
|
5333
5895
|
<SectionIconButton
|
|
5334
|
-
|
|
5896
|
+
label={
|
|
5897
|
+
backdropBlurValue > 0
|
|
5898
|
+
? t("editPanel.labels.hideLayer")
|
|
5899
|
+
: t("editPanel.labels.showLayer")
|
|
5900
|
+
}
|
|
5901
|
+
onClick={() => {
|
|
5902
|
+
if (backdropBlurValue > 0) {
|
|
5903
|
+
setHiddenEffectStash((prev) => ({
|
|
5904
|
+
...prev,
|
|
5905
|
+
[backdropBlurStashKey]: String(backdropBlurValue),
|
|
5906
|
+
}));
|
|
5907
|
+
onStyleChange(
|
|
5908
|
+
"backdropFilter",
|
|
5909
|
+
setBlurFilterValue(backdropFilterValue, 0),
|
|
5910
|
+
);
|
|
5911
|
+
return;
|
|
5912
|
+
}
|
|
5913
|
+
|
|
5914
|
+
const restored = Number(
|
|
5915
|
+
hiddenEffectStash[backdropBlurStashKey],
|
|
5916
|
+
);
|
|
5917
|
+
const nextBlur =
|
|
5918
|
+
Number.isFinite(restored) && restored > 0 ? restored : 8;
|
|
5919
|
+
setHiddenEffectStash((prev) => {
|
|
5920
|
+
const next = { ...prev };
|
|
5921
|
+
delete next[backdropBlurStashKey];
|
|
5922
|
+
return next;
|
|
5923
|
+
});
|
|
5924
|
+
onStyleChange(
|
|
5925
|
+
"backdropFilter",
|
|
5926
|
+
setBlurFilterValue(backdropFilterValue, nextBlur),
|
|
5927
|
+
);
|
|
5928
|
+
}}
|
|
5929
|
+
>
|
|
5930
|
+
{backdropBlurValue > 0 ? (
|
|
5931
|
+
<IconEye className="size-3.5" />
|
|
5932
|
+
) : (
|
|
5933
|
+
<IconEyeOff className="size-3.5" />
|
|
5934
|
+
)}
|
|
5935
|
+
</SectionIconButton>
|
|
5936
|
+
<SectionIconButton
|
|
5335
5937
|
label={t("editPanel.labels.removeLayer")}
|
|
5336
5938
|
onClick={() => onStyleChange("backdropFilter", "none")}
|
|
5337
|
-
disabled={
|
|
5338
|
-
!styles.backdropFilter || styles.backdropFilter === "none"
|
|
5339
|
-
}
|
|
5939
|
+
disabled={!backdropFilterHasBlur}
|
|
5340
5940
|
>
|
|
5341
5941
|
<IconMinus className="size-3.5" />
|
|
5342
5942
|
</SectionIconButton>
|
|
@@ -5353,7 +5953,7 @@ function EffectsProperties({
|
|
|
5353
5953
|
onChange={(value) =>
|
|
5354
5954
|
onStyleChange(
|
|
5355
5955
|
"backdropFilter",
|
|
5356
|
-
|
|
5956
|
+
setBlurFilterValue(backdropFilterValue, value),
|
|
5357
5957
|
)
|
|
5358
5958
|
}
|
|
5359
5959
|
unit="px"
|
|
@@ -5433,7 +6033,7 @@ function SelectionColorsProperties({
|
|
|
5433
6033
|
<PopoverTrigger asChild>
|
|
5434
6034
|
<button
|
|
5435
6035
|
type="button"
|
|
5436
|
-
className="flex h-6 w-full items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
6036
|
+
className="flex h-6 w-full items-center gap-1.5 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 !text-[11px] hover:bg-[var(--design-editor-panel-raised-bg)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
5437
6037
|
aria-label={color.value}
|
|
5438
6038
|
>
|
|
5439
6039
|
<span
|
|
@@ -5466,7 +6066,7 @@ function SelectionColorsProperties({
|
|
|
5466
6066
|
) : (
|
|
5467
6067
|
<button
|
|
5468
6068
|
type="button"
|
|
5469
|
-
className="flex h-6 w-full items-center justify-between gap-2 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 text-left text-[11px] text-muted-foreground hover:bg-[var(--design-editor-panel-raised-bg)] hover:text-foreground"
|
|
6069
|
+
className="flex h-6 w-full items-center justify-between gap-2 rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 text-left !text-[11px] text-muted-foreground hover:bg-[var(--design-editor-panel-raised-bg)] hover:text-foreground"
|
|
5470
6070
|
onClick={() => setExpanded(true)}
|
|
5471
6071
|
>
|
|
5472
6072
|
<span className="truncate">
|
|
@@ -5637,11 +6237,11 @@ function MakeItRealCard({
|
|
|
5637
6237
|
|
|
5638
6238
|
const summary =
|
|
5639
6239
|
cta.kind === "configure-project"
|
|
5640
|
-
? `
|
|
6240
|
+
? `Choose a Builder project to enable ${featureLabel}.`
|
|
5641
6241
|
: `Connect Builder to enable ${featureLabel}.`;
|
|
5642
6242
|
const primaryLabel =
|
|
5643
6243
|
cta.kind === "configure-project"
|
|
5644
|
-
? "
|
|
6244
|
+
? "Choose" /* i18n-ignore make-it-real card */
|
|
5645
6245
|
: "Connect"; /* i18n-ignore make-it-real card */
|
|
5646
6246
|
|
|
5647
6247
|
return (
|
|
@@ -6087,10 +6687,10 @@ export function ComponentSection({
|
|
|
6087
6687
|
<div className="flex min-h-9 items-center gap-2 px-3">
|
|
6088
6688
|
{/* Accent diamond matching the workbench artboard component rows */}
|
|
6089
6689
|
<span
|
|
6090
|
-
className="size-2 shrink-0 rotate-45 rounded-[2px] bg-[var(--design-editor-
|
|
6690
|
+
className="size-2 shrink-0 rotate-45 rounded-[2px] bg-[var(--design-editor-component-color)]"
|
|
6091
6691
|
aria-hidden="true"
|
|
6092
6692
|
/>
|
|
6093
|
-
<h3 className="min-w-0 flex-1 truncate text-[11px] font-semibold text-foreground">
|
|
6693
|
+
<h3 className="min-w-0 flex-1 truncate !text-[11px] font-semibold text-foreground">
|
|
6094
6694
|
{name}
|
|
6095
6695
|
</h3>
|
|
6096
6696
|
{/* Jump-to-source action */}
|
|
@@ -6121,14 +6721,14 @@ export function ComponentSection({
|
|
|
6121
6721
|
canJumpToSource
|
|
6122
6722
|
? "Edit component source" /* i18n-ignore design inspector action */
|
|
6123
6723
|
: (capabilities.ctaMessage ??
|
|
6124
|
-
"
|
|
6724
|
+
"Source jump needs a connected app") /* i18n-ignore design inspector tooltip */
|
|
6125
6725
|
}
|
|
6126
6726
|
</TooltipContent>
|
|
6127
6727
|
</Tooltip>
|
|
6128
6728
|
</div>
|
|
6129
6729
|
|
|
6130
6730
|
{/* ── Body ── */}
|
|
6131
|
-
<div className="space-y-1.5 px-3 pb-3 pt-0.5 text-[11px]">
|
|
6731
|
+
<div className="space-y-1.5 px-3 pb-3 pt-0.5 !text-[11px]">
|
|
6132
6732
|
{/* Source path chip */}
|
|
6133
6733
|
{sourceChip && (
|
|
6134
6734
|
<div
|
|
@@ -6156,7 +6756,7 @@ export function ComponentSection({
|
|
|
6156
6756
|
const disabled = !editingEnabled || applyPropMutation.isPending;
|
|
6157
6757
|
return (
|
|
6158
6758
|
<div key={row.name} className="flex items-center gap-1.5">
|
|
6159
|
-
<Label className="w-[64px] shrink-0 truncate text-[11px] font-medium capitalize text-muted-foreground">
|
|
6759
|
+
<Label className="w-[64px] shrink-0 truncate !text-[11px] font-medium capitalize text-muted-foreground">
|
|
6160
6760
|
{row.name}
|
|
6161
6761
|
</Label>
|
|
6162
6762
|
{hasOptions ? (
|
|
@@ -6166,7 +6766,7 @@ export function ComponentSection({
|
|
|
6166
6766
|
onValueChange={(v) => commitProp(row, v)}
|
|
6167
6767
|
disabled={disabled}
|
|
6168
6768
|
>
|
|
6169
|
-
<SelectTrigger className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
6769
|
+
<SelectTrigger className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
6170
6770
|
<SelectValue />
|
|
6171
6771
|
</SelectTrigger>
|
|
6172
6772
|
<SelectContent>
|
|
@@ -6174,7 +6774,7 @@ export function ComponentSection({
|
|
|
6174
6774
|
<SelectItem
|
|
6175
6775
|
key={opt}
|
|
6176
6776
|
value={opt}
|
|
6177
|
-
className="text-[11px]"
|
|
6777
|
+
className="!text-[11px]"
|
|
6178
6778
|
>
|
|
6179
6779
|
{opt}
|
|
6180
6780
|
</SelectItem>
|
|
@@ -6209,7 +6809,7 @@ export function ComponentSection({
|
|
|
6209
6809
|
e.currentTarget.blur();
|
|
6210
6810
|
}
|
|
6211
6811
|
}}
|
|
6212
|
-
className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-[11px] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
|
|
6812
|
+
className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)] md:!text-[11px]"
|
|
6213
6813
|
/>
|
|
6214
6814
|
)}
|
|
6215
6815
|
</div>
|
|
@@ -6218,11 +6818,11 @@ export function ComponentSection({
|
|
|
6218
6818
|
</div>
|
|
6219
6819
|
)}
|
|
6220
6820
|
|
|
6221
|
-
{/* Connect-Builder CTA (
|
|
6222
|
-
{capabilities.ctaRequired && (
|
|
6821
|
+
{/* Connect-Builder CTA (only when prop editing is actually gated). */}
|
|
6822
|
+
{capabilities.ctaRequired && !editingEnabled && (
|
|
6223
6823
|
<MakeItRealCard
|
|
6224
6824
|
designId={designId}
|
|
6225
|
-
featureLabel="component source jump and
|
|
6825
|
+
featureLabel="component source jump and typed prop metadata"
|
|
6226
6826
|
/>
|
|
6227
6827
|
)}
|
|
6228
6828
|
</div>
|
|
@@ -6232,6 +6832,7 @@ export function ComponentSection({
|
|
|
6232
6832
|
|
|
6233
6833
|
export function EditPanel({
|
|
6234
6834
|
selectedElement,
|
|
6835
|
+
selectedElements,
|
|
6235
6836
|
pageStyles = {},
|
|
6236
6837
|
headerTrailing,
|
|
6237
6838
|
width = 256,
|
|
@@ -6243,21 +6844,18 @@ export function EditPanel({
|
|
|
6243
6844
|
onRequestTweaks,
|
|
6244
6845
|
onStyleChange,
|
|
6245
6846
|
onStylesChange,
|
|
6246
|
-
onAutoLayoutConvert,
|
|
6247
6847
|
onExport,
|
|
6248
6848
|
exporting = false,
|
|
6249
|
-
readOnly = false,
|
|
6250
6849
|
fileId,
|
|
6251
|
-
filename,
|
|
6252
6850
|
activeContent,
|
|
6253
6851
|
activeFileUpdatedAt,
|
|
6254
6852
|
designId,
|
|
6255
6853
|
onComponentPropApplied,
|
|
6256
|
-
statesPanelProps,
|
|
6257
6854
|
reviewPanelProps,
|
|
6258
6855
|
componentNodeId,
|
|
6259
6856
|
sourceCapabilities = [],
|
|
6260
6857
|
onCreateComponent,
|
|
6858
|
+
selectedElementAlreadyComponent = false,
|
|
6261
6859
|
defaultComponentName = "Component",
|
|
6262
6860
|
inspectCode,
|
|
6263
6861
|
}: EditPanelProps) {
|
|
@@ -6268,12 +6866,42 @@ export function EditPanel({
|
|
|
6268
6866
|
);
|
|
6269
6867
|
const [showExportPreview, setShowExportPreview] = useState(false);
|
|
6270
6868
|
|
|
6271
|
-
const
|
|
6272
|
-
|
|
6869
|
+
const effectiveSelectedElements = useMemo(
|
|
6870
|
+
() =>
|
|
6871
|
+
selectedElements && selectedElements.length > 0
|
|
6872
|
+
? selectedElements
|
|
6873
|
+
: selectedElement
|
|
6874
|
+
? [selectedElement]
|
|
6875
|
+
: [],
|
|
6876
|
+
[selectedElement, selectedElements],
|
|
6877
|
+
);
|
|
6878
|
+
const inspectorElement = useMemo(
|
|
6879
|
+
() =>
|
|
6880
|
+
effectiveSelectedElements.length > 1
|
|
6881
|
+
? mixedElementFromSelection(effectiveSelectedElements)
|
|
6882
|
+
: (effectiveSelectedElements[0] ?? null),
|
|
6883
|
+
[effectiveSelectedElements],
|
|
6884
|
+
);
|
|
6885
|
+
const selectedCount = effectiveSelectedElements.length;
|
|
6886
|
+
const selectionAlreadyComponent =
|
|
6887
|
+
selectedCount === 1 &&
|
|
6888
|
+
(selectedElementAlreadyComponent ||
|
|
6889
|
+
elementIsComponentSelection(selectedElement));
|
|
6890
|
+
const canCreateComponent = Boolean(
|
|
6891
|
+
onCreateComponent &&
|
|
6892
|
+
selectedElement &&
|
|
6893
|
+
selectedCount <= 1 &&
|
|
6894
|
+
!selectionAlreadyComponent,
|
|
6895
|
+
);
|
|
6896
|
+
const selectedElementKey = inspectorElement
|
|
6897
|
+
? `${selectedCount}:${elementIdentityKey(inspectorElement)}`
|
|
6273
6898
|
: "none";
|
|
6274
|
-
const
|
|
6275
|
-
|
|
6276
|
-
|
|
6899
|
+
const selectionHasTextElement = effectiveSelectedElements.some((element) =>
|
|
6900
|
+
isTextElement(element),
|
|
6901
|
+
);
|
|
6902
|
+
const selectionHasContainerElement = effectiveSelectedElements.some(
|
|
6903
|
+
(element) => isContainerElement(element),
|
|
6904
|
+
);
|
|
6277
6905
|
const handleActiveTabChange = useCallback(
|
|
6278
6906
|
(tab: InspectorTab) => onActiveTabChange?.(tab),
|
|
6279
6907
|
[onActiveTabChange],
|
|
@@ -6296,6 +6924,10 @@ export function EditPanel({
|
|
|
6296
6924
|
setShowExportPreview(false);
|
|
6297
6925
|
}, [selectedElementKey]);
|
|
6298
6926
|
|
|
6927
|
+
useEffect(() => {
|
|
6928
|
+
if (!canCreateComponent) setCreateComponentOpen(false);
|
|
6929
|
+
}, [canCreateComponent]);
|
|
6930
|
+
|
|
6299
6931
|
// Scroll guard: suppress the click that fires immediately after a scroll
|
|
6300
6932
|
// gesture ends (rubber-band or normal scroll). Using onScroll instead of
|
|
6301
6933
|
// onPointerDown avoids side-effects like Radix DismissableLayer detecting a
|
|
@@ -6306,41 +6938,6 @@ export function EditPanel({
|
|
|
6306
6938
|
const userScrollIntentRef = useRef(false);
|
|
6307
6939
|
const scrollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
6308
6940
|
|
|
6309
|
-
if (readOnly) {
|
|
6310
|
-
return (
|
|
6311
|
-
<div
|
|
6312
|
-
className={cn(
|
|
6313
|
-
"shrink-0 bg-[var(--design-editor-panel-bg)]",
|
|
6314
|
-
"flex h-full min-h-0 flex-col overflow-hidden",
|
|
6315
|
-
)}
|
|
6316
|
-
style={{ width }}
|
|
6317
|
-
>
|
|
6318
|
-
<div className="flex min-h-8 shrink-0 items-center border-b border-border/90 px-3">
|
|
6319
|
-
<h2 className="min-w-0 truncate text-[12px] font-semibold text-foreground">
|
|
6320
|
-
{t("editPanel.properties")}
|
|
6321
|
-
</h2>
|
|
6322
|
-
</div>
|
|
6323
|
-
<div className="flex min-h-0 flex-1 items-center justify-center px-5">
|
|
6324
|
-
<div
|
|
6325
|
-
className="max-w-[188px] text-center"
|
|
6326
|
-
aria-live="polite"
|
|
6327
|
-
aria-busy="true"
|
|
6328
|
-
>
|
|
6329
|
-
<div className="mx-auto mb-3 flex size-9 items-center justify-center rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-muted-foreground/60">
|
|
6330
|
-
<IconLock className="size-4" aria-hidden="true" />
|
|
6331
|
-
</div>
|
|
6332
|
-
<h3 className="text-[12px] font-semibold leading-snug text-foreground">
|
|
6333
|
-
{t("designEditor.inspectorLockedTitle")}
|
|
6334
|
-
</h3>
|
|
6335
|
-
<p className="mt-1.5 text-[11px] leading-snug text-muted-foreground/70">
|
|
6336
|
-
{t("designEditor.inspectorLockedDescription")}
|
|
6337
|
-
</p>
|
|
6338
|
-
</div>
|
|
6339
|
-
</div>
|
|
6340
|
-
</div>
|
|
6341
|
-
);
|
|
6342
|
-
}
|
|
6343
|
-
|
|
6344
6941
|
return (
|
|
6345
6942
|
<div
|
|
6346
6943
|
className={cn(
|
|
@@ -6358,28 +6955,21 @@ export function EditPanel({
|
|
|
6358
6955
|
{activeTab === "design" ? (
|
|
6359
6956
|
<>
|
|
6360
6957
|
<SelectionHeader
|
|
6361
|
-
element={
|
|
6958
|
+
element={inspectorElement}
|
|
6959
|
+
selectedCount={selectedCount}
|
|
6362
6960
|
onCreateComponent={
|
|
6363
|
-
onCreateComponent
|
|
6364
|
-
? () => setCreateComponentOpen(true)
|
|
6365
|
-
: undefined
|
|
6961
|
+
canCreateComponent ? onCreateComponent : undefined
|
|
6366
6962
|
}
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6963
|
+
createComponentOpen={createComponentOpen}
|
|
6964
|
+
onCreateComponentOpenChange={setCreateComponentOpen}
|
|
6965
|
+
showCreateComponentAction={!selectionAlreadyComponent}
|
|
6966
|
+
defaultComponentName={defaultComponentName}
|
|
6967
|
+
inspectCode={
|
|
6968
|
+
inspectCode && selectedElement && selectedCount <= 1
|
|
6969
|
+
? inspectCode
|
|
6372
6970
|
: undefined
|
|
6373
6971
|
}
|
|
6374
6972
|
/>
|
|
6375
|
-
{onCreateComponent && (
|
|
6376
|
-
<CreateComponentDialog
|
|
6377
|
-
open={createComponentOpen}
|
|
6378
|
-
onOpenChange={setCreateComponentOpen}
|
|
6379
|
-
defaultName={defaultComponentName}
|
|
6380
|
-
onSubmit={onCreateComponent}
|
|
6381
|
-
/>
|
|
6382
|
-
)}
|
|
6383
6973
|
|
|
6384
6974
|
<div
|
|
6385
6975
|
className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain"
|
|
@@ -6456,7 +7046,7 @@ export function EditPanel({
|
|
|
6456
7046
|
>
|
|
6457
7047
|
{/* §6.1 Component section — shown at the top when a component
|
|
6458
7048
|
instance is selected. Requires designId + componentNodeId. */}
|
|
6459
|
-
{designId && componentNodeId && (
|
|
7049
|
+
{designId && componentNodeId && selectedCount <= 1 && (
|
|
6460
7050
|
<ComponentSection
|
|
6461
7051
|
designId={designId}
|
|
6462
7052
|
fileId={fileId}
|
|
@@ -6468,7 +7058,7 @@ export function EditPanel({
|
|
|
6468
7058
|
/>
|
|
6469
7059
|
)}
|
|
6470
7060
|
|
|
6471
|
-
{!
|
|
7061
|
+
{!inspectorElement && (
|
|
6472
7062
|
<PageProperties
|
|
6473
7063
|
styles={pageStyles}
|
|
6474
7064
|
onStyleChange={onStyleChange}
|
|
@@ -6476,64 +7066,52 @@ export function EditPanel({
|
|
|
6476
7066
|
/>
|
|
6477
7067
|
)}
|
|
6478
7068
|
|
|
6479
|
-
{
|
|
7069
|
+
{inspectorElement && (
|
|
6480
7070
|
<>
|
|
6481
7071
|
<PositionLayoutProperties
|
|
6482
|
-
element={
|
|
7072
|
+
element={inspectorElement}
|
|
6483
7073
|
onStyleChange={onStyleChange}
|
|
6484
7074
|
/>
|
|
6485
7075
|
<LayoutContextProperties
|
|
6486
|
-
element={
|
|
7076
|
+
element={inspectorElement}
|
|
6487
7077
|
onStyleChange={onStyleChange}
|
|
6488
7078
|
onStylesChange={onStylesChange}
|
|
6489
|
-
onAutoLayoutConvert={onAutoLayoutConvert}
|
|
6490
7079
|
/>
|
|
6491
7080
|
<AppearanceProperties
|
|
6492
|
-
element={
|
|
7081
|
+
element={inspectorElement}
|
|
6493
7082
|
onStyleChange={onStyleChange}
|
|
6494
7083
|
/>
|
|
6495
|
-
{
|
|
7084
|
+
{selectionHasTextElement ? (
|
|
6496
7085
|
<TypographyProperties
|
|
6497
|
-
element={
|
|
7086
|
+
element={inspectorElement}
|
|
6498
7087
|
onStyleChange={onStyleChange}
|
|
6499
7088
|
/>
|
|
6500
7089
|
) : null}
|
|
6501
7090
|
<FillProperties
|
|
6502
|
-
element={
|
|
7091
|
+
element={inspectorElement}
|
|
6503
7092
|
onStyleChange={onStyleChange}
|
|
6504
7093
|
onStylesChange={onStylesChange}
|
|
6505
7094
|
/>
|
|
6506
7095
|
<StrokeProperties
|
|
6507
|
-
element={
|
|
7096
|
+
element={inspectorElement}
|
|
6508
7097
|
onStyleChange={onStyleChange}
|
|
6509
7098
|
onStylesChange={onStylesChange}
|
|
6510
7099
|
/>
|
|
6511
7100
|
<EffectsProperties
|
|
6512
|
-
element={
|
|
7101
|
+
element={inspectorElement}
|
|
6513
7102
|
onStyleChange={onStyleChange}
|
|
6514
7103
|
onStylesChange={onStylesChange}
|
|
6515
7104
|
/>
|
|
6516
7105
|
<SelectionColorsProperties
|
|
6517
|
-
element={
|
|
7106
|
+
element={inspectorElement}
|
|
6518
7107
|
onStyleChange={onStyleChange}
|
|
6519
7108
|
/>
|
|
6520
|
-
{
|
|
7109
|
+
{selectionHasContainerElement ? (
|
|
6521
7110
|
<LayoutGuideProperties
|
|
6522
|
-
element={
|
|
7111
|
+
element={inspectorElement}
|
|
6523
7112
|
onStyleChange={onStyleChange}
|
|
6524
7113
|
/>
|
|
6525
7114
|
) : null}
|
|
6526
|
-
{/* AI edit request block — collapsible, collapsed by default */}
|
|
6527
|
-
<section className="shrink-0 border-t border-[var(--design-editor-control-border)]">
|
|
6528
|
-
<InspectorAiActions
|
|
6529
|
-
selector={selectedElement.selector}
|
|
6530
|
-
sourceId={selectedElement.sourceId}
|
|
6531
|
-
designId={designId}
|
|
6532
|
-
fileId={fileId}
|
|
6533
|
-
filename={filename}
|
|
6534
|
-
canEdit={!readOnly}
|
|
6535
|
-
/>
|
|
6536
|
-
</section>
|
|
6537
7115
|
</>
|
|
6538
7116
|
)}
|
|
6539
7117
|
{onExport ? (
|
|
@@ -6564,56 +7142,11 @@ export function EditPanel({
|
|
|
6564
7142
|
onExport={onExport}
|
|
6565
7143
|
/>
|
|
6566
7144
|
{showExportPreview ? (
|
|
6567
|
-
<ExportPreview element={
|
|
7145
|
+
<ExportPreview element={inspectorElement} />
|
|
6568
7146
|
) : null}
|
|
6569
7147
|
</PanelSection>
|
|
6570
7148
|
) : null}
|
|
6571
7149
|
|
|
6572
|
-
{/* §6.4 States & Responsive — contextual section in Design tab.
|
|
6573
|
-
Collapsed by default so existing Design content is not buried.
|
|
6574
|
-
Only mounts when designId is provided so there is something to
|
|
6575
|
-
load; without it the panel would fire an action with no id. */}
|
|
6576
|
-
{designId && statesPanelProps ? (
|
|
6577
|
-
<PanelSection
|
|
6578
|
-
title={"States" /* i18n-ignore design inspector section */}
|
|
6579
|
-
defaultCollapsed
|
|
6580
|
-
actions={
|
|
6581
|
-
<Tooltip>
|
|
6582
|
-
<TooltipTrigger asChild>
|
|
6583
|
-
<Button
|
|
6584
|
-
type="button"
|
|
6585
|
-
variant="ghost"
|
|
6586
|
-
size="icon"
|
|
6587
|
-
className="size-6 rounded-md text-muted-foreground hover:text-foreground"
|
|
6588
|
-
aria-label={
|
|
6589
|
-
"Breakpoints & states" /* i18n-ignore design inspector action */
|
|
6590
|
-
}
|
|
6591
|
-
>
|
|
6592
|
-
<IconDeviceDesktop className="size-3.5" />
|
|
6593
|
-
</Button>
|
|
6594
|
-
</TooltipTrigger>
|
|
6595
|
-
<TooltipContent>
|
|
6596
|
-
{
|
|
6597
|
-
"Breakpoints & states" /* i18n-ignore design inspector action */
|
|
6598
|
-
}
|
|
6599
|
-
</TooltipContent>
|
|
6600
|
-
</Tooltip>
|
|
6601
|
-
}
|
|
6602
|
-
>
|
|
6603
|
-
<StatesPanel designId={designId} {...statesPanelProps} />
|
|
6604
|
-
{/* §6.4 / §6.6 — live captures (real running-app data, route
|
|
6605
|
-
props, API responses) are a real-app capability. When
|
|
6606
|
-
canCapture is false (inline source) surface a compact
|
|
6607
|
-
"Make it real" CTA so the user knows it's one step away. */}
|
|
6608
|
-
{!statesPanelProps.canCapture && (
|
|
6609
|
-
<MakeItRealCard
|
|
6610
|
-
designId={designId}
|
|
6611
|
-
featureLabel="live data captures and real-app states"
|
|
6612
|
-
/>
|
|
6613
|
-
)}
|
|
6614
|
-
</PanelSection>
|
|
6615
|
-
) : null}
|
|
6616
|
-
|
|
6617
7150
|
{/* §6.5 Review — contextual section in Design tab.
|
|
6618
7151
|
Collapsed by default. Renders when reviewPanelProps is provided,
|
|
6619
7152
|
no designId check needed since ReviewPanel is statically fed. */}
|
|
@@ -6629,17 +7162,25 @@ export function EditPanel({
|
|
|
6629
7162
|
variant="ghost"
|
|
6630
7163
|
size="icon"
|
|
6631
7164
|
className="size-6 rounded-md text-muted-foreground hover:text-foreground"
|
|
7165
|
+
disabled={reviewPanelProps.auditLoading}
|
|
7166
|
+
onClick={(event) => {
|
|
7167
|
+
event.stopPropagation();
|
|
7168
|
+
reviewPanelProps.onRunAudit?.();
|
|
7169
|
+
}}
|
|
6632
7170
|
aria-label={
|
|
6633
|
-
"
|
|
7171
|
+
"Run audit" /* i18n-ignore design inspector action */
|
|
6634
7172
|
}
|
|
6635
7173
|
>
|
|
6636
|
-
<
|
|
7174
|
+
<IconRefresh
|
|
7175
|
+
className={cn(
|
|
7176
|
+
"size-3.5",
|
|
7177
|
+
reviewPanelProps.auditLoading && "animate-spin",
|
|
7178
|
+
)}
|
|
7179
|
+
/>
|
|
6637
7180
|
</Button>
|
|
6638
7181
|
</TooltipTrigger>
|
|
6639
7182
|
<TooltipContent>
|
|
6640
|
-
{
|
|
6641
|
-
"Accessibility & visual diff" /* i18n-ignore design inspector action */
|
|
6642
|
-
}
|
|
7183
|
+
{"Run audit" /* i18n-ignore design inspector action */}
|
|
6643
7184
|
</TooltipContent>
|
|
6644
7185
|
</Tooltip>
|
|
6645
7186
|
}
|