@agent-native/core 0.80.7 → 0.80.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +6 -0
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +8 -0
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +10 -2
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +10 -2
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +7 -0
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +7 -0
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +7 -0
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +9 -2
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +6 -0
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +6 -0
- package/corpus/core/docs/content/template-design.mdx +9 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/mcp.ts +10 -0
- package/corpus/core/src/cli/skills.ts +58 -12
- package/corpus/core/src/client/AssistantChat.tsx +70 -27
- package/corpus/core/src/client/analytics.ts +3 -1
- package/corpus/core/src/client/error-format.ts +25 -0
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +13 -11
- package/corpus/core/src/client/guided-questions.tsx +32 -4
- package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +33 -1
- package/corpus/core/src/client/resources/BuiltinCapabilityDetail.tsx +8 -0
- package/corpus/core/src/client/resources/use-builtin-capabilities.ts +4 -2
- package/corpus/core/src/client/session-replay.ts +34 -0
- package/corpus/core/src/client/sharing/ShareButton.tsx +106 -21
- package/corpus/core/src/coding-tools/index.ts +1 -1
- package/corpus/core/src/mcp/index.ts +2 -0
- package/corpus/core/src/mcp/screen-memory-stdio.ts +290 -0
- package/corpus/core/src/mcp-client/builtin-capabilities.ts +13 -1
- package/corpus/core/src/mcp-client/index.ts +9 -0
- package/corpus/core/src/mcp-client/screen-memory-local.ts +461 -0
- package/corpus/core/src/onboarding/types.ts +7 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +25 -0
- package/corpus/core/src/server/agents-bundle.ts +35 -7
- package/corpus/core/src/styles/agent-native.css +5 -0
- package/corpus/core/src/templates/default/app/global.css +35 -35
- package/corpus/templates/analytics/AGENTS.md +6 -0
- package/corpus/templates/analytics/actions/github-repo-files.ts +9 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +4 -4
- package/corpus/templates/analytics/app/global.css +144 -40
- package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +1 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +4 -4
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -9
- package/corpus/templates/analytics/changelog/2026-06-29-agents-can-search-and-read-connected-github-repositories-whe.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-sessions-and-explorer-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
- package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/assets/app/components/library/LibraryPresetGrid.tsx +1 -1
- package/corpus/templates/assets/app/global.css +99 -35
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +4 -4
- package/corpus/templates/assets/app/routes/library.tsx +4 -4
- package/corpus/templates/assets/changelog/2026-06-29-asset-library-and-detail-layouts-adapt-when-the-agent-sidebar.md +6 -0
- package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/brain/app/global.css +63 -35
- package/corpus/templates/brain/app/routes/search.tsx +1 -1
- package/corpus/templates/brain/app/routes/settings.tsx +1 -1
- package/corpus/templates/brain/app/routes/sources.tsx +2 -2
- package/corpus/templates/brain/changelog/2026-06-29-search-settings-and-source-layouts-adapt-when-the-agent-sidebar.md +6 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +4 -4
- package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +3 -2
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -2
- package/corpus/templates/calendar/app/global.css +69 -35
- package/corpus/templates/calendar/changelog/2026-06-29-event-and-find-time-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
- package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/chat/app/global.css +35 -35
- package/corpus/templates/chat/changelog/2026-06-29-chat-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/clips/AGENTS.md +13 -0
- package/corpus/templates/clips/actions/get-screen-memory-status.ts +12 -0
- package/corpus/templates/clips/actions/query-screen-memory-context.ts +29 -0
- package/corpus/templates/clips/actions/view-screen.ts +2 -2
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/global.css +47 -35
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +3 -3
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +45 -18
- package/corpus/templates/clips/changelog/2026-06-29-meeting-detail-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-29-screen-memory-keeps-a-local-rolling-screen-buffer-for-agent-context.md +5 -0
- package/corpus/templates/clips/changelog/2026-06-29-video-chat-is-easier-to-find-on-recording-pages-and-s3-setup.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/styles.css +39 -39
- package/corpus/templates/clips/desktop/src/app.tsx +290 -1
- package/corpus/templates/clips/desktop/src/shared/config.ts +83 -0
- package/corpus/templates/clips/desktop/src/styles.css +67 -52
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +50 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +17 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +13 -13
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +990 -0
- package/corpus/templates/clips/server/plugins/onboarding.ts +2 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +69 -16
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +3 -3
- package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/content/app/global.css +29 -15
- package/corpus/templates/content/changelog/2026-06-29-database-gallery-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/content/changelog/2026-06-29-read-only-pages-no-longer-try-to-autosave-or-open-editor-onl.md +6 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +25 -20
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -0
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +8 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +13 -0
- package/corpus/templates/design/AGENTS.md +24 -14
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1054 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +24 -4
- package/corpus/templates/design/actions/edit-design.ts +6 -3
- package/corpus/templates/design/actions/export-coding-handoff.ts +12 -4
- package/corpus/templates/design/actions/generate-design.ts +6 -21
- package/corpus/templates/design/actions/generate-screens.ts +48 -5
- package/corpus/templates/design/actions/get-design.ts +1 -0
- package/corpus/templates/design/actions/present-design-variants.ts +355 -30
- package/corpus/templates/design/actions/update-design.ts +47 -10
- package/corpus/templates/design/actions/update-file.ts +33 -8
- package/corpus/templates/design/actions/view-screen.ts +89 -31
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +389 -71
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +1 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +596 -95
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +428 -221
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1197 -228
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +94 -217
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +28 -14
- package/corpus/templates/design/app/components/design/index.ts +0 -1
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +228 -170
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +140 -13
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +39 -8
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +4 -2
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +75 -5
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +37 -12
- package/corpus/templates/design/app/components/design/inspector/index.ts +1 -0
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +17 -4
- package/corpus/templates/design/app/components/design/types.ts +9 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +23 -5
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +448 -259
- package/corpus/templates/design/app/global.css +77 -49
- package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +8 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/design/app/i18n-data.ts +55 -3
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2783 -658
- package/corpus/templates/design/app/root.tsx +1 -1
- package/corpus/templates/design/changelog/2026-06-29-agent-edits-to-a-design-are-no-longer-occasionally-dropped-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-box-shadow-and-gradient-colors-keep-named-css-colors-like-re.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-containers-no-longer-block-selecting-nested-elements-and-nor.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-cut-cmd-ctrl-x-now-removes-the-selected-element-to-the-clipb.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-deleting-with-multiple-layers-selected-in-the-layers-panel-n.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-agents-now-generate-and-refine-variants-from-stronger.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-questions-now-appear-as-a-cleaner-canvas-intake-with-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-variant-directions-now-appear-as-board-screens-with-chat-buttons.md +5 -0
- package/corpus/templates/design/changelog/2026-06-29-duplicating-a-screen-now-gives-the-copy-its-own-layer-ids-so.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-element-selection-no-longer-snaps-back-to-the-previous-layer.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-frame-resize-measurements-now-stay-beside-the-cursor-while-d.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-hiding-and-re-showing-a-gradient-fill-now-preserves-each-sto.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-inspector-number-fields-have-cleaner-tooltips-and-draggable-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-locking-layer-nesting-generated-question-intake-and-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-names-in-the-sidebar-can-scroll-horizontally-instead-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-nesting-in-the-sidebar-now-uses-tighter-spacing-so-nam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layers-no-longer-repeat-document-wrapper-rows-when-a-screen-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-overview-previews-now-keep-element-hover-and-selection-tied-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-picking-a-design-direction-now-submits-to-the-agent-right-aw.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-pressing-escape-now-cancels-a-stuck-screen-drag-in-the-overv.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-is-easier-to-find-and-named-screens-open-dir.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-only-highlights-screens-when-the-frame-or-ti.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-previews-now-resize-to-match-the-selected-de.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-now-uses-a-pointer-cursor-instead-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-outlines-now-stay-locked-to-the-se.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-zoom-now-reports-real-screen-scale-separatel.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-feels-smoother-and-selection-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-now-stays-on-the-overview-canvas-ins.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-selecting-a-nested-layer-from-all-screens-now-keeps-you-in-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-share-now-groups-link-sharing-exports-and-agent-handoff.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-style-changes-that-set-several-properties-at-once-fixed-size.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-design-editor-loading-skeleton-now-uses-a-softer-charcoa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-device-preview-menu-now-sits-next-to-the-preview-button-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-editor-no-longer-crashes-when-a-screen-ends-up-with-dupl.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-inspector-stays-read-only-while-an-empty-design-is-still.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-missing-design-view-now-matches-the-rest-of-the-editor-c.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-icon-and-tool-option-menus-better-match-the-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-typography-controls-now-keep-the-font-name-visible-and-move-.md +6 -0
- package/corpus/templates/design/e2e/README.md +4 -3
- package/corpus/templates/design/e2e/global-setup.ts +24 -23
- package/corpus/templates/design/e2e/helpers.ts +122 -11
- package/corpus/templates/design/playwright.config.ts +19 -4
- package/corpus/templates/design/server/lib/coding-handoff.ts +126 -2
- package/corpus/templates/design/server/plugins/core-routes.ts +1 -2
- package/corpus/templates/design/server/routes/api/design-handoff/[id].zip.get.ts +86 -0
- package/corpus/templates/design/shared/code-layer.ts +125 -22
- package/corpus/templates/design/shared/color-utils.ts +159 -0
- package/corpus/templates/design/shared/generation-session.ts +42 -0
- package/corpus/templates/dispatch/app/global.css +79 -35
- package/corpus/templates/dispatch/app/routes/integrations.tsx +8 -7
- package/corpus/templates/dispatch/changelog/2026-06-29-integration-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/forms/app/components/layout/Layout.tsx +6 -2
- package/corpus/templates/forms/app/global.css +64 -35
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +2 -2
- package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +3 -3
- package/corpus/templates/forms/changelog/2026-06-29-form-list-and-insight-layouts-adapt-when-the-agent-sidebar.md +6 -0
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +3 -1
- package/corpus/templates/macros/app/global.css +25 -9
- package/corpus/templates/macros/changelog/2026-06-29-macro-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -2
- package/corpus/templates/mail/app/global.css +42 -36
- package/corpus/templates/mail/app/pages/InboxPage.tsx +1 -1
- package/corpus/templates/mail/changelog/2026-06-29-the-contact-panel-now-adapts-to-the-available-mail-pane-width.md +6 -0
- package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/plan/app/components/plan/DocumentArea.tsx +6 -7
- package/corpus/templates/plan/app/global.css +74 -42
- package/corpus/templates/plan/changelog/2026-06-29-plan-document-blocks-adapt-to-the-available-document-width.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-29-plan-titles-and-summaries-can-be-edited-inline-without-focus.md +6 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/slides/app/global.css +49 -33
- package/corpus/templates/slides/changelog/2026-06-29-slide-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/videos/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/videos/app/global.css +35 -35
- package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +10 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +58 -12
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +54 -24
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +2 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +17 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +3 -4
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/guided-questions.d.ts +4 -0
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +16 -7
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.js +26 -1
- package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
- package/dist/client/resources/BuiltinCapabilityDetail.d.ts.map +1 -1
- package/dist/client/resources/BuiltinCapabilityDetail.js +1 -1
- package/dist/client/resources/BuiltinCapabilityDetail.js.map +1 -1
- package/dist/client/resources/use-builtin-capabilities.d.ts +1 -1
- package/dist/client/resources/use-builtin-capabilities.d.ts.map +1 -1
- package/dist/client/resources/use-builtin-capabilities.js +2 -1
- package/dist/client/resources/use-builtin-capabilities.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +31 -0
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +14 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +36 -7
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/coding-tools/index.js +1 -1
- package/dist/coding-tools/index.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +6 -0
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -0
- package/dist/mcp/screen-memory-stdio.js +213 -0
- package/dist/mcp/screen-memory-stdio.js.map +1 -0
- package/dist/mcp-client/builtin-capabilities.d.ts +1 -1
- package/dist/mcp-client/builtin-capabilities.d.ts.map +1 -1
- package/dist/mcp-client/builtin-capabilities.js +10 -0
- package/dist/mcp-client/builtin-capabilities.js.map +1 -1
- package/dist/mcp-client/index.d.ts +1 -0
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +1 -0
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/screen-memory-local.d.ts +61 -0
- package/dist/mcp-client/screen-memory-local.d.ts.map +1 -0
- package/dist/mcp-client/screen-memory-local.js +336 -0
- package/dist/mcp-client/screen-memory-local.js.map +1 -0
- package/dist/onboarding/types.d.ts +7 -0
- package/dist/onboarding/types.d.ts.map +1 -1
- package/dist/onboarding/types.js.map +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +26 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agents-bundle.d.ts.map +1 -1
- package/dist/server/agents-bundle.js +22 -7
- package/dist/server/agents-bundle.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +5 -0
- package/dist/templates/default/app/global.css +35 -35
- package/docs/content/locales/ar-SA/template-design.mdx +6 -0
- package/docs/content/locales/de-DE/template-design.mdx +8 -0
- package/docs/content/locales/es-ES/template-design.mdx +10 -2
- package/docs/content/locales/fr-FR/template-design.mdx +10 -2
- package/docs/content/locales/hi-IN/template-design.mdx +7 -0
- package/docs/content/locales/ja-JP/template-design.mdx +7 -0
- package/docs/content/locales/ko-KR/template-design.mdx +7 -0
- package/docs/content/locales/pt-BR/template-design.mdx +9 -2
- package/docs/content/locales/zh-CN/template-design.mdx +6 -0
- package/docs/content/locales/zh-TW/template-design.mdx +6 -0
- package/docs/content/template-design.mdx +9 -2
- package/package.json +1 -1
- package/src/templates/default/app/global.css +35 -35
- package/corpus/templates/design/app/components/design/VariantGrid.tsx +0 -189
- package/corpus/templates/design/app/components/design/VariantHandoffCard.tsx +0 -115
- package/corpus/templates/design/app/hooks/use-variant-flow.ts +0 -258
|
@@ -7,12 +7,14 @@ import {
|
|
|
7
7
|
IconLock,
|
|
8
8
|
IconLockOpen,
|
|
9
9
|
IconLayoutGrid,
|
|
10
|
+
IconPencil,
|
|
10
11
|
IconPlus,
|
|
11
12
|
IconSearch,
|
|
12
13
|
} from "@tabler/icons-react";
|
|
13
14
|
import {
|
|
14
15
|
useCallback,
|
|
15
16
|
useEffect,
|
|
17
|
+
useLayoutEffect,
|
|
16
18
|
useMemo,
|
|
17
19
|
useRef,
|
|
18
20
|
useState,
|
|
@@ -23,6 +25,13 @@ import {
|
|
|
23
25
|
} from "react";
|
|
24
26
|
|
|
25
27
|
import { Button } from "@/components/ui/button";
|
|
28
|
+
import {
|
|
29
|
+
ContextMenu,
|
|
30
|
+
ContextMenuContent,
|
|
31
|
+
ContextMenuItem,
|
|
32
|
+
ContextMenuSeparator,
|
|
33
|
+
ContextMenuTrigger,
|
|
34
|
+
} from "@/components/ui/context-menu";
|
|
26
35
|
import { Input } from "@/components/ui/input";
|
|
27
36
|
import {
|
|
28
37
|
Tooltip,
|
|
@@ -31,6 +40,9 @@ import {
|
|
|
31
40
|
} from "@/components/ui/tooltip";
|
|
32
41
|
import { cn } from "@/lib/utils";
|
|
33
42
|
|
|
43
|
+
const LAYER_ROW_BASE_INDENT = 4;
|
|
44
|
+
const LAYER_ROW_DEPTH_INDENT = 18;
|
|
45
|
+
|
|
34
46
|
export type LayersPanelNodeType =
|
|
35
47
|
| "file"
|
|
36
48
|
| "screen"
|
|
@@ -94,6 +106,7 @@ export interface LayersPanelSelectionIntent {
|
|
|
94
106
|
id: string;
|
|
95
107
|
selectedIds: string[];
|
|
96
108
|
additive: boolean;
|
|
109
|
+
currentSelectedIds?: string[];
|
|
97
110
|
range: boolean;
|
|
98
111
|
source: "keyboard" | "pointer";
|
|
99
112
|
}
|
|
@@ -107,6 +120,7 @@ export interface LayersPanelMoveIntent {
|
|
|
107
120
|
export interface LayersPanelLabels {
|
|
108
121
|
title: string;
|
|
109
122
|
screens: string;
|
|
123
|
+
allScreens: string;
|
|
110
124
|
screenOverview: string;
|
|
111
125
|
addScreen: string;
|
|
112
126
|
searchPlaceholder: string;
|
|
@@ -128,6 +142,7 @@ export interface LayersPanelLabels {
|
|
|
128
142
|
export interface LayersPanelProps {
|
|
129
143
|
screens?: LayersPanelFile[];
|
|
130
144
|
activeScreenId?: string;
|
|
145
|
+
screenOverviewActive?: boolean;
|
|
131
146
|
files?: LayersPanelFile[];
|
|
132
147
|
layers?: LayersPanelNode[];
|
|
133
148
|
codeLayers?: LayersPanelNode[];
|
|
@@ -161,19 +176,45 @@ interface FlatLayerRow {
|
|
|
161
176
|
depth: number;
|
|
162
177
|
ancestorIds: string[];
|
|
163
178
|
hasChildren: boolean;
|
|
179
|
+
canAcceptChildren: boolean;
|
|
164
180
|
}
|
|
165
181
|
|
|
182
|
+
// Node types that can contain children even when currently empty.
|
|
183
|
+
// Leaf / void types (text, image, shape, rectangle) are excluded so we don't
|
|
184
|
+
// offer an "inside" drop zone on genuinely non-container elements.
|
|
185
|
+
const CONTAINER_TYPES = new Set<LayersPanelNodeType | undefined>([
|
|
186
|
+
"file",
|
|
187
|
+
"screen",
|
|
188
|
+
"frame",
|
|
189
|
+
"group",
|
|
190
|
+
"section",
|
|
191
|
+
"component",
|
|
192
|
+
"instance",
|
|
193
|
+
"code",
|
|
194
|
+
"element",
|
|
195
|
+
]);
|
|
196
|
+
|
|
166
197
|
const SECTION_CODE_ID = "__design_layers_code__";
|
|
167
198
|
const SECTION_ELEMENT_ID = "__design_layers_elements__";
|
|
168
199
|
|
|
169
200
|
// Module-level drag state: dataTransfer.getData() returns "" during dragover
|
|
170
201
|
// per spec; the source row stores the drag payload here on dragstart instead.
|
|
171
202
|
let activeDragState: { sourceId: string; draggedIds: string[] } | null = null;
|
|
203
|
+
let activeDropIntent: LayersPanelMoveIntent | null = null;
|
|
204
|
+
|
|
205
|
+
const ROW_BASE_INDENT = 4;
|
|
206
|
+
const ROW_INDENT_STEP = 28;
|
|
207
|
+
const ROW_MAX_INDENT = 96;
|
|
208
|
+
|
|
209
|
+
function rowIndent(depth: number): number {
|
|
210
|
+
return Math.min(ROW_BASE_INDENT + depth * ROW_INDENT_STEP, ROW_MAX_INDENT);
|
|
211
|
+
}
|
|
172
212
|
|
|
173
213
|
function defaultLabels(t: ReturnType<typeof useT>): LayersPanelLabels {
|
|
174
214
|
return {
|
|
175
215
|
title: t("layersPanel.title"),
|
|
176
216
|
screens: t("layersPanel.screens"),
|
|
217
|
+
allScreens: t("layersPanel.allScreens"),
|
|
177
218
|
screenOverview: t("designEditor.screenOverview"),
|
|
178
219
|
addScreen: t("layersPanel.addScreen"),
|
|
179
220
|
searchPlaceholder: t("layersPanel.searchPlaceholder"),
|
|
@@ -307,8 +348,16 @@ function flattenRows(
|
|
|
307
348
|
nodes.forEach((node, index) => {
|
|
308
349
|
const children = node.children ?? [];
|
|
309
350
|
const hasChildren = children.length > 0;
|
|
351
|
+
const canAcceptChildren = CONTAINER_TYPES.has(node.type);
|
|
310
352
|
const rowKey = `${parentKey}/${node.id}:${index}`;
|
|
311
|
-
rows.push({
|
|
353
|
+
rows.push({
|
|
354
|
+
node,
|
|
355
|
+
rowKey,
|
|
356
|
+
depth,
|
|
357
|
+
ancestorIds,
|
|
358
|
+
hasChildren,
|
|
359
|
+
canAcceptChildren,
|
|
360
|
+
});
|
|
312
361
|
if (hasChildren && (forceExpanded || expandedIds.has(node.id))) {
|
|
313
362
|
flattenRows(
|
|
314
363
|
children,
|
|
@@ -374,6 +423,7 @@ function layerCanShowBadge(node: LayersPanelNode) {
|
|
|
374
423
|
export function LayersPanel({
|
|
375
424
|
screens,
|
|
376
425
|
activeScreenId,
|
|
426
|
+
screenOverviewActive = false,
|
|
377
427
|
files,
|
|
378
428
|
layers,
|
|
379
429
|
codeLayers,
|
|
@@ -400,6 +450,7 @@ export function LayersPanel({
|
|
|
400
450
|
const t = useT();
|
|
401
451
|
const labels = useMemo(() => mergeLabels(labelsProp, t), [labelsProp, t]);
|
|
402
452
|
const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
|
|
453
|
+
const selectedIdsRef = useRef<readonly string[]>(selectedIds);
|
|
403
454
|
const expandedIdSet = useMemo(() => new Set(expandedIds), [expandedIds]);
|
|
404
455
|
const lastSelectionAnchorRef = useRef<string | null>(selectedIds[0] ?? null);
|
|
405
456
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -435,6 +486,10 @@ export function LayersPanel({
|
|
|
435
486
|
[roots, selectedIdSet],
|
|
436
487
|
);
|
|
437
488
|
|
|
489
|
+
useLayoutEffect(() => {
|
|
490
|
+
selectedIdsRef.current = selectedIds;
|
|
491
|
+
}, [selectedIds]);
|
|
492
|
+
|
|
438
493
|
useEffect(() => {
|
|
439
494
|
if (selectedAncestorIds.length === 0) return;
|
|
440
495
|
const next = new Set(expandedIds);
|
|
@@ -481,33 +536,47 @@ export function LayersPanel({
|
|
|
481
536
|
id: string,
|
|
482
537
|
options: {
|
|
483
538
|
additive: boolean;
|
|
539
|
+
currentSelectedIds?: string[];
|
|
484
540
|
range: boolean;
|
|
485
541
|
source: "keyboard" | "pointer";
|
|
486
542
|
},
|
|
487
543
|
) => {
|
|
544
|
+
const currentSelectedIds =
|
|
545
|
+
options.currentSelectedIds ?? selectedIdsRef.current;
|
|
546
|
+
const currentSelectedIdSet = new Set(currentSelectedIds);
|
|
488
547
|
let nextIds: string[];
|
|
489
548
|
if (options.range && lastSelectionAnchorRef.current) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
549
|
+
let anchor = lastSelectionAnchorRef.current;
|
|
550
|
+
if (selectableVisibleIds.indexOf(anchor) < 0) {
|
|
551
|
+
// Stale anchor (deleted / filtered / collapsed out of view): pivot from
|
|
552
|
+
// the last selected layer that is still visible & selectable, matching
|
|
553
|
+
// Figma's behavior instead of dropping the range to a single select.
|
|
554
|
+
const fallback = [...selectedIds]
|
|
555
|
+
.reverse()
|
|
556
|
+
.find((sid) => selectableVisibleIds.includes(sid));
|
|
557
|
+
if (fallback) {
|
|
558
|
+
anchor = fallback;
|
|
559
|
+
lastSelectionAnchorRef.current = fallback;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const from = selectableVisibleIds.indexOf(anchor);
|
|
493
563
|
const to = selectableVisibleIds.indexOf(id);
|
|
494
564
|
if (from >= 0 && to >= 0) {
|
|
495
565
|
const [start, end] = from < to ? [from, to] : [to, from];
|
|
496
566
|
const rangeIds = selectableVisibleIds.slice(start, end + 1);
|
|
497
567
|
nextIds = options.additive
|
|
498
|
-
? Array.from(new Set([...
|
|
568
|
+
? Array.from(new Set([...currentSelectedIds, ...rangeIds]))
|
|
499
569
|
: rangeIds;
|
|
500
570
|
} else {
|
|
501
571
|
nextIds = [id];
|
|
502
572
|
}
|
|
503
573
|
} else if (options.additive) {
|
|
504
|
-
nextIds =
|
|
505
|
-
?
|
|
506
|
-
: [...
|
|
574
|
+
nextIds = currentSelectedIdSet.has(id)
|
|
575
|
+
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
576
|
+
: [...currentSelectedIds, id];
|
|
507
577
|
} else {
|
|
508
578
|
nextIds = [id];
|
|
509
579
|
}
|
|
510
|
-
|
|
511
580
|
// Only advance the anchor on plain clicks; Shift+clicks extend from the
|
|
512
581
|
// existing anchor so the pivot stays fixed across consecutive range clicks.
|
|
513
582
|
if (!options.range) {
|
|
@@ -515,7 +584,7 @@ export function LayersPanel({
|
|
|
515
584
|
}
|
|
516
585
|
onSelectionChange(nextIds, { id, selectedIds: nextIds, ...options });
|
|
517
586
|
},
|
|
518
|
-
[onSelectionChange, selectableVisibleIds
|
|
587
|
+
[onSelectionChange, selectableVisibleIds],
|
|
519
588
|
);
|
|
520
589
|
|
|
521
590
|
const commitRename = useCallback(
|
|
@@ -599,12 +668,6 @@ export function LayersPanel({
|
|
|
599
668
|
{labels.screens}
|
|
600
669
|
</h2>
|
|
601
670
|
<div className="flex items-center gap-0.5 text-muted-foreground">
|
|
602
|
-
<IconTooltipButton
|
|
603
|
-
label={labels.screenOverview}
|
|
604
|
-
onClick={onScreenOverview}
|
|
605
|
-
>
|
|
606
|
-
<IconLayoutGrid className="size-3.5" />
|
|
607
|
-
</IconTooltipButton>
|
|
608
671
|
<IconTooltipButton
|
|
609
672
|
label={labels.searchPlaceholder}
|
|
610
673
|
onClick={openSearch}
|
|
@@ -620,9 +683,30 @@ export function LayersPanel({
|
|
|
620
683
|
</IconTooltipButton>
|
|
621
684
|
</div>
|
|
622
685
|
</div>
|
|
686
|
+
<div className="px-2">
|
|
687
|
+
<button
|
|
688
|
+
type="button"
|
|
689
|
+
className={cn(
|
|
690
|
+
"flex h-8 w-full cursor-default items-center gap-2 rounded-[5px] px-2 text-left text-[12px] font-semibold outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
|
|
691
|
+
screenOverviewActive
|
|
692
|
+
? "bg-[var(--design-editor-active-row-color)] text-foreground"
|
|
693
|
+
: "text-foreground/85 hover:bg-[var(--design-editor-active-row-color)] hover:text-foreground",
|
|
694
|
+
)}
|
|
695
|
+
aria-current={screenOverviewActive ? "page" : undefined}
|
|
696
|
+
onClick={() => onScreenOverview?.()}
|
|
697
|
+
title={labels.allScreens}
|
|
698
|
+
>
|
|
699
|
+
<IconLayoutGrid className="size-4 shrink-0" />
|
|
700
|
+
<span className="min-w-0 flex-1 truncate">
|
|
701
|
+
{labels.allScreens}
|
|
702
|
+
</span>
|
|
703
|
+
</button>
|
|
704
|
+
</div>
|
|
705
|
+
<div className="mx-3 my-2 border-t border-[var(--design-editor-panel-divider-color)]" />
|
|
623
706
|
<div className="space-y-0.5 px-2">
|
|
624
707
|
{screenRows.map((screen) => {
|
|
625
|
-
const isActive =
|
|
708
|
+
const isActive =
|
|
709
|
+
!screenOverviewActive && screen.id === activeScreenId;
|
|
626
710
|
return (
|
|
627
711
|
<button
|
|
628
712
|
key={screen.id}
|
|
@@ -633,6 +717,7 @@ export function LayersPanel({
|
|
|
633
717
|
? "bg-[var(--design-editor-active-row-color)] text-foreground"
|
|
634
718
|
: "text-foreground/85 hover:bg-[var(--design-editor-active-row-color)] hover:text-foreground",
|
|
635
719
|
)}
|
|
720
|
+
aria-current={isActive ? "page" : undefined}
|
|
636
721
|
onClick={() => onScreenSelect?.(screen.id)}
|
|
637
722
|
title={screen.filename ?? screen.name}
|
|
638
723
|
>
|
|
@@ -697,9 +782,13 @@ export function LayersPanel({
|
|
|
697
782
|
</div>
|
|
698
783
|
) : null}
|
|
699
784
|
|
|
700
|
-
<div className="min-h-0 flex-1 overflow-
|
|
785
|
+
<div className="min-h-0 flex-1 overflow-auto overscroll-contain py-2">
|
|
701
786
|
{visibleRows.length ? (
|
|
702
|
-
<div
|
|
787
|
+
<div
|
|
788
|
+
className="w-max min-w-full px-2"
|
|
789
|
+
role="tree"
|
|
790
|
+
aria-label={labels.title}
|
|
791
|
+
>
|
|
703
792
|
{visibleRows.map((row) => (
|
|
704
793
|
<LayerRow
|
|
705
794
|
key={row.rowKey}
|
|
@@ -723,6 +812,7 @@ export function LayersPanel({
|
|
|
723
812
|
onCommitRename={commitRename}
|
|
724
813
|
onCancelRename={() => setRenamingId(null)}
|
|
725
814
|
onStartRename={startRename}
|
|
815
|
+
onRename={onRename}
|
|
726
816
|
onSelect={selectNode}
|
|
727
817
|
onToggleExpanded={(expanded) =>
|
|
728
818
|
onExpandedIdsChange(
|
|
@@ -766,6 +856,7 @@ function LayerRow({
|
|
|
766
856
|
onCommitRename,
|
|
767
857
|
onCancelRename,
|
|
768
858
|
onStartRename,
|
|
859
|
+
onRename,
|
|
769
860
|
onSelect,
|
|
770
861
|
onToggleExpanded,
|
|
771
862
|
onToggleLocked,
|
|
@@ -792,10 +883,12 @@ function LayerRow({
|
|
|
792
883
|
onCommitRename: (id: string) => void;
|
|
793
884
|
onCancelRename: () => void;
|
|
794
885
|
onStartRename: (node: LayersPanelNode) => void;
|
|
886
|
+
onRename?: (id: string, name: string) => void;
|
|
795
887
|
onSelect: (
|
|
796
888
|
id: string,
|
|
797
889
|
options: {
|
|
798
890
|
additive: boolean;
|
|
891
|
+
currentSelectedIds?: string[];
|
|
799
892
|
range: boolean;
|
|
800
893
|
source: "keyboard" | "pointer";
|
|
801
894
|
},
|
|
@@ -812,21 +905,42 @@ function LayerRow({
|
|
|
812
905
|
selectedIds: readonly string[];
|
|
813
906
|
visibleRows: FlatLayerRow[];
|
|
814
907
|
}) {
|
|
815
|
-
const { node, depth, hasChildren } = row;
|
|
908
|
+
const { node, depth, hasChildren, canAcceptChildren } = row;
|
|
816
909
|
const selectable = node.selectable !== false;
|
|
817
910
|
const lockable = node.lockable !== false && Boolean(onToggleLocked);
|
|
818
911
|
const hideable = node.hideable !== false && Boolean(onToggleHidden);
|
|
819
912
|
const draggable = selectable && Boolean(onMoveLayer);
|
|
913
|
+
const canDropInside = layerCanDropInside(node, hasChildren);
|
|
820
914
|
const activeDrop =
|
|
821
915
|
dropIndicator?.targetId === node.id ? dropIndicator.placement : null;
|
|
822
916
|
// Tracks whether the user pressed Escape to cancel rename so that the
|
|
823
917
|
// subsequent blur event does not commit the edit.
|
|
824
918
|
const renameCancelledRef = useRef(false);
|
|
825
919
|
|
|
920
|
+
const readSelectedIdsFromTree = (target: HTMLElement): string[] => {
|
|
921
|
+
const tree = target.closest('[role="tree"]');
|
|
922
|
+
if (!tree) return selectedIds.filter((id) => !id.startsWith("__"));
|
|
923
|
+
return Array.from(
|
|
924
|
+
tree.querySelectorAll<HTMLElement>(
|
|
925
|
+
'[role="treeitem"][aria-selected="true"] [data-layer-row-button][data-layer-node-id]',
|
|
926
|
+
),
|
|
927
|
+
)
|
|
928
|
+
.map((button) => button.dataset.layerNodeId)
|
|
929
|
+
.filter((id): id is string => Boolean(id && !id.startsWith("__")));
|
|
930
|
+
};
|
|
931
|
+
|
|
826
932
|
const handlePointerSelect = (event: MouseEvent<HTMLButtonElement>) => {
|
|
827
933
|
if (!selectable) return;
|
|
934
|
+
if (event.detail === 0) return;
|
|
935
|
+
const nativeEvent = event.nativeEvent;
|
|
936
|
+
const additive =
|
|
937
|
+
event.metaKey ||
|
|
938
|
+
event.ctrlKey ||
|
|
939
|
+
nativeEvent.metaKey ||
|
|
940
|
+
nativeEvent.ctrlKey;
|
|
828
941
|
onSelect(node.id, {
|
|
829
|
-
additive
|
|
942
|
+
additive,
|
|
943
|
+
currentSelectedIds: readSelectedIdsFromTree(event.currentTarget),
|
|
830
944
|
range: event.shiftKey,
|
|
831
945
|
source: "pointer",
|
|
832
946
|
});
|
|
@@ -857,11 +971,12 @@ function LayerRow({
|
|
|
857
971
|
(visibleRow) => visibleRow.rowKey === row.rowKey,
|
|
858
972
|
);
|
|
859
973
|
|
|
860
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
974
|
+
if (event.key === "Enter" || event.key === " " || event.key === "Space") {
|
|
861
975
|
event.preventDefault();
|
|
862
976
|
if (!selectable) return;
|
|
863
977
|
onSelect(node.id, {
|
|
864
978
|
additive: event.metaKey || event.ctrlKey,
|
|
979
|
+
currentSelectedIds: readSelectedIdsFromTree(event.currentTarget),
|
|
865
980
|
range: event.shiftKey,
|
|
866
981
|
source: "keyboard",
|
|
867
982
|
});
|
|
@@ -959,11 +1074,12 @@ function LayerRow({
|
|
|
959
1074
|
const intent = {
|
|
960
1075
|
draggedIds: cleanedIds,
|
|
961
1076
|
targetId: node.id,
|
|
962
|
-
placement: dropPlacementForEvent(event,
|
|
1077
|
+
placement: dropPlacementForEvent(event, canDropInside),
|
|
963
1078
|
} satisfies LayersPanelMoveIntent;
|
|
964
1079
|
if (canMoveLayer && !canMoveLayer(intent)) return;
|
|
965
1080
|
event.preventDefault();
|
|
966
1081
|
event.dataTransfer.dropEffect = "move";
|
|
1082
|
+
activeDropIntent = intent;
|
|
967
1083
|
onDropIndicatorChange(intent);
|
|
968
1084
|
};
|
|
969
1085
|
|
|
@@ -992,233 +1108,312 @@ function LayerRow({
|
|
|
992
1108
|
(id) => id && id !== node.id && !id.startsWith("__"),
|
|
993
1109
|
);
|
|
994
1110
|
if (cleanedIds.length > 0) {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1111
|
+
const storedIntent =
|
|
1112
|
+
activeDropIntent?.targetId === node.id
|
|
1113
|
+
? {
|
|
1114
|
+
...activeDropIntent,
|
|
1115
|
+
draggedIds: activeDropIntent.draggedIds.filter((id) =>
|
|
1116
|
+
cleanedIds.includes(id),
|
|
1117
|
+
),
|
|
1118
|
+
}
|
|
1119
|
+
: null;
|
|
1120
|
+
const intent =
|
|
1121
|
+
storedIntent && storedIntent.draggedIds.length > 0
|
|
1122
|
+
? storedIntent
|
|
1123
|
+
: ({
|
|
1124
|
+
draggedIds: cleanedIds,
|
|
1125
|
+
targetId: node.id,
|
|
1126
|
+
placement: dropPlacementForEvent(event, canDropInside),
|
|
1127
|
+
} satisfies LayersPanelMoveIntent);
|
|
1002
1128
|
if (!canMoveLayer || canMoveLayer(intent)) {
|
|
1003
1129
|
onMoveLayer(intent);
|
|
1004
1130
|
}
|
|
1005
1131
|
}
|
|
1132
|
+
activeDropIntent = null;
|
|
1006
1133
|
onDropIndicatorChange(null);
|
|
1007
1134
|
};
|
|
1008
1135
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
role="treeitem"
|
|
1013
|
-
aria-expanded={hasChildren ? isExpanded : undefined}
|
|
1014
|
-
aria-level={depth + 1}
|
|
1015
|
-
aria-selected={selectable ? isSelected : undefined}
|
|
1016
|
-
className="relative"
|
|
1017
|
-
draggable={draggable}
|
|
1018
|
-
onDragStart={handleDragStart}
|
|
1019
|
-
onDragOver={handleDragOver}
|
|
1020
|
-
onDragLeave={(event) => {
|
|
1021
|
-
// Only clear the indicator when the pointer truly leaves this row.
|
|
1022
|
-
// Moving into a child element fires dragleave on the outer div too, so
|
|
1023
|
-
// we suppress the clear when relatedTarget is still within this row.
|
|
1024
|
-
if (event.currentTarget.contains(event.relatedTarget as Node | null))
|
|
1025
|
-
return;
|
|
1026
|
-
onDropIndicatorChange(null);
|
|
1027
|
-
}}
|
|
1028
|
-
onDrop={handleDrop}
|
|
1029
|
-
onDragEnd={() => {
|
|
1030
|
-
activeDragState = null;
|
|
1031
|
-
onDropIndicatorChange(null);
|
|
1032
|
-
}}
|
|
1033
|
-
onMouseEnter={() => onHoverLayer?.(node.id)}
|
|
1034
|
-
onMouseLeave={() => onLeaveLayer?.(node.id)}
|
|
1035
|
-
>
|
|
1036
|
-
{activeDrop === "before" ? (
|
|
1037
|
-
<span className="pointer-events-none absolute left-2 right-2 top-0 z-10 h-px bg-[var(--design-editor-accent-color)]" />
|
|
1038
|
-
) : null}
|
|
1039
|
-
{activeDrop === "after" ? (
|
|
1040
|
-
<span className="pointer-events-none absolute bottom-0 left-2 right-2 z-10 h-px bg-[var(--design-editor-accent-color)]" />
|
|
1041
|
-
) : null}
|
|
1042
|
-
<div
|
|
1043
|
-
className={cn(
|
|
1044
|
-
"group flex h-8 items-center gap-1 rounded-[5px] pr-1 text-[12px]",
|
|
1045
|
-
activeDrop === "inside" &&
|
|
1046
|
-
"ring-1 ring-inset ring-[var(--design-editor-accent-color)]",
|
|
1047
|
-
isSelected &&
|
|
1048
|
-
"bg-[var(--design-editor-selection-color)] text-foreground",
|
|
1049
|
-
!isSelected &&
|
|
1050
|
-
isInSelectedSubtree &&
|
|
1051
|
-
"bg-[var(--design-editor-selected-subtree-color)] text-foreground/95",
|
|
1052
|
-
!isSelected &&
|
|
1053
|
-
isActiveScreen &&
|
|
1054
|
-
"bg-[var(--design-editor-active-row-color)] text-foreground hover:bg-[var(--design-editor-active-row-color)]",
|
|
1055
|
-
!isSelected &&
|
|
1056
|
-
!isInSelectedSubtree &&
|
|
1057
|
-
!isActiveScreen &&
|
|
1058
|
-
"text-foreground/90 hover:bg-[var(--design-editor-layer-hover-color)] hover:text-foreground",
|
|
1059
|
-
node.hidden && "text-muted-foreground",
|
|
1060
|
-
)}
|
|
1061
|
-
style={{ paddingLeft: 4 + depth * 28 }}
|
|
1062
|
-
>
|
|
1063
|
-
{hasChildren ? (
|
|
1064
|
-
<Button
|
|
1065
|
-
type="button"
|
|
1066
|
-
variant="ghost"
|
|
1067
|
-
size="icon"
|
|
1068
|
-
className="size-4 shrink-0 rounded-sm p-0 text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
1069
|
-
aria-label={isExpanded ? labels.collapse : labels.expand}
|
|
1070
|
-
onClick={(event) => {
|
|
1071
|
-
event.stopPropagation();
|
|
1072
|
-
onToggleExpanded(!isExpanded);
|
|
1073
|
-
}}
|
|
1074
|
-
>
|
|
1075
|
-
{isExpanded ? (
|
|
1076
|
-
<IconChevronDown className="size-4" />
|
|
1077
|
-
) : (
|
|
1078
|
-
<IconChevronRight className="size-4 rtl:-scale-x-100" />
|
|
1079
|
-
)}
|
|
1080
|
-
</Button>
|
|
1081
|
-
) : (
|
|
1082
|
-
<span className="size-4 shrink-0" />
|
|
1083
|
-
)}
|
|
1136
|
+
const showContextMenu =
|
|
1137
|
+
selectable &&
|
|
1138
|
+
(Boolean(onRename && node.renamable !== false) || lockable || hideable);
|
|
1084
1139
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1140
|
+
return (
|
|
1141
|
+
<ContextMenu>
|
|
1142
|
+
<ContextMenuTrigger asChild disabled={!showContextMenu}>
|
|
1143
|
+
<div
|
|
1144
|
+
ref={rowRef}
|
|
1145
|
+
role="treeitem"
|
|
1146
|
+
aria-expanded={hasChildren ? isExpanded : undefined}
|
|
1147
|
+
aria-level={depth + 1}
|
|
1148
|
+
aria-selected={selectable ? isSelected : undefined}
|
|
1149
|
+
className="relative min-w-full"
|
|
1150
|
+
draggable={draggable}
|
|
1151
|
+
onDragStart={handleDragStart}
|
|
1152
|
+
onDragOver={handleDragOver}
|
|
1153
|
+
onDragLeave={(event) => {
|
|
1154
|
+
// Only clear the indicator when the pointer truly leaves this row.
|
|
1155
|
+
// Moving into a child element fires dragleave on the outer div too, so
|
|
1156
|
+
// we suppress the clear when relatedTarget is still within this row.
|
|
1157
|
+
if (
|
|
1158
|
+
event.currentTarget.contains(event.relatedTarget as Node | null)
|
|
1159
|
+
)
|
|
1160
|
+
return;
|
|
1161
|
+
if (activeDropIntent?.targetId === node.id) activeDropIntent = null;
|
|
1162
|
+
onDropIndicatorChange(null);
|
|
1163
|
+
}}
|
|
1164
|
+
onDrop={handleDrop}
|
|
1165
|
+
onDragEnd={() => {
|
|
1166
|
+
activeDragState = null;
|
|
1167
|
+
activeDropIntent = null;
|
|
1168
|
+
onDropIndicatorChange(null);
|
|
1169
|
+
}}
|
|
1170
|
+
onMouseEnter={() => onHoverLayer?.(node.id)}
|
|
1171
|
+
onMouseLeave={() => onLeaveLayer?.(node.id)}
|
|
1097
1172
|
>
|
|
1098
|
-
|
|
1173
|
+
{activeDrop === "before" ? (
|
|
1174
|
+
<span className="pointer-events-none absolute left-2 right-2 top-0 z-10 h-px bg-[var(--design-editor-accent-color)]" />
|
|
1175
|
+
) : null}
|
|
1176
|
+
{activeDrop === "after" ? (
|
|
1177
|
+
<span className="pointer-events-none absolute bottom-0 left-2 right-2 z-10 h-px bg-[var(--design-editor-accent-color)]" />
|
|
1178
|
+
) : null}
|
|
1179
|
+
<div
|
|
1099
1180
|
className={cn(
|
|
1100
|
-
"
|
|
1101
|
-
|
|
1181
|
+
"group flex h-8 min-w-full items-center gap-1 rounded-[5px] pr-1 text-[12px]",
|
|
1182
|
+
activeDrop === "inside" &&
|
|
1183
|
+
"ring-1 ring-inset ring-[var(--design-editor-accent-color)]",
|
|
1184
|
+
isSelected &&
|
|
1185
|
+
"bg-[var(--design-editor-selection-color)] text-foreground",
|
|
1186
|
+
!isSelected &&
|
|
1187
|
+
isInSelectedSubtree &&
|
|
1188
|
+
"bg-[var(--design-editor-selected-subtree-color)] text-foreground/95",
|
|
1189
|
+
!isSelected &&
|
|
1190
|
+
isActiveScreen &&
|
|
1191
|
+
"bg-[var(--design-editor-active-row-color)] text-foreground hover:bg-[var(--design-editor-active-row-color)]",
|
|
1192
|
+
!isSelected &&
|
|
1193
|
+
!isInSelectedSubtree &&
|
|
1194
|
+
!isActiveScreen &&
|
|
1195
|
+
"text-foreground/90 hover:bg-[var(--design-editor-layer-hover-color)] hover:text-foreground",
|
|
1196
|
+
node.hidden && "text-muted-foreground",
|
|
1102
1197
|
)}
|
|
1198
|
+
style={{ paddingLeft: rowIndent(depth) }}
|
|
1103
1199
|
>
|
|
1104
|
-
{
|
|
1105
|
-
<LayerGlyph
|
|
1106
|
-
node={node}
|
|
1107
|
-
selected={isSelected}
|
|
1108
|
-
inSelectedSubtree={isInSelectedSubtree}
|
|
1109
|
-
/>
|
|
1110
|
-
)}
|
|
1111
|
-
</span>
|
|
1112
|
-
{isRenaming ? (
|
|
1113
|
-
<input
|
|
1114
|
-
autoFocus
|
|
1115
|
-
value={renameDraft}
|
|
1116
|
-
onClick={(event) => event.stopPropagation()}
|
|
1117
|
-
onChange={(event) => onRenameDraftChange(event.target.value)}
|
|
1118
|
-
onBlur={() => {
|
|
1119
|
-
// Escape sets renameCancelledRef before blur fires; skip commit.
|
|
1120
|
-
if (renameCancelledRef.current) {
|
|
1121
|
-
renameCancelledRef.current = false;
|
|
1122
|
-
return;
|
|
1123
|
-
}
|
|
1124
|
-
onCommitRename(node.id);
|
|
1125
|
-
}}
|
|
1126
|
-
onKeyDown={(event) => {
|
|
1127
|
-
if (event.key === "Enter") {
|
|
1128
|
-
event.preventDefault();
|
|
1129
|
-
onCommitRename(node.id);
|
|
1130
|
-
} else if (event.key === "Escape") {
|
|
1131
|
-
event.preventDefault();
|
|
1132
|
-
renameCancelledRef.current = true;
|
|
1133
|
-
onCancelRename();
|
|
1134
|
-
}
|
|
1135
|
-
}}
|
|
1136
|
-
className="h-6 min-w-0 flex-1 rounded-[4px] border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-panel-bg)] px-1.5 text-[12px] text-foreground outline-none"
|
|
1137
|
-
aria-label={labels.rename}
|
|
1138
|
-
/>
|
|
1139
|
-
) : (
|
|
1140
|
-
<span
|
|
1141
|
-
className={cn(
|
|
1142
|
-
"min-w-0 flex-1 truncate font-medium leading-none",
|
|
1143
|
-
node.hidden && "line-through",
|
|
1144
|
-
)}
|
|
1145
|
-
>
|
|
1146
|
-
{node.name}
|
|
1147
|
-
</span>
|
|
1148
|
-
)}
|
|
1149
|
-
{!isRenaming &&
|
|
1150
|
-
layerCanShowBadge(node) &&
|
|
1151
|
-
node.badge !== null &&
|
|
1152
|
-
node.badge !== undefined ? (
|
|
1153
|
-
<span className="shrink-0 rounded-sm bg-muted px-1 text-[10px] text-muted-foreground">
|
|
1154
|
-
{node.badge}
|
|
1155
|
-
</span>
|
|
1156
|
-
) : null}
|
|
1157
|
-
</button>
|
|
1158
|
-
|
|
1159
|
-
{lockable ? (
|
|
1160
|
-
<Tooltip>
|
|
1161
|
-
<TooltipTrigger asChild>
|
|
1200
|
+
{hasChildren ? (
|
|
1162
1201
|
<Button
|
|
1163
1202
|
type="button"
|
|
1164
1203
|
variant="ghost"
|
|
1165
1204
|
size="icon"
|
|
1166
|
-
className=
|
|
1167
|
-
|
|
1168
|
-
node.locked && "opacity-100",
|
|
1169
|
-
isSelected && "text-foreground",
|
|
1170
|
-
)}
|
|
1171
|
-
aria-label={node.locked ? labels.unlock : labels.lock}
|
|
1205
|
+
className="size-4 shrink-0 rounded-sm p-0 text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
1206
|
+
aria-label={isExpanded ? labels.collapse : labels.expand}
|
|
1172
1207
|
onClick={(event) => {
|
|
1173
1208
|
event.stopPropagation();
|
|
1174
|
-
|
|
1209
|
+
onToggleExpanded(!isExpanded);
|
|
1175
1210
|
}}
|
|
1176
1211
|
>
|
|
1177
|
-
{
|
|
1178
|
-
<
|
|
1212
|
+
{isExpanded ? (
|
|
1213
|
+
<IconChevronDown className="size-4" />
|
|
1179
1214
|
) : (
|
|
1180
|
-
<
|
|
1215
|
+
<IconChevronRight className="size-4 rtl:-scale-x-100" />
|
|
1181
1216
|
)}
|
|
1182
1217
|
</Button>
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
</TooltipContent>
|
|
1187
|
-
</Tooltip>
|
|
1188
|
-
) : null}
|
|
1218
|
+
) : (
|
|
1219
|
+
<span className="size-4 shrink-0" />
|
|
1220
|
+
)}
|
|
1189
1221
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1222
|
+
<button
|
|
1223
|
+
type="button"
|
|
1224
|
+
disabled={!selectable}
|
|
1225
|
+
data-layer-row-button
|
|
1226
|
+
data-layer-node-id={node.id}
|
|
1227
|
+
className={cn(
|
|
1228
|
+
"flex min-w-0 flex-1 items-center gap-2 rounded-sm px-0.5 py-0 text-left outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
|
|
1229
|
+
selectable ? "cursor-default" : "cursor-default opacity-80",
|
|
1230
|
+
)}
|
|
1231
|
+
onClick={handlePointerSelect}
|
|
1232
|
+
onDoubleClick={() => onStartRename(node)}
|
|
1233
|
+
onKeyDown={handleKeyDown}
|
|
1234
|
+
>
|
|
1235
|
+
<span
|
|
1197
1236
|
className={cn(
|
|
1198
|
-
"
|
|
1199
|
-
|
|
1200
|
-
isSelected && "text-foreground",
|
|
1237
|
+
"shrink-0 text-muted-foreground",
|
|
1238
|
+
(isSelected || isInSelectedSubtree) && "text-foreground",
|
|
1201
1239
|
)}
|
|
1202
|
-
aria-label={node.hidden ? labels.show : labels.hide}
|
|
1203
|
-
onClick={(event) => {
|
|
1204
|
-
event.stopPropagation();
|
|
1205
|
-
onToggleHidden?.(node.id, !node.hidden);
|
|
1206
|
-
}}
|
|
1207
1240
|
>
|
|
1208
|
-
{node.
|
|
1209
|
-
<
|
|
1210
|
-
|
|
1211
|
-
|
|
1241
|
+
{node.icon ?? (
|
|
1242
|
+
<LayerGlyph
|
|
1243
|
+
node={node}
|
|
1244
|
+
selected={isSelected}
|
|
1245
|
+
inSelectedSubtree={isInSelectedSubtree}
|
|
1246
|
+
/>
|
|
1212
1247
|
)}
|
|
1213
|
-
</
|
|
1214
|
-
|
|
1215
|
-
|
|
1248
|
+
</span>
|
|
1249
|
+
{isRenaming ? (
|
|
1250
|
+
<input
|
|
1251
|
+
autoFocus
|
|
1252
|
+
value={renameDraft}
|
|
1253
|
+
onClick={(event) => event.stopPropagation()}
|
|
1254
|
+
onChange={(event) => onRenameDraftChange(event.target.value)}
|
|
1255
|
+
onBlur={() => {
|
|
1256
|
+
// Escape sets renameCancelledRef before blur fires; skip commit.
|
|
1257
|
+
if (renameCancelledRef.current) {
|
|
1258
|
+
renameCancelledRef.current = false;
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
onCommitRename(node.id);
|
|
1262
|
+
}}
|
|
1263
|
+
onKeyDown={(event) => {
|
|
1264
|
+
if (event.key === "Enter") {
|
|
1265
|
+
event.preventDefault();
|
|
1266
|
+
// Stop propagation so the keydown does not bubble up to the
|
|
1267
|
+
// parent row <button>'s handleKeyDown, which would fire
|
|
1268
|
+
// onSelect and potentially trigger canvas-level side effects
|
|
1269
|
+
// (e.g. switching to overview mode or selecting a wrong layer).
|
|
1270
|
+
event.stopPropagation();
|
|
1271
|
+
onCommitRename(node.id);
|
|
1272
|
+
} else if (event.key === "Tab") {
|
|
1273
|
+
// Commit the rename on Tab (Figma behavior) and prevent the
|
|
1274
|
+
// keydown from reaching the global design hotkeys handler which
|
|
1275
|
+
// would cycle the active file when Tab fires outside an input.
|
|
1276
|
+
event.preventDefault();
|
|
1277
|
+
event.stopPropagation();
|
|
1278
|
+
onCommitRename(node.id);
|
|
1279
|
+
} else if (event.key === "Escape") {
|
|
1280
|
+
event.preventDefault();
|
|
1281
|
+
event.stopPropagation();
|
|
1282
|
+
renameCancelledRef.current = true;
|
|
1283
|
+
onCancelRename();
|
|
1284
|
+
}
|
|
1285
|
+
}}
|
|
1286
|
+
className="h-6 min-w-0 flex-1 rounded-[4px] border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-panel-bg)] px-1.5 text-[12px] text-foreground outline-none"
|
|
1287
|
+
aria-label={labels.rename}
|
|
1288
|
+
/>
|
|
1289
|
+
) : (
|
|
1290
|
+
<span
|
|
1291
|
+
className={cn(
|
|
1292
|
+
"min-w-0 flex-1 truncate font-medium leading-none",
|
|
1293
|
+
node.hidden && "line-through",
|
|
1294
|
+
)}
|
|
1295
|
+
title={node.name}
|
|
1296
|
+
>
|
|
1297
|
+
{node.name}
|
|
1298
|
+
</span>
|
|
1299
|
+
)}
|
|
1300
|
+
{!isRenaming &&
|
|
1301
|
+
layerCanShowBadge(node) &&
|
|
1302
|
+
node.badge !== null &&
|
|
1303
|
+
node.badge !== undefined ? (
|
|
1304
|
+
<span className="shrink-0 rounded-sm bg-muted px-1 text-[10px] text-muted-foreground">
|
|
1305
|
+
{node.badge}
|
|
1306
|
+
</span>
|
|
1307
|
+
) : null}
|
|
1308
|
+
</button>
|
|
1309
|
+
|
|
1310
|
+
{lockable ? (
|
|
1311
|
+
<Tooltip>
|
|
1312
|
+
<TooltipTrigger asChild>
|
|
1313
|
+
<Button
|
|
1314
|
+
type="button"
|
|
1315
|
+
variant="ghost"
|
|
1316
|
+
size="icon"
|
|
1317
|
+
className={cn(
|
|
1318
|
+
"size-5 shrink-0 rounded-sm p-0 text-muted-foreground opacity-0 hover:bg-transparent hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100",
|
|
1319
|
+
node.locked && "opacity-100",
|
|
1320
|
+
isSelected && "text-foreground",
|
|
1321
|
+
)}
|
|
1322
|
+
aria-label={node.locked ? labels.unlock : labels.lock}
|
|
1323
|
+
onClick={(event) => {
|
|
1324
|
+
event.stopPropagation();
|
|
1325
|
+
onToggleLocked?.(node.id, !node.locked);
|
|
1326
|
+
}}
|
|
1327
|
+
>
|
|
1328
|
+
{node.locked ? (
|
|
1329
|
+
<IconLock className="size-3" />
|
|
1330
|
+
) : (
|
|
1331
|
+
<IconLockOpen className="size-3" />
|
|
1332
|
+
)}
|
|
1333
|
+
</Button>
|
|
1334
|
+
</TooltipTrigger>
|
|
1335
|
+
<TooltipContent>
|
|
1336
|
+
{node.locked ? labels.unlock : labels.lock}
|
|
1337
|
+
</TooltipContent>
|
|
1338
|
+
</Tooltip>
|
|
1339
|
+
) : null}
|
|
1340
|
+
|
|
1341
|
+
{hideable ? (
|
|
1342
|
+
<Tooltip>
|
|
1343
|
+
<TooltipTrigger asChild>
|
|
1344
|
+
<Button
|
|
1345
|
+
type="button"
|
|
1346
|
+
variant="ghost"
|
|
1347
|
+
size="icon"
|
|
1348
|
+
className={cn(
|
|
1349
|
+
"size-5 shrink-0 rounded-sm p-0 text-muted-foreground opacity-0 hover:bg-transparent hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100",
|
|
1350
|
+
node.hidden && "opacity-100",
|
|
1351
|
+
isSelected && "text-foreground",
|
|
1352
|
+
)}
|
|
1353
|
+
aria-label={node.hidden ? labels.show : labels.hide}
|
|
1354
|
+
onClick={(event) => {
|
|
1355
|
+
event.stopPropagation();
|
|
1356
|
+
onToggleHidden?.(node.id, !node.hidden);
|
|
1357
|
+
}}
|
|
1358
|
+
>
|
|
1359
|
+
{node.hidden ? (
|
|
1360
|
+
<IconEyeOff className="size-3" />
|
|
1361
|
+
) : (
|
|
1362
|
+
<IconEye className="size-3" />
|
|
1363
|
+
)}
|
|
1364
|
+
</Button>
|
|
1365
|
+
</TooltipTrigger>
|
|
1366
|
+
<TooltipContent>
|
|
1367
|
+
{node.hidden ? labels.show : labels.hide}
|
|
1368
|
+
</TooltipContent>
|
|
1369
|
+
</Tooltip>
|
|
1370
|
+
) : null}
|
|
1371
|
+
</div>
|
|
1372
|
+
</div>
|
|
1373
|
+
</ContextMenuTrigger>
|
|
1374
|
+
{showContextMenu ? (
|
|
1375
|
+
<ContextMenuContent className="z-[300] min-w-[160px] text-[12px]">
|
|
1376
|
+
{onRename && node.renamable !== false ? (
|
|
1377
|
+
<ContextMenuItem
|
|
1378
|
+
className="gap-2 text-[12px]"
|
|
1379
|
+
onSelect={() => onStartRename(node)}
|
|
1380
|
+
>
|
|
1381
|
+
<IconPencil className="size-3.5 text-muted-foreground" />
|
|
1382
|
+
{labels.rename}
|
|
1383
|
+
</ContextMenuItem>
|
|
1384
|
+
) : null}
|
|
1385
|
+
{onRename && node.renamable !== false && (lockable || hideable) ? (
|
|
1386
|
+
<ContextMenuSeparator />
|
|
1387
|
+
) : null}
|
|
1388
|
+
{lockable ? (
|
|
1389
|
+
<ContextMenuItem
|
|
1390
|
+
className="gap-2 text-[12px]"
|
|
1391
|
+
onSelect={() => onToggleLocked?.(node.id, !node.locked)}
|
|
1392
|
+
>
|
|
1393
|
+
{node.locked ? (
|
|
1394
|
+
<IconLockOpen className="size-3.5 text-muted-foreground" />
|
|
1395
|
+
) : (
|
|
1396
|
+
<IconLock className="size-3.5 text-muted-foreground" />
|
|
1397
|
+
)}
|
|
1398
|
+
{node.locked ? labels.unlock : labels.lock}
|
|
1399
|
+
</ContextMenuItem>
|
|
1400
|
+
) : null}
|
|
1401
|
+
{hideable ? (
|
|
1402
|
+
<ContextMenuItem
|
|
1403
|
+
className="gap-2 text-[12px]"
|
|
1404
|
+
onSelect={() => onToggleHidden?.(node.id, !node.hidden)}
|
|
1405
|
+
>
|
|
1406
|
+
{node.hidden ? (
|
|
1407
|
+
<IconEye className="size-3.5 text-muted-foreground" />
|
|
1408
|
+
) : (
|
|
1409
|
+
<IconEyeOff className="size-3.5 text-muted-foreground" />
|
|
1410
|
+
)}
|
|
1216
1411
|
{node.hidden ? labels.show : labels.hide}
|
|
1217
|
-
</
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
</
|
|
1412
|
+
</ContextMenuItem>
|
|
1413
|
+
) : null}
|
|
1414
|
+
</ContextMenuContent>
|
|
1415
|
+
) : null}
|
|
1416
|
+
</ContextMenu>
|
|
1222
1417
|
);
|
|
1223
1418
|
}
|
|
1224
1419
|
|
|
@@ -1580,6 +1775,18 @@ function ElementLayerGlyph({ className }: { className?: string }) {
|
|
|
1580
1775
|
);
|
|
1581
1776
|
}
|
|
1582
1777
|
|
|
1778
|
+
function layerCanDropInside(node: LayersPanelNode, hasChildren: boolean) {
|
|
1779
|
+
return (
|
|
1780
|
+
hasChildren ||
|
|
1781
|
+
Boolean(node.layout?.isFlexContainer || node.layout?.isGridContainer) ||
|
|
1782
|
+
node.type === "file" ||
|
|
1783
|
+
node.type === "screen" ||
|
|
1784
|
+
node.type === "frame" ||
|
|
1785
|
+
node.type === "group" ||
|
|
1786
|
+
node.type === "section"
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1583
1790
|
function dropPlacementForEvent(
|
|
1584
1791
|
event: DragEvent<HTMLDivElement>,
|
|
1585
1792
|
canDropInside: boolean,
|