@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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Point } from "./types";
|
|
2
|
+
|
|
3
|
+
/** Path-local point -> canvas-space point, given the path's canvas origin. */
|
|
4
|
+
export function vectorEditLocalToCanvasPoint(
|
|
5
|
+
local: Point,
|
|
6
|
+
originCanvas: Point,
|
|
7
|
+
): Point {
|
|
8
|
+
return { x: originCanvas.x + local.x, y: originCanvas.y + local.y };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Canvas-space point -> path-local point, given the path's canvas origin.
|
|
12
|
+
* Inverse of `vectorEditLocalToCanvasPoint`. */
|
|
13
|
+
export function vectorEditCanvasToLocalPoint(
|
|
14
|
+
canvasPoint: Point,
|
|
15
|
+
originCanvas: Point,
|
|
16
|
+
): Point {
|
|
17
|
+
return {
|
|
18
|
+
x: canvasPoint.x - originCanvas.x,
|
|
19
|
+
y: canvasPoint.y - originCanvas.y,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -15,6 +15,21 @@ export interface ElementInfo {
|
|
|
15
15
|
componentName?: string;
|
|
16
16
|
id?: string;
|
|
17
17
|
sourceId?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Node-id integrity (id-on-demand): a durable candidate id the bridge minted
|
|
20
|
+
* for this element because it has no stable `data-agent-native-node-id`
|
|
21
|
+
* (or other stable source id) at all — common on AI-generated screens,
|
|
22
|
+
* where every id-keyed host operation (move/reorder, style commits that
|
|
23
|
+
* resolve a targetNode, motion tracks, scrub) otherwise silently no-ops or
|
|
24
|
+
* throws `Node with data-agent-native-node-id="" not found in sourceHtml`.
|
|
25
|
+
* Only present when `sourceId` is absent/empty. The host should persist
|
|
26
|
+
* this value into the source as the element's real
|
|
27
|
+
* `data-agent-native-node-id` the moment it sees one (see
|
|
28
|
+
* DesignEditor.tsx's selection handlers), through the same guarded write
|
|
29
|
+
* path every other edit uses — after that every subsequent id-keyed op
|
|
30
|
+
* against this element resolves normally via `sourceId`.
|
|
31
|
+
*/
|
|
32
|
+
pendingNodeId?: string;
|
|
18
33
|
selector?: string;
|
|
19
34
|
classes: string[];
|
|
20
35
|
computedStyles: Record<string, string>;
|
|
@@ -339,6 +339,33 @@ function isNestedPromptPopoverTarget(target: EventTarget | null) {
|
|
|
339
339
|
);
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
// While a nested Radix Select/Popover/Dropdown is open, Radix locks
|
|
343
|
+
// `pointer-events` on everything outside its own portalled content so only
|
|
344
|
+
// that content (and its trigger) remain interactive. That lockout is what
|
|
345
|
+
// lets the interaction happen at all, but it also means the pointer/focus
|
|
346
|
+
// event that reaches our `onInteractOutside` handler resolves its `target`
|
|
347
|
+
// to `<html>`/`document` instead of the element the user actually clicked —
|
|
348
|
+
// the real target sits under a `pointer-events: none` ancestor, so the
|
|
349
|
+
// browser reports the outermost still-hit-testable node. That target fails
|
|
350
|
+
// any `closest()` containment check, so the click looks "outside" the
|
|
351
|
+
// dialog and dismisses it even though the user never left the popover.
|
|
352
|
+
//
|
|
353
|
+
// Rather than pattern-match on the resolved (and unreliable) target, check
|
|
354
|
+
// whether any of our nested portalled surfaces are currently open in the
|
|
355
|
+
// DOM — they're stamped with the same data attributes whether or not the
|
|
356
|
+
// interact-outside target correctly resolved into them. If one is open,
|
|
357
|
+
// this is never a genuine outside click.
|
|
358
|
+
function hasOpenNestedPromptPopoverSurface() {
|
|
359
|
+
if (typeof document === "undefined") return false;
|
|
360
|
+
return Boolean(
|
|
361
|
+
document.querySelector(
|
|
362
|
+
'[data-agent-native-composer-popover][data-state="open"],' +
|
|
363
|
+
"[data-assets-picker-dialog]," +
|
|
364
|
+
'[data-agent-native-prompt-select][data-state="open"]',
|
|
365
|
+
),
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
342
369
|
export default function PromptPopover({
|
|
343
370
|
open,
|
|
344
371
|
onOpenChange,
|
|
@@ -363,6 +390,38 @@ export default function PromptPopover({
|
|
|
363
390
|
const [pickedAssets, setPickedAssets] = useState<UploadedFile[]>([]);
|
|
364
391
|
const [selectedUploadFiles, setSelectedUploadFiles] = useState<File[]>([]);
|
|
365
392
|
const [assetsPickerOpen, setAssetsPickerOpen] = useState(false);
|
|
393
|
+
// While the nested design-system Select is open, Radix disables pointer
|
|
394
|
+
// events on everything else and the click that closes the Select also
|
|
395
|
+
// moves focus back to its trigger. That focus-return is itself reported to
|
|
396
|
+
// the popover's dismissable layer as a "focus outside" interaction — and it
|
|
397
|
+
// fires *after* the Select has already unmounted its portalled content, so
|
|
398
|
+
// by then there is nothing left in the DOM for `onInteractOutside` to
|
|
399
|
+
// recognize as "still nested and open". Latch a short-lived flag the
|
|
400
|
+
// instant the Select reports closing, and have the popover's
|
|
401
|
+
// interact-outside guard also honor that flag so the popover survives the
|
|
402
|
+
// Select's own close-triggered focus shuffle. See PromptDialog R87/R91.
|
|
403
|
+
const justClosedNestedSelectRef = useRef(false);
|
|
404
|
+
const clearJustClosedNestedSelectTimeoutRef = useRef<ReturnType<
|
|
405
|
+
typeof setTimeout
|
|
406
|
+
> | null>(null);
|
|
407
|
+
const markNestedSelectJustClosed = useCallback(() => {
|
|
408
|
+
justClosedNestedSelectRef.current = true;
|
|
409
|
+
if (clearJustClosedNestedSelectTimeoutRef.current != null) {
|
|
410
|
+
clearTimeout(clearJustClosedNestedSelectTimeoutRef.current);
|
|
411
|
+
}
|
|
412
|
+
clearJustClosedNestedSelectTimeoutRef.current = setTimeout(() => {
|
|
413
|
+
justClosedNestedSelectRef.current = false;
|
|
414
|
+
clearJustClosedNestedSelectTimeoutRef.current = null;
|
|
415
|
+
}, 300);
|
|
416
|
+
}, []);
|
|
417
|
+
useEffect(
|
|
418
|
+
() => () => {
|
|
419
|
+
if (clearJustClosedNestedSelectTimeoutRef.current != null) {
|
|
420
|
+
clearTimeout(clearJustClosedNestedSelectTimeoutRef.current);
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
[],
|
|
424
|
+
);
|
|
366
425
|
|
|
367
426
|
useEffect(() => {
|
|
368
427
|
if (open) return;
|
|
@@ -502,10 +561,48 @@ export default function PromptPopover({
|
|
|
502
561
|
[assetsPickerOpen, onOpenChange],
|
|
503
562
|
);
|
|
504
563
|
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
564
|
+
const hasLiveVirtualAnchor = !centered && Boolean(anchorRef?.current);
|
|
565
|
+
// Radix keeps the closed popover mounted while the exit animation plays, but
|
|
566
|
+
// callers may clear `anchorRef` as soon as they close it. Latch the anchor
|
|
567
|
+
// mode from the last open render so the closing popover never swaps over to
|
|
568
|
+
// the static fallback anchor mid-exit (which re-anchored the fading popover
|
|
569
|
+
// to the top-left corner of the screen).
|
|
570
|
+
const anchorModeWhileOpenRef = useRef(hasLiveVirtualAnchor);
|
|
571
|
+
if (open) {
|
|
572
|
+
anchorModeWhileOpenRef.current = hasLiveVirtualAnchor;
|
|
573
|
+
}
|
|
574
|
+
const hasVirtualAnchor = open
|
|
575
|
+
? hasLiveVirtualAnchor
|
|
576
|
+
: anchorModeWhileOpenRef.current;
|
|
577
|
+
|
|
578
|
+
// Stable virtual anchor that measures the live anchor element while it is
|
|
579
|
+
// attached and falls back to the last good rect afterwards. The anchor
|
|
580
|
+
// element can unmount on unrelated sidebar re-renders, and the ref can be
|
|
581
|
+
// cleared during the exit animation; measuring a detached element returns a
|
|
582
|
+
// zero rect, which used to reposition the popover to the viewport origin.
|
|
583
|
+
const latestAnchorRef = useRef(anchorRef);
|
|
584
|
+
latestAnchorRef.current = anchorRef;
|
|
585
|
+
const lastAnchorRectRef = useRef<DOMRect | null>(null);
|
|
586
|
+
const [virtualAnchorRef] = useState<
|
|
587
|
+
React.RefObject<{ getBoundingClientRect: () => DOMRect }>
|
|
588
|
+
>(() => ({
|
|
589
|
+
current: {
|
|
590
|
+
getBoundingClientRect: () => {
|
|
591
|
+
const el = latestAnchorRef.current?.current;
|
|
592
|
+
if (el?.isConnected) {
|
|
593
|
+
const rect = el.getBoundingClientRect();
|
|
594
|
+
if (rect.width > 0 || rect.height > 0) {
|
|
595
|
+
lastAnchorRectRef.current = rect;
|
|
596
|
+
return rect;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return (
|
|
600
|
+
lastAnchorRectRef.current ??
|
|
601
|
+
new DOMRect(window.innerWidth / 2, window.innerHeight / 2, 0, 0)
|
|
602
|
+
);
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
}));
|
|
509
606
|
|
|
510
607
|
return (
|
|
511
608
|
<Popover open={open} onOpenChange={handlePopoverOpenChange}>
|
|
@@ -539,7 +636,11 @@ export default function PromptPopover({
|
|
|
539
636
|
if (assetsPickerOpen) event.preventDefault();
|
|
540
637
|
}}
|
|
541
638
|
onInteractOutside={(event) => {
|
|
542
|
-
if (
|
|
639
|
+
if (
|
|
640
|
+
isNestedPromptPopoverTarget(event.target) ||
|
|
641
|
+
hasOpenNestedPromptPopoverSurface() ||
|
|
642
|
+
justClosedNestedSelectRef.current
|
|
643
|
+
) {
|
|
543
644
|
event.preventDefault();
|
|
544
645
|
}
|
|
545
646
|
}}
|
|
@@ -588,6 +689,9 @@ export default function PromptPopover({
|
|
|
588
689
|
onValueChange={(value) =>
|
|
589
690
|
onDesignSystemChange?.(value === "none" ? null : value)
|
|
590
691
|
}
|
|
692
|
+
onOpenChange={(nextOpen) => {
|
|
693
|
+
if (!nextOpen) markNestedSelectJustClosed();
|
|
694
|
+
}}
|
|
591
695
|
disabled={designSystemsLoading}
|
|
592
696
|
>
|
|
593
697
|
<SelectTrigger className="h-8 min-w-0 flex-1 text-xs">
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
--sidebar-accent-foreground: 0 0% 15%;
|
|
37
37
|
--sidebar-border: 0 0% 90%;
|
|
38
38
|
--sidebar-ring: 0 0% 40%;
|
|
39
|
-
--design-editor-accent-color: hsl(
|
|
40
|
-
--design-editor-accent-hover-color: hsl(
|
|
39
|
+
--design-editor-accent-color: hsl(205 100% 53%);
|
|
40
|
+
--design-editor-accent-hover-color: hsl(205 81% 42%);
|
|
41
41
|
--design-editor-selection-color: color-mix(
|
|
42
42
|
in srgb,
|
|
43
|
-
hsl(
|
|
43
|
+
hsl(205 100% 53%) 14%,
|
|
44
44
|
transparent
|
|
45
45
|
);
|
|
46
46
|
--design-editor-component-color: hsl(263 84% 64%);
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
);
|
|
70
70
|
--design-editor-selected-subtree-color: color-mix(
|
|
71
71
|
in srgb,
|
|
72
|
-
hsl(
|
|
72
|
+
hsl(205 100% 53%) 9%,
|
|
73
73
|
transparent
|
|
74
74
|
);
|
|
75
75
|
--design-editor-panel-divider-color: color-mix(
|
|
@@ -122,11 +122,11 @@
|
|
|
122
122
|
--sidebar-accent-foreground: 0 0% 90%;
|
|
123
123
|
--sidebar-border: 0 0% 20%;
|
|
124
124
|
--sidebar-ring: 0 0% 60%;
|
|
125
|
-
--design-editor-accent-color: hsl(
|
|
126
|
-
--design-editor-accent-hover-color: hsl(
|
|
125
|
+
--design-editor-accent-color: hsl(205 100% 53%);
|
|
126
|
+
--design-editor-accent-hover-color: hsl(205 81% 42%);
|
|
127
127
|
--design-editor-selection-color: color-mix(
|
|
128
128
|
in srgb,
|
|
129
|
-
hsl(
|
|
129
|
+
hsl(205 100% 53%) 24%,
|
|
130
130
|
transparent
|
|
131
131
|
);
|
|
132
132
|
--design-editor-component-color: hsl(263 88% 74%);
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
);
|
|
152
152
|
--design-editor-selected-subtree-color: color-mix(
|
|
153
153
|
in srgb,
|
|
154
|
-
hsl(
|
|
154
|
+
hsl(205 100% 53%) 18%,
|
|
155
155
|
transparent
|
|
156
156
|
);
|
|
157
157
|
--design-editor-panel-divider-color: hsl(0 0% 22%);
|
|
@@ -41,6 +41,24 @@ export interface DesignHotkeyOpacityDetails extends DesignHotkeyDetails {
|
|
|
41
41
|
opacity: number;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export type DesignHotkeyAlignEdge =
|
|
45
|
+
| "left"
|
|
46
|
+
| "center-h"
|
|
47
|
+
| "right"
|
|
48
|
+
| "top"
|
|
49
|
+
| "center-v"
|
|
50
|
+
| "bottom";
|
|
51
|
+
|
|
52
|
+
export interface DesignHotkeyAlignDetails extends DesignHotkeyDetails {
|
|
53
|
+
edge: DesignHotkeyAlignEdge;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type DesignHotkeyDistributeAxis = "horizontal" | "vertical";
|
|
57
|
+
|
|
58
|
+
export interface DesignHotkeyDistributeDetails extends DesignHotkeyDetails {
|
|
59
|
+
axis: DesignHotkeyDistributeAxis;
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
export type DesignHotkeyTarget = Window | Document | HTMLElement;
|
|
45
63
|
export type DesignHotkeyHandler = (details: DesignHotkeyDetails) => void;
|
|
46
64
|
export type DesignHotkeyToolHandler = (
|
|
@@ -54,6 +72,12 @@ export type DesignHotkeyTabHandler = (details: DesignHotkeyTabDetails) => void;
|
|
|
54
72
|
export type DesignHotkeyOpacityHandler = (
|
|
55
73
|
details: DesignHotkeyOpacityDetails,
|
|
56
74
|
) => void;
|
|
75
|
+
export type DesignHotkeyAlignHandler = (
|
|
76
|
+
details: DesignHotkeyAlignDetails,
|
|
77
|
+
) => void;
|
|
78
|
+
export type DesignHotkeyDistributeHandler = (
|
|
79
|
+
details: DesignHotkeyDistributeDetails,
|
|
80
|
+
) => void;
|
|
57
81
|
|
|
58
82
|
export interface UseDesignHotkeysProps {
|
|
59
83
|
enabled?: boolean;
|
|
@@ -87,6 +111,9 @@ export interface UseDesignHotkeysProps {
|
|
|
87
111
|
onSelectAll?: DesignHotkeyHandler;
|
|
88
112
|
onGroup?: DesignHotkeyHandler;
|
|
89
113
|
onUngroup?: DesignHotkeyHandler;
|
|
114
|
+
/** Figma's Cmd+Alt+G — "Frame selection": wrap the selection in a frame
|
|
115
|
+
* container (distinct from onGroup's plain-group wrapper). */
|
|
116
|
+
onFrameSelection?: DesignHotkeyHandler;
|
|
90
117
|
onUndo?: DesignHotkeyHandler;
|
|
91
118
|
onRedo?: DesignHotkeyHandler;
|
|
92
119
|
onBringForward?: DesignHotkeyHandler;
|
|
@@ -113,6 +140,52 @@ export interface UseDesignHotkeysProps {
|
|
|
113
140
|
* editable-target keystroke (already filtered by ignoreEditableTargets).
|
|
114
141
|
*/
|
|
115
142
|
onOpacityChange?: DesignHotkeyOpacityHandler;
|
|
143
|
+
/** Figma's Cmd+Shift+H — toggle hide/show for the current selection (all
|
|
144
|
+
* selected layers/screens). */
|
|
145
|
+
onToggleHidden?: DesignHotkeyHandler;
|
|
146
|
+
/** Figma's Cmd+Shift+L — toggle lock/unlock for the current selection. */
|
|
147
|
+
onToggleLocked?: DesignHotkeyHandler;
|
|
148
|
+
/** Figma's Shift+H — flip the current selection horizontally. */
|
|
149
|
+
onFlipHorizontal?: DesignHotkeyHandler;
|
|
150
|
+
/** Figma's Shift+V — flip the current selection vertically. */
|
|
151
|
+
onFlipVertical?: DesignHotkeyHandler;
|
|
152
|
+
/** Figma's Shift+X — swap the current selection's fill and stroke. */
|
|
153
|
+
onSwapFillStroke?: DesignHotkeyHandler;
|
|
154
|
+
/** Figma's Shift+Cmd+R — paste to replace the current selection with the
|
|
155
|
+
* internal canvas clipboard's contents. */
|
|
156
|
+
onPasteToReplace?: DesignHotkeyHandler;
|
|
157
|
+
/**
|
|
158
|
+
* Figma's "I" — eyedropper: sample a color from anywhere on screen and
|
|
159
|
+
* apply it to the current selection. A one-shot action, not a tool (so it
|
|
160
|
+
* is intentionally NOT in TOOL_SHORTCUTS/onToolChange).
|
|
161
|
+
*/
|
|
162
|
+
onEyedropper?: DesignHotkeyHandler;
|
|
163
|
+
/**
|
|
164
|
+
* Figma's Alt+A/D/W/S/H/V — align the current selection to left/right/top/
|
|
165
|
+
* bottom/center-h/center-v. Alt-only (no cmd/shift); moves the selection
|
|
166
|
+
* itself, matching the EditPanel Alignment row's `onAlignSelection`
|
|
167
|
+
* contract (see PositionLayoutProperties in EditPanel.tsx).
|
|
168
|
+
*/
|
|
169
|
+
onAlignSelection?: DesignHotkeyAlignHandler;
|
|
170
|
+
/**
|
|
171
|
+
* Figma's Alt+Shift+H / Alt+Shift+V — distribute the selection evenly
|
|
172
|
+
* along the horizontal/vertical axis (3+ objects; first/last stay put).
|
|
173
|
+
*/
|
|
174
|
+
onDistributeSelection?: DesignHotkeyDistributeHandler;
|
|
175
|
+
/** Figma's Ctrl+Alt+T (literal Control on every platform, not Cmd) — Tidy
|
|
176
|
+
* up: arrange the selection into a compact grid with uniform gaps. */
|
|
177
|
+
onTidyUp?: DesignHotkeyHandler;
|
|
178
|
+
/**
|
|
179
|
+
* Figma's Shift+A — Add auto layout. Shift-only so it never shadows the
|
|
180
|
+
* plain "a" frame-tool shortcut (TOOL_SHORTCUTS only fires with no
|
|
181
|
+
* modifiers held) or SHIFT_TOOL_SHORTCUTS (which has no "a" entry).
|
|
182
|
+
*/
|
|
183
|
+
onAddAutoLayout?: DesignHotkeyHandler;
|
|
184
|
+
/** Figma's Cmd+\ — toggle Show/Hide UI (left rail, right panel, bottom
|
|
185
|
+
* toolbar chrome). */
|
|
186
|
+
onToggleUi?: DesignHotkeyHandler;
|
|
187
|
+
/** Figma's Shift+C — toggle Show/Hide comments (comment pins). */
|
|
188
|
+
onToggleComments?: DesignHotkeyHandler;
|
|
116
189
|
}
|
|
117
190
|
|
|
118
191
|
const TOOL_SHORTCUTS: Record<
|
|
@@ -121,6 +194,10 @@ const TOOL_SHORTCUTS: Record<
|
|
|
121
194
|
> = {
|
|
122
195
|
v: { tool: "move", handler: "onMoveTool" },
|
|
123
196
|
f: { tool: "frame", handler: "onFrameTool" },
|
|
197
|
+
// Figma binds BOTH F and A to the frame tool; A is the muscle-memory
|
|
198
|
+
// favorite. Cmd+A (select all) is unaffected — tool shortcuts only fire
|
|
199
|
+
// with no modifiers held.
|
|
200
|
+
a: { tool: "frame", handler: "onFrameTool" },
|
|
124
201
|
r: { tool: "rectangle", handler: "onRectangleTool" },
|
|
125
202
|
o: { tool: "ellipse", handler: "onEllipseTool" },
|
|
126
203
|
l: { tool: "line", handler: "onLineTool" },
|
|
@@ -299,6 +376,20 @@ function handleDesignHotkey(
|
|
|
299
376
|
return true;
|
|
300
377
|
};
|
|
301
378
|
|
|
379
|
+
const runAlign = (edge: DesignHotkeyAlignEdge) => {
|
|
380
|
+
if (!props.onAlignSelection) return false;
|
|
381
|
+
prevent();
|
|
382
|
+
props.onAlignSelection({ ...details, edge });
|
|
383
|
+
return true;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const runDistribute = (axis: DesignHotkeyDistributeAxis) => {
|
|
387
|
+
if (!props.onDistributeSelection) return false;
|
|
388
|
+
prevent();
|
|
389
|
+
props.onDistributeSelection({ ...details, axis });
|
|
390
|
+
return true;
|
|
391
|
+
};
|
|
392
|
+
|
|
302
393
|
if (!primary && !event.altKey && event.shiftKey) {
|
|
303
394
|
// H1: shift+key variant (e.g. Shift+L → arrow tool) takes priority over
|
|
304
395
|
// the base binding for the same key while shift is held.
|
|
@@ -319,6 +410,12 @@ function handleDesignHotkey(
|
|
|
319
410
|
props[toolShortcut.handler] as DesignHotkeyHandler | undefined,
|
|
320
411
|
);
|
|
321
412
|
}
|
|
413
|
+
// Figma's "I" — eyedropper. Deliberately NOT in TOOL_SHORTCUTS: it's a
|
|
414
|
+
// one-shot sample-and-apply action, not a persistent armed tool (no
|
|
415
|
+
// onToolChange call, no cursor mode to stay in).
|
|
416
|
+
if (key === "i") {
|
|
417
|
+
return run(props.onEyedropper);
|
|
418
|
+
}
|
|
322
419
|
}
|
|
323
420
|
|
|
324
421
|
if (event.key in ARROW_DIRECTIONS && !primary && !event.altKey) {
|
|
@@ -364,10 +461,28 @@ function handleDesignHotkey(
|
|
|
364
461
|
return run(props.onPaste);
|
|
365
462
|
}
|
|
366
463
|
if (primary && key === "d") return run(props.onDuplicate);
|
|
464
|
+
// Figma's Shift+Cmd+R — "Paste to replace" — must be checked BEFORE plain
|
|
465
|
+
// Cmd+R (rename) so shift wins; onPasteToReplace absent falls through to
|
|
466
|
+
// rename so existing behavior is unaffected until the handler is wired.
|
|
467
|
+
if (primary && event.shiftKey && key === "r") {
|
|
468
|
+
return run(props.onPasteToReplace);
|
|
469
|
+
}
|
|
367
470
|
if (primary && key === "r") return run(props.onRename);
|
|
471
|
+
// Cmd+Shift+H/L (hide/lock the current selection) must take precedence
|
|
472
|
+
// over any other h/l handling (e.g. the plain "h" hand-tool shortcut only
|
|
473
|
+
// fires with no modifiers held, so there's no real conflict, but this is
|
|
474
|
+
// checked first defensively to guarantee primary+shift+h/l always wins).
|
|
475
|
+
if (primary && event.shiftKey && key === "h") {
|
|
476
|
+
return run(props.onToggleHidden);
|
|
477
|
+
}
|
|
478
|
+
if (primary && event.shiftKey && key === "l") {
|
|
479
|
+
return run(props.onToggleLocked);
|
|
480
|
+
}
|
|
368
481
|
if (primary && key === "g") {
|
|
369
|
-
// Figma
|
|
370
|
-
|
|
482
|
+
// Figma: ⇧⌘G is ungroup. ⌥⌘G is a DIFFERENT command — "Frame selection"
|
|
483
|
+
// (wrap the selection in a frame) — not an ungroup alias.
|
|
484
|
+
if (event.shiftKey) return run(props.onUngroup);
|
|
485
|
+
if (event.altKey) return run(props.onFrameSelection);
|
|
371
486
|
return run(props.onGroup);
|
|
372
487
|
}
|
|
373
488
|
|
|
@@ -375,6 +490,25 @@ function handleDesignHotkey(
|
|
|
375
490
|
if (primary && key === "-") return run(props.onZoomOut);
|
|
376
491
|
if (primary && key === "0") return run(props.onZoomReset);
|
|
377
492
|
|
|
493
|
+
// Figma: plain +/= and - (no modifiers) also zoom in/out.
|
|
494
|
+
if (!primary && !event.altKey && !event.shiftKey) {
|
|
495
|
+
if (key === "=" || key === "+") return run(props.onZoomIn);
|
|
496
|
+
if (key === "-") return run(props.onZoomOut);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Figma: Shift+= ("+" on a US keyboard, since "+" is the shifted "=") also
|
|
500
|
+
// zooms in. Checked separately from the plain-modifier branch above (which
|
|
501
|
+
// requires !shiftKey) so this doesn't need to disturb that branch's other
|
|
502
|
+
// no-modifier-only guarantees; key === "+" already implies shift produced
|
|
503
|
+
// it on layouts where "+" isn't its own physical key, but this also covers
|
|
504
|
+
// event.code === "Equal" with shiftKey true for layouts that report key as
|
|
505
|
+
// "=" while shifted.
|
|
506
|
+
if (!primary && !event.altKey && event.shiftKey) {
|
|
507
|
+
if (key === "+" || (key === "=" && event.code === "Equal")) {
|
|
508
|
+
return run(props.onZoomIn);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
378
512
|
// H2: Cmd+Alt+K — create component from the current selection.
|
|
379
513
|
if (primary && event.altKey && key === "k") {
|
|
380
514
|
return run(props.onCreateComponent);
|
|
@@ -410,18 +544,141 @@ function handleDesignHotkey(
|
|
|
410
544
|
return true;
|
|
411
545
|
}
|
|
412
546
|
|
|
547
|
+
// Ground-truth Figma: plain ]/[ (no modifiers) are Bring to front / Send
|
|
548
|
+
// to back — the "big jump" commands. Cmd+]/Cmd+[ are Bring forward / Send
|
|
549
|
+
// backward (single-step reorder). Alt+Cmd+]/Alt+Cmd+[ are silent aliases
|
|
550
|
+
// of the plain front/back commands (kept for muscle memory / older
|
|
551
|
+
// bindings), NOT of forward/backward.
|
|
413
552
|
if (primary && key === "]") {
|
|
414
553
|
return event.altKey ? run(props.onBringToFront) : run(props.onBringForward);
|
|
415
554
|
}
|
|
416
555
|
if (primary && key === "[") {
|
|
417
556
|
return event.altKey ? run(props.onSendToBack) : run(props.onSendBackward);
|
|
418
557
|
}
|
|
558
|
+
if (!primary && !event.altKey && !event.shiftKey && key === "]") {
|
|
559
|
+
return run(props.onBringToFront);
|
|
560
|
+
}
|
|
561
|
+
if (!primary && !event.altKey && !event.shiftKey && key === "[") {
|
|
562
|
+
return run(props.onSendToBack);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// H3: Shift+H / Shift+V — flip selection horizontal/vertical. Checked
|
|
566
|
+
// after the primary+shift h/l (hide/lock) branch above so Cmd+Shift+H
|
|
567
|
+
// still wins; h/v aren't in SHIFT_TOOL_SHORTCUTS so there's no real
|
|
568
|
+
// shortcut collision here either.
|
|
569
|
+
if (!primary && !event.altKey && event.shiftKey && key === "h") {
|
|
570
|
+
return run(props.onFlipHorizontal);
|
|
571
|
+
}
|
|
572
|
+
if (!primary && !event.altKey && event.shiftKey && key === "v") {
|
|
573
|
+
return run(props.onFlipVertical);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Figma: Shift+X — swap fill and stroke. Cmd+X (cut) is unaffected since
|
|
577
|
+
// this only fires when shift is held and no primary modifier is present.
|
|
578
|
+
if (!primary && !event.altKey && event.shiftKey && key === "x") {
|
|
579
|
+
return run(props.onSwapFillStroke);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Figma: Alt+Shift+H / Alt+Shift+V — distribute the selection evenly along
|
|
583
|
+
// the horizontal/vertical axis. Checked BEFORE the plain Alt+H/Alt+V align
|
|
584
|
+
// bindings below so the shift-held distribute variant always wins over the
|
|
585
|
+
// no-shift align variant for the same letter.
|
|
586
|
+
if (!primary && event.altKey && event.shiftKey) {
|
|
587
|
+
if (key === "h") return runDistribute("horizontal");
|
|
588
|
+
if (key === "v") return runDistribute("vertical");
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Figma: Alt+A/D/W/S/H/V — align the selection to left/right/top/bottom/
|
|
592
|
+
// center-h/center-v. Alt-only (no cmd, no shift — shift+h/v are the
|
|
593
|
+
// distribute bindings just above, shift+a is Add auto layout below).
|
|
594
|
+
if (!primary && event.altKey && !event.shiftKey) {
|
|
595
|
+
if (key === "a") return runAlign("left");
|
|
596
|
+
if (key === "d") return runAlign("right");
|
|
597
|
+
if (key === "w") return runAlign("top");
|
|
598
|
+
if (key === "s") return runAlign("bottom");
|
|
599
|
+
if (key === "h") return runAlign("center-h");
|
|
600
|
+
if (key === "v") return runAlign("center-v");
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Figma: Ctrl+Alt+T — Tidy up. A rare Figma shortcut that stays literal
|
|
604
|
+
// Control on every platform (never remapped to Cmd on Mac), so this checks
|
|
605
|
+
// event.ctrlKey directly instead of the combined `primary` flag.
|
|
606
|
+
if (event.ctrlKey && event.altKey && !event.metaKey && key === "t") {
|
|
607
|
+
return run(props.onTidyUp);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Figma: Shift+A — Add auto layout. Shift-only: plain "a" is the frame-tool
|
|
611
|
+
// shortcut (TOOL_SHORTCUTS, no-modifier only) and SHIFT_TOOL_SHORTCUTS has
|
|
612
|
+
// no "a" entry, so this can't shadow either.
|
|
613
|
+
if (!primary && !event.altKey && event.shiftKey && key === "a") {
|
|
614
|
+
return run(props.onAddAutoLayout);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Figma: Cmd+\ — Show/Hide UI (empty-canvas context-menu item).
|
|
618
|
+
if (primary && key === "\\") {
|
|
619
|
+
return run(props.onToggleUi);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Figma: Shift+C — Show/Hide comments. Plain "c" (no modifiers) is the
|
|
623
|
+
// comment-pin TOOL_SHORTCUTS entry, so shift+c can't shadow it.
|
|
624
|
+
if (!primary && !event.altKey && event.shiftKey && key === "c") {
|
|
625
|
+
return run(props.onToggleComments);
|
|
626
|
+
}
|
|
419
627
|
|
|
420
628
|
return false;
|
|
421
629
|
}
|
|
422
630
|
|
|
631
|
+
// Physical-position (event.code) letter/punctuation lookup used ONLY when
|
|
632
|
+
// event.altKey is true. On real macOS keyboards, Option+letter produces a
|
|
633
|
+
// composed/dead-key character in event.key (Option+A -> "å", Option+H ->
|
|
634
|
+
// "˙", Option+] tends to be unaffected but other punctuation isn't), so
|
|
635
|
+
// every alt-held combo must match on the physical key position instead of
|
|
636
|
+
// the printed character. This mirrors Figma's own behavior: its alt-letter
|
|
637
|
+
// shortcuts (align, distribute, frame-selection, create-component, z-order
|
|
638
|
+
// aliases, tidy up) fire from the QWERTY physical position regardless of
|
|
639
|
+
// what character Option composes on the active input layout.
|
|
640
|
+
const ALT_CODE_KEYS: Record<string, string> = {
|
|
641
|
+
KeyA: "a",
|
|
642
|
+
KeyB: "b",
|
|
643
|
+
KeyC: "c",
|
|
644
|
+
KeyD: "d",
|
|
645
|
+
KeyE: "e",
|
|
646
|
+
KeyF: "f",
|
|
647
|
+
KeyG: "g",
|
|
648
|
+
KeyH: "h",
|
|
649
|
+
KeyI: "i",
|
|
650
|
+
KeyJ: "j",
|
|
651
|
+
KeyK: "k",
|
|
652
|
+
KeyL: "l",
|
|
653
|
+
KeyM: "m",
|
|
654
|
+
KeyN: "n",
|
|
655
|
+
KeyO: "o",
|
|
656
|
+
KeyP: "p",
|
|
657
|
+
KeyQ: "q",
|
|
658
|
+
KeyR: "r",
|
|
659
|
+
KeyS: "s",
|
|
660
|
+
KeyT: "t",
|
|
661
|
+
KeyU: "u",
|
|
662
|
+
KeyV: "v",
|
|
663
|
+
KeyW: "w",
|
|
664
|
+
KeyX: "x",
|
|
665
|
+
KeyY: "y",
|
|
666
|
+
KeyZ: "z",
|
|
667
|
+
BracketRight: "]",
|
|
668
|
+
BracketLeft: "[",
|
|
669
|
+
};
|
|
670
|
+
|
|
423
671
|
function normalizedKey(event: KeyboardEvent) {
|
|
424
672
|
if (event.key === " ") return "space";
|
|
673
|
+
// Alt-held combos: derive the letter/punctuation from the physical key
|
|
674
|
+
// position (event.code) instead of the composed character (event.key).
|
|
675
|
+
// See ALT_CODE_KEYS above for why. Falls back to the plain event.key
|
|
676
|
+
// handling below for codes we don't map (e.g. Escape, Tab, arrows), so
|
|
677
|
+
// non-letter alt combos are unaffected.
|
|
678
|
+
if (event.altKey) {
|
|
679
|
+
const fromCode = ALT_CODE_KEYS[event.code];
|
|
680
|
+
if (fromCode) return fromCode;
|
|
681
|
+
}
|
|
425
682
|
return event.key.length === 1 ? event.key.toLowerCase() : event.key;
|
|
426
683
|
}
|
|
427
684
|
|