@agent-native/core 0.80.10 → 0.81.0
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 +53 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +108 -15
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/AssistantChat.tsx +123 -26
- package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
- package/corpus/core/src/client/chat-view-transition.ts +33 -2
- package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
- package/corpus/core/src/client/sse-event-processor.ts +9 -0
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
- package/corpus/core/src/db/client.ts +29 -2
- package/corpus/core/src/provider-api/index.ts +29 -0
- package/corpus/core/src/server/core-routes-plugin.ts +58 -20
- package/corpus/core/src/styles/agent-native.css +54 -0
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
- package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
- package/corpus/templates/analytics/app/global.css +8 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
- package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
- package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
- package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
- package/corpus/templates/assets/AGENTS.md +4 -0
- package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
- package/corpus/templates/assets/actions/generate-image.ts +118 -67
- package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
- package/corpus/templates/assets/app/global.css +15 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
- package/corpus/templates/assets/app/i18n-data.ts +61 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +233 -52
- package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/assets/server/lib/generation.ts +135 -11
- package/corpus/templates/assets/shared/api.ts +4 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
- package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
- package/corpus/templates/design/AGENTS.md +27 -0
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +76 -25
- package/corpus/templates/design/actions/apply-design-state.ts +17 -4
- package/corpus/templates/design/actions/apply-motion-edit.ts +76 -115
- package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
- package/corpus/templates/design/actions/capture-design-state.ts +27 -4
- package/corpus/templates/design/actions/connect-localhost.ts +59 -22
- package/corpus/templates/design/actions/delete-design.ts +20 -0
- package/corpus/templates/design/actions/export-zip.ts +23 -8
- package/corpus/templates/design/actions/generate-design.ts +53 -1
- package/corpus/templates/design/actions/get-component-details.ts +21 -26
- package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +18 -5
- package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
- package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
- package/corpus/templates/design/actions/list-design-states.ts +19 -1
- package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
- package/corpus/templates/design/actions/navigate.ts +18 -3
- package/corpus/templates/design/actions/open-component-source.ts +7 -2
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +32 -26
- package/corpus/templates/design/actions/preview-shader-fill.ts +9 -11
- package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
- package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
- package/corpus/templates/design/actions/provider-api-request.ts +3 -3
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
- package/corpus/templates/design/actions/run-design-audit.ts +74 -10
- package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
- package/corpus/templates/design/actions/view-screen.ts +1 -1
- package/corpus/templates/design/actions/write-local-file.ts +213 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +244 -3197
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +795 -289
- package/corpus/templates/design/app/components/design/EditPanel.tsx +635 -234
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +124 -32
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +244 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +491 -56
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +55 -67
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +38 -63
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +264 -8
- package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
- package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
- package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
- package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
- package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
- package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/global.css +7 -0
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +22 -0
- package/corpus/templates/design/app/i18n-data.ts +369 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +3024 -616
- package/corpus/templates/design/app/pages/Index.tsx +5 -1
- package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-token-edits-now-stay-visible-in-previews-and-token-li.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +94 -2
- package/corpus/templates/design/e2e/helpers.ts +168 -65
- package/corpus/templates/design/package.json +3 -1
- package/corpus/templates/design/server/db/schema.ts +23 -0
- package/corpus/templates/design/server/handlers/uploads.ts +22 -1
- package/corpus/templates/design/server/lib/provider-api.ts +7 -4
- package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
- package/corpus/templates/design/server/plugins/db.ts +39 -0
- package/corpus/templates/design/server/register-secrets.ts +38 -0
- package/corpus/templates/design/shared/board-file.ts +228 -0
- package/corpus/templates/design/shared/board-objects.ts +288 -0
- package/corpus/templates/design/shared/code-layer.ts +52 -0
- package/corpus/templates/design/shared/component-model.ts +35 -0
- package/corpus/templates/design/shared/motion-compiler.ts +137 -15
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
- package/corpus/templates/design/tsconfig.json +2 -1
- package/corpus/templates/slides/AGENTS.md +9 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
- package/corpus/templates/slides/app/vite-env.d.ts +18 -0
- package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/vite.config.ts +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +94 -10
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts +25 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +357 -18
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +14 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +115 -22
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +1 -0
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +5 -5
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat-view-transition.d.ts +6 -0
- package/dist/client/chat-view-transition.d.ts.map +1 -1
- package/dist/client/chat-view-transition.js +23 -2
- package/dist/client/chat-view-transition.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +3 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +11 -4
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -3
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +20 -6
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +28 -2
- package/dist/db/client.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/index.d.ts +5 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -0
- package/dist/provider-api/index.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +25 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +43 -18
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +54 -0
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
rgbaToHex,
|
|
12
12
|
withColorOpacity,
|
|
13
13
|
} from "@shared/color-utils";
|
|
14
|
+
import { propNameToDataAttribute } from "@shared/component-model";
|
|
14
15
|
import {
|
|
15
16
|
IconAlignCenter,
|
|
16
17
|
IconAlignJustified,
|
|
@@ -51,7 +52,6 @@ import {
|
|
|
51
52
|
IconLock,
|
|
52
53
|
IconMaximize,
|
|
53
54
|
IconMinus,
|
|
54
|
-
IconPalette,
|
|
55
55
|
IconPhoto,
|
|
56
56
|
IconPlus,
|
|
57
57
|
IconShadow,
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
type ReactNode,
|
|
70
70
|
} from "react";
|
|
71
71
|
import { useParams } from "react-router";
|
|
72
|
+
import { toast } from "sonner";
|
|
72
73
|
|
|
73
74
|
import { Button } from "@/components/ui/button";
|
|
74
75
|
import {
|
|
@@ -109,10 +110,6 @@ import {
|
|
|
109
110
|
} from "@/components/ui/tooltip";
|
|
110
111
|
import { cn } from "@/lib/utils";
|
|
111
112
|
|
|
112
|
-
import {
|
|
113
|
-
DesignExtensionsPanel,
|
|
114
|
-
type DesignExtensionSlotContext,
|
|
115
|
-
} from "./DesignExtensionsPanel";
|
|
116
113
|
import {
|
|
117
114
|
AutoLayoutMatrix,
|
|
118
115
|
ConstraintsWidget,
|
|
@@ -141,18 +138,10 @@ import { ReviewPanel } from "./ReviewPanel";
|
|
|
141
138
|
import type { ReviewPanelProps } from "./ReviewPanel";
|
|
142
139
|
import { StatesPanel } from "./StatesPanel";
|
|
143
140
|
import type { StatesPanelProps } from "./StatesPanel";
|
|
144
|
-
import { TokensPanel } from "./TokensPanel";
|
|
145
141
|
import { TweaksPanelContent } from "./TweaksPanel";
|
|
146
142
|
import type { ElementInfo } from "./types";
|
|
147
143
|
|
|
148
|
-
export type InspectorTab = "design" | "tweaks"
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Sub-tab within the Tweaks area: "tokens" = TokensPanel face (CSS-var
|
|
152
|
-
* editing through the Tweaks loop); "tweaks" = raw TweakDefinition controls.
|
|
153
|
-
* Defaults to "tokens" when a designId is provided, otherwise "tweaks".
|
|
154
|
-
*/
|
|
155
|
-
type TweaksSubTab = "tokens" | "tweaks";
|
|
144
|
+
export type InspectorTab = "design" | "tweaks";
|
|
156
145
|
|
|
157
146
|
interface EditPanelProps {
|
|
158
147
|
selectedElement: ElementInfo | null;
|
|
@@ -164,7 +153,6 @@ interface EditPanelProps {
|
|
|
164
153
|
onActiveTabChange?: (tab: InspectorTab) => void;
|
|
165
154
|
tweaks?: TweakDefinition[];
|
|
166
155
|
tweakValues?: Record<string, string | number | boolean>;
|
|
167
|
-
extensionContext?: DesignExtensionSlotContext;
|
|
168
156
|
onTweakChange?: (id: string, value: string | number | boolean) => void;
|
|
169
157
|
onRequestTweaks?: (anchor: HTMLElement) => void;
|
|
170
158
|
onStyleChange: (property: string, value: string) => void;
|
|
@@ -180,12 +168,25 @@ interface EditPanelProps {
|
|
|
180
168
|
fileId?: string;
|
|
181
169
|
/** Active file name (e.g. "index.html") — forwarded to InspectorAiActions. */
|
|
182
170
|
filename?: string;
|
|
171
|
+
/** Latest active file HTML, used to compose rapid sequential source edits. */
|
|
172
|
+
activeContent?: string;
|
|
173
|
+
/** Server revision for activeContent. */
|
|
174
|
+
activeFileUpdatedAt?: string | null;
|
|
183
175
|
// -------------------------------------------------------------------------
|
|
184
176
|
// Design Studio panels (§6.2, §6.4, §6.5)
|
|
185
177
|
// Pass `designId` to unlock Tokens, States, and Review sections.
|
|
186
178
|
// -------------------------------------------------------------------------
|
|
187
179
|
/** The active design's id — required to mount Tokens / States / Review. */
|
|
188
180
|
designId?: string;
|
|
181
|
+
/**
|
|
182
|
+
* Called after a component prop edit returns the patched source so the parent
|
|
183
|
+
* editor can sync local/Yjs content instead of waiting for query invalidation.
|
|
184
|
+
*/
|
|
185
|
+
onComponentPropApplied?: (
|
|
186
|
+
fileId: string,
|
|
187
|
+
content: string,
|
|
188
|
+
updatedAt?: string,
|
|
189
|
+
) => void;
|
|
189
190
|
/**
|
|
190
191
|
* Called after a token edit is applied so the parent can push the resolved
|
|
191
192
|
* CSS-var map into the iframe via the tweak-values postMessage.
|
|
@@ -355,6 +356,9 @@ function ColorInput({
|
|
|
355
356
|
value,
|
|
356
357
|
onChange,
|
|
357
358
|
backgroundImage,
|
|
359
|
+
backgroundSize,
|
|
360
|
+
backgroundRepeat,
|
|
361
|
+
backgroundPosition,
|
|
358
362
|
onBackgroundImageChange,
|
|
359
363
|
onImageFillChange,
|
|
360
364
|
blendMode,
|
|
@@ -367,6 +371,9 @@ function ColorInput({
|
|
|
367
371
|
value: string;
|
|
368
372
|
onChange: (value: string) => void;
|
|
369
373
|
backgroundImage?: string;
|
|
374
|
+
backgroundSize?: string;
|
|
375
|
+
backgroundRepeat?: string;
|
|
376
|
+
backgroundPosition?: string;
|
|
370
377
|
onBackgroundImageChange?: (value: string) => void;
|
|
371
378
|
onImageFillChange?: (value: ImageFillValue) => void;
|
|
372
379
|
blendMode?: string;
|
|
@@ -385,6 +392,9 @@ function ColorInput({
|
|
|
385
392
|
}, [value]);
|
|
386
393
|
|
|
387
394
|
const backgroundLayers = splitCssLayers(backgroundImage || "");
|
|
395
|
+
const backgroundSizeLayers = splitCssLayers(backgroundSize || "");
|
|
396
|
+
const backgroundRepeatLayers = splitCssLayers(backgroundRepeat || "");
|
|
397
|
+
const backgroundPositionLayers = splitCssLayers(backgroundPosition || "");
|
|
388
398
|
const selectedLayerIndex = fillLayerIndex(selectedFillId);
|
|
389
399
|
const selectedGradient =
|
|
390
400
|
selectedLayerIndex !== null
|
|
@@ -619,6 +629,8 @@ function ColorInput({
|
|
|
619
629
|
selectedLayerIndex !== null
|
|
620
630
|
? (backgroundLayers[selectedLayerIndex] ?? draft ?? value ?? "#000000")
|
|
621
631
|
: draft || "#000000";
|
|
632
|
+
const selectedBackgroundLayerValue = (layers: string[]): string | undefined =>
|
|
633
|
+
selectedLayerIndex !== null ? layers[selectedLayerIndex] : undefined;
|
|
622
634
|
const handlePaintTypeChange = (type: DesignPaintType) => {
|
|
623
635
|
const selectedLayer = fillLayerIndex(selectedFillId);
|
|
624
636
|
if (type === "solid") {
|
|
@@ -679,6 +691,12 @@ function ColorInput({
|
|
|
679
691
|
supportsLayeredFills ? handlePaintValueChange : undefined
|
|
680
692
|
}
|
|
681
693
|
onImageFillChange={onImageFillChange}
|
|
694
|
+
backgroundImage={selectedBackgroundLayerValue(backgroundLayers)}
|
|
695
|
+
backgroundSize={selectedBackgroundLayerValue(backgroundSizeLayers)}
|
|
696
|
+
backgroundRepeat={selectedBackgroundLayerValue(backgroundRepeatLayers)}
|
|
697
|
+
backgroundPosition={selectedBackgroundLayerValue(
|
|
698
|
+
backgroundPositionLayers,
|
|
699
|
+
)}
|
|
682
700
|
blendMode={blendMode}
|
|
683
701
|
onBlendModeChange={onBlendModeChange}
|
|
684
702
|
showBlendMode={Boolean(onBlendModeChange)}
|
|
@@ -2149,6 +2167,292 @@ export function serializeAlpineDataObject(obj: Record<string, string>): string {
|
|
|
2149
2167
|
return parts.length ? `{ ${parts.join(", ")} }` : "{}";
|
|
2150
2168
|
}
|
|
2151
2169
|
|
|
2170
|
+
/**
|
|
2171
|
+
* Format a single editable prop value as an `x-data` literal: bare for
|
|
2172
|
+
* boolean / number values, single-quoted (with escaping) for strings.
|
|
2173
|
+
*
|
|
2174
|
+
* Pure — exported for tests.
|
|
2175
|
+
*/
|
|
2176
|
+
export function alpineDataValueLiteral(value: string): string {
|
|
2177
|
+
const isBoolean = value === "true" || value === "false";
|
|
2178
|
+
const isNumber = /^-?\d+(\.\d+)?$/.test(value);
|
|
2179
|
+
return isBoolean || isNumber ? value : `'${value.replace(/'/g, "\\'")}'`;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/**
|
|
2183
|
+
* Surgically replace a single top-level key's value inside an Alpine `x-data`
|
|
2184
|
+
* object literal, preserving everything else byte-for-byte — methods
|
|
2185
|
+
* (`toggle() { … }`), nested objects, escaped strings, quoted keys, comments,
|
|
2186
|
+
* and whitespace are all left untouched.
|
|
2187
|
+
*
|
|
2188
|
+
* Unlike a `parseAlpineDataObject` → mutate → `serializeAlpineDataObject`
|
|
2189
|
+
* round-trip (which only understands a flat object of simple literals and so
|
|
2190
|
+
* *drops* anything it can't model), this walks the original string, finds the
|
|
2191
|
+
* `key:` token at the top level (depth 0, not inside a string/comment), and
|
|
2192
|
+
* rewrites only the value literal that immediately follows it.
|
|
2193
|
+
*
|
|
2194
|
+
* Returns `null` when the key cannot be located surgically (e.g. the value is
|
|
2195
|
+
* an expression/function/object rather than a simple string/boolean/number, or
|
|
2196
|
+
* the literal isn't a `{ … }` object) so the caller can fail safe instead of
|
|
2197
|
+
* persisting a lossy rewrite.
|
|
2198
|
+
*
|
|
2199
|
+
* Pure — exported for tests.
|
|
2200
|
+
*/
|
|
2201
|
+
export function replaceAlpineDataKeyValue(
|
|
2202
|
+
xData: string | null | undefined,
|
|
2203
|
+
key: string,
|
|
2204
|
+
nextValue: string,
|
|
2205
|
+
): string | null {
|
|
2206
|
+
if (!xData) return null;
|
|
2207
|
+
const trimmed = xData.trim();
|
|
2208
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
|
|
2209
|
+
|
|
2210
|
+
const s = xData;
|
|
2211
|
+
const n = s.length;
|
|
2212
|
+
// Walk the whole string tracking nesting depth and skipping over strings,
|
|
2213
|
+
// template literals, regex-ish slashes are not handled (Alpine x-data does
|
|
2214
|
+
// not use them at the object-key level), and line / block comments. Only at
|
|
2215
|
+
// object depth 1 (directly inside the outermost `{ … }`) do we look for the
|
|
2216
|
+
// target `key :` token.
|
|
2217
|
+
let depth = 0;
|
|
2218
|
+
let i = 0;
|
|
2219
|
+
|
|
2220
|
+
/** Advance `i` past a quoted string starting at `i` (handles escapes). */
|
|
2221
|
+
const skipString = (quote: string): void => {
|
|
2222
|
+
i += 1; // opening quote
|
|
2223
|
+
while (i < n) {
|
|
2224
|
+
const c = s[i];
|
|
2225
|
+
if (c === "\\") {
|
|
2226
|
+
i += 2;
|
|
2227
|
+
continue;
|
|
2228
|
+
}
|
|
2229
|
+
if (c === quote) {
|
|
2230
|
+
i += 1;
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
i += 1;
|
|
2234
|
+
}
|
|
2235
|
+
};
|
|
2236
|
+
|
|
2237
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2238
|
+
// Bare identifier key at a token boundary: `key` then optional ws then `:`.
|
|
2239
|
+
const bareRe = new RegExp(`^(${escapedKey})(\\s*:\\s*)`);
|
|
2240
|
+
// Quoted key: `'key'` or `"key"` then optional ws then `:`.
|
|
2241
|
+
const quotedRe = new RegExp(`^(['"]${escapedKey}['"])(\\s*:\\s*)`);
|
|
2242
|
+
|
|
2243
|
+
while (i < n) {
|
|
2244
|
+
const c = s[i];
|
|
2245
|
+
|
|
2246
|
+
// At the top level, a `{` / `,` (or the string start) opens a fresh value
|
|
2247
|
+
// slot. Try to match the target key here *before* treating a quote as an
|
|
2248
|
+
// opaque string — this is how quoted keys (`'size': …`) are recognised.
|
|
2249
|
+
if (depth === 1) {
|
|
2250
|
+
const prev = lastNonSpaceBefore(s, i);
|
|
2251
|
+
if (prev === "{" || prev === ",") {
|
|
2252
|
+
const rest = s.slice(i);
|
|
2253
|
+
const m = bareRe.exec(rest) ?? quotedRe.exec(rest);
|
|
2254
|
+
if (m) {
|
|
2255
|
+
const valueStart = i + m[1].length + m[2].length;
|
|
2256
|
+
const valueEnd = simpleValueEnd(s, valueStart);
|
|
2257
|
+
if (valueEnd === null) return null; // value is not a simple literal
|
|
2258
|
+
return (
|
|
2259
|
+
s.slice(0, valueStart) +
|
|
2260
|
+
alpineDataValueLiteral(nextValue) +
|
|
2261
|
+
s.slice(valueEnd)
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
// Skip strings / template literals wholesale.
|
|
2268
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
2269
|
+
skipString(c);
|
|
2270
|
+
continue;
|
|
2271
|
+
}
|
|
2272
|
+
// Skip comments.
|
|
2273
|
+
if (c === "/" && s[i + 1] === "/") {
|
|
2274
|
+
i += 2;
|
|
2275
|
+
while (i < n && s[i] !== "\n") i += 1;
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
if (c === "/" && s[i + 1] === "*") {
|
|
2279
|
+
i += 2;
|
|
2280
|
+
while (i < n && !(s[i] === "*" && s[i + 1] === "/")) i += 1;
|
|
2281
|
+
i += 2;
|
|
2282
|
+
continue;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
if (c === "{" || c === "[" || c === "(") {
|
|
2286
|
+
depth += 1;
|
|
2287
|
+
i += 1;
|
|
2288
|
+
continue;
|
|
2289
|
+
}
|
|
2290
|
+
if (c === "}" || c === "]" || c === ")") {
|
|
2291
|
+
depth -= 1;
|
|
2292
|
+
i += 1;
|
|
2293
|
+
continue;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
i += 1;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
return null;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
/** Last non-whitespace char strictly before index `i` (or `""`). */
|
|
2303
|
+
function lastNonSpaceBefore(s: string, i: number): string {
|
|
2304
|
+
let j = i - 1;
|
|
2305
|
+
while (j >= 0 && /\s/.test(s[j]!)) j -= 1;
|
|
2306
|
+
return j >= 0 ? s[j]! : "";
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
/**
|
|
2310
|
+
* Given the start index of a value in an `x-data` literal, return the index
|
|
2311
|
+
* just past a *simple* literal value (single/double-quoted string with
|
|
2312
|
+
* escapes, boolean, or number). Returns `null` when the value is anything else
|
|
2313
|
+
* (an expression, function, object, array, template literal, etc.) so the
|
|
2314
|
+
* caller can fail safe rather than mangle it.
|
|
2315
|
+
*/
|
|
2316
|
+
function simpleValueEnd(s: string, start: number): number | null {
|
|
2317
|
+
const c = s[start];
|
|
2318
|
+
if (c === "'" || c === '"') {
|
|
2319
|
+
let i = start + 1;
|
|
2320
|
+
while (i < s.length) {
|
|
2321
|
+
if (s[i] === "\\") {
|
|
2322
|
+
i += 2;
|
|
2323
|
+
continue;
|
|
2324
|
+
}
|
|
2325
|
+
if (s[i] === c) return i + 1;
|
|
2326
|
+
i += 1;
|
|
2327
|
+
}
|
|
2328
|
+
return null; // unterminated string
|
|
2329
|
+
}
|
|
2330
|
+
// Boolean / number: read the bare token, then confirm it is exactly one.
|
|
2331
|
+
const m = /^[A-Za-z0-9_.+-]+/.exec(s.slice(start));
|
|
2332
|
+
if (!m) return null;
|
|
2333
|
+
const token = m[0];
|
|
2334
|
+
const isBoolean = token === "true" || token === "false";
|
|
2335
|
+
const isNumber = /^-?\d+(\.\d+)?$/.test(token);
|
|
2336
|
+
if (!isBoolean && !isNumber) return null;
|
|
2337
|
+
return start + token.length;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
/**
|
|
2341
|
+
* True when an `x-data` literal can be rebuilt from its flat parsed map with
|
|
2342
|
+
* no loss — i.e. there is nothing richer than the simple `key: literal` pairs
|
|
2343
|
+
* that `serializeAlpineDataObject` already round-trips. Used as the gate for
|
|
2344
|
+
* falling back to a full rebuild when a surgical single-key replace is not
|
|
2345
|
+
* possible (e.g. when adding a brand-new key).
|
|
2346
|
+
*
|
|
2347
|
+
* Returns `true` for an empty / absent literal (nothing to lose) and for a
|
|
2348
|
+
* flat object whose `parse → serialize` round-trip is semantically stable.
|
|
2349
|
+
* Returns `false` when the original holds methods, nested objects, comments,
|
|
2350
|
+
* or expressions that a rebuild would silently drop.
|
|
2351
|
+
*
|
|
2352
|
+
* Pure — exported for tests.
|
|
2353
|
+
*/
|
|
2354
|
+
export function canRebuildAlpineDataLosslessly(
|
|
2355
|
+
xData: string | null | undefined,
|
|
2356
|
+
): boolean {
|
|
2357
|
+
const trimmed = (xData ?? "").trim();
|
|
2358
|
+
// No object literal at all → there is nothing richer to preserve.
|
|
2359
|
+
if (!trimmed || trimmed === "{}" || trimmed === "{ }") return true;
|
|
2360
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return false;
|
|
2361
|
+
|
|
2362
|
+
const parsed = parseAlpineDataObject(trimmed);
|
|
2363
|
+
if (!parsed) return false;
|
|
2364
|
+
|
|
2365
|
+
// Re-serialize and re-parse; if the round-trip is stable AND the parsed map
|
|
2366
|
+
// accounts for every top-level key actually present in the original, a
|
|
2367
|
+
// rebuild loses nothing.
|
|
2368
|
+
const reserialized = serializeAlpineDataObject(parsed);
|
|
2369
|
+
const reparsed = parseAlpineDataObject(reserialized);
|
|
2370
|
+
if (!reparsed) return false;
|
|
2371
|
+
const keysA = Object.keys(parsed).sort().join(",");
|
|
2372
|
+
const keysB = Object.keys(reparsed).sort().join(",");
|
|
2373
|
+
if (keysA !== keysB) return false;
|
|
2374
|
+
|
|
2375
|
+
// Guard against dropped content the flat parser ignores (e.g. a trailing
|
|
2376
|
+
// method): the number of top-level `key:` tokens in the original must match
|
|
2377
|
+
// the number of parsed keys. Count top-level `:` separators conservatively.
|
|
2378
|
+
return countTopLevelKeys(trimmed) === Object.keys(parsed).length;
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
/**
|
|
2382
|
+
* Count top-level `key:` entries in an `x-data` object literal, skipping
|
|
2383
|
+
* strings, comments, and nested braces/brackets/parens. A method like
|
|
2384
|
+
* `toggle() { … }` is counted as a key too (its `:`-less form still occupies a
|
|
2385
|
+
* top-level slot), so a mismatch against the flat parser's key count reveals
|
|
2386
|
+
* dropped content.
|
|
2387
|
+
*/
|
|
2388
|
+
function countTopLevelKeys(xData: string): number {
|
|
2389
|
+
const s = xData;
|
|
2390
|
+
const n = s.length;
|
|
2391
|
+
let depth = 0;
|
|
2392
|
+
let i = 0;
|
|
2393
|
+
let count = 0;
|
|
2394
|
+
let sawTokenInSlot = false;
|
|
2395
|
+
|
|
2396
|
+
const skipString = (quote: string): void => {
|
|
2397
|
+
i += 1;
|
|
2398
|
+
while (i < n) {
|
|
2399
|
+
if (s[i] === "\\") {
|
|
2400
|
+
i += 2;
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
if (s[i] === quote) {
|
|
2404
|
+
i += 1;
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
i += 1;
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
|
|
2411
|
+
while (i < n) {
|
|
2412
|
+
const c = s[i]!;
|
|
2413
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
2414
|
+
if (depth === 1) sawTokenInSlot = true;
|
|
2415
|
+
skipString(c);
|
|
2416
|
+
continue;
|
|
2417
|
+
}
|
|
2418
|
+
if (c === "/" && s[i + 1] === "/") {
|
|
2419
|
+
i += 2;
|
|
2420
|
+
while (i < n && s[i] !== "\n") i += 1;
|
|
2421
|
+
continue;
|
|
2422
|
+
}
|
|
2423
|
+
if (c === "/" && s[i + 1] === "*") {
|
|
2424
|
+
i += 2;
|
|
2425
|
+
while (i < n && !(s[i] === "*" && s[i + 1] === "/")) i += 1;
|
|
2426
|
+
i += 2;
|
|
2427
|
+
continue;
|
|
2428
|
+
}
|
|
2429
|
+
if (c === "{" || c === "[" || c === "(") {
|
|
2430
|
+
depth += 1;
|
|
2431
|
+
i += 1;
|
|
2432
|
+
continue;
|
|
2433
|
+
}
|
|
2434
|
+
if (c === "}" || c === "]" || c === ")") {
|
|
2435
|
+
if (depth === 1 && c === "}" && sawTokenInSlot) {
|
|
2436
|
+
count += 1;
|
|
2437
|
+
sawTokenInSlot = false;
|
|
2438
|
+
}
|
|
2439
|
+
depth -= 1;
|
|
2440
|
+
i += 1;
|
|
2441
|
+
continue;
|
|
2442
|
+
}
|
|
2443
|
+
if (depth === 1) {
|
|
2444
|
+
if (c === ",") {
|
|
2445
|
+
if (sawTokenInSlot) count += 1;
|
|
2446
|
+
sawTokenInSlot = false;
|
|
2447
|
+
} else if (!/\s/.test(c)) {
|
|
2448
|
+
sawTokenInSlot = true;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
i += 1;
|
|
2452
|
+
}
|
|
2453
|
+
return count;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2152
2456
|
/** A boolean-ish prop value (`"true"` / `"false"`), case-insensitive. */
|
|
2153
2457
|
export function isBooleanPropValue(value: string): boolean {
|
|
2154
2458
|
const v = value.trim().toLowerCase();
|
|
@@ -2370,12 +2674,6 @@ function InspectorTabsHeader({
|
|
|
2370
2674
|
>
|
|
2371
2675
|
{t("designEditor.tweaks")}
|
|
2372
2676
|
</TabsTrigger>
|
|
2373
|
-
<TabsTrigger
|
|
2374
|
-
value="extensions"
|
|
2375
|
-
className="h-6 rounded-md px-1.5 text-[11px] font-semibold text-muted-foreground shadow-none transition-colors hover:text-foreground data-[state=active]:bg-[var(--design-editor-panel-raised-bg)] data-[state=active]:text-foreground data-[state=active]:shadow-none"
|
|
2376
|
-
>
|
|
2377
|
-
{t("designEditor.extensions")}
|
|
2378
|
-
</TabsTrigger>
|
|
2379
2677
|
</TabsList>
|
|
2380
2678
|
</Tabs>
|
|
2381
2679
|
{trailing ? <div className="shrink-0">{trailing}</div> : null}
|
|
@@ -2387,15 +2685,19 @@ function SectionIconButton({
|
|
|
2387
2685
|
label,
|
|
2388
2686
|
onClick,
|
|
2389
2687
|
children,
|
|
2688
|
+
activateOnPointerDown = false,
|
|
2390
2689
|
disabled = false,
|
|
2391
2690
|
className,
|
|
2392
2691
|
}: {
|
|
2393
2692
|
label: string;
|
|
2394
2693
|
onClick?: () => void;
|
|
2395
2694
|
children: ReactNode;
|
|
2695
|
+
activateOnPointerDown?: boolean;
|
|
2396
2696
|
disabled?: boolean;
|
|
2397
2697
|
className?: string;
|
|
2398
2698
|
}) {
|
|
2699
|
+
const pointerActivatedRef = useRef(false);
|
|
2700
|
+
|
|
2399
2701
|
return (
|
|
2400
2702
|
<Tooltip>
|
|
2401
2703
|
<TooltipTrigger asChild>
|
|
@@ -2408,7 +2710,22 @@ function SectionIconButton({
|
|
|
2408
2710
|
className,
|
|
2409
2711
|
)}
|
|
2410
2712
|
disabled={disabled}
|
|
2411
|
-
|
|
2713
|
+
onPointerDown={(event) => {
|
|
2714
|
+
if (!activateOnPointerDown || disabled || event.button !== 0) {
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
pointerActivatedRef.current = true;
|
|
2718
|
+
event.preventDefault();
|
|
2719
|
+
event.stopPropagation();
|
|
2720
|
+
onClick?.();
|
|
2721
|
+
}}
|
|
2722
|
+
onClick={() => {
|
|
2723
|
+
if (pointerActivatedRef.current) {
|
|
2724
|
+
pointerActivatedRef.current = false;
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
onClick?.();
|
|
2728
|
+
}}
|
|
2412
2729
|
aria-label={label}
|
|
2413
2730
|
>
|
|
2414
2731
|
{children}
|
|
@@ -3103,7 +3420,10 @@ function PageProperties({
|
|
|
3103
3420
|
label={t("editPanel.labels.background")}
|
|
3104
3421
|
value={styles.backgroundColor || ""}
|
|
3105
3422
|
onChange={(v) => onStyleChange("backgroundColor", v)}
|
|
3106
|
-
backgroundImage={styles.backgroundImage
|
|
3423
|
+
backgroundImage={styles.backgroundImage}
|
|
3424
|
+
backgroundSize={styles.backgroundSize}
|
|
3425
|
+
backgroundRepeat={styles.backgroundRepeat}
|
|
3426
|
+
backgroundPosition={styles.backgroundPosition}
|
|
3107
3427
|
onBackgroundImageChange={(v) => onStyleChange("backgroundImage", v)}
|
|
3108
3428
|
onImageFillChange={(value) =>
|
|
3109
3429
|
commitStylePatch(
|
|
@@ -4240,6 +4560,15 @@ function FillProperties({
|
|
|
4240
4560
|
const backgroundLayers = isTextElement
|
|
4241
4561
|
? []
|
|
4242
4562
|
: splitCssLayers(styles.backgroundImage || "");
|
|
4563
|
+
const backgroundSizeLayers = isTextElement
|
|
4564
|
+
? []
|
|
4565
|
+
: splitCssLayers(styles.backgroundSize || "");
|
|
4566
|
+
const backgroundRepeatLayers = isTextElement
|
|
4567
|
+
? []
|
|
4568
|
+
: splitCssLayers(styles.backgroundRepeat || "");
|
|
4569
|
+
const backgroundPositionLayers = isTextElement
|
|
4570
|
+
? []
|
|
4571
|
+
: splitCssLayers(styles.backgroundPosition || "");
|
|
4243
4572
|
const hasBackgroundLayer = !isTextElement && backgroundLayers.length > 0;
|
|
4244
4573
|
const hasVisibleFill =
|
|
4245
4574
|
isTextElement || colorHasVisibleAlpha(fillValue) || hasBackgroundLayer;
|
|
@@ -4378,6 +4707,7 @@ function FillProperties({
|
|
|
4378
4707
|
: t("editPanel.labels.hideLayer")
|
|
4379
4708
|
}
|
|
4380
4709
|
onClick={handleFillVisibilityToggle}
|
|
4710
|
+
activateOnPointerDown
|
|
4381
4711
|
>
|
|
4382
4712
|
{isHidden ? (
|
|
4383
4713
|
<IconEyeOff className="size-3.5" />
|
|
@@ -4492,6 +4822,10 @@ function FillProperties({
|
|
|
4492
4822
|
);
|
|
4493
4823
|
}}
|
|
4494
4824
|
paintType={gradient?.type ?? "image"}
|
|
4825
|
+
backgroundImage={layer}
|
|
4826
|
+
backgroundSize={backgroundSizeLayers[index]}
|
|
4827
|
+
backgroundRepeat={backgroundRepeatLayers[index]}
|
|
4828
|
+
backgroundPosition={backgroundPositionLayers[index]}
|
|
4495
4829
|
gradientType={gradient?.type}
|
|
4496
4830
|
onGradientTypeChange={(type) => {
|
|
4497
4831
|
if (!gradient) return;
|
|
@@ -5236,8 +5570,8 @@ function MakeItRealCard({
|
|
|
5236
5570
|
// height so the inspector doesn't jump when the data arrives.
|
|
5237
5571
|
if (isLoading || !data) {
|
|
5238
5572
|
return (
|
|
5239
|
-
<div className="
|
|
5240
|
-
<div className="h-3 w-
|
|
5573
|
+
<div className="flex h-7 items-center rounded-[5px] bg-[var(--design-editor-control-bg)] px-2">
|
|
5574
|
+
<div className="h-3 w-28 animate-pulse rounded bg-muted/40" />
|
|
5241
5575
|
</div>
|
|
5242
5576
|
);
|
|
5243
5577
|
}
|
|
@@ -5282,85 +5616,86 @@ function MakeItRealCard({
|
|
|
5282
5616
|
|
|
5283
5617
|
if (migrateResult?.status === "processing" && migrateResult.url) {
|
|
5284
5618
|
return (
|
|
5285
|
-
<div className="rounded-
|
|
5286
|
-
<
|
|
5287
|
-
|
|
5288
|
-
</p>
|
|
5289
|
-
<p className="text-[10px] leading-snug text-muted-foreground">
|
|
5619
|
+
<div className="flex items-center gap-2 rounded-[5px] border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 py-1.5">
|
|
5620
|
+
<IconLoader2 className="size-3.5 shrink-0 animate-spin text-[var(--design-editor-accent-color)]" />
|
|
5621
|
+
<p className="min-w-0 flex-1 truncate text-[10px] text-muted-foreground">
|
|
5290
5622
|
{migrateResult.message ??
|
|
5291
|
-
`
|
|
5623
|
+
`Generating ${migrateResult.branchName ?? "React app"}.`}
|
|
5292
5624
|
</p>
|
|
5293
5625
|
<a
|
|
5294
5626
|
href={migrateResult.url}
|
|
5295
5627
|
target="_blank"
|
|
5296
5628
|
rel="noopener noreferrer"
|
|
5297
|
-
className="inline-flex items-center gap-1 text-[10px] font-
|
|
5629
|
+
className="inline-flex h-6 shrink-0 items-center gap-1 rounded-md px-1.5 text-[10px] font-semibold text-[var(--design-editor-accent-color)] hover:bg-[var(--design-editor-panel-raised-bg)]"
|
|
5298
5630
|
>
|
|
5299
|
-
{"Open
|
|
5631
|
+
{"Open" /* i18n-ignore make-it-real card */}
|
|
5300
5632
|
<IconExternalLink className="size-2.5" />
|
|
5301
5633
|
</a>
|
|
5302
5634
|
</div>
|
|
5303
5635
|
);
|
|
5304
5636
|
}
|
|
5305
5637
|
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
<p className="text-[10px] leading-snug text-muted-foreground">
|
|
5315
|
-
{cta.description}
|
|
5316
|
-
{featureLabel ? (
|
|
5317
|
-
<>
|
|
5318
|
-
{" "}
|
|
5319
|
-
<span className="italic">{featureLabel}</span>
|
|
5320
|
-
{" requires a real-app source."}
|
|
5321
|
-
</>
|
|
5322
|
-
) : null}
|
|
5323
|
-
</p>
|
|
5638
|
+
const summary =
|
|
5639
|
+
cta.kind === "configure-project"
|
|
5640
|
+
? `Configure Builder to enable ${featureLabel}.`
|
|
5641
|
+
: `Connect Builder to enable ${featureLabel}.`;
|
|
5642
|
+
const primaryLabel =
|
|
5643
|
+
cta.kind === "configure-project"
|
|
5644
|
+
? "Configure" /* i18n-ignore make-it-real card */
|
|
5645
|
+
: "Connect"; /* i18n-ignore make-it-real card */
|
|
5324
5646
|
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5647
|
+
return (
|
|
5648
|
+
<div className="space-y-1">
|
|
5649
|
+
<div className="flex items-center gap-2 rounded-[5px] border border-dashed border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)]/70 px-2 py-1.5">
|
|
5650
|
+
<span
|
|
5651
|
+
className="size-1.5 shrink-0 rounded-full bg-[var(--design-editor-accent-color)]"
|
|
5652
|
+
aria-hidden="true"
|
|
5653
|
+
/>
|
|
5654
|
+
<p
|
|
5655
|
+
className="min-w-0 flex-1 truncate text-[10px] text-muted-foreground"
|
|
5656
|
+
title={summary}
|
|
5657
|
+
>
|
|
5658
|
+
{summary}
|
|
5331
5659
|
</p>
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
{/* Actions */}
|
|
5335
|
-
<div className="flex items-center gap-1.5 flex-wrap">
|
|
5336
|
-
<button
|
|
5660
|
+
<Button
|
|
5337
5661
|
type="button"
|
|
5662
|
+
size="sm"
|
|
5338
5663
|
onClick={handlePrimary}
|
|
5339
|
-
|
|
5664
|
+
title={cta.primaryAction}
|
|
5665
|
+
className="h-6 shrink-0 gap-1 rounded-md bg-[var(--design-editor-accent-color)] px-1.5 text-[10px] font-semibold text-white hover:bg-[var(--design-editor-accent-hover-color)]"
|
|
5340
5666
|
>
|
|
5341
|
-
{
|
|
5667
|
+
{primaryLabel}
|
|
5342
5668
|
<IconArrowRight className="size-2.5" />
|
|
5343
|
-
</
|
|
5669
|
+
</Button>
|
|
5344
5670
|
|
|
5345
5671
|
{/* When Builder is fully connected, also offer direct migration */}
|
|
5346
5672
|
{data.connected && data.builderEnabled && (
|
|
5347
|
-
<
|
|
5673
|
+
<Button
|
|
5348
5674
|
type="button"
|
|
5675
|
+
variant="ghost"
|
|
5676
|
+
size="sm"
|
|
5349
5677
|
onClick={handleMigrate}
|
|
5350
5678
|
disabled={isPending}
|
|
5351
|
-
className="
|
|
5679
|
+
className="h-6 shrink-0 gap-1 rounded-md px-1.5 text-[10px] font-semibold text-muted-foreground hover:text-foreground disabled:cursor-wait disabled:opacity-60"
|
|
5352
5680
|
>
|
|
5353
5681
|
{isPending ? (
|
|
5354
5682
|
<>
|
|
5355
5683
|
<IconLoader2 className="size-2.5 animate-spin" />
|
|
5356
|
-
{"
|
|
5684
|
+
{"Generating" /* i18n-ignore make-it-real card */}
|
|
5357
5685
|
</>
|
|
5358
5686
|
) : (
|
|
5359
|
-
<>{"Generate
|
|
5687
|
+
<>{"Generate" /* i18n-ignore make-it-real card */}</>
|
|
5360
5688
|
)}
|
|
5361
|
-
</
|
|
5689
|
+
</Button>
|
|
5362
5690
|
)}
|
|
5363
5691
|
</div>
|
|
5692
|
+
{migrateError ? (
|
|
5693
|
+
<p className="px-2 text-[10px] text-destructive">
|
|
5694
|
+
{migrateError instanceof Error
|
|
5695
|
+
? migrateError.message
|
|
5696
|
+
: "Migration failed. Please try again."}
|
|
5697
|
+
</p>
|
|
5698
|
+
) : null}
|
|
5364
5699
|
</div>
|
|
5365
5700
|
);
|
|
5366
5701
|
}
|
|
@@ -5372,6 +5707,7 @@ function MakeItRealCard({
|
|
|
5372
5707
|
* typed here; the action may return additional fields.
|
|
5373
5708
|
*/
|
|
5374
5709
|
interface ComponentDetailsResult {
|
|
5710
|
+
nodeId: string;
|
|
5375
5711
|
name: string;
|
|
5376
5712
|
sourceType: string;
|
|
5377
5713
|
observedProps: Array<{ name: string; value: string }>;
|
|
@@ -5405,32 +5741,79 @@ interface ComponentDetailsResult {
|
|
|
5405
5741
|
*
|
|
5406
5742
|
* Matches the workbench artboard spec in DESIGN-STUDIO-PLAN.md §6.1.
|
|
5407
5743
|
*/
|
|
5408
|
-
function ComponentSection({
|
|
5744
|
+
export function ComponentSection({
|
|
5409
5745
|
designId,
|
|
5746
|
+
fileId,
|
|
5747
|
+
activeContent,
|
|
5748
|
+
activeFileUpdatedAt,
|
|
5410
5749
|
nodeId,
|
|
5750
|
+
onComponentPropApplied,
|
|
5411
5751
|
sourceCapabilities = [],
|
|
5412
5752
|
}: {
|
|
5413
5753
|
designId: string;
|
|
5754
|
+
fileId?: string;
|
|
5755
|
+
activeContent?: string;
|
|
5756
|
+
activeFileUpdatedAt?: string | null;
|
|
5414
5757
|
nodeId: string;
|
|
5758
|
+
onComponentPropApplied?: (
|
|
5759
|
+
fileId: string,
|
|
5760
|
+
content: string,
|
|
5761
|
+
updatedAt?: string,
|
|
5762
|
+
) => void;
|
|
5415
5763
|
/** Capability names advertised by the current source. */
|
|
5416
5764
|
sourceCapabilities?: string[];
|
|
5417
5765
|
}) {
|
|
5418
5766
|
const queryClient = useQueryClient();
|
|
5419
|
-
const
|
|
5767
|
+
const detailsParams = { designId, nodeId, ...(fileId ? { fileId } : {}) };
|
|
5768
|
+
const detailsKey = ["action", "get-component-details", detailsParams];
|
|
5769
|
+
const latestSourceRef = useRef<{
|
|
5770
|
+
content: string;
|
|
5771
|
+
revision?: string | null;
|
|
5772
|
+
}>({
|
|
5773
|
+
content: activeContent ?? "",
|
|
5774
|
+
revision: activeFileUpdatedAt ?? null,
|
|
5775
|
+
});
|
|
5420
5776
|
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5777
|
+
useEffect(() => {
|
|
5778
|
+
latestSourceRef.current = {
|
|
5779
|
+
content: activeContent ?? "",
|
|
5780
|
+
revision: activeFileUpdatedAt ?? null,
|
|
5781
|
+
};
|
|
5782
|
+
}, [activeContent, activeFileUpdatedAt, fileId, nodeId]);
|
|
5783
|
+
|
|
5784
|
+
const { data, isLoading, error, refetch } =
|
|
5785
|
+
useActionQuery<ComponentDetailsResult>(
|
|
5786
|
+
"get-component-details",
|
|
5787
|
+
detailsParams,
|
|
5788
|
+
{ refetchOnMount: "always" },
|
|
5789
|
+
);
|
|
5425
5790
|
|
|
5426
5791
|
const openSourceMutation = useActionMutation("open-component-source");
|
|
5427
5792
|
const applyPropMutation = useActionMutation("apply-component-prop-edit");
|
|
5428
5793
|
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5794
|
+
const postComponentPropPreview = useCallback(
|
|
5795
|
+
(attribute: string, value: string) => {
|
|
5796
|
+
if (typeof document === "undefined") return;
|
|
5797
|
+
|
|
5798
|
+
const iframe = document.querySelector<HTMLIFrameElement>(
|
|
5799
|
+
"iframe[data-design-preview-iframe]",
|
|
5800
|
+
);
|
|
5801
|
+
iframe?.contentWindow?.postMessage(
|
|
5802
|
+
{
|
|
5803
|
+
type: "style-change",
|
|
5804
|
+
selector: data?.instance?.selector ?? "",
|
|
5805
|
+
nodeId: data?.instance?.nodeId ?? nodeId,
|
|
5806
|
+
attributeOverrides: { [attribute]: value },
|
|
5807
|
+
},
|
|
5808
|
+
"*",
|
|
5809
|
+
);
|
|
5810
|
+
},
|
|
5811
|
+
[data?.instance?.nodeId, data?.instance?.selector, nodeId],
|
|
5812
|
+
);
|
|
5813
|
+
|
|
5814
|
+
// Persist a single prop change through apply-component-prop-edit. Attribute
|
|
5815
|
+
// props also preview immediately in the iframe so the selected component
|
|
5816
|
+
// changes without waiting for the write/refetch round-trip.
|
|
5434
5817
|
const persistPropEdit = (
|
|
5435
5818
|
edit:
|
|
5436
5819
|
| { kind: "alpineData"; value: string }
|
|
@@ -5440,20 +5823,90 @@ function ComponentSection({
|
|
|
5440
5823
|
queryClient.setQueryData<ComponentDetailsResult>(detailsKey, (prev) =>
|
|
5441
5824
|
prev ? optimistic(prev) : prev,
|
|
5442
5825
|
);
|
|
5826
|
+
if (edit.kind === "attribute") {
|
|
5827
|
+
postComponentPropPreview(edit.attribute, edit.value);
|
|
5828
|
+
}
|
|
5829
|
+
const latestSource = latestSourceRef.current;
|
|
5443
5830
|
applyPropMutation.mutate(
|
|
5444
|
-
{ designId, nodeId, edit },
|
|
5445
5831
|
{
|
|
5832
|
+
designId,
|
|
5833
|
+
nodeId,
|
|
5834
|
+
...(fileId ? { fileId } : {}),
|
|
5835
|
+
edit,
|
|
5836
|
+
...(latestSource.content
|
|
5837
|
+
? {
|
|
5838
|
+
source: {
|
|
5839
|
+
currentContent: latestSource.content,
|
|
5840
|
+
...(latestSource.revision
|
|
5841
|
+
? { revision: latestSource.revision }
|
|
5842
|
+
: {}),
|
|
5843
|
+
},
|
|
5844
|
+
}
|
|
5845
|
+
: {}),
|
|
5846
|
+
},
|
|
5847
|
+
{
|
|
5848
|
+
onSuccess: (result) => {
|
|
5849
|
+
const response = result as {
|
|
5850
|
+
content?: unknown;
|
|
5851
|
+
fileId?: unknown;
|
|
5852
|
+
updatedAt?: unknown;
|
|
5853
|
+
conflict?: unknown;
|
|
5854
|
+
error?: unknown;
|
|
5855
|
+
};
|
|
5856
|
+
if (response.conflict) {
|
|
5857
|
+
toast.error(
|
|
5858
|
+
typeof response.error === "string"
|
|
5859
|
+
? response.error
|
|
5860
|
+
: "This file changed since this component prop edit was prepared. Refresh and try again.",
|
|
5861
|
+
);
|
|
5862
|
+
return;
|
|
5863
|
+
}
|
|
5864
|
+
if (
|
|
5865
|
+
typeof response.fileId === "string" &&
|
|
5866
|
+
typeof response.content === "string"
|
|
5867
|
+
) {
|
|
5868
|
+
const updatedAt =
|
|
5869
|
+
typeof response.updatedAt === "string"
|
|
5870
|
+
? response.updatedAt
|
|
5871
|
+
: undefined;
|
|
5872
|
+
latestSourceRef.current = {
|
|
5873
|
+
content: response.content,
|
|
5874
|
+
revision: updatedAt ?? latestSourceRef.current.revision,
|
|
5875
|
+
};
|
|
5876
|
+
onComponentPropApplied?.(
|
|
5877
|
+
response.fileId,
|
|
5878
|
+
response.content,
|
|
5879
|
+
updatedAt,
|
|
5880
|
+
);
|
|
5881
|
+
}
|
|
5882
|
+
},
|
|
5446
5883
|
onSettled: () => {
|
|
5447
|
-
// Re-render the canvas from the new content and re-read the props.
|
|
5448
5884
|
void queryClient.invalidateQueries({
|
|
5449
5885
|
queryKey: ["action", "get-design"],
|
|
5450
5886
|
});
|
|
5451
5887
|
void queryClient.invalidateQueries({ queryKey: detailsKey });
|
|
5888
|
+
void refetch();
|
|
5452
5889
|
},
|
|
5453
5890
|
},
|
|
5454
5891
|
);
|
|
5455
5892
|
};
|
|
5456
5893
|
|
|
5894
|
+
useEffect(() => {
|
|
5895
|
+
if (typeof document === "undefined") return;
|
|
5896
|
+
|
|
5897
|
+
const handleMessage = (event: MessageEvent) => {
|
|
5898
|
+
if (
|
|
5899
|
+
(event.data as { type?: unknown } | null)?.type === "element-select"
|
|
5900
|
+
) {
|
|
5901
|
+
void refetch();
|
|
5902
|
+
}
|
|
5903
|
+
};
|
|
5904
|
+
window.addEventListener("message", handleMessage);
|
|
5905
|
+
return () => {
|
|
5906
|
+
window.removeEventListener("message", handleMessage);
|
|
5907
|
+
};
|
|
5908
|
+
}, [refetch]);
|
|
5909
|
+
|
|
5457
5910
|
// While loading, show a compact skeleton that matches the section width.
|
|
5458
5911
|
if (isLoading) {
|
|
5459
5912
|
return (
|
|
@@ -5491,7 +5944,7 @@ function ComponentSection({
|
|
|
5491
5944
|
// Real-app sources keep the deeper source-prop controls gated as-is, so for
|
|
5492
5945
|
// non-inline sources the controls are read-only here.
|
|
5493
5946
|
const isInline = sourceType === "inline";
|
|
5494
|
-
const editingEnabled = isInline; // gated; real-app stays read-only for now
|
|
5947
|
+
const editingEnabled = isInline && capabilities.canEditProps; // gated; real-app stays read-only for now
|
|
5495
5948
|
const alpineData = parseAlpineDataObject(instance?.alpineData);
|
|
5496
5949
|
|
|
5497
5950
|
// Each editable row: name + current value + how it persists + its options.
|
|
@@ -5552,22 +6005,51 @@ function ComponentSection({
|
|
|
5552
6005
|
if (!editingEnabled || nextValue === row.value) return;
|
|
5553
6006
|
|
|
5554
6007
|
if (row.surface === "alpineData") {
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
6008
|
+
// Surgically replace only the edited key's value inside the original
|
|
6009
|
+
// x-data string so methods, nested objects, escaped strings, quoted
|
|
6010
|
+
// keys, and whitespace survive byte-for-byte. A full
|
|
6011
|
+
// parse→mutate→serialize round-trip would drop anything
|
|
6012
|
+
// parseAlpineDataObject can't model (e.g. `toggle() { … }`).
|
|
6013
|
+
const original = instance?.alpineData ?? "";
|
|
6014
|
+
const surgical = replaceAlpineDataKeyValue(original, row.name, nextValue);
|
|
6015
|
+
|
|
6016
|
+
let serialized: string;
|
|
6017
|
+
if (surgical != null) {
|
|
6018
|
+
serialized = surgical;
|
|
6019
|
+
} else if (canRebuildAlpineDataLosslessly(original)) {
|
|
6020
|
+
// The key isn't present yet (or there is no original literal). Rebuild
|
|
6021
|
+
// from the flat map — safe here precisely because the original holds
|
|
6022
|
+
// nothing richer than the flat literals serialize already preserves.
|
|
6023
|
+
const nextData = { ...(alpineData ?? {}), [row.name]: nextValue };
|
|
6024
|
+
serialized = serializeAlpineDataObject(nextData);
|
|
6025
|
+
} else {
|
|
6026
|
+
// The original carries content (methods / nested / expressions) we
|
|
6027
|
+
// can't rewrite for this key without dropping it. Fail safe: skip the
|
|
6028
|
+
// edit rather than persist a lossy rewrite, and tell the user why so
|
|
6029
|
+
// the change doesn't silently vanish.
|
|
6030
|
+
toast.error(
|
|
6031
|
+
// i18n-ignore
|
|
6032
|
+
"Can’t safely edit this prop inline — this component’s Alpine state is too complex. Edit the source instead.",
|
|
6033
|
+
);
|
|
6034
|
+
return;
|
|
6035
|
+
}
|
|
6036
|
+
|
|
6037
|
+
const nextSerialized = serialized;
|
|
6038
|
+
persistPropEdit(
|
|
6039
|
+
{ kind: "alpineData", value: nextSerialized },
|
|
6040
|
+
(prev) => ({
|
|
6041
|
+
...prev,
|
|
6042
|
+
instance: { ...(prev.instance ?? {}), alpineData: nextSerialized },
|
|
6043
|
+
observedProps: prev.observedProps.map((p) =>
|
|
6044
|
+
p.name === row.name ? { ...p, value: nextValue } : p,
|
|
6045
|
+
),
|
|
6046
|
+
}),
|
|
6047
|
+
);
|
|
5564
6048
|
} else {
|
|
5565
|
-
// camelCase prop name → data-agent-native-prop-<kebab>
|
|
5566
|
-
const kebab = row.name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
5567
6049
|
persistPropEdit(
|
|
5568
6050
|
{
|
|
5569
6051
|
kind: "attribute",
|
|
5570
|
-
attribute:
|
|
6052
|
+
attribute: propNameToDataAttribute(row.name),
|
|
5571
6053
|
value: nextValue,
|
|
5572
6054
|
},
|
|
5573
6055
|
(prev) => {
|
|
@@ -5624,7 +6106,11 @@ function ComponentSection({
|
|
|
5624
6106
|
"Edit component source" /* i18n-ignore design inspector action */
|
|
5625
6107
|
}
|
|
5626
6108
|
onClick={() => {
|
|
5627
|
-
openSourceMutation.mutate({
|
|
6109
|
+
openSourceMutation.mutate({
|
|
6110
|
+
designId,
|
|
6111
|
+
nodeId,
|
|
6112
|
+
...(fileId ? { fileId } : {}),
|
|
6113
|
+
});
|
|
5628
6114
|
}}
|
|
5629
6115
|
>
|
|
5630
6116
|
<IconExternalLink className="size-3.5" />
|
|
@@ -5667,6 +6153,7 @@ function ComponentSection({
|
|
|
5667
6153
|
{rows.map((row) => {
|
|
5668
6154
|
const hasOptions = (row.options?.length ?? 0) > 0;
|
|
5669
6155
|
const isBoolean = !hasOptions && isBooleanPropValue(row.value);
|
|
6156
|
+
const disabled = !editingEnabled || applyPropMutation.isPending;
|
|
5670
6157
|
return (
|
|
5671
6158
|
<div key={row.name} className="flex items-center gap-1.5">
|
|
5672
6159
|
<Label className="w-[64px] shrink-0 truncate text-[11px] font-medium capitalize text-muted-foreground">
|
|
@@ -5677,7 +6164,7 @@ function ComponentSection({
|
|
|
5677
6164
|
<Select
|
|
5678
6165
|
value={row.value || row.options![0] || ""}
|
|
5679
6166
|
onValueChange={(v) => commitProp(row, v)}
|
|
5680
|
-
disabled={
|
|
6167
|
+
disabled={disabled}
|
|
5681
6168
|
>
|
|
5682
6169
|
<SelectTrigger className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
|
|
5683
6170
|
<SelectValue />
|
|
@@ -5702,7 +6189,7 @@ function ComponentSection({
|
|
|
5702
6189
|
onCheckedChange={(checked) =>
|
|
5703
6190
|
commitProp(row, checked ? "true" : "false")
|
|
5704
6191
|
}
|
|
5705
|
-
disabled={
|
|
6192
|
+
disabled={disabled}
|
|
5706
6193
|
className="h-4 w-7 [&>span]:size-3 [&>span]:data-[state=checked]:translate-x-3"
|
|
5707
6194
|
aria-label={
|
|
5708
6195
|
row.name /* i18n-ignore dynamic prop name */
|
|
@@ -5714,7 +6201,7 @@ function ComponentSection({
|
|
|
5714
6201
|
<Input
|
|
5715
6202
|
defaultValue={row.value}
|
|
5716
6203
|
key={`${row.name}:${row.value}`}
|
|
5717
|
-
disabled={
|
|
6204
|
+
disabled={disabled}
|
|
5718
6205
|
onBlur={(e) => commitProp(row, e.target.value)}
|
|
5719
6206
|
onKeyDown={(e) => {
|
|
5720
6207
|
if (e.key === "Enter") {
|
|
@@ -5752,7 +6239,6 @@ export function EditPanel({
|
|
|
5752
6239
|
onActiveTabChange,
|
|
5753
6240
|
tweaks = [],
|
|
5754
6241
|
tweakValues = {},
|
|
5755
|
-
extensionContext,
|
|
5756
6242
|
onTweakChange,
|
|
5757
6243
|
onRequestTweaks,
|
|
5758
6244
|
onStyleChange,
|
|
@@ -5763,8 +6249,10 @@ export function EditPanel({
|
|
|
5763
6249
|
readOnly = false,
|
|
5764
6250
|
fileId,
|
|
5765
6251
|
filename,
|
|
6252
|
+
activeContent,
|
|
6253
|
+
activeFileUpdatedAt,
|
|
5766
6254
|
designId,
|
|
5767
|
-
|
|
6255
|
+
onComponentPropApplied,
|
|
5768
6256
|
statesPanelProps,
|
|
5769
6257
|
reviewPanelProps,
|
|
5770
6258
|
componentNodeId,
|
|
@@ -5779,12 +6267,6 @@ export function EditPanel({
|
|
|
5779
6267
|
DEFAULT_EXPORT_SETTINGS,
|
|
5780
6268
|
);
|
|
5781
6269
|
const [showExportPreview, setShowExportPreview] = useState(false);
|
|
5782
|
-
// Sub-tab within the Tweaks area: "tokens" (TokensPanel face) or "tweaks"
|
|
5783
|
-
// (raw TweakDefinition controls). Always default to the existing "tweaks"
|
|
5784
|
-
// controls so opening Tweaks lands on the user's TweakDefinition controls
|
|
5785
|
-
// (and the + affordance) rather than auto-firing token indexing. "Tokens"
|
|
5786
|
-
// remains available as a sub-tab the user can click when designId is set.
|
|
5787
|
-
const [tweaksSubTab, setTweaksSubTab] = useState<TweaksSubTab>("tweaks");
|
|
5788
6270
|
|
|
5789
6271
|
const selectedElementKey = selectedElement
|
|
5790
6272
|
? elementIdentityKey(selectedElement)
|
|
@@ -5802,12 +6284,8 @@ export function EditPanel({
|
|
|
5802
6284
|
},
|
|
5803
6285
|
[onTweakChange],
|
|
5804
6286
|
);
|
|
5805
|
-
// The "Add Tweaks" (+) flow lives only on the "tweaks" sub-tab. Switch to it
|
|
5806
|
-
// before opening the prompt so the + button and the newly added control are
|
|
5807
|
-
// visible (avoids leaving the user on the Tokens sub-tab where + isn't shown).
|
|
5808
6287
|
const handleRequestTweaks = useCallback(
|
|
5809
6288
|
(anchor: HTMLElement) => {
|
|
5810
|
-
setTweaksSubTab("tweaks");
|
|
5811
6289
|
onRequestTweaks?.(anchor);
|
|
5812
6290
|
},
|
|
5813
6291
|
[onRequestTweaks],
|
|
@@ -5825,6 +6303,7 @@ export function EditPanel({
|
|
|
5825
6303
|
// over-scroll bounce, could briefly un-shield the canvas and allow a stray
|
|
5826
6304
|
// pointer event to deselect the selected canvas element (R3 regression).
|
|
5827
6305
|
const scrolledRecentlyRef = useRef(false);
|
|
6306
|
+
const userScrollIntentRef = useRef(false);
|
|
5828
6307
|
const scrollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
5829
6308
|
|
|
5830
6309
|
if (readOnly) {
|
|
@@ -5904,7 +6383,14 @@ export function EditPanel({
|
|
|
5904
6383
|
|
|
5905
6384
|
<div
|
|
5906
6385
|
className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain"
|
|
6386
|
+
onWheelCapture={() => {
|
|
6387
|
+
userScrollIntentRef.current = true;
|
|
6388
|
+
}}
|
|
6389
|
+
onTouchMoveCapture={() => {
|
|
6390
|
+
userScrollIntentRef.current = true;
|
|
6391
|
+
}}
|
|
5907
6392
|
onScroll={() => {
|
|
6393
|
+
if (!userScrollIntentRef.current) return;
|
|
5908
6394
|
// Mark that a scroll just happened so the click that some
|
|
5909
6395
|
// browsers fire at the end of a scroll gesture (or after an
|
|
5910
6396
|
// overscroll/rubber-band bounce) is suppressed. Crucially this
|
|
@@ -5919,6 +6405,7 @@ export function EditPanel({
|
|
|
5919
6405
|
}
|
|
5920
6406
|
scrollTimerRef.current = setTimeout(() => {
|
|
5921
6407
|
scrolledRecentlyRef.current = false;
|
|
6408
|
+
userScrollIntentRef.current = false;
|
|
5922
6409
|
scrollTimerRef.current = null;
|
|
5923
6410
|
}, 300);
|
|
5924
6411
|
}}
|
|
@@ -5930,6 +6417,7 @@ export function EditPanel({
|
|
|
5930
6417
|
// browsers generate after a touch-scroll ends.
|
|
5931
6418
|
if (!scrolledRecentlyRef.current) return;
|
|
5932
6419
|
scrolledRecentlyRef.current = false;
|
|
6420
|
+
userScrollIntentRef.current = false;
|
|
5933
6421
|
if (scrollTimerRef.current !== null) {
|
|
5934
6422
|
clearTimeout(scrollTimerRef.current);
|
|
5935
6423
|
scrollTimerRef.current = null;
|
|
@@ -5971,7 +6459,11 @@ export function EditPanel({
|
|
|
5971
6459
|
{designId && componentNodeId && (
|
|
5972
6460
|
<ComponentSection
|
|
5973
6461
|
designId={designId}
|
|
6462
|
+
fileId={fileId}
|
|
6463
|
+
activeContent={activeContent}
|
|
6464
|
+
activeFileUpdatedAt={activeFileUpdatedAt}
|
|
5974
6465
|
nodeId={componentNodeId}
|
|
6466
|
+
onComponentPropApplied={onComponentPropApplied}
|
|
5975
6467
|
sourceCapabilities={sourceCapabilities}
|
|
5976
6468
|
/>
|
|
5977
6469
|
)}
|
|
@@ -6160,132 +6652,41 @@ export function EditPanel({
|
|
|
6160
6652
|
</>
|
|
6161
6653
|
) : activeTab === "tweaks" ? (
|
|
6162
6654
|
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
className={cn(
|
|
6188
|
-
"h-6 rounded-md px-2 text-[11px] font-semibold text-muted-foreground transition-colors hover:text-foreground",
|
|
6189
|
-
tweaksSubTab === "tweaks" &&
|
|
6190
|
-
"bg-[var(--design-editor-panel-raised-bg)] text-foreground",
|
|
6191
|
-
)}
|
|
6192
|
-
onClick={() => setTweaksSubTab("tweaks")}
|
|
6193
|
-
>
|
|
6194
|
-
{t("designEditor.tweaks")}
|
|
6195
|
-
</Button>
|
|
6196
|
-
</div>
|
|
6197
|
-
) : (
|
|
6198
|
-
<div className="flex h-10 shrink-0 items-center justify-between gap-2 border-b border-border/90 px-3">
|
|
6199
|
-
<h3 className="min-w-0 flex-1 truncate text-[13px] font-semibold text-foreground">
|
|
6200
|
-
{t("designEditor.tweaks")}
|
|
6201
|
-
</h3>
|
|
6202
|
-
{onRequestTweaks ? (
|
|
6203
|
-
<Tooltip>
|
|
6204
|
-
<TooltipTrigger asChild>
|
|
6205
|
-
<Button
|
|
6206
|
-
type="button"
|
|
6207
|
-
variant="ghost"
|
|
6208
|
-
size="icon"
|
|
6209
|
-
className="size-7 rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
6210
|
-
aria-label={t("designEditor.addTweaks")}
|
|
6211
|
-
onClick={(event) =>
|
|
6212
|
-
handleRequestTweaks(event.currentTarget)
|
|
6213
|
-
}
|
|
6214
|
-
>
|
|
6215
|
-
<IconPlus className="size-3.5" />
|
|
6216
|
-
</Button>
|
|
6217
|
-
</TooltipTrigger>
|
|
6218
|
-
<TooltipContent>{t("designEditor.addTweaks")}</TooltipContent>
|
|
6219
|
-
</Tooltip>
|
|
6220
|
-
) : null}
|
|
6221
|
-
</div>
|
|
6222
|
-
)}
|
|
6655
|
+
<div className="flex h-10 shrink-0 items-center justify-between gap-2 border-b border-border/90 px-3">
|
|
6656
|
+
<h3 className="min-w-0 flex-1 truncate text-[13px] font-semibold text-foreground">
|
|
6657
|
+
{t("designEditor.tweaks")}
|
|
6658
|
+
</h3>
|
|
6659
|
+
{onRequestTweaks ? (
|
|
6660
|
+
<Tooltip>
|
|
6661
|
+
<TooltipTrigger asChild>
|
|
6662
|
+
<Button
|
|
6663
|
+
type="button"
|
|
6664
|
+
variant="ghost"
|
|
6665
|
+
size="icon"
|
|
6666
|
+
className="size-7 rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
6667
|
+
aria-label={t("designEditor.addTweaks")}
|
|
6668
|
+
onClick={(event) =>
|
|
6669
|
+
handleRequestTweaks(event.currentTarget)
|
|
6670
|
+
}
|
|
6671
|
+
>
|
|
6672
|
+
<IconPlus className="size-3.5" />
|
|
6673
|
+
</Button>
|
|
6674
|
+
</TooltipTrigger>
|
|
6675
|
+
<TooltipContent>{t("designEditor.addTweaks")}</TooltipContent>
|
|
6676
|
+
</Tooltip>
|
|
6677
|
+
) : null}
|
|
6678
|
+
</div>
|
|
6223
6679
|
|
|
6224
6680
|
<div className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
onTokensApplied={onTokensApplied}
|
|
6233
|
-
/>
|
|
6234
|
-
{/* §5 / §6.6 — token write-back to source is a real-app
|
|
6235
|
-
capability. On inline sources (sourceCapabilities lacks
|
|
6236
|
-
"writeFile") show a compact "Make it real" CTA after the
|
|
6237
|
-
token list so the user knows write-back is one step away. */}
|
|
6238
|
-
{!sourceCapabilities.includes("writeFile") && (
|
|
6239
|
-
<div className="px-3 pb-3 pt-1">
|
|
6240
|
-
<MakeItRealCard
|
|
6241
|
-
designId={designId}
|
|
6242
|
-
featureLabel="token write-back to source files"
|
|
6243
|
-
/>
|
|
6244
|
-
</div>
|
|
6245
|
-
)}
|
|
6246
|
-
</>
|
|
6247
|
-
) : (
|
|
6248
|
-
/* Raw TweakDefinition controls — shown when no designId, or when
|
|
6249
|
-
the user switches to the Tweaks sub-tab. */
|
|
6250
|
-
<>
|
|
6251
|
-
{designId && onRequestTweaks ? (
|
|
6252
|
-
/* When in sub-tab mode, show the add-tweaks action in the
|
|
6253
|
-
scrollable content area above the raw controls. */
|
|
6254
|
-
<div className="flex items-center justify-end px-3 pt-2">
|
|
6255
|
-
<Tooltip>
|
|
6256
|
-
<TooltipTrigger asChild>
|
|
6257
|
-
<Button
|
|
6258
|
-
type="button"
|
|
6259
|
-
variant="ghost"
|
|
6260
|
-
size="icon"
|
|
6261
|
-
className="size-7 rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
6262
|
-
aria-label={t("designEditor.addTweaks")}
|
|
6263
|
-
onClick={(event) =>
|
|
6264
|
-
handleRequestTweaks(event.currentTarget)
|
|
6265
|
-
}
|
|
6266
|
-
>
|
|
6267
|
-
<IconPlus className="size-3.5" />
|
|
6268
|
-
</Button>
|
|
6269
|
-
</TooltipTrigger>
|
|
6270
|
-
<TooltipContent>
|
|
6271
|
-
{t("designEditor.addTweaks")}
|
|
6272
|
-
</TooltipContent>
|
|
6273
|
-
</Tooltip>
|
|
6274
|
-
</div>
|
|
6275
|
-
) : null}
|
|
6276
|
-
<TweaksPanelContent
|
|
6277
|
-
tweaks={tweaks}
|
|
6278
|
-
values={tweakValues}
|
|
6279
|
-
onChange={handleTweakChange}
|
|
6280
|
-
onRequestTweaks={!designId ? handleRequestTweaks : undefined}
|
|
6281
|
-
className="px-3 py-3"
|
|
6282
|
-
/>
|
|
6283
|
-
</>
|
|
6284
|
-
)}
|
|
6681
|
+
<TweaksPanelContent
|
|
6682
|
+
tweaks={tweaks}
|
|
6683
|
+
values={tweakValues}
|
|
6684
|
+
onChange={handleTweakChange}
|
|
6685
|
+
onRequestTweaks={handleRequestTweaks}
|
|
6686
|
+
className="px-3 py-3"
|
|
6687
|
+
/>
|
|
6285
6688
|
</div>
|
|
6286
6689
|
</div>
|
|
6287
|
-
) : extensionContext ? (
|
|
6288
|
-
<DesignExtensionsPanel context={extensionContext} />
|
|
6289
6690
|
) : null}
|
|
6290
6691
|
</div>
|
|
6291
6692
|
);
|