@agent-native/core 0.80.8 → 0.80.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +6 -0
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +8 -0
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +10 -2
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +10 -2
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +7 -0
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +7 -0
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +7 -0
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +9 -2
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +6 -0
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +6 -0
- package/corpus/core/docs/content/template-design.mdx +9 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/mcp.ts +10 -0
- package/corpus/core/src/cli/skills.ts +58 -12
- package/corpus/core/src/client/error-format.ts +25 -0
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +13 -11
- package/corpus/core/src/client/guided-questions.tsx +32 -4
- package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +33 -1
- package/corpus/core/src/client/resources/BuiltinCapabilityDetail.tsx +8 -0
- package/corpus/core/src/client/resources/use-builtin-capabilities.ts +4 -2
- package/corpus/core/src/client/session-replay.ts +34 -0
- package/corpus/core/src/client/sharing/ShareButton.tsx +106 -21
- package/corpus/core/src/coding-tools/index.ts +1 -1
- package/corpus/core/src/mcp/index.ts +2 -0
- package/corpus/core/src/mcp/screen-memory-stdio.ts +290 -0
- package/corpus/core/src/mcp-client/builtin-capabilities.ts +13 -1
- package/corpus/core/src/mcp-client/index.ts +9 -0
- package/corpus/core/src/mcp-client/screen-memory-local.ts +461 -0
- package/corpus/core/src/onboarding/types.ts +7 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +25 -0
- package/corpus/core/src/server/agents-bundle.ts +35 -7
- package/corpus/core/src/styles/agent-native.css +5 -0
- package/corpus/core/src/templates/default/app/global.css +35 -35
- package/corpus/templates/analytics/AGENTS.md +6 -0
- package/corpus/templates/analytics/actions/github-repo-files.ts +9 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +4 -4
- package/corpus/templates/analytics/app/global.css +144 -40
- package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +1 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +4 -4
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -9
- package/corpus/templates/analytics/changelog/2026-06-29-agents-can-search-and-read-connected-github-repositories-whe.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-sessions-and-explorer-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
- package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/assets/app/components/library/LibraryPresetGrid.tsx +1 -1
- package/corpus/templates/assets/app/global.css +99 -35
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +4 -4
- package/corpus/templates/assets/app/routes/library.tsx +4 -4
- package/corpus/templates/assets/changelog/2026-06-29-asset-library-and-detail-layouts-adapt-when-the-agent-sidebar.md +6 -0
- package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/brain/app/global.css +63 -35
- package/corpus/templates/brain/app/routes/search.tsx +1 -1
- package/corpus/templates/brain/app/routes/settings.tsx +1 -1
- package/corpus/templates/brain/app/routes/sources.tsx +2 -2
- package/corpus/templates/brain/changelog/2026-06-29-search-settings-and-source-layouts-adapt-when-the-agent-sidebar.md +6 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +4 -4
- package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +3 -2
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -2
- package/corpus/templates/calendar/app/global.css +69 -35
- package/corpus/templates/calendar/changelog/2026-06-29-event-and-find-time-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
- package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/chat/app/global.css +35 -35
- package/corpus/templates/chat/changelog/2026-06-29-chat-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/clips/AGENTS.md +13 -0
- package/corpus/templates/clips/actions/get-screen-memory-status.ts +12 -0
- package/corpus/templates/clips/actions/query-screen-memory-context.ts +29 -0
- package/corpus/templates/clips/actions/view-screen.ts +2 -2
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/global.css +47 -35
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +3 -3
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +45 -18
- package/corpus/templates/clips/changelog/2026-06-29-meeting-detail-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-29-screen-memory-keeps-a-local-rolling-screen-buffer-for-agent-context.md +5 -0
- package/corpus/templates/clips/changelog/2026-06-29-video-chat-is-easier-to-find-on-recording-pages-and-s3-setup.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/styles.css +39 -39
- package/corpus/templates/clips/desktop/src/app.tsx +290 -1
- package/corpus/templates/clips/desktop/src/shared/config.ts +83 -0
- package/corpus/templates/clips/desktop/src/styles.css +67 -52
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +50 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +17 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +13 -13
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +990 -0
- package/corpus/templates/clips/server/plugins/onboarding.ts +2 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +3 -3
- package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/content/app/global.css +29 -15
- package/corpus/templates/content/changelog/2026-06-29-database-gallery-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +25 -20
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -0
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +8 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +13 -0
- package/corpus/templates/design/AGENTS.md +20 -12
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1054 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +24 -4
- package/corpus/templates/design/actions/export-coding-handoff.ts +12 -4
- package/corpus/templates/design/actions/generate-design.ts +6 -21
- package/corpus/templates/design/actions/generate-screens.ts +43 -2
- package/corpus/templates/design/actions/get-design.ts +1 -0
- package/corpus/templates/design/actions/present-design-variants.ts +355 -30
- package/corpus/templates/design/actions/view-screen.ts +75 -31
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +329 -69
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +1 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +479 -77
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +380 -216
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1050 -196
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +83 -206
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +1 -1
- package/corpus/templates/design/app/components/design/index.ts +0 -1
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +225 -133
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +122 -11
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +24 -5
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +57 -1
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +37 -12
- package/corpus/templates/design/app/components/design/inspector/index.ts +1 -0
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +12 -5
- package/corpus/templates/design/app/components/design/types.ts +9 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +23 -5
- package/corpus/templates/design/app/global.css +77 -49
- package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +8 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +45 -3
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2602 -657
- package/corpus/templates/design/app/root.tsx +1 -1
- package/corpus/templates/design/changelog/2026-06-29-containers-no-longer-block-selecting-nested-elements-and-nor.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-agents-now-generate-and-refine-variants-from-stronger.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-questions-now-appear-as-a-cleaner-canvas-intake-with-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-variant-directions-now-appear-as-board-screens-with-chat-buttons.md +5 -0
- package/corpus/templates/design/changelog/2026-06-29-element-selection-no-longer-snaps-back-to-the-previous-layer.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-frame-resize-measurements-now-stay-beside-the-cursor-while-d.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-inspector-number-fields-have-cleaner-tooltips-and-draggable-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-locking-layer-nesting-generated-question-intake-and-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-names-in-the-sidebar-can-scroll-horizontally-instead-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-nesting-in-the-sidebar-now-uses-tighter-spacing-so-nam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layers-no-longer-repeat-document-wrapper-rows-when-a-screen-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-overview-previews-now-keep-element-hover-and-selection-tied-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-pressing-escape-now-cancels-a-stuck-screen-drag-in-the-overv.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-is-easier-to-find-and-named-screens-open-dir.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-only-highlights-screens-when-the-frame-or-ti.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-previews-now-resize-to-match-the-selected-de.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-now-uses-a-pointer-cursor-instead-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-outlines-now-stay-locked-to-the-se.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-zoom-now-reports-real-screen-scale-separatel.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-feels-smoother-and-selection-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-now-stays-on-the-overview-canvas-ins.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-selecting-a-nested-layer-from-all-screens-now-keeps-you-in-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-share-now-groups-link-sharing-exports-and-agent-handoff.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-design-editor-loading-skeleton-now-uses-a-softer-charcoa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-device-preview-menu-now-sits-next-to-the-preview-button-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-inspector-stays-read-only-while-an-empty-design-is-still.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-missing-design-view-now-matches-the-rest-of-the-editor-c.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-icon-and-tool-option-menus-better-match-the-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-typography-controls-now-keep-the-font-name-visible-and-move-.md +6 -0
- package/corpus/templates/design/e2e/README.md +3 -3
- package/corpus/templates/design/e2e/global-setup.ts +11 -22
- package/corpus/templates/design/e2e/helpers.ts +122 -11
- package/corpus/templates/design/playwright.config.ts +9 -3
- package/corpus/templates/design/server/lib/coding-handoff.ts +126 -2
- package/corpus/templates/design/server/plugins/core-routes.ts +1 -2
- package/corpus/templates/design/server/routes/api/design-handoff/[id].zip.get.ts +86 -0
- package/corpus/templates/design/shared/code-layer.ts +125 -22
- package/corpus/templates/design/shared/generation-session.ts +41 -0
- package/corpus/templates/dispatch/app/global.css +79 -35
- package/corpus/templates/dispatch/app/routes/integrations.tsx +8 -7
- package/corpus/templates/dispatch/changelog/2026-06-29-integration-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/forms/app/components/layout/Layout.tsx +6 -2
- package/corpus/templates/forms/app/global.css +64 -35
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +2 -2
- package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +3 -3
- package/corpus/templates/forms/changelog/2026-06-29-form-list-and-insight-layouts-adapt-when-the-agent-sidebar.md +6 -0
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +3 -1
- package/corpus/templates/macros/app/global.css +25 -9
- package/corpus/templates/macros/changelog/2026-06-29-macro-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -2
- package/corpus/templates/mail/app/global.css +42 -36
- package/corpus/templates/mail/app/pages/InboxPage.tsx +1 -1
- package/corpus/templates/mail/changelog/2026-06-29-the-contact-panel-now-adapts-to-the-available-mail-pane-width.md +6 -0
- package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/plan/app/components/plan/DocumentArea.tsx +6 -7
- package/corpus/templates/plan/app/global.css +74 -42
- package/corpus/templates/plan/changelog/2026-06-29-plan-document-blocks-adapt-to-the-available-document-width.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-29-plan-titles-and-summaries-can-be-edited-inline-without-focus.md +6 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/slides/app/global.css +49 -33
- package/corpus/templates/slides/changelog/2026-06-29-slide-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/corpus/templates/videos/app/components/layout/Layout.tsx +3 -1
- package/corpus/templates/videos/app/global.css +35 -35
- package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +10 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +58 -12
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +17 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +3 -4
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/guided-questions.d.ts +4 -0
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +16 -7
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.js +26 -1
- package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
- package/dist/client/resources/BuiltinCapabilityDetail.d.ts.map +1 -1
- package/dist/client/resources/BuiltinCapabilityDetail.js +1 -1
- package/dist/client/resources/BuiltinCapabilityDetail.js.map +1 -1
- package/dist/client/resources/use-builtin-capabilities.d.ts +1 -1
- package/dist/client/resources/use-builtin-capabilities.d.ts.map +1 -1
- package/dist/client/resources/use-builtin-capabilities.js +2 -1
- package/dist/client/resources/use-builtin-capabilities.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +31 -0
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +14 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +36 -7
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/coding-tools/index.js +1 -1
- package/dist/coding-tools/index.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +6 -0
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -0
- package/dist/mcp/screen-memory-stdio.js +213 -0
- package/dist/mcp/screen-memory-stdio.js.map +1 -0
- package/dist/mcp-client/builtin-capabilities.d.ts +1 -1
- package/dist/mcp-client/builtin-capabilities.d.ts.map +1 -1
- package/dist/mcp-client/builtin-capabilities.js +10 -0
- package/dist/mcp-client/builtin-capabilities.js.map +1 -1
- package/dist/mcp-client/index.d.ts +1 -0
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +1 -0
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/screen-memory-local.d.ts +61 -0
- package/dist/mcp-client/screen-memory-local.d.ts.map +1 -0
- package/dist/mcp-client/screen-memory-local.js +336 -0
- package/dist/mcp-client/screen-memory-local.js.map +1 -0
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +8 -8
- package/dist/onboarding/types.d.ts +7 -0
- package/dist/onboarding/types.d.ts.map +1 -1
- package/dist/onboarding/types.js.map +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +26 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agents-bundle.d.ts.map +1 -1
- package/dist/server/agents-bundle.js +22 -7
- package/dist/server/agents-bundle.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +5 -0
- package/dist/templates/default/app/global.css +35 -35
- package/docs/content/locales/ar-SA/template-design.mdx +6 -0
- package/docs/content/locales/de-DE/template-design.mdx +8 -0
- package/docs/content/locales/es-ES/template-design.mdx +10 -2
- package/docs/content/locales/fr-FR/template-design.mdx +10 -2
- package/docs/content/locales/hi-IN/template-design.mdx +7 -0
- package/docs/content/locales/ja-JP/template-design.mdx +7 -0
- package/docs/content/locales/ko-KR/template-design.mdx +7 -0
- package/docs/content/locales/pt-BR/template-design.mdx +9 -2
- package/docs/content/locales/zh-CN/template-design.mdx +6 -0
- package/docs/content/locales/zh-TW/template-design.mdx +6 -0
- package/docs/content/template-design.mdx +9 -2
- package/package.json +1 -1
- package/src/templates/default/app/global.css +35 -35
- package/corpus/templates/design/app/components/design/VariantGrid.tsx +0 -189
- package/corpus/templates/design/app/components/design/VariantHandoffCard.tsx +0 -115
- package/corpus/templates/design/app/hooks/use-variant-flow.ts +0 -342
|
@@ -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[];
|
|
@@ -185,6 +200,7 @@ const SECTION_ELEMENT_ID = "__design_layers_elements__";
|
|
|
185
200
|
// Module-level drag state: dataTransfer.getData() returns "" during dragover
|
|
186
201
|
// per spec; the source row stores the drag payload here on dragstart instead.
|
|
187
202
|
let activeDragState: { sourceId: string; draggedIds: string[] } | null = null;
|
|
203
|
+
let activeDropIntent: LayersPanelMoveIntent | null = null;
|
|
188
204
|
|
|
189
205
|
const ROW_BASE_INDENT = 4;
|
|
190
206
|
const ROW_INDENT_STEP = 28;
|
|
@@ -198,6 +214,7 @@ function defaultLabels(t: ReturnType<typeof useT>): LayersPanelLabels {
|
|
|
198
214
|
return {
|
|
199
215
|
title: t("layersPanel.title"),
|
|
200
216
|
screens: t("layersPanel.screens"),
|
|
217
|
+
allScreens: t("layersPanel.allScreens"),
|
|
201
218
|
screenOverview: t("designEditor.screenOverview"),
|
|
202
219
|
addScreen: t("layersPanel.addScreen"),
|
|
203
220
|
searchPlaceholder: t("layersPanel.searchPlaceholder"),
|
|
@@ -406,6 +423,7 @@ function layerCanShowBadge(node: LayersPanelNode) {
|
|
|
406
423
|
export function LayersPanel({
|
|
407
424
|
screens,
|
|
408
425
|
activeScreenId,
|
|
426
|
+
screenOverviewActive = false,
|
|
409
427
|
files,
|
|
410
428
|
layers,
|
|
411
429
|
codeLayers,
|
|
@@ -432,6 +450,7 @@ export function LayersPanel({
|
|
|
432
450
|
const t = useT();
|
|
433
451
|
const labels = useMemo(() => mergeLabels(labelsProp, t), [labelsProp, t]);
|
|
434
452
|
const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
|
|
453
|
+
const selectedIdsRef = useRef<readonly string[]>(selectedIds);
|
|
435
454
|
const expandedIdSet = useMemo(() => new Set(expandedIds), [expandedIds]);
|
|
436
455
|
const lastSelectionAnchorRef = useRef<string | null>(selectedIds[0] ?? null);
|
|
437
456
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -467,6 +486,10 @@ export function LayersPanel({
|
|
|
467
486
|
[roots, selectedIdSet],
|
|
468
487
|
);
|
|
469
488
|
|
|
489
|
+
useLayoutEffect(() => {
|
|
490
|
+
selectedIdsRef.current = selectedIds;
|
|
491
|
+
}, [selectedIds]);
|
|
492
|
+
|
|
470
493
|
useEffect(() => {
|
|
471
494
|
if (selectedAncestorIds.length === 0) return;
|
|
472
495
|
const next = new Set(expandedIds);
|
|
@@ -513,10 +536,14 @@ export function LayersPanel({
|
|
|
513
536
|
id: string,
|
|
514
537
|
options: {
|
|
515
538
|
additive: boolean;
|
|
539
|
+
currentSelectedIds?: string[];
|
|
516
540
|
range: boolean;
|
|
517
541
|
source: "keyboard" | "pointer";
|
|
518
542
|
},
|
|
519
543
|
) => {
|
|
544
|
+
const currentSelectedIds =
|
|
545
|
+
options.currentSelectedIds ?? selectedIdsRef.current;
|
|
546
|
+
const currentSelectedIdSet = new Set(currentSelectedIds);
|
|
520
547
|
let nextIds: string[];
|
|
521
548
|
if (options.range && lastSelectionAnchorRef.current) {
|
|
522
549
|
let anchor = lastSelectionAnchorRef.current;
|
|
@@ -538,19 +565,18 @@ export function LayersPanel({
|
|
|
538
565
|
const [start, end] = from < to ? [from, to] : [to, from];
|
|
539
566
|
const rangeIds = selectableVisibleIds.slice(start, end + 1);
|
|
540
567
|
nextIds = options.additive
|
|
541
|
-
? Array.from(new Set([...
|
|
568
|
+
? Array.from(new Set([...currentSelectedIds, ...rangeIds]))
|
|
542
569
|
: rangeIds;
|
|
543
570
|
} else {
|
|
544
571
|
nextIds = [id];
|
|
545
572
|
}
|
|
546
573
|
} else if (options.additive) {
|
|
547
|
-
nextIds =
|
|
548
|
-
?
|
|
549
|
-
: [...
|
|
574
|
+
nextIds = currentSelectedIdSet.has(id)
|
|
575
|
+
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
576
|
+
: [...currentSelectedIds, id];
|
|
550
577
|
} else {
|
|
551
578
|
nextIds = [id];
|
|
552
579
|
}
|
|
553
|
-
|
|
554
580
|
// Only advance the anchor on plain clicks; Shift+clicks extend from the
|
|
555
581
|
// existing anchor so the pivot stays fixed across consecutive range clicks.
|
|
556
582
|
if (!options.range) {
|
|
@@ -558,7 +584,7 @@ export function LayersPanel({
|
|
|
558
584
|
}
|
|
559
585
|
onSelectionChange(nextIds, { id, selectedIds: nextIds, ...options });
|
|
560
586
|
},
|
|
561
|
-
[onSelectionChange, selectableVisibleIds
|
|
587
|
+
[onSelectionChange, selectableVisibleIds],
|
|
562
588
|
);
|
|
563
589
|
|
|
564
590
|
const commitRename = useCallback(
|
|
@@ -642,12 +668,6 @@ export function LayersPanel({
|
|
|
642
668
|
{labels.screens}
|
|
643
669
|
</h2>
|
|
644
670
|
<div className="flex items-center gap-0.5 text-muted-foreground">
|
|
645
|
-
<IconTooltipButton
|
|
646
|
-
label={labels.screenOverview}
|
|
647
|
-
onClick={onScreenOverview}
|
|
648
|
-
>
|
|
649
|
-
<IconLayoutGrid className="size-3.5" />
|
|
650
|
-
</IconTooltipButton>
|
|
651
671
|
<IconTooltipButton
|
|
652
672
|
label={labels.searchPlaceholder}
|
|
653
673
|
onClick={openSearch}
|
|
@@ -663,9 +683,30 @@ export function LayersPanel({
|
|
|
663
683
|
</IconTooltipButton>
|
|
664
684
|
</div>
|
|
665
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)]" />
|
|
666
706
|
<div className="space-y-0.5 px-2">
|
|
667
707
|
{screenRows.map((screen) => {
|
|
668
|
-
const isActive =
|
|
708
|
+
const isActive =
|
|
709
|
+
!screenOverviewActive && screen.id === activeScreenId;
|
|
669
710
|
return (
|
|
670
711
|
<button
|
|
671
712
|
key={screen.id}
|
|
@@ -676,6 +717,7 @@ export function LayersPanel({
|
|
|
676
717
|
? "bg-[var(--design-editor-active-row-color)] text-foreground"
|
|
677
718
|
: "text-foreground/85 hover:bg-[var(--design-editor-active-row-color)] hover:text-foreground",
|
|
678
719
|
)}
|
|
720
|
+
aria-current={isActive ? "page" : undefined}
|
|
679
721
|
onClick={() => onScreenSelect?.(screen.id)}
|
|
680
722
|
title={screen.filename ?? screen.name}
|
|
681
723
|
>
|
|
@@ -740,9 +782,13 @@ export function LayersPanel({
|
|
|
740
782
|
</div>
|
|
741
783
|
) : null}
|
|
742
784
|
|
|
743
|
-
<div className="min-h-0 flex-1 overflow-
|
|
785
|
+
<div className="min-h-0 flex-1 overflow-auto overscroll-contain py-2">
|
|
744
786
|
{visibleRows.length ? (
|
|
745
|
-
<div
|
|
787
|
+
<div
|
|
788
|
+
className="w-max min-w-full px-2"
|
|
789
|
+
role="tree"
|
|
790
|
+
aria-label={labels.title}
|
|
791
|
+
>
|
|
746
792
|
{visibleRows.map((row) => (
|
|
747
793
|
<LayerRow
|
|
748
794
|
key={row.rowKey}
|
|
@@ -766,6 +812,7 @@ export function LayersPanel({
|
|
|
766
812
|
onCommitRename={commitRename}
|
|
767
813
|
onCancelRename={() => setRenamingId(null)}
|
|
768
814
|
onStartRename={startRename}
|
|
815
|
+
onRename={onRename}
|
|
769
816
|
onSelect={selectNode}
|
|
770
817
|
onToggleExpanded={(expanded) =>
|
|
771
818
|
onExpandedIdsChange(
|
|
@@ -809,6 +856,7 @@ function LayerRow({
|
|
|
809
856
|
onCommitRename,
|
|
810
857
|
onCancelRename,
|
|
811
858
|
onStartRename,
|
|
859
|
+
onRename,
|
|
812
860
|
onSelect,
|
|
813
861
|
onToggleExpanded,
|
|
814
862
|
onToggleLocked,
|
|
@@ -835,10 +883,12 @@ function LayerRow({
|
|
|
835
883
|
onCommitRename: (id: string) => void;
|
|
836
884
|
onCancelRename: () => void;
|
|
837
885
|
onStartRename: (node: LayersPanelNode) => void;
|
|
886
|
+
onRename?: (id: string, name: string) => void;
|
|
838
887
|
onSelect: (
|
|
839
888
|
id: string,
|
|
840
889
|
options: {
|
|
841
890
|
additive: boolean;
|
|
891
|
+
currentSelectedIds?: string[];
|
|
842
892
|
range: boolean;
|
|
843
893
|
source: "keyboard" | "pointer";
|
|
844
894
|
},
|
|
@@ -860,16 +910,37 @@ function LayerRow({
|
|
|
860
910
|
const lockable = node.lockable !== false && Boolean(onToggleLocked);
|
|
861
911
|
const hideable = node.hideable !== false && Boolean(onToggleHidden);
|
|
862
912
|
const draggable = selectable && Boolean(onMoveLayer);
|
|
913
|
+
const canDropInside = layerCanDropInside(node, hasChildren);
|
|
863
914
|
const activeDrop =
|
|
864
915
|
dropIndicator?.targetId === node.id ? dropIndicator.placement : null;
|
|
865
916
|
// Tracks whether the user pressed Escape to cancel rename so that the
|
|
866
917
|
// subsequent blur event does not commit the edit.
|
|
867
918
|
const renameCancelledRef = useRef(false);
|
|
868
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
|
+
|
|
869
932
|
const handlePointerSelect = (event: MouseEvent<HTMLButtonElement>) => {
|
|
870
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;
|
|
871
941
|
onSelect(node.id, {
|
|
872
|
-
additive
|
|
942
|
+
additive,
|
|
943
|
+
currentSelectedIds: readSelectedIdsFromTree(event.currentTarget),
|
|
873
944
|
range: event.shiftKey,
|
|
874
945
|
source: "pointer",
|
|
875
946
|
});
|
|
@@ -900,11 +971,12 @@ function LayerRow({
|
|
|
900
971
|
(visibleRow) => visibleRow.rowKey === row.rowKey,
|
|
901
972
|
);
|
|
902
973
|
|
|
903
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
974
|
+
if (event.key === "Enter" || event.key === " " || event.key === "Space") {
|
|
904
975
|
event.preventDefault();
|
|
905
976
|
if (!selectable) return;
|
|
906
977
|
onSelect(node.id, {
|
|
907
978
|
additive: event.metaKey || event.ctrlKey,
|
|
979
|
+
currentSelectedIds: readSelectedIdsFromTree(event.currentTarget),
|
|
908
980
|
range: event.shiftKey,
|
|
909
981
|
source: "keyboard",
|
|
910
982
|
});
|
|
@@ -1002,11 +1074,12 @@ function LayerRow({
|
|
|
1002
1074
|
const intent = {
|
|
1003
1075
|
draggedIds: cleanedIds,
|
|
1004
1076
|
targetId: node.id,
|
|
1005
|
-
placement: dropPlacementForEvent(event,
|
|
1077
|
+
placement: dropPlacementForEvent(event, canDropInside),
|
|
1006
1078
|
} satisfies LayersPanelMoveIntent;
|
|
1007
1079
|
if (canMoveLayer && !canMoveLayer(intent)) return;
|
|
1008
1080
|
event.preventDefault();
|
|
1009
1081
|
event.dataTransfer.dropEffect = "move";
|
|
1082
|
+
activeDropIntent = intent;
|
|
1010
1083
|
onDropIndicatorChange(intent);
|
|
1011
1084
|
};
|
|
1012
1085
|
|
|
@@ -1035,233 +1108,312 @@ function LayerRow({
|
|
|
1035
1108
|
(id) => id && id !== node.id && !id.startsWith("__"),
|
|
1036
1109
|
);
|
|
1037
1110
|
if (cleanedIds.length > 0) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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);
|
|
1045
1128
|
if (!canMoveLayer || canMoveLayer(intent)) {
|
|
1046
1129
|
onMoveLayer(intent);
|
|
1047
1130
|
}
|
|
1048
1131
|
}
|
|
1132
|
+
activeDropIntent = null;
|
|
1049
1133
|
onDropIndicatorChange(null);
|
|
1050
1134
|
};
|
|
1051
1135
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
role="treeitem"
|
|
1056
|
-
aria-expanded={hasChildren ? isExpanded : undefined}
|
|
1057
|
-
aria-level={depth + 1}
|
|
1058
|
-
aria-selected={selectable ? isSelected : undefined}
|
|
1059
|
-
className="relative"
|
|
1060
|
-
draggable={draggable}
|
|
1061
|
-
onDragStart={handleDragStart}
|
|
1062
|
-
onDragOver={handleDragOver}
|
|
1063
|
-
onDragLeave={(event) => {
|
|
1064
|
-
// Only clear the indicator when the pointer truly leaves this row.
|
|
1065
|
-
// Moving into a child element fires dragleave on the outer div too, so
|
|
1066
|
-
// we suppress the clear when relatedTarget is still within this row.
|
|
1067
|
-
if (event.currentTarget.contains(event.relatedTarget as Node | null))
|
|
1068
|
-
return;
|
|
1069
|
-
onDropIndicatorChange(null);
|
|
1070
|
-
}}
|
|
1071
|
-
onDrop={handleDrop}
|
|
1072
|
-
onDragEnd={() => {
|
|
1073
|
-
activeDragState = null;
|
|
1074
|
-
onDropIndicatorChange(null);
|
|
1075
|
-
}}
|
|
1076
|
-
onMouseEnter={() => onHoverLayer?.(node.id)}
|
|
1077
|
-
onMouseLeave={() => onLeaveLayer?.(node.id)}
|
|
1078
|
-
>
|
|
1079
|
-
{activeDrop === "before" ? (
|
|
1080
|
-
<span className="pointer-events-none absolute left-2 right-2 top-0 z-10 h-px bg-[var(--design-editor-accent-color)]" />
|
|
1081
|
-
) : null}
|
|
1082
|
-
{activeDrop === "after" ? (
|
|
1083
|
-
<span className="pointer-events-none absolute bottom-0 left-2 right-2 z-10 h-px bg-[var(--design-editor-accent-color)]" />
|
|
1084
|
-
) : null}
|
|
1085
|
-
<div
|
|
1086
|
-
className={cn(
|
|
1087
|
-
"group flex h-8 items-center gap-1 rounded-[5px] pr-1 text-[12px]",
|
|
1088
|
-
activeDrop === "inside" &&
|
|
1089
|
-
"ring-1 ring-inset ring-[var(--design-editor-accent-color)]",
|
|
1090
|
-
isSelected &&
|
|
1091
|
-
"bg-[var(--design-editor-selection-color)] text-foreground",
|
|
1092
|
-
!isSelected &&
|
|
1093
|
-
isInSelectedSubtree &&
|
|
1094
|
-
"bg-[var(--design-editor-selected-subtree-color)] text-foreground/95",
|
|
1095
|
-
!isSelected &&
|
|
1096
|
-
isActiveScreen &&
|
|
1097
|
-
"bg-[var(--design-editor-active-row-color)] text-foreground hover:bg-[var(--design-editor-active-row-color)]",
|
|
1098
|
-
!isSelected &&
|
|
1099
|
-
!isInSelectedSubtree &&
|
|
1100
|
-
!isActiveScreen &&
|
|
1101
|
-
"text-foreground/90 hover:bg-[var(--design-editor-layer-hover-color)] hover:text-foreground",
|
|
1102
|
-
node.hidden && "text-muted-foreground",
|
|
1103
|
-
)}
|
|
1104
|
-
style={{ paddingLeft: rowIndent(depth) }}
|
|
1105
|
-
>
|
|
1106
|
-
{hasChildren ? (
|
|
1107
|
-
<Button
|
|
1108
|
-
type="button"
|
|
1109
|
-
variant="ghost"
|
|
1110
|
-
size="icon"
|
|
1111
|
-
className="size-4 shrink-0 rounded-sm p-0 text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
1112
|
-
aria-label={isExpanded ? labels.collapse : labels.expand}
|
|
1113
|
-
onClick={(event) => {
|
|
1114
|
-
event.stopPropagation();
|
|
1115
|
-
onToggleExpanded(!isExpanded);
|
|
1116
|
-
}}
|
|
1117
|
-
>
|
|
1118
|
-
{isExpanded ? (
|
|
1119
|
-
<IconChevronDown className="size-4" />
|
|
1120
|
-
) : (
|
|
1121
|
-
<IconChevronRight className="size-4 rtl:-scale-x-100" />
|
|
1122
|
-
)}
|
|
1123
|
-
</Button>
|
|
1124
|
-
) : (
|
|
1125
|
-
<span className="size-4 shrink-0" />
|
|
1126
|
-
)}
|
|
1136
|
+
const showContextMenu =
|
|
1137
|
+
selectable &&
|
|
1138
|
+
(Boolean(onRename && node.renamable !== false) || lockable || hideable);
|
|
1127
1139
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
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)}
|
|
1140
1172
|
>
|
|
1141
|
-
|
|
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
|
|
1142
1180
|
className={cn(
|
|
1143
|
-
"
|
|
1144
|
-
|
|
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",
|
|
1145
1197
|
)}
|
|
1198
|
+
style={{ paddingLeft: rowIndent(depth) }}
|
|
1146
1199
|
>
|
|
1147
|
-
{
|
|
1148
|
-
<LayerGlyph
|
|
1149
|
-
node={node}
|
|
1150
|
-
selected={isSelected}
|
|
1151
|
-
inSelectedSubtree={isInSelectedSubtree}
|
|
1152
|
-
/>
|
|
1153
|
-
)}
|
|
1154
|
-
</span>
|
|
1155
|
-
{isRenaming ? (
|
|
1156
|
-
<input
|
|
1157
|
-
autoFocus
|
|
1158
|
-
value={renameDraft}
|
|
1159
|
-
onClick={(event) => event.stopPropagation()}
|
|
1160
|
-
onChange={(event) => onRenameDraftChange(event.target.value)}
|
|
1161
|
-
onBlur={() => {
|
|
1162
|
-
// Escape sets renameCancelledRef before blur fires; skip commit.
|
|
1163
|
-
if (renameCancelledRef.current) {
|
|
1164
|
-
renameCancelledRef.current = false;
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
onCommitRename(node.id);
|
|
1168
|
-
}}
|
|
1169
|
-
onKeyDown={(event) => {
|
|
1170
|
-
if (event.key === "Enter") {
|
|
1171
|
-
event.preventDefault();
|
|
1172
|
-
onCommitRename(node.id);
|
|
1173
|
-
} else if (event.key === "Escape") {
|
|
1174
|
-
event.preventDefault();
|
|
1175
|
-
renameCancelledRef.current = true;
|
|
1176
|
-
onCancelRename();
|
|
1177
|
-
}
|
|
1178
|
-
}}
|
|
1179
|
-
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"
|
|
1180
|
-
aria-label={labels.rename}
|
|
1181
|
-
/>
|
|
1182
|
-
) : (
|
|
1183
|
-
<span
|
|
1184
|
-
className={cn(
|
|
1185
|
-
"min-w-0 flex-1 truncate font-medium leading-none",
|
|
1186
|
-
node.hidden && "line-through",
|
|
1187
|
-
)}
|
|
1188
|
-
>
|
|
1189
|
-
{node.name}
|
|
1190
|
-
</span>
|
|
1191
|
-
)}
|
|
1192
|
-
{!isRenaming &&
|
|
1193
|
-
layerCanShowBadge(node) &&
|
|
1194
|
-
node.badge !== null &&
|
|
1195
|
-
node.badge !== undefined ? (
|
|
1196
|
-
<span className="shrink-0 rounded-sm bg-muted px-1 text-[10px] text-muted-foreground">
|
|
1197
|
-
{node.badge}
|
|
1198
|
-
</span>
|
|
1199
|
-
) : null}
|
|
1200
|
-
</button>
|
|
1201
|
-
|
|
1202
|
-
{lockable ? (
|
|
1203
|
-
<Tooltip>
|
|
1204
|
-
<TooltipTrigger asChild>
|
|
1200
|
+
{hasChildren ? (
|
|
1205
1201
|
<Button
|
|
1206
1202
|
type="button"
|
|
1207
1203
|
variant="ghost"
|
|
1208
1204
|
size="icon"
|
|
1209
|
-
className=
|
|
1210
|
-
|
|
1211
|
-
node.locked && "opacity-100",
|
|
1212
|
-
isSelected && "text-foreground",
|
|
1213
|
-
)}
|
|
1214
|
-
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}
|
|
1215
1207
|
onClick={(event) => {
|
|
1216
1208
|
event.stopPropagation();
|
|
1217
|
-
|
|
1209
|
+
onToggleExpanded(!isExpanded);
|
|
1218
1210
|
}}
|
|
1219
1211
|
>
|
|
1220
|
-
{
|
|
1221
|
-
<
|
|
1212
|
+
{isExpanded ? (
|
|
1213
|
+
<IconChevronDown className="size-4" />
|
|
1222
1214
|
) : (
|
|
1223
|
-
<
|
|
1215
|
+
<IconChevronRight className="size-4 rtl:-scale-x-100" />
|
|
1224
1216
|
)}
|
|
1225
1217
|
</Button>
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
</TooltipContent>
|
|
1230
|
-
</Tooltip>
|
|
1231
|
-
) : null}
|
|
1218
|
+
) : (
|
|
1219
|
+
<span className="size-4 shrink-0" />
|
|
1220
|
+
)}
|
|
1232
1221
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
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
|
|
1240
1236
|
className={cn(
|
|
1241
|
-
"
|
|
1242
|
-
|
|
1243
|
-
isSelected && "text-foreground",
|
|
1237
|
+
"shrink-0 text-muted-foreground",
|
|
1238
|
+
(isSelected || isInSelectedSubtree) && "text-foreground",
|
|
1244
1239
|
)}
|
|
1245
|
-
aria-label={node.hidden ? labels.show : labels.hide}
|
|
1246
|
-
onClick={(event) => {
|
|
1247
|
-
event.stopPropagation();
|
|
1248
|
-
onToggleHidden?.(node.id, !node.hidden);
|
|
1249
|
-
}}
|
|
1250
1240
|
>
|
|
1251
|
-
{node.
|
|
1252
|
-
<
|
|
1253
|
-
|
|
1254
|
-
|
|
1241
|
+
{node.icon ?? (
|
|
1242
|
+
<LayerGlyph
|
|
1243
|
+
node={node}
|
|
1244
|
+
selected={isSelected}
|
|
1245
|
+
inSelectedSubtree={isInSelectedSubtree}
|
|
1246
|
+
/>
|
|
1255
1247
|
)}
|
|
1256
|
-
</
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
+
)}
|
|
1259
1411
|
{node.hidden ? labels.show : labels.hide}
|
|
1260
|
-
</
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
</
|
|
1412
|
+
</ContextMenuItem>
|
|
1413
|
+
) : null}
|
|
1414
|
+
</ContextMenuContent>
|
|
1415
|
+
) : null}
|
|
1416
|
+
</ContextMenu>
|
|
1265
1417
|
);
|
|
1266
1418
|
}
|
|
1267
1419
|
|
|
@@ -1623,6 +1775,18 @@ function ElementLayerGlyph({ className }: { className?: string }) {
|
|
|
1623
1775
|
);
|
|
1624
1776
|
}
|
|
1625
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
|
+
|
|
1626
1790
|
function dropPlacementForEvent(
|
|
1627
1791
|
event: DragEvent<HTMLDivElement>,
|
|
1628
1792
|
canDropInside: boolean,
|