@agent-native/core 0.80.8 → 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 +12 -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/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/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/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 +20 -12
- 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/export-coding-handoff.ts +12 -4
- package/corpus/templates/design/actions/generate-design.ts +6 -21
- package/corpus/templates/design/actions/generate-screens.ts +43 -2
- 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/view-screen.ts +75 -31
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +329 -69
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +1 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +479 -77
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +380 -216
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1050 -196
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +83 -206
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +1 -1
- package/corpus/templates/design/app/components/design/index.ts +0 -1
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +225 -133
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +122 -11
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +24 -5
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +57 -1
- 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 +12 -5
- 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/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 +3 -0
- package/corpus/templates/design/app/i18n-data.ts +45 -3
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2602 -657
- package/corpus/templates/design/app/root.tsx +1 -1
- 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-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-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-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-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-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-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 +3 -3
- package/corpus/templates/design/e2e/global-setup.ts +11 -22
- package/corpus/templates/design/e2e/helpers.ts +122 -11
- package/corpus/templates/design/playwright.config.ts +9 -3
- 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/generation-session.ts +41 -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/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/file-upload/actions/upload-image.d.ts +2 -2
- 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/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +8 -8
- 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/agent-engine-api-key-route.d.ts +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 -342
|
@@ -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
|
/**
|
|
@@ -238,6 +249,7 @@ function ColorInput({
|
|
|
238
249
|
onChange,
|
|
239
250
|
backgroundImage,
|
|
240
251
|
onBackgroundImageChange,
|
|
252
|
+
onImageFillChange,
|
|
241
253
|
blendMode,
|
|
242
254
|
onBlendModeChange,
|
|
243
255
|
supportsLayeredFills = false,
|
|
@@ -249,6 +261,7 @@ function ColorInput({
|
|
|
249
261
|
onChange: (value: string) => void;
|
|
250
262
|
backgroundImage?: string;
|
|
251
263
|
onBackgroundImageChange?: (value: string) => void;
|
|
264
|
+
onImageFillChange?: (value: ImageFillValue) => void;
|
|
252
265
|
blendMode?: string;
|
|
253
266
|
onBlendModeChange?: (value: string) => void;
|
|
254
267
|
supportsLayeredFills?: boolean;
|
|
@@ -558,6 +571,7 @@ function ColorInput({
|
|
|
558
571
|
onPaintValueChange={
|
|
559
572
|
supportsLayeredFills ? handlePaintValueChange : undefined
|
|
560
573
|
}
|
|
574
|
+
onImageFillChange={onImageFillChange}
|
|
561
575
|
blendMode={blendMode}
|
|
562
576
|
onBlendModeChange={onBlendModeChange}
|
|
563
577
|
showBlendMode={supportsLayeredFills}
|
|
@@ -912,8 +926,6 @@ function PropSlider({
|
|
|
912
926
|
);
|
|
913
927
|
}
|
|
914
928
|
|
|
915
|
-
const EmptyScrubIcon = () => null;
|
|
916
|
-
|
|
917
929
|
/**
|
|
918
930
|
* design-editor inspector section. Matches the design editor "Design" panel chrome:
|
|
919
931
|
* - NO left collapse chevron (the design editor uses none).
|
|
@@ -1131,6 +1143,99 @@ const FONT_WEIGHT_OPTIONS = [
|
|
|
1131
1143
|
{ value: "900", key: "black" },
|
|
1132
1144
|
] as const;
|
|
1133
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
|
+
|
|
1134
1239
|
const ALIGN_SELF_OPTIONS = [
|
|
1135
1240
|
{ value: "auto", key: "auto" },
|
|
1136
1241
|
{ value: "flex-start", key: "start" },
|
|
@@ -1282,6 +1387,8 @@ function ScrubStyleInput({
|
|
|
1282
1387
|
step = 1,
|
|
1283
1388
|
labelClassName,
|
|
1284
1389
|
inputClassName,
|
|
1390
|
+
ariaLabel,
|
|
1391
|
+
tooltipLabel,
|
|
1285
1392
|
hideIcon = true,
|
|
1286
1393
|
}: {
|
|
1287
1394
|
label: string;
|
|
@@ -1295,11 +1402,15 @@ function ScrubStyleInput({
|
|
|
1295
1402
|
labelClassName?: string;
|
|
1296
1403
|
inputClassName?: string;
|
|
1297
1404
|
hideIcon?: boolean;
|
|
1405
|
+
ariaLabel?: string;
|
|
1406
|
+
tooltipLabel?: string;
|
|
1298
1407
|
}) {
|
|
1299
1408
|
return (
|
|
1300
1409
|
<ScrubInput
|
|
1301
1410
|
label={label}
|
|
1302
|
-
|
|
1411
|
+
ariaLabel={ariaLabel}
|
|
1412
|
+
tooltipLabel={tooltipLabel}
|
|
1413
|
+
icon={hideIcon ? null : undefined}
|
|
1303
1414
|
value={value ? parseNumericValue(value) : (placeholder ?? 0)}
|
|
1304
1415
|
onChange={onChange}
|
|
1305
1416
|
unit={unit}
|
|
@@ -1650,6 +1761,33 @@ function inferElementSizing(
|
|
|
1650
1761
|
return "fixed";
|
|
1651
1762
|
}
|
|
1652
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
|
+
|
|
1653
1791
|
function commitElementSizing(
|
|
1654
1792
|
element: ElementInfo,
|
|
1655
1793
|
axis: AutoLayoutSizingAxis,
|
|
@@ -1659,12 +1797,10 @@ function commitElementSizing(
|
|
|
1659
1797
|
) {
|
|
1660
1798
|
const isHorizontal = axis === "horizontal";
|
|
1661
1799
|
const sizeProperty = isHorizontal ? "width" : "height";
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
),
|
|
1667
|
-
);
|
|
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)));
|
|
1668
1804
|
const parentDirection = parentFlexDirection(element);
|
|
1669
1805
|
const isFlex = isParentFlex(element);
|
|
1670
1806
|
const isGrid = isParentGrid(element);
|
|
@@ -1770,7 +1906,7 @@ function InspectorTabsHeader({
|
|
|
1770
1906
|
const t = useT();
|
|
1771
1907
|
|
|
1772
1908
|
return (
|
|
1773
|
-
<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">
|
|
1774
1910
|
<Tabs
|
|
1775
1911
|
value={activeTab}
|
|
1776
1912
|
onValueChange={(value) => onActiveTabChange(value as InspectorTab)}
|
|
@@ -1912,12 +2048,113 @@ function InspectorIconButton({
|
|
|
1912
2048
|
|
|
1913
2049
|
function InspectorSegment({ children }: { children: ReactNode }) {
|
|
1914
2050
|
return (
|
|
1915
|
-
<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)]">
|
|
1916
2052
|
{children}
|
|
1917
2053
|
</div>
|
|
1918
2054
|
);
|
|
1919
2055
|
}
|
|
1920
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
|
+
|
|
1921
2158
|
function CornerRadiusControl({
|
|
1922
2159
|
styles,
|
|
1923
2160
|
onStyleChange,
|
|
@@ -2389,20 +2626,29 @@ function ShadowEffectRow({
|
|
|
2389
2626
|
function PageProperties({
|
|
2390
2627
|
styles,
|
|
2391
2628
|
onStyleChange,
|
|
2629
|
+
onStylesChange,
|
|
2392
2630
|
}: {
|
|
2393
2631
|
styles: Record<string, string>;
|
|
2394
2632
|
onStyleChange: (property: string, value: string) => void;
|
|
2633
|
+
onStylesChange?: (styles: Record<string, string>) => void;
|
|
2395
2634
|
}) {
|
|
2396
2635
|
const t = useT();
|
|
2397
|
-
const
|
|
2636
|
+
const baseFontFamilyOptions = FONT_FAMILY_OPTIONS.map((option) => ({
|
|
2398
2637
|
value: option.value,
|
|
2399
2638
|
label: t(`editPanel.fontFamilies.${option.key}`),
|
|
2400
2639
|
}));
|
|
2401
|
-
const fontFamily =
|
|
2402
|
-
|
|
2640
|
+
const fontFamily = resolveFontFamilySelectValue(styles.fontFamily);
|
|
2641
|
+
const fontFamilyOptions = FONT_FAMILY_OPTIONS.some(
|
|
2642
|
+
(option) => option.value === fontFamily,
|
|
2403
2643
|
)
|
|
2404
|
-
?
|
|
2405
|
-
:
|
|
2644
|
+
? baseFontFamilyOptions
|
|
2645
|
+
: [
|
|
2646
|
+
{
|
|
2647
|
+
value: fontFamily,
|
|
2648
|
+
label: displayFontFamilyName(styles.fontFamily || fontFamily),
|
|
2649
|
+
},
|
|
2650
|
+
...baseFontFamilyOptions,
|
|
2651
|
+
];
|
|
2406
2652
|
|
|
2407
2653
|
return (
|
|
2408
2654
|
<div>
|
|
@@ -2413,6 +2659,13 @@ function PageProperties({
|
|
|
2413
2659
|
onChange={(v) => onStyleChange("backgroundColor", v)}
|
|
2414
2660
|
backgroundImage={styles.backgroundImage || ""}
|
|
2415
2661
|
onBackgroundImageChange={(v) => onStyleChange("backgroundImage", v)}
|
|
2662
|
+
onImageFillChange={(value) =>
|
|
2663
|
+
commitStylePatch(
|
|
2664
|
+
imageFillToBackgroundStyles(value),
|
|
2665
|
+
onStyleChange,
|
|
2666
|
+
onStylesChange,
|
|
2667
|
+
)
|
|
2668
|
+
}
|
|
2416
2669
|
blendMode={styles.backgroundBlendMode || "normal"}
|
|
2417
2670
|
onBlendModeChange={(v) => onStyleChange("backgroundBlendMode", v)}
|
|
2418
2671
|
supportsLayeredFills
|
|
@@ -2445,10 +2698,22 @@ function TypographyProperties({
|
|
|
2445
2698
|
}) {
|
|
2446
2699
|
const t = useT();
|
|
2447
2700
|
const styles = element.computedStyles;
|
|
2448
|
-
const
|
|
2701
|
+
const baseFontFamilyOptions = FONT_FAMILY_OPTIONS.map((option) => ({
|
|
2449
2702
|
value: option.value,
|
|
2450
2703
|
label: t(`editPanel.fontFamilies.${option.key}`),
|
|
2451
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
|
+
];
|
|
2452
2717
|
const fontWeightOptions = FONT_WEIGHT_OPTIONS.map((option) => ({
|
|
2453
2718
|
value: option.value,
|
|
2454
2719
|
label: t(`editPanel.fontWeights.${option.key}`),
|
|
@@ -2463,7 +2728,7 @@ function TypographyProperties({
|
|
|
2463
2728
|
!styles.width || styles.width === "auto" || styles.width === "max-content";
|
|
2464
2729
|
const heightIsAuto = !styles.height || styles.height === "auto";
|
|
2465
2730
|
const noWrap = styles.whiteSpace === "nowrap";
|
|
2466
|
-
const resizeMode:
|
|
2731
|
+
const resizeMode: TextResizeMode =
|
|
2467
2732
|
widthIsAuto && noWrap
|
|
2468
2733
|
? "auto-width"
|
|
2469
2734
|
: !heightIsAuto && !widthIsAuto
|
|
@@ -2471,7 +2736,7 @@ function TypographyProperties({
|
|
|
2471
2736
|
: "auto-height";
|
|
2472
2737
|
const currentWidth = styles.width && !widthIsAuto ? styles.width : "200px";
|
|
2473
2738
|
const currentHeight = styles.height && !heightIsAuto ? styles.height : "48px";
|
|
2474
|
-
const setResizeMode = (mode:
|
|
2739
|
+
const setResizeMode = (mode: TextResizeMode) => {
|
|
2475
2740
|
if (mode === "auto-width") {
|
|
2476
2741
|
onStyleChange("width", "auto");
|
|
2477
2742
|
onStyleChange("height", "auto");
|
|
@@ -2521,51 +2786,36 @@ function TypographyProperties({
|
|
|
2521
2786
|
|
|
2522
2787
|
return (
|
|
2523
2788
|
<PanelSection title={t("editPanel.sections.typography")}>
|
|
2524
|
-
{/* Row
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
label={"Auto height" /* i18n-ignore design text-resize mode */}
|
|
2535
|
-
active={resizeMode === "auto-height"}
|
|
2536
|
-
onClick={() => setResizeMode("auto-height")}
|
|
2537
|
-
>
|
|
2538
|
-
<IconArrowAutofitHeight className="size-3.5" />
|
|
2539
|
-
</InspectorIconButton>
|
|
2540
|
-
<InspectorIconButton
|
|
2541
|
-
label={"Fixed size" /* i18n-ignore design text-resize mode */}
|
|
2542
|
-
active={resizeMode === "fixed"}
|
|
2543
|
-
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)}
|
|
2544
2799
|
>
|
|
2545
|
-
<
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
{opt.label}
|
|
2565
|
-
</SelectItem>
|
|
2566
|
-
))}
|
|
2567
|
-
</SelectContent>
|
|
2568
|
-
</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>
|
|
2569
2819
|
|
|
2570
2820
|
{/* Row 2: weight + size side by side */}
|
|
2571
2821
|
<div className="grid grid-cols-2 gap-1.5">
|
|
@@ -2639,7 +2889,7 @@ function TypographyProperties({
|
|
|
2639
2889
|
</div>
|
|
2640
2890
|
|
|
2641
2891
|
{/* Row 4: horizontal + vertical text alignment */}
|
|
2642
|
-
<div className="
|
|
2892
|
+
<div className="flex items-center gap-1.5">
|
|
2643
2893
|
<InspectorSegment>
|
|
2644
2894
|
<InspectorIconButton
|
|
2645
2895
|
label={t("editPanel.textAligns.left")}
|
|
@@ -2693,6 +2943,12 @@ function TypographyProperties({
|
|
|
2693
2943
|
<IconLayoutAlignBottom className="size-3.5" />
|
|
2694
2944
|
</InspectorIconButton>
|
|
2695
2945
|
</InspectorSegment>
|
|
2946
|
+
<div className="ml-auto shrink-0">
|
|
2947
|
+
<TypographyDetailsPopover
|
|
2948
|
+
resizeMode={resizeMode}
|
|
2949
|
+
onResizeModeChange={setResizeMode}
|
|
2950
|
+
/>
|
|
2951
|
+
</div>
|
|
2696
2952
|
</div>
|
|
2697
2953
|
</PanelSection>
|
|
2698
2954
|
);
|
|
@@ -2758,8 +3014,8 @@ function FlexContainerControls({
|
|
|
2758
3014
|
},
|
|
2759
3015
|
clipContent: styles.overflow === "hidden",
|
|
2760
3016
|
resolvedSize: {
|
|
2761
|
-
horizontal: element
|
|
2762
|
-
vertical: element
|
|
3017
|
+
horizontal: cssElementSize(element, "horizontal"),
|
|
3018
|
+
vertical: cssElementSize(element, "vertical"),
|
|
2763
3019
|
},
|
|
2764
3020
|
// Forward the raw CSS display so the matrix can render the correct Flow
|
|
2765
3021
|
// state (normal flow for block/grid, flex for flex/inline-flex). Added as an
|
|
@@ -2843,6 +3099,9 @@ function FlexContainerControls({
|
|
|
2843
3099
|
onStylesChange,
|
|
2844
3100
|
);
|
|
2845
3101
|
}}
|
|
3102
|
+
onChildSizeChange={(axis, px) =>
|
|
3103
|
+
onStyleChange(axis === "horizontal" ? "width" : "height", `${px}px`)
|
|
3104
|
+
}
|
|
2846
3105
|
onChildMinMaxChange={(axis, kind, val) =>
|
|
2847
3106
|
commitElementMinMax(axis, kind, val, onStyleChange)
|
|
2848
3107
|
}
|
|
@@ -2986,7 +3245,7 @@ function LayoutContextProperties({
|
|
|
2986
3245
|
axis="W"
|
|
2987
3246
|
sizingAxis="horizontal"
|
|
2988
3247
|
value={inferElementSizing(element, "horizontal")}
|
|
2989
|
-
resolvedSize={element
|
|
3248
|
+
resolvedSize={cssElementSize(element, "horizontal")}
|
|
2990
3249
|
minMax={readElementMinMax(element, "horizontal")}
|
|
2991
3250
|
options={availableSizing.horizontal ?? ["fixed"]}
|
|
2992
3251
|
disabled={false}
|
|
@@ -2999,6 +3258,7 @@ function LayoutContextProperties({
|
|
|
2999
3258
|
onStylesChange,
|
|
3000
3259
|
)
|
|
3001
3260
|
}
|
|
3261
|
+
onSizeChange={(px) => onStyleChange("width", `${px}px`)}
|
|
3002
3262
|
onMinMaxChange={(axis, kind, val) =>
|
|
3003
3263
|
commitElementMinMax(axis, kind, val, onStyleChange)
|
|
3004
3264
|
}
|
|
@@ -3007,7 +3267,7 @@ function LayoutContextProperties({
|
|
|
3007
3267
|
axis="H"
|
|
3008
3268
|
sizingAxis="vertical"
|
|
3009
3269
|
value={inferElementSizing(element, "vertical")}
|
|
3010
|
-
resolvedSize={element
|
|
3270
|
+
resolvedSize={cssElementSize(element, "vertical")}
|
|
3011
3271
|
minMax={readElementMinMax(element, "vertical")}
|
|
3012
3272
|
options={availableSizing.vertical ?? ["fixed"]}
|
|
3013
3273
|
disabled={false}
|
|
@@ -3020,6 +3280,7 @@ function LayoutContextProperties({
|
|
|
3020
3280
|
onStylesChange,
|
|
3021
3281
|
)
|
|
3022
3282
|
}
|
|
3283
|
+
onSizeChange={(px) => onStyleChange("height", `${px}px`)}
|
|
3023
3284
|
onMinMaxChange={(axis, kind, val) =>
|
|
3024
3285
|
commitElementMinMax(axis, kind, val, onStyleChange)
|
|
3025
3286
|
}
|
|
@@ -3372,6 +3633,8 @@ function PositionLayoutProperties({
|
|
|
3372
3633
|
<div className="grid grid-cols-2 gap-2">
|
|
3373
3634
|
<ScrubStyleInput
|
|
3374
3635
|
label="X"
|
|
3636
|
+
ariaLabel="X-position"
|
|
3637
|
+
tooltipLabel="X-position"
|
|
3375
3638
|
value={styles.left || ""}
|
|
3376
3639
|
placeholder={element.boundingRect.x}
|
|
3377
3640
|
inputClassName="h-6"
|
|
@@ -3379,6 +3642,8 @@ function PositionLayoutProperties({
|
|
|
3379
3642
|
/>
|
|
3380
3643
|
<ScrubStyleInput
|
|
3381
3644
|
label="Y"
|
|
3645
|
+
ariaLabel="Y-position"
|
|
3646
|
+
tooltipLabel="Y-position"
|
|
3382
3647
|
value={styles.top || ""}
|
|
3383
3648
|
placeholder={element.boundingRect.y}
|
|
3384
3649
|
inputClassName="h-6"
|
|
@@ -3474,7 +3739,7 @@ function FillProperties({
|
|
|
3474
3739
|
Record<string, string>
|
|
3475
3740
|
>({});
|
|
3476
3741
|
const stashKey = `${elementIdentityKey(element)}:${fillProperty}`;
|
|
3477
|
-
const isHidden = fillValue
|
|
3742
|
+
const isHidden = !colorHasVisibleAlpha(fillValue);
|
|
3478
3743
|
const handleFillVisibilityToggle = () => {
|
|
3479
3744
|
if (isHidden) {
|
|
3480
3745
|
// Restore the stashed color, or fall back to a sensible default.
|
|
@@ -3629,8 +3894,17 @@ function FillProperties({
|
|
|
3629
3894
|
const opacity = gradient
|
|
3630
3895
|
? averageGradientOpacity(gradient.stops)
|
|
3631
3896
|
: 100;
|
|
3897
|
+
const layerStashKey = `${elementIdentityKey(element)}:layer:${index}`;
|
|
3898
|
+
const stashedLayer = hiddenLayerStash[layerStashKey];
|
|
3899
|
+
const hiddenImagePlaceholder = Boolean(
|
|
3900
|
+
stashedLayer && gradient && opacity <= 0,
|
|
3901
|
+
);
|
|
3632
3902
|
const label = gradient
|
|
3633
|
-
?
|
|
3903
|
+
? hiddenImagePlaceholder
|
|
3904
|
+
? `${"Image" /* i18n-ignore design inspector paint row */} ${
|
|
3905
|
+
index + 1
|
|
3906
|
+
}`
|
|
3907
|
+
: `${gradientLabel(gradient.type)} ${index + 1}`
|
|
3634
3908
|
: `${"Image" /* i18n-ignore design inspector paint row */} ${
|
|
3635
3909
|
index + 1
|
|
3636
3910
|
}`;
|
|
@@ -3728,10 +4002,6 @@ function FillProperties({
|
|
|
3728
4002
|
: t("editPanel.labels.hideLayer")
|
|
3729
4003
|
}
|
|
3730
4004
|
onClick={() => {
|
|
3731
|
-
if (!gradient) return;
|
|
3732
|
-
const layerStashKey = `${elementIdentityKey(
|
|
3733
|
-
element,
|
|
3734
|
-
)}:layer:${index}`;
|
|
3735
4005
|
if (opacity <= 0) {
|
|
3736
4006
|
// Show: restore the exact pre-hide layer if stashed,
|
|
3737
4007
|
// otherwise fall back to forcing every stop opaque.
|
|
@@ -3743,7 +4013,7 @@ function FillProperties({
|
|
|
3743
4013
|
delete next[layerStashKey];
|
|
3744
4014
|
return next;
|
|
3745
4015
|
});
|
|
3746
|
-
} else {
|
|
4016
|
+
} else if (gradient) {
|
|
3747
4017
|
replaceLayer(
|
|
3748
4018
|
buildGradientLayer(
|
|
3749
4019
|
gradient.type,
|
|
@@ -3755,6 +4025,29 @@ function FillProperties({
|
|
|
3755
4025
|
),
|
|
3756
4026
|
);
|
|
3757
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
|
+
);
|
|
3758
4051
|
} else {
|
|
3759
4052
|
// Hide: stash the current layer (with its real per-stop
|
|
3760
4053
|
// opacities) before zeroing every stop's alpha.
|
|
@@ -4373,6 +4666,7 @@ export function EditPanel({
|
|
|
4373
4666
|
onStylesChange,
|
|
4374
4667
|
onExport,
|
|
4375
4668
|
exporting = false,
|
|
4669
|
+
readOnly = false,
|
|
4376
4670
|
}: EditPanelProps) {
|
|
4377
4671
|
const t = useT();
|
|
4378
4672
|
const [exportSettings, setExportSettings] = useState<ExportSettingsValue>(
|
|
@@ -4401,6 +4695,50 @@ export function EditPanel({
|
|
|
4401
4695
|
setShowExportPreview(false);
|
|
4402
4696
|
}, [selectedElementKey]);
|
|
4403
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
|
+
|
|
4404
4742
|
return (
|
|
4405
4743
|
<div
|
|
4406
4744
|
className={cn(
|
|
@@ -4419,11 +4757,75 @@ export function EditPanel({
|
|
|
4419
4757
|
<>
|
|
4420
4758
|
<SelectionHeader element={selectedElement} />
|
|
4421
4759
|
|
|
4422
|
-
<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
|
+
>
|
|
4423
4824
|
{!selectedElement && (
|
|
4424
4825
|
<PageProperties
|
|
4425
4826
|
styles={pageStyles}
|
|
4426
4827
|
onStyleChange={onStyleChange}
|
|
4828
|
+
onStylesChange={onStylesChange}
|
|
4427
4829
|
/>
|
|
4428
4830
|
)}
|
|
4429
4831
|
|