@agent-native/core 0.85.6 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +130 -1
- package/corpus/core/README.md +0 -11
- package/corpus/core/docs/content/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/ar-SA/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/ar-SA/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/workspace-connections.mdx +11 -11
- package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/de-DE/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/de-DE/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/es-ES/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/es-ES/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/fr-FR/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/fr-FR/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/hi-IN/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/hi-IN/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/ja-JP/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/ja-JP/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/ko-KR/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/ko-KR/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/pt-BR/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/pt-BR/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/zh-CN/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/zh-CN/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +0 -1
- package/corpus/core/docs/content/locales/zh-TW/plan-plugin.mdx +10 -9
- package/corpus/core/docs/content/locales/zh-TW/sharing.mdx +1 -2
- package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +2 -3
- package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +23 -7
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/workspace-connections.mdx +1 -1
- package/corpus/core/docs/content/plan-plugin.mdx +12 -11
- package/corpus/core/docs/content/sharing.mdx +1 -2
- package/corpus/core/docs/content/skills-guide.mdx +4 -3
- package/corpus/core/docs/content/template-plan.mdx +37 -6
- package/corpus/core/docs/content/tracking.mdx +1 -1
- package/corpus/core/docs/content/workspace-connections.mdx +1 -1
- package/corpus/core/package.json +6 -6
- package/corpus/core/src/agent/run-store.ts +1 -1
- package/corpus/core/src/chat-threads/store.ts +73 -11
- package/corpus/core/src/checkpoints/store.ts +13 -2
- package/corpus/core/src/cli/create.ts +3 -4
- package/corpus/core/src/cli/design-connect.ts +469 -9
- package/corpus/core/src/cli/index.ts +13 -0
- package/corpus/core/src/cli/plan-local.ts +281 -24
- package/corpus/core/src/cli/skills.ts +177 -21
- package/corpus/core/src/cli/templates-meta.ts +0 -13
- package/corpus/core/src/cli/visualize-repo.ts +715 -0
- package/corpus/core/src/client/AssistantChat.tsx +126 -17
- package/corpus/core/src/client/agent-chat-adapter.ts +6 -5
- package/corpus/core/src/client/app-providers.tsx +2 -2
- package/corpus/core/src/client/collab/index.ts +31 -0
- package/corpus/core/src/client/components/LiveCursorOverlay.tsx +16 -1
- package/corpus/core/src/client/components/PresenceBar.tsx +14 -1
- package/corpus/core/src/client/components/RecentEditHighlights.tsx +219 -0
- package/corpus/core/src/client/components/RemoteSelectionRings.tsx +40 -6
- package/corpus/core/src/client/index.ts +48 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +0 -4
- package/corpus/core/src/client/rich-markdown-editor/SharedRichEditor.tsx +49 -32
- package/corpus/core/src/client/rich-markdown-editor/index.ts +6 -0
- package/corpus/core/src/client/rich-markdown-editor/surgical-apply.ts +192 -0
- package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +59 -8
- package/corpus/core/src/client/session-replay.ts +181 -15
- package/corpus/core/src/client/sse-event-processor.ts +90 -1
- package/corpus/core/src/client/use-action.ts +123 -25
- package/corpus/core/src/client/use-agent-engine-configured.ts +7 -1
- package/corpus/core/src/client/use-chat-threads.ts +18 -7
- package/corpus/core/src/client/use-db-sync.ts +104 -19
- package/corpus/core/src/client/use-pinch-zoom.ts +76 -11
- package/corpus/core/src/client/visual-style-controls.tsx +475 -0
- package/corpus/core/src/collab/agent-presence.ts +191 -62
- package/corpus/core/src/collab/awareness.ts +83 -6
- package/corpus/core/src/collab/client.ts +89 -146
- package/corpus/core/src/collab/index.ts +34 -0
- package/corpus/core/src/collab/presence.ts +63 -3
- package/corpus/core/src/collab/recent-edits.ts +171 -0
- package/corpus/core/src/collab/undo.ts +432 -0
- package/corpus/core/src/collab/ydoc-manager.ts +68 -0
- package/corpus/core/src/connections/catalog.ts +2 -3
- package/corpus/core/src/db/create-get-db.ts +86 -1
- package/corpus/core/src/db/ensure-additive-columns.ts +430 -0
- package/corpus/core/src/db/index.ts +6 -0
- package/corpus/core/src/db/migrations.ts +236 -35
- package/corpus/core/src/jobs/scheduler.ts +17 -14
- package/corpus/core/src/observability/traces.ts +182 -1
- package/corpus/core/src/org/context.ts +79 -2
- package/corpus/core/src/server/agent-chat-plugin.ts +10 -6
- package/corpus/core/src/server/auth-marketing.ts +0 -11
- package/corpus/core/src/server/auth.ts +11 -4
- package/corpus/core/src/server/collab-plugin.ts +56 -13
- package/corpus/core/src/server/design-token-utils.ts +1 -1
- package/corpus/core/src/server/fusion-app.ts +447 -0
- package/corpus/core/src/server/index.ts +13 -0
- package/corpus/core/src/server/onboarding-html.ts +3 -4
- package/corpus/core/src/server/poll-events.ts +14 -2
- package/corpus/core/src/server/poll.ts +219 -12
- package/corpus/core/src/server/schema-prompt.ts +28 -9
- package/corpus/core/src/settings/store.ts +33 -2
- package/corpus/core/src/sharing/actions/set-resource-visibility.ts +2 -0
- package/corpus/core/src/sharing/actions/share-resource.ts +3 -0
- package/corpus/core/src/sharing/actions/unshare-resource.ts +2 -0
- package/corpus/core/src/styles/agent-native.css +0 -5
- package/corpus/core/src/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/core/src/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/core/src/templates/default/package.json +3 -3
- package/corpus/core/src/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
- package/corpus/core/src/tracking/providers.ts +22 -1
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +20 -5
- package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/analytics/AGENTS.md +33 -6
- package/corpus/templates/analytics/actions/navigate.ts +14 -4
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/actions/view-screen.ts +57 -0
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +23 -1
- package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +15 -1
- package/corpus/templates/analytics/app/i18n-data.ts +151 -10
- package/corpus/templates/analytics/app/pages/Agents.tsx +118 -0
- package/corpus/templates/analytics/app/pages/DataSources.tsx +11 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +76 -40
- package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +5 -0
- package/corpus/templates/analytics/app/routes/agents.tsx +10 -0
- package/corpus/templates/analytics/changelog/2026-07-03-agent-admin-tools-page.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-03-agent-llm-observability-dashboard.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-03-session-recordings-now-include-response-bodies-for-5xx-error.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +34 -0
- package/corpus/templates/analytics/docs/schemas/tracked-events.md +6 -0
- package/corpus/templates/analytics/package.json +3 -3
- package/corpus/templates/analytics/seeds/dashboards/agent-observability-llm.json +173 -0
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +23 -5
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +58 -13
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +23 -0
- package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +5 -0
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +120 -8
- package/corpus/templates/analytics/server/lib/session-replay.ts +3 -2
- package/corpus/templates/analytics/server/plugins/db.ts +93 -2
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +45 -1
- package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +10 -0
- package/corpus/templates/assets/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/assets/package.json +3 -3
- package/corpus/templates/assets/server/plugins/db.ts +63 -2
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/brain/package.json +3 -3
- package/corpus/templates/brain/server/plugins/db.ts +75 -2
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/calendar/package.json +3 -3
- package/corpus/templates/calendar/server/plugins/db.ts +64 -2
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/chat/package.json +3 -3
- package/corpus/templates/clips/actions/finalize-recording.ts +50 -40
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +8 -6
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +3 -3
- package/corpus/templates/clips/changelog/2026-07-03-mobile-recording-pages-keep-videos-in-a-16-9-player-with-act.md +6 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +245 -77
- package/corpus/templates/clips/package.json +3 -3
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +56 -0
- package/corpus/templates/clips/server/plugins/db.ts +68 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +27 -3
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/content/actions/edit-document.ts +48 -1
- package/corpus/templates/content/actions/list-documents.ts +101 -94
- package/corpus/templates/content/actions/search-documents.ts +19 -3
- package/corpus/templates/content/actions/update-document.ts +54 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +127 -7
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +97 -2
- package/corpus/templates/content/changelog/2026-07-03-agent-document-edits-now-appear-as-a-live-collaborator-prese.md +6 -0
- package/corpus/templates/content/changelog/2026-07-03-document-search-and-list-views-load-faster-on-large-workspac.md +6 -0
- package/corpus/templates/content/package.json +3 -3
- package/corpus/templates/content/server/plugins/db.ts +57 -1
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +190 -13
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +16 -4
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +46 -3
- package/corpus/templates/design/.agents/skills/full-app-build/SKILL.md +140 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +20 -0
- package/corpus/templates/design/AGENTS.md +83 -13
- package/corpus/templates/design/actions/add-fusion-screens.ts +91 -0
- package/corpus/templates/design/actions/apply-a11y-fix.ts +5 -1
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +24 -69
- package/corpus/templates/design/actions/apply-fusion-edits.ts +161 -0
- package/corpus/templates/design/actions/apply-motion-edit.ts +29 -2
- package/corpus/templates/design/actions/apply-visual-edit.ts +38 -3
- package/corpus/templates/design/actions/connect-localhost.ts +25 -18
- package/corpus/templates/design/actions/create-component.ts +5 -1
- package/corpus/templates/design/actions/create-fusion-app.ts +202 -0
- package/corpus/templates/design/actions/deploy-fusion-app.ts +123 -0
- package/corpus/templates/design/actions/edit-design.ts +13 -16
- package/corpus/templates/design/actions/export-pdf.ts +8 -1
- package/corpus/templates/design/actions/export-zip.ts +10 -1
- package/corpus/templates/design/actions/get-fusion-deploy-status.ts +86 -0
- package/corpus/templates/design/actions/get-motion-timeline.ts +26 -20
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +10 -0
- package/corpus/templates/design/actions/insert-asset.ts +190 -7
- package/corpus/templates/design/actions/list-fusion-edits.ts +96 -0
- package/corpus/templates/design/actions/list-local-files.ts +133 -0
- package/corpus/templates/design/actions/open-visual-edit.ts +27 -5
- package/corpus/templates/design/actions/present-design-variants.ts +2 -2
- package/corpus/templates/design/actions/push-fusion-app.ts +62 -0
- package/corpus/templates/design/actions/queue-fusion-edit.ts +112 -0
- package/corpus/templates/design/actions/read-local-file.ts +139 -0
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +18 -14
- package/corpus/templates/design/actions/run-design-audit.ts +7 -4
- package/corpus/templates/design/actions/send-fusion-message.ts +69 -0
- package/corpus/templates/design/actions/sync-fusion-app.ts +196 -0
- package/corpus/templates/design/actions/write-local-file.ts +162 -36
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +12 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +600 -66
- package/corpus/templates/design/app/components/design/EditPanel.tsx +793 -304
- package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +328 -0
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +671 -154
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +1 -1
- package/corpus/templates/design/app/components/design/MotionDock.tsx +234 -46
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2070 -420
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +25 -2
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -98
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +640 -72
- package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +605 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +50 -26
- package/corpus/templates/design/app/components/design/code-workbench/ActivityBar.tsx +85 -0
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +466 -0
- package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +53 -0
- package/corpus/templates/design/app/components/design/code-workbench/commands.ts +339 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/Breadcrumbs.tsx +42 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/BufferSync.tsx +128 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/EditorTabs.tsx +363 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +268 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/StatusBar.tsx +208 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-setup.ts +61 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-theme-palette.ts +191 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-theme.ts +29 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/status-bar-lang.ts +21 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/tab-drag.ts +27 -0
- package/corpus/templates/design/app/components/design/code-workbench/explorer/ExplorerView.tsx +155 -0
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +551 -0
- package/corpus/templates/design/app/components/design/code-workbench/explorer/file-icons.tsx +115 -0
- package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +65 -0
- package/corpus/templates/design/app/components/design/code-workbench/format/prettier-format.ts +182 -0
- package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +133 -0
- package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +563 -0
- package/corpus/templates/design/app/components/design/code-workbench/quickinput/fuzzy.ts +170 -0
- package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +508 -0
- package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +280 -0
- package/corpus/templates/design/app/components/design/code-workbench/store.tsx +734 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +39 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +196 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/localhost-provider.ts +177 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/tree.ts +110 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/types.ts +103 -0
- package/corpus/templates/design/app/components/design/code-workbench-theme.ts +68 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +16 -4
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +217 -113
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +111 -5
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -2
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +79 -16
- package/corpus/templates/design/app/components/design/inspector/index.ts +9 -0
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +67 -2
- package/corpus/templates/design/app/components/design/types.ts +16 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +80 -1
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +24 -6
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +15 -1
- package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +6 -16
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +77 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/design/app/i18n-data.ts +31 -0
- package/corpus/templates/design/app/lib/design-import.ts +95 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +5586 -1112
- package/corpus/templates/design/app/pages/Index.tsx +90 -17
- package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-03-inspector-fixes-mixed-selections-show-mixed-instead-of-wrong.md +6 -0
- package/corpus/templates/design/changelog/2026-07-03-keyframe-timeline-works-reliably-drag-keyframes-smoothly-pic.md +6 -0
- package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +6 -0
- package/corpus/templates/design/changelog/2026-07-03-much-faster-editor-smooth-dragging-zooming-and-panel-updates.md +6 -0
- package/corpus/templates/design/changelog/2026-07-03-pen-tool-refinements-click-the-first-point-to-close-with-a-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-03-text-editing-enter-adds-a-line-break-international-ime-typin.md +6 -0
- package/corpus/templates/design/changelog/2026-07-03-the-ai-now-edits-like-a-collaborator-on-canvas-its-cursor-gl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-03-the-code-panel-is-now-a-full-vs-code-style-editor-with-a-fil.md +6 -0
- package/corpus/templates/design/changelog/2026-07-03-undo-and-redo-are-dependable-across-agent-edits-screen-switc.md +6 -0
- package/corpus/templates/design/package.json +4 -3
- package/corpus/templates/design/server/db/schema.ts +31 -0
- package/corpus/templates/design/server/lib/design-export.ts +43 -1
- package/corpus/templates/design/server/lib/fusion-screens.ts +246 -0
- package/corpus/templates/design/server/plugins/db.ts +89 -2
- package/corpus/templates/design/shared/board-file.ts +25 -5
- package/corpus/templates/design/shared/canvas-math.ts +754 -9
- package/corpus/templates/design/shared/code-layer.ts +304 -26
- package/corpus/templates/design/shared/collab-selection.ts +52 -0
- package/corpus/templates/design/shared/color-utils.ts +84 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +16 -7
- package/corpus/templates/design/shared/full-app.ts +126 -0
- package/corpus/templates/design/shared/motion-compiler.ts +75 -31
- package/corpus/templates/design/shared/motion-timeline.ts +335 -0
- package/corpus/templates/design/shared/pen-path.ts +496 -23
- package/corpus/templates/dispatch/package.json +3 -3
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/forms/package.json +3 -3
- package/corpus/templates/forms/server/plugins/db.ts +62 -2
- package/corpus/templates/macros/package.json +3 -3
- package/corpus/templates/macros/server/plugins/db.ts +63 -2
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +8 -0
- package/corpus/templates/mail/package.json +3 -3
- package/corpus/templates/mail/server/lib/contact-frequency.ts +42 -15
- package/corpus/templates/mail/server/plugins/db.ts +63 -2
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +9 -3
- package/corpus/templates/plan/.agents/skills/visualize-repo/SKILL.md +92 -0
- package/corpus/templates/plan/actions/export-visual-plan.ts +5 -0
- package/corpus/templates/plan/actions/get-local-plan-folder.ts +4 -22
- package/corpus/templates/plan/actions/get-plan-version.ts +6 -16
- package/corpus/templates/plan/actions/list-plan-versions.ts +48 -20
- package/corpus/templates/plan/actions/patch-visual-plan-source.ts +41 -1
- package/corpus/templates/plan/actions/promote-local-plan-folder.ts +4 -22
- package/corpus/templates/plan/actions/update-local-plan-comments.ts +4 -2
- package/corpus/templates/plan/actions/update-local-plan-folder.ts +4 -22
- package/corpus/templates/plan/actions/update-visual-plan.ts +60 -1
- package/corpus/templates/plan/agent-native.app-skill.json +10 -0
- package/corpus/templates/plan/app/components/editor/PlanDocumentEditor.tsx +138 -39
- package/corpus/templates/plan/app/components/editor/usePlanUndoStack.ts +165 -23
- package/corpus/templates/plan/app/components/plan/PlanContentRenderer.tsx +80 -4
- package/corpus/templates/plan/app/global.css +11 -9
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +10 -1
- package/corpus/templates/plan/app/hooks/use-plan-presence.ts +66 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +215 -63
- package/corpus/templates/plan/changelog/2026-07-03-local-visual-plans-and-recaps-can-save-comments-without-sign.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-03-plan-tabs-render-nested-text-instead-of-raw-json.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-03-plans-are-now-fully-real-time-collaborative-multiple-people-.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-03-plans-open-and-refresh-faster-especially-long-lived-plans-wi.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-03-undo-now-survives-agent-edits-cmd-z-keeps-working-on-your-ow.md +6 -0
- package/corpus/templates/plan/package.json +5 -3
- package/corpus/templates/plan/server/db/schema.ts +12 -0
- package/corpus/templates/plan/server/lib/local-plan-kind.ts +25 -0
- package/corpus/templates/plan/server/lib/plan-versions.ts +83 -1
- package/corpus/templates/plan/server/lib/public-action-paths.ts +0 -2
- package/corpus/templates/plan/server/plans.ts +26 -3
- package/corpus/templates/plan/server/plugins/db.ts +105 -2
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +8 -4
- package/corpus/templates/slides/AGENTS.md +4 -0
- package/corpus/templates/slides/actions/_agent-presence.ts +48 -0
- package/corpus/templates/slides/actions/add-slide.ts +16 -18
- package/corpus/templates/slides/actions/patch-deck.ts +4 -2
- package/corpus/templates/slides/actions/update-slide.ts +114 -77
- package/corpus/templates/slides/actions/view-screen.ts +39 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +47 -3
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +32 -35
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +625 -199
- package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +342 -0
- package/corpus/templates/slides/app/components/editor/TweaksPanel.tsx +46 -96
- package/corpus/templates/slides/app/components/visual-editor/SaveStatusIndicator.tsx +8 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +589 -176
- package/corpus/templates/slides/app/hooks/use-deck-presence.ts +84 -42
- package/corpus/templates/slides/app/i18n/ar-SA.ts +46 -0
- package/corpus/templates/slides/app/i18n/de-DE.ts +46 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +46 -0
- package/corpus/templates/slides/app/i18n/es-ES.ts +46 -0
- package/corpus/templates/slides/app/i18n/fr-FR.ts +46 -0
- package/corpus/templates/slides/app/i18n/hi-IN.ts +46 -0
- package/corpus/templates/slides/app/i18n/ja-JP.ts +46 -0
- package/corpus/templates/slides/app/i18n/ko-KR.ts +46 -0
- package/corpus/templates/slides/app/i18n/pt-BR.ts +46 -0
- package/corpus/templates/slides/app/i18n/zh-CN.ts +46 -0
- package/corpus/templates/slides/app/i18n/zh-TW.ts +46 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +25 -13
- package/corpus/templates/slides/changelog/2026-07-03-figma-style-slide-editing.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-03-see-the-ai-working-with-you-when-the-agent-edits-a-slide-it-.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-03-undo-redo-is-now-precise-and-safe-with-collaborators-it-only.md +6 -0
- package/corpus/templates/slides/package.json +3 -3
- package/corpus/templates/slides/server/handlers/decks.ts +31 -3
- package/corpus/templates/slides/server/plugins/db.ts +65 -2
- package/corpus/templates/videos/netlify.toml +1 -12
- package/dist/agent/run-store.js +1 -1
- package/dist/agent/run-store.js.map +1 -1
- package/dist/chat-threads/store.d.ts +9 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +63 -11
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/checkpoints/store.d.ts.map +1 -1
- package/dist/checkpoints/store.js +10 -2
- package/dist/checkpoints/store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +3 -5
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/design-connect.d.ts +49 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +414 -9
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/index.js +12 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plan-local.d.ts +24 -0
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +188 -20
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/skills.d.ts +3 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +173 -21
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +0 -14
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/cli/visualize-repo.d.ts +49 -0
- package/dist/cli/visualize-repo.d.ts.map +1 -0
- package/dist/cli/visualize-repo.js +561 -0
- package/dist/cli/visualize-repo.js.map +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +120 -15
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +6 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/app-providers.d.ts +2 -2
- package/dist/client/app-providers.js.map +1 -1
- package/dist/client/collab/index.d.ts +4 -1
- package/dist/client/collab/index.d.ts.map +1 -1
- package/dist/client/collab/index.js +3 -0
- package/dist/client/collab/index.js.map +1 -1
- package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -1
- package/dist/client/components/LiveCursorOverlay.js +11 -3
- package/dist/client/components/LiveCursorOverlay.js.map +1 -1
- package/dist/client/components/PresenceBar.d.ts.map +1 -1
- package/dist/client/components/PresenceBar.js +6 -1
- package/dist/client/components/PresenceBar.js.map +1 -1
- package/dist/client/components/RecentEditHighlights.d.ts +43 -0
- package/dist/client/components/RecentEditHighlights.d.ts.map +1 -0
- package/dist/client/components/RecentEditHighlights.js +146 -0
- package/dist/client/components/RecentEditHighlights.js.map +1 -0
- package/dist/client/components/RemoteSelectionRings.d.ts +9 -0
- package/dist/client/components/RemoteSelectionRings.d.ts.map +1 -1
- package/dist/client/components/RemoteSelectionRings.js +22 -8
- package/dist/client/components/RemoteSelectionRings.js.map +1 -1
- package/dist/client/index.d.ts +6 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +7 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +1 -3
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +8 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/SharedRichEditor.js +8 -2
- package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
- package/dist/client/rich-markdown-editor/index.d.ts +1 -0
- package/dist/client/rich-markdown-editor/index.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/index.js +1 -0
- package/dist/client/rich-markdown-editor/index.js.map +1 -1
- package/dist/client/rich-markdown-editor/surgical-apply.d.ts +66 -0
- package/dist/client/rich-markdown-editor/surgical-apply.d.ts.map +1 -0
- package/dist/client/rich-markdown-editor/surgical-apply.js +147 -0
- package/dist/client/rich-markdown-editor/surgical-apply.js.map +1 -0
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts +18 -2
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +39 -8
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
- package/dist/client/session-replay.d.ts +10 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +134 -7
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +81 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-action.d.ts +24 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +109 -27
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +7 -1
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +11 -3
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +36 -0
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +68 -12
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-pinch-zoom.d.ts.map +1 -1
- package/dist/client/use-pinch-zoom.js +76 -11
- package/dist/client/use-pinch-zoom.js.map +1 -1
- package/dist/client/visual-style-controls.d.ts +79 -0
- package/dist/client/visual-style-controls.d.ts.map +1 -0
- package/dist/client/visual-style-controls.js +150 -0
- package/dist/client/visual-style-controls.js.map +1 -0
- package/dist/collab/agent-presence.d.ts +51 -5
- package/dist/collab/agent-presence.d.ts.map +1 -1
- package/dist/collab/agent-presence.js +144 -58
- package/dist/collab/agent-presence.js.map +1 -1
- package/dist/collab/awareness-store.d.ts +32 -0
- package/dist/collab/awareness-store.d.ts.map +1 -0
- package/dist/collab/awareness-store.js +0 -0
- package/dist/collab/awareness-store.js.map +1 -0
- package/dist/collab/awareness.d.ts +14 -3
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/awareness.js +53 -5
- package/dist/collab/awareness.js.map +1 -1
- package/dist/collab/client.d.ts +2 -0
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +79 -119
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/index.d.ts +3 -1
- package/dist/collab/index.d.ts.map +1 -1
- package/dist/collab/index.js +5 -1
- package/dist/collab/index.js.map +1 -1
- package/dist/collab/presence.d.ts.map +1 -1
- package/dist/collab/presence.js +54 -3
- package/dist/collab/presence.js.map +1 -1
- package/dist/collab/recent-edits.d.ts +87 -0
- package/dist/collab/recent-edits.d.ts.map +1 -0
- package/dist/collab/recent-edits.js +116 -0
- package/dist/collab/recent-edits.js.map +1 -0
- package/dist/collab/undo.d.ts +121 -0
- package/dist/collab/undo.d.ts.map +1 -0
- package/dist/collab/undo.js +244 -0
- package/dist/collab/undo.js.map +1 -0
- package/dist/collab/ydoc-manager.d.ts +10 -0
- package/dist/collab/ydoc-manager.d.ts.map +1 -1
- package/dist/collab/ydoc-manager.js +57 -0
- package/dist/collab/ydoc-manager.js.map +1 -1
- package/dist/connections/catalog.d.ts +2 -2
- package/dist/connections/catalog.d.ts.map +1 -1
- package/dist/connections/catalog.js +1 -1
- package/dist/connections/catalog.js.map +1 -1
- package/dist/db/create-get-db.d.ts +22 -0
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +73 -1
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/db/ensure-additive-columns.d.ts +93 -0
- package/dist/db/ensure-additive-columns.d.ts.map +1 -0
- package/dist/db/ensure-additive-columns.js +355 -0
- package/dist/db/ensure-additive-columns.js.map +1 -0
- package/dist/db/index.d.ts +1 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +70 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +196 -33
- package/dist/db/migrations.js.map +1 -1
- package/dist/jobs/scheduler.js +17 -10
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +155 -1
- package/dist/observability/traces.js.map +1 -1
- package/dist/org/context.d.ts +7 -0
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +54 -2
- package/dist/org/context.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +10 -6
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth-marketing.d.ts.map +1 -1
- package/dist/server/auth-marketing.js +0 -10
- package/dist/server/auth-marketing.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +6 -4
- package/dist/server/auth.js.map +1 -1
- package/dist/server/collab-plugin.d.ts.map +1 -1
- package/dist/server/collab-plugin.js +45 -13
- package/dist/server/collab-plugin.js.map +1 -1
- package/dist/server/design-token-utils.d.ts +1 -1
- package/dist/server/design-token-utils.js +1 -1
- package/dist/server/design-token-utils.js.map +1 -1
- package/dist/server/fusion-app.d.ts +107 -0
- package/dist/server/fusion-app.d.ts.map +1 -0
- package/dist/server/fusion-app.js +371 -0
- package/dist/server/fusion-app.js.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +3 -4
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/poll-events.d.ts +2 -0
- package/dist/server/poll-events.d.ts.map +1 -1
- package/dist/server/poll-events.js +7 -2
- package/dist/server/poll-events.js.map +1 -1
- package/dist/server/poll.d.ts +47 -3
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +176 -10
- package/dist/server/poll.js.map +1 -1
- package/dist/server/schema-prompt.d.ts.map +1 -1
- package/dist/server/schema-prompt.js +24 -7
- package/dist/server/schema-prompt.js.map +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +32 -3
- package/dist/settings/store.js.map +1 -1
- package/dist/sharing/actions/set-resource-visibility.js +2 -0
- package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
- package/dist/sharing/actions/share-resource.d.ts.map +1 -1
- package/dist/sharing/actions/share-resource.js +3 -0
- package/dist/sharing/actions/share-resource.js.map +1 -1
- package/dist/sharing/actions/unshare-resource.js +2 -0
- package/dist/sharing/actions/unshare-resource.js.map +1 -1
- package/dist/styles/agent-native.css +0 -5
- package/dist/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/dist/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
- package/dist/templates/default/package.json +3 -3
- package/dist/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
- package/dist/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
- package/dist/tracking/providers.d.ts.map +1 -1
- package/dist/tracking/providers.js +17 -1
- package/dist/tracking/providers.js.map +1 -1
- package/docs/content/cloneable-saas.mdx +0 -1
- package/docs/content/locales/ar-SA/cloneable-saas.mdx +0 -1
- package/docs/content/locales/ar-SA/plan-plugin.mdx +10 -9
- package/docs/content/locales/ar-SA/sharing.mdx +1 -2
- package/docs/content/locales/ar-SA/skills-guide.mdx +2 -3
- package/docs/content/locales/ar-SA/template-plan.mdx +23 -7
- package/docs/content/locales/ar-SA/tracking.mdx +1 -1
- package/docs/content/locales/ar-SA/workspace-connections.mdx +11 -11
- package/docs/content/locales/de-DE/cloneable-saas.mdx +0 -1
- package/docs/content/locales/de-DE/plan-plugin.mdx +10 -9
- package/docs/content/locales/de-DE/sharing.mdx +1 -2
- package/docs/content/locales/de-DE/skills-guide.mdx +2 -3
- package/docs/content/locales/de-DE/template-plan.mdx +23 -7
- package/docs/content/locales/de-DE/tracking.mdx +1 -1
- package/docs/content/locales/de-DE/workspace-connections.mdx +1 -1
- package/docs/content/locales/es-ES/cloneable-saas.mdx +0 -1
- package/docs/content/locales/es-ES/plan-plugin.mdx +10 -9
- package/docs/content/locales/es-ES/sharing.mdx +1 -2
- package/docs/content/locales/es-ES/skills-guide.mdx +2 -3
- package/docs/content/locales/es-ES/template-plan.mdx +23 -7
- package/docs/content/locales/es-ES/tracking.mdx +1 -1
- package/docs/content/locales/es-ES/workspace-connections.mdx +1 -1
- package/docs/content/locales/fr-FR/cloneable-saas.mdx +0 -1
- package/docs/content/locales/fr-FR/plan-plugin.mdx +10 -9
- package/docs/content/locales/fr-FR/sharing.mdx +1 -2
- package/docs/content/locales/fr-FR/skills-guide.mdx +2 -3
- package/docs/content/locales/fr-FR/template-plan.mdx +23 -7
- package/docs/content/locales/fr-FR/tracking.mdx +1 -1
- package/docs/content/locales/fr-FR/workspace-connections.mdx +1 -1
- package/docs/content/locales/hi-IN/cloneable-saas.mdx +0 -1
- package/docs/content/locales/hi-IN/plan-plugin.mdx +10 -9
- package/docs/content/locales/hi-IN/sharing.mdx +1 -2
- package/docs/content/locales/hi-IN/skills-guide.mdx +2 -3
- package/docs/content/locales/hi-IN/template-plan.mdx +23 -7
- package/docs/content/locales/hi-IN/tracking.mdx +1 -1
- package/docs/content/locales/hi-IN/workspace-connections.mdx +1 -1
- package/docs/content/locales/ja-JP/cloneable-saas.mdx +0 -1
- package/docs/content/locales/ja-JP/plan-plugin.mdx +10 -9
- package/docs/content/locales/ja-JP/sharing.mdx +1 -2
- package/docs/content/locales/ja-JP/skills-guide.mdx +2 -3
- package/docs/content/locales/ja-JP/template-plan.mdx +23 -7
- package/docs/content/locales/ja-JP/tracking.mdx +1 -1
- package/docs/content/locales/ja-JP/workspace-connections.mdx +1 -1
- package/docs/content/locales/ko-KR/cloneable-saas.mdx +0 -1
- package/docs/content/locales/ko-KR/plan-plugin.mdx +10 -9
- package/docs/content/locales/ko-KR/sharing.mdx +1 -2
- package/docs/content/locales/ko-KR/skills-guide.mdx +2 -3
- package/docs/content/locales/ko-KR/template-plan.mdx +23 -7
- package/docs/content/locales/ko-KR/tracking.mdx +1 -1
- package/docs/content/locales/ko-KR/workspace-connections.mdx +1 -1
- package/docs/content/locales/pt-BR/cloneable-saas.mdx +0 -1
- package/docs/content/locales/pt-BR/plan-plugin.mdx +10 -9
- package/docs/content/locales/pt-BR/sharing.mdx +1 -2
- package/docs/content/locales/pt-BR/skills-guide.mdx +2 -3
- package/docs/content/locales/pt-BR/template-plan.mdx +23 -7
- package/docs/content/locales/pt-BR/tracking.mdx +1 -1
- package/docs/content/locales/pt-BR/workspace-connections.mdx +1 -1
- package/docs/content/locales/zh-CN/cloneable-saas.mdx +0 -1
- package/docs/content/locales/zh-CN/plan-plugin.mdx +10 -9
- package/docs/content/locales/zh-CN/sharing.mdx +1 -2
- package/docs/content/locales/zh-CN/skills-guide.mdx +2 -3
- package/docs/content/locales/zh-CN/template-plan.mdx +23 -7
- package/docs/content/locales/zh-CN/tracking.mdx +1 -1
- package/docs/content/locales/zh-CN/workspace-connections.mdx +1 -1
- package/docs/content/locales/zh-TW/cloneable-saas.mdx +0 -1
- package/docs/content/locales/zh-TW/plan-plugin.mdx +10 -9
- package/docs/content/locales/zh-TW/sharing.mdx +1 -2
- package/docs/content/locales/zh-TW/skills-guide.mdx +2 -3
- package/docs/content/locales/zh-TW/template-plan.mdx +23 -7
- package/docs/content/locales/zh-TW/tracking.mdx +1 -1
- package/docs/content/locales/zh-TW/workspace-connections.mdx +1 -1
- package/docs/content/plan-plugin.mdx +12 -11
- package/docs/content/sharing.mdx +1 -2
- package/docs/content/skills-guide.mdx +4 -3
- package/docs/content/template-plan.mdx +37 -6
- package/docs/content/tracking.mdx +1 -1
- package/docs/content/workspace-connections.mdx +1 -1
- package/package.json +6 -6
- package/src/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/src/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
- package/src/templates/default/package.json +3 -3
- package/src/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
- package/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
- package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
- package/corpus/core/docs/content/locales/ar-SA/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/de-DE/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/es-ES/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/fr-FR/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/hi-IN/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/ja-JP/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/ko-KR/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/pt-BR/template-videos.mdx +0 -336
- package/corpus/core/docs/content/locales/zh-CN/template-videos.mdx +0 -334
- package/corpus/core/docs/content/locales/zh-TW/template-videos.mdx +0 -334
- package/corpus/core/docs/content/template-videos.mdx +0 -332
- package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +0 -697
- package/corpus/templates/slides/app/components/editor/CodeEditor.tsx +0 -49
- package/corpus/templates/videos/.agents/skills/actions/SKILL.md +0 -432
- package/corpus/templates/videos/.agents/skills/animation-tracks/SKILL.md +0 -99
- package/corpus/templates/videos/.agents/skills/capture-learnings/SKILL.md +0 -76
- package/corpus/templates/videos/.agents/skills/composition-management/SKILL.md +0 -106
- package/corpus/templates/videos/.agents/skills/create-skill/SKILL.md +0 -221
- package/corpus/templates/videos/.agents/skills/delegate-to-agent/SKILL.md +0 -263
- package/corpus/templates/videos/.agents/skills/frontend-design/SKILL.md +0 -112
- package/corpus/templates/videos/.agents/skills/security/SKILL.md +0 -263
- package/corpus/templates/videos/.agents/skills/self-modifying-code/SKILL.md +0 -88
- package/corpus/templates/videos/.agents/skills/shadcn-ui/SKILL.md +0 -95
- package/corpus/templates/videos/.builder/ANIMATED_PROPERTIES.md +0 -335
- package/corpus/templates/videos/.builder/KEYFRAME_SYNC.md +0 -166
- package/corpus/templates/videos/.builder/SEAMLESS_SYNC.md +0 -211
- package/corpus/templates/videos/.builder/rules/deploy-app.mdc +0 -22
- package/corpus/templates/videos/.builder/rules/organize-ui.mdc +0 -9
- package/corpus/templates/videos/.builder/skills/remotion/SKILL.md +0 -29
- package/corpus/templates/videos/.builder/skills/remotion/rules/animations.md +0 -27
- package/corpus/templates/videos/.builder/skills/remotion/rules/assets.md +0 -43
- package/corpus/templates/videos/.builder/skills/remotion/rules/audio.md +0 -58
- package/corpus/templates/videos/.builder/skills/remotion/rules/compositions.md +0 -129
- package/corpus/templates/videos/.builder/skills/remotion/rules/fonts.md +0 -47
- package/corpus/templates/videos/.builder/skills/remotion/rules/images.md +0 -41
- package/corpus/templates/videos/.builder/skills/remotion/rules/parameters.md +0 -59
- package/corpus/templates/videos/.builder/skills/remotion/rules/sequencing.md +0 -54
- package/corpus/templates/videos/.builder/skills/remotion/rules/tailwind.md +0 -11
- package/corpus/templates/videos/.builder/skills/remotion/rules/text-animations.md +0 -18
- package/corpus/templates/videos/.builder/skills/remotion/rules/timing.md +0 -103
- package/corpus/templates/videos/.builder/skills/remotion/rules/transparent-videos.md +0 -35
- package/corpus/templates/videos/.builder/skills/remotion/rules/trimming.md +0 -38
- package/corpus/templates/videos/.builder/skills/remotion/rules/videos.md +0 -54
- package/corpus/templates/videos/.dockerignore +0 -62
- package/corpus/templates/videos/.env.example +0 -4
- package/corpus/templates/videos/.oxfmtrc.json +0 -8
- package/corpus/templates/videos/AGENTS.md +0 -46
- package/corpus/templates/videos/CHANGELOG.md +0 -10
- package/corpus/templates/videos/DEVELOPING.md +0 -143
- package/corpus/templates/videos/README.md +0 -33
- package/corpus/templates/videos/actions/analyze-brand-assets.ts +0 -151
- package/corpus/templates/videos/actions/apply-design-system.ts +0 -35
- package/corpus/templates/videos/actions/create-design-system.ts +0 -84
- package/corpus/templates/videos/actions/create-folder.ts +0 -48
- package/corpus/templates/videos/actions/db-connect.ts +0 -45
- package/corpus/templates/videos/actions/db-health.ts +0 -24
- package/corpus/templates/videos/actions/db-status.ts +0 -28
- package/corpus/templates/videos/actions/delete-composition.ts +0 -272
- package/corpus/templates/videos/actions/delete-folder.ts +0 -27
- package/corpus/templates/videos/actions/generate-animated-component.ts +0 -470
- package/corpus/templates/videos/actions/get-composition.ts +0 -36
- package/corpus/templates/videos/actions/get-design-system.ts +0 -35
- package/corpus/templates/videos/actions/import-code.ts +0 -67
- package/corpus/templates/videos/actions/import-design-project.ts +0 -42
- package/corpus/templates/videos/actions/import-document.ts +0 -125
- package/corpus/templates/videos/actions/import-from-url.ts +0 -18
- package/corpus/templates/videos/actions/import-github.ts +0 -182
- package/corpus/templates/videos/actions/list-compositions.ts +0 -32
- package/corpus/templates/videos/actions/list-design-systems.ts +0 -66
- package/corpus/templates/videos/actions/list-folders.ts +0 -61
- package/corpus/templates/videos/actions/move-composition-to-folder.ts +0 -59
- package/corpus/templates/videos/actions/navigate.ts +0 -40
- package/corpus/templates/videos/actions/rename-folder.ts +0 -41
- package/corpus/templates/videos/actions/run.ts +0 -2
- package/corpus/templates/videos/actions/save-composition.ts +0 -114
- package/corpus/templates/videos/actions/set-default-design-system.ts +0 -40
- package/corpus/templates/videos/actions/update-composition.ts +0 -81
- package/corpus/templates/videos/actions/update-design-system.ts +0 -68
- package/corpus/templates/videos/actions/validate-compositions.ts +0 -502
- package/corpus/templates/videos/actions/view-screen.ts +0 -48
- package/corpus/templates/videos/app/components/CameraControls.tsx +0 -442
- package/corpus/templates/videos/app/components/CameraToolbar.tsx +0 -437
- package/corpus/templates/videos/app/components/CloudUpgrade.tsx +0 -287
- package/corpus/templates/videos/app/components/CollabPresenceBar.tsx +0 -81
- package/corpus/templates/videos/app/components/CompSettingsEditor.tsx +0 -107
- package/corpus/templates/videos/app/components/ComponentLibrarySidebar.tsx +0 -457
- package/corpus/templates/videos/app/components/ComponentPropsPanel.tsx +0 -227
- package/corpus/templates/videos/app/components/ComponentsSidebar.tsx +0 -70
- package/corpus/templates/videos/app/components/CompositionCard.tsx +0 -147
- package/corpus/templates/videos/app/components/CurrentElementPanel.tsx +0 -835
- package/corpus/templates/videos/app/components/CursorControls.tsx +0 -664
- package/corpus/templates/videos/app/components/CursorPositioningOverlay.tsx +0 -238
- package/corpus/templates/videos/app/components/LibraryFolderRow.tsx +0 -235
- package/corpus/templates/videos/app/components/MotionCurveSelect.tsx +0 -61
- package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +0 -192
- package/corpus/templates/videos/app/components/PropsEditor.tsx +0 -210
- package/corpus/templates/videos/app/components/QuestionFlow.tsx +0 -40
- package/corpus/templates/videos/app/components/Sidebar.tsx +0 -609
- package/corpus/templates/videos/app/components/StudioHeader.tsx +0 -109
- package/corpus/templates/videos/app/components/Timeline.tsx +0 -2067
- package/corpus/templates/videos/app/components/TrackPropertiesPanel.tsx +0 -922
- package/corpus/templates/videos/app/components/TweaksPanel.tsx +0 -277
- package/corpus/templates/videos/app/components/VideoPlayer.tsx +0 -487
- package/corpus/templates/videos/app/components/design-system/DesignSystemCard.tsx +0 -134
- package/corpus/templates/videos/app/components/design-system/DesignSystemPreview.tsx +0 -176
- package/corpus/templates/videos/app/components/design-system/DesignSystemSetup.tsx +0 -707
- package/corpus/templates/videos/app/components/keyframes/KeyframeActionButtons.tsx +0 -78
- package/corpus/templates/videos/app/components/keyframes/KeyframeNavigation.tsx +0 -59
- package/corpus/templates/videos/app/components/keyframes/PropertyNumberInput.tsx +0 -56
- package/corpus/templates/videos/app/components/layout/Header.tsx +0 -64
- package/corpus/templates/videos/app/components/layout/HeaderActions.tsx +0 -84
- package/corpus/templates/videos/app/components/layout/Layout.tsx +0 -132
- package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +0 -180
- package/corpus/templates/videos/app/components/ui/accordion.tsx +0 -56
- package/corpus/templates/videos/app/components/ui/alert-dialog.tsx +0 -139
- package/corpus/templates/videos/app/components/ui/alert.tsx +0 -59
- package/corpus/templates/videos/app/components/ui/aspect-ratio.tsx +0 -5
- package/corpus/templates/videos/app/components/ui/avatar.tsx +0 -48
- package/corpus/templates/videos/app/components/ui/badge.tsx +0 -37
- package/corpus/templates/videos/app/components/ui/breadcrumb.tsx +0 -115
- package/corpus/templates/videos/app/components/ui/button.tsx +0 -57
- package/corpus/templates/videos/app/components/ui/calendar.tsx +0 -68
- package/corpus/templates/videos/app/components/ui/card.tsx +0 -86
- package/corpus/templates/videos/app/components/ui/carousel.tsx +0 -260
- package/corpus/templates/videos/app/components/ui/chart.tsx +0 -375
- package/corpus/templates/videos/app/components/ui/checkbox.tsx +0 -28
- package/corpus/templates/videos/app/components/ui/collapsible.tsx +0 -9
- package/corpus/templates/videos/app/components/ui/command.tsx +0 -154
- package/corpus/templates/videos/app/components/ui/context-menu.tsx +0 -202
- package/corpus/templates/videos/app/components/ui/dialog.tsx +0 -130
- package/corpus/templates/videos/app/components/ui/drawer.tsx +0 -116
- package/corpus/templates/videos/app/components/ui/dropdown-menu.tsx +0 -212
- package/corpus/templates/videos/app/components/ui/form.tsx +0 -177
- package/corpus/templates/videos/app/components/ui/hover-card.tsx +0 -27
- package/corpus/templates/videos/app/components/ui/input-otp.tsx +0 -69
- package/corpus/templates/videos/app/components/ui/input.tsx +0 -22
- package/corpus/templates/videos/app/components/ui/label.tsx +0 -24
- package/corpus/templates/videos/app/components/ui/menubar.tsx +0 -234
- package/corpus/templates/videos/app/components/ui/navigation-menu.tsx +0 -128
- package/corpus/templates/videos/app/components/ui/pagination.tsx +0 -121
- package/corpus/templates/videos/app/components/ui/popover.tsx +0 -44
- package/corpus/templates/videos/app/components/ui/progress.tsx +0 -26
- package/corpus/templates/videos/app/components/ui/radio-group.tsx +0 -42
- package/corpus/templates/videos/app/components/ui/resizable.tsx +0 -43
- package/corpus/templates/videos/app/components/ui/scroll-area.tsx +0 -46
- package/corpus/templates/videos/app/components/ui/select.tsx +0 -158
- package/corpus/templates/videos/app/components/ui/separator.tsx +0 -29
- package/corpus/templates/videos/app/components/ui/sheet.tsx +0 -160
- package/corpus/templates/videos/app/components/ui/sidebar.tsx +0 -782
- package/corpus/templates/videos/app/components/ui/skeleton.tsx +0 -15
- package/corpus/templates/videos/app/components/ui/slider.tsx +0 -26
- package/corpus/templates/videos/app/components/ui/sonner.tsx +0 -58
- package/corpus/templates/videos/app/components/ui/spinner.tsx +0 -17
- package/corpus/templates/videos/app/components/ui/switch.tsx +0 -27
- package/corpus/templates/videos/app/components/ui/table.tsx +0 -117
- package/corpus/templates/videos/app/components/ui/tabs.tsx +0 -53
- package/corpus/templates/videos/app/components/ui/textarea.tsx +0 -23
- package/corpus/templates/videos/app/components/ui/toast.tsx +0 -127
- package/corpus/templates/videos/app/components/ui/toaster.tsx +0 -33
- package/corpus/templates/videos/app/components/ui/toggle-group.tsx +0 -59
- package/corpus/templates/videos/app/components/ui/toggle.tsx +0 -43
- package/corpus/templates/videos/app/components/ui/tooltip.tsx +0 -30
- package/corpus/templates/videos/app/components/ui/use-toast.ts +0 -3
- package/corpus/templates/videos/app/config/constants.ts +0 -204
- package/corpus/templates/videos/app/contexts/CompositionContext.tsx +0 -437
- package/corpus/templates/videos/app/contexts/CurrentElementContext.tsx +0 -247
- package/corpus/templates/videos/app/contexts/PlaybackContext.tsx +0 -65
- package/corpus/templates/videos/app/contexts/TimelineContext.tsx +0 -433
- package/corpus/templates/videos/app/entry.client.tsx +0 -19
- package/corpus/templates/videos/app/entry.server.tsx +0 -10
- package/corpus/templates/videos/app/global.css +0 -171
- package/corpus/templates/videos/app/hooks/use-composition-collab.ts +0 -269
- package/corpus/templates/videos/app/hooks/use-composition-design-system.ts +0 -47
- package/corpus/templates/videos/app/hooks/use-database-compositions.ts +0 -144
- package/corpus/templates/videos/app/hooks/use-db-status.ts +0 -30
- package/corpus/templates/videos/app/hooks/use-design-systems.ts +0 -21
- package/corpus/templates/videos/app/hooks/use-folders.ts +0 -199
- package/corpus/templates/videos/app/hooks/use-mobile.tsx +0 -21
- package/corpus/templates/videos/app/hooks/use-navigation-state.ts +0 -52
- package/corpus/templates/videos/app/hooks/use-question-flow.ts +0 -14
- package/corpus/templates/videos/app/hooks/use-toast.ts +0 -188
- package/corpus/templates/videos/app/hooks/useUnsavedChanges.ts +0 -48
- package/corpus/templates/videos/app/i18n/ar-SA.ts +0 -446
- package/corpus/templates/videos/app/i18n/de-DE.ts +0 -470
- package/corpus/templates/videos/app/i18n/en-US.ts +0 -452
- package/corpus/templates/videos/app/i18n/es-ES.ts +0 -468
- package/corpus/templates/videos/app/i18n/fr-FR.ts +0 -469
- package/corpus/templates/videos/app/i18n/hi-IN.ts +0 -446
- package/corpus/templates/videos/app/i18n/index.ts +0 -34
- package/corpus/templates/videos/app/i18n/ja-JP.ts +0 -460
- package/corpus/templates/videos/app/i18n/ko-KR.ts +0 -453
- package/corpus/templates/videos/app/i18n/pt-BR.ts +0 -461
- package/corpus/templates/videos/app/i18n/zh-CN.ts +0 -437
- package/corpus/templates/videos/app/i18n/zh-TW.ts +0 -437
- package/corpus/templates/videos/app/lib/database-compositions.ts +0 -67
- package/corpus/templates/videos/app/lib/design-systems.ts +0 -234
- package/corpus/templates/videos/app/lib/tab-id.ts +0 -1
- package/corpus/templates/videos/app/lib/utils.ts +0 -1
- package/corpus/templates/videos/app/pages/ComponentLibrary.tsx +0 -134
- package/corpus/templates/videos/app/pages/ComponentLibraryView.tsx +0 -362
- package/corpus/templates/videos/app/pages/CompositionView.tsx +0 -715
- package/corpus/templates/videos/app/pages/DesignSystems.tsx +0 -180
- package/corpus/templates/videos/app/pages/Index.tsx +0 -258
- package/corpus/templates/videos/app/pages/NewComposition.tsx +0 -95
- package/corpus/templates/videos/app/pages/NotFound.tsx +0 -36
- package/corpus/templates/videos/app/remotion/CameraHost.tsx +0 -223
- package/corpus/templates/videos/app/remotion/animations/useElementAnimations.ts +0 -231
- package/corpus/templates/videos/app/remotion/componentRegistry.ts +0 -473
- package/corpus/templates/videos/app/remotion/components/AnimatedElement.tsx +0 -436
- package/corpus/templates/videos/app/remotion/components/InteractiveElement.tsx +0 -159
- package/corpus/templates/videos/app/remotion/components/InteractiveZoneEditor.tsx +0 -273
- package/corpus/templates/videos/app/remotion/compositions/BlankComposition.tsx +0 -204
- package/corpus/templates/videos/app/remotion/compositions/index.ts +0 -2
- package/corpus/templates/videos/app/remotion/createComponentCursorTrack.ts +0 -132
- package/corpus/templates/videos/app/remotion/docs/CURSOR_TRACKS.md +0 -285
- package/corpus/templates/videos/app/remotion/easingFunctions.ts +0 -255
- package/corpus/templates/videos/app/remotion/hooks/createInteractiveComposition.tsx +0 -283
- package/corpus/templates/videos/app/remotion/hooks/useCursorHistory.ts +0 -72
- package/corpus/templates/videos/app/remotion/hooks/useCursorTypeFromHover.ts +0 -35
- package/corpus/templates/videos/app/remotion/hooks/useEditableZones.tsx +0 -267
- package/corpus/templates/videos/app/remotion/hooks/useHoverAnimation.ts +0 -90
- package/corpus/templates/videos/app/remotion/hooks/useHoverAnimationSmooth.ts +0 -146
- package/corpus/templates/videos/app/remotion/hooks/useInteractiveComponent.ts +0 -711
- package/corpus/templates/videos/app/remotion/hooks/useRegisterInteractiveElement.ts +0 -56
- package/corpus/templates/videos/app/remotion/library-components/Button.tsx +0 -252
- package/corpus/templates/videos/app/remotion/library-components/Card.tsx +0 -241
- package/corpus/templates/videos/app/remotion/library-components/CodePanel.tsx +0 -1005
- package/corpus/templates/videos/app/remotion/library-components/FileItem.tsx +0 -196
- package/corpus/templates/videos/app/remotion/library-components/FolderItem.tsx +0 -199
- package/corpus/templates/videos/app/remotion/library-components/PrimaryButton.tsx +0 -177
- package/corpus/templates/videos/app/remotion/library-components/SecondaryButton.tsx +0 -167
- package/corpus/templates/videos/app/remotion/library-components/SectionHeader.tsx +0 -108
- package/corpus/templates/videos/app/remotion/library-components/TEMPLATE.tsx +0 -211
- package/corpus/templates/videos/app/remotion/library-components/index.ts +0 -10
- package/corpus/templates/videos/app/remotion/registry.ts +0 -91
- package/corpus/templates/videos/app/remotion/testing/animationTestUtils.ts +0 -366
- package/corpus/templates/videos/app/remotion/trackAnimation.ts +0 -179
- package/corpus/templates/videos/app/remotion/trackHelpers.ts +0 -110
- package/corpus/templates/videos/app/remotion/ui-components/Cursor.tsx +0 -182
- package/corpus/templates/videos/app/remotion/ui-components/InteractiveButton.tsx +0 -116
- package/corpus/templates/videos/app/remotion/ui-components/InteractiveCard.tsx +0 -126
- package/corpus/templates/videos/app/remotion/ui-components/index.ts +0 -16
- package/corpus/templates/videos/app/remotion/utils/animationHelpers.ts +0 -444
- package/corpus/templates/videos/app/remotion/utils/interactiveElements.ts +0 -235
- package/corpus/templates/videos/app/remotion/utils/springAnimation.ts +0 -76
- package/corpus/templates/videos/app/root.tsx +0 -165
- package/corpus/templates/videos/app/routes/$.tsx +0 -10
- package/corpus/templates/videos/app/routes/_index.tsx +0 -25
- package/corpus/templates/videos/app/routes/c.$compositionId.tsx +0 -10
- package/corpus/templates/videos/app/routes/components.tsx +0 -10
- package/corpus/templates/videos/app/routes/design-systems.tsx +0 -10
- package/corpus/templates/videos/app/routes/extensions.$id.$slug.tsx +0 -2
- package/corpus/templates/videos/app/routes/extensions.$id.tsx +0 -5
- package/corpus/templates/videos/app/routes/extensions._index.tsx +0 -5
- package/corpus/templates/videos/app/routes/extensions.tsx +0 -5
- package/corpus/templates/videos/app/routes/settings.tsx +0 -88
- package/corpus/templates/videos/app/routes/team.tsx +0 -11
- package/corpus/templates/videos/app/routes.ts +0 -4
- package/corpus/templates/videos/app/state.ts +0 -77
- package/corpus/templates/videos/app/types/elementAnimations.ts +0 -121
- package/corpus/templates/videos/app/types.ts +0 -200
- package/corpus/templates/videos/app/utils/compositionHelpers.ts +0 -445
- package/corpus/templates/videos/app/utils/debug.ts +0 -63
- package/corpus/templates/videos/app/utils/keyframeUtils.ts +0 -331
- package/corpus/templates/videos/app/utils/timelineCoordinates.ts +0 -79
- package/corpus/templates/videos/app/vite-env.d.ts +0 -6
- package/corpus/templates/videos/changelog/2026-06-24-a-new-settings-page-gives-quick-access-to-language-workspace.md +0 -6
- package/corpus/templates/videos/changelog/2026-06-24-added-a-language-picker-and-localized-app-chrome-for-support.md +0 -6
- package/corpus/templates/videos/changelog/2026-06-27-traditional-chinese-copy-uses-taiwan-terminology.md +0 -6
- package/corpus/templates/videos/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +0 -6
- package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +0 -6
- package/corpus/templates/videos/components.json +0 -20
- package/corpus/templates/videos/data/sync-config.json +0 -3
- package/corpus/templates/videos/docs/examples/create-composition.example.ts +0 -128
- package/corpus/templates/videos/drizzle.config.ts +0 -3
- package/corpus/templates/videos/learnings.defaults.md +0 -5
- package/corpus/templates/videos/package.json +0 -114
- package/corpus/templates/videos/public/agent-native-icon-dark.svg +0 -10
- package/corpus/templates/videos/public/agent-native-icon-light.svg +0 -10
- package/corpus/templates/videos/public/agent-native-logo-dark.svg +0 -21
- package/corpus/templates/videos/public/agent-native-logo-light.svg +0 -21
- package/corpus/templates/videos/public/favicon.svg +0 -1
- package/corpus/templates/videos/public/icon-180.svg +0 -1
- package/corpus/templates/videos/public/icon-192.svg +0 -1
- package/corpus/templates/videos/public/icon-512.svg +0 -1
- package/corpus/templates/videos/public/manifest.json +0 -21
- package/corpus/templates/videos/public/placeholder.svg +0 -1
- package/corpus/templates/videos/public/robots.txt +0 -14
- package/corpus/templates/videos/react-router.config.ts +0 -7
- package/corpus/templates/videos/server/db/index.ts +0 -37
- package/corpus/templates/videos/server/db/schema.ts +0 -55
- package/corpus/templates/videos/server/middleware/auth.ts +0 -15
- package/corpus/templates/videos/server/plugins/agent-chat.ts +0 -29
- package/corpus/templates/videos/server/plugins/auth.ts +0 -14
- package/corpus/templates/videos/server/plugins/collab.ts +0 -11
- package/corpus/templates/videos/server/plugins/core-routes.ts +0 -22
- package/corpus/templates/videos/server/plugins/db.ts +0 -160
- package/corpus/templates/videos/server/routes/[...page].get.ts +0 -5
- package/corpus/templates/videos/server/routes/[...page].head.ts +0 -1
- package/corpus/templates/videos/server/routes/api/save-composition-defaults.post.ts +0 -274
- package/corpus/templates/videos/shared/api.ts +0 -75
- package/corpus/templates/videos/ssr-entry.ts +0 -15
- package/corpus/templates/videos/tsconfig.json +0 -21
- package/corpus/templates/videos/vite.config.ts +0 -19
- package/corpus/templates/videos/vitest.config.ts +0 -16
- package/docs/content/locales/ar-SA/template-videos.mdx +0 -336
- package/docs/content/locales/de-DE/template-videos.mdx +0 -336
- package/docs/content/locales/es-ES/template-videos.mdx +0 -336
- package/docs/content/locales/fr-FR/template-videos.mdx +0 -336
- package/docs/content/locales/hi-IN/template-videos.mdx +0 -336
- package/docs/content/locales/ja-JP/template-videos.mdx +0 -336
- package/docs/content/locales/ko-KR/template-videos.mdx +0 -336
- package/docs/content/locales/pt-BR/template-videos.mdx +0 -336
- package/docs/content/locales/zh-CN/template-videos.mdx +0 -334
- package/docs/content/locales/zh-TW/template-videos.mdx +0 -334
- package/docs/content/template-videos.mdx +0 -332
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
} from "@tabler/icons-react";
|
|
68
68
|
import { useQueryClient } from "@tanstack/react-query";
|
|
69
69
|
import {
|
|
70
|
+
memo,
|
|
70
71
|
useCallback,
|
|
71
72
|
useEffect,
|
|
72
73
|
useMemo,
|
|
@@ -82,6 +83,7 @@ import {
|
|
|
82
83
|
DropdownMenu,
|
|
83
84
|
DropdownMenuContent,
|
|
84
85
|
DropdownMenuItem,
|
|
86
|
+
DropdownMenuSeparator,
|
|
85
87
|
DropdownMenuTrigger,
|
|
86
88
|
} from "@/components/ui/dropdown-menu";
|
|
87
89
|
import { Input } from "@/components/ui/input";
|
|
@@ -129,6 +131,7 @@ import {
|
|
|
129
131
|
type DesignGradientStopPatch,
|
|
130
132
|
type DesignGradientType,
|
|
131
133
|
type ImageFillValue,
|
|
134
|
+
type ScrubInputChangeMeta,
|
|
132
135
|
} from "./inspector";
|
|
133
136
|
import { IconLayoutSettings } from "./inspector/design-icons";
|
|
134
137
|
import type { DesignPaintType } from "./inspector/DesignColorPicker";
|
|
@@ -140,6 +143,33 @@ import type { ElementInfo } from "./types";
|
|
|
140
143
|
|
|
141
144
|
export type InspectorTab = "design" | "tweaks";
|
|
142
145
|
|
|
146
|
+
/**
|
|
147
|
+
* PF12: gesture-lifecycle metadata threaded alongside a style commit.
|
|
148
|
+
*
|
|
149
|
+
* - "preview": a live, in-progress tick (a ScrubInput scrub sample or a
|
|
150
|
+
* DesignColorPicker drag tick) — cheap to show in the live iframe preview,
|
|
151
|
+
* but must NOT trigger the expensive source commit (projection parse + HTML
|
|
152
|
+
* patch + history entry) on every tick.
|
|
153
|
+
* - "commit" (or omitted, for callers that don't pass meta at all): the
|
|
154
|
+
* gesture's authoritative final value — exactly one per gesture — which
|
|
155
|
+
* DOES trigger the full source commit. Omitting meta entirely preserves
|
|
156
|
+
* prior behavior (treated as "commit") for every non-scrub/color call site.
|
|
157
|
+
*/
|
|
158
|
+
export interface StyleChangeMeta {
|
|
159
|
+
phase?: "preview" | "commit";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type StyleChangeHandler = (
|
|
163
|
+
property: string,
|
|
164
|
+
value: string,
|
|
165
|
+
meta?: StyleChangeMeta,
|
|
166
|
+
) => void;
|
|
167
|
+
|
|
168
|
+
export type StylesChangeHandler = (
|
|
169
|
+
styles: Record<string, string>,
|
|
170
|
+
meta?: StyleChangeMeta,
|
|
171
|
+
) => void;
|
|
172
|
+
|
|
143
173
|
const MIXED_VALUE = "Mixed";
|
|
144
174
|
|
|
145
175
|
function isMixedValue(value: string | undefined): boolean {
|
|
@@ -152,7 +182,7 @@ function sameOrMixed(values: string[]): string {
|
|
|
152
182
|
return values.every((value) => value === first) ? first : MIXED_VALUE;
|
|
153
183
|
}
|
|
154
184
|
|
|
155
|
-
function mixedElementFromSelection(
|
|
185
|
+
export function mixedElementFromSelection(
|
|
156
186
|
elements: ElementInfo[],
|
|
157
187
|
): ElementInfo | null {
|
|
158
188
|
const base = elements[elements.length - 1];
|
|
@@ -167,6 +197,27 @@ function mixedElementFromSelection(
|
|
|
167
197
|
sameOrMixed(elements.map((element) => element.computedStyles[key] ?? "")),
|
|
168
198
|
]),
|
|
169
199
|
);
|
|
200
|
+
// Mix inlineStyles the same way as computedStyles so authoredStyleValue()
|
|
201
|
+
// sees a proper Mixed sentinel across a multi-selection instead of
|
|
202
|
+
// silently inheriting the last-selected element's raw inline value
|
|
203
|
+
// (spreading ...base alone would leak that stale single-element value).
|
|
204
|
+
const inlineStyleKeys = new Set<string>();
|
|
205
|
+
elements.forEach((element) => {
|
|
206
|
+
Object.keys(element.inlineStyles ?? {}).forEach((key) =>
|
|
207
|
+
inlineStyleKeys.add(key),
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
const inlineStyles =
|
|
211
|
+
inlineStyleKeys.size > 0
|
|
212
|
+
? Object.fromEntries(
|
|
213
|
+
Array.from(inlineStyleKeys).map((key) => [
|
|
214
|
+
key,
|
|
215
|
+
sameOrMixed(
|
|
216
|
+
elements.map((element) => element.inlineStyles?.[key] ?? ""),
|
|
217
|
+
),
|
|
218
|
+
]),
|
|
219
|
+
)
|
|
220
|
+
: undefined;
|
|
170
221
|
const minX = Math.min(...elements.map((element) => element.boundingRect.x));
|
|
171
222
|
const minY = Math.min(...elements.map((element) => element.boundingRect.y));
|
|
172
223
|
const maxX = Math.max(
|
|
@@ -187,6 +238,12 @@ function mixedElementFromSelection(
|
|
|
187
238
|
selector: base.selector,
|
|
188
239
|
classes: [],
|
|
189
240
|
computedStyles,
|
|
241
|
+
inlineStyles,
|
|
242
|
+
// Mix like tagName above — otherwise isTextElement() would trust
|
|
243
|
+
// base.primitiveKind alone and misclassify a mixed text+shape selection.
|
|
244
|
+
primitiveKind: sameOrMixed(
|
|
245
|
+
elements.map((element) => element.primitiveKind ?? ""),
|
|
246
|
+
),
|
|
190
247
|
boundingRect: {
|
|
191
248
|
x: minX,
|
|
192
249
|
y: minY,
|
|
@@ -217,8 +274,8 @@ interface EditPanelProps {
|
|
|
217
274
|
tweakValues?: Record<string, string | number | boolean>;
|
|
218
275
|
onTweakChange?: (id: string, value: string | number | boolean) => void;
|
|
219
276
|
onRequestTweaks?: (anchor: HTMLElement) => void;
|
|
220
|
-
onStyleChange:
|
|
221
|
-
onStylesChange?:
|
|
277
|
+
onStyleChange: StyleChangeHandler;
|
|
278
|
+
onStylesChange?: StylesChangeHandler;
|
|
222
279
|
onExport?: (settings: ExportSettingsValue[]) => void;
|
|
223
280
|
exporting?: boolean;
|
|
224
281
|
/** Active file id — used for component prop editing context. */
|
|
@@ -393,6 +450,9 @@ function PropInput({
|
|
|
393
450
|
}) {
|
|
394
451
|
const [draft, setDraft] = useState(value);
|
|
395
452
|
const mixed = isMixedValue(value);
|
|
453
|
+
// Escape reverts and blurs; the blur handler must then skip its commit or it
|
|
454
|
+
// would re-commit the stale draft closure (mirrors ScrubInput's Escape path).
|
|
455
|
+
const skipNextBlurCommitRef = useRef(false);
|
|
396
456
|
|
|
397
457
|
useEffect(() => {
|
|
398
458
|
setDraft(value);
|
|
@@ -431,12 +491,27 @@ function PropInput({
|
|
|
431
491
|
// defaultUnit) keep the responsive live-update behavior.
|
|
432
492
|
if (defaultUnit === undefined) onChange(e.target.value);
|
|
433
493
|
}}
|
|
434
|
-
onBlur={
|
|
494
|
+
onBlur={() => {
|
|
495
|
+
if (skipNextBlurCommitRef.current) {
|
|
496
|
+
skipNextBlurCommitRef.current = false;
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
commit();
|
|
500
|
+
}}
|
|
435
501
|
onKeyDown={(e) => {
|
|
436
502
|
if (e.key === "Enter") {
|
|
437
503
|
e.preventDefault();
|
|
438
504
|
commit();
|
|
439
505
|
(e.currentTarget as HTMLInputElement).blur();
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (e.key === "Escape") {
|
|
509
|
+
e.preventDefault();
|
|
510
|
+
// Revert the draft to the last committed value and blur, matching
|
|
511
|
+
// ScrubInput's Escape behavior.
|
|
512
|
+
setDraft(value);
|
|
513
|
+
skipNextBlurCommitRef.current = true;
|
|
514
|
+
(e.currentTarget as HTMLInputElement).blur();
|
|
440
515
|
}
|
|
441
516
|
}}
|
|
442
517
|
placeholder={placeholder}
|
|
@@ -465,7 +540,7 @@ function ColorInput({
|
|
|
465
540
|
}: {
|
|
466
541
|
label: string;
|
|
467
542
|
value: string;
|
|
468
|
-
onChange: (value: string) => void;
|
|
543
|
+
onChange: (value: string, meta?: StyleChangeMeta) => void;
|
|
469
544
|
backgroundImage?: string;
|
|
470
545
|
backgroundSize?: string;
|
|
471
546
|
backgroundRepeat?: string;
|
|
@@ -533,9 +608,28 @@ function ColorInput({
|
|
|
533
608
|
}
|
|
534
609
|
}, [activeStopIds, selectedStopId]);
|
|
535
610
|
|
|
536
|
-
|
|
611
|
+
// PF12: `phase` defaults to "commit" so every discrete/one-shot caller
|
|
612
|
+
// (swatch clicks, paint-type switches, hex commit, fill-row edits) keeps
|
|
613
|
+
// committing immediately as before. Only the raw per-tick `onChange` wired
|
|
614
|
+
// to DesignColorPicker below passes "preview" explicitly — the picker's own
|
|
615
|
+
// `onChangeComplete` re-invokes setNext with the same final value tagged
|
|
616
|
+
// "commit" once the gesture ends (see the DesignColorPicker render below).
|
|
617
|
+
const setNext = (next: string, phase: "preview" | "commit" = "commit") => {
|
|
618
|
+
// Guard rail for callers that don't wire onBackgroundImageChange (i.e.
|
|
619
|
+
// supportsLayeredFills is false, e.g. the text-fill "color" row): the
|
|
620
|
+
// picker manages gradient/image paint-type selection as *local* UI state
|
|
621
|
+
// independent of props (see DesignColorPicker's localPaintType), so a
|
|
622
|
+
// user can still open the Gradient/Image tab there even when this
|
|
623
|
+
// ColorInput never offered layered fills. When that happens,
|
|
624
|
+
// emitPaintValue falls back to this onChange with a full gradient/url()
|
|
625
|
+
// CSS string, which is invalid for a plain color property (color /
|
|
626
|
+
// backgroundColor) and gets silently dropped by the browser — but not
|
|
627
|
+
// before clobbering the last-known-good value in this component's own
|
|
628
|
+
// state. Reject anything that doesn't parse as a plain solid color in
|
|
629
|
+
// that case instead of forwarding it.
|
|
630
|
+
if (!supportsLayeredFills && !parseCssColor(next)) return;
|
|
537
631
|
setDraft(next);
|
|
538
|
-
onChange(next);
|
|
632
|
+
onChange(next, { phase });
|
|
539
633
|
};
|
|
540
634
|
|
|
541
635
|
const replaceBackgroundLayer = (index: number, nextLayer: string) => {
|
|
@@ -794,7 +888,14 @@ function ColorInput({
|
|
|
794
888
|
key={pickerKey}
|
|
795
889
|
label={label}
|
|
796
890
|
value={pickerValue}
|
|
797
|
-
onChange
|
|
891
|
+
// PF12: `onChange` fires on every SV/hue/alpha drag tick — tag those as
|
|
892
|
+
// "preview" so the caller can skip the expensive source commit and only
|
|
893
|
+
// update the live iframe preview. `onChangeComplete` fires exactly once
|
|
894
|
+
// per gesture (drag-end, hex commit, keyboard nudge, swatch click,
|
|
895
|
+
// paint-type switch) with the same final value, tagged "commit" so the
|
|
896
|
+
// authoritative source write always happens exactly once.
|
|
897
|
+
onChange={(v) => setNext(v, "preview")}
|
|
898
|
+
onChangeComplete={(v) => setNext(v, "commit")}
|
|
798
899
|
onPaintValueChange={
|
|
799
900
|
supportsLayeredFills ? handlePaintValueChange : undefined
|
|
800
901
|
}
|
|
@@ -911,6 +1012,46 @@ function averageGradientOpacity(stops: DesignGradientStop[]): number {
|
|
|
911
1012
|
return Math.round(total / stops.length);
|
|
912
1013
|
}
|
|
913
1014
|
|
|
1015
|
+
/**
|
|
1016
|
+
* Marker used to non-destructively hide a single backgroundImage layer
|
|
1017
|
+
* (gradient or image). CSS strips comments from computed style values — a
|
|
1018
|
+
* trailing comment appended to a backgroundImage layer does not survive
|
|
1019
|
+
* getComputedStyle (verified: browsers normalize/serialize computed values
|
|
1020
|
+
* without their source comments) — so we can't tag the layer text itself.
|
|
1021
|
+
* Instead we pair the untouched original layer with a zero-size
|
|
1022
|
+
* background-size entry at the same index: `background-size: 0px 0px` makes
|
|
1023
|
+
* that layer render nothing while backgroundImage keeps the exact original
|
|
1024
|
+
* CSS text. Both backgroundImage and backgroundSize are real, valid,
|
|
1025
|
+
* positionally-paired CSS lists that DO round-trip through computed style,
|
|
1026
|
+
* so hiding survives reselect/reload with no React state stash required.
|
|
1027
|
+
*/
|
|
1028
|
+
const HIDDEN_LAYER_SIZE_MARKER = "0px 0px";
|
|
1029
|
+
|
|
1030
|
+
export function isLayerHiddenBySize(sizeEntry: string | undefined): boolean {
|
|
1031
|
+
return (
|
|
1032
|
+
(sizeEntry ?? "").trim().replace(/\s+/g, " ") === HIDDEN_LAYER_SIZE_MARKER
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Rewrites the background-size list so `index` is hidden/shown via the
|
|
1038
|
+
* zero-size marker, padding shorter lists with "auto" (the CSS default) so
|
|
1039
|
+
* every other layer keeps rendering at its current/default size.
|
|
1040
|
+
*/
|
|
1041
|
+
export function withLayerSizeMarker(
|
|
1042
|
+
sizeLayers: string[],
|
|
1043
|
+
layerCount: number,
|
|
1044
|
+
index: number,
|
|
1045
|
+
hidden: boolean,
|
|
1046
|
+
): string {
|
|
1047
|
+
const next = Array.from(
|
|
1048
|
+
{ length: layerCount },
|
|
1049
|
+
(_, i) => sizeLayers[i] || "auto",
|
|
1050
|
+
);
|
|
1051
|
+
next[index] = hidden ? HIDDEN_LAYER_SIZE_MARKER : "auto";
|
|
1052
|
+
return joinCssLayers(next);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
914
1055
|
function splitCssLayers(value: string): string[] {
|
|
915
1056
|
const trimmed = value.trim();
|
|
916
1057
|
if (!trimmed || trimmed === "none") return [];
|
|
@@ -1578,8 +1719,30 @@ function parseScaleValue(value: string | undefined): [number, number] {
|
|
|
1578
1719
|
return [Number.isFinite(x) ? x : 1, Number.isFinite(y) ? y : 1];
|
|
1579
1720
|
}
|
|
1580
1721
|
|
|
1581
|
-
|
|
1582
|
-
|
|
1722
|
+
/**
|
|
1723
|
+
* Normalize an angle in degrees into the (-180, 180] range the inspector
|
|
1724
|
+
* displays and commits (design-tool convention: 270° reads as -90°, a full
|
|
1725
|
+
* 360° turn reads as 0°). Exported for tests.
|
|
1726
|
+
*/
|
|
1727
|
+
export function normalizeRotationDegrees(degrees: number): number {
|
|
1728
|
+
if (!Number.isFinite(degrees)) return 0;
|
|
1729
|
+
let normalized = degrees % 360;
|
|
1730
|
+
if (normalized > 180) normalized -= 360;
|
|
1731
|
+
else if (normalized <= -180) normalized += 360;
|
|
1732
|
+
return Object.is(normalized, -0) ? 0 : normalized;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
/** Exported for tests. */
|
|
1736
|
+
export function mergeRotationValue(
|
|
1737
|
+
transform: string | undefined,
|
|
1738
|
+
degrees: number,
|
|
1739
|
+
) {
|
|
1740
|
+
// Round first, then normalize, so a rounded -179.96 → -180 still lands
|
|
1741
|
+
// inside (-180, 180] as +180.
|
|
1742
|
+
const normalizedDegrees = normalizeRotationDegrees(
|
|
1743
|
+
Math.round(degrees * 10) / 10,
|
|
1744
|
+
);
|
|
1745
|
+
const nextRotate = `rotate(${normalizedDegrees}deg)`;
|
|
1583
1746
|
if (!transform || transform === "none") return nextRotate;
|
|
1584
1747
|
// Replace an existing rotate()/rotateZ() in ANY unit so we don't append a
|
|
1585
1748
|
// second rotate() (which would compound, e.g. "rotate(0.5turn) rotate(30deg)").
|
|
@@ -1628,7 +1791,7 @@ function ScrubStyleInput({
|
|
|
1628
1791
|
label: string;
|
|
1629
1792
|
value: string;
|
|
1630
1793
|
placeholder?: number;
|
|
1631
|
-
onChange: (value: number) => void;
|
|
1794
|
+
onChange: (value: number, meta?: ScrubInputChangeMeta) => void;
|
|
1632
1795
|
unit?: string;
|
|
1633
1796
|
min?: number;
|
|
1634
1797
|
max?: number;
|
|
@@ -1670,8 +1833,8 @@ function ScrubStyleInput({
|
|
|
1670
1833
|
|
|
1671
1834
|
function commitStylePatch(
|
|
1672
1835
|
styles: Record<string, string>,
|
|
1673
|
-
onStyleChange:
|
|
1674
|
-
onStylesChange?:
|
|
1836
|
+
onStyleChange: StyleChangeHandler,
|
|
1837
|
+
onStylesChange?: StylesChangeHandler,
|
|
1675
1838
|
) {
|
|
1676
1839
|
if (onStylesChange) {
|
|
1677
1840
|
onStylesChange(styles);
|
|
@@ -1695,6 +1858,11 @@ function cssLengthNumber(value: string | undefined, fallback = 0): number {
|
|
|
1695
1858
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1696
1859
|
}
|
|
1697
1860
|
|
|
1861
|
+
/** Round to one decimal place — matches the `precision={1}` ScrubInput controls advertise (e.g. stroke weight, font size) so 0.5-unit values aren't silently floored to whole numbers. */
|
|
1862
|
+
export function roundToOneDecimal(value: number): number {
|
|
1863
|
+
return Math.round(value * 10) / 10;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1698
1866
|
function cssColorOrFallback(value: string | undefined, fallback: string) {
|
|
1699
1867
|
const normalized = value?.trim();
|
|
1700
1868
|
if (
|
|
@@ -1711,6 +1879,17 @@ function strokeIsVisible(width: string | undefined, style: string | undefined) {
|
|
|
1711
1879
|
return cssLengthNumber(width) > 0 && style !== "none";
|
|
1712
1880
|
}
|
|
1713
1881
|
|
|
1882
|
+
/**
|
|
1883
|
+
* True when a stroke's own color is the reason it's invisible (alpha
|
|
1884
|
+
* zeroed) rather than its width/style. Used so the eye toggle can hide a
|
|
1885
|
+
* stroke by zeroing color alpha — preserving the original border-style
|
|
1886
|
+
* (solid/dashed/dotted/etc, which has no "unset" round-trip once written as
|
|
1887
|
+
* "none") — instead of forcing borderStyle back to "solid" on every show.
|
|
1888
|
+
*/
|
|
1889
|
+
export function strokeHiddenByColor(color: string | undefined): boolean {
|
|
1890
|
+
return Boolean(color) && !colorHasVisibleAlpha(color);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1714
1893
|
function swatchStyle(value: string | undefined) {
|
|
1715
1894
|
return {
|
|
1716
1895
|
background:
|
|
@@ -1894,8 +2073,29 @@ function parentFlexDirection(element: ElementInfo): AutoLayoutSizingAxis {
|
|
|
1894
2073
|
: "horizontal";
|
|
1895
2074
|
}
|
|
1896
2075
|
|
|
1897
|
-
function isTextElement(element: ElementInfo): boolean {
|
|
1898
|
-
|
|
2076
|
+
export function isTextElement(element: ElementInfo): boolean {
|
|
2077
|
+
const tag = (element.tagName || "").toLowerCase();
|
|
2078
|
+
if (TEXT_TAGS.has(tag)) return true;
|
|
2079
|
+
// T-tool text primitives are plain `div`s stamped with
|
|
2080
|
+
// data-an-primitive="text" (see DesignEditor primitive creation). The
|
|
2081
|
+
// bridge now forwards that marker as ElementInfo.primitiveKind — prefer it
|
|
2082
|
+
// when present since it's exact.
|
|
2083
|
+
if (element.primitiveKind) return element.primitiveKind === "text";
|
|
2084
|
+
// Fallback for older payloads that predate primitiveKind: approximate a
|
|
2085
|
+
// T-tool text div with a content heuristic — a childless div that has its
|
|
2086
|
+
// own text content and isn't already flagged as a layout container. This
|
|
2087
|
+
// intentionally excludes empty frames/shapes (no text) and containers with
|
|
2088
|
+
// element children.
|
|
2089
|
+
if (
|
|
2090
|
+
tag === "div" &&
|
|
2091
|
+
!element.isFlexContainer &&
|
|
2092
|
+
!element.isGridContainer &&
|
|
2093
|
+
(element.childElementCount ?? 0) === 0 &&
|
|
2094
|
+
Boolean(element.textContent?.trim())
|
|
2095
|
+
) {
|
|
2096
|
+
return true;
|
|
2097
|
+
}
|
|
2098
|
+
return false;
|
|
1899
2099
|
}
|
|
1900
2100
|
|
|
1901
2101
|
/**
|
|
@@ -1972,7 +2172,7 @@ function commitElementMinMax(
|
|
|
1972
2172
|
axis: AutoLayoutSizingAxis,
|
|
1973
2173
|
kind: "min" | "max",
|
|
1974
2174
|
value: number | null,
|
|
1975
|
-
onStyleChange:
|
|
2175
|
+
onStyleChange: StyleChangeHandler,
|
|
1976
2176
|
) {
|
|
1977
2177
|
const isHorizontal = axis === "horizontal";
|
|
1978
2178
|
const property =
|
|
@@ -2047,8 +2247,8 @@ function commitElementSizing(
|
|
|
2047
2247
|
element: ElementInfo,
|
|
2048
2248
|
axis: AutoLayoutSizingAxis,
|
|
2049
2249
|
sizing: AutoLayoutSizing,
|
|
2050
|
-
onStyleChange:
|
|
2051
|
-
onStylesChange?:
|
|
2250
|
+
onStyleChange: StyleChangeHandler,
|
|
2251
|
+
onStylesChange?: StylesChangeHandler,
|
|
2052
2252
|
) {
|
|
2053
2253
|
const isHorizontal = axis === "horizontal";
|
|
2054
2254
|
const sizeProperty = isHorizontal ? "width" : "height";
|
|
@@ -3327,39 +3527,83 @@ function CornerRadiusControl({
|
|
|
3327
3527
|
onStyleChange,
|
|
3328
3528
|
}: {
|
|
3329
3529
|
styles: Record<string, string>;
|
|
3330
|
-
onStyleChange:
|
|
3530
|
+
onStyleChange: StyleChangeHandler;
|
|
3331
3531
|
}) {
|
|
3332
3532
|
const t = useT();
|
|
3333
3533
|
const independentCornersLabel = t("editPanel.labels.independentCorners");
|
|
3534
|
+
const cornerSources = {
|
|
3535
|
+
topLeft: styles.borderTopLeftRadius || styles.borderRadius,
|
|
3536
|
+
topRight: styles.borderTopRightRadius || styles.borderRadius,
|
|
3537
|
+
bottomRight: styles.borderBottomRightRadius || styles.borderRadius,
|
|
3538
|
+
bottomLeft: styles.borderBottomLeftRadius || styles.borderRadius,
|
|
3539
|
+
};
|
|
3540
|
+
const cornerMixed = {
|
|
3541
|
+
topLeft: isMixedValue(cornerSources.topLeft),
|
|
3542
|
+
topRight: isMixedValue(cornerSources.topRight),
|
|
3543
|
+
bottomRight: isMixedValue(cornerSources.bottomRight),
|
|
3544
|
+
bottomLeft: isMixedValue(cornerSources.bottomLeft),
|
|
3545
|
+
};
|
|
3546
|
+
// Guard cssLengthNumber against the Mixed sentinel — parseFloat("Mixed")
|
|
3547
|
+
// would silently coerce it to 0 and render a concrete value.
|
|
3334
3548
|
const corners = {
|
|
3335
|
-
topLeft:
|
|
3336
|
-
topRight:
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
bottomRight:
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
bottomLeft:
|
|
3343
|
-
|
|
3344
|
-
|
|
3549
|
+
topLeft: cornerMixed.topLeft ? 0 : cssLengthNumber(cornerSources.topLeft),
|
|
3550
|
+
topRight: cornerMixed.topRight
|
|
3551
|
+
? 0
|
|
3552
|
+
: cssLengthNumber(cornerSources.topRight),
|
|
3553
|
+
bottomRight: cornerMixed.bottomRight
|
|
3554
|
+
? 0
|
|
3555
|
+
: cssLengthNumber(cornerSources.bottomRight),
|
|
3556
|
+
bottomLeft: cornerMixed.bottomLeft
|
|
3557
|
+
? 0
|
|
3558
|
+
: cssLengthNumber(cornerSources.bottomLeft),
|
|
3345
3559
|
};
|
|
3346
|
-
const
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3560
|
+
const anyCornerMixed =
|
|
3561
|
+
cornerMixed.topLeft ||
|
|
3562
|
+
cornerMixed.topRight ||
|
|
3563
|
+
cornerMixed.bottomRight ||
|
|
3564
|
+
cornerMixed.bottomLeft;
|
|
3565
|
+
const allCornersMixed =
|
|
3566
|
+
cornerMixed.topLeft &&
|
|
3567
|
+
cornerMixed.topRight &&
|
|
3568
|
+
cornerMixed.bottomRight &&
|
|
3569
|
+
cornerMixed.bottomLeft;
|
|
3570
|
+
// With mixed sentinels the parsed numbers are placeholders, so compare
|
|
3571
|
+
// mixed-ness instead: all-mixed reads as uniform (each element may still be
|
|
3572
|
+
// uniform), partially-mixed means at least one element has differing corners.
|
|
3573
|
+
const cornersDiffer = anyCornerMixed
|
|
3574
|
+
? !allCornersMixed
|
|
3575
|
+
: corners.topLeft !== corners.topRight ||
|
|
3576
|
+
corners.topLeft !== corners.bottomRight ||
|
|
3577
|
+
corners.topLeft !== corners.bottomLeft;
|
|
3350
3578
|
const [showIndependentCorners, setShowIndependentCorners] =
|
|
3351
3579
|
useState(cornersDiffer);
|
|
3352
|
-
const
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3580
|
+
const radiusMixed =
|
|
3581
|
+
anyCornerMixed || (!cornersDiffer && isMixedValue(styles.borderRadius));
|
|
3582
|
+
const radius = radiusMixed
|
|
3583
|
+
? 0
|
|
3584
|
+
: cornersDiffer
|
|
3585
|
+
? corners.topLeft
|
|
3586
|
+
: cssLengthNumber(styles.borderRadius || String(corners.topLeft));
|
|
3587
|
+
const commitRadius = (value: number, meta?: ScrubInputChangeMeta) => {
|
|
3356
3588
|
const next = `${Math.max(0, Math.round(value))}px`;
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
onStyleChange("
|
|
3361
|
-
onStyleChange("
|
|
3362
|
-
onStyleChange("
|
|
3589
|
+
// Always write the longhands along with the shorthand: stale inline
|
|
3590
|
+
// longhand declarations serialize after the shorthand and would override
|
|
3591
|
+
// it, turning uniform-radius commits into silent no-ops.
|
|
3592
|
+
onStyleChange("borderRadius", next, meta);
|
|
3593
|
+
onStyleChange("borderTopLeftRadius", next, meta);
|
|
3594
|
+
onStyleChange("borderTopRightRadius", next, meta);
|
|
3595
|
+
onStyleChange("borderBottomRightRadius", next, meta);
|
|
3596
|
+
onStyleChange("borderBottomLeftRadius", next, meta);
|
|
3597
|
+
};
|
|
3598
|
+
const toggleIndependentCorners = () => {
|
|
3599
|
+
// Collapsing while corners differ flattens them to the displayed uniform
|
|
3600
|
+
// value; otherwise the stale longhands would keep overriding the shorthand
|
|
3601
|
+
// and the single field would silently no-op. Mixed selections collapse the
|
|
3602
|
+
// UI only — committing would stamp the placeholder 0 onto every object.
|
|
3603
|
+
if (showIndependentCorners && cornersDiffer && !radiusMixed) {
|
|
3604
|
+
commitRadius(radius);
|
|
3605
|
+
}
|
|
3606
|
+
setShowIndependentCorners(!showIndependentCorners);
|
|
3363
3607
|
};
|
|
3364
3608
|
|
|
3365
3609
|
useEffect(() => {
|
|
@@ -3373,6 +3617,7 @@ function CornerRadiusControl({
|
|
|
3373
3617
|
icon={IconBorderRadius}
|
|
3374
3618
|
value={radius}
|
|
3375
3619
|
onChange={commitRadius}
|
|
3620
|
+
mixed={radiusMixed}
|
|
3376
3621
|
min={0}
|
|
3377
3622
|
precision={0}
|
|
3378
3623
|
/>
|
|
@@ -3389,7 +3634,7 @@ function CornerRadiusControl({
|
|
|
3389
3634
|
)}
|
|
3390
3635
|
aria-label={independentCornersLabel}
|
|
3391
3636
|
aria-pressed={showIndependentCorners}
|
|
3392
|
-
onClick={
|
|
3637
|
+
onClick={toggleIndependentCorners}
|
|
3393
3638
|
>
|
|
3394
3639
|
<IconBorderCorners className="size-3.5" />
|
|
3395
3640
|
</Button>
|
|
@@ -3403,12 +3648,14 @@ function CornerRadiusControl({
|
|
|
3403
3648
|
ariaLabel="Top left"
|
|
3404
3649
|
icon={IconRadiusTopLeft}
|
|
3405
3650
|
value={corners.topLeft}
|
|
3406
|
-
onChange={(value) =>
|
|
3651
|
+
onChange={(value, meta) =>
|
|
3407
3652
|
onStyleChange(
|
|
3408
3653
|
"borderTopLeftRadius",
|
|
3409
3654
|
`${Math.max(0, Math.round(value))}px`,
|
|
3655
|
+
meta,
|
|
3410
3656
|
)
|
|
3411
3657
|
}
|
|
3658
|
+
mixed={cornerMixed.topLeft}
|
|
3412
3659
|
min={0}
|
|
3413
3660
|
precision={1}
|
|
3414
3661
|
/>
|
|
@@ -3417,12 +3664,14 @@ function CornerRadiusControl({
|
|
|
3417
3664
|
ariaLabel="Top right"
|
|
3418
3665
|
icon={IconRadiusTopRight}
|
|
3419
3666
|
value={corners.topRight}
|
|
3420
|
-
onChange={(value) =>
|
|
3667
|
+
onChange={(value, meta) =>
|
|
3421
3668
|
onStyleChange(
|
|
3422
3669
|
"borderTopRightRadius",
|
|
3423
3670
|
`${Math.max(0, Math.round(value))}px`,
|
|
3671
|
+
meta,
|
|
3424
3672
|
)
|
|
3425
3673
|
}
|
|
3674
|
+
mixed={cornerMixed.topRight}
|
|
3426
3675
|
min={0}
|
|
3427
3676
|
precision={1}
|
|
3428
3677
|
/>
|
|
@@ -3432,12 +3681,14 @@ function CornerRadiusControl({
|
|
|
3432
3681
|
ariaLabel="Bottom left"
|
|
3433
3682
|
icon={IconRadiusBottomLeft}
|
|
3434
3683
|
value={corners.bottomLeft}
|
|
3435
|
-
onChange={(value) =>
|
|
3684
|
+
onChange={(value, meta) =>
|
|
3436
3685
|
onStyleChange(
|
|
3437
3686
|
"borderBottomLeftRadius",
|
|
3438
3687
|
`${Math.max(0, Math.round(value))}px`,
|
|
3688
|
+
meta,
|
|
3439
3689
|
)
|
|
3440
3690
|
}
|
|
3691
|
+
mixed={cornerMixed.bottomLeft}
|
|
3441
3692
|
min={0}
|
|
3442
3693
|
precision={1}
|
|
3443
3694
|
/>
|
|
@@ -3446,12 +3697,14 @@ function CornerRadiusControl({
|
|
|
3446
3697
|
ariaLabel="Bottom right"
|
|
3447
3698
|
icon={IconRadiusBottomRight}
|
|
3448
3699
|
value={corners.bottomRight}
|
|
3449
|
-
onChange={(value) =>
|
|
3700
|
+
onChange={(value, meta) =>
|
|
3450
3701
|
onStyleChange(
|
|
3451
3702
|
"borderBottomRightRadius",
|
|
3452
3703
|
`${Math.max(0, Math.round(value))}px`,
|
|
3704
|
+
meta,
|
|
3453
3705
|
)
|
|
3454
3706
|
}
|
|
3707
|
+
mixed={cornerMixed.bottomRight}
|
|
3455
3708
|
min={0}
|
|
3456
3709
|
precision={1}
|
|
3457
3710
|
/>
|
|
@@ -3479,7 +3732,7 @@ function AppearanceScrubField({
|
|
|
3479
3732
|
ariaLabel?: string;
|
|
3480
3733
|
icon: (props: { className?: string }) => ReactNode;
|
|
3481
3734
|
value: number;
|
|
3482
|
-
onChange: (value: number) => void;
|
|
3735
|
+
onChange: (value: number, meta?: ScrubInputChangeMeta) => void;
|
|
3483
3736
|
mixed?: boolean;
|
|
3484
3737
|
min?: number;
|
|
3485
3738
|
max?: number;
|
|
@@ -3512,7 +3765,7 @@ function BlendModeMenu({
|
|
|
3512
3765
|
onStyleChange,
|
|
3513
3766
|
}: {
|
|
3514
3767
|
styles: Record<string, string>;
|
|
3515
|
-
onStyleChange:
|
|
3768
|
+
onStyleChange: StyleChangeHandler;
|
|
3516
3769
|
}) {
|
|
3517
3770
|
const [open, setOpen] = useState(false);
|
|
3518
3771
|
const blendMode = optionValue(
|
|
@@ -3520,8 +3773,16 @@ function BlendModeMenu({
|
|
|
3520
3773
|
styles.mixBlendMode || "normal",
|
|
3521
3774
|
"normal",
|
|
3522
3775
|
);
|
|
3523
|
-
|
|
3524
|
-
|
|
3776
|
+
// Recognize the Mixed sentinel BEFORE optionValue's fallback maps it to
|
|
3777
|
+
// "normal" — a mixed selection must not check a wrong concrete mode.
|
|
3778
|
+
// Isolation only disambiguates pass-through vs normal, so it only makes the
|
|
3779
|
+
// state mixed when the blend mode itself resolves to normal.
|
|
3780
|
+
const blendModeMixed =
|
|
3781
|
+
isMixedValue(styles.mixBlendMode) ||
|
|
3782
|
+
(blendMode === "normal" && isMixedValue(styles.isolation));
|
|
3783
|
+
const selectedBlendMode = blendModeMixed
|
|
3784
|
+
? MIXED_VALUE
|
|
3785
|
+
: blendMode === "normal" && styles.isolation !== "isolate"
|
|
3525
3786
|
? "pass-through"
|
|
3526
3787
|
: blendMode;
|
|
3527
3788
|
const options = [
|
|
@@ -3565,6 +3826,20 @@ function BlendModeMenu({
|
|
|
3565
3826
|
sideOffset={8}
|
|
3566
3827
|
className="z-[100010] w-48 rounded-xl border-[var(--design-editor-control-border)] bg-[var(--design-editor-panel-bg)] p-1 text-[13px] text-foreground shadow-2xl"
|
|
3567
3828
|
>
|
|
3829
|
+
{blendModeMixed ? (
|
|
3830
|
+
<>
|
|
3831
|
+
{/* Placeholder state for a mixed selection: the check sits next to
|
|
3832
|
+
"Mixed" instead of a wrong concrete mode. Picking any option
|
|
3833
|
+
below applies it to every selected object. */}
|
|
3834
|
+
<div className="flex h-9 items-center gap-3 rounded-md px-3 text-[13px] text-muted-foreground">
|
|
3835
|
+
<span className="flex size-4 shrink-0 items-center justify-center">
|
|
3836
|
+
<IconCheck className="size-4" />
|
|
3837
|
+
</span>
|
|
3838
|
+
<span>{MIXED_VALUE}</span>
|
|
3839
|
+
</div>
|
|
3840
|
+
<DropdownMenuSeparator />
|
|
3841
|
+
</>
|
|
3842
|
+
) : null}
|
|
3568
3843
|
{options.map((option) => (
|
|
3569
3844
|
<DropdownMenuItem
|
|
3570
3845
|
key={option.value}
|
|
@@ -3600,7 +3875,7 @@ function StrokeLayerControl({
|
|
|
3600
3875
|
color: string;
|
|
3601
3876
|
width: string;
|
|
3602
3877
|
styleValue: string;
|
|
3603
|
-
onStyleChange:
|
|
3878
|
+
onStyleChange: StyleChangeHandler;
|
|
3604
3879
|
onRemove: () => void;
|
|
3605
3880
|
}) {
|
|
3606
3881
|
const t = useT();
|
|
@@ -3631,7 +3906,9 @@ function StrokeLayerControl({
|
|
|
3631
3906
|
<ColorInput
|
|
3632
3907
|
label=""
|
|
3633
3908
|
value={cssColorOrFallback(color, "#000000")}
|
|
3634
|
-
onChange={(value) =>
|
|
3909
|
+
onChange={(value, meta) =>
|
|
3910
|
+
onStyleChange(`${prefix}Color`, value, meta)
|
|
3911
|
+
}
|
|
3635
3912
|
/>
|
|
3636
3913
|
</div>
|
|
3637
3914
|
<SectionIconButton
|
|
@@ -3641,11 +3918,25 @@ function StrokeLayerControl({
|
|
|
3641
3918
|
: t("editPanel.labels.showLayer")
|
|
3642
3919
|
}
|
|
3643
3920
|
onClick={() => {
|
|
3921
|
+
// Hide/show by zeroing the stroke color's alpha (preserving its
|
|
3922
|
+
// RGB channels — same durable, comment-free technique as the
|
|
3923
|
+
// fill visibility toggle) instead of forcing borderStyle to
|
|
3924
|
+
// "none"/"solid". Writing "none" would lose a dashed/dotted
|
|
3925
|
+
// style permanently, since there is no round-trippable "unset"
|
|
3926
|
+
// for that keyword once it's overwritten.
|
|
3927
|
+
const parsed = parseCssColor(color);
|
|
3644
3928
|
if (visible) {
|
|
3645
|
-
onStyleChange(
|
|
3929
|
+
onStyleChange(
|
|
3930
|
+
`${prefix}Color`,
|
|
3931
|
+
parsed ? rgbaToCss(withColorOpacity(parsed, 0)) : "transparent",
|
|
3932
|
+
);
|
|
3646
3933
|
return;
|
|
3647
3934
|
}
|
|
3648
|
-
|
|
3935
|
+
const restoredColor = parsed
|
|
3936
|
+
? rgbaToCss(withColorOpacity(parsed, 100))
|
|
3937
|
+
: "#000000";
|
|
3938
|
+
onStyleChange(`${prefix}Color`, restoredColor);
|
|
3939
|
+
if (styleValue === "none") onStyleChange(`${prefix}Style`, "solid");
|
|
3649
3940
|
onStyleChange(
|
|
3650
3941
|
`${prefix}Width`,
|
|
3651
3942
|
width === "0px" ? "1px" : width || "1px",
|
|
@@ -3688,10 +3979,11 @@ function StrokeLayerControl({
|
|
|
3688
3979
|
ariaLabel={t("editPanel.labels.weight")}
|
|
3689
3980
|
icon={IconBorderStyle}
|
|
3690
3981
|
value={cssLengthNumber(width)}
|
|
3691
|
-
onChange={(value) =>
|
|
3982
|
+
onChange={(value, meta) =>
|
|
3692
3983
|
onStyleChange(
|
|
3693
3984
|
`${prefix}Width`,
|
|
3694
|
-
`${Math.max(0,
|
|
3985
|
+
`${Math.max(0, roundToOneDecimal(value))}px`,
|
|
3986
|
+
meta,
|
|
3695
3987
|
)
|
|
3696
3988
|
}
|
|
3697
3989
|
unit="px"
|
|
@@ -3789,7 +4081,9 @@ function serializeShadowLayers(layers: ShadowLayer[]) {
|
|
|
3789
4081
|
`${Math.round(layer.x)}px`,
|
|
3790
4082
|
`${Math.round(layer.y)}px`,
|
|
3791
4083
|
`${Math.max(0, Math.round(layer.blur))}px`,
|
|
3792
|
-
|
|
4084
|
+
// Spread radius may legitimately be negative for either inset or
|
|
4085
|
+
// drop shadows — only blur-radius is clamped to >= 0 in CSS.
|
|
4086
|
+
`${Math.round(layer.spread)}px`,
|
|
3793
4087
|
layer.color,
|
|
3794
4088
|
]
|
|
3795
4089
|
.filter(Boolean)
|
|
@@ -3833,7 +4127,7 @@ function ShadowEffectRow({
|
|
|
3833
4127
|
}: {
|
|
3834
4128
|
layer: ShadowLayer;
|
|
3835
4129
|
index: number;
|
|
3836
|
-
onChange: (patch: Partial<ShadowLayer
|
|
4130
|
+
onChange: (patch: Partial<ShadowLayer>, meta?: StyleChangeMeta) => void;
|
|
3837
4131
|
onRemove: () => void;
|
|
3838
4132
|
onToggleVisibility: () => void;
|
|
3839
4133
|
}) {
|
|
@@ -3912,7 +4206,9 @@ function ShadowEffectRow({
|
|
|
3912
4206
|
<ScrubInput
|
|
3913
4207
|
label="X"
|
|
3914
4208
|
value={layer.x}
|
|
3915
|
-
onChange={(value) =>
|
|
4209
|
+
onChange={(value, meta) =>
|
|
4210
|
+
onChange({ x: value }, { phase: meta?.phase })
|
|
4211
|
+
}
|
|
3916
4212
|
unit="px"
|
|
3917
4213
|
precision={1}
|
|
3918
4214
|
inputClassName="h-6"
|
|
@@ -3920,7 +4216,9 @@ function ShadowEffectRow({
|
|
|
3920
4216
|
<ScrubInput
|
|
3921
4217
|
label="Y"
|
|
3922
4218
|
value={layer.y}
|
|
3923
|
-
onChange={(value) =>
|
|
4219
|
+
onChange={(value, meta) =>
|
|
4220
|
+
onChange({ y: value }, { phase: meta?.phase })
|
|
4221
|
+
}
|
|
3924
4222
|
unit="px"
|
|
3925
4223
|
precision={1}
|
|
3926
4224
|
inputClassName="h-6"
|
|
@@ -3928,7 +4226,9 @@ function ShadowEffectRow({
|
|
|
3928
4226
|
<ScrubInput
|
|
3929
4227
|
label={t("editPanel.labels.blur")}
|
|
3930
4228
|
value={layer.blur}
|
|
3931
|
-
onChange={(value
|
|
4229
|
+
onChange={(value, meta) =>
|
|
4230
|
+
onChange({ blur: Math.max(0, value) }, { phase: meta?.phase })
|
|
4231
|
+
}
|
|
3932
4232
|
unit="px"
|
|
3933
4233
|
min={0}
|
|
3934
4234
|
precision={1}
|
|
@@ -3937,11 +4237,14 @@ function ShadowEffectRow({
|
|
|
3937
4237
|
<ScrubInput
|
|
3938
4238
|
label={t("editPanel.labels.spread")}
|
|
3939
4239
|
value={layer.spread}
|
|
3940
|
-
|
|
3941
|
-
|
|
4240
|
+
// Spread radius is valid negative for both inset AND drop
|
|
4241
|
+
// (non-inset) shadows in real CSS — negative spread shrinks
|
|
4242
|
+
// the shadow smaller than the box before blurring, a common
|
|
4243
|
+
// technique. Only blur-radius must stay >= 0.
|
|
4244
|
+
onChange={(value, meta) =>
|
|
4245
|
+
onChange({ spread: value }, { phase: meta?.phase })
|
|
3942
4246
|
}
|
|
3943
4247
|
unit="px"
|
|
3944
|
-
min={layer.inset ? undefined : 0}
|
|
3945
4248
|
precision={1}
|
|
3946
4249
|
inputClassName="h-6"
|
|
3947
4250
|
/>
|
|
@@ -3949,7 +4252,7 @@ function ShadowEffectRow({
|
|
|
3949
4252
|
<ColorInput
|
|
3950
4253
|
label={t("editPanel.labels.color")}
|
|
3951
4254
|
value={cssColorOrFallback(layer.color, "rgba(0, 0, 0, 0.25)")}
|
|
3952
|
-
onChange={(value) => onChange({ color: value })}
|
|
4255
|
+
onChange={(value, meta) => onChange({ color: value }, meta)}
|
|
3953
4256
|
/>
|
|
3954
4257
|
</div>
|
|
3955
4258
|
</PopoverContent>
|
|
@@ -3964,8 +4267,8 @@ function PageProperties({
|
|
|
3964
4267
|
onStylesChange,
|
|
3965
4268
|
}: {
|
|
3966
4269
|
styles: Record<string, string>;
|
|
3967
|
-
onStyleChange:
|
|
3968
|
-
onStylesChange?:
|
|
4270
|
+
onStyleChange: StyleChangeHandler;
|
|
4271
|
+
onStylesChange?: StylesChangeHandler;
|
|
3969
4272
|
}) {
|
|
3970
4273
|
const t = useT();
|
|
3971
4274
|
const baseFontFamilyOptions = FONT_FAMILY_OPTIONS.map((option) => ({
|
|
@@ -3991,7 +4294,7 @@ function PageProperties({
|
|
|
3991
4294
|
<ColorInput
|
|
3992
4295
|
label={t("editPanel.labels.background")}
|
|
3993
4296
|
value={styles.backgroundColor || ""}
|
|
3994
|
-
onChange={(v) => onStyleChange("backgroundColor", v)}
|
|
4297
|
+
onChange={(v, meta) => onStyleChange("backgroundColor", v, meta)}
|
|
3995
4298
|
backgroundImage={styles.backgroundImage}
|
|
3996
4299
|
backgroundSize={styles.backgroundSize}
|
|
3997
4300
|
backgroundRepeat={styles.backgroundRepeat}
|
|
@@ -4032,7 +4335,7 @@ function TypographyProperties({
|
|
|
4032
4335
|
onStyleChange,
|
|
4033
4336
|
}: {
|
|
4034
4337
|
element: ElementInfo;
|
|
4035
|
-
onStyleChange:
|
|
4338
|
+
onStyleChange: StyleChangeHandler;
|
|
4036
4339
|
}) {
|
|
4037
4340
|
const t = useT();
|
|
4038
4341
|
const styles = element.computedStyles;
|
|
@@ -4058,16 +4361,48 @@ function TypographyProperties({
|
|
|
4058
4361
|
}));
|
|
4059
4362
|
const textAlign = styles.textAlign || "left";
|
|
4060
4363
|
|
|
4061
|
-
//
|
|
4062
|
-
//
|
|
4063
|
-
//
|
|
4064
|
-
//
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4364
|
+
// Mixed-selection guards: a multi-selection with differing values injects
|
|
4365
|
+
// the MIXED_VALUE sentinel string into these computedStyles fields (see
|
|
4366
|
+
// mixedElementFromSelection/sameOrMixed). Parsing that sentinel with
|
|
4367
|
+
// parseNumericValue/Number() silently yields 0/NaN-fallback instead of
|
|
4368
|
+
// reflecting "differs across selection", which previously showed a
|
|
4369
|
+
// fabricated 0 (size), 1.2 (line-height), or blank (tracking) rather than
|
|
4370
|
+
// the Mixed state ScrubInput already knows how to render — same pattern as
|
|
4371
|
+
// the rotation field above.
|
|
4372
|
+
const fontWeightIsMixed = isMixedValue(styles.fontWeight);
|
|
4373
|
+
const fontSizeIsMixed = isMixedValue(styles.fontSize);
|
|
4374
|
+
const lineHeightIsMixed = isMixedValue(styles.lineHeight);
|
|
4375
|
+
const letterSpacingIsMixed = isMixedValue(styles.letterSpacing);
|
|
4376
|
+
|
|
4377
|
+
// M1 · Text resizing mode (auto-width / auto-height / fixed). the design
|
|
4378
|
+
// editor's text nodes always expose this segment. Read authored
|
|
4379
|
+
// (inlineStyles) values, not computed ones: an absolutely-positioned
|
|
4380
|
+
// element's computed width/height always resolve to a real px value even
|
|
4381
|
+
// when the author never set them, so "auto" and "a specific 200px" were
|
|
4382
|
+
// indistinguishable before — every text node misread as "fixed". Falls
|
|
4383
|
+
// back to the computed-style heuristic for older payloads that predate
|
|
4384
|
+
// inlineStyles. Convention (matches DesignEditor primitive creation and
|
|
4385
|
+
// setResizeMode below): auto-width = width unset/max-content + pre-wrap;
|
|
4386
|
+
// auto-height = fixed width + height unset/auto; fixed = both fixed. A
|
|
4387
|
+
// drag-created box (display:flex, explicit width+height, whiteSpace
|
|
4388
|
+
// unset→normal) correctly falls through to "fixed".
|
|
4389
|
+
const authoredResizeWidth = authoredStyleValue(element, "width");
|
|
4390
|
+
const authoredResizeHeight = authoredStyleValue(element, "height");
|
|
4391
|
+
const authoredWhiteSpace = authoredStyleValue(element, "whiteSpace");
|
|
4392
|
+
const hasInlineStyleInfo = Boolean(element.inlineStyles);
|
|
4393
|
+
const widthIsAuto = hasInlineStyleInfo
|
|
4394
|
+
? !authoredResizeWidth || authoredResizeWidth === "max-content"
|
|
4395
|
+
: !styles.width ||
|
|
4396
|
+
styles.width === "auto" ||
|
|
4397
|
+
styles.width === "max-content";
|
|
4398
|
+
const heightIsAuto = hasInlineStyleInfo
|
|
4399
|
+
? !authoredResizeHeight || authoredResizeHeight === "auto"
|
|
4400
|
+
: !styles.height || styles.height === "auto";
|
|
4401
|
+
const isPreWrapOrNoWrap = hasInlineStyleInfo
|
|
4402
|
+
? authoredWhiteSpace === "pre-wrap" || authoredWhiteSpace === "nowrap"
|
|
4403
|
+
: styles.whiteSpace === "nowrap";
|
|
4069
4404
|
const resizeMode: TextResizeMode =
|
|
4070
|
-
widthIsAuto &&
|
|
4405
|
+
widthIsAuto && isPreWrapOrNoWrap
|
|
4071
4406
|
? "auto-width"
|
|
4072
4407
|
: !heightIsAuto && !widthIsAuto
|
|
4073
4408
|
? "fixed"
|
|
@@ -4076,9 +4411,9 @@ function TypographyProperties({
|
|
|
4076
4411
|
const currentHeight = styles.height && !heightIsAuto ? styles.height : "48px";
|
|
4077
4412
|
const setResizeMode = (mode: TextResizeMode) => {
|
|
4078
4413
|
if (mode === "auto-width") {
|
|
4079
|
-
onStyleChange("width", "
|
|
4414
|
+
onStyleChange("width", "max-content");
|
|
4080
4415
|
onStyleChange("height", "auto");
|
|
4081
|
-
onStyleChange("whiteSpace", "
|
|
4416
|
+
onStyleChange("whiteSpace", "pre-wrap");
|
|
4082
4417
|
} else if (mode === "auto-height") {
|
|
4083
4418
|
onStyleChange("width", currentWidth);
|
|
4084
4419
|
onStyleChange("height", "auto");
|
|
@@ -4090,36 +4425,44 @@ function TypographyProperties({
|
|
|
4090
4425
|
}
|
|
4091
4426
|
};
|
|
4092
4427
|
|
|
4093
|
-
// M2 · Vertical text alignment (top / middle / bottom). For auto-layout
|
|
4094
|
-
//
|
|
4095
|
-
//
|
|
4096
|
-
//
|
|
4428
|
+
// M2 · Vertical text alignment (top / middle / bottom). For an auto-layout
|
|
4429
|
+
// text container (display:flex) this maps to whichever flex property
|
|
4430
|
+
// controls the vertical/cross axis — justifyContent when flex-direction is
|
|
4431
|
+
// column, alignItems when row (the DesignEditor drag-created default; see
|
|
4432
|
+
// primitive creation, which sets display:flex + alignItems:center with no
|
|
4433
|
+
// explicit flex-direction, i.e. row). For any non-flex display,
|
|
4434
|
+
// `verticalAlign` is a no-op: it only affects how an inline/inline-block/
|
|
4435
|
+
// table-cell box sits relative to *sibling* line-box content, not how its
|
|
4436
|
+
// own content sits within its own box — exactly the case for point text
|
|
4437
|
+
// (inline-block). So instead of ever writing verticalAlign, convert the
|
|
4438
|
+
// element to flex the same way a drag-created box is authored, then read/
|
|
4439
|
+
// write through the row-axis property (alignItems) like that default.
|
|
4097
4440
|
const display = (styles.display || "").toLowerCase();
|
|
4098
4441
|
const isFlexText = display.includes("flex");
|
|
4099
|
-
const
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4442
|
+
const isColumnFlexText =
|
|
4443
|
+
isFlexText && styles.flexDirection?.includes("column");
|
|
4444
|
+
const verticalAlignSourceProp = isColumnFlexText
|
|
4445
|
+
? styles.justifyContent
|
|
4446
|
+
: styles.alignItems;
|
|
4447
|
+
const verticalAlign = !isFlexText
|
|
4448
|
+
? "top"
|
|
4449
|
+
: verticalAlignSourceProp === "center"
|
|
4106
4450
|
? "middle"
|
|
4107
|
-
:
|
|
4451
|
+
: verticalAlignSourceProp === "flex-end"
|
|
4108
4452
|
? "bottom"
|
|
4109
4453
|
: "top";
|
|
4110
4454
|
const setVerticalAlign = (mode: "top" | "middle" | "bottom") => {
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
}
|
|
4455
|
+
// Converting a non-flex element matches the drag-created fixed-size text
|
|
4456
|
+
// box exactly: display:flex, default (row) flex-direction — so the
|
|
4457
|
+
// vertical axis is alignItems, same as the pre-existing row case below.
|
|
4458
|
+
if (!isFlexText) onStyleChange("display", "flex");
|
|
4459
|
+
const cssValue =
|
|
4460
|
+
mode === "middle"
|
|
4461
|
+
? "center"
|
|
4462
|
+
: mode === "bottom"
|
|
4463
|
+
? "flex-end"
|
|
4464
|
+
: "flex-start";
|
|
4465
|
+
onStyleChange(isColumnFlexText ? "justifyContent" : "alignItems", cssValue);
|
|
4123
4466
|
};
|
|
4124
4467
|
|
|
4125
4468
|
return (
|
|
@@ -4158,13 +4501,22 @@ function TypographyProperties({
|
|
|
4158
4501
|
{/* Row 2: weight + size side by side */}
|
|
4159
4502
|
<div className="grid grid-cols-2 gap-1.5">
|
|
4160
4503
|
<Select
|
|
4161
|
-
value={styles.fontWeight || "400"}
|
|
4504
|
+
value={fontWeightIsMixed ? MIXED_VALUE : styles.fontWeight || "400"}
|
|
4162
4505
|
onValueChange={(v) => onStyleChange("fontWeight", v)}
|
|
4163
4506
|
>
|
|
4164
4507
|
<SelectTrigger className="h-6 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
4165
4508
|
<SelectValue />
|
|
4166
4509
|
</SelectTrigger>
|
|
4167
4510
|
<SelectContent>
|
|
4511
|
+
{fontWeightIsMixed ? (
|
|
4512
|
+
<SelectItem
|
|
4513
|
+
value={MIXED_VALUE}
|
|
4514
|
+
disabled
|
|
4515
|
+
className="!text-[11px] text-muted-foreground"
|
|
4516
|
+
>
|
|
4517
|
+
{MIXED_VALUE}
|
|
4518
|
+
</SelectItem>
|
|
4519
|
+
) : null}
|
|
4168
4520
|
{fontWeightOptions.map((opt) => (
|
|
4169
4521
|
<SelectItem
|
|
4170
4522
|
key={opt.value}
|
|
@@ -4180,9 +4532,20 @@ function TypographyProperties({
|
|
|
4180
4532
|
label={t("editPanel.labels.size")}
|
|
4181
4533
|
ariaLabel={t("editPanel.labels.size")}
|
|
4182
4534
|
icon={IconLetterCase}
|
|
4183
|
-
value={
|
|
4184
|
-
|
|
4185
|
-
|
|
4535
|
+
value={
|
|
4536
|
+
fontSizeIsMixed
|
|
4537
|
+
? 0
|
|
4538
|
+
: styles.fontSize
|
|
4539
|
+
? parseNumericValue(styles.fontSize)
|
|
4540
|
+
: 16
|
|
4541
|
+
}
|
|
4542
|
+
mixed={fontSizeIsMixed}
|
|
4543
|
+
onChange={(value, meta) =>
|
|
4544
|
+
onStyleChange(
|
|
4545
|
+
"fontSize",
|
|
4546
|
+
`${Math.max(1, roundToOneDecimal(value))}px`,
|
|
4547
|
+
meta,
|
|
4548
|
+
)
|
|
4186
4549
|
}
|
|
4187
4550
|
unit="px"
|
|
4188
4551
|
min={1}
|
|
@@ -4199,9 +4562,14 @@ function TypographyProperties({
|
|
|
4199
4562
|
label={t("editPanel.labels.lineHeight")}
|
|
4200
4563
|
ariaLabel={t("editPanel.labels.lineHeight")}
|
|
4201
4564
|
icon={IconLineHeight}
|
|
4202
|
-
value={
|
|
4203
|
-
|
|
4204
|
-
|
|
4565
|
+
value={
|
|
4566
|
+
lineHeightIsMixed
|
|
4567
|
+
? 0
|
|
4568
|
+
: resolveLineHeight(styles.lineHeight, styles.fontSize)
|
|
4569
|
+
}
|
|
4570
|
+
mixed={lineHeightIsMixed}
|
|
4571
|
+
onChange={(value, meta) =>
|
|
4572
|
+
onStyleChange("lineHeight", String(Math.max(0.1, value)), meta)
|
|
4205
4573
|
}
|
|
4206
4574
|
min={0.1}
|
|
4207
4575
|
step={0.1}
|
|
@@ -4215,9 +4583,16 @@ function TypographyProperties({
|
|
|
4215
4583
|
ariaLabel={t("editPanel.labels.tracking")}
|
|
4216
4584
|
icon={IconLetterSpacing}
|
|
4217
4585
|
value={
|
|
4218
|
-
|
|
4586
|
+
letterSpacingIsMixed
|
|
4587
|
+
? 0
|
|
4588
|
+
: styles.letterSpacing
|
|
4589
|
+
? parseNumericValue(styles.letterSpacing)
|
|
4590
|
+
: 0
|
|
4591
|
+
}
|
|
4592
|
+
mixed={letterSpacingIsMixed}
|
|
4593
|
+
onChange={(value, meta) =>
|
|
4594
|
+
onStyleChange("letterSpacing", `${value}px`, meta)
|
|
4219
4595
|
}
|
|
4220
|
-
onChange={(value) => onStyleChange("letterSpacing", `${value}px`)}
|
|
4221
4596
|
unit="px"
|
|
4222
4597
|
precision={1}
|
|
4223
4598
|
className="gap-0"
|
|
@@ -4299,8 +4674,8 @@ function FlexContainerControls({
|
|
|
4299
4674
|
onStylesChange,
|
|
4300
4675
|
}: {
|
|
4301
4676
|
element: ElementInfo;
|
|
4302
|
-
onStyleChange:
|
|
4303
|
-
onStylesChange?:
|
|
4677
|
+
onStyleChange: StyleChangeHandler;
|
|
4678
|
+
onStylesChange?: StylesChangeHandler;
|
|
4304
4679
|
}) {
|
|
4305
4680
|
const t = useT();
|
|
4306
4681
|
const styles = element.computedStyles;
|
|
@@ -4484,7 +4859,7 @@ function FlexChildControls({
|
|
|
4484
4859
|
onStyleChange,
|
|
4485
4860
|
}: {
|
|
4486
4861
|
element: ElementInfo;
|
|
4487
|
-
onStyleChange:
|
|
4862
|
+
onStyleChange: StyleChangeHandler;
|
|
4488
4863
|
}) {
|
|
4489
4864
|
const t = useT();
|
|
4490
4865
|
const styles = element.computedStyles;
|
|
@@ -4536,7 +4911,7 @@ function GridChildControls({
|
|
|
4536
4911
|
onStyleChange,
|
|
4537
4912
|
}: {
|
|
4538
4913
|
element: ElementInfo;
|
|
4539
|
-
onStyleChange:
|
|
4914
|
+
onStyleChange: StyleChangeHandler;
|
|
4540
4915
|
}) {
|
|
4541
4916
|
const t = useT();
|
|
4542
4917
|
const styles = element.computedStyles;
|
|
@@ -4578,8 +4953,8 @@ function LayoutContextProperties({
|
|
|
4578
4953
|
onStylesChange,
|
|
4579
4954
|
}: {
|
|
4580
4955
|
element: ElementInfo;
|
|
4581
|
-
onStyleChange:
|
|
4582
|
-
onStylesChange?:
|
|
4956
|
+
onStyleChange: StyleChangeHandler;
|
|
4957
|
+
onStylesChange?: StylesChangeHandler;
|
|
4583
4958
|
}) {
|
|
4584
4959
|
const t = useT();
|
|
4585
4960
|
const flexChild = isParentFlex(element);
|
|
@@ -4697,7 +5072,7 @@ function LayoutGuideProperties({
|
|
|
4697
5072
|
onStyleChange,
|
|
4698
5073
|
}: {
|
|
4699
5074
|
element: ElementInfo;
|
|
4700
|
-
onStyleChange:
|
|
5075
|
+
onStyleChange: StyleChangeHandler;
|
|
4701
5076
|
}) {
|
|
4702
5077
|
const styles = element.computedStyles;
|
|
4703
5078
|
const active = hasLayoutGuide(styles);
|
|
@@ -4801,43 +5176,96 @@ function ExportPreview({ element }: { element: ElementInfo | null }) {
|
|
|
4801
5176
|
);
|
|
4802
5177
|
}
|
|
4803
5178
|
|
|
5179
|
+
/**
|
|
5180
|
+
* Reads an authored (as-written) offset/size style, preferring the bridge's
|
|
5181
|
+
* `inlineStyles` capture (raw `el.style.<prop>` — "auto"/"" reliably means
|
|
5182
|
+
* unset) over `computedStyles` (which always resolves to a px value for
|
|
5183
|
+
* absolutely-positioned elements even when the author never set the
|
|
5184
|
+
* property, making "unset" indistinguishable from "0px"). Falls back to the
|
|
5185
|
+
* computed value for older payloads that predate `inlineStyles`.
|
|
5186
|
+
*/
|
|
5187
|
+
export function authoredStyleValue(
|
|
5188
|
+
element: ElementInfo,
|
|
5189
|
+
property: string,
|
|
5190
|
+
): string | undefined {
|
|
5191
|
+
const inline = element.inlineStyles?.[property];
|
|
5192
|
+
if (inline !== undefined) return inline === "auto" ? "" : inline;
|
|
5193
|
+
return element.computedStyles[property];
|
|
5194
|
+
}
|
|
5195
|
+
|
|
4804
5196
|
/** Position, size, and spacing properties */
|
|
4805
5197
|
function PositionLayoutProperties({
|
|
4806
5198
|
element,
|
|
4807
5199
|
onStyleChange,
|
|
4808
5200
|
}: {
|
|
4809
5201
|
element: ElementInfo;
|
|
4810
|
-
onStyleChange:
|
|
5202
|
+
onStyleChange: StyleChangeHandler;
|
|
4811
5203
|
}) {
|
|
4812
5204
|
const t = useT();
|
|
4813
5205
|
const styles = element.computedStyles;
|
|
4814
5206
|
const constrainedPosition =
|
|
4815
5207
|
styles.position === "absolute" || styles.position === "fixed";
|
|
4816
|
-
// Reflect the active packing in the alignment segments (the design editor
|
|
4817
|
-
// current alignment)
|
|
4818
|
-
|
|
4819
|
-
|
|
5208
|
+
// Reflect the active packing in the alignment segments (the design editor
|
|
5209
|
+
// highlights the current alignment) — these buttons align the selected
|
|
5210
|
+
// element's own children, the same operation FlexContainerControls' full
|
|
5211
|
+
// AutoLayoutMatrix alignment grid performs. A column-direction flex
|
|
5212
|
+
// container swaps which CSS axis is "horizontal"/"vertical" from the
|
|
5213
|
+
// user's point of view: justifyContent packs the main (here: vertical)
|
|
5214
|
+
// axis and alignItems packs the cross (horizontal) axis, so reading/
|
|
5215
|
+
// writing must go through autoLayoutAlignmentFromStyles /
|
|
5216
|
+
// horizontalToJustify / verticalToAlign like the matrix control does,
|
|
5217
|
+
// instead of assuming justifyContent is always horizontal.
|
|
5218
|
+
const positionFlexDirection: AutoLayoutMatrixValue["direction"] =
|
|
5219
|
+
styles.flexDirection?.includes("column") ? "vertical" : "horizontal";
|
|
5220
|
+
const positionAlignment = autoLayoutAlignmentFromStyles(
|
|
5221
|
+
styles,
|
|
5222
|
+
positionFlexDirection,
|
|
5223
|
+
);
|
|
5224
|
+
const alignH = positionAlignment.horizontal;
|
|
5225
|
+
const alignV = positionAlignment.vertical;
|
|
5226
|
+
const handlePositionAlignH = (value: AlignmentMatrixValue["horizontal"]) => {
|
|
5227
|
+
onStyleChange(
|
|
5228
|
+
positionFlexDirection === "vertical" ? "alignItems" : "justifyContent",
|
|
5229
|
+
horizontalToJustify(value),
|
|
5230
|
+
);
|
|
5231
|
+
};
|
|
5232
|
+
const handlePositionAlignV = (value: AlignmentMatrixValue["vertical"]) => {
|
|
5233
|
+
onStyleChange(
|
|
5234
|
+
positionFlexDirection === "vertical" ? "justifyContent" : "alignItems",
|
|
5235
|
+
verticalToAlign(value),
|
|
5236
|
+
);
|
|
5237
|
+
};
|
|
5238
|
+
// Authored (not computed) offsets: when inlineStyles is present, "auto"/absent
|
|
5239
|
+
// is treated as truly unset instead of the computed px fallback, so a plain
|
|
5240
|
+
// top-left-anchored element reads as "left" rather than always "left-right".
|
|
5241
|
+
const authoredLeft = authoredStyleValue(element, "left");
|
|
5242
|
+
const authoredRight = authoredStyleValue(element, "right");
|
|
5243
|
+
const authoredTop = authoredStyleValue(element, "top");
|
|
5244
|
+
const authoredBottom = authoredStyleValue(element, "bottom");
|
|
5245
|
+
const authoredWidth = authoredStyleValue(element, "width");
|
|
5246
|
+
const authoredHeight = authoredStyleValue(element, "height");
|
|
5247
|
+
const authoredTransform = authoredStyleValue(element, "transform");
|
|
4820
5248
|
const constraintsValue: ConstraintsValue = {
|
|
4821
5249
|
horizontal:
|
|
4822
5250
|
// Check scale before left+right: "scale" writes width:100% and clears
|
|
4823
5251
|
// left/right to auto, but legacy data may have 0px values that are truthy.
|
|
4824
|
-
|
|
5252
|
+
authoredWidth === "100%"
|
|
4825
5253
|
? "scale"
|
|
4826
|
-
:
|
|
5254
|
+
: authoredLeft && authoredRight
|
|
4827
5255
|
? "left-right"
|
|
4828
|
-
:
|
|
5256
|
+
: authoredRight
|
|
4829
5257
|
? "right"
|
|
4830
|
-
:
|
|
5258
|
+
: authoredTransform?.includes("translateX(-50%)")
|
|
4831
5259
|
? "center"
|
|
4832
5260
|
: "left",
|
|
4833
5261
|
vertical:
|
|
4834
|
-
|
|
5262
|
+
authoredHeight === "100%"
|
|
4835
5263
|
? "scale"
|
|
4836
|
-
:
|
|
5264
|
+
: authoredTop && authoredBottom
|
|
4837
5265
|
? "top-bottom"
|
|
4838
|
-
:
|
|
5266
|
+
: authoredBottom
|
|
4839
5267
|
? "bottom"
|
|
4840
|
-
:
|
|
5268
|
+
: authoredTransform?.includes("translateY(-50%)")
|
|
4841
5269
|
? "center"
|
|
4842
5270
|
: "top",
|
|
4843
5271
|
};
|
|
@@ -4854,7 +5282,7 @@ function PositionLayoutProperties({
|
|
|
4854
5282
|
const tyValue = value.vertical === "center" ? "-50%" : null;
|
|
4855
5283
|
// Start from the current transform, apply X, then apply Y on top.
|
|
4856
5284
|
const transformAfterX = mergeTranslateFunction(
|
|
4857
|
-
|
|
5285
|
+
authoredTransform,
|
|
4858
5286
|
"X",
|
|
4859
5287
|
txValue,
|
|
4860
5288
|
);
|
|
@@ -4867,7 +5295,7 @@ function PositionLayoutProperties({
|
|
|
4867
5295
|
if (value.horizontal === "left") {
|
|
4868
5296
|
onStyleChange(
|
|
4869
5297
|
"left",
|
|
4870
|
-
|
|
5298
|
+
authoredLeft || `${Math.round(element.boundingRect.x)}px`,
|
|
4871
5299
|
);
|
|
4872
5300
|
onStyleChange("right", "auto");
|
|
4873
5301
|
} else if (value.horizontal === "right") {
|
|
@@ -4876,7 +5304,7 @@ function PositionLayoutProperties({
|
|
|
4876
5304
|
} else if (value.horizontal === "left-right") {
|
|
4877
5305
|
onStyleChange(
|
|
4878
5306
|
"left",
|
|
4879
|
-
|
|
5307
|
+
authoredLeft || `${Math.round(element.boundingRect.x)}px`,
|
|
4880
5308
|
);
|
|
4881
5309
|
onStyleChange("right", "0px");
|
|
4882
5310
|
} else if (value.horizontal === "center") {
|
|
@@ -4893,7 +5321,7 @@ function PositionLayoutProperties({
|
|
|
4893
5321
|
if (value.vertical === "top") {
|
|
4894
5322
|
onStyleChange(
|
|
4895
5323
|
"top",
|
|
4896
|
-
|
|
5324
|
+
authoredTop || `${Math.round(element.boundingRect.y)}px`,
|
|
4897
5325
|
);
|
|
4898
5326
|
onStyleChange("bottom", "auto");
|
|
4899
5327
|
} else if (value.vertical === "bottom") {
|
|
@@ -4902,7 +5330,7 @@ function PositionLayoutProperties({
|
|
|
4902
5330
|
} else if (value.vertical === "top-bottom") {
|
|
4903
5331
|
onStyleChange(
|
|
4904
5332
|
"top",
|
|
4905
|
-
|
|
5333
|
+
authoredTop || `${Math.round(element.boundingRect.y)}px`,
|
|
4906
5334
|
);
|
|
4907
5335
|
onStyleChange("bottom", "0px");
|
|
4908
5336
|
} else if (value.vertical === "center") {
|
|
@@ -4922,9 +5350,9 @@ function PositionLayoutProperties({
|
|
|
4922
5350
|
element.boundingRect.x,
|
|
4923
5351
|
element.boundingRect.y,
|
|
4924
5352
|
onStyleChange,
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
5353
|
+
authoredLeft,
|
|
5354
|
+
authoredTop,
|
|
5355
|
+
authoredTransform,
|
|
4928
5356
|
],
|
|
4929
5357
|
);
|
|
4930
5358
|
|
|
@@ -4955,21 +5383,21 @@ function PositionLayoutProperties({
|
|
|
4955
5383
|
<InspectorIconButton
|
|
4956
5384
|
label={t("editPanel.textAligns.left")}
|
|
4957
5385
|
active={alignH === "left"}
|
|
4958
|
-
onClick={() =>
|
|
5386
|
+
onClick={() => handlePositionAlignH("left")}
|
|
4959
5387
|
>
|
|
4960
5388
|
<IconLayoutAlignLeft className="size-3.5" />
|
|
4961
5389
|
</InspectorIconButton>
|
|
4962
5390
|
<InspectorIconButton
|
|
4963
5391
|
label={t("editPanel.textAligns.center")}
|
|
4964
5392
|
active={alignH === "center"}
|
|
4965
|
-
onClick={() =>
|
|
5393
|
+
onClick={() => handlePositionAlignH("center")}
|
|
4966
5394
|
>
|
|
4967
5395
|
<IconLayoutAlignCenter className="size-3.5" />
|
|
4968
5396
|
</InspectorIconButton>
|
|
4969
5397
|
<InspectorIconButton
|
|
4970
5398
|
label={t("editPanel.textAligns.right")}
|
|
4971
5399
|
active={alignH === "right"}
|
|
4972
|
-
onClick={() =>
|
|
5400
|
+
onClick={() => handlePositionAlignH("right")}
|
|
4973
5401
|
>
|
|
4974
5402
|
<IconLayoutAlignRight className="size-3.5" />
|
|
4975
5403
|
</InspectorIconButton>
|
|
@@ -4978,21 +5406,21 @@ function PositionLayoutProperties({
|
|
|
4978
5406
|
<InspectorIconButton
|
|
4979
5407
|
label={t("editPanel.alignSelfOptions.start")}
|
|
4980
5408
|
active={alignV === "top"}
|
|
4981
|
-
onClick={() =>
|
|
5409
|
+
onClick={() => handlePositionAlignV("top")}
|
|
4982
5410
|
>
|
|
4983
5411
|
<IconLayoutAlignTop className="size-3.5" />
|
|
4984
5412
|
</InspectorIconButton>
|
|
4985
5413
|
<InspectorIconButton
|
|
4986
5414
|
label={t("editPanel.alignSelfOptions.center")}
|
|
4987
5415
|
active={alignV === "middle"}
|
|
4988
|
-
onClick={() =>
|
|
5416
|
+
onClick={() => handlePositionAlignV("middle")}
|
|
4989
5417
|
>
|
|
4990
5418
|
<IconLayoutAlignMiddle className="size-3.5" />
|
|
4991
5419
|
</InspectorIconButton>
|
|
4992
5420
|
<InspectorIconButton
|
|
4993
5421
|
label={t("editPanel.alignSelfOptions.end")}
|
|
4994
5422
|
active={alignV === "bottom"}
|
|
4995
|
-
onClick={() =>
|
|
5423
|
+
onClick={() => handlePositionAlignV("bottom")}
|
|
4996
5424
|
>
|
|
4997
5425
|
<IconLayoutAlignBottom className="size-3.5" />
|
|
4998
5426
|
</InspectorIconButton>
|
|
@@ -5007,19 +5435,32 @@ function PositionLayoutProperties({
|
|
|
5007
5435
|
label="X"
|
|
5008
5436
|
ariaLabel="X-position"
|
|
5009
5437
|
tooltipLabel="X-position"
|
|
5010
|
-
value={
|
|
5438
|
+
value={
|
|
5439
|
+
isMixedValue(authoredLeft) ? MIXED_VALUE : authoredLeft || ""
|
|
5440
|
+
}
|
|
5011
5441
|
placeholder={element.boundingRect.x}
|
|
5012
5442
|
inputClassName="h-6"
|
|
5013
|
-
onChange={(v) =>
|
|
5443
|
+
onChange={(v, meta) => {
|
|
5444
|
+
// Typing X/Y on a static (non-positioned) element is a no-op on
|
|
5445
|
+
// canvas unless we first give it a position to offset from —
|
|
5446
|
+
// mirror handleConstraintsChange, which always sets
|
|
5447
|
+
// position:absolute (the convention canvas drag/resize and
|
|
5448
|
+
// primitive creation both use) before writing left/top.
|
|
5449
|
+
if (!constrainedPosition) onStyleChange("position", "absolute");
|
|
5450
|
+
onStyleChange("left", `${Math.round(v)}px`, meta);
|
|
5451
|
+
}}
|
|
5014
5452
|
/>
|
|
5015
5453
|
<ScrubStyleInput
|
|
5016
5454
|
label="Y"
|
|
5017
5455
|
ariaLabel="Y-position"
|
|
5018
5456
|
tooltipLabel="Y-position"
|
|
5019
|
-
value={
|
|
5457
|
+
value={isMixedValue(authoredTop) ? MIXED_VALUE : authoredTop || ""}
|
|
5020
5458
|
placeholder={element.boundingRect.y}
|
|
5021
5459
|
inputClassName="h-6"
|
|
5022
|
-
onChange={(v) =>
|
|
5460
|
+
onChange={(v, meta) => {
|
|
5461
|
+
if (!constrainedPosition) onStyleChange("position", "absolute");
|
|
5462
|
+
onStyleChange("top", `${Math.round(v)}px`, meta);
|
|
5463
|
+
}}
|
|
5023
5464
|
/>
|
|
5024
5465
|
<Tooltip>
|
|
5025
5466
|
<TooltipTrigger asChild>
|
|
@@ -5067,13 +5508,30 @@ function PositionLayoutProperties({
|
|
|
5067
5508
|
hideIcon={false}
|
|
5068
5509
|
icon={IconAngle}
|
|
5069
5510
|
labelClassName="[&>span]:sr-only"
|
|
5070
|
-
|
|
5511
|
+
// Detect the Mixed sentinel BEFORE parsing: parseRotationValue
|
|
5512
|
+
// would silently turn "Mixed" into 0 and render "0deg" instead
|
|
5513
|
+
// of the mixed state (mirrors the opacity field's guard).
|
|
5514
|
+
value={
|
|
5515
|
+
isMixedValue(styles.transform)
|
|
5516
|
+
? MIXED_VALUE
|
|
5517
|
+
: `${parseRotationValue(styles.transform)}deg`
|
|
5518
|
+
}
|
|
5071
5519
|
unit="deg"
|
|
5072
5520
|
inputClassName="h-6"
|
|
5073
|
-
onChange={(v) =>
|
|
5521
|
+
onChange={(v, meta) =>
|
|
5074
5522
|
onStyleChange(
|
|
5075
5523
|
"transform",
|
|
5076
|
-
|
|
5524
|
+
// From a mixed selection the sentinel is not a transform —
|
|
5525
|
+
// treat it as absent so the typed value applies cleanly to
|
|
5526
|
+
// every selected object instead of producing
|
|
5527
|
+
// "Mixed rotate(…)".
|
|
5528
|
+
mergeRotationValue(
|
|
5529
|
+
isMixedValue(styles.transform)
|
|
5530
|
+
? undefined
|
|
5531
|
+
: styles.transform,
|
|
5532
|
+
v,
|
|
5533
|
+
),
|
|
5534
|
+
meta,
|
|
5077
5535
|
)
|
|
5078
5536
|
}
|
|
5079
5537
|
/>
|
|
@@ -5110,26 +5568,26 @@ function FillProperties({
|
|
|
5110
5568
|
onStylesChange,
|
|
5111
5569
|
}: {
|
|
5112
5570
|
element: ElementInfo;
|
|
5113
|
-
onStyleChange:
|
|
5114
|
-
onStylesChange?:
|
|
5571
|
+
onStyleChange: StyleChangeHandler;
|
|
5572
|
+
onStylesChange?: StylesChangeHandler;
|
|
5115
5573
|
}) {
|
|
5116
5574
|
const t = useT();
|
|
5117
5575
|
const styles = element.computedStyles;
|
|
5118
|
-
const
|
|
5119
|
-
const fillProperty =
|
|
5120
|
-
const fillValue =
|
|
5576
|
+
const isTextFillElement = isTextElement(element);
|
|
5577
|
+
const fillProperty = isTextFillElement ? "color" : "backgroundColor";
|
|
5578
|
+
const fillValue = isTextFillElement
|
|
5121
5579
|
? styles.color || ""
|
|
5122
5580
|
: styles.backgroundColor || "";
|
|
5123
|
-
const backgroundLayers =
|
|
5581
|
+
const backgroundLayers = isTextFillElement
|
|
5124
5582
|
? []
|
|
5125
5583
|
: splitCssLayers(styles.backgroundImage || "");
|
|
5126
|
-
const backgroundSizeLayers =
|
|
5584
|
+
const backgroundSizeLayers = isTextFillElement
|
|
5127
5585
|
? []
|
|
5128
5586
|
: splitCssLayers(styles.backgroundSize || "");
|
|
5129
|
-
const backgroundRepeatLayers =
|
|
5587
|
+
const backgroundRepeatLayers = isTextFillElement
|
|
5130
5588
|
? []
|
|
5131
5589
|
: splitCssLayers(styles.backgroundRepeat || "");
|
|
5132
|
-
const backgroundPositionLayers =
|
|
5590
|
+
const backgroundPositionLayers = isTextFillElement
|
|
5133
5591
|
? []
|
|
5134
5592
|
: splitCssLayers(styles.backgroundPosition || "");
|
|
5135
5593
|
const fillIsMixed =
|
|
@@ -5138,39 +5596,36 @@ function FillProperties({
|
|
|
5138
5596
|
isMixedValue(styles.backgroundSize) ||
|
|
5139
5597
|
isMixedValue(styles.backgroundRepeat) ||
|
|
5140
5598
|
isMixedValue(styles.backgroundPosition);
|
|
5141
|
-
const hasBackgroundLayer = !
|
|
5599
|
+
const hasBackgroundLayer = !isTextFillElement && backgroundLayers.length > 0;
|
|
5142
5600
|
const hasVisibleFill =
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
// Non-destructive fill hide:
|
|
5146
|
-
//
|
|
5147
|
-
//
|
|
5148
|
-
//
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
//
|
|
5153
|
-
//
|
|
5154
|
-
//
|
|
5155
|
-
const [hiddenLayerStash, setHiddenLayerStash] = useState<
|
|
5156
|
-
Record<string, string>
|
|
5157
|
-
>({});
|
|
5158
|
-
const stashKey = `${elementIdentityKey(element)}:${fillProperty}`;
|
|
5601
|
+
isTextFillElement || colorHasVisibleAlpha(fillValue) || hasBackgroundLayer;
|
|
5602
|
+
|
|
5603
|
+
// Non-destructive fill hide: instead of stashing the pre-hide color in
|
|
5604
|
+
// React state (lost on unmount — e.g. deselect then reselect the same
|
|
5605
|
+
// element, since FillProperties only mounts while something is selected),
|
|
5606
|
+
// zero the alpha channel while preserving the RGB channels in the
|
|
5607
|
+
// persisted CSS itself: rgba(r,g,b,0) renders identically to fully
|
|
5608
|
+
// transparent, but getComputedStyle round-trips the r/g/b losslessly (CSS
|
|
5609
|
+
// color-list channels are real data, unlike comments — verified
|
|
5610
|
+
// separately: computed style strips comments but keeps rgba() channels).
|
|
5611
|
+
// Showing again just restores alpha to 1 using those same channels, so no
|
|
5612
|
+
// stash is required and the hide survives reselect/reload.
|
|
5159
5613
|
const isHidden = !colorHasVisibleAlpha(fillValue);
|
|
5160
5614
|
const handleFillVisibilityToggle = () => {
|
|
5615
|
+
const parsed = parseCssColor(fillValue);
|
|
5161
5616
|
if (isHidden) {
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5617
|
+
const restored = parsed
|
|
5618
|
+
? rgbaToCss(withColorOpacity(parsed, 100))
|
|
5619
|
+
: isTextFillElement
|
|
5620
|
+
? "#000000"
|
|
5621
|
+
: "#ffffff";
|
|
5165
5622
|
onStyleChange(fillProperty, restored);
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
delete next[stashKey];
|
|
5169
|
-
return next;
|
|
5170
|
-
});
|
|
5623
|
+
} else if (parsed) {
|
|
5624
|
+
onStyleChange(fillProperty, rgbaToCss(withColorOpacity(parsed, 0)));
|
|
5171
5625
|
} else {
|
|
5172
|
-
//
|
|
5173
|
-
|
|
5626
|
+
// Value wasn't a parseable color (e.g. already the literal
|
|
5627
|
+
// "transparent") — nothing to preserve, transparent is the best we
|
|
5628
|
+
// can do.
|
|
5174
5629
|
onStyleChange(fillProperty, "transparent");
|
|
5175
5630
|
}
|
|
5176
5631
|
};
|
|
@@ -5219,7 +5674,7 @@ function FillProperties({
|
|
|
5219
5674
|
);
|
|
5220
5675
|
return;
|
|
5221
5676
|
}
|
|
5222
|
-
if (
|
|
5677
|
+
if (isTextFillElement) {
|
|
5223
5678
|
onStyleChange(
|
|
5224
5679
|
"color",
|
|
5225
5680
|
cssColorOrFallback(styles.color, "#000000"),
|
|
@@ -5257,25 +5712,59 @@ function FillProperties({
|
|
|
5257
5712
|
</p>
|
|
5258
5713
|
) : hasVisibleFill ? (
|
|
5259
5714
|
<div className="space-y-1.5">
|
|
5260
|
-
{
|
|
5715
|
+
{isTextFillElement || colorHasVisibleAlpha(fillValue) ? (
|
|
5261
5716
|
/* design row: [swatch+hex trigger (flex-1)] [eye] [remove] */
|
|
5262
5717
|
<div className="group flex items-center gap-1.5">
|
|
5263
5718
|
<div className="min-w-0 flex-1">
|
|
5264
5719
|
<ColorInput
|
|
5265
5720
|
label=""
|
|
5266
5721
|
value={fillValue}
|
|
5267
|
-
onChange={(v) => onStyleChange(fillProperty, v)}
|
|
5268
|
-
|
|
5722
|
+
onChange={(v, meta) => onStyleChange(fillProperty, v, meta)}
|
|
5723
|
+
// Pass the real layer stack (not "") so that switching this
|
|
5724
|
+
// swatch's paint type to gradient/image composes a new
|
|
5725
|
+
// layer on top of any existing backgroundImage layers
|
|
5726
|
+
// (rendered as their own rows below) instead of clobbering
|
|
5727
|
+
// them — ColorInput derives its add/replace-layer logic
|
|
5728
|
+
// from this prop.
|
|
5729
|
+
backgroundImage={
|
|
5730
|
+
isTextFillElement ? "" : styles.backgroundImage
|
|
5731
|
+
}
|
|
5269
5732
|
blendMode={
|
|
5270
|
-
|
|
5733
|
+
isTextFillElement
|
|
5271
5734
|
? undefined
|
|
5272
5735
|
: styles.backgroundBlendMode || "normal"
|
|
5273
5736
|
}
|
|
5274
5737
|
onBlendModeChange={
|
|
5275
|
-
|
|
5738
|
+
isTextFillElement
|
|
5276
5739
|
? undefined
|
|
5277
5740
|
: (v) => onStyleChange("backgroundBlendMode", v)
|
|
5278
5741
|
}
|
|
5742
|
+
// Text fill ("color") can't hold a gradient/image paint —
|
|
5743
|
+
// there is no background-clip:text support here — so never
|
|
5744
|
+
// offer layered fills for it; the picker's Gradient/Image
|
|
5745
|
+
// tabs remain reachable as raw UI (it manages that tab
|
|
5746
|
+
// selection as local state independent of these props) but
|
|
5747
|
+
// ColorInput's setNext guard rejects non-color writes back
|
|
5748
|
+
// into `color` when supportsLayeredFills is false. For any
|
|
5749
|
+
// other element the base fill is a real backgroundImage
|
|
5750
|
+
// layer stack, so wire the same layered-fill handlers the
|
|
5751
|
+
// page background row uses (see PageProperties above).
|
|
5752
|
+
supportsLayeredFills={!isTextFillElement}
|
|
5753
|
+
onBackgroundImageChange={
|
|
5754
|
+
isTextFillElement
|
|
5755
|
+
? undefined
|
|
5756
|
+
: (v) => onStyleChange("backgroundImage", v)
|
|
5757
|
+
}
|
|
5758
|
+
onImageFillChange={
|
|
5759
|
+
isTextFillElement
|
|
5760
|
+
? undefined
|
|
5761
|
+
: (value) =>
|
|
5762
|
+
commitStylePatch(
|
|
5763
|
+
imageFillToBackgroundStyles(value),
|
|
5764
|
+
onStyleChange,
|
|
5765
|
+
onStylesChange,
|
|
5766
|
+
)
|
|
5767
|
+
}
|
|
5279
5768
|
documentColors={documentColors}
|
|
5280
5769
|
pickerKey={[
|
|
5281
5770
|
element.sourceId ??
|
|
@@ -5304,7 +5793,7 @@ function FillProperties({
|
|
|
5304
5793
|
<SectionIconButton
|
|
5305
5794
|
label={t("editPanel.labels.removeLayer")}
|
|
5306
5795
|
onClick={() => {
|
|
5307
|
-
if (
|
|
5796
|
+
if (isTextFillElement) {
|
|
5308
5797
|
onStyleChange(fillProperty, "transparent");
|
|
5309
5798
|
return;
|
|
5310
5799
|
}
|
|
@@ -5322,23 +5811,21 @@ function FillProperties({
|
|
|
5322
5811
|
</SectionIconButton>
|
|
5323
5812
|
</div>
|
|
5324
5813
|
) : null}
|
|
5325
|
-
{!
|
|
5814
|
+
{!isTextFillElement
|
|
5326
5815
|
? backgroundLayers.map((layer, index) => {
|
|
5327
5816
|
const gradient = parseGradientLayer(layer);
|
|
5817
|
+
// Hidden state lives in the real, persisted backgroundSize
|
|
5818
|
+
// marker (see withLayerSizeMarker) rather than React state,
|
|
5819
|
+
// so it survives deselect/reselect. Opacity still reflects
|
|
5820
|
+
// the gradient's own stop opacities for display, but no
|
|
5821
|
+
// longer drives hide/show — a layer can be a fully-opaque
|
|
5822
|
+
// gradient and still be hidden via zero-size.
|
|
5823
|
+
const hidden = isLayerHiddenBySize(backgroundSizeLayers[index]);
|
|
5328
5824
|
const opacity = gradient
|
|
5329
5825
|
? averageGradientOpacity(gradient.stops)
|
|
5330
5826
|
: 100;
|
|
5331
|
-
const layerStashKey = `${elementIdentityKey(element)}:layer:${index}`;
|
|
5332
|
-
const stashedLayer = hiddenLayerStash[layerStashKey];
|
|
5333
|
-
const hiddenImagePlaceholder = Boolean(
|
|
5334
|
-
stashedLayer && gradient && opacity <= 0,
|
|
5335
|
-
);
|
|
5336
5827
|
const label = gradient
|
|
5337
|
-
?
|
|
5338
|
-
? `${"Image" /* i18n-ignore design inspector paint row */} ${
|
|
5339
|
-
index + 1
|
|
5340
|
-
}`
|
|
5341
|
-
: `${gradientLabel(gradient.type)} ${index + 1}`
|
|
5828
|
+
? `${gradientLabel(gradient.type)} ${index + 1}`
|
|
5342
5829
|
: `${"Image" /* i18n-ignore design inspector paint row */} ${
|
|
5343
5830
|
index + 1
|
|
5344
5831
|
}`;
|
|
@@ -5356,6 +5843,25 @@ function FillProperties({
|
|
|
5356
5843
|
),
|
|
5357
5844
|
),
|
|
5358
5845
|
);
|
|
5846
|
+
onStyleChange(
|
|
5847
|
+
"backgroundSize",
|
|
5848
|
+
joinCssLayers(
|
|
5849
|
+
backgroundSizeLayers.filter(
|
|
5850
|
+
(_, layerIndex) => layerIndex !== index,
|
|
5851
|
+
),
|
|
5852
|
+
),
|
|
5853
|
+
);
|
|
5854
|
+
};
|
|
5855
|
+
const setLayerHidden = (nextHidden: boolean) => {
|
|
5856
|
+
onStyleChange(
|
|
5857
|
+
"backgroundSize",
|
|
5858
|
+
withLayerSizeMarker(
|
|
5859
|
+
backgroundSizeLayers,
|
|
5860
|
+
backgroundLayers.length,
|
|
5861
|
+
index,
|
|
5862
|
+
nextHidden,
|
|
5863
|
+
),
|
|
5864
|
+
);
|
|
5359
5865
|
};
|
|
5360
5866
|
|
|
5361
5867
|
return (
|
|
@@ -5378,7 +5884,7 @@ function FillProperties({
|
|
|
5378
5884
|
{label}
|
|
5379
5885
|
</span>
|
|
5380
5886
|
<span className="shrink-0 tabular-nums text-muted-foreground">
|
|
5381
|
-
{opacity}%
|
|
5887
|
+
{hidden ? 0 : opacity}%
|
|
5382
5888
|
</span>
|
|
5383
5889
|
</button>
|
|
5384
5890
|
</PopoverTrigger>
|
|
@@ -5389,7 +5895,7 @@ function FillProperties({
|
|
|
5389
5895
|
className="w-80 p-0"
|
|
5390
5896
|
>
|
|
5391
5897
|
<DesignColorPicker
|
|
5392
|
-
value={
|
|
5898
|
+
value={layer}
|
|
5393
5899
|
onPaintValueChange={replaceLayer}
|
|
5394
5900
|
onChange={(nextColor) => {
|
|
5395
5901
|
if (!gradient) return;
|
|
@@ -5434,78 +5940,14 @@ function FillProperties({
|
|
|
5434
5940
|
</Popover>
|
|
5435
5941
|
<SectionIconButton
|
|
5436
5942
|
label={
|
|
5437
|
-
|
|
5943
|
+
hidden
|
|
5438
5944
|
? t("editPanel.labels.showLayer")
|
|
5439
5945
|
: t("editPanel.labels.hideLayer")
|
|
5440
5946
|
}
|
|
5441
|
-
onClick={() =>
|
|
5442
|
-
|
|
5443
|
-
// Show: restore the exact pre-hide layer if stashed,
|
|
5444
|
-
// otherwise fall back to forcing every stop opaque.
|
|
5445
|
-
const stashed = hiddenLayerStash[layerStashKey];
|
|
5446
|
-
if (stashed !== undefined) {
|
|
5447
|
-
replaceLayer(stashed);
|
|
5448
|
-
setHiddenLayerStash((prev) => {
|
|
5449
|
-
const next = { ...prev };
|
|
5450
|
-
delete next[layerStashKey];
|
|
5451
|
-
return next;
|
|
5452
|
-
});
|
|
5453
|
-
} else if (gradient) {
|
|
5454
|
-
replaceLayer(
|
|
5455
|
-
buildGradientLayer(
|
|
5456
|
-
gradient.type,
|
|
5457
|
-
gradient.stops.map((stop) => ({
|
|
5458
|
-
...stop,
|
|
5459
|
-
opacity: 100,
|
|
5460
|
-
})),
|
|
5461
|
-
gradient.prefix,
|
|
5462
|
-
),
|
|
5463
|
-
);
|
|
5464
|
-
}
|
|
5465
|
-
return;
|
|
5466
|
-
}
|
|
5467
|
-
if (!gradient) {
|
|
5468
|
-
setHiddenLayerStash((prev) => ({
|
|
5469
|
-
...prev,
|
|
5470
|
-
[layerStashKey]: layer,
|
|
5471
|
-
}));
|
|
5472
|
-
replaceLayer(
|
|
5473
|
-
buildGradientLayer("linear", [
|
|
5474
|
-
{
|
|
5475
|
-
id: "stop-0",
|
|
5476
|
-
color: "rgba(0, 0, 0, 0)",
|
|
5477
|
-
position: 0,
|
|
5478
|
-
opacity: 0,
|
|
5479
|
-
},
|
|
5480
|
-
{
|
|
5481
|
-
id: "stop-1",
|
|
5482
|
-
color: "rgba(0, 0, 0, 0)",
|
|
5483
|
-
position: 100,
|
|
5484
|
-
opacity: 0,
|
|
5485
|
-
},
|
|
5486
|
-
]),
|
|
5487
|
-
);
|
|
5488
|
-
} else {
|
|
5489
|
-
// Hide: stash the current layer (with its real per-stop
|
|
5490
|
-
// opacities) before zeroing every stop's alpha.
|
|
5491
|
-
setHiddenLayerStash((prev) => ({
|
|
5492
|
-
...prev,
|
|
5493
|
-
[layerStashKey]: layer,
|
|
5494
|
-
}));
|
|
5495
|
-
replaceLayer(
|
|
5496
|
-
buildGradientLayer(
|
|
5497
|
-
gradient.type,
|
|
5498
|
-
gradient.stops.map((stop) => ({
|
|
5499
|
-
...stop,
|
|
5500
|
-
opacity: 0,
|
|
5501
|
-
})),
|
|
5502
|
-
gradient.prefix,
|
|
5503
|
-
),
|
|
5504
|
-
);
|
|
5505
|
-
}
|
|
5506
|
-
}}
|
|
5947
|
+
onClick={() => setLayerHidden(!hidden)}
|
|
5948
|
+
activateOnPointerDown
|
|
5507
5949
|
>
|
|
5508
|
-
{
|
|
5950
|
+
{hidden ? (
|
|
5509
5951
|
<IconEyeOff className="size-3.5" />
|
|
5510
5952
|
) : (
|
|
5511
5953
|
<IconEye className="size-3.5" />
|
|
@@ -5533,16 +5975,19 @@ function StrokeProperties({
|
|
|
5533
5975
|
onStylesChange,
|
|
5534
5976
|
}: {
|
|
5535
5977
|
element: ElementInfo;
|
|
5536
|
-
onStyleChange:
|
|
5537
|
-
onStylesChange?:
|
|
5978
|
+
onStyleChange: StyleChangeHandler;
|
|
5979
|
+
onStylesChange?: StylesChangeHandler;
|
|
5538
5980
|
}) {
|
|
5539
5981
|
const t = useT();
|
|
5540
5982
|
const styles = element.computedStyles;
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
styles.
|
|
5545
|
-
|
|
5983
|
+
// Visible requires: real width, style not "none" (legacy hide path), and
|
|
5984
|
+
// color not zero-alpha (current hide path — see strokeHiddenByColor).
|
|
5985
|
+
const borderVisible =
|
|
5986
|
+
strokeIsVisible(styles.borderWidth, styles.borderStyle) &&
|
|
5987
|
+
!strokeHiddenByColor(styles.borderColor);
|
|
5988
|
+
const outlineVisible =
|
|
5989
|
+
strokeIsVisible(styles.outlineWidth, styles.outlineStyle) &&
|
|
5990
|
+
!strokeHiddenByColor(styles.outlineColor);
|
|
5546
5991
|
const strokeIsMixed = [
|
|
5547
5992
|
styles.borderWidth,
|
|
5548
5993
|
styles.borderStyle,
|
|
@@ -5580,8 +6025,17 @@ function StrokeProperties({
|
|
|
5580
6025
|
return;
|
|
5581
6026
|
}
|
|
5582
6027
|
if (!borderVisible) {
|
|
6028
|
+
// Restore full alpha before falling back to cssColorOrFallback
|
|
6029
|
+
// — a border previously hidden via the eye toggle (zero-alpha,
|
|
6030
|
+
// real RGB preserved) is not "transparent" by that helper's
|
|
6031
|
+
// narrow literal check, so without this an "Add" click here
|
|
6032
|
+
// could silently re-add an invisible border.
|
|
6033
|
+
const existingBorderColor = styles.borderColor || styles.color;
|
|
6034
|
+
const existingParsed = parseCssColor(existingBorderColor || "");
|
|
5583
6035
|
const borderColor = cssColorOrFallback(
|
|
5584
|
-
|
|
6036
|
+
existingParsed
|
|
6037
|
+
? rgbaToCss(withColorOpacity(existingParsed, 100))
|
|
6038
|
+
: existingBorderColor,
|
|
5585
6039
|
"#000000",
|
|
5586
6040
|
);
|
|
5587
6041
|
commitStylePatch(
|
|
@@ -5684,7 +6138,7 @@ function AppearanceProperties({
|
|
|
5684
6138
|
onStyleChange,
|
|
5685
6139
|
}: {
|
|
5686
6140
|
element: ElementInfo;
|
|
5687
|
-
onStyleChange:
|
|
6141
|
+
onStyleChange: StyleChangeHandler;
|
|
5688
6142
|
}) {
|
|
5689
6143
|
const t = useT();
|
|
5690
6144
|
const styles = element.computedStyles;
|
|
@@ -5734,7 +6188,9 @@ function AppearanceProperties({
|
|
|
5734
6188
|
? 0
|
|
5735
6189
|
: parseNumericValue(styles.opacity || "1") * 100
|
|
5736
6190
|
}
|
|
5737
|
-
onChange={(v) =>
|
|
6191
|
+
onChange={(v, meta) =>
|
|
6192
|
+
onStyleChange("opacity", String(v / 100), meta)
|
|
6193
|
+
}
|
|
5738
6194
|
mixed={isMixedValue(styles.opacity)}
|
|
5739
6195
|
min={0}
|
|
5740
6196
|
max={100}
|
|
@@ -5742,7 +6198,15 @@ function AppearanceProperties({
|
|
|
5742
6198
|
unit="%"
|
|
5743
6199
|
precision={1}
|
|
5744
6200
|
/>
|
|
5745
|
-
|
|
6201
|
+
{/* Selection-stable key so per-selection UI state (the independent-
|
|
6202
|
+
corners toggle, which ratchets open while corners differ) resets on
|
|
6203
|
+
selection change instead of leaking to the next element — same
|
|
6204
|
+
pattern as ExportSettingsPanel. */}
|
|
6205
|
+
<CornerRadiusControl
|
|
6206
|
+
key={elementIdentityKey(element)}
|
|
6207
|
+
styles={styles}
|
|
6208
|
+
onStyleChange={onStyleChange}
|
|
6209
|
+
/>
|
|
5746
6210
|
</div>
|
|
5747
6211
|
</PanelSection>
|
|
5748
6212
|
);
|
|
@@ -5754,8 +6218,8 @@ function EffectsProperties({
|
|
|
5754
6218
|
onStylesChange,
|
|
5755
6219
|
}: {
|
|
5756
6220
|
element: ElementInfo;
|
|
5757
|
-
onStyleChange:
|
|
5758
|
-
onStylesChange?:
|
|
6221
|
+
onStyleChange: StyleChangeHandler;
|
|
6222
|
+
onStylesChange?: StylesChangeHandler;
|
|
5759
6223
|
}) {
|
|
5760
6224
|
const t = useT();
|
|
5761
6225
|
const styles = element.computedStyles;
|
|
@@ -5774,10 +6238,10 @@ function EffectsProperties({
|
|
|
5774
6238
|
const layerBlurStashKey = `${effectStashKey}:filter:blur`;
|
|
5775
6239
|
const backdropBlurStashKey = `${effectStashKey}:backdrop-filter:blur`;
|
|
5776
6240
|
const shadowLayers = parseShadowLayers(styles.boxShadow);
|
|
5777
|
-
const setShadowLayers = (layers: ShadowLayer[]) => {
|
|
6241
|
+
const setShadowLayers = (layers: ShadowLayer[], meta?: StyleChangeMeta) => {
|
|
5778
6242
|
const boxShadow = serializeShadowLayers(layers);
|
|
5779
|
-
if (onStylesChange) onStylesChange({ boxShadow });
|
|
5780
|
-
else onStyleChange("boxShadow", boxShadow);
|
|
6243
|
+
if (onStylesChange) onStylesChange({ boxShadow }, meta);
|
|
6244
|
+
else onStyleChange("boxShadow", boxShadow, meta);
|
|
5781
6245
|
};
|
|
5782
6246
|
const addDropShadow = () =>
|
|
5783
6247
|
setShadowLayers([
|
|
@@ -5836,13 +6300,13 @@ function EffectsProperties({
|
|
|
5836
6300
|
key={layer.id}
|
|
5837
6301
|
layer={layer}
|
|
5838
6302
|
index={index}
|
|
5839
|
-
onChange={(patch) => {
|
|
6303
|
+
onChange={(patch, meta) => {
|
|
5840
6304
|
const next = shadowLayers.map((candidate) =>
|
|
5841
6305
|
candidate.id === layer.id
|
|
5842
6306
|
? { ...candidate, ...patch }
|
|
5843
6307
|
: candidate,
|
|
5844
6308
|
);
|
|
5845
|
-
setShadowLayers(next);
|
|
6309
|
+
setShadowLayers(next, meta);
|
|
5846
6310
|
}}
|
|
5847
6311
|
onToggleVisibility={() => {
|
|
5848
6312
|
const visible = colorHasVisibleAlpha(layer.color);
|
|
@@ -5958,10 +6422,11 @@ function EffectsProperties({
|
|
|
5958
6422
|
<ScrubInput
|
|
5959
6423
|
label={t("editPanel.labels.blur")}
|
|
5960
6424
|
value={blurValue}
|
|
5961
|
-
onChange={(value) =>
|
|
6425
|
+
onChange={(value, meta) =>
|
|
5962
6426
|
onStyleChange(
|
|
5963
6427
|
"filter",
|
|
5964
6428
|
setBlurFilterValue(styles.filter, value),
|
|
6429
|
+
meta,
|
|
5965
6430
|
)
|
|
5966
6431
|
}
|
|
5967
6432
|
unit="px"
|
|
@@ -6048,10 +6513,11 @@ function EffectsProperties({
|
|
|
6048
6513
|
<ScrubInput
|
|
6049
6514
|
label={t("editPanel.labels.blur")}
|
|
6050
6515
|
value={backdropBlurValue}
|
|
6051
|
-
onChange={(value) =>
|
|
6516
|
+
onChange={(value, meta) =>
|
|
6052
6517
|
onStyleChange(
|
|
6053
6518
|
"backdropFilter",
|
|
6054
6519
|
setBlurFilterValue(backdropFilterValue, value),
|
|
6520
|
+
meta,
|
|
6055
6521
|
)
|
|
6056
6522
|
}
|
|
6057
6523
|
unit="px"
|
|
@@ -6108,7 +6574,7 @@ function SelectionColorsProperties({
|
|
|
6108
6574
|
onStyleChange,
|
|
6109
6575
|
}: {
|
|
6110
6576
|
element: ElementInfo;
|
|
6111
|
-
onStyleChange:
|
|
6577
|
+
onStyleChange: StyleChangeHandler;
|
|
6112
6578
|
}) {
|
|
6113
6579
|
// M6 · the design editor's Selection colors collapses to a single "Show selection colors"
|
|
6114
6580
|
// affordance, expanding to one editable [swatch · hex · opacity] row per
|
|
@@ -6154,7 +6620,19 @@ function SelectionColorsProperties({
|
|
|
6154
6620
|
>
|
|
6155
6621
|
<DesignColorPicker
|
|
6156
6622
|
value={cssColorOrFallback(color.value, "#000000")}
|
|
6157
|
-
|
|
6623
|
+
// PF12: per-tick drag preview vs. one authoritative
|
|
6624
|
+
// commit on gesture-end — same split as ColorInput's
|
|
6625
|
+
// setNext (see its PF12 comment above).
|
|
6626
|
+
onChange={(value) =>
|
|
6627
|
+
onStyleChange(color.property, value, {
|
|
6628
|
+
phase: "preview",
|
|
6629
|
+
})
|
|
6630
|
+
}
|
|
6631
|
+
onChangeComplete={(value) =>
|
|
6632
|
+
onStyleChange(color.property, value, {
|
|
6633
|
+
phase: "commit",
|
|
6634
|
+
})
|
|
6635
|
+
}
|
|
6158
6636
|
/>
|
|
6159
6637
|
</PopoverContent>
|
|
6160
6638
|
</Popover>
|
|
@@ -6928,7 +7406,18 @@ export function ComponentSection({
|
|
|
6928
7406
|
);
|
|
6929
7407
|
}
|
|
6930
7408
|
|
|
6931
|
-
|
|
7409
|
+
// PF8: EditPanel re-renders on every DesignEditor state change (drag,
|
|
7410
|
+
// hover, zoom) unless memoized. Nearly all props are already stabilized at
|
|
7411
|
+
// the call site (useMemo/useCallback — see DesignEditor.tsx's
|
|
7412
|
+
// selectedInspectorElements/selectedScreenGeometry/pageStyles/tweaks/
|
|
7413
|
+
// sourceCapabilities/statesPanelProps/reviewPanelProps and the onXxx
|
|
7414
|
+
// handlers passed to <EditPanel>). `headerTrailing` and `aiActions` are
|
|
7415
|
+
// legitimately-dynamic ReactNode slots (the zoom control repaints its own
|
|
7416
|
+
// live percentage every zoom tick; aiActions depends on the live
|
|
7417
|
+
// selection) — their identity is expected to change often and a custom
|
|
7418
|
+
// comparator that ignored them would hide real content changes, so this
|
|
7419
|
+
// uses the default shallow comparison rather than special-casing them.
|
|
7420
|
+
export const EditPanel = memo(function EditPanel({
|
|
6932
7421
|
selectedElement,
|
|
6933
7422
|
selectedElements,
|
|
6934
7423
|
selectedScreenGeometry,
|
|
@@ -7344,4 +7833,4 @@ export function EditPanel({
|
|
|
7344
7833
|
) : null}
|
|
7345
7834
|
</div>
|
|
7346
7835
|
);
|
|
7347
|
-
}
|
|
7836
|
+
});
|