@agent-native/core 0.80.7 → 0.80.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +6 -0
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +8 -0
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +10 -2
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +10 -2
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +7 -0
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +7 -0
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +7 -0
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +9 -2
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +6 -0
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +6 -0
- package/corpus/core/docs/content/template-design.mdx +9 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/mcp.ts +10 -0
- package/corpus/core/src/cli/skills.ts +58 -12
- package/corpus/core/src/client/AssistantChat.tsx +70 -27
- package/corpus/core/src/client/analytics.ts +3 -1
- package/corpus/core/src/client/error-format.ts +25 -0
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +13 -11
- package/corpus/core/src/client/guided-questions.tsx +32 -4
- package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +33 -1
- package/corpus/core/src/client/resources/BuiltinCapabilityDetail.tsx +8 -0
- package/corpus/core/src/client/resources/use-builtin-capabilities.ts +4 -2
- package/corpus/core/src/client/session-replay.ts +34 -0
- package/corpus/core/src/client/sharing/ShareButton.tsx +106 -21
- package/corpus/core/src/coding-tools/index.ts +1 -1
- package/corpus/core/src/mcp/index.ts +2 -0
- package/corpus/core/src/mcp/screen-memory-stdio.ts +290 -0
- package/corpus/core/src/mcp-client/builtin-capabilities.ts +13 -1
- package/corpus/core/src/mcp-client/index.ts +9 -0
- package/corpus/core/src/mcp-client/screen-memory-local.ts +461 -0
- package/corpus/core/src/onboarding/types.ts +7 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +25 -0
- package/corpus/core/src/server/agents-bundle.ts +35 -7
- package/corpus/core/src/styles/agent-native.css +5 -0
- package/corpus/core/src/templates/default/app/global.css +35 -35
- package/corpus/templates/analytics/AGENTS.md +6 -0
- package/corpus/templates/analytics/actions/github-repo-files.ts +9 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +4 -4
- package/corpus/templates/analytics/app/global.css +144 -40
- package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +1 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +4 -4
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -9
- package/corpus/templates/analytics/changelog/2026-06-29-agents-can-search-and-read-connected-github-repositories-whe.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-sessions-and-explorer-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
- package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/assets/app/components/library/LibraryPresetGrid.tsx +1 -1
- package/corpus/templates/assets/app/global.css +99 -35
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +4 -4
- package/corpus/templates/assets/app/routes/library.tsx +4 -4
- package/corpus/templates/assets/changelog/2026-06-29-asset-library-and-detail-layouts-adapt-when-the-agent-sidebar.md +6 -0
- package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/brain/app/global.css +63 -35
- package/corpus/templates/brain/app/routes/search.tsx +1 -1
- package/corpus/templates/brain/app/routes/settings.tsx +1 -1
- package/corpus/templates/brain/app/routes/sources.tsx +2 -2
- package/corpus/templates/brain/changelog/2026-06-29-search-settings-and-source-layouts-adapt-when-the-agent-sidebar.md +6 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +4 -4
- package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +3 -2
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -2
- package/corpus/templates/calendar/app/global.css +69 -35
- package/corpus/templates/calendar/changelog/2026-06-29-event-and-find-time-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
- package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/chat/app/global.css +35 -35
- package/corpus/templates/chat/changelog/2026-06-29-chat-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/clips/AGENTS.md +13 -0
- package/corpus/templates/clips/actions/get-screen-memory-status.ts +12 -0
- package/corpus/templates/clips/actions/query-screen-memory-context.ts +29 -0
- package/corpus/templates/clips/actions/view-screen.ts +2 -2
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/global.css +47 -35
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +3 -3
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +45 -18
- package/corpus/templates/clips/changelog/2026-06-29-meeting-detail-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-29-screen-memory-keeps-a-local-rolling-screen-buffer-for-agent-context.md +5 -0
- package/corpus/templates/clips/changelog/2026-06-29-video-chat-is-easier-to-find-on-recording-pages-and-s3-setup.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/styles.css +39 -39
- package/corpus/templates/clips/desktop/src/app.tsx +290 -1
- package/corpus/templates/clips/desktop/src/shared/config.ts +83 -0
- package/corpus/templates/clips/desktop/src/styles.css +67 -52
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +50 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +17 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +13 -13
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +990 -0
- package/corpus/templates/clips/server/plugins/onboarding.ts +2 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +69 -16
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +3 -3
- package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/content/app/global.css +29 -15
- package/corpus/templates/content/changelog/2026-06-29-database-gallery-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/content/changelog/2026-06-29-read-only-pages-no-longer-try-to-autosave-or-open-editor-onl.md +6 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +25 -20
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -0
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +8 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +13 -0
- package/corpus/templates/design/AGENTS.md +24 -14
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1054 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +24 -4
- package/corpus/templates/design/actions/edit-design.ts +6 -3
- package/corpus/templates/design/actions/export-coding-handoff.ts +12 -4
- package/corpus/templates/design/actions/generate-design.ts +6 -21
- package/corpus/templates/design/actions/generate-screens.ts +48 -5
- package/corpus/templates/design/actions/get-design.ts +1 -0
- package/corpus/templates/design/actions/present-design-variants.ts +355 -30
- package/corpus/templates/design/actions/update-design.ts +47 -10
- package/corpus/templates/design/actions/update-file.ts +33 -8
- package/corpus/templates/design/actions/view-screen.ts +89 -31
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +389 -71
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +1 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +596 -95
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +428 -221
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1197 -228
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +94 -217
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +28 -14
- package/corpus/templates/design/app/components/design/index.ts +0 -1
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +228 -170
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +140 -13
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +39 -8
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +4 -2
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +75 -5
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +37 -12
- package/corpus/templates/design/app/components/design/inspector/index.ts +1 -0
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +17 -4
- package/corpus/templates/design/app/components/design/types.ts +9 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +23 -5
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +448 -259
- package/corpus/templates/design/app/global.css +77 -49
- package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +8 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/design/app/i18n-data.ts +55 -3
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2783 -658
- package/corpus/templates/design/app/root.tsx +1 -1
- package/corpus/templates/design/changelog/2026-06-29-agent-edits-to-a-design-are-no-longer-occasionally-dropped-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-box-shadow-and-gradient-colors-keep-named-css-colors-like-re.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-containers-no-longer-block-selecting-nested-elements-and-nor.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-cut-cmd-ctrl-x-now-removes-the-selected-element-to-the-clipb.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-deleting-with-multiple-layers-selected-in-the-layers-panel-n.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-agents-now-generate-and-refine-variants-from-stronger.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-questions-now-appear-as-a-cleaner-canvas-intake-with-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-variant-directions-now-appear-as-board-screens-with-chat-buttons.md +5 -0
- package/corpus/templates/design/changelog/2026-06-29-duplicating-a-screen-now-gives-the-copy-its-own-layer-ids-so.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-element-selection-no-longer-snaps-back-to-the-previous-layer.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-frame-resize-measurements-now-stay-beside-the-cursor-while-d.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-hiding-and-re-showing-a-gradient-fill-now-preserves-each-sto.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-inspector-number-fields-have-cleaner-tooltips-and-draggable-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-locking-layer-nesting-generated-question-intake-and-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-names-in-the-sidebar-can-scroll-horizontally-instead-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-nesting-in-the-sidebar-now-uses-tighter-spacing-so-nam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layers-no-longer-repeat-document-wrapper-rows-when-a-screen-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-overview-previews-now-keep-element-hover-and-selection-tied-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-picking-a-design-direction-now-submits-to-the-agent-right-aw.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-pressing-escape-now-cancels-a-stuck-screen-drag-in-the-overv.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-is-easier-to-find-and-named-screens-open-dir.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-only-highlights-screens-when-the-frame-or-ti.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-previews-now-resize-to-match-the-selected-de.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-now-uses-a-pointer-cursor-instead-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-outlines-now-stay-locked-to-the-se.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-zoom-now-reports-real-screen-scale-separatel.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-feels-smoother-and-selection-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-now-stays-on-the-overview-canvas-ins.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-selecting-a-nested-layer-from-all-screens-now-keeps-you-in-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-share-now-groups-link-sharing-exports-and-agent-handoff.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-style-changes-that-set-several-properties-at-once-fixed-size.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-design-editor-loading-skeleton-now-uses-a-softer-charcoa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-device-preview-menu-now-sits-next-to-the-preview-button-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-editor-no-longer-crashes-when-a-screen-ends-up-with-dupl.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-inspector-stays-read-only-while-an-empty-design-is-still.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-missing-design-view-now-matches-the-rest-of-the-editor-c.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-icon-and-tool-option-menus-better-match-the-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-typography-controls-now-keep-the-font-name-visible-and-move-.md +6 -0
- package/corpus/templates/design/e2e/README.md +4 -3
- package/corpus/templates/design/e2e/global-setup.ts +24 -23
- package/corpus/templates/design/e2e/helpers.ts +122 -11
- package/corpus/templates/design/playwright.config.ts +19 -4
- package/corpus/templates/design/server/lib/coding-handoff.ts +126 -2
- package/corpus/templates/design/server/plugins/core-routes.ts +1 -2
- package/corpus/templates/design/server/routes/api/design-handoff/[id].zip.get.ts +86 -0
- package/corpus/templates/design/shared/code-layer.ts +125 -22
- package/corpus/templates/design/shared/color-utils.ts +159 -0
- package/corpus/templates/design/shared/generation-session.ts +42 -0
- package/corpus/templates/dispatch/app/global.css +79 -35
- package/corpus/templates/dispatch/app/routes/integrations.tsx +8 -7
- package/corpus/templates/dispatch/changelog/2026-06-29-integration-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/forms/app/components/layout/Layout.tsx +6 -2
- package/corpus/templates/forms/app/global.css +64 -35
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +2 -2
- package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +3 -3
- package/corpus/templates/forms/changelog/2026-06-29-form-list-and-insight-layouts-adapt-when-the-agent-sidebar.md +6 -0
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +3 -1
- package/corpus/templates/macros/app/global.css +25 -9
- package/corpus/templates/macros/changelog/2026-06-29-macro-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -2
- package/corpus/templates/mail/app/global.css +42 -36
- package/corpus/templates/mail/app/pages/InboxPage.tsx +1 -1
- package/corpus/templates/mail/changelog/2026-06-29-the-contact-panel-now-adapts-to-the-available-mail-pane-width.md +6 -0
- package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/plan/app/components/plan/DocumentArea.tsx +6 -7
- package/corpus/templates/plan/app/global.css +74 -42
- package/corpus/templates/plan/changelog/2026-06-29-plan-document-blocks-adapt-to-the-available-document-width.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-29-plan-titles-and-summaries-can-be-edited-inline-without-focus.md +6 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/slides/app/global.css +49 -33
- package/corpus/templates/slides/changelog/2026-06-29-slide-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/videos/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/videos/app/global.css +35 -35
- package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +10 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +58 -12
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +54 -24
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +2 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +17 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +3 -4
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/guided-questions.d.ts +4 -0
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +16 -7
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.js +26 -1
- package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
- package/dist/client/resources/BuiltinCapabilityDetail.d.ts.map +1 -1
- package/dist/client/resources/BuiltinCapabilityDetail.js +1 -1
- package/dist/client/resources/BuiltinCapabilityDetail.js.map +1 -1
- package/dist/client/resources/use-builtin-capabilities.d.ts +1 -1
- package/dist/client/resources/use-builtin-capabilities.d.ts.map +1 -1
- package/dist/client/resources/use-builtin-capabilities.js +2 -1
- package/dist/client/resources/use-builtin-capabilities.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +31 -0
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +14 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +36 -7
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/coding-tools/index.js +1 -1
- package/dist/coding-tools/index.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +6 -0
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -0
- package/dist/mcp/screen-memory-stdio.js +213 -0
- package/dist/mcp/screen-memory-stdio.js.map +1 -0
- package/dist/mcp-client/builtin-capabilities.d.ts +1 -1
- package/dist/mcp-client/builtin-capabilities.d.ts.map +1 -1
- package/dist/mcp-client/builtin-capabilities.js +10 -0
- package/dist/mcp-client/builtin-capabilities.js.map +1 -1
- package/dist/mcp-client/index.d.ts +1 -0
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +1 -0
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/screen-memory-local.d.ts +61 -0
- package/dist/mcp-client/screen-memory-local.d.ts.map +1 -0
- package/dist/mcp-client/screen-memory-local.js +336 -0
- package/dist/mcp-client/screen-memory-local.js.map +1 -0
- package/dist/onboarding/types.d.ts +7 -0
- package/dist/onboarding/types.d.ts.map +1 -1
- package/dist/onboarding/types.js.map +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +26 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agents-bundle.d.ts.map +1 -1
- package/dist/server/agents-bundle.js +22 -7
- package/dist/server/agents-bundle.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +5 -0
- package/dist/templates/default/app/global.css +35 -35
- package/docs/content/locales/ar-SA/template-design.mdx +6 -0
- package/docs/content/locales/de-DE/template-design.mdx +8 -0
- package/docs/content/locales/es-ES/template-design.mdx +10 -2
- package/docs/content/locales/fr-FR/template-design.mdx +10 -2
- package/docs/content/locales/hi-IN/template-design.mdx +7 -0
- package/docs/content/locales/ja-JP/template-design.mdx +7 -0
- package/docs/content/locales/ko-KR/template-design.mdx +7 -0
- package/docs/content/locales/pt-BR/template-design.mdx +9 -2
- package/docs/content/locales/zh-CN/template-design.mdx +6 -0
- package/docs/content/locales/zh-TW/template-design.mdx +6 -0
- package/docs/content/template-design.mdx +9 -2
- package/package.json +1 -1
- package/src/templates/default/app/global.css +35 -35
- package/corpus/templates/design/app/components/design/VariantGrid.tsx +0 -189
- package/corpus/templates/design/app/components/design/VariantHandoffCard.tsx +0 -115
- package/corpus/templates/design/app/hooks/use-variant-flow.ts +0 -258
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
IconLetterSpacing,
|
|
41
41
|
IconLineHeight,
|
|
42
42
|
IconLink,
|
|
43
|
+
IconLock,
|
|
43
44
|
IconMaximize,
|
|
44
45
|
IconMinus,
|
|
45
46
|
IconPalette,
|
|
@@ -51,7 +52,13 @@ import {
|
|
|
51
52
|
IconUnlink,
|
|
52
53
|
IconVector,
|
|
53
54
|
} from "@tabler/icons-react";
|
|
54
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
useCallback,
|
|
57
|
+
useEffect,
|
|
58
|
+
useRef,
|
|
59
|
+
useState,
|
|
60
|
+
type ReactNode,
|
|
61
|
+
} from "react";
|
|
55
62
|
|
|
56
63
|
import { Button } from "@/components/ui/button";
|
|
57
64
|
import {
|
|
@@ -100,12 +107,15 @@ import {
|
|
|
100
107
|
type AutoLayoutSizingAxis,
|
|
101
108
|
type ConstraintsValue,
|
|
102
109
|
type ExportSettingsValue,
|
|
110
|
+
imageFillToBackgroundStyles,
|
|
103
111
|
type DesignFillRow,
|
|
104
112
|
type DesignFillRowPatch,
|
|
105
113
|
type DesignGradientStop,
|
|
106
114
|
type DesignGradientStopPatch,
|
|
107
115
|
type DesignGradientType,
|
|
116
|
+
type ImageFillValue,
|
|
108
117
|
} from "./inspector";
|
|
118
|
+
import { IconLayoutSettings } from "./inspector/design-icons";
|
|
109
119
|
import type { DesignPaintType } from "./inspector/DesignColorPicker";
|
|
110
120
|
import { TweaksPanelContent } from "./TweaksPanel";
|
|
111
121
|
import type { ElementInfo } from "./types";
|
|
@@ -129,6 +139,7 @@ interface EditPanelProps {
|
|
|
129
139
|
onStylesChange?: (styles: Record<string, string>) => void;
|
|
130
140
|
onExport?: (settings: ExportSettingsValue[]) => void;
|
|
131
141
|
exporting?: boolean;
|
|
142
|
+
readOnly?: boolean;
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
/**
|
|
@@ -137,10 +148,23 @@ interface EditPanelProps {
|
|
|
137
148
|
* valid CSS length. Lets users type "32" and get the expected "32px" when
|
|
138
149
|
* the field is committed.
|
|
139
150
|
*/
|
|
140
|
-
function normalizeLengthValue(raw: string, defaultUnit: string): string {
|
|
151
|
+
function normalizeLengthValue(raw: string, defaultUnit: string): string | null {
|
|
141
152
|
const trimmed = raw.trim();
|
|
142
|
-
|
|
153
|
+
// Empty / invalid input returns null so the caller reverts the field instead
|
|
154
|
+
// of committing an empty or garbage CSS value (e.g. fontSize:"" or
|
|
155
|
+
// flexBasis:"abc") to the element's inline style.
|
|
156
|
+
if (!trimmed) return null;
|
|
143
157
|
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return `${trimmed}${defaultUnit}`;
|
|
158
|
+
// Validate free-form CSS so junk text never reaches the style. Fall back to
|
|
159
|
+
// accepting the value when CSS.supports is unavailable (SSR/tests) to keep
|
|
160
|
+
// prior behavior in non-DOM environments.
|
|
161
|
+
if (typeof CSS !== "undefined" && typeof CSS.supports === "function") {
|
|
162
|
+
const ok =
|
|
163
|
+
CSS.supports("width", trimmed) ||
|
|
164
|
+
CSS.supports("font-size", trimmed) ||
|
|
165
|
+
CSS.supports("flex-basis", trimmed);
|
|
166
|
+
return ok ? trimmed : null;
|
|
167
|
+
}
|
|
144
168
|
return trimmed;
|
|
145
169
|
}
|
|
146
170
|
|
|
@@ -180,6 +204,11 @@ function PropInput({
|
|
|
180
204
|
return;
|
|
181
205
|
}
|
|
182
206
|
const next = normalizeLengthValue(draft, defaultUnit);
|
|
207
|
+
if (next === null) {
|
|
208
|
+
// Invalid or empty — revert the field to the last committed value.
|
|
209
|
+
setDraft(value);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
183
212
|
if (next !== draft) setDraft(next);
|
|
184
213
|
if (next !== value) onChange(next);
|
|
185
214
|
};
|
|
@@ -220,6 +249,7 @@ function ColorInput({
|
|
|
220
249
|
onChange,
|
|
221
250
|
backgroundImage,
|
|
222
251
|
onBackgroundImageChange,
|
|
252
|
+
onImageFillChange,
|
|
223
253
|
blendMode,
|
|
224
254
|
onBlendModeChange,
|
|
225
255
|
supportsLayeredFills = false,
|
|
@@ -231,6 +261,7 @@ function ColorInput({
|
|
|
231
261
|
onChange: (value: string) => void;
|
|
232
262
|
backgroundImage?: string;
|
|
233
263
|
onBackgroundImageChange?: (value: string) => void;
|
|
264
|
+
onImageFillChange?: (value: ImageFillValue) => void;
|
|
234
265
|
blendMode?: string;
|
|
235
266
|
onBlendModeChange?: (value: string) => void;
|
|
236
267
|
supportsLayeredFills?: boolean;
|
|
@@ -540,6 +571,7 @@ function ColorInput({
|
|
|
540
571
|
onPaintValueChange={
|
|
541
572
|
supportsLayeredFills ? handlePaintValueChange : undefined
|
|
542
573
|
}
|
|
574
|
+
onImageFillChange={onImageFillChange}
|
|
543
575
|
blendMode={blendMode}
|
|
544
576
|
onBlendModeChange={onBlendModeChange}
|
|
545
577
|
showBlendMode={supportsLayeredFills}
|
|
@@ -745,7 +777,11 @@ function gradientTypeFromCss(
|
|
|
745
777
|
layer: string,
|
|
746
778
|
): DesignGradientType {
|
|
747
779
|
if (functionName.toLowerCase() === "conic") return "angular";
|
|
748
|
-
|
|
780
|
+
// Recognize both diamond serializations — EditPanel's "closest-corner" and
|
|
781
|
+
// GradientEditor's "ellipse closest-side" — so a diamond authored in either
|
|
782
|
+
// place round-trips as diamond instead of flipping to radial.
|
|
783
|
+
if (/closest-corner/i.test(layer) || /ellipse\s+closest-side/i.test(layer))
|
|
784
|
+
return "diamond";
|
|
749
785
|
if (functionName.toLowerCase() === "radial") return "radial";
|
|
750
786
|
return "linear";
|
|
751
787
|
}
|
|
@@ -890,8 +926,6 @@ function PropSlider({
|
|
|
890
926
|
);
|
|
891
927
|
}
|
|
892
928
|
|
|
893
|
-
const EmptyScrubIcon = () => null;
|
|
894
|
-
|
|
895
929
|
/**
|
|
896
930
|
* design-editor inspector section. Matches the design editor "Design" panel chrome:
|
|
897
931
|
* - NO left collapse chevron (the design editor uses none).
|
|
@@ -1109,6 +1143,99 @@ const FONT_WEIGHT_OPTIONS = [
|
|
|
1109
1143
|
{ value: "900", key: "black" },
|
|
1110
1144
|
] as const;
|
|
1111
1145
|
|
|
1146
|
+
type TextResizeMode = "auto-width" | "auto-height" | "fixed";
|
|
1147
|
+
|
|
1148
|
+
function cleanFontFamilyName(value: string): string {
|
|
1149
|
+
const trimmed = value.trim();
|
|
1150
|
+
if (
|
|
1151
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
1152
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
1153
|
+
) {
|
|
1154
|
+
return trimmed.slice(1, -1).trim();
|
|
1155
|
+
}
|
|
1156
|
+
return trimmed;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function splitFontFamilyList(value: string | undefined): string[] {
|
|
1160
|
+
const raw = value?.trim();
|
|
1161
|
+
if (!raw) return [];
|
|
1162
|
+
|
|
1163
|
+
const families: string[] = [];
|
|
1164
|
+
let token = "";
|
|
1165
|
+
let quote: '"' | "'" | null = null;
|
|
1166
|
+
|
|
1167
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
1168
|
+
const char = raw[i];
|
|
1169
|
+
if ((char === '"' || char === "'") && raw[i - 1] !== "\\") {
|
|
1170
|
+
if (quote === char) quote = null;
|
|
1171
|
+
else if (!quote) quote = char;
|
|
1172
|
+
token += char;
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
if (char === "," && !quote) {
|
|
1176
|
+
const cleaned = cleanFontFamilyName(token);
|
|
1177
|
+
if (cleaned) families.push(cleaned);
|
|
1178
|
+
token = "";
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
token += char;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const cleaned = cleanFontFamilyName(token);
|
|
1185
|
+
if (cleaned) families.push(cleaned);
|
|
1186
|
+
return families;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function normalizeFontFamilyName(value: string): string {
|
|
1190
|
+
return cleanFontFamilyName(value).replace(/\s+/g, " ").toLowerCase();
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function normalizeFontFamilyStack(value: string): string {
|
|
1194
|
+
return splitFontFamilyList(value).map(normalizeFontFamilyName).join(",");
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function displayFontFamilyName(value: string | undefined): string {
|
|
1198
|
+
const first = splitFontFamilyList(value)[0];
|
|
1199
|
+
if (!first) return "Sans Serif"; // i18n-ignore design generic font label
|
|
1200
|
+
|
|
1201
|
+
const normalized = normalizeFontFamilyName(first);
|
|
1202
|
+
if (normalized === "sans-serif") {
|
|
1203
|
+
return "Sans Serif"; // i18n-ignore design generic font label
|
|
1204
|
+
}
|
|
1205
|
+
if (normalized === "serif") return "Serif"; // i18n-ignore design generic font label
|
|
1206
|
+
if (normalized === "monospace") {
|
|
1207
|
+
return "Monospace"; // i18n-ignore design generic font label
|
|
1208
|
+
}
|
|
1209
|
+
if (normalized === "system-ui" || normalized === "-apple-system") {
|
|
1210
|
+
return "System UI"; // i18n-ignore design generic font label
|
|
1211
|
+
}
|
|
1212
|
+
if (normalized === "blinkmacsystemfont") {
|
|
1213
|
+
return "Apple System"; // i18n-ignore design generic font label
|
|
1214
|
+
}
|
|
1215
|
+
return first;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function resolveFontFamilySelectValue(value: string | undefined): string {
|
|
1219
|
+
const raw = value?.trim();
|
|
1220
|
+
if (!raw) return "sans-serif";
|
|
1221
|
+
|
|
1222
|
+
const normalizedStack = normalizeFontFamilyStack(raw);
|
|
1223
|
+
const exactOption = FONT_FAMILY_OPTIONS.find(
|
|
1224
|
+
(option) => normalizeFontFamilyStack(option.value) === normalizedStack,
|
|
1225
|
+
);
|
|
1226
|
+
if (exactOption) return exactOption.value;
|
|
1227
|
+
|
|
1228
|
+
const firstFamily = normalizeFontFamilyName(
|
|
1229
|
+
splitFontFamilyList(raw)[0] ?? "",
|
|
1230
|
+
);
|
|
1231
|
+
const firstFamilyOption = FONT_FAMILY_OPTIONS.find(
|
|
1232
|
+
(option) =>
|
|
1233
|
+
normalizeFontFamilyName(splitFontFamilyList(option.value)[0] ?? "") ===
|
|
1234
|
+
firstFamily,
|
|
1235
|
+
);
|
|
1236
|
+
return firstFamilyOption?.value ?? raw;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1112
1239
|
const ALIGN_SELF_OPTIONS = [
|
|
1113
1240
|
{ value: "auto", key: "auto" },
|
|
1114
1241
|
{ value: "flex-start", key: "start" },
|
|
@@ -1170,9 +1297,39 @@ function resolveLineHeight(
|
|
|
1170
1297
|
return Number.isFinite(numeric) && numeric > 0 ? numeric : 1.2;
|
|
1171
1298
|
}
|
|
1172
1299
|
|
|
1300
|
+
// Matches a 2D rotate()/rotateZ() with any CSS angle unit (not rotateX/Y/3d).
|
|
1301
|
+
const ROTATE_FN_PATTERN =
|
|
1302
|
+
/rotate[Zz]?\(\s*([+-]?[\d.]+(?:e[+-]?\d+)?)(deg|rad|turn|grad)?\s*\)/i;
|
|
1303
|
+
|
|
1173
1304
|
function parseRotationValue(transform: string | undefined): number {
|
|
1174
|
-
|
|
1175
|
-
|
|
1305
|
+
if (!transform || transform === "none") return 0;
|
|
1306
|
+
const match = transform.match(ROTATE_FN_PATTERN);
|
|
1307
|
+
if (match) {
|
|
1308
|
+
const value = Number(match[1]);
|
|
1309
|
+
if (Number.isFinite(value)) {
|
|
1310
|
+
const unit = (match[2] || "deg").toLowerCase();
|
|
1311
|
+
const deg =
|
|
1312
|
+
unit === "rad"
|
|
1313
|
+
? value * (180 / Math.PI)
|
|
1314
|
+
: unit === "turn"
|
|
1315
|
+
? value * 360
|
|
1316
|
+
: unit === "grad"
|
|
1317
|
+
? value * 0.9
|
|
1318
|
+
: value;
|
|
1319
|
+
return Math.round(deg * 10) / 10;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
// Fallback for rotate3d()/matrix()/skew composites: read the 2D rotation
|
|
1323
|
+
// component off the resolved matrix so the panel doesn't report 0.
|
|
1324
|
+
if (typeof DOMMatrixReadOnly !== "undefined") {
|
|
1325
|
+
try {
|
|
1326
|
+
const m = new DOMMatrixReadOnly(transform);
|
|
1327
|
+
return Math.round(((Math.atan2(m.b, m.a) * 180) / Math.PI) * 10) / 10;
|
|
1328
|
+
} catch {
|
|
1329
|
+
// Unparseable transform — fall through to 0.
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
return 0;
|
|
1176
1333
|
}
|
|
1177
1334
|
|
|
1178
1335
|
/**
|
|
@@ -1191,8 +1348,10 @@ function parseScaleValue(value: string | undefined): [number, number] {
|
|
|
1191
1348
|
function mergeRotationValue(transform: string | undefined, degrees: number) {
|
|
1192
1349
|
const nextRotate = `rotate(${Math.round(degrees * 10) / 10}deg)`;
|
|
1193
1350
|
if (!transform || transform === "none") return nextRotate;
|
|
1194
|
-
|
|
1195
|
-
|
|
1351
|
+
// Replace an existing rotate()/rotateZ() in ANY unit so we don't append a
|
|
1352
|
+
// second rotate() (which would compound, e.g. "rotate(0.5turn) rotate(30deg)").
|
|
1353
|
+
if (ROTATE_FN_PATTERN.test(transform)) {
|
|
1354
|
+
return transform.replace(ROTATE_FN_PATTERN, nextRotate);
|
|
1196
1355
|
}
|
|
1197
1356
|
return `${transform} ${nextRotate}`;
|
|
1198
1357
|
}
|
|
@@ -1228,6 +1387,8 @@ function ScrubStyleInput({
|
|
|
1228
1387
|
step = 1,
|
|
1229
1388
|
labelClassName,
|
|
1230
1389
|
inputClassName,
|
|
1390
|
+
ariaLabel,
|
|
1391
|
+
tooltipLabel,
|
|
1231
1392
|
hideIcon = true,
|
|
1232
1393
|
}: {
|
|
1233
1394
|
label: string;
|
|
@@ -1241,11 +1402,15 @@ function ScrubStyleInput({
|
|
|
1241
1402
|
labelClassName?: string;
|
|
1242
1403
|
inputClassName?: string;
|
|
1243
1404
|
hideIcon?: boolean;
|
|
1405
|
+
ariaLabel?: string;
|
|
1406
|
+
tooltipLabel?: string;
|
|
1244
1407
|
}) {
|
|
1245
1408
|
return (
|
|
1246
1409
|
<ScrubInput
|
|
1247
1410
|
label={label}
|
|
1248
|
-
|
|
1411
|
+
ariaLabel={ariaLabel}
|
|
1412
|
+
tooltipLabel={tooltipLabel}
|
|
1413
|
+
icon={hideIcon ? null : undefined}
|
|
1249
1414
|
value={value ? parseNumericValue(value) : (placeholder ?? 0)}
|
|
1250
1415
|
onChange={onChange}
|
|
1251
1416
|
unit={unit}
|
|
@@ -1596,6 +1761,33 @@ function inferElementSizing(
|
|
|
1596
1761
|
return "fixed";
|
|
1597
1762
|
}
|
|
1598
1763
|
|
|
1764
|
+
/**
|
|
1765
|
+
* Return the element's geometric dimension on the given axis in CSS pixels.
|
|
1766
|
+
*
|
|
1767
|
+
* `getComputedStyle().width/height` always resolves to a computed px value
|
|
1768
|
+
* (even for `width: auto` the browser returns e.g. "200px"). For rotated
|
|
1769
|
+
* elements this is the pre-rotation CSS box size — what Figma shows in the
|
|
1770
|
+
* inspector — while `getBoundingClientRect().width/height` would be the
|
|
1771
|
+
* axis-aligned bounding box which is inflated by the rotation.
|
|
1772
|
+
*
|
|
1773
|
+
* Falls back to the bounding-rect dimension only when the computed style is
|
|
1774
|
+
* missing or unparseable (e.g. the bridge hasn't populated it yet).
|
|
1775
|
+
*/
|
|
1776
|
+
function cssElementSize(
|
|
1777
|
+
element: ElementInfo,
|
|
1778
|
+
axis: AutoLayoutSizingAxis,
|
|
1779
|
+
): number {
|
|
1780
|
+
const isHorizontal = axis === "horizontal";
|
|
1781
|
+
const cssValue = isHorizontal
|
|
1782
|
+
? element.computedStyles.width
|
|
1783
|
+
: element.computedStyles.height;
|
|
1784
|
+
const parsed = parseFloat(cssValue || "");
|
|
1785
|
+
const fallback = isHorizontal
|
|
1786
|
+
? element.boundingRect.width
|
|
1787
|
+
: element.boundingRect.height;
|
|
1788
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1599
1791
|
function commitElementSizing(
|
|
1600
1792
|
element: ElementInfo,
|
|
1601
1793
|
axis: AutoLayoutSizingAxis,
|
|
@@ -1605,12 +1797,10 @@ function commitElementSizing(
|
|
|
1605
1797
|
) {
|
|
1606
1798
|
const isHorizontal = axis === "horizontal";
|
|
1607
1799
|
const sizeProperty = isHorizontal ? "width" : "height";
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
),
|
|
1613
|
-
);
|
|
1800
|
+
// Use CSS computed dimension (pre-rotation box size) as the seed for "fixed"
|
|
1801
|
+
// sizing so a rotated element is locked to its actual CSS width/height rather
|
|
1802
|
+
// than the inflated axis-aligned bounding rect.
|
|
1803
|
+
const resolvedSize = Math.max(1, Math.round(cssElementSize(element, axis)));
|
|
1614
1804
|
const parentDirection = parentFlexDirection(element);
|
|
1615
1805
|
const isFlex = isParentFlex(element);
|
|
1616
1806
|
const isGrid = isParentGrid(element);
|
|
@@ -1716,7 +1906,7 @@ function InspectorTabsHeader({
|
|
|
1716
1906
|
const t = useT();
|
|
1717
1907
|
|
|
1718
1908
|
return (
|
|
1719
|
-
<div className="flex min-h-8 shrink-0 items-center justify-between gap-1 border-b border-border/90 px-2">
|
|
1909
|
+
<div className="flex min-h-8 shrink-0 items-center justify-between gap-1 border-b border-border/90 px-2 py-1">
|
|
1720
1910
|
<Tabs
|
|
1721
1911
|
value={activeTab}
|
|
1722
1912
|
onValueChange={(value) => onActiveTabChange(value as InspectorTab)}
|
|
@@ -1858,12 +2048,113 @@ function InspectorIconButton({
|
|
|
1858
2048
|
|
|
1859
2049
|
function InspectorSegment({ children }: { children: ReactNode }) {
|
|
1860
2050
|
return (
|
|
1861
|
-
<div className="flex min-w-0 overflow-hidden rounded-md bg-[var(--design-editor-control-bg)]">
|
|
2051
|
+
<div className="flex w-fit max-w-full min-w-0 overflow-hidden rounded-md bg-[var(--design-editor-control-bg)]">
|
|
1862
2052
|
{children}
|
|
1863
2053
|
</div>
|
|
1864
2054
|
);
|
|
1865
2055
|
}
|
|
1866
2056
|
|
|
2057
|
+
function TextResizeControls({
|
|
2058
|
+
resizeMode,
|
|
2059
|
+
onResizeModeChange,
|
|
2060
|
+
}: {
|
|
2061
|
+
resizeMode: TextResizeMode;
|
|
2062
|
+
onResizeModeChange: (mode: TextResizeMode) => void;
|
|
2063
|
+
}) {
|
|
2064
|
+
const t = useT();
|
|
2065
|
+
|
|
2066
|
+
return (
|
|
2067
|
+
<InspectorSegment>
|
|
2068
|
+
<InspectorIconButton
|
|
2069
|
+
label={t("editPanel.textResize.autoWidth")}
|
|
2070
|
+
active={resizeMode === "auto-width"}
|
|
2071
|
+
onClick={() => onResizeModeChange("auto-width")}
|
|
2072
|
+
>
|
|
2073
|
+
<IconArrowAutofitWidth className="size-3.5" />
|
|
2074
|
+
</InspectorIconButton>
|
|
2075
|
+
<InspectorIconButton
|
|
2076
|
+
label={t("editPanel.textResize.autoHeight")}
|
|
2077
|
+
active={resizeMode === "auto-height"}
|
|
2078
|
+
onClick={() => onResizeModeChange("auto-height")}
|
|
2079
|
+
>
|
|
2080
|
+
<IconArrowAutofitHeight className="size-3.5" />
|
|
2081
|
+
</InspectorIconButton>
|
|
2082
|
+
<InspectorIconButton
|
|
2083
|
+
label={t("editPanel.textResize.fixed")}
|
|
2084
|
+
active={resizeMode === "fixed"}
|
|
2085
|
+
onClick={() => onResizeModeChange("fixed")}
|
|
2086
|
+
>
|
|
2087
|
+
<IconSquare className="size-3.5" />
|
|
2088
|
+
</InspectorIconButton>
|
|
2089
|
+
</InspectorSegment>
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
function TypographyDetailsPopover({
|
|
2094
|
+
resizeMode,
|
|
2095
|
+
onResizeModeChange,
|
|
2096
|
+
}: {
|
|
2097
|
+
resizeMode: TextResizeMode;
|
|
2098
|
+
onResizeModeChange: (mode: TextResizeMode) => void;
|
|
2099
|
+
}) {
|
|
2100
|
+
const [open, setOpen] = useState(false);
|
|
2101
|
+
|
|
2102
|
+
return (
|
|
2103
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
2104
|
+
<PopoverTrigger asChild>
|
|
2105
|
+
<Button
|
|
2106
|
+
type="button"
|
|
2107
|
+
variant="ghost"
|
|
2108
|
+
size="icon"
|
|
2109
|
+
aria-label={"Typography details" /* i18n-ignore design action */}
|
|
2110
|
+
aria-pressed={open}
|
|
2111
|
+
className={cn(
|
|
2112
|
+
"h-6 min-w-6 cursor-pointer rounded-md text-muted-foreground hover:bg-[var(--design-editor-panel-raised-bg)] hover:text-foreground",
|
|
2113
|
+
open &&
|
|
2114
|
+
"bg-[var(--design-editor-accent-color)]/20 text-[var(--design-editor-accent-color)] hover:text-[var(--design-editor-accent-color)]",
|
|
2115
|
+
)}
|
|
2116
|
+
>
|
|
2117
|
+
<IconLayoutSettings className="size-3.5" />
|
|
2118
|
+
</Button>
|
|
2119
|
+
</PopoverTrigger>
|
|
2120
|
+
<PopoverContent
|
|
2121
|
+
side="left"
|
|
2122
|
+
align="end"
|
|
2123
|
+
sideOffset={8}
|
|
2124
|
+
className="z-[100010] w-[360px] rounded-xl border-[var(--design-editor-control-border)] bg-[var(--design-editor-panel-bg)] p-0 text-foreground shadow-2xl"
|
|
2125
|
+
>
|
|
2126
|
+
<div className="flex items-center gap-1 border-b border-[var(--design-editor-control-border)] p-2.5">
|
|
2127
|
+
<div className="flex rounded-md bg-[var(--design-editor-control-bg)] p-0.5">
|
|
2128
|
+
<span className="rounded bg-[var(--design-editor-panel-raised-bg)] px-2.5 py-1 text-[11px] font-semibold text-foreground">
|
|
2129
|
+
{"Basics" /* i18n-ignore design typography details tab */}
|
|
2130
|
+
</span>
|
|
2131
|
+
<span className="px-2.5 py-1 text-[11px] font-medium text-muted-foreground">
|
|
2132
|
+
{"Details" /* i18n-ignore design typography details tab */}
|
|
2133
|
+
</span>
|
|
2134
|
+
<span className="px-2.5 py-1 text-[11px] font-medium text-muted-foreground">
|
|
2135
|
+
{"Variable" /* i18n-ignore design typography details tab */}
|
|
2136
|
+
</span>
|
|
2137
|
+
</div>
|
|
2138
|
+
</div>
|
|
2139
|
+
<div className="space-y-3 p-4 text-[11px]">
|
|
2140
|
+
<div className="flex h-20 items-center justify-center rounded-md bg-[var(--design-editor-control-bg)] text-[18px] text-muted-foreground/80">
|
|
2141
|
+
{"Preview" /* i18n-ignore design typography details preview */}
|
|
2142
|
+
</div>
|
|
2143
|
+
<div className="flex items-center justify-between gap-3">
|
|
2144
|
+
<span className="text-[11px] font-medium text-muted-foreground">
|
|
2145
|
+
{"Text box" /* i18n-ignore design typography details label */}
|
|
2146
|
+
</span>
|
|
2147
|
+
<TextResizeControls
|
|
2148
|
+
resizeMode={resizeMode}
|
|
2149
|
+
onResizeModeChange={onResizeModeChange}
|
|
2150
|
+
/>
|
|
2151
|
+
</div>
|
|
2152
|
+
</div>
|
|
2153
|
+
</PopoverContent>
|
|
2154
|
+
</Popover>
|
|
2155
|
+
);
|
|
2156
|
+
}
|
|
2157
|
+
|
|
1867
2158
|
function CornerRadiusControl({
|
|
1868
2159
|
styles,
|
|
1869
2160
|
onStyleChange,
|
|
@@ -2025,10 +2316,10 @@ function StrokeLayerControl({
|
|
|
2025
2316
|
const nextPrefix = next === "outside" ? "outline" : "border";
|
|
2026
2317
|
onStyleChange(`${nextPrefix}Color`, color);
|
|
2027
2318
|
onStyleChange(`${nextPrefix}Width`, width || "1px");
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
);
|
|
2319
|
+
// Preserve the original border-style so a hidden stroke (style:none, kept
|
|
2320
|
+
// visible as a row because width>0) stays hidden when its position moves
|
|
2321
|
+
// between inside/outside. Only default to solid when there's no style at all.
|
|
2322
|
+
onStyleChange(`${nextPrefix}Style`, styleValue || "solid");
|
|
2032
2323
|
onRemove();
|
|
2033
2324
|
};
|
|
2034
2325
|
|
|
@@ -2149,7 +2440,12 @@ function parseShadowLayer(layer: string, index: number): ShadowLayer {
|
|
|
2149
2440
|
const tokens = splitCssTokens(layer);
|
|
2150
2441
|
const inset = tokens.includes("inset");
|
|
2151
2442
|
const colorToken =
|
|
2152
|
-
tokens.find((token) => parseCssColor(token) || token === "transparent")
|
|
2443
|
+
tokens.find((token) => parseCssColor(token) || token === "transparent") ??
|
|
2444
|
+
// Preserve a color we don't parse into RGBA (currentColor, var(--x), or any
|
|
2445
|
+
// unrecognized keyword): the color is the non-inset token that doesn't look
|
|
2446
|
+
// like a numeric length. Without this, tweaking x/y/blur would reset it to
|
|
2447
|
+
// the hardcoded default below.
|
|
2448
|
+
tokens.find((token) => token !== "inset" && !/^[-+]?[\d.]/.test(token)) ??
|
|
2153
2449
|
"rgba(0, 0, 0, 0.25)";
|
|
2154
2450
|
const numericTokens = tokens
|
|
2155
2451
|
.filter((token) => token !== "inset" && token !== colorToken)
|
|
@@ -2330,20 +2626,29 @@ function ShadowEffectRow({
|
|
|
2330
2626
|
function PageProperties({
|
|
2331
2627
|
styles,
|
|
2332
2628
|
onStyleChange,
|
|
2629
|
+
onStylesChange,
|
|
2333
2630
|
}: {
|
|
2334
2631
|
styles: Record<string, string>;
|
|
2335
2632
|
onStyleChange: (property: string, value: string) => void;
|
|
2633
|
+
onStylesChange?: (styles: Record<string, string>) => void;
|
|
2336
2634
|
}) {
|
|
2337
2635
|
const t = useT();
|
|
2338
|
-
const
|
|
2636
|
+
const baseFontFamilyOptions = FONT_FAMILY_OPTIONS.map((option) => ({
|
|
2339
2637
|
value: option.value,
|
|
2340
2638
|
label: t(`editPanel.fontFamilies.${option.key}`),
|
|
2341
2639
|
}));
|
|
2342
|
-
const fontFamily =
|
|
2343
|
-
|
|
2640
|
+
const fontFamily = resolveFontFamilySelectValue(styles.fontFamily);
|
|
2641
|
+
const fontFamilyOptions = FONT_FAMILY_OPTIONS.some(
|
|
2642
|
+
(option) => option.value === fontFamily,
|
|
2344
2643
|
)
|
|
2345
|
-
?
|
|
2346
|
-
:
|
|
2644
|
+
? baseFontFamilyOptions
|
|
2645
|
+
: [
|
|
2646
|
+
{
|
|
2647
|
+
value: fontFamily,
|
|
2648
|
+
label: displayFontFamilyName(styles.fontFamily || fontFamily),
|
|
2649
|
+
},
|
|
2650
|
+
...baseFontFamilyOptions,
|
|
2651
|
+
];
|
|
2347
2652
|
|
|
2348
2653
|
return (
|
|
2349
2654
|
<div>
|
|
@@ -2354,6 +2659,13 @@ function PageProperties({
|
|
|
2354
2659
|
onChange={(v) => onStyleChange("backgroundColor", v)}
|
|
2355
2660
|
backgroundImage={styles.backgroundImage || ""}
|
|
2356
2661
|
onBackgroundImageChange={(v) => onStyleChange("backgroundImage", v)}
|
|
2662
|
+
onImageFillChange={(value) =>
|
|
2663
|
+
commitStylePatch(
|
|
2664
|
+
imageFillToBackgroundStyles(value),
|
|
2665
|
+
onStyleChange,
|
|
2666
|
+
onStylesChange,
|
|
2667
|
+
)
|
|
2668
|
+
}
|
|
2357
2669
|
blendMode={styles.backgroundBlendMode || "normal"}
|
|
2358
2670
|
onBlendModeChange={(v) => onStyleChange("backgroundBlendMode", v)}
|
|
2359
2671
|
supportsLayeredFills
|
|
@@ -2386,10 +2698,22 @@ function TypographyProperties({
|
|
|
2386
2698
|
}) {
|
|
2387
2699
|
const t = useT();
|
|
2388
2700
|
const styles = element.computedStyles;
|
|
2389
|
-
const
|
|
2701
|
+
const baseFontFamilyOptions = FONT_FAMILY_OPTIONS.map((option) => ({
|
|
2390
2702
|
value: option.value,
|
|
2391
2703
|
label: t(`editPanel.fontFamilies.${option.key}`),
|
|
2392
2704
|
}));
|
|
2705
|
+
const fontFamily = resolveFontFamilySelectValue(styles.fontFamily);
|
|
2706
|
+
const fontFamilyOptions = FONT_FAMILY_OPTIONS.some(
|
|
2707
|
+
(option) => option.value === fontFamily,
|
|
2708
|
+
)
|
|
2709
|
+
? baseFontFamilyOptions
|
|
2710
|
+
: [
|
|
2711
|
+
{
|
|
2712
|
+
value: fontFamily,
|
|
2713
|
+
label: displayFontFamilyName(styles.fontFamily || fontFamily),
|
|
2714
|
+
},
|
|
2715
|
+
...baseFontFamilyOptions,
|
|
2716
|
+
];
|
|
2393
2717
|
const fontWeightOptions = FONT_WEIGHT_OPTIONS.map((option) => ({
|
|
2394
2718
|
value: option.value,
|
|
2395
2719
|
label: t(`editPanel.fontWeights.${option.key}`),
|
|
@@ -2404,7 +2728,7 @@ function TypographyProperties({
|
|
|
2404
2728
|
!styles.width || styles.width === "auto" || styles.width === "max-content";
|
|
2405
2729
|
const heightIsAuto = !styles.height || styles.height === "auto";
|
|
2406
2730
|
const noWrap = styles.whiteSpace === "nowrap";
|
|
2407
|
-
const resizeMode:
|
|
2731
|
+
const resizeMode: TextResizeMode =
|
|
2408
2732
|
widthIsAuto && noWrap
|
|
2409
2733
|
? "auto-width"
|
|
2410
2734
|
: !heightIsAuto && !widthIsAuto
|
|
@@ -2412,7 +2736,7 @@ function TypographyProperties({
|
|
|
2412
2736
|
: "auto-height";
|
|
2413
2737
|
const currentWidth = styles.width && !widthIsAuto ? styles.width : "200px";
|
|
2414
2738
|
const currentHeight = styles.height && !heightIsAuto ? styles.height : "48px";
|
|
2415
|
-
const setResizeMode = (mode:
|
|
2739
|
+
const setResizeMode = (mode: TextResizeMode) => {
|
|
2416
2740
|
if (mode === "auto-width") {
|
|
2417
2741
|
onStyleChange("width", "auto");
|
|
2418
2742
|
onStyleChange("height", "auto");
|
|
@@ -2462,51 +2786,36 @@ function TypographyProperties({
|
|
|
2462
2786
|
|
|
2463
2787
|
return (
|
|
2464
2788
|
<PanelSection title={t("editPanel.sections.typography")}>
|
|
2465
|
-
{/* Row
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
label={"Auto height" /* i18n-ignore design text-resize mode */}
|
|
2476
|
-
active={resizeMode === "auto-height"}
|
|
2477
|
-
onClick={() => setResizeMode("auto-height")}
|
|
2478
|
-
>
|
|
2479
|
-
<IconArrowAutofitHeight className="size-3.5" />
|
|
2480
|
-
</InspectorIconButton>
|
|
2481
|
-
<InspectorIconButton
|
|
2482
|
-
label={"Fixed size" /* i18n-ignore design text-resize mode */}
|
|
2483
|
-
active={resizeMode === "fixed"}
|
|
2484
|
-
onClick={() => setResizeMode("fixed")}
|
|
2789
|
+
{/* Row 1: font family full-width.
|
|
2790
|
+
Wrapped in a height-constrained div so the SelectTrigger button's
|
|
2791
|
+
hit-target is exactly h-6 (24 px) and cannot visually or physically
|
|
2792
|
+
overlap the weight/size row below (bug: trigger extended ~12 px into
|
|
2793
|
+
the next row, causing clicks meant for the size input to open this
|
|
2794
|
+
dropdown instead). */}
|
|
2795
|
+
<div className="h-6 overflow-hidden">
|
|
2796
|
+
<Select
|
|
2797
|
+
value={fontFamily}
|
|
2798
|
+
onValueChange={(v) => onStyleChange("fontFamily", v)}
|
|
2485
2799
|
>
|
|
2486
|
-
<
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
{opt.label}
|
|
2506
|
-
</SelectItem>
|
|
2507
|
-
))}
|
|
2508
|
-
</SelectContent>
|
|
2509
|
-
</Select>
|
|
2800
|
+
<SelectTrigger
|
|
2801
|
+
aria-label={t("editPanel.labels.font")}
|
|
2802
|
+
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)]"
|
|
2803
|
+
>
|
|
2804
|
+
<SelectValue />
|
|
2805
|
+
</SelectTrigger>
|
|
2806
|
+
<SelectContent>
|
|
2807
|
+
{fontFamilyOptions.map((opt) => (
|
|
2808
|
+
<SelectItem
|
|
2809
|
+
key={opt.value}
|
|
2810
|
+
value={opt.value}
|
|
2811
|
+
className="text-[11px]"
|
|
2812
|
+
>
|
|
2813
|
+
{opt.label}
|
|
2814
|
+
</SelectItem>
|
|
2815
|
+
))}
|
|
2816
|
+
</SelectContent>
|
|
2817
|
+
</Select>
|
|
2818
|
+
</div>
|
|
2510
2819
|
|
|
2511
2820
|
{/* Row 2: weight + size side by side */}
|
|
2512
2821
|
<div className="grid grid-cols-2 gap-1.5">
|
|
@@ -2580,7 +2889,7 @@ function TypographyProperties({
|
|
|
2580
2889
|
</div>
|
|
2581
2890
|
|
|
2582
2891
|
{/* Row 4: horizontal + vertical text alignment */}
|
|
2583
|
-
<div className="
|
|
2892
|
+
<div className="flex items-center gap-1.5">
|
|
2584
2893
|
<InspectorSegment>
|
|
2585
2894
|
<InspectorIconButton
|
|
2586
2895
|
label={t("editPanel.textAligns.left")}
|
|
@@ -2634,6 +2943,12 @@ function TypographyProperties({
|
|
|
2634
2943
|
<IconLayoutAlignBottom className="size-3.5" />
|
|
2635
2944
|
</InspectorIconButton>
|
|
2636
2945
|
</InspectorSegment>
|
|
2946
|
+
<div className="ml-auto shrink-0">
|
|
2947
|
+
<TypographyDetailsPopover
|
|
2948
|
+
resizeMode={resizeMode}
|
|
2949
|
+
onResizeModeChange={setResizeMode}
|
|
2950
|
+
/>
|
|
2951
|
+
</div>
|
|
2637
2952
|
</div>
|
|
2638
2953
|
</PanelSection>
|
|
2639
2954
|
);
|
|
@@ -2699,8 +3014,8 @@ function FlexContainerControls({
|
|
|
2699
3014
|
},
|
|
2700
3015
|
clipContent: styles.overflow === "hidden",
|
|
2701
3016
|
resolvedSize: {
|
|
2702
|
-
horizontal: element
|
|
2703
|
-
vertical: element
|
|
3017
|
+
horizontal: cssElementSize(element, "horizontal"),
|
|
3018
|
+
vertical: cssElementSize(element, "vertical"),
|
|
2704
3019
|
},
|
|
2705
3020
|
// Forward the raw CSS display so the matrix can render the correct Flow
|
|
2706
3021
|
// state (normal flow for block/grid, flex for flex/inline-flex). Added as an
|
|
@@ -2784,6 +3099,9 @@ function FlexContainerControls({
|
|
|
2784
3099
|
onStylesChange,
|
|
2785
3100
|
);
|
|
2786
3101
|
}}
|
|
3102
|
+
onChildSizeChange={(axis, px) =>
|
|
3103
|
+
onStyleChange(axis === "horizontal" ? "width" : "height", `${px}px`)
|
|
3104
|
+
}
|
|
2787
3105
|
onChildMinMaxChange={(axis, kind, val) =>
|
|
2788
3106
|
commitElementMinMax(axis, kind, val, onStyleChange)
|
|
2789
3107
|
}
|
|
@@ -2927,7 +3245,7 @@ function LayoutContextProperties({
|
|
|
2927
3245
|
axis="W"
|
|
2928
3246
|
sizingAxis="horizontal"
|
|
2929
3247
|
value={inferElementSizing(element, "horizontal")}
|
|
2930
|
-
resolvedSize={element
|
|
3248
|
+
resolvedSize={cssElementSize(element, "horizontal")}
|
|
2931
3249
|
minMax={readElementMinMax(element, "horizontal")}
|
|
2932
3250
|
options={availableSizing.horizontal ?? ["fixed"]}
|
|
2933
3251
|
disabled={false}
|
|
@@ -2940,6 +3258,7 @@ function LayoutContextProperties({
|
|
|
2940
3258
|
onStylesChange,
|
|
2941
3259
|
)
|
|
2942
3260
|
}
|
|
3261
|
+
onSizeChange={(px) => onStyleChange("width", `${px}px`)}
|
|
2943
3262
|
onMinMaxChange={(axis, kind, val) =>
|
|
2944
3263
|
commitElementMinMax(axis, kind, val, onStyleChange)
|
|
2945
3264
|
}
|
|
@@ -2948,7 +3267,7 @@ function LayoutContextProperties({
|
|
|
2948
3267
|
axis="H"
|
|
2949
3268
|
sizingAxis="vertical"
|
|
2950
3269
|
value={inferElementSizing(element, "vertical")}
|
|
2951
|
-
resolvedSize={element
|
|
3270
|
+
resolvedSize={cssElementSize(element, "vertical")}
|
|
2952
3271
|
minMax={readElementMinMax(element, "vertical")}
|
|
2953
3272
|
options={availableSizing.vertical ?? ["fixed"]}
|
|
2954
3273
|
disabled={false}
|
|
@@ -2961,6 +3280,7 @@ function LayoutContextProperties({
|
|
|
2961
3280
|
onStylesChange,
|
|
2962
3281
|
)
|
|
2963
3282
|
}
|
|
3283
|
+
onSizeChange={(px) => onStyleChange("height", `${px}px`)}
|
|
2964
3284
|
onMinMaxChange={(axis, kind, val) =>
|
|
2965
3285
|
commitElementMinMax(axis, kind, val, onStyleChange)
|
|
2966
3286
|
}
|
|
@@ -3313,6 +3633,8 @@ function PositionLayoutProperties({
|
|
|
3313
3633
|
<div className="grid grid-cols-2 gap-2">
|
|
3314
3634
|
<ScrubStyleInput
|
|
3315
3635
|
label="X"
|
|
3636
|
+
ariaLabel="X-position"
|
|
3637
|
+
tooltipLabel="X-position"
|
|
3316
3638
|
value={styles.left || ""}
|
|
3317
3639
|
placeholder={element.boundingRect.x}
|
|
3318
3640
|
inputClassName="h-6"
|
|
@@ -3320,6 +3642,8 @@ function PositionLayoutProperties({
|
|
|
3320
3642
|
/>
|
|
3321
3643
|
<ScrubStyleInput
|
|
3322
3644
|
label="Y"
|
|
3645
|
+
ariaLabel="Y-position"
|
|
3646
|
+
tooltipLabel="Y-position"
|
|
3323
3647
|
value={styles.top || ""}
|
|
3324
3648
|
placeholder={element.boundingRect.y}
|
|
3325
3649
|
inputClassName="h-6"
|
|
@@ -3408,8 +3732,14 @@ function FillProperties({
|
|
|
3408
3732
|
const [hiddenFillStash, setHiddenFillStash] = useState<
|
|
3409
3733
|
Record<string, string>
|
|
3410
3734
|
>({});
|
|
3735
|
+
// Same non-destructive idea for background gradient/image layers: stash the
|
|
3736
|
+
// exact layer string on hide so per-stop opacity survives a hide→show toggle
|
|
3737
|
+
// instead of being flattened to all-0 then all-100.
|
|
3738
|
+
const [hiddenLayerStash, setHiddenLayerStash] = useState<
|
|
3739
|
+
Record<string, string>
|
|
3740
|
+
>({});
|
|
3411
3741
|
const stashKey = `${elementIdentityKey(element)}:${fillProperty}`;
|
|
3412
|
-
const isHidden = fillValue
|
|
3742
|
+
const isHidden = !colorHasVisibleAlpha(fillValue);
|
|
3413
3743
|
const handleFillVisibilityToggle = () => {
|
|
3414
3744
|
if (isHidden) {
|
|
3415
3745
|
// Restore the stashed color, or fall back to a sensible default.
|
|
@@ -3564,8 +3894,17 @@ function FillProperties({
|
|
|
3564
3894
|
const opacity = gradient
|
|
3565
3895
|
? averageGradientOpacity(gradient.stops)
|
|
3566
3896
|
: 100;
|
|
3897
|
+
const layerStashKey = `${elementIdentityKey(element)}:layer:${index}`;
|
|
3898
|
+
const stashedLayer = hiddenLayerStash[layerStashKey];
|
|
3899
|
+
const hiddenImagePlaceholder = Boolean(
|
|
3900
|
+
stashedLayer && gradient && opacity <= 0,
|
|
3901
|
+
);
|
|
3567
3902
|
const label = gradient
|
|
3568
|
-
?
|
|
3903
|
+
? hiddenImagePlaceholder
|
|
3904
|
+
? `${"Image" /* i18n-ignore design inspector paint row */} ${
|
|
3905
|
+
index + 1
|
|
3906
|
+
}`
|
|
3907
|
+
: `${gradientLabel(gradient.type)} ${index + 1}`
|
|
3569
3908
|
: `${"Image" /* i18n-ignore design inspector paint row */} ${
|
|
3570
3909
|
index + 1
|
|
3571
3910
|
}`;
|
|
@@ -3663,17 +4002,70 @@ function FillProperties({
|
|
|
3663
4002
|
: t("editPanel.labels.hideLayer")
|
|
3664
4003
|
}
|
|
3665
4004
|
onClick={() => {
|
|
3666
|
-
if (
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
4005
|
+
if (opacity <= 0) {
|
|
4006
|
+
// Show: restore the exact pre-hide layer if stashed,
|
|
4007
|
+
// otherwise fall back to forcing every stop opaque.
|
|
4008
|
+
const stashed = hiddenLayerStash[layerStashKey];
|
|
4009
|
+
if (stashed !== undefined) {
|
|
4010
|
+
replaceLayer(stashed);
|
|
4011
|
+
setHiddenLayerStash((prev) => {
|
|
4012
|
+
const next = { ...prev };
|
|
4013
|
+
delete next[layerStashKey];
|
|
4014
|
+
return next;
|
|
4015
|
+
});
|
|
4016
|
+
} else if (gradient) {
|
|
4017
|
+
replaceLayer(
|
|
4018
|
+
buildGradientLayer(
|
|
4019
|
+
gradient.type,
|
|
4020
|
+
gradient.stops.map((stop) => ({
|
|
4021
|
+
...stop,
|
|
4022
|
+
opacity: 100,
|
|
4023
|
+
})),
|
|
4024
|
+
gradient.prefix,
|
|
4025
|
+
),
|
|
4026
|
+
);
|
|
4027
|
+
}
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
4030
|
+
if (!gradient) {
|
|
4031
|
+
setHiddenLayerStash((prev) => ({
|
|
4032
|
+
...prev,
|
|
4033
|
+
[layerStashKey]: layer,
|
|
4034
|
+
}));
|
|
4035
|
+
replaceLayer(
|
|
4036
|
+
buildGradientLayer("linear", [
|
|
4037
|
+
{
|
|
4038
|
+
id: "stop-0",
|
|
4039
|
+
color: "rgba(0, 0, 0, 0)",
|
|
4040
|
+
position: 0,
|
|
4041
|
+
opacity: 0,
|
|
4042
|
+
},
|
|
4043
|
+
{
|
|
4044
|
+
id: "stop-1",
|
|
4045
|
+
color: "rgba(0, 0, 0, 0)",
|
|
4046
|
+
position: 100,
|
|
4047
|
+
opacity: 0,
|
|
4048
|
+
},
|
|
4049
|
+
]),
|
|
4050
|
+
);
|
|
4051
|
+
} else {
|
|
4052
|
+
// Hide: stash the current layer (with its real per-stop
|
|
4053
|
+
// opacities) before zeroing every stop's alpha.
|
|
4054
|
+
setHiddenLayerStash((prev) => ({
|
|
4055
|
+
...prev,
|
|
4056
|
+
[layerStashKey]: layer,
|
|
4057
|
+
}));
|
|
4058
|
+
replaceLayer(
|
|
4059
|
+
buildGradientLayer(
|
|
4060
|
+
gradient.type,
|
|
4061
|
+
gradient.stops.map((stop) => ({
|
|
4062
|
+
...stop,
|
|
4063
|
+
opacity: 0,
|
|
4064
|
+
})),
|
|
4065
|
+
gradient.prefix,
|
|
4066
|
+
),
|
|
4067
|
+
);
|
|
4068
|
+
}
|
|
3677
4069
|
}}
|
|
3678
4070
|
>
|
|
3679
4071
|
{opacity <= 0 ? (
|
|
@@ -4274,6 +4666,7 @@ export function EditPanel({
|
|
|
4274
4666
|
onStylesChange,
|
|
4275
4667
|
onExport,
|
|
4276
4668
|
exporting = false,
|
|
4669
|
+
readOnly = false,
|
|
4277
4670
|
}: EditPanelProps) {
|
|
4278
4671
|
const t = useT();
|
|
4279
4672
|
const [exportSettings, setExportSettings] = useState<ExportSettingsValue>(
|
|
@@ -4302,6 +4695,50 @@ export function EditPanel({
|
|
|
4302
4695
|
setShowExportPreview(false);
|
|
4303
4696
|
}, [selectedElementKey]);
|
|
4304
4697
|
|
|
4698
|
+
// Scroll guard: suppress the click that fires immediately after a scroll
|
|
4699
|
+
// gesture ends (rubber-band or normal scroll). Using onScroll instead of
|
|
4700
|
+
// onPointerDown avoids side-effects like Radix DismissableLayer detecting a
|
|
4701
|
+
// "pointerdown outside" and closing open popovers — which, during an
|
|
4702
|
+
// over-scroll bounce, could briefly un-shield the canvas and allow a stray
|
|
4703
|
+
// pointer event to deselect the selected canvas element (R3 regression).
|
|
4704
|
+
const scrolledRecentlyRef = useRef(false);
|
|
4705
|
+
const scrollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
4706
|
+
|
|
4707
|
+
if (readOnly) {
|
|
4708
|
+
return (
|
|
4709
|
+
<div
|
|
4710
|
+
className={cn(
|
|
4711
|
+
"shrink-0 bg-[var(--design-editor-panel-bg)]",
|
|
4712
|
+
"flex h-full min-h-0 flex-col overflow-hidden",
|
|
4713
|
+
)}
|
|
4714
|
+
style={{ width }}
|
|
4715
|
+
>
|
|
4716
|
+
<div className="flex min-h-8 shrink-0 items-center border-b border-border/90 px-3">
|
|
4717
|
+
<h2 className="min-w-0 truncate text-[12px] font-semibold text-foreground">
|
|
4718
|
+
{t("editPanel.properties")}
|
|
4719
|
+
</h2>
|
|
4720
|
+
</div>
|
|
4721
|
+
<div className="flex min-h-0 flex-1 items-center justify-center px-5">
|
|
4722
|
+
<div
|
|
4723
|
+
className="max-w-[188px] text-center"
|
|
4724
|
+
aria-live="polite"
|
|
4725
|
+
aria-busy="true"
|
|
4726
|
+
>
|
|
4727
|
+
<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">
|
|
4728
|
+
<IconLock className="size-4" aria-hidden="true" />
|
|
4729
|
+
</div>
|
|
4730
|
+
<h3 className="text-[12px] font-semibold leading-snug text-foreground">
|
|
4731
|
+
{t("designEditor.inspectorLockedTitle")}
|
|
4732
|
+
</h3>
|
|
4733
|
+
<p className="mt-1.5 text-[11px] leading-snug text-muted-foreground/70">
|
|
4734
|
+
{t("designEditor.inspectorLockedDescription")}
|
|
4735
|
+
</p>
|
|
4736
|
+
</div>
|
|
4737
|
+
</div>
|
|
4738
|
+
</div>
|
|
4739
|
+
);
|
|
4740
|
+
}
|
|
4741
|
+
|
|
4305
4742
|
return (
|
|
4306
4743
|
<div
|
|
4307
4744
|
className={cn(
|
|
@@ -4320,11 +4757,75 @@ export function EditPanel({
|
|
|
4320
4757
|
<>
|
|
4321
4758
|
<SelectionHeader element={selectedElement} />
|
|
4322
4759
|
|
|
4323
|
-
<div
|
|
4760
|
+
<div
|
|
4761
|
+
className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain"
|
|
4762
|
+
onScroll={() => {
|
|
4763
|
+
// Mark that a scroll just happened so the click that some
|
|
4764
|
+
// browsers fire at the end of a scroll gesture (or after an
|
|
4765
|
+
// overscroll/rubber-band bounce) is suppressed. Crucially this
|
|
4766
|
+
// runs on the scroll event — NOT on pointerdown — so it never
|
|
4767
|
+
// triggers Radix's DismissableLayer "outside pointerdown"
|
|
4768
|
+
// detection, which would close open inspector popovers and, once
|
|
4769
|
+
// the shield is removed, allow a stray canvas pointer event to
|
|
4770
|
+
// deselect the selected element (the R3 overscroll regression).
|
|
4771
|
+
scrolledRecentlyRef.current = true;
|
|
4772
|
+
if (scrollTimerRef.current !== null) {
|
|
4773
|
+
clearTimeout(scrollTimerRef.current);
|
|
4774
|
+
}
|
|
4775
|
+
scrollTimerRef.current = setTimeout(() => {
|
|
4776
|
+
scrolledRecentlyRef.current = false;
|
|
4777
|
+
scrollTimerRef.current = null;
|
|
4778
|
+
}, 300);
|
|
4779
|
+
}}
|
|
4780
|
+
onClickCapture={(e) => {
|
|
4781
|
+
// Suppress spurious clicks (e.g. color-picker opening) that
|
|
4782
|
+
// fire immediately after a scroll gesture ends. The 300ms
|
|
4783
|
+
// window from the last scroll event covers both the synchronous
|
|
4784
|
+
// scroll-end click and the delayed synthetic click that mobile
|
|
4785
|
+
// browsers generate after a touch-scroll ends.
|
|
4786
|
+
if (!scrolledRecentlyRef.current) return;
|
|
4787
|
+
scrolledRecentlyRef.current = false;
|
|
4788
|
+
if (scrollTimerRef.current !== null) {
|
|
4789
|
+
clearTimeout(scrollTimerRef.current);
|
|
4790
|
+
scrollTimerRef.current = null;
|
|
4791
|
+
}
|
|
4792
|
+
e.stopPropagation();
|
|
4793
|
+
e.preventDefault();
|
|
4794
|
+
}}
|
|
4795
|
+
onKeyDown={(e) => {
|
|
4796
|
+
// Trap Tab within the inspector panel so it never focuses the
|
|
4797
|
+
// canvas iframe. When the canvas iframe gains focus it forwards
|
|
4798
|
+
// a synthetic Tab keydown to the parent window, which is picked
|
|
4799
|
+
// up by the design-editor hotkey handler as "cycle file" and
|
|
4800
|
+
// causes apparent deselection / overview-mode switch (bug: Tab
|
|
4801
|
+
// in a numeric field deselected the canvas element).
|
|
4802
|
+
if (e.key !== "Tab") return;
|
|
4803
|
+
const panel = e.currentTarget;
|
|
4804
|
+
const focusable = Array.from(
|
|
4805
|
+
panel.querySelectorAll<HTMLElement>(
|
|
4806
|
+
'input, button, select, textarea, [tabindex]:not([tabindex="-1"])',
|
|
4807
|
+
),
|
|
4808
|
+
).filter(
|
|
4809
|
+
(el) =>
|
|
4810
|
+
!el.hasAttribute("disabled") &&
|
|
4811
|
+
el.tabIndex !== -1 &&
|
|
4812
|
+
!el.closest('[aria-hidden="true"]'),
|
|
4813
|
+
);
|
|
4814
|
+
if (focusable.length === 0) return;
|
|
4815
|
+
e.preventDefault();
|
|
4816
|
+
const current = document.activeElement as HTMLElement | null;
|
|
4817
|
+
const idx = current ? focusable.indexOf(current) : -1;
|
|
4818
|
+
const next = e.shiftKey
|
|
4819
|
+
? focusable[(idx - 1 + focusable.length) % focusable.length]
|
|
4820
|
+
: focusable[(idx + 1) % focusable.length];
|
|
4821
|
+
next?.focus();
|
|
4822
|
+
}}
|
|
4823
|
+
>
|
|
4324
4824
|
{!selectedElement && (
|
|
4325
4825
|
<PageProperties
|
|
4326
4826
|
styles={pageStyles}
|
|
4327
4827
|
onStyleChange={onStyleChange}
|
|
4828
|
+
onStylesChange={onStylesChange}
|
|
4328
4829
|
/>
|
|
4329
4830
|
)}
|
|
4330
4831
|
|