@agent-native/core 0.90.1 → 0.90.2
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 +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +1 -1
- package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
- package/corpus/core/src/client/use-action.ts +39 -2
- package/corpus/core/src/client/use-db-sync.ts +44 -18
- package/corpus/core/src/db/create-get-db.ts +32 -2
- package/corpus/core/src/server/framework-request-handler.ts +52 -4
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
- package/corpus/templates/assets/AGENTS.md +23 -4
- package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
- package/corpus/templates/assets/actions/_helpers.ts +8 -1
- package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
- package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
- package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
- package/corpus/templates/assets/actions/generate-image.ts +342 -25
- package/corpus/templates/assets/actions/get-library.ts +4 -3
- package/corpus/templates/assets/actions/navigate.ts +2 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
- package/corpus/templates/assets/actions/update-asset.ts +1 -0
- package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
- package/corpus/templates/assets/actions/view-screen.ts +8 -1
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
- package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
- package/corpus/templates/assets/app/i18n-data.ts +650 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
- package/corpus/templates/assets/app/routes/library.tsx +43 -1
- package/corpus/templates/assets/server/lib/generation.ts +366 -44
- package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
- package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
- package/corpus/templates/assets/shared/api.ts +47 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
- package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
- package/corpus/templates/clips/app/routes/record.tsx +157 -68
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -5
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +20 -3
- package/corpus/templates/clips/shared/finalize-recovery.ts +23 -3
- package/corpus/templates/content/AGENTS.md +52 -52
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
- package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
- package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
- package/corpus/templates/content/actions/_database-utils.ts +44 -4
- package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
- package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
- package/corpus/templates/content/actions/delete-content-database.ts +1 -0
- package/corpus/templates/content/actions/get-document.ts +11 -2
- package/corpus/templates/content/actions/list-content-databases.ts +57 -9
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
- package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
- package/corpus/templates/content/actions/restore-content-database.ts +1 -0
- package/corpus/templates/content/actions/update-document.ts +43 -0
- package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
- package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
- package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
- package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
- package/corpus/templates/content/app/global.css +41 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
- package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
- package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
- package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
- package/corpus/templates/content/app/i18n-data.ts +355 -0
- package/corpus/templates/content/app/root.tsx +22 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
- package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
- package/corpus/templates/content/shared/api.ts +13 -0
- package/corpus/templates/content/shared/builder-mdx.ts +16 -3
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
- package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
- package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
- package/corpus/templates/design/AGENTS.md +9 -2
- package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
- package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
- package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
- package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
- package/corpus/templates/design/actions/create-component.ts +29 -60
- package/corpus/templates/design/actions/create-file.ts +9 -2
- package/corpus/templates/design/actions/duplicate-design.ts +31 -2
- package/corpus/templates/design/actions/edit-design.ts +36 -38
- package/corpus/templates/design/actions/generate-design-title.ts +87 -0
- package/corpus/templates/design/actions/generate-design.ts +53 -18
- package/corpus/templates/design/actions/generate-screens.ts +108 -4
- package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
- package/corpus/templates/design/actions/insert-asset.ts +34 -29
- package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
- package/corpus/templates/design/actions/present-design-variants.ts +7 -2
- package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
- package/corpus/templates/design/actions/update-file.ts +190 -75
- package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
- package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
- package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
- package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
- package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
- package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
- package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
- package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
- package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
- package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
- package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
- package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
- package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
- package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
- package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
- package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
- package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
- package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
- package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
- package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
- package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
- package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
- package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
- package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
- package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
- package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
- package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
- package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
- package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
- package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
- package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
- package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
- package/corpus/templates/design/app/components/design/types.ts +15 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
- package/corpus/templates/design/app/global.css +8 -8
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
- package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
- package/corpus/templates/design/app/i18n-data.ts +1664 -152
- package/corpus/templates/design/app/i18n-motion.ts +563 -0
- package/corpus/templates/design/app/lib/design-import.ts +73 -0
- package/corpus/templates/design/app/pages/Index.tsx +41 -22
- package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
- package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
- package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
- package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
- package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
- package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
- package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
- package/corpus/templates/design/server/source-workspace.ts +139 -59
- package/corpus/templates/design/shared/board-file.ts +11 -2
- package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
- package/corpus/templates/design/shared/canvas-math.ts +354 -35
- package/corpus/templates/design/shared/code-layer.ts +277 -8
- package/corpus/templates/design/shared/design-state.ts +11 -4
- package/corpus/templates/design/shared/interaction-states.ts +548 -0
- package/corpus/templates/design/shared/motion-compiler.ts +186 -23
- package/corpus/templates/design/shared/motion-easing.ts +294 -0
- package/corpus/templates/design/shared/motion-timeline.ts +400 -34
- package/corpus/templates/design/shared/prompt-title.ts +95 -0
- package/corpus/templates/design/shared/responsive-classes.ts +448 -10
- package/corpus/templates/design/shared/screen-annotation.ts +46 -0
- package/corpus/templates/design/shared/shader-fills.ts +1009 -0
- package/corpus/templates/design/shared/shader-presets.ts +852 -4
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
- package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
- package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
- package/dist/client/use-action.d.ts +3 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +34 -3
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +4 -0
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +37 -18
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/create-get-db.d.ts +25 -0
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +25 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +37 -1
- package/dist/server/framework-request-handler.js.map +1 -1
- package/package.json +1 -1
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
- package/corpus/templates/design/app/pages/DesignEditor.tsx +0 -22924
|
@@ -2,14 +2,23 @@ import { useT } from "@agent-native/core/client";
|
|
|
2
2
|
import {
|
|
3
3
|
IconChevronDown,
|
|
4
4
|
IconChevronRight,
|
|
5
|
+
IconClipboard,
|
|
6
|
+
IconCopy,
|
|
5
7
|
IconEye,
|
|
6
8
|
IconEyeOff,
|
|
9
|
+
IconFlipHorizontal,
|
|
10
|
+
IconFlipVertical,
|
|
11
|
+
IconFrame,
|
|
12
|
+
IconLayersSubtract,
|
|
13
|
+
IconLayersUnion,
|
|
7
14
|
IconLock,
|
|
8
15
|
IconLockOpen,
|
|
9
16
|
IconLayoutGrid,
|
|
10
17
|
IconPencil,
|
|
11
18
|
IconPlus,
|
|
12
19
|
IconSearch,
|
|
20
|
+
IconStackBack,
|
|
21
|
+
IconStackFront,
|
|
13
22
|
} from "@tabler/icons-react";
|
|
14
23
|
import {
|
|
15
24
|
forwardRef,
|
|
@@ -35,6 +44,7 @@ import {
|
|
|
35
44
|
ContextMenuContent,
|
|
36
45
|
ContextMenuItem,
|
|
37
46
|
ContextMenuSeparator,
|
|
47
|
+
ContextMenuShortcut,
|
|
38
48
|
ContextMenuTrigger,
|
|
39
49
|
} from "@/components/ui/context-menu";
|
|
40
50
|
import { Input } from "@/components/ui/input";
|
|
@@ -146,6 +156,15 @@ export interface LayersPanelLabels {
|
|
|
146
156
|
hide: string;
|
|
147
157
|
show: string;
|
|
148
158
|
rename: string;
|
|
159
|
+
copy: string;
|
|
160
|
+
pasteToReplace: string;
|
|
161
|
+
group: string;
|
|
162
|
+
ungroup: string;
|
|
163
|
+
frameSelection: string;
|
|
164
|
+
bringToFront: string;
|
|
165
|
+
sendToBack: string;
|
|
166
|
+
flipHorizontal: string;
|
|
167
|
+
flipVertical: string;
|
|
149
168
|
}
|
|
150
169
|
|
|
151
170
|
export interface LayersPanelProps {
|
|
@@ -181,6 +200,48 @@ export interface LayersPanelProps {
|
|
|
181
200
|
// Board elements — top-level layer nodes projected from the board file.
|
|
182
201
|
// When absent the panel is unchanged.
|
|
183
202
|
boardElements?: LayersPanelNode[];
|
|
203
|
+
// Id of a layer currently hovered elsewhere (e.g. on the canvas). When set,
|
|
204
|
+
// the matching row gets a subtle hover-highlight background, visually
|
|
205
|
+
// distinct from selection. This is display-only: it never triggers the
|
|
206
|
+
// row's scroll-into-view behavior (that only follows selectedIds), and it
|
|
207
|
+
// never affects keyboard focus. Optional — the panel is unchanged when
|
|
208
|
+
// absent.
|
|
209
|
+
hoveredLayerId?: string | null;
|
|
210
|
+
// Figma-parity row context-menu actions beyond rename/lock/hide. Each item
|
|
211
|
+
// renders only when its callback prop is provided, so the panel keeps
|
|
212
|
+
// working correctly before every callback is wired up from the caller. See
|
|
213
|
+
// the LayerRow context menu below for the exact order/separators/shortcut
|
|
214
|
+
// hints — LIVE-VERIFIED against real Figma's layer-row menu: Copy, Paste
|
|
215
|
+
// to replace, Bring to front, Send to back, Group selection, Frame
|
|
216
|
+
// selection, Rename, Show/Hide, Lock/Unlock, Flip horizontal, Flip
|
|
217
|
+
// vertical. Real Figma has NO Duplicate/Delete/Paste-here on this menu
|
|
218
|
+
// (those are keyboard-only there), and NO Ungroup on a plain row — only on
|
|
219
|
+
// a container row (see onUngroupSelection below).
|
|
220
|
+
onCopyLayer?: (ids: string[]) => void;
|
|
221
|
+
// Kept for callers that still wire it (e.g. a future keyboard shortcut or
|
|
222
|
+
// a different surface); intentionally never rendered in the row menu
|
|
223
|
+
// itself, matching Figma (no "Paste here" on layer rows).
|
|
224
|
+
onPasteHere?: (targetId: string) => void;
|
|
225
|
+
onPasteToReplace?: (ids: string[]) => void;
|
|
226
|
+
// Kept for callers/back-compat; intentionally never rendered in the row
|
|
227
|
+
// menu itself, matching Figma (Duplicate is keyboard-only there).
|
|
228
|
+
onDuplicateLayer?: (ids: string[]) => void;
|
|
229
|
+
// Kept for callers/back-compat; intentionally never rendered in the row
|
|
230
|
+
// menu itself, matching Figma (Delete is keyboard-only there).
|
|
231
|
+
onDeleteLayer?: (ids: string[]) => void;
|
|
232
|
+
onGroupSelection?: (ids: string[]) => void;
|
|
233
|
+
onFrameSelection?: (ids: string[]) => void;
|
|
234
|
+
// Real Figma only offers Ungroup on a CONTAINER row (a group/frame you can
|
|
235
|
+
// ungroup), not on a plain leaf row. The row gates rendering this on
|
|
236
|
+
// `row.canAcceptChildren` (see showContextMenu/LayerRow below) in addition
|
|
237
|
+
// to this callback being provided.
|
|
238
|
+
onUngroupSelection?: (ids: string[]) => void;
|
|
239
|
+
onReorderLayer?: (
|
|
240
|
+
ids: string[],
|
|
241
|
+
direction: "front" | "forward" | "backward" | "back",
|
|
242
|
+
) => void;
|
|
243
|
+
onFlipHorizontal?: (ids: string[]) => void;
|
|
244
|
+
onFlipVertical?: (ids: string[]) => void;
|
|
184
245
|
}
|
|
185
246
|
|
|
186
247
|
// L12: imperative handle so an external trigger (Cmd+R hotkey, canvas
|
|
@@ -260,6 +321,15 @@ function defaultLabels(t: ReturnType<typeof useT>): LayersPanelLabels {
|
|
|
260
321
|
hide: t("layersPanel.hide"),
|
|
261
322
|
show: t("layersPanel.show"),
|
|
262
323
|
rename: t("layersPanel.rename"),
|
|
324
|
+
copy: t("layersPanel.copy"),
|
|
325
|
+
pasteToReplace: t("layersPanel.pasteToReplace"),
|
|
326
|
+
group: t("layersPanel.group"),
|
|
327
|
+
ungroup: t("layersPanel.ungroup"),
|
|
328
|
+
frameSelection: t("layersPanel.frameSelection"),
|
|
329
|
+
bringToFront: t("layersPanel.bringToFront"),
|
|
330
|
+
sendToBack: t("layersPanel.sendToBack"),
|
|
331
|
+
flipHorizontal: t("layersPanel.flipHorizontal"),
|
|
332
|
+
flipVertical: t("layersPanel.flipVertical"),
|
|
263
333
|
};
|
|
264
334
|
}
|
|
265
335
|
|
|
@@ -463,6 +533,40 @@ function nextExpandedIds(
|
|
|
463
533
|
return Array.from(next);
|
|
464
534
|
}
|
|
465
535
|
|
|
536
|
+
// Alt-click on a row's expand chevron (Figma behavior): expand/collapse the
|
|
537
|
+
// node AND every descendant that can itself have children, in one batched
|
|
538
|
+
// state change. Pure tree walk — collects every node id with a non-empty
|
|
539
|
+
// children array so nextExpandedIdsForSubtree can add/remove them all at
|
|
540
|
+
// once instead of the caller looping many onExpandedIdsChange calls.
|
|
541
|
+
export function collectDescendantContainerIds(node: LayersPanelNode): string[] {
|
|
542
|
+
const ids: string[] = [];
|
|
543
|
+
function visit(current: LayersPanelNode) {
|
|
544
|
+
const children = current.children ?? [];
|
|
545
|
+
if (children.length === 0) return;
|
|
546
|
+
ids.push(current.id);
|
|
547
|
+
children.forEach(visit);
|
|
548
|
+
}
|
|
549
|
+
visit(node);
|
|
550
|
+
return ids;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export function nextExpandedIdsForSubtree(
|
|
554
|
+
ids: readonly string[],
|
|
555
|
+
node: LayersPanelNode,
|
|
556
|
+
expanded: boolean,
|
|
557
|
+
): string[] {
|
|
558
|
+
const subtreeIds = collectDescendantContainerIds(node);
|
|
559
|
+
const next = new Set(ids);
|
|
560
|
+
subtreeIds.forEach((id) => {
|
|
561
|
+
if (expanded) {
|
|
562
|
+
next.add(id);
|
|
563
|
+
} else {
|
|
564
|
+
next.delete(id);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
return Array.from(next);
|
|
568
|
+
}
|
|
569
|
+
|
|
466
570
|
/**
|
|
467
571
|
* L1: pure computation for the auto-expand-ancestors-of-selection effect.
|
|
468
572
|
* Given the current selection's ancestor ids and the CURRENT expanded set,
|
|
@@ -636,6 +740,23 @@ export function getDraggedLayerIdsForRows(args: {
|
|
|
636
740
|
});
|
|
637
741
|
}
|
|
638
742
|
|
|
743
|
+
// Row context-menu actions (copy/duplicate/delete/group/reorder) operate on
|
|
744
|
+
// the whole current selection when the right-clicked row is already part of
|
|
745
|
+
// it (matching Figma), or on just that row otherwise (right-clicking an
|
|
746
|
+
// unselected layer acts on that layer alone). Mirrors the same shape as
|
|
747
|
+
// getDraggedLayerIdsForRows's selection-vs-single-row resolution, kept
|
|
748
|
+
// separate since context-menu actions don't need the descendant-exclusion
|
|
749
|
+
// step a drag payload does.
|
|
750
|
+
export function getContextMenuTargetIds(args: {
|
|
751
|
+
selectedIds: readonly string[];
|
|
752
|
+
nodeId: string;
|
|
753
|
+
visibleRows: readonly FlatLayerRow[];
|
|
754
|
+
}): string[] {
|
|
755
|
+
return args.selectedIds.includes(args.nodeId)
|
|
756
|
+
? getTreeOrderedLayerIds(args.selectedIds, args.visibleRows)
|
|
757
|
+
: [args.nodeId];
|
|
758
|
+
}
|
|
759
|
+
|
|
639
760
|
export function shouldResyncLayerSelectionAnchor(args: {
|
|
640
761
|
selectionSignature: string;
|
|
641
762
|
lastPanelSelectionSignature: string;
|
|
@@ -700,6 +821,18 @@ function LayersPanelImpl(
|
|
|
700
821
|
onMoveLayer,
|
|
701
822
|
canMoveLayer,
|
|
702
823
|
boardElements,
|
|
824
|
+
hoveredLayerId,
|
|
825
|
+
onCopyLayer,
|
|
826
|
+
onPasteHere,
|
|
827
|
+
onPasteToReplace,
|
|
828
|
+
onDuplicateLayer,
|
|
829
|
+
onDeleteLayer,
|
|
830
|
+
onGroupSelection,
|
|
831
|
+
onFrameSelection,
|
|
832
|
+
onUngroupSelection,
|
|
833
|
+
onReorderLayer,
|
|
834
|
+
onFlipHorizontal,
|
|
835
|
+
onFlipVertical,
|
|
703
836
|
}: LayersPanelProps,
|
|
704
837
|
ref: Ref<LayersPanelHandle>,
|
|
705
838
|
) {
|
|
@@ -1013,7 +1146,17 @@ function LayersPanelImpl(
|
|
|
1013
1146
|
// render — this keeps LayerRow's props referentially stable so
|
|
1014
1147
|
// React.memo(LayerRow) actually skips re-renders.
|
|
1015
1148
|
const handleToggleExpanded = useCallback(
|
|
1016
|
-
(id: string, expanded: boolean) => {
|
|
1149
|
+
(id: string, expanded: boolean, node?: LayersPanelNode) => {
|
|
1150
|
+
// Alt-click (see LayerRow's chevron onClick): expand/collapse this node
|
|
1151
|
+
// AND all of its descendants in one batched state change, matching
|
|
1152
|
+
// Figma. Only takes this path when the caller passes the node (the
|
|
1153
|
+
// plain toggle path below stays a single-id update).
|
|
1154
|
+
if (node) {
|
|
1155
|
+
onExpandedIdsChange(
|
|
1156
|
+
nextExpandedIdsForSubtree(expandedIdsRef.current, node, expanded),
|
|
1157
|
+
);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1017
1160
|
onExpandedIdsChange(
|
|
1018
1161
|
nextExpandedIds(expandedIdsRef.current, id, expanded),
|
|
1019
1162
|
);
|
|
@@ -1254,6 +1397,8 @@ function LayersPanelImpl(
|
|
|
1254
1397
|
const isInSelectedSubtree = row.ancestorIds.some((id) =>
|
|
1255
1398
|
selectedIdSet.has(id),
|
|
1256
1399
|
);
|
|
1400
|
+
const isHovered =
|
|
1401
|
+
hoveredLayerId != null && row.node.id === hoveredLayerId;
|
|
1257
1402
|
const isActiveScreen =
|
|
1258
1403
|
row.node.id === activeScreenId &&
|
|
1259
1404
|
(row.node.type === "file" ||
|
|
@@ -1273,6 +1418,7 @@ function LayersPanelImpl(
|
|
|
1273
1418
|
isSelected={isSelected}
|
|
1274
1419
|
isInSelectedSubtree={isInSelectedSubtree}
|
|
1275
1420
|
isActiveScreen={isActiveScreen}
|
|
1421
|
+
isHovered={isHovered}
|
|
1276
1422
|
isRenaming={isRenaming}
|
|
1277
1423
|
renameDraft={isRenaming ? renameDraft : ""}
|
|
1278
1424
|
registerRowElement={registerRowElement}
|
|
@@ -1294,6 +1440,14 @@ function LayersPanelImpl(
|
|
|
1294
1440
|
selectedIdsRef={selectedIdsRef}
|
|
1295
1441
|
visibleRowsRef={visibleRowsRef}
|
|
1296
1442
|
rootsRef={rootsRef}
|
|
1443
|
+
onCopyLayer={onCopyLayer}
|
|
1444
|
+
onPasteToReplace={onPasteToReplace}
|
|
1445
|
+
onGroupSelection={onGroupSelection}
|
|
1446
|
+
onFrameSelection={onFrameSelection}
|
|
1447
|
+
onUngroupSelection={onUngroupSelection}
|
|
1448
|
+
onReorderLayer={onReorderLayer}
|
|
1449
|
+
onFlipHorizontal={onFlipHorizontal}
|
|
1450
|
+
onFlipVertical={onFlipVertical}
|
|
1297
1451
|
/>
|
|
1298
1452
|
);
|
|
1299
1453
|
})}
|
|
@@ -1325,6 +1479,10 @@ interface LayerRowProps {
|
|
|
1325
1479
|
isSelected: boolean;
|
|
1326
1480
|
isInSelectedSubtree: boolean;
|
|
1327
1481
|
isActiveScreen: boolean;
|
|
1482
|
+
// Display-only hover highlight (e.g. mirroring canvas hover), distinct from
|
|
1483
|
+
// selection. Never drives scroll-into-view or focus — see hoveredLayerId on
|
|
1484
|
+
// LayersPanelProps.
|
|
1485
|
+
isHovered: boolean;
|
|
1328
1486
|
isRenaming: boolean;
|
|
1329
1487
|
registerRowElement: (rowKey: string, element: HTMLDivElement | null) => void;
|
|
1330
1488
|
renameDraft: string;
|
|
@@ -1342,7 +1500,13 @@ interface LayerRowProps {
|
|
|
1342
1500
|
source: "keyboard" | "pointer";
|
|
1343
1501
|
},
|
|
1344
1502
|
) => void;
|
|
1345
|
-
|
|
1503
|
+
// node is optional; passed on alt-click so the caller can batch-expand the
|
|
1504
|
+
// whole subtree in one state change instead of a single-id toggle.
|
|
1505
|
+
onToggleExpanded: (
|
|
1506
|
+
id: string,
|
|
1507
|
+
expanded: boolean,
|
|
1508
|
+
node?: LayersPanelNode,
|
|
1509
|
+
) => void;
|
|
1346
1510
|
onToggleLocked?: (id: string, locked: boolean) => void;
|
|
1347
1511
|
onToggleHidden?: (id: string, hidden: boolean) => void;
|
|
1348
1512
|
onHoverLayer?: (id: string) => void;
|
|
@@ -1363,6 +1527,23 @@ interface LayerRowProps {
|
|
|
1363
1527
|
// full-tree ancestor map — see buildAncestorIdMap and L13 in the drag-start
|
|
1364
1528
|
// handler below.
|
|
1365
1529
|
rootsRef: RefObject<LayersPanelNode[]>;
|
|
1530
|
+
// Figma-parity context-menu actions. Each is optional; the corresponding
|
|
1531
|
+
// menu item only renders when its callback is provided (see showContextMenu
|
|
1532
|
+
// / the ContextMenuContent below). Note: onPasteHere/onDuplicateLayer/
|
|
1533
|
+
// onDeleteLayer are NOT threaded down to the row — real Figma's layer-row
|
|
1534
|
+
// menu has no Paste here/Duplicate/Delete items (see LayersPanelProps for
|
|
1535
|
+
// the full back-compat callback surface).
|
|
1536
|
+
onCopyLayer?: (ids: string[]) => void;
|
|
1537
|
+
onPasteToReplace?: (ids: string[]) => void;
|
|
1538
|
+
onGroupSelection?: (ids: string[]) => void;
|
|
1539
|
+
onFrameSelection?: (ids: string[]) => void;
|
|
1540
|
+
onUngroupSelection?: (ids: string[]) => void;
|
|
1541
|
+
onReorderLayer?: (
|
|
1542
|
+
ids: string[],
|
|
1543
|
+
direction: "front" | "forward" | "backward" | "back",
|
|
1544
|
+
) => void;
|
|
1545
|
+
onFlipHorizontal?: (ids: string[]) => void;
|
|
1546
|
+
onFlipVertical?: (ids: string[]) => void;
|
|
1366
1547
|
}
|
|
1367
1548
|
|
|
1368
1549
|
const LayerRow = memo(function LayerRow({
|
|
@@ -1372,6 +1553,7 @@ const LayerRow = memo(function LayerRow({
|
|
|
1372
1553
|
isSelected,
|
|
1373
1554
|
isInSelectedSubtree,
|
|
1374
1555
|
isActiveScreen,
|
|
1556
|
+
isHovered,
|
|
1375
1557
|
isRenaming,
|
|
1376
1558
|
registerRowElement,
|
|
1377
1559
|
renameDraft,
|
|
@@ -1393,6 +1575,14 @@ const LayerRow = memo(function LayerRow({
|
|
|
1393
1575
|
selectedIdsRef,
|
|
1394
1576
|
visibleRowsRef,
|
|
1395
1577
|
rootsRef,
|
|
1578
|
+
onCopyLayer,
|
|
1579
|
+
onPasteToReplace,
|
|
1580
|
+
onGroupSelection,
|
|
1581
|
+
onFrameSelection,
|
|
1582
|
+
onUngroupSelection,
|
|
1583
|
+
onReorderLayer,
|
|
1584
|
+
onFlipHorizontal,
|
|
1585
|
+
onFlipVertical,
|
|
1396
1586
|
}: LayerRowProps) {
|
|
1397
1587
|
const { node, depth, hasChildren, canAcceptChildren } = row;
|
|
1398
1588
|
const isComponentLayer = layerNodeIsComponent(node);
|
|
@@ -1470,36 +1660,19 @@ const LayerRow = memo(function LayerRow({
|
|
|
1470
1660
|
});
|
|
1471
1661
|
};
|
|
1472
1662
|
|
|
1663
|
+
// GROUND TRUTH (live-verified against real Figma): after clicking a layer
|
|
1664
|
+
// row, ArrowUp/ArrowDown/Home/End do NOT navigate the layers list and must
|
|
1665
|
+
// NOT be intercepted here at all — no preventDefault, no focus move, no
|
|
1666
|
+
// selection change. Figma's list focus does not consume those keys; they
|
|
1667
|
+
// fall through to the app's global hotkey nudge handler, which moves the
|
|
1668
|
+
// SELECTED OBJECT on canvas by 1px (arrow) or listens for its own Home/End
|
|
1669
|
+
// handling. A previous revision made this row intercept those keys (first
|
|
1670
|
+
// to move DOM focus, later to change selection directly) — both were wrong
|
|
1671
|
+
// and are removed here. ArrowLeft/ArrowRight are the one exception Figma
|
|
1672
|
+
// keeps at the list level: they only toggle the focused row's own
|
|
1673
|
+
// expand/collapse (chevron) state, and must NOT change selection while
|
|
1674
|
+
// doing so.
|
|
1473
1675
|
const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
|
|
1474
|
-
const getFocusableButtons = () => {
|
|
1475
|
-
const tree = event.currentTarget.closest('[role="tree"]');
|
|
1476
|
-
if (!tree) return [];
|
|
1477
|
-
return Array.from(
|
|
1478
|
-
tree.querySelectorAll<HTMLButtonElement>("[data-layer-row-button]"),
|
|
1479
|
-
).filter((button) => !button.disabled);
|
|
1480
|
-
};
|
|
1481
|
-
const focusVisibleButton = (nextIndex: number) => {
|
|
1482
|
-
const buttons = getFocusableButtons();
|
|
1483
|
-
const target =
|
|
1484
|
-
buttons[Math.max(0, Math.min(buttons.length - 1, nextIndex))];
|
|
1485
|
-
target?.focus();
|
|
1486
|
-
return Boolean(target);
|
|
1487
|
-
};
|
|
1488
|
-
const focusNodeButton = (nodeId: string) => {
|
|
1489
|
-
const tree = event.currentTarget.closest('[role="tree"]');
|
|
1490
|
-
const target = tree?.querySelector<HTMLButtonElement>(
|
|
1491
|
-
`[data-layer-node-id="${CSS.escape(nodeId)}"]`,
|
|
1492
|
-
);
|
|
1493
|
-
if (!target || target.disabled) return false;
|
|
1494
|
-
target.focus();
|
|
1495
|
-
return true;
|
|
1496
|
-
};
|
|
1497
|
-
const currentIndex = visibleRowsRef.current.findIndex(
|
|
1498
|
-
(visibleRow) => visibleRow.rowKey === row.rowKey,
|
|
1499
|
-
);
|
|
1500
|
-
const focusableButtons = getFocusableButtons();
|
|
1501
|
-
const currentFocusableIndex = focusableButtons.indexOf(event.currentTarget);
|
|
1502
|
-
|
|
1503
1676
|
if (event.key === "Enter" || event.key === " " || event.key === "Space") {
|
|
1504
1677
|
event.preventDefault();
|
|
1505
1678
|
if (!selectable) return;
|
|
@@ -1516,55 +1689,16 @@ const LayerRow = memo(function LayerRow({
|
|
|
1516
1689
|
onStartRename(node);
|
|
1517
1690
|
return;
|
|
1518
1691
|
}
|
|
1519
|
-
if (event.key === "ArrowDown") {
|
|
1520
|
-
event.preventDefault();
|
|
1521
|
-
focusVisibleButton(currentFocusableIndex + 1);
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
if (event.key === "ArrowUp") {
|
|
1525
|
-
event.preventDefault();
|
|
1526
|
-
focusVisibleButton(currentFocusableIndex - 1);
|
|
1527
|
-
return;
|
|
1528
|
-
}
|
|
1529
|
-
if (event.key === "Home") {
|
|
1530
|
-
event.preventDefault();
|
|
1531
|
-
focusVisibleButton(0);
|
|
1532
|
-
return;
|
|
1533
|
-
}
|
|
1534
|
-
if (event.key === "End") {
|
|
1535
|
-
event.preventDefault();
|
|
1536
|
-
focusVisibleButton(focusableButtons.length - 1);
|
|
1537
|
-
return;
|
|
1538
|
-
}
|
|
1539
1692
|
if (event.key === "ArrowRight" && hasChildren && !isExpanded) {
|
|
1540
1693
|
event.preventDefault();
|
|
1541
1694
|
onToggleExpanded(node.id, true);
|
|
1542
1695
|
return;
|
|
1543
1696
|
}
|
|
1544
|
-
if (event.key === "ArrowRight" && hasChildren && isExpanded) {
|
|
1545
|
-
event.preventDefault();
|
|
1546
|
-
const nextButton = focusableButtons[currentFocusableIndex + 1];
|
|
1547
|
-
const childId = nextButton?.dataset.layerNodeId;
|
|
1548
|
-
const child = childId
|
|
1549
|
-
? visibleRowsRef.current.find(
|
|
1550
|
-
(visibleRow) => visibleRow.node.id === childId,
|
|
1551
|
-
)
|
|
1552
|
-
: null;
|
|
1553
|
-
if (child?.ancestorIds.includes(node.id)) {
|
|
1554
|
-
focusVisibleButton(currentFocusableIndex + 1);
|
|
1555
|
-
}
|
|
1556
|
-
return;
|
|
1557
|
-
}
|
|
1558
1697
|
if (event.key === "ArrowLeft" && hasChildren && isExpanded) {
|
|
1559
1698
|
event.preventDefault();
|
|
1560
1699
|
onToggleExpanded(node.id, false);
|
|
1561
1700
|
return;
|
|
1562
1701
|
}
|
|
1563
|
-
if (event.key === "ArrowLeft" && row.ancestorIds.length > 0) {
|
|
1564
|
-
event.preventDefault();
|
|
1565
|
-
const parentId = row.ancestorIds[row.ancestorIds.length - 1];
|
|
1566
|
-
if (parentId) focusNodeButton(parentId);
|
|
1567
|
-
}
|
|
1568
1702
|
};
|
|
1569
1703
|
|
|
1570
1704
|
const handleDragStart = (event: DragEvent<HTMLDivElement>) => {
|
|
@@ -1724,9 +1858,44 @@ const LayerRow = memo(function LayerRow({
|
|
|
1724
1858
|
clearSpringLoadTimer();
|
|
1725
1859
|
};
|
|
1726
1860
|
|
|
1861
|
+
// Right-clicking a row that's already part of the current selection acts on
|
|
1862
|
+
// the whole selection (matching Figma); right-clicking an unselected row
|
|
1863
|
+
// acts on just that row. Computed lazily at action time (not memoized on
|
|
1864
|
+
// render) so it always reflects the live selectedIdsRef/visibleRowsRef —
|
|
1865
|
+
// both are refs updated outside the render cycle (see their declarations in
|
|
1866
|
+
// LayersPanel), so a render-time useMemo could see stale values by the time
|
|
1867
|
+
// the user actually picks a menu item.
|
|
1868
|
+
const getContextMenuTargetIdsForRow = () =>
|
|
1869
|
+
getContextMenuTargetIds({
|
|
1870
|
+
selectedIds: selectedIdsRef.current,
|
|
1871
|
+
nodeId: node.id,
|
|
1872
|
+
visibleRows: visibleRowsRef.current,
|
|
1873
|
+
});
|
|
1874
|
+
|
|
1875
|
+
// Real Figma only offers Ungroup on a container row (something you could
|
|
1876
|
+
// actually ungroup), never on a plain leaf row — gate it on
|
|
1877
|
+
// canAcceptChildren in addition to the callback being provided. Duplicate/
|
|
1878
|
+
// Delete/Paste-here are intentionally excluded here: real Figma's layer
|
|
1879
|
+
// row menu doesn't have them (they're keyboard-only there), so they must
|
|
1880
|
+
// not factor into whether the menu/trigger renders at all.
|
|
1881
|
+
const canUngroupThisRow = Boolean(onUngroupSelection && canAcceptChildren);
|
|
1882
|
+
const hasEditActions = Boolean(
|
|
1883
|
+
onCopyLayer ||
|
|
1884
|
+
onPasteToReplace ||
|
|
1885
|
+
onGroupSelection ||
|
|
1886
|
+
onFrameSelection ||
|
|
1887
|
+
canUngroupThisRow ||
|
|
1888
|
+
onReorderLayer ||
|
|
1889
|
+
onFlipHorizontal ||
|
|
1890
|
+
onFlipVertical,
|
|
1891
|
+
);
|
|
1892
|
+
|
|
1727
1893
|
const showContextMenu =
|
|
1728
1894
|
selectable &&
|
|
1729
|
-
(Boolean(onRename && node.renamable !== false) ||
|
|
1895
|
+
(Boolean(onRename && node.renamable !== false) ||
|
|
1896
|
+
lockable ||
|
|
1897
|
+
hideable ||
|
|
1898
|
+
hasEditActions);
|
|
1730
1899
|
|
|
1731
1900
|
return (
|
|
1732
1901
|
<ContextMenu>
|
|
@@ -1764,10 +1933,16 @@ const LayerRow = memo(function LayerRow({
|
|
|
1764
1933
|
onMouseLeave={() => onLeaveLayer?.(node.id)}
|
|
1765
1934
|
>
|
|
1766
1935
|
{activeDrop === "before" ? (
|
|
1767
|
-
<span
|
|
1936
|
+
<span
|
|
1937
|
+
className="pointer-events-none absolute right-2 top-0 z-10 h-px bg-[var(--design-editor-accent-color)]"
|
|
1938
|
+
style={{ left: rowIndent(depth) }}
|
|
1939
|
+
/>
|
|
1768
1940
|
) : null}
|
|
1769
1941
|
{activeDrop === "after" ? (
|
|
1770
|
-
<span
|
|
1942
|
+
<span
|
|
1943
|
+
className="pointer-events-none absolute bottom-0 right-2 z-10 h-px bg-[var(--design-editor-accent-color)]"
|
|
1944
|
+
style={{ left: rowIndent(depth) }}
|
|
1945
|
+
/>
|
|
1771
1946
|
) : null}
|
|
1772
1947
|
<div
|
|
1773
1948
|
className={cn(
|
|
@@ -1790,6 +1965,15 @@ const LayerRow = memo(function LayerRow({
|
|
|
1790
1965
|
!isInSelectedSubtree &&
|
|
1791
1966
|
!isActiveScreen &&
|
|
1792
1967
|
"text-foreground/90 hover:bg-[var(--design-editor-layer-hover-color)] hover:text-foreground",
|
|
1968
|
+
// Canvas-hover highlight (hoveredLayerId): a subtle background,
|
|
1969
|
+
// visually distinct from selection/subtree/active-screen state.
|
|
1970
|
+
// Only applied when none of those stronger states already own
|
|
1971
|
+
// the row's background, and never triggers scroll-into-view.
|
|
1972
|
+
isHovered &&
|
|
1973
|
+
!isSelected &&
|
|
1974
|
+
!isInSelectedSubtree &&
|
|
1975
|
+
!isActiveScreen &&
|
|
1976
|
+
"bg-[var(--design-editor-layer-hover-color)] text-foreground",
|
|
1793
1977
|
node.hidden && "text-muted-foreground",
|
|
1794
1978
|
)}
|
|
1795
1979
|
style={{ paddingLeft: rowIndent(depth) }}
|
|
@@ -1803,7 +1987,13 @@ const LayerRow = memo(function LayerRow({
|
|
|
1803
1987
|
aria-label={isExpanded ? labels.collapse : labels.expand}
|
|
1804
1988
|
onClick={(event) => {
|
|
1805
1989
|
event.stopPropagation();
|
|
1806
|
-
|
|
1990
|
+
// Alt-click (Figma behavior): expand/collapse this node AND
|
|
1991
|
+
// all of its descendants in one batched update.
|
|
1992
|
+
onToggleExpanded(
|
|
1993
|
+
node.id,
|
|
1994
|
+
!isExpanded,
|
|
1995
|
+
event.altKey ? node : undefined,
|
|
1996
|
+
);
|
|
1807
1997
|
}}
|
|
1808
1998
|
>
|
|
1809
1999
|
{isExpanded ? (
|
|
@@ -1886,7 +2076,6 @@ const LayerRow = memo(function LayerRow({
|
|
|
1886
2076
|
"min-w-0 flex-1 truncate font-medium leading-none",
|
|
1887
2077
|
isComponentLayer &&
|
|
1888
2078
|
"text-[var(--design-editor-component-color)]",
|
|
1889
|
-
node.hidden && "line-through",
|
|
1890
2079
|
)}
|
|
1891
2080
|
title={node.name}
|
|
1892
2081
|
>
|
|
@@ -1968,7 +2157,98 @@ const LayerRow = memo(function LayerRow({
|
|
|
1968
2157
|
</div>
|
|
1969
2158
|
</ContextMenuTrigger>
|
|
1970
2159
|
{showContextMenu ? (
|
|
1971
|
-
<ContextMenuContent className="z-[300] min-w-[
|
|
2160
|
+
<ContextMenuContent className="z-[300] min-w-[200px] text-[12px]">
|
|
2161
|
+
{/* LIVE-VERIFIED Figma layer-row menu order: Copy, Paste to
|
|
2162
|
+
replace — Bring to front, Send to back — Group selection,
|
|
2163
|
+
(Ungroup, container rows only), Frame selection, Rename —
|
|
2164
|
+
Show/Hide, Lock/Unlock — Flip horizontal, Flip vertical. Real
|
|
2165
|
+
Figma has no Duplicate/Delete/Paste-here on this menu (those
|
|
2166
|
+
are keyboard-only there — see ⌘D/Delete). Each item only
|
|
2167
|
+
renders when its callback prop is provided, so the menu
|
|
2168
|
+
degrades gracefully before every callback is wired up from the
|
|
2169
|
+
caller. */}
|
|
2170
|
+
{onCopyLayer ? (
|
|
2171
|
+
<ContextMenuItem
|
|
2172
|
+
className="gap-2 text-[12px]"
|
|
2173
|
+
onSelect={() => onCopyLayer(getContextMenuTargetIdsForRow())}
|
|
2174
|
+
>
|
|
2175
|
+
<IconCopy className="size-3.5 text-muted-foreground" />
|
|
2176
|
+
{labels.copy}
|
|
2177
|
+
<ContextMenuShortcut>⌘C</ContextMenuShortcut>
|
|
2178
|
+
</ContextMenuItem>
|
|
2179
|
+
) : null}
|
|
2180
|
+
{onPasteToReplace ? (
|
|
2181
|
+
<ContextMenuItem
|
|
2182
|
+
className="gap-2 text-[12px]"
|
|
2183
|
+
onSelect={() => onPasteToReplace(getContextMenuTargetIdsForRow())}
|
|
2184
|
+
>
|
|
2185
|
+
<IconClipboard className="size-3.5 text-muted-foreground" />
|
|
2186
|
+
{labels.pasteToReplace}
|
|
2187
|
+
<ContextMenuShortcut>
|
|
2188
|
+
{"⇧⌘R" /* i18n-ignore keyboard shortcut glyph */}
|
|
2189
|
+
</ContextMenuShortcut>
|
|
2190
|
+
</ContextMenuItem>
|
|
2191
|
+
) : null}
|
|
2192
|
+
|
|
2193
|
+
{(onCopyLayer || onPasteToReplace) && onReorderLayer ? (
|
|
2194
|
+
<ContextMenuSeparator />
|
|
2195
|
+
) : null}
|
|
2196
|
+
|
|
2197
|
+
{onReorderLayer ? (
|
|
2198
|
+
<>
|
|
2199
|
+
<ContextMenuItem
|
|
2200
|
+
className="gap-2 text-[12px]"
|
|
2201
|
+
onSelect={() =>
|
|
2202
|
+
onReorderLayer(getContextMenuTargetIdsForRow(), "front")
|
|
2203
|
+
}
|
|
2204
|
+
>
|
|
2205
|
+
<IconStackFront className="size-3.5 text-muted-foreground" />
|
|
2206
|
+
{labels.bringToFront}
|
|
2207
|
+
<ContextMenuShortcut>]</ContextMenuShortcut>
|
|
2208
|
+
</ContextMenuItem>
|
|
2209
|
+
<ContextMenuItem
|
|
2210
|
+
className="gap-2 text-[12px]"
|
|
2211
|
+
onSelect={() =>
|
|
2212
|
+
onReorderLayer(getContextMenuTargetIdsForRow(), "back")
|
|
2213
|
+
}
|
|
2214
|
+
>
|
|
2215
|
+
<IconStackBack className="size-3.5 text-muted-foreground" />
|
|
2216
|
+
{labels.sendToBack}
|
|
2217
|
+
<ContextMenuShortcut>[</ContextMenuShortcut>
|
|
2218
|
+
</ContextMenuItem>
|
|
2219
|
+
</>
|
|
2220
|
+
) : null}
|
|
2221
|
+
|
|
2222
|
+
{onReorderLayer &&
|
|
2223
|
+
(onGroupSelection ||
|
|
2224
|
+
canUngroupThisRow ||
|
|
2225
|
+
onFrameSelection ||
|
|
2226
|
+
onRename) ? (
|
|
2227
|
+
<ContextMenuSeparator />
|
|
2228
|
+
) : null}
|
|
2229
|
+
|
|
2230
|
+
{onGroupSelection ? (
|
|
2231
|
+
<ContextMenuItem
|
|
2232
|
+
className="gap-2 text-[12px]"
|
|
2233
|
+
onSelect={() => onGroupSelection(getContextMenuTargetIdsForRow())}
|
|
2234
|
+
>
|
|
2235
|
+
<IconLayersUnion className="size-3.5 text-muted-foreground" />
|
|
2236
|
+
{labels.group}
|
|
2237
|
+
<ContextMenuShortcut>⌘G</ContextMenuShortcut>
|
|
2238
|
+
</ContextMenuItem>
|
|
2239
|
+
) : null}
|
|
2240
|
+
{onFrameSelection ? (
|
|
2241
|
+
<ContextMenuItem
|
|
2242
|
+
className="gap-2 text-[12px]"
|
|
2243
|
+
onSelect={() => onFrameSelection(getContextMenuTargetIdsForRow())}
|
|
2244
|
+
>
|
|
2245
|
+
<IconFrame className="size-3.5 text-muted-foreground" />
|
|
2246
|
+
{labels.frameSelection}
|
|
2247
|
+
<ContextMenuShortcut>
|
|
2248
|
+
{"⌥⌘G" /* i18n-ignore keyboard shortcut glyph */}
|
|
2249
|
+
</ContextMenuShortcut>
|
|
2250
|
+
</ContextMenuItem>
|
|
2251
|
+
) : null}
|
|
1972
2252
|
{onRename && node.renamable !== false ? (
|
|
1973
2253
|
<ContextMenuItem
|
|
1974
2254
|
className="gap-2 text-[12px]"
|
|
@@ -1976,11 +2256,50 @@ const LayerRow = memo(function LayerRow({
|
|
|
1976
2256
|
>
|
|
1977
2257
|
<IconPencil className="size-3.5 text-muted-foreground" />
|
|
1978
2258
|
{labels.rename}
|
|
2259
|
+
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
|
|
1979
2260
|
</ContextMenuItem>
|
|
1980
2261
|
) : null}
|
|
1981
|
-
|
|
2262
|
+
|
|
2263
|
+
{/* Real Figma only shows Ungroup on a container row — a plain row
|
|
2264
|
+
never gets it, even when the callback is wired up. */}
|
|
2265
|
+
{canUngroupThisRow ? (
|
|
2266
|
+
<ContextMenuItem
|
|
2267
|
+
className="gap-2 text-[12px]"
|
|
2268
|
+
onSelect={() =>
|
|
2269
|
+
onUngroupSelection?.(getContextMenuTargetIdsForRow())
|
|
2270
|
+
}
|
|
2271
|
+
>
|
|
2272
|
+
<IconLayersSubtract className="size-3.5 text-muted-foreground" />
|
|
2273
|
+
{labels.ungroup}
|
|
2274
|
+
<ContextMenuShortcut>
|
|
2275
|
+
{"⇧⌘G" /* i18n-ignore keyboard shortcut glyph */}
|
|
2276
|
+
</ContextMenuShortcut>
|
|
2277
|
+
</ContextMenuItem>
|
|
2278
|
+
) : null}
|
|
2279
|
+
|
|
2280
|
+
{(onGroupSelection ||
|
|
2281
|
+
canUngroupThisRow ||
|
|
2282
|
+
onFrameSelection ||
|
|
2283
|
+
(onRename && node.renamable !== false)) &&
|
|
2284
|
+
(lockable || hideable) ? (
|
|
1982
2285
|
<ContextMenuSeparator />
|
|
1983
2286
|
) : null}
|
|
2287
|
+
{hideable ? (
|
|
2288
|
+
<ContextMenuItem
|
|
2289
|
+
className="gap-2 text-[12px]"
|
|
2290
|
+
onSelect={() => onToggleHidden?.(node.id, !node.hidden)}
|
|
2291
|
+
>
|
|
2292
|
+
{node.hidden ? (
|
|
2293
|
+
<IconEye className="size-3.5 text-muted-foreground" />
|
|
2294
|
+
) : (
|
|
2295
|
+
<IconEyeOff className="size-3.5 text-muted-foreground" />
|
|
2296
|
+
)}
|
|
2297
|
+
{node.hidden ? labels.show : labels.hide}
|
|
2298
|
+
<ContextMenuShortcut>
|
|
2299
|
+
{"⇧⌘H" /* i18n-ignore keyboard shortcut glyph */}
|
|
2300
|
+
</ContextMenuShortcut>
|
|
2301
|
+
</ContextMenuItem>
|
|
2302
|
+
) : null}
|
|
1984
2303
|
{lockable ? (
|
|
1985
2304
|
<ContextMenuItem
|
|
1986
2305
|
className="gap-2 text-[12px]"
|
|
@@ -1992,19 +2311,34 @@ const LayerRow = memo(function LayerRow({
|
|
|
1992
2311
|
<IconLock className="size-3.5 text-muted-foreground" />
|
|
1993
2312
|
)}
|
|
1994
2313
|
{node.locked ? labels.unlock : labels.lock}
|
|
2314
|
+
<ContextMenuShortcut>
|
|
2315
|
+
{"⇧⌘L" /* i18n-ignore keyboard shortcut glyph */}
|
|
2316
|
+
</ContextMenuShortcut>
|
|
1995
2317
|
</ContextMenuItem>
|
|
1996
2318
|
) : null}
|
|
1997
|
-
|
|
2319
|
+
|
|
2320
|
+
{(lockable || hideable) && (onFlipHorizontal || onFlipVertical) ? (
|
|
2321
|
+
<ContextMenuSeparator />
|
|
2322
|
+
) : null}
|
|
2323
|
+
|
|
2324
|
+
{onFlipHorizontal ? (
|
|
1998
2325
|
<ContextMenuItem
|
|
1999
2326
|
className="gap-2 text-[12px]"
|
|
2000
|
-
onSelect={() =>
|
|
2327
|
+
onSelect={() => onFlipHorizontal(getContextMenuTargetIdsForRow())}
|
|
2001
2328
|
>
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2329
|
+
<IconFlipHorizontal className="size-3.5 text-muted-foreground" />
|
|
2330
|
+
{labels.flipHorizontal}
|
|
2331
|
+
<ContextMenuShortcut>⇧H</ContextMenuShortcut>
|
|
2332
|
+
</ContextMenuItem>
|
|
2333
|
+
) : null}
|
|
2334
|
+
{onFlipVertical ? (
|
|
2335
|
+
<ContextMenuItem
|
|
2336
|
+
className="gap-2 text-[12px]"
|
|
2337
|
+
onSelect={() => onFlipVertical(getContextMenuTargetIdsForRow())}
|
|
2338
|
+
>
|
|
2339
|
+
<IconFlipVertical className="size-3.5 text-muted-foreground" />
|
|
2340
|
+
{labels.flipVertical}
|
|
2341
|
+
<ContextMenuShortcut>⇧V</ContextMenuShortcut>
|
|
2008
2342
|
</ContextMenuItem>
|
|
2009
2343
|
) : null}
|
|
2010
2344
|
</ContextMenuContent>
|