@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,315 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client";
|
|
2
|
+
import { IconLink, IconUnlink } from "@tabler/icons-react";
|
|
3
|
+
import { useState, type ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Label } from "@/components/ui/label";
|
|
7
|
+
import {
|
|
8
|
+
Tooltip,
|
|
9
|
+
TooltipContent,
|
|
10
|
+
TooltipTrigger,
|
|
11
|
+
} from "@/components/ui/tooltip";
|
|
12
|
+
import { cn } from "@/lib/utils";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
BreakpointOverrideIndicator,
|
|
16
|
+
MotionKeyframeDiamond,
|
|
17
|
+
motionPropertyHasKeyframe,
|
|
18
|
+
ScrubInput,
|
|
19
|
+
type MotionKeyframeCssProperty,
|
|
20
|
+
type ScrubInputChangeMeta,
|
|
21
|
+
} from "../inspector";
|
|
22
|
+
import type { ElementInfo } from "../types";
|
|
23
|
+
import { isMixedValue } from "./selection-helpers";
|
|
24
|
+
import {
|
|
25
|
+
resolveBreakpointOverride,
|
|
26
|
+
type BreakpointOverrideFieldContext,
|
|
27
|
+
type MotionKeyframeFieldContext,
|
|
28
|
+
type StyleChangeHandler,
|
|
29
|
+
type StylesChangeHandler,
|
|
30
|
+
} from "./style-change-types";
|
|
31
|
+
import { parseNumericValue, sidesAreLinked } from "./style-options";
|
|
32
|
+
|
|
33
|
+
export function DesignSpacingControl({
|
|
34
|
+
label,
|
|
35
|
+
values,
|
|
36
|
+
onChange,
|
|
37
|
+
}: {
|
|
38
|
+
label: string;
|
|
39
|
+
values: { top: string; right: string; bottom: string; left: string };
|
|
40
|
+
onChange: (side: string, value: string) => void;
|
|
41
|
+
}) {
|
|
42
|
+
const t = useT();
|
|
43
|
+
const [linked, setLinked] = useState(() => sidesAreLinked(values));
|
|
44
|
+
const numeric = {
|
|
45
|
+
top: parseNumericValue(values.top || "0"),
|
|
46
|
+
right: parseNumericValue(values.right || "0"),
|
|
47
|
+
bottom: parseNumericValue(values.bottom || "0"),
|
|
48
|
+
left: parseNumericValue(values.left || "0"),
|
|
49
|
+
};
|
|
50
|
+
const linkedValue = Math.round(
|
|
51
|
+
(numeric.top + numeric.right + numeric.bottom + numeric.left) / 4,
|
|
52
|
+
);
|
|
53
|
+
const setSide = (
|
|
54
|
+
side: "Top" | "Right" | "Bottom" | "Left",
|
|
55
|
+
value: number,
|
|
56
|
+
) => {
|
|
57
|
+
onChange(side, `${Math.round(value)}px`);
|
|
58
|
+
};
|
|
59
|
+
const setAll = (value: number) => {
|
|
60
|
+
(["Top", "Right", "Bottom", "Left"] as const).forEach((side) =>
|
|
61
|
+
setSide(side, value),
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
const linkedLabel = linked
|
|
65
|
+
? t("editPanel.labels.unlinkSides")
|
|
66
|
+
: t("editPanel.labels.linkSides");
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="space-y-1.5">
|
|
70
|
+
<div className="flex items-center justify-between gap-1.5">
|
|
71
|
+
<Label className="!text-[11px] font-medium text-muted-foreground">
|
|
72
|
+
{label}
|
|
73
|
+
</Label>
|
|
74
|
+
<Tooltip>
|
|
75
|
+
<TooltipTrigger asChild>
|
|
76
|
+
<Button
|
|
77
|
+
type="button"
|
|
78
|
+
variant="ghost"
|
|
79
|
+
size="icon"
|
|
80
|
+
className="size-6 rounded-md text-muted-foreground hover:text-foreground"
|
|
81
|
+
onClick={() => setLinked((current) => !current)}
|
|
82
|
+
aria-label={linkedLabel}
|
|
83
|
+
>
|
|
84
|
+
{linked ? (
|
|
85
|
+
<IconLink className="size-3.5" />
|
|
86
|
+
) : (
|
|
87
|
+
<IconUnlink className="size-3.5" />
|
|
88
|
+
)}
|
|
89
|
+
</Button>
|
|
90
|
+
</TooltipTrigger>
|
|
91
|
+
<TooltipContent>{linkedLabel}</TooltipContent>
|
|
92
|
+
</Tooltip>
|
|
93
|
+
</div>
|
|
94
|
+
{linked ? (
|
|
95
|
+
<ScrubInput
|
|
96
|
+
label={t("editPanel.labels.allSides")}
|
|
97
|
+
value={linkedValue}
|
|
98
|
+
onChange={setAll}
|
|
99
|
+
unit="px"
|
|
100
|
+
min={0}
|
|
101
|
+
labelClassName="w-16"
|
|
102
|
+
inputClassName="h-6"
|
|
103
|
+
/>
|
|
104
|
+
) : (
|
|
105
|
+
<div className="grid grid-cols-2 gap-1.5">
|
|
106
|
+
<ScrubInput
|
|
107
|
+
label={t("editPanel.sidePlaceholders.top")}
|
|
108
|
+
value={numeric.top}
|
|
109
|
+
onChange={(value) => setSide("Top", value)}
|
|
110
|
+
unit="px"
|
|
111
|
+
min={0}
|
|
112
|
+
precision={1}
|
|
113
|
+
inputClassName="h-6"
|
|
114
|
+
/>
|
|
115
|
+
<ScrubInput
|
|
116
|
+
label={t("editPanel.sidePlaceholders.right")}
|
|
117
|
+
value={numeric.right}
|
|
118
|
+
onChange={(value) => setSide("Right", value)}
|
|
119
|
+
unit="px"
|
|
120
|
+
min={0}
|
|
121
|
+
precision={1}
|
|
122
|
+
inputClassName="h-6"
|
|
123
|
+
/>
|
|
124
|
+
<ScrubInput
|
|
125
|
+
label={t("editPanel.sidePlaceholders.bottom")}
|
|
126
|
+
value={numeric.bottom}
|
|
127
|
+
onChange={(value) => setSide("Bottom", value)}
|
|
128
|
+
unit="px"
|
|
129
|
+
min={0}
|
|
130
|
+
precision={1}
|
|
131
|
+
inputClassName="h-6"
|
|
132
|
+
/>
|
|
133
|
+
<ScrubInput
|
|
134
|
+
label={t("editPanel.sidePlaceholders.left")}
|
|
135
|
+
value={numeric.left}
|
|
136
|
+
onChange={(value) => setSide("Left", value)}
|
|
137
|
+
unit="px"
|
|
138
|
+
min={0}
|
|
139
|
+
precision={1}
|
|
140
|
+
inputClassName="h-6"
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* FieldTrailer — composes the motion keyframe diamond and the breakpoint
|
|
150
|
+
* override indicator/reset for one field, in the Figma-parity order (diamond
|
|
151
|
+
* first, then the override dot). Renders `null` when neither affordance
|
|
152
|
+
* applies, so call sites can drop it in unconditionally next to any
|
|
153
|
+
* keyframeable/overridable field without their own presence checks.
|
|
154
|
+
*
|
|
155
|
+
* `motionCssProperty` drives the keyframe diamond (omit to skip it — e.g.
|
|
156
|
+
* for fields with no motion-catalog equivalent); `overrideProperty` drives
|
|
157
|
+
* the breakpoint override indicator (defaults to `motionCssProperty` when
|
|
158
|
+
* omitted, since most fields use the same identifier for both — pass it
|
|
159
|
+
* explicitly when a field's CSS property differs from its motion-catalog
|
|
160
|
+
* name, e.g. corner radius's independent-corner longhands).
|
|
161
|
+
*/
|
|
162
|
+
export function FieldTrailer({
|
|
163
|
+
element,
|
|
164
|
+
motionCssProperty,
|
|
165
|
+
overrideProperty,
|
|
166
|
+
motionKeyframeContext,
|
|
167
|
+
breakpointOverrideContext,
|
|
168
|
+
hoverRevealClassName,
|
|
169
|
+
className,
|
|
170
|
+
}: {
|
|
171
|
+
element: ElementInfo;
|
|
172
|
+
motionCssProperty?: MotionKeyframeCssProperty;
|
|
173
|
+
overrideProperty?: string;
|
|
174
|
+
motionKeyframeContext?: MotionKeyframeFieldContext;
|
|
175
|
+
breakpointOverrideContext?: BreakpointOverrideFieldContext;
|
|
176
|
+
/**
|
|
177
|
+
* Applied ONLY to the keyframe diamond, and only while it's in its muted
|
|
178
|
+
* outline (not-yet-keyframed) state — e.g. `"opacity-0
|
|
179
|
+
* group-hover/field:opacity-100"` to hide it until the field is hovered.
|
|
180
|
+
* A filled (already-keyframed) diamond, and the breakpoint override dot,
|
|
181
|
+
* always render regardless of this class since both convey real state
|
|
182
|
+
* rather than a quiet affordance.
|
|
183
|
+
*/
|
|
184
|
+
hoverRevealClassName?: string;
|
|
185
|
+
className?: string;
|
|
186
|
+
}) {
|
|
187
|
+
const showDiamond =
|
|
188
|
+
motionCssProperty != null && motionKeyframeContext?.hasTimeline === true;
|
|
189
|
+
const hasKeyframe = showDiamond
|
|
190
|
+
? motionPropertyHasKeyframe(
|
|
191
|
+
motionKeyframeContext?.keyframedProperties,
|
|
192
|
+
motionCssProperty!,
|
|
193
|
+
)
|
|
194
|
+
: false;
|
|
195
|
+
const resolvedOverrideProperty = overrideProperty ?? motionCssProperty;
|
|
196
|
+
const overrideState = resolvedOverrideProperty
|
|
197
|
+
? resolveBreakpointOverride(
|
|
198
|
+
breakpointOverrideContext,
|
|
199
|
+
element.classes.join(" "),
|
|
200
|
+
resolvedOverrideProperty,
|
|
201
|
+
)
|
|
202
|
+
: undefined;
|
|
203
|
+
|
|
204
|
+
if (!showDiamond && !overrideState?.overriddenAtActive) return null;
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<span
|
|
208
|
+
className={cn(
|
|
209
|
+
"group/trailer inline-flex items-center gap-0.5",
|
|
210
|
+
className,
|
|
211
|
+
)}
|
|
212
|
+
>
|
|
213
|
+
{showDiamond ? (
|
|
214
|
+
<MotionKeyframeDiamond
|
|
215
|
+
cssProperty={motionCssProperty!}
|
|
216
|
+
hasKeyframe={hasKeyframe}
|
|
217
|
+
onToggle={() => motionKeyframeContext?.onToggle?.(motionCssProperty!)}
|
|
218
|
+
className={hasKeyframe ? undefined : hoverRevealClassName}
|
|
219
|
+
/>
|
|
220
|
+
) : null}
|
|
221
|
+
{overrideState?.overriddenAtActive && resolvedOverrideProperty ? (
|
|
222
|
+
<BreakpointOverrideIndicator
|
|
223
|
+
overridden
|
|
224
|
+
maxWidthPx={overrideState.activeUpperBoundPx}
|
|
225
|
+
onReset={
|
|
226
|
+
breakpointOverrideContext &&
|
|
227
|
+
overrideState.activeUpperBoundPx != null
|
|
228
|
+
? () =>
|
|
229
|
+
breakpointOverrideContext.onReset(
|
|
230
|
+
resolvedOverrideProperty,
|
|
231
|
+
overrideState.activeUpperBoundPx!,
|
|
232
|
+
)
|
|
233
|
+
: undefined
|
|
234
|
+
}
|
|
235
|
+
/>
|
|
236
|
+
) : null}
|
|
237
|
+
</span>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function ScrubStyleInput({
|
|
242
|
+
label,
|
|
243
|
+
value,
|
|
244
|
+
placeholder,
|
|
245
|
+
onChange,
|
|
246
|
+
unit = "px",
|
|
247
|
+
min,
|
|
248
|
+
max,
|
|
249
|
+
step = 1,
|
|
250
|
+
labelClassName,
|
|
251
|
+
inputClassName,
|
|
252
|
+
ariaLabel,
|
|
253
|
+
tooltipLabel,
|
|
254
|
+
hideIcon = true,
|
|
255
|
+
icon,
|
|
256
|
+
disabled = false,
|
|
257
|
+
}: {
|
|
258
|
+
label: string;
|
|
259
|
+
value: string;
|
|
260
|
+
placeholder?: number;
|
|
261
|
+
onChange: (value: number, meta?: ScrubInputChangeMeta) => void;
|
|
262
|
+
unit?: string;
|
|
263
|
+
min?: number;
|
|
264
|
+
max?: number;
|
|
265
|
+
step?: number;
|
|
266
|
+
labelClassName?: string;
|
|
267
|
+
inputClassName?: string;
|
|
268
|
+
hideIcon?: boolean;
|
|
269
|
+
ariaLabel?: string;
|
|
270
|
+
tooltipLabel?: string;
|
|
271
|
+
icon?: (props: { className?: string }) => ReactNode;
|
|
272
|
+
disabled?: boolean;
|
|
273
|
+
}) {
|
|
274
|
+
const mixed = isMixedValue(value);
|
|
275
|
+
return (
|
|
276
|
+
<ScrubInput
|
|
277
|
+
label={label}
|
|
278
|
+
ariaLabel={ariaLabel}
|
|
279
|
+
tooltipLabel={tooltipLabel}
|
|
280
|
+
icon={hideIcon ? null : icon}
|
|
281
|
+
value={mixed ? 0 : value ? parseNumericValue(value) : (placeholder ?? 0)}
|
|
282
|
+
onChange={onChange}
|
|
283
|
+
mixed={mixed}
|
|
284
|
+
unit={unit}
|
|
285
|
+
min={min}
|
|
286
|
+
max={max}
|
|
287
|
+
step={step}
|
|
288
|
+
precision={1}
|
|
289
|
+
disabled={disabled}
|
|
290
|
+
className="gap-0"
|
|
291
|
+
labelClassName={cn(
|
|
292
|
+
"h-6 w-7 justify-center gap-0 rounded-l-md rounded-r-none border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] !text-[11px] tabular-nums",
|
|
293
|
+
labelClassName,
|
|
294
|
+
)}
|
|
295
|
+
inputClassName={cn(
|
|
296
|
+
"h-6 rounded-l-none rounded-r-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
|
|
297
|
+
inputClassName,
|
|
298
|
+
)}
|
|
299
|
+
/>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function commitStylePatch(
|
|
304
|
+
styles: Record<string, string>,
|
|
305
|
+
onStyleChange: StyleChangeHandler,
|
|
306
|
+
onStylesChange?: StylesChangeHandler,
|
|
307
|
+
) {
|
|
308
|
+
if (onStylesChange) {
|
|
309
|
+
onStylesChange(styles);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
Object.entries(styles).forEach(([property, value]) => {
|
|
313
|
+
onStyleChange(property, value);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import {
|
|
2
|
+
alphaToOpacity,
|
|
3
|
+
parseCssColor,
|
|
4
|
+
rgbaToCss,
|
|
5
|
+
withColorOpacity,
|
|
6
|
+
} from "@shared/color-utils";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type DesignFillRow,
|
|
10
|
+
type DesignGradientStop,
|
|
11
|
+
type DesignGradientType,
|
|
12
|
+
type ExportSettingsValue,
|
|
13
|
+
} from "../inspector";
|
|
14
|
+
import { cssColorOrFallback } from "./position-helpers";
|
|
15
|
+
|
|
16
|
+
export const SOLID_FILL_ID = "solid";
|
|
17
|
+
export const FILL_LAYER_PREFIX = "layer:";
|
|
18
|
+
|
|
19
|
+
interface ParsedGradientLayer {
|
|
20
|
+
type: DesignGradientType;
|
|
21
|
+
prefix?: string;
|
|
22
|
+
stops: DesignGradientStop[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_EXPORT_SETTINGS: ExportSettingsValue = {
|
|
26
|
+
scale: 1,
|
|
27
|
+
format: "png",
|
|
28
|
+
suffix: "",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function clampNumber(value: number, min: number, max: number): number {
|
|
32
|
+
if (!Number.isFinite(value)) return min;
|
|
33
|
+
return Math.max(min, Math.min(max, Math.round(value)));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function fillLayerId(index: number): string {
|
|
37
|
+
return `${FILL_LAYER_PREFIX}${index}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function fillLayerIndex(id: string): number | null {
|
|
41
|
+
if (!id.startsWith(FILL_LAYER_PREFIX)) return null;
|
|
42
|
+
const index = Number(id.slice(FILL_LAYER_PREFIX.length));
|
|
43
|
+
return Number.isInteger(index) && index >= 0 ? index : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function buildFillRows(
|
|
47
|
+
colorValue: string,
|
|
48
|
+
backgroundLayers: string[],
|
|
49
|
+
selectedFillId: string,
|
|
50
|
+
): DesignFillRow[] {
|
|
51
|
+
const solid = parseCssColor(colorValue);
|
|
52
|
+
const rows: DesignFillRow[] = [
|
|
53
|
+
{
|
|
54
|
+
id: SOLID_FILL_ID,
|
|
55
|
+
label: "Solid", // i18n-ignore inspector fallback label
|
|
56
|
+
type: "solid",
|
|
57
|
+
value: colorValue,
|
|
58
|
+
swatch: colorValue,
|
|
59
|
+
opacity: solid ? alphaToOpacity(solid.a) : 100,
|
|
60
|
+
selected: selectedFillId === SOLID_FILL_ID,
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
backgroundLayers.forEach((layer, index) => {
|
|
65
|
+
const gradient = parseGradientLayer(layer);
|
|
66
|
+
rows.push({
|
|
67
|
+
id: fillLayerId(index),
|
|
68
|
+
label: gradient
|
|
69
|
+
? `Gradient ${index + 1}` // i18n-ignore inspector fallback label
|
|
70
|
+
: `Image ${index + 1}`, // i18n-ignore inspector fallback label
|
|
71
|
+
type: gradient ? "gradient" : "image",
|
|
72
|
+
value: layer,
|
|
73
|
+
swatch: layer,
|
|
74
|
+
opacity: gradient ? averageGradientOpacity(gradient.stops) : 100,
|
|
75
|
+
selected: selectedFillId === fillLayerId(index),
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return rows;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function averageGradientOpacity(stops: DesignGradientStop[]): number {
|
|
83
|
+
if (!stops.length) return 100;
|
|
84
|
+
const total = stops.reduce((sum, stop) => {
|
|
85
|
+
const parsed = parseCssColor(stop.color);
|
|
86
|
+
return sum + (stop.opacity ?? (parsed ? alphaToOpacity(parsed.a) : 100));
|
|
87
|
+
}, 0);
|
|
88
|
+
return Math.round(total / stops.length);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Marker used to non-destructively hide a single backgroundImage layer
|
|
93
|
+
* (gradient or image). CSS strips comments from computed style values — a
|
|
94
|
+
* trailing comment appended to a backgroundImage layer does not survive
|
|
95
|
+
* getComputedStyle (verified: browsers normalize/serialize computed values
|
|
96
|
+
* without their source comments) — so we can't tag the layer text itself.
|
|
97
|
+
* Instead we pair the untouched original layer with a zero-size
|
|
98
|
+
* background-size entry at the same index: `background-size: 0px 0px` makes
|
|
99
|
+
* that layer render nothing while backgroundImage keeps the exact original
|
|
100
|
+
* CSS text. Both backgroundImage and backgroundSize are real, valid,
|
|
101
|
+
* positionally-paired CSS lists that DO round-trip through computed style,
|
|
102
|
+
* so hiding survives reselect/reload with no React state stash required.
|
|
103
|
+
*/
|
|
104
|
+
const HIDDEN_LAYER_SIZE_MARKER = "0px 0px";
|
|
105
|
+
|
|
106
|
+
export function isLayerHiddenBySize(sizeEntry: string | undefined): boolean {
|
|
107
|
+
return (
|
|
108
|
+
(sizeEntry ?? "").trim().replace(/\s+/g, " ") === HIDDEN_LAYER_SIZE_MARKER
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Rewrites the background-size list so `index` is hidden/shown via the
|
|
114
|
+
* zero-size marker, padding shorter lists with "auto" (the CSS default) so
|
|
115
|
+
* every other layer keeps rendering at its current/default size.
|
|
116
|
+
*/
|
|
117
|
+
export function withLayerSizeMarker(
|
|
118
|
+
sizeLayers: string[],
|
|
119
|
+
layerCount: number,
|
|
120
|
+
index: number,
|
|
121
|
+
hidden: boolean,
|
|
122
|
+
): string {
|
|
123
|
+
const next = Array.from(
|
|
124
|
+
{ length: layerCount },
|
|
125
|
+
(_, i) => sizeLayers[i] || "auto",
|
|
126
|
+
);
|
|
127
|
+
next[index] = hidden ? HIDDEN_LAYER_SIZE_MARKER : "auto";
|
|
128
|
+
return joinCssLayers(next);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function splitCssLayers(value: string): string[] {
|
|
132
|
+
const trimmed = value.trim();
|
|
133
|
+
if (!trimmed || trimmed === "none") return [];
|
|
134
|
+
const layers: string[] = [];
|
|
135
|
+
let depth = 0;
|
|
136
|
+
let start = 0;
|
|
137
|
+
|
|
138
|
+
for (let index = 0; index < trimmed.length; index += 1) {
|
|
139
|
+
const char = trimmed[index];
|
|
140
|
+
if (char === "(") depth += 1;
|
|
141
|
+
if (char === ")") depth = Math.max(0, depth - 1);
|
|
142
|
+
if (char === "," && depth === 0) {
|
|
143
|
+
const layer = trimmed.slice(start, index).trim();
|
|
144
|
+
if (layer) layers.push(layer);
|
|
145
|
+
start = index + 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const finalLayer = trimmed.slice(start).trim();
|
|
150
|
+
if (finalLayer) layers.push(finalLayer);
|
|
151
|
+
return layers;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function joinCssLayers(layers: string[]): string {
|
|
155
|
+
const cleaned = layers.map((layer) => layer.trim()).filter(Boolean);
|
|
156
|
+
return cleaned.length ? cleaned.join(", ") : "none";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** One fill layer's index-aligned parallel CSS values. */
|
|
160
|
+
export interface FillLayerArrays {
|
|
161
|
+
backgroundImage: string[];
|
|
162
|
+
backgroundSize: string[];
|
|
163
|
+
backgroundRepeat: string[];
|
|
164
|
+
backgroundPosition: string[];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Removes the layer at `index` from all four index-aligned parallel fill
|
|
169
|
+
* arrays (image/size/repeat/position) together, returning a single patch of
|
|
170
|
+
* joined CSS layer-list strings ready to commit as one atomic style change.
|
|
171
|
+
*
|
|
172
|
+
* Splicing only `backgroundImage`/`backgroundSize` (as a previous version of
|
|
173
|
+
* `removeLayer` did) and leaving `backgroundRepeat`/`backgroundPosition`
|
|
174
|
+
* untouched shifts every remaining layer's index relative to those two
|
|
175
|
+
* arrays, silently re-pairing each of them with the *next* layer's original
|
|
176
|
+
* repeat/position. Splicing all four together — the same pattern
|
|
177
|
+
* `reorderFillLayers` already uses for permutation — keeps every remaining
|
|
178
|
+
* layer's size/repeat/position aligned with its own image after the removal.
|
|
179
|
+
*/
|
|
180
|
+
export function removeFillLayerAtIndex(
|
|
181
|
+
layers: FillLayerArrays,
|
|
182
|
+
index: number,
|
|
183
|
+
): Record<
|
|
184
|
+
| "backgroundImage"
|
|
185
|
+
| "backgroundSize"
|
|
186
|
+
| "backgroundRepeat"
|
|
187
|
+
| "backgroundPosition",
|
|
188
|
+
string
|
|
189
|
+
> {
|
|
190
|
+
const withoutIndex = (values: string[]) =>
|
|
191
|
+
values.filter((_, layerIndex) => layerIndex !== index);
|
|
192
|
+
return {
|
|
193
|
+
backgroundImage: joinCssLayers(withoutIndex(layers.backgroundImage)),
|
|
194
|
+
backgroundSize: joinCssLayers(withoutIndex(layers.backgroundSize)),
|
|
195
|
+
backgroundRepeat: joinCssLayers(withoutIndex(layers.backgroundRepeat)),
|
|
196
|
+
backgroundPosition: joinCssLayers(withoutIndex(layers.backgroundPosition)),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function parseGradientLayer(layer: string): ParsedGradientLayer | null {
|
|
201
|
+
const match = layer.trim().match(/^(linear|radial|conic)-gradient\((.*)\)$/i);
|
|
202
|
+
if (!match) return null;
|
|
203
|
+
|
|
204
|
+
const parts = splitCssLayers(match[2] || "");
|
|
205
|
+
const type = gradientTypeFromCss(match[1] || "", layer);
|
|
206
|
+
const firstStop = parseGradientStop(parts[0] || "", 0, parts.length);
|
|
207
|
+
const prefix = firstStop ? undefined : parts[0]?.trim();
|
|
208
|
+
const stopParts = firstStop ? parts : parts.slice(1);
|
|
209
|
+
const stops = stopParts
|
|
210
|
+
.map((part, index) => parseGradientStop(part, index, stopParts.length))
|
|
211
|
+
.filter((stop): stop is DesignGradientStop => Boolean(stop));
|
|
212
|
+
|
|
213
|
+
if (!stops.length) return null;
|
|
214
|
+
return { type, prefix, stops };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseGradientStop(
|
|
218
|
+
part: string,
|
|
219
|
+
index: number,
|
|
220
|
+
total: number,
|
|
221
|
+
): DesignGradientStop | null {
|
|
222
|
+
const color = readLeadingColor(part);
|
|
223
|
+
if (!color) return null;
|
|
224
|
+
const parsed = parseCssColor(color.value);
|
|
225
|
+
const remaining = part.slice(color.raw.length);
|
|
226
|
+
const positionMatch = remaining.match(/(-?\d+(?:\.\d+)?)%/);
|
|
227
|
+
const position = positionMatch
|
|
228
|
+
? clampNumber(Number(positionMatch[1]), 0, 100)
|
|
229
|
+
: total <= 1
|
|
230
|
+
? 0
|
|
231
|
+
: Math.round((index / (total - 1)) * 100);
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
id: `stop-${index}`,
|
|
235
|
+
color: parsed ? rgbaToCss(parsed) : color.value,
|
|
236
|
+
position,
|
|
237
|
+
opacity: parsed ? alphaToOpacity(parsed.a) : 100,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function readLeadingColor(part: string): { raw: string; value: string } | null {
|
|
242
|
+
const trimmed = part.trim();
|
|
243
|
+
const hex = trimmed.match(/^#[0-9a-f]{3,8}\b/i);
|
|
244
|
+
if (hex) return { raw: hex[0], value: hex[0] };
|
|
245
|
+
const transparent = trimmed.match(/^transparent\b/i);
|
|
246
|
+
if (transparent) {
|
|
247
|
+
return { raw: transparent[0], value: "rgba(0, 0, 0, 0)" };
|
|
248
|
+
}
|
|
249
|
+
const functionName = trimmed.match(/^[a-z][a-z0-9-]*\(/i);
|
|
250
|
+
if (!functionName) return null;
|
|
251
|
+
let depth = 0;
|
|
252
|
+
for (let index = 0; index < trimmed.length; index += 1) {
|
|
253
|
+
const char = trimmed[index];
|
|
254
|
+
if (char === "(") depth += 1;
|
|
255
|
+
if (char === ")") {
|
|
256
|
+
depth -= 1;
|
|
257
|
+
if (depth === 0) {
|
|
258
|
+
const raw = trimmed.slice(0, index + 1);
|
|
259
|
+
return { raw, value: raw };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function gradientTypeFromCss(
|
|
267
|
+
functionName: string,
|
|
268
|
+
layer: string,
|
|
269
|
+
): DesignGradientType {
|
|
270
|
+
if (functionName.toLowerCase() === "conic") return "angular";
|
|
271
|
+
// Recognize both diamond serializations — EditPanel's "closest-corner" and
|
|
272
|
+
// GradientEditor's "ellipse closest-side" — so a diamond authored in either
|
|
273
|
+
// place round-trips as diamond instead of flipping to radial.
|
|
274
|
+
if (/closest-corner/i.test(layer) || /ellipse\s+closest-side/i.test(layer))
|
|
275
|
+
return "diamond";
|
|
276
|
+
if (functionName.toLowerCase() === "radial") return "radial";
|
|
277
|
+
return "linear";
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function gradientLabel(type: DesignGradientType): string {
|
|
281
|
+
if (type === "radial") {
|
|
282
|
+
return "Radial gradient"; // i18n-ignore design inspector paint row
|
|
283
|
+
}
|
|
284
|
+
if (type === "angular") {
|
|
285
|
+
return "Angular gradient"; // i18n-ignore design inspector paint row
|
|
286
|
+
}
|
|
287
|
+
if (type === "diamond") {
|
|
288
|
+
return "Diamond gradient"; // i18n-ignore design inspector paint row
|
|
289
|
+
}
|
|
290
|
+
return "Linear gradient"; // i18n-ignore design inspector paint row
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function defaultGradientPrefix(type: DesignGradientType): string {
|
|
294
|
+
if (type === "radial") return "circle at 50% 50%";
|
|
295
|
+
if (type === "angular") return "from 0deg at 50% 50%";
|
|
296
|
+
if (type === "diamond") return "closest-corner at 50% 50%";
|
|
297
|
+
return "90deg";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function buildGradientLayer(
|
|
301
|
+
type: DesignGradientType,
|
|
302
|
+
stops: DesignGradientStop[],
|
|
303
|
+
prefix = defaultGradientPrefix(type),
|
|
304
|
+
): string {
|
|
305
|
+
const stopList = [...stops]
|
|
306
|
+
.sort((a, b) => a.position - b.position)
|
|
307
|
+
.map((stop) => {
|
|
308
|
+
const parsed = parseCssColor(stop.color);
|
|
309
|
+
const opacity = stop.opacity ?? (parsed ? alphaToOpacity(parsed.a) : 100);
|
|
310
|
+
const color = parsed
|
|
311
|
+
? rgbaToCss(withColorOpacity(parsed, opacity))
|
|
312
|
+
: stop.color;
|
|
313
|
+
return `${color} ${clampNumber(stop.position, 0, 100)}%`;
|
|
314
|
+
})
|
|
315
|
+
.join(", ");
|
|
316
|
+
|
|
317
|
+
if (type === "radial" || type === "diamond") {
|
|
318
|
+
return `radial-gradient(${prefix}, ${stopList})`;
|
|
319
|
+
}
|
|
320
|
+
if (type === "angular") return `conic-gradient(${prefix}, ${stopList})`;
|
|
321
|
+
return `linear-gradient(${prefix}, ${stopList})`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function defaultGradientStops(colorValue: string): DesignGradientStop[] {
|
|
325
|
+
const parsed =
|
|
326
|
+
parseCssColor(cssColorOrFallback(colorValue, "#000000")) ??
|
|
327
|
+
parseCssColor("#000000");
|
|
328
|
+
const start = parsed ? rgbaToCss(withColorOpacity(parsed, 100)) : "#000000";
|
|
329
|
+
const end = parsed
|
|
330
|
+
? rgbaToCss(withColorOpacity(parsed, 0))
|
|
331
|
+
: "rgba(0, 0, 0, 0)";
|
|
332
|
+
|
|
333
|
+
return [
|
|
334
|
+
{ id: "stop-0", color: start, position: 0, opacity: 100 },
|
|
335
|
+
{ id: "stop-1", color: end, position: 100, opacity: 0 },
|
|
336
|
+
];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function defaultGradientLayer(
|
|
340
|
+
type: DesignGradientType,
|
|
341
|
+
colorValue: string,
|
|
342
|
+
) {
|
|
343
|
+
return buildGradientLayer(type, defaultGradientStops(colorValue));
|
|
344
|
+
}
|