@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
|
@@ -14,6 +14,7 @@ import type { ShaderDescriptor } from "@shared/shader-presets";
|
|
|
14
14
|
import { IconChevronDown, IconColorPicker } from "@tabler/icons-react";
|
|
15
15
|
import {
|
|
16
16
|
useEffect,
|
|
17
|
+
useMemo,
|
|
17
18
|
useRef,
|
|
18
19
|
useState,
|
|
19
20
|
type JSX,
|
|
@@ -130,6 +131,7 @@ export interface DesignColorPickerProps {
|
|
|
130
131
|
value: string;
|
|
131
132
|
onChange: (value: string) => void;
|
|
132
133
|
onPaintValueChange?: (value: string) => void;
|
|
134
|
+
onImageFillChange?: (value: ImageFillValue) => void;
|
|
133
135
|
label?: string;
|
|
134
136
|
opacity?: number;
|
|
135
137
|
onOpacityChange?: (opacity: number) => void;
|
|
@@ -191,6 +193,87 @@ interface HsvaColor {
|
|
|
191
193
|
|
|
192
194
|
const FALLBACK_COLOR: RgbaColor = { r: 0, g: 0, b: 0, a: 1 };
|
|
193
195
|
|
|
196
|
+
// ─── Extended CSS color parser ──────────────────────────────────────────────────
|
|
197
|
+
//
|
|
198
|
+
// `parseCssColor` from color-utils handles hex, comma-separated rgb/rgba, and
|
|
199
|
+
// hsl/hsla. Browsers increasingly emit modern CSS Level 4 formats from
|
|
200
|
+
// getComputedStyle: space-separated `rgb(R G B)`, `rgb(R G B / A)`, and
|
|
201
|
+
// opaque formats like `oklch(...)` or `color(display-p3 ...)`.
|
|
202
|
+
//
|
|
203
|
+
// This local wrapper extends the parser to cover those cases so that colors
|
|
204
|
+
// arriving from the canvas's computed-style bridge are always usable.
|
|
205
|
+
|
|
206
|
+
const MODERN_RGB_PATTERN =
|
|
207
|
+
/^rgba?\(\s*([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)(?:\s*\/\s*([0-9.]+%?))?\s*\)$/i;
|
|
208
|
+
|
|
209
|
+
/** Canvas element reused across calls for DOM-based color resolution. */
|
|
210
|
+
let _resolverCanvas: HTMLCanvasElement | null = null;
|
|
211
|
+
let _resolverCtx: CanvasRenderingContext2D | null = null;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Parses a CSS color string into RgbaColor, extending the base parser with:
|
|
215
|
+
* - Modern space-separated `rgb(R G B)` / `rgb(R G B / A)` syntax
|
|
216
|
+
* - Opaque formats (oklch, color, etc.) resolved via a hidden canvas
|
|
217
|
+
*
|
|
218
|
+
* Falls back to null if the value is unparseable and the DOM is unavailable.
|
|
219
|
+
*/
|
|
220
|
+
function parseCssColorExtended(value: string): RgbaColor | null {
|
|
221
|
+
// 1. Try the standard parser first (handles hex, comma rgb/rgba, hsl/hsla).
|
|
222
|
+
const standard = parseCssColor(value);
|
|
223
|
+
if (standard) return standard;
|
|
224
|
+
|
|
225
|
+
const trimmed = value.trim();
|
|
226
|
+
if (!trimmed || trimmed === "transparent" || trimmed === "none") return null;
|
|
227
|
+
|
|
228
|
+
// 2. Modern space-separated rgb/rgba — CSS Level 4.
|
|
229
|
+
const modernRgb = trimmed.match(MODERN_RGB_PATTERN);
|
|
230
|
+
if (modernRgb) {
|
|
231
|
+
const parseAlphaLocal = (v: string | undefined): number => {
|
|
232
|
+
if (!v) return 1;
|
|
233
|
+
if (v.endsWith("%"))
|
|
234
|
+
return Math.max(0, Math.min(1, Number(v.slice(0, -1)) / 100));
|
|
235
|
+
return Math.max(0, Math.min(1, Number(v)));
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
r: Math.round(Math.max(0, Math.min(255, Number(modernRgb[1])))),
|
|
239
|
+
g: Math.round(Math.max(0, Math.min(255, Number(modernRgb[2])))),
|
|
240
|
+
b: Math.round(Math.max(0, Math.min(255, Number(modernRgb[3])))),
|
|
241
|
+
a: parseAlphaLocal(modernRgb[4]),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 3. DOM-based resolver for oklch, color(display-p3 ...), hsl (modern), etc.
|
|
246
|
+
// Uses a hidden 1×1 canvas to resolve any valid CSS color to rgb().
|
|
247
|
+
if (typeof document === "undefined") return null;
|
|
248
|
+
try {
|
|
249
|
+
if (!_resolverCanvas) {
|
|
250
|
+
_resolverCanvas = document.createElement("canvas");
|
|
251
|
+
_resolverCanvas.width = 1;
|
|
252
|
+
_resolverCanvas.height = 1;
|
|
253
|
+
}
|
|
254
|
+
if (!_resolverCtx) {
|
|
255
|
+
_resolverCtx = _resolverCanvas.getContext("2d", {
|
|
256
|
+
willReadFrequently: true,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const ctx = _resolverCtx;
|
|
260
|
+
if (!ctx) return null;
|
|
261
|
+
// Detect invalid color values: save fillStyle before and after assignment.
|
|
262
|
+
// If the browser rejects the value, fillStyle won't change.
|
|
263
|
+
const prev = ctx.fillStyle;
|
|
264
|
+
ctx.fillStyle = trimmed;
|
|
265
|
+
const next = ctx.fillStyle; // browser normalises to rgb/hex on accept
|
|
266
|
+
// If the value was rejected, fillStyle stays at the previous value.
|
|
267
|
+
if (next === prev) return null;
|
|
268
|
+
ctx.clearRect(0, 0, 1, 1);
|
|
269
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
270
|
+
const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
|
|
271
|
+
return { r, g, b, a: a / 255 };
|
|
272
|
+
} catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
194
277
|
const DEFAULT_LABELS: DesignColorPickerLabels = {
|
|
195
278
|
trigger: "Open color picker", // i18n-ignore fallback component label
|
|
196
279
|
hex: "Hex", // i18n-ignore fallback component label
|
|
@@ -575,6 +658,7 @@ export function DesignColorPicker({
|
|
|
575
658
|
value,
|
|
576
659
|
onChange,
|
|
577
660
|
onPaintValueChange,
|
|
661
|
+
onImageFillChange,
|
|
578
662
|
label: _label,
|
|
579
663
|
opacity,
|
|
580
664
|
onOpacityChange,
|
|
@@ -605,7 +689,7 @@ export function DesignColorPicker({
|
|
|
605
689
|
className,
|
|
606
690
|
}: DesignColorPickerProps) {
|
|
607
691
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
608
|
-
const color =
|
|
692
|
+
const color = parseCssColorExtended(value) ?? FALLBACK_COLOR;
|
|
609
693
|
const hsv = rgbaToHsv(color);
|
|
610
694
|
const hsl = rgbaToHsl(color);
|
|
611
695
|
const effectiveOpacity = opacity ?? alphaToOpacity(color.a);
|
|
@@ -651,16 +735,24 @@ export function DesignColorPicker({
|
|
|
651
735
|
|
|
652
736
|
// Resolve the active gradient: prefer EditPanel-driven props; otherwise parse
|
|
653
737
|
// the live CSS value, falling back to local edit state.
|
|
654
|
-
const parsedGradient =
|
|
738
|
+
const parsedGradient = useMemo(
|
|
739
|
+
() => parseGradientCss(value, gradientType ?? "linear"),
|
|
740
|
+
[gradientType, value],
|
|
741
|
+
);
|
|
742
|
+
const fallbackGradient = useMemo(
|
|
743
|
+
() =>
|
|
744
|
+
GRADIENT_TYPES.has(effectivePaintType)
|
|
745
|
+
? defaultGradient(
|
|
746
|
+
effectivePaintType as GradientKind,
|
|
747
|
+
toCssColor(color) || "#000000",
|
|
748
|
+
)
|
|
749
|
+
: null,
|
|
750
|
+
[color.r, color.g, color.b, color.a, effectivePaintType],
|
|
751
|
+
);
|
|
655
752
|
const activeGradient: GradientValue | null = GRADIENT_TYPES.has(
|
|
656
753
|
effectivePaintType,
|
|
657
754
|
)
|
|
658
|
-
? (localGradient ??
|
|
659
|
-
parsedGradient ??
|
|
660
|
-
defaultGradient(
|
|
661
|
-
effectivePaintType as GradientKind,
|
|
662
|
-
toCssColor(color) || "#000000",
|
|
663
|
-
))
|
|
755
|
+
? (localGradient ?? parsedGradient ?? fallbackGradient)
|
|
664
756
|
: null;
|
|
665
757
|
|
|
666
758
|
useEffect(() => {
|
|
@@ -754,7 +846,8 @@ export function DesignColorPicker({
|
|
|
754
846
|
|
|
755
847
|
// The 2D field edits the selected gradient stop's color when in gradient mode.
|
|
756
848
|
const fieldColor: RgbaColor = activeGradient
|
|
757
|
-
? (
|
|
849
|
+
? (parseCssColorExtended(selectedStop?.color ?? "#000000") ??
|
|
850
|
+
FALLBACK_COLOR)
|
|
758
851
|
: color;
|
|
759
852
|
const rawFieldHsv = rgbaToHsv(fieldColor);
|
|
760
853
|
// Preserve the last non-zero hue so dragging through gray doesn't lose it.
|
|
@@ -771,7 +864,7 @@ export function DesignColorPicker({
|
|
|
771
864
|
const selectedStopColor = selectedStop?.color;
|
|
772
865
|
useEffect(() => {
|
|
773
866
|
if (!activeGradient || !selectedStopColor) return;
|
|
774
|
-
const parsed =
|
|
867
|
+
const parsed = parseCssColorExtended(selectedStopColor);
|
|
775
868
|
if (parsed) setHexDraft(toDisplayHex(parsed));
|
|
776
869
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
777
870
|
}, [selectedStopColor, selectedStopId]);
|
|
@@ -807,6 +900,10 @@ export function DesignColorPicker({
|
|
|
807
900
|
|
|
808
901
|
const emitImageFill = (next: ImageFillValue) => {
|
|
809
902
|
setImageFill(next);
|
|
903
|
+
if (onImageFillChange) {
|
|
904
|
+
onImageFillChange(next);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
810
907
|
emitPaintValue(imageFillToCss(next));
|
|
811
908
|
};
|
|
812
909
|
|
|
@@ -860,6 +957,10 @@ export function DesignColorPicker({
|
|
|
860
957
|
return;
|
|
861
958
|
}
|
|
862
959
|
if (nextType === "image") {
|
|
960
|
+
if (onImageFillChange && imageFill.url) {
|
|
961
|
+
onImageFillChange(imageFill);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
863
964
|
emitPaintValue(imageFill.url ? imageFillToCss(imageFill) : "transparent");
|
|
864
965
|
return;
|
|
865
966
|
}
|
|
@@ -889,7 +990,17 @@ export function DesignColorPicker({
|
|
|
889
990
|
setPicking(true);
|
|
890
991
|
try {
|
|
891
992
|
const result = await new EyeDropperCtor().open();
|
|
892
|
-
if (result.sRGBHex)
|
|
993
|
+
if (result.sRGBHex) {
|
|
994
|
+
if (activeGradient) {
|
|
995
|
+
// In gradient mode, route to the selected stop (preserving its alpha)
|
|
996
|
+
// like every other color edit — don't replace the whole gradient with
|
|
997
|
+
// a solid hex.
|
|
998
|
+
const parsed = parseCssColor(result.sRGBHex);
|
|
999
|
+
if (parsed) emitStopColor({ ...parsed, a: fieldColor.a });
|
|
1000
|
+
} else {
|
|
1001
|
+
onChange(result.sRGBHex);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
893
1004
|
} catch {
|
|
894
1005
|
// Browser cancels are expected; keep the current color.
|
|
895
1006
|
} finally {
|
|
@@ -1050,8 +1161,18 @@ export function DesignColorPicker({
|
|
|
1050
1161
|
align="start"
|
|
1051
1162
|
sideOffset={8}
|
|
1052
1163
|
className="z-[10000] w-[252px] p-0 shadow-xl"
|
|
1164
|
+
// Keep the picker open when the style change triggered by a paint-type
|
|
1165
|
+
// switch causes the canvas to re-project the element. Without this,
|
|
1166
|
+
// Radix treats the resulting focus shift as an "interact outside" event
|
|
1167
|
+
// and closes the popover before the type switch is visible.
|
|
1168
|
+
onInteractOutside={(e) => {
|
|
1169
|
+
// Allow closing only for genuine pointer clicks on the canvas area
|
|
1170
|
+
// (the user clicked somewhere else). Programmatic focus changes from
|
|
1171
|
+
// the canvas bridge (element re-projection) should not close the picker.
|
|
1172
|
+
if (e.type === "focusoutside") e.preventDefault();
|
|
1173
|
+
}}
|
|
1053
1174
|
>
|
|
1054
|
-
<div className="
|
|
1175
|
+
<div className="rounded-md bg-popover text-popover-foreground">
|
|
1055
1176
|
{view === "shader" ? (
|
|
1056
1177
|
<ShaderFillsPanel
|
|
1057
1178
|
descriptor={shaderDescriptor ?? undefined}
|
|
@@ -1845,8 +1966,14 @@ export function inferPaintType(
|
|
|
1845
1966
|
if (
|
|
1846
1967
|
lower.startsWith("radial-gradient") ||
|
|
1847
1968
|
lower.startsWith("repeating-radial-gradient")
|
|
1848
|
-
)
|
|
1969
|
+
) {
|
|
1970
|
+
// Diamond fills serialize as a radial gradient (either "closest-corner"
|
|
1971
|
+
// from EditPanel or "ellipse closest-side" from GradientEditor). Recognize
|
|
1972
|
+
// both so a diamond doesn't silently become a plain radial on reselect.
|
|
1973
|
+
if (/closest-corner/.test(lower) || /ellipse\s+closest-side/.test(lower))
|
|
1974
|
+
return "diamond";
|
|
1849
1975
|
return "radial";
|
|
1976
|
+
}
|
|
1850
1977
|
if (
|
|
1851
1978
|
lower.startsWith("conic-gradient") ||
|
|
1852
1979
|
lower.startsWith("repeating-conic-gradient")
|
|
@@ -57,6 +57,14 @@ const DEFAULT_LABELS: ExportSettingsPanelLabels = {
|
|
|
57
57
|
|
|
58
58
|
const DEFAULT_FORMATS: ExportFormat[] = ["png", "jpg", "svg", "pdf", "webp"];
|
|
59
59
|
|
|
60
|
+
const controlChromeClass =
|
|
61
|
+
"border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-foreground shadow-none hover:bg-[var(--design-editor-panel-raised-bg)] hover:text-foreground focus:ring-1 focus:ring-[var(--design-editor-accent-color)] focus:ring-offset-0 focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)] focus-visible:ring-offset-0";
|
|
62
|
+
|
|
63
|
+
// Export scale bounds — must match the clamp in DesignEditor's PNG/SVG export
|
|
64
|
+
// handlers so the panel never promises a range the exporter won't render.
|
|
65
|
+
const MIN_EXPORT_SCALE = 0.1;
|
|
66
|
+
const MAX_EXPORT_SCALE = 4;
|
|
67
|
+
|
|
60
68
|
/** Preset scale options shown in the scale dropdown — matches the design editor's presets */
|
|
61
69
|
const SCALE_PRESETS: { label: string; value: ExportScale }[] = [
|
|
62
70
|
{ label: "0.5x", value: "0.5" },
|
|
@@ -130,7 +138,12 @@ function ScaleSelect({
|
|
|
130
138
|
}
|
|
131
139
|
}}
|
|
132
140
|
>
|
|
133
|
-
<SelectTrigger
|
|
141
|
+
<SelectTrigger
|
|
142
|
+
className={cn(
|
|
143
|
+
"h-6 w-14 shrink-0 px-1.5 text-[11px]",
|
|
144
|
+
controlChromeClass,
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
134
147
|
<SelectValue />
|
|
135
148
|
</SelectTrigger>
|
|
136
149
|
<SelectContent>
|
|
@@ -262,7 +275,7 @@ export function ExportSettingsPanel({
|
|
|
262
275
|
variant="outline"
|
|
263
276
|
disabled={isDisabled}
|
|
264
277
|
onClick={handleExport}
|
|
265
|
-
className="h-6 w-full px-2 text-[11px]"
|
|
278
|
+
className={cn("h-6 w-full px-2 text-[11px]", controlChromeClass)}
|
|
266
279
|
>
|
|
267
280
|
<IconDownload className="size-3.5" />
|
|
268
281
|
{copy.export}
|
|
@@ -297,13 +310,20 @@ function ExportRow({
|
|
|
297
310
|
type="number"
|
|
298
311
|
value={row.scale}
|
|
299
312
|
disabled={isDisabled}
|
|
300
|
-
|
|
301
|
-
|
|
313
|
+
// Bounds match the exporter's real clamp ([0.1, 4]); see
|
|
314
|
+
// DesignEditor handleDownload* . Previously the field accepted up to
|
|
315
|
+
// 100x but the exporter silently clamped to 4x with no feedback.
|
|
316
|
+
min={MIN_EXPORT_SCALE}
|
|
317
|
+
max={MAX_EXPORT_SCALE}
|
|
302
318
|
step={0.5}
|
|
303
319
|
onChange={(e) => {
|
|
304
320
|
const parsed = parseFloat(e.target.value);
|
|
305
321
|
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
306
|
-
|
|
322
|
+
const clamped = Math.min(
|
|
323
|
+
MAX_EXPORT_SCALE,
|
|
324
|
+
Math.max(MIN_EXPORT_SCALE, parsed),
|
|
325
|
+
);
|
|
326
|
+
onPatchRow(row.id, { scale: clamped });
|
|
307
327
|
}
|
|
308
328
|
}}
|
|
309
329
|
onBlur={() => {
|
|
@@ -312,7 +332,10 @@ function ExportRow({
|
|
|
312
332
|
onPatchRow(row.id, { customScale: false });
|
|
313
333
|
}
|
|
314
334
|
}}
|
|
315
|
-
className=
|
|
335
|
+
className={cn(
|
|
336
|
+
"h-6 w-14 shrink-0 px-1.5 text-[11px] tabular-nums",
|
|
337
|
+
controlChromeClass,
|
|
338
|
+
)}
|
|
316
339
|
aria-label="Scale"
|
|
317
340
|
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
318
341
|
autoFocus
|
|
@@ -340,7 +363,12 @@ function ExportRow({
|
|
|
340
363
|
onPatchRow(row.id, { format: format as ExportFormat })
|
|
341
364
|
}
|
|
342
365
|
>
|
|
343
|
-
<SelectTrigger
|
|
366
|
+
<SelectTrigger
|
|
367
|
+
className={cn(
|
|
368
|
+
"h-6 min-w-0 flex-1 px-1.5 text-[11px]",
|
|
369
|
+
controlChromeClass,
|
|
370
|
+
)}
|
|
371
|
+
>
|
|
344
372
|
<SelectValue />
|
|
345
373
|
</SelectTrigger>
|
|
346
374
|
<SelectContent>
|
|
@@ -364,7 +392,10 @@ function ExportRow({
|
|
|
364
392
|
disabled={isDisabled}
|
|
365
393
|
onChange={(e) => onPatchRow(row.id, { suffix: e.target.value })}
|
|
366
394
|
placeholder={labels.suffix}
|
|
367
|
-
className=
|
|
395
|
+
className={cn(
|
|
396
|
+
"h-6 min-w-0 flex-1 px-1.5 text-[11px]",
|
|
397
|
+
controlChromeClass,
|
|
398
|
+
)}
|
|
368
399
|
aria-label={labels.suffix}
|
|
369
400
|
/>
|
|
370
401
|
|
|
@@ -167,10 +167,12 @@ export function parseGradientCss(
|
|
|
167
167
|
}
|
|
168
168
|
} else if (fn === "radial") {
|
|
169
169
|
kind =
|
|
170
|
-
/ellipse\s+closest-side/i.test(first) ||
|
|
170
|
+
/ellipse\s+closest-side/i.test(first) ||
|
|
171
|
+
/closest-corner/i.test(first) ||
|
|
172
|
+
fallbackKind === "diamond"
|
|
171
173
|
? "diamond"
|
|
172
174
|
: "radial";
|
|
173
|
-
if (/circle|ellipse|at\s
|
|
175
|
+
if (/circle|ellipse|at\s|closest-/i.test(first)) stopStart = 1;
|
|
174
176
|
} else if (fn === "conic") {
|
|
175
177
|
kind = "angular";
|
|
176
178
|
const angleMatch = first.match(ANGLE_RE);
|
|
@@ -34,6 +34,16 @@ const FIT_MODES: Array<{ mode: ImageFitMode; label: string }> = [
|
|
|
34
34
|
|
|
35
35
|
// ─── CSS serialization ─────────────────────────────────────────────────────────
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Escape a URL for embedding inside a double-quoted CSS url("...") token.
|
|
39
|
+
* Only `"` and `\` are CSS-significant inside a double-quoted string; newlines
|
|
40
|
+
* must also be stripped. Everything else (parens, commas, single-quotes, etc.)
|
|
41
|
+
* is safe inside double quotes, so we leave it alone to keep the URL intact.
|
|
42
|
+
*/
|
|
43
|
+
function escapeForQuotedUrl(url: string): string {
|
|
44
|
+
return url.replace(/\\/g, "\\\\").replace(/"/g, "%22").replace(/\r?\n/g, "");
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
const CHECKER_A = "#d4d4d4";
|
|
38
48
|
const CHECKERBOARD_IMAGE = `linear-gradient(45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(-45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(45deg, transparent 75%, ${CHECKER_A} 75%), linear-gradient(-45deg, transparent 75%, ${CHECKER_A} 75%)`;
|
|
39
49
|
const FIT_MARKER_RE =
|
|
@@ -55,7 +65,7 @@ function imageFitMarker(fit: ImageFitMode): string {
|
|
|
55
65
|
export function imageFillToCss(value: ImageFillValue): string {
|
|
56
66
|
const url = value.url.trim();
|
|
57
67
|
if (!url) return "transparent";
|
|
58
|
-
const safeUrl = url
|
|
68
|
+
const safeUrl = escapeForQuotedUrl(url);
|
|
59
69
|
const image = `url("${safeUrl}")`;
|
|
60
70
|
switch (value.fit) {
|
|
61
71
|
case "fit":
|
|
@@ -70,13 +80,70 @@ export function imageFillToCss(value: ImageFillValue): string {
|
|
|
70
80
|
}
|
|
71
81
|
}
|
|
72
82
|
|
|
73
|
-
|
|
83
|
+
export function imageFillToBackgroundStyles(
|
|
84
|
+
value: ImageFillValue,
|
|
85
|
+
): Record<
|
|
86
|
+
| "backgroundImage"
|
|
87
|
+
| "backgroundSize"
|
|
88
|
+
| "backgroundRepeat"
|
|
89
|
+
| "backgroundPosition",
|
|
90
|
+
string
|
|
91
|
+
> {
|
|
92
|
+
const url = value.url.trim();
|
|
93
|
+
if (!url) {
|
|
94
|
+
return {
|
|
95
|
+
backgroundImage: "none",
|
|
96
|
+
backgroundSize: "auto",
|
|
97
|
+
backgroundRepeat: "no-repeat",
|
|
98
|
+
backgroundPosition: "center",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const safeUrl = url.replace(/["')]/g, encodeURIComponent);
|
|
102
|
+
const backgroundImage = `url("${safeUrl}")`;
|
|
103
|
+
switch (value.fit) {
|
|
104
|
+
case "fit":
|
|
105
|
+
return {
|
|
106
|
+
backgroundImage,
|
|
107
|
+
backgroundSize: "contain",
|
|
108
|
+
backgroundRepeat: "no-repeat",
|
|
109
|
+
backgroundPosition: "center",
|
|
110
|
+
};
|
|
111
|
+
case "tile":
|
|
112
|
+
return {
|
|
113
|
+
backgroundImage,
|
|
114
|
+
backgroundSize: "auto",
|
|
115
|
+
backgroundRepeat: "repeat",
|
|
116
|
+
backgroundPosition: "top left",
|
|
117
|
+
};
|
|
118
|
+
case "crop":
|
|
119
|
+
return {
|
|
120
|
+
backgroundImage,
|
|
121
|
+
backgroundSize: "cover",
|
|
122
|
+
backgroundRepeat: "no-repeat",
|
|
123
|
+
backgroundPosition: "center",
|
|
124
|
+
};
|
|
125
|
+
case "fill":
|
|
126
|
+
default:
|
|
127
|
+
return {
|
|
128
|
+
backgroundImage,
|
|
129
|
+
backgroundSize: "cover",
|
|
130
|
+
backgroundRepeat: "no-repeat",
|
|
131
|
+
backgroundPosition: "center",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Matches url() in three forms:
|
|
137
|
+
// group 1 — double-quoted: url("...anything...")
|
|
138
|
+
// group 2 — single-quoted: url('...anything...')
|
|
139
|
+
// group 3 — unquoted: url(...no-parens-or-quotes...)
|
|
140
|
+
const URL_RE = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^)'"]*?))\s*\)/i;
|
|
74
141
|
|
|
75
142
|
/** Extract the URL + fit mode from a CSS background value, if present. */
|
|
76
143
|
export function parseImageFillCss(value: string): ImageFillValue | null {
|
|
77
144
|
const match = value.match(URL_RE);
|
|
78
145
|
if (!match) return null;
|
|
79
|
-
const url = match[2];
|
|
146
|
+
const url = (match[1] ?? match[2] ?? match[3] ?? "").trim();
|
|
80
147
|
const marker = value.match(FIT_MARKER_RE)?.[1] as ImageFitMode | undefined;
|
|
81
148
|
if (marker) return { url, fit: marker };
|
|
82
149
|
// Heuristic fallback when no marker comment is present (e.g. CSS pasted from
|
|
@@ -141,7 +208,7 @@ export function ImageFillControls({
|
|
|
141
208
|
className="relative h-24 w-full overflow-hidden rounded-md border border-border/60"
|
|
142
209
|
style={{
|
|
143
210
|
backgroundImage: value.url
|
|
144
|
-
? `url("${value.url.trim()
|
|
211
|
+
? `url("${escapeForQuotedUrl(value.url.trim())}")`
|
|
145
212
|
: CHECKERBOARD_IMAGE,
|
|
146
213
|
backgroundSize: value.url
|
|
147
214
|
? value.fit === "fit"
|
|
@@ -235,7 +302,10 @@ export function ImageFillControls({
|
|
|
235
302
|
onValueChange={(v) => onChange({ ...value, fit: v as ImageFitMode })}
|
|
236
303
|
disabled={disabled}
|
|
237
304
|
>
|
|
238
|
-
<SelectTrigger
|
|
305
|
+
<SelectTrigger
|
|
306
|
+
aria-label={"Fill" /* i18n-ignore image fit selector */}
|
|
307
|
+
className="h-6 w-full rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 text-[11px] shadow-none focus:ring-0 focus:ring-offset-0 focus-visible:ring-2 focus-visible:ring-ring [&>svg]:size-3 [&>svg]:shrink-0"
|
|
308
|
+
>
|
|
239
309
|
<SelectValue />
|
|
240
310
|
</SelectTrigger>
|
|
241
311
|
<SelectContent className="text-[11px]">
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
useId,
|
|
5
5
|
useRef,
|
|
6
6
|
useState,
|
|
7
|
-
type ComponentType,
|
|
8
7
|
type KeyboardEvent,
|
|
9
8
|
type PointerEvent,
|
|
9
|
+
type ReactNode,
|
|
10
10
|
} from "react";
|
|
11
11
|
|
|
12
12
|
import { Input } from "@/components/ui/input";
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
type ScrubExpressionOptions,
|
|
27
27
|
} from "./scrub-input-utils";
|
|
28
28
|
|
|
29
|
-
type ScrubInputIcon =
|
|
29
|
+
type ScrubInputIcon = (props: { className?: string }) => ReactNode;
|
|
30
30
|
|
|
31
31
|
export interface ScrubInputChangeMeta {
|
|
32
32
|
source: "commit" | "keyboard" | "scrub";
|
|
@@ -39,13 +39,14 @@ export interface ScrubInputProps extends ScrubExpressionOptions {
|
|
|
39
39
|
onChange: (value: number, meta: ScrubInputChangeMeta) => void;
|
|
40
40
|
id?: string;
|
|
41
41
|
step?: number;
|
|
42
|
-
icon?: ScrubInputIcon;
|
|
42
|
+
icon?: ScrubInputIcon | null;
|
|
43
43
|
disabled?: boolean;
|
|
44
44
|
placeholder?: string;
|
|
45
45
|
className?: string;
|
|
46
46
|
inputClassName?: string;
|
|
47
47
|
labelClassName?: string;
|
|
48
48
|
ariaLabel?: string;
|
|
49
|
+
tooltipLabel?: string;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export function ScrubInput({
|
|
@@ -65,12 +66,17 @@ export function ScrubInput({
|
|
|
65
66
|
inputClassName,
|
|
66
67
|
labelClassName,
|
|
67
68
|
ariaLabel,
|
|
69
|
+
tooltipLabel,
|
|
68
70
|
}: ScrubInputProps) {
|
|
69
71
|
const generatedId = useId();
|
|
70
72
|
const inputId = id ?? generatedId;
|
|
71
73
|
const [draft, setDraft] = useState(() =>
|
|
72
74
|
formatScrubValue(value, { unit, precision }),
|
|
73
75
|
);
|
|
76
|
+
// Track the latest draft in a ref so commitDraft always reads the most
|
|
77
|
+
// up-to-date value even if the blur event fires before the React state
|
|
78
|
+
// update has been committed to the render tree (concurrent mode / batching).
|
|
79
|
+
const draftRef = useRef(draft);
|
|
74
80
|
const [focused, setFocused] = useState(false);
|
|
75
81
|
const [dragging, setDragging] = useState(false);
|
|
76
82
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
@@ -83,27 +89,41 @@ export function ScrubInput({
|
|
|
83
89
|
});
|
|
84
90
|
|
|
85
91
|
useEffect(() => {
|
|
86
|
-
if (!focused)
|
|
92
|
+
if (!focused) {
|
|
93
|
+
const formatted = formatScrubValue(value, { unit, precision });
|
|
94
|
+
draftRef.current = formatted;
|
|
95
|
+
setDraft(formatted);
|
|
96
|
+
}
|
|
87
97
|
}, [focused, precision, unit, value]);
|
|
88
98
|
|
|
89
99
|
const options = { unit, min, max, precision };
|
|
100
|
+
const resolvedTooltipLabel = tooltipLabel ?? ariaLabel ?? label;
|
|
90
101
|
|
|
91
102
|
const setNextValue = (nextValue: number, meta: ScrubInputChangeMeta) => {
|
|
92
103
|
const normalized = normalizeScrubNumber(nextValue, options);
|
|
93
104
|
onChange(normalized, meta);
|
|
94
|
-
|
|
105
|
+
const formatted = formatScrubValue(normalized, options);
|
|
106
|
+
draftRef.current = formatted;
|
|
107
|
+
setDraft(formatted);
|
|
95
108
|
};
|
|
96
109
|
|
|
97
110
|
const commitDraft = () => {
|
|
98
|
-
|
|
111
|
+
// Always read from the ref so we use the latest typed value even if the
|
|
112
|
+
// React render with the updated draft state hasn't committed yet (e.g.
|
|
113
|
+
// when blur fires in the same synchronous batch as the last onChange).
|
|
114
|
+
const currentDraft = draftRef.current;
|
|
115
|
+
const parsed = parseScrubExpression(currentDraft, value, options);
|
|
99
116
|
if (!parsed) {
|
|
100
|
-
|
|
117
|
+
const reverted = formatScrubValue(value, options);
|
|
118
|
+
draftRef.current = reverted;
|
|
119
|
+
setDraft(reverted);
|
|
101
120
|
return;
|
|
102
121
|
}
|
|
103
122
|
|
|
123
|
+
draftRef.current = parsed.normalized;
|
|
104
124
|
setDraft(parsed.normalized);
|
|
105
125
|
if (parsed.value !== value) {
|
|
106
|
-
onChange(parsed.value, { source: "commit", expression:
|
|
126
|
+
onChange(parsed.value, { source: "commit", expression: currentDraft });
|
|
107
127
|
}
|
|
108
128
|
};
|
|
109
129
|
|
|
@@ -131,7 +151,9 @@ export function ScrubInput({
|
|
|
131
151
|
|
|
132
152
|
if (event.key === "Escape") {
|
|
133
153
|
event.preventDefault();
|
|
134
|
-
|
|
154
|
+
const reverted = formatScrubValue(value, options);
|
|
155
|
+
draftRef.current = reverted;
|
|
156
|
+
setDraft(reverted);
|
|
135
157
|
skipNextBlurCommitRef.current = true;
|
|
136
158
|
event.currentTarget.blur();
|
|
137
159
|
}
|
|
@@ -202,11 +224,11 @@ export function ScrubInput({
|
|
|
202
224
|
labelClassName,
|
|
203
225
|
)}
|
|
204
226
|
>
|
|
205
|
-
<Icon className="size-3 shrink-0" />
|
|
227
|
+
{Icon ? <Icon className="size-3 shrink-0" /> : null}
|
|
206
228
|
<span className="truncate">{label}</span>
|
|
207
229
|
</Label>
|
|
208
230
|
</TooltipTrigger>
|
|
209
|
-
<TooltipContent>{
|
|
231
|
+
<TooltipContent>{resolvedTooltipLabel}</TooltipContent>
|
|
210
232
|
</Tooltip>
|
|
211
233
|
<Input
|
|
212
234
|
ref={inputRef}
|
|
@@ -228,7 +250,10 @@ export function ScrubInput({
|
|
|
228
250
|
}
|
|
229
251
|
commitDraft();
|
|
230
252
|
}}
|
|
231
|
-
onChange={(event) =>
|
|
253
|
+
onChange={(event) => {
|
|
254
|
+
draftRef.current = event.target.value;
|
|
255
|
+
setDraft(event.target.value);
|
|
256
|
+
}}
|
|
232
257
|
onKeyDown={handleKeyDown}
|
|
233
258
|
className={cn(
|
|
234
259
|
// Compact design-editor: h-6, 11px tabular text, ring-1 with no offset.
|
|
@@ -58,10 +58,23 @@ export function formatScrubValue(
|
|
|
58
58
|
options: Pick<ScrubExpressionOptions, "precision" | "unit"> = {},
|
|
59
59
|
): string {
|
|
60
60
|
const normalized = normalizeScrubNumber(value, options);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
let numeric: string;
|
|
62
|
+
if (Number.isFinite(options.precision) && options.precision! >= 0) {
|
|
63
|
+
const fixed = normalized.toFixed(options.precision!);
|
|
64
|
+
if (options.unit) {
|
|
65
|
+
// For fields with units (px, %, °, etc.) collapse all trailing zeros
|
|
66
|
+
// including the decimal point: "12.30px" → "12.3px", "10.00px" → "10px".
|
|
67
|
+
// Leave integer strings alone; otherwise precision 0 turns "100" into "1".
|
|
68
|
+
numeric = fixed.includes(".") ? fixed.replace(/\.?0+$/, "") : fixed;
|
|
69
|
+
} else {
|
|
70
|
+
// For unitless fields (e.g. line-height), preserve at least one decimal
|
|
71
|
+
// digit so values like "2.0" stay "2.0" rather than collapsing to "2".
|
|
72
|
+
// Only strip redundant trailing zeros beyond the first decimal digit.
|
|
73
|
+
numeric = fixed.includes(".") ? fixed.replace(/(?<=\.\d)0+$/, "") : fixed;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
numeric = String(normalized);
|
|
77
|
+
}
|
|
65
78
|
return `${numeric}${options.unit ?? ""}`;
|
|
66
79
|
}
|
|
67
80
|
|
|
@@ -36,6 +36,15 @@ export interface ElementInfo {
|
|
|
36
36
|
|
|
37
37
|
export type DeviceFrameType = "none" | "desktop" | "tablet" | "mobile";
|
|
38
38
|
|
|
39
|
+
export const DEVICE_FRAME_VIEWPORTS = {
|
|
40
|
+
desktop: { width: 1280, height: 800 },
|
|
41
|
+
tablet: { width: 768, height: 1024 },
|
|
42
|
+
mobile: { width: 390, height: 844 },
|
|
43
|
+
} as const satisfies Record<
|
|
44
|
+
Exclude<DeviceFrameType, "none">,
|
|
45
|
+
{ width: number; height: number }
|
|
46
|
+
>;
|
|
47
|
+
|
|
39
48
|
export interface ViewportTab {
|
|
40
49
|
id: string;
|
|
41
50
|
filename: string;
|