@agent-native/core 0.92.7 → 0.92.8
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 +12 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/pure-agent-apps.mdx +22 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builtin.ts +1 -1
- package/corpus/core/src/agent/model-config.ts +30 -30
- package/corpus/core/src/agent/production-agent.ts +129 -7
- package/corpus/core/src/cli/design-connect.ts +438 -83
- package/corpus/core/src/cli/recap.ts +2 -2
- package/corpus/core/src/client/AgentPanel.tsx +8 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
- package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
- package/corpus/core/src/client/index.ts +4 -0
- package/corpus/core/src/client/org/TeamPage.tsx +41 -26
- package/corpus/core/src/client/use-action.ts +113 -1
- package/corpus/core/src/db/client.ts +12 -2
- package/corpus/core/src/db/create-get-db.ts +4 -1
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
- package/corpus/core/src/server/transcribe-voice.ts +1 -1
- package/corpus/core/src/usage/store.ts +13 -4
- package/corpus/core/src/vite/client.ts +38 -1
- package/corpus/templates/analytics/AGENTS.md +3 -0
- package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
- package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
- package/corpus/templates/analytics/app/global.css +11 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +20 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
- package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
- package/corpus/templates/analytics/server/db/index.ts +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
- package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
- package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
- package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/content/app/i18n-data.ts +28 -0
- package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
- package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
- package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
- package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
- package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
- package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
- package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
- package/corpus/templates/design/actions/capture-design-state.ts +2 -2
- package/corpus/templates/design/actions/connect-localhost.ts +58 -12
- package/corpus/templates/design/actions/create-component.ts +2 -18
- package/corpus/templates/design/actions/create-design-branch.ts +38 -18
- package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
- package/corpus/templates/design/actions/delete-file.ts +19 -34
- package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
- package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
- package/corpus/templates/design/actions/export-pdf.ts +2 -1
- package/corpus/templates/design/actions/export-zip.ts +9 -3
- package/corpus/templates/design/actions/generate-design.ts +107 -70
- package/corpus/templates/design/actions/get-component-details.ts +2 -18
- package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
- package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
- package/corpus/templates/design/actions/get-design.ts +2 -1
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
- package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
- package/corpus/templates/design/actions/index-components.ts +2 -18
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
- package/corpus/templates/design/actions/list-local-files.ts +9 -2
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
- package/corpus/templates/design/actions/open-component-source.ts +2 -18
- package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
- package/corpus/templates/design/actions/present-design-variants.ts +70 -70
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
- package/corpus/templates/design/actions/read-local-file.ts +9 -2
- package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
- package/corpus/templates/design/actions/rename-screen.ts +425 -0
- package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
- package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
- package/corpus/templates/design/actions/update-design.ts +376 -61
- package/corpus/templates/design/actions/update-file.ts +455 -188
- package/corpus/templates/design/actions/write-local-file.ts +10 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
- package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
- package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
- package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
- package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
- package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
- package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
- package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
- package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
- package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
- package/corpus/templates/design/app/components/design/index.ts +2 -2
- package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
- package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
- package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
- package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
- package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
- package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
- package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
- package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
- package/corpus/templates/design/app/i18n-data.ts +223 -0
- package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
- package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
- package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
- package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
- package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
- package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
- package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
- package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
- package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
- package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
- package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
- package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
- package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
- package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
- package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
- package/corpus/templates/design/server/db/index.ts +2 -52
- package/corpus/templates/design/server/db/schema.ts +16 -0
- package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
- package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
- package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
- package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
- package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
- package/corpus/templates/design/server/plugins/db.ts +19 -0
- package/corpus/templates/design/server/source-workspace.ts +12 -13
- package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
- package/corpus/templates/design/shared/screen-rename.ts +42 -0
- package/corpus/templates/design/shared/source-mode.ts +84 -4
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builtin.js +1 -1
- package/dist/agent/engine/builtin.js.map +1 -1
- package/dist/agent/model-config.d.ts +9 -9
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +30 -30
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts +60 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +106 -7
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts +30 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +359 -75
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/recap.js +2 -2
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +10 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/TiptapComposer.js +1 -1
- package/dist/client/composer/TiptapComposer.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/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +6 -5
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/use-action.d.ts +32 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +68 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.d.ts +3 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +7 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.js +1 -1
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +13 -4
- package/dist/usage/store.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +36 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/docs/content/pr-visual-recap.mdx +3 -3
- package/docs/content/pure-agent-apps.mdx +22 -0
- package/package.json +2 -2
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildCodeLayerProjection,
|
|
3
|
+
type CodeLayerNode,
|
|
4
|
+
type CodeLayerProjection,
|
|
5
|
+
type CodeLayerTreeNode,
|
|
6
|
+
removeCodeLayerNodeFromHtml,
|
|
7
|
+
} from "@shared/code-layer";
|
|
8
|
+
import { isComponentInstance } from "@shared/component-model";
|
|
9
|
+
export {
|
|
10
|
+
renameFilenamePreservingExtension,
|
|
11
|
+
replaceDataScreenReferences,
|
|
12
|
+
} from "@shared/screen-rename";
|
|
13
|
+
|
|
14
|
+
import type { LayersPanelNode } from "@/components/design/LayersPanel";
|
|
15
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
16
|
+
|
|
17
|
+
import { queryUniqueSelector } from "./dom-utils";
|
|
18
|
+
|
|
19
|
+
export function layerTypeForCodeLayer(
|
|
20
|
+
node: CodeLayerTreeNode,
|
|
21
|
+
): LayersPanelNode["type"] {
|
|
22
|
+
if (node.type === "group") return "group";
|
|
23
|
+
if (node.type === "component") return "component";
|
|
24
|
+
if (node.type === "ellipse") return "ellipse";
|
|
25
|
+
if (node.type === "shape") return "shape";
|
|
26
|
+
if (node.type === "vector") return "vector";
|
|
27
|
+
if (node.type === "line") return "line";
|
|
28
|
+
if (node.type === "arrow") return "arrow";
|
|
29
|
+
if (node.type === "polygon") return "polygon";
|
|
30
|
+
if (node.type === "star") return "star";
|
|
31
|
+
if (node.type === "text") return "text";
|
|
32
|
+
if (node.type === "image") return "image";
|
|
33
|
+
return "element";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function codeLayerNodeLooksLikeComponent(
|
|
37
|
+
node: CodeLayerNode | null | undefined,
|
|
38
|
+
): boolean {
|
|
39
|
+
if (!node) return false;
|
|
40
|
+
if (isComponentInstance(node)) return true;
|
|
41
|
+
const tag = node.tag.toLowerCase();
|
|
42
|
+
if (
|
|
43
|
+
tag === "button" ||
|
|
44
|
+
tag === "input" ||
|
|
45
|
+
tag === "select" ||
|
|
46
|
+
tag === "textarea"
|
|
47
|
+
) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (/component|card|button|control/i.test(node.layerName)) return true;
|
|
51
|
+
return node.classes.some((item) =>
|
|
52
|
+
/component|card|button|control/i.test(item),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function preferredCodeLayerSelector(node: CodeLayerNode): string {
|
|
57
|
+
return (
|
|
58
|
+
node.selectors.find((selector) =>
|
|
59
|
+
/^\[data-(agent-native-node-id|code-layer-id|layer-id|builder-id|loc)=/.test(
|
|
60
|
+
selector,
|
|
61
|
+
),
|
|
62
|
+
) ??
|
|
63
|
+
node.path ??
|
|
64
|
+
node.selector
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function codeLayerSelectorAliases(
|
|
69
|
+
node: CodeLayerNode | null | undefined,
|
|
70
|
+
): string[] {
|
|
71
|
+
if (!node) return [];
|
|
72
|
+
return Array.from(
|
|
73
|
+
new Set(
|
|
74
|
+
[
|
|
75
|
+
preferredCodeLayerSelector(node),
|
|
76
|
+
node.selector,
|
|
77
|
+
node.path,
|
|
78
|
+
...node.selectors,
|
|
79
|
+
]
|
|
80
|
+
.map((selector) => selector.trim())
|
|
81
|
+
.filter(Boolean),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function normalizeCodeLayerSelector(selector: string): string {
|
|
87
|
+
return (
|
|
88
|
+
selector
|
|
89
|
+
.trim()
|
|
90
|
+
.replace(/\s*>\s*/g, " > ")
|
|
91
|
+
.replace(/\s+/g, " ")
|
|
92
|
+
// Bridge emits :nth-of-type(1) for first siblings when multiple share a
|
|
93
|
+
// tag; the projection omits the suffix for first occurrences. Strip it so
|
|
94
|
+
// both forms round-trip to the same normalized string.
|
|
95
|
+
.replace(/:nth-of-type\(1\)/g, "")
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function codeLayerSelectorPartTag(selectorPart: string): string | null {
|
|
100
|
+
const match = selectorPart.trim().match(/^([A-Za-z][A-Za-z0-9:-]*)/);
|
|
101
|
+
return match?.[1]?.toLowerCase() ?? null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function stripLeadingDocumentRootSelectorParts(
|
|
105
|
+
selector: string,
|
|
106
|
+
): string {
|
|
107
|
+
const parts = normalizeCodeLayerSelector(selector)
|
|
108
|
+
.split(" > ")
|
|
109
|
+
.map((part) => part.trim())
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
while (
|
|
112
|
+
parts.length > 0 &&
|
|
113
|
+
["html", "body"].includes(codeLayerSelectorPartTag(parts[0] ?? "") ?? "")
|
|
114
|
+
) {
|
|
115
|
+
parts.shift();
|
|
116
|
+
}
|
|
117
|
+
return parts.join(" > ");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function codeLayerSelectorMatchTargets(selector: string): string[] {
|
|
121
|
+
return Array.from(
|
|
122
|
+
new Set(
|
|
123
|
+
[
|
|
124
|
+
normalizeCodeLayerSelector(selector),
|
|
125
|
+
stripLeadingDocumentRootSelectorParts(selector),
|
|
126
|
+
]
|
|
127
|
+
.map((target) => target.trim())
|
|
128
|
+
.filter(Boolean),
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function codeLayerSelectorMatches(
|
|
134
|
+
node: CodeLayerNode | null | undefined,
|
|
135
|
+
selector: string | undefined,
|
|
136
|
+
): boolean {
|
|
137
|
+
if (!node || !selector) return false;
|
|
138
|
+
const targets = codeLayerSelectorMatchTargets(selector);
|
|
139
|
+
return codeLayerSelectorAliases(node).some((candidate) => {
|
|
140
|
+
const normalized = normalizeCodeLayerSelector(candidate);
|
|
141
|
+
return targets.some((target) => {
|
|
142
|
+
const targetHasDirectPath = target.includes(" > ");
|
|
143
|
+
return (
|
|
144
|
+
normalized === target ||
|
|
145
|
+
(targetHasDirectPath &&
|
|
146
|
+
normalized.includes(" > ") &&
|
|
147
|
+
(normalized.endsWith(` > ${target}`) ||
|
|
148
|
+
target.endsWith(` > ${normalized}`)))
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const GENERIC_TAG_DISPLAY_NAMES: Record<string, string> = {
|
|
155
|
+
html: "Document",
|
|
156
|
+
head: "Head",
|
|
157
|
+
canvas: "Canvas",
|
|
158
|
+
table: "Table",
|
|
159
|
+
thead: "Table Head",
|
|
160
|
+
tbody: "Table Body",
|
|
161
|
+
tr: "Table Row",
|
|
162
|
+
td: "Table Cell",
|
|
163
|
+
th: "Table Header",
|
|
164
|
+
dl: "Description List",
|
|
165
|
+
dt: "Description Term",
|
|
166
|
+
dd: "Description",
|
|
167
|
+
blockquote: "Quote",
|
|
168
|
+
pre: "Preformatted",
|
|
169
|
+
code: "Code",
|
|
170
|
+
input: "Input",
|
|
171
|
+
select: "Select",
|
|
172
|
+
textarea: "Textarea",
|
|
173
|
+
video: "Video",
|
|
174
|
+
audio: "Audio",
|
|
175
|
+
iframe: "Embed",
|
|
176
|
+
details: "Details",
|
|
177
|
+
summary: "Summary",
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export function resolvedLayerName(node: CodeLayerTreeNode): string {
|
|
181
|
+
// layerNameSource "tag" means the projection fell back to the raw tag name.
|
|
182
|
+
// For unrecognised tags fallbackTagLayerName() returns tag.toUpperCase(),
|
|
183
|
+
// which is not user-friendly. Override those with a friendlier label while
|
|
184
|
+
// leaving explicit semantic/text/attribute names unchanged.
|
|
185
|
+
if (
|
|
186
|
+
node.name === node.tag.toUpperCase() ||
|
|
187
|
+
node.name === node.tag.toLowerCase()
|
|
188
|
+
) {
|
|
189
|
+
return GENERIC_TAG_DISPLAY_NAMES[node.tag] ?? node.name;
|
|
190
|
+
}
|
|
191
|
+
return node.name;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function codeLayerTreeToPanelNodes(
|
|
195
|
+
nodes: CodeLayerTreeNode[],
|
|
196
|
+
lockedIds: Set<string>,
|
|
197
|
+
hiddenIds: Set<string>,
|
|
198
|
+
inheritedLocked = false,
|
|
199
|
+
inheritedHidden = false,
|
|
200
|
+
// Ancestor-path ids guarding against a cyclic projection (e.g. duplicate or
|
|
201
|
+
// empty node ids like "an-" that make a node its own descendant) recursing
|
|
202
|
+
// forever and crashing the whole editor with a stack overflow.
|
|
203
|
+
ancestors: Set<string> = new Set(),
|
|
204
|
+
): LayersPanelNode[] {
|
|
205
|
+
return nodes.map((node) => {
|
|
206
|
+
const selfLocked = lockedIds.has(node.id);
|
|
207
|
+
const selfHidden = hiddenIds.has(node.id);
|
|
208
|
+
const locked = inheritedLocked || selfLocked;
|
|
209
|
+
const hidden = inheritedHidden || selfHidden;
|
|
210
|
+
let children: LayersPanelNode[] = [];
|
|
211
|
+
if (!ancestors.has(node.id)) {
|
|
212
|
+
ancestors.add(node.id);
|
|
213
|
+
children = codeLayerTreeToPanelNodes(
|
|
214
|
+
node.children,
|
|
215
|
+
lockedIds,
|
|
216
|
+
hiddenIds,
|
|
217
|
+
locked,
|
|
218
|
+
hidden,
|
|
219
|
+
ancestors,
|
|
220
|
+
);
|
|
221
|
+
ancestors.delete(node.id);
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
id: node.id,
|
|
225
|
+
name: resolvedLayerName(node),
|
|
226
|
+
type: layerTypeForCodeLayer(node),
|
|
227
|
+
tagName: node.tag,
|
|
228
|
+
layout: node.layout,
|
|
229
|
+
detail: node.detail,
|
|
230
|
+
badge: node.badge,
|
|
231
|
+
selectable: true,
|
|
232
|
+
renamable: node.renamable,
|
|
233
|
+
// L18: a descendant that is read-only only because an ANCESTOR is
|
|
234
|
+
// locked/hidden (not itself directly locked/hidden) still renders in
|
|
235
|
+
// the dimmed/read-only visual state (locked/hidden below stay true so
|
|
236
|
+
// existing row styling picks it up), but its own toggle affordance is
|
|
237
|
+
// suppressed — toggling it directly would be a no-op while the
|
|
238
|
+
// ancestor is still locked/hidden, which is confusing UI. Only a
|
|
239
|
+
// directly (self) locked/hidden row keeps a live toggle.
|
|
240
|
+
lockable: selfLocked || !inheritedLocked,
|
|
241
|
+
hideable: selfHidden || !inheritedHidden,
|
|
242
|
+
locked,
|
|
243
|
+
hidden,
|
|
244
|
+
children,
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface EffectiveCodeLayerState {
|
|
250
|
+
lockedIds: Set<string>;
|
|
251
|
+
hiddenIds: Set<string>;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface SelectedLayerTarget {
|
|
255
|
+
layerId: string;
|
|
256
|
+
fileId: string;
|
|
257
|
+
node: CodeLayerNode;
|
|
258
|
+
tree: CodeLayerTreeNode[];
|
|
259
|
+
elementInfo: ElementInfo;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function collectEffectiveCodeLayerState(
|
|
263
|
+
nodes: CodeLayerTreeNode[],
|
|
264
|
+
lockedIds: Set<string>,
|
|
265
|
+
hiddenIds: Set<string>,
|
|
266
|
+
inheritedLocked: boolean,
|
|
267
|
+
inheritedHidden: boolean,
|
|
268
|
+
state: EffectiveCodeLayerState,
|
|
269
|
+
// Ids on the current ancestor path — guards against a malformed/cyclic
|
|
270
|
+
// projection (e.g. a node that appears as its own descendant from duplicate
|
|
271
|
+
// node ids) recursing forever and crashing the whole editor with a stack
|
|
272
|
+
// overflow. A true cycle is skipped; duplicate ids in disjoint subtrees are
|
|
273
|
+
// still visited.
|
|
274
|
+
ancestors: Set<string> = new Set(),
|
|
275
|
+
): EffectiveCodeLayerState {
|
|
276
|
+
nodes.forEach((node) => {
|
|
277
|
+
if (ancestors.has(node.id)) return;
|
|
278
|
+
const locked = inheritedLocked || lockedIds.has(node.id);
|
|
279
|
+
const hidden = inheritedHidden || hiddenIds.has(node.id);
|
|
280
|
+
if (locked) state.lockedIds.add(node.id);
|
|
281
|
+
if (hidden) state.hiddenIds.add(node.id);
|
|
282
|
+
ancestors.add(node.id);
|
|
283
|
+
collectEffectiveCodeLayerState(
|
|
284
|
+
node.children,
|
|
285
|
+
lockedIds,
|
|
286
|
+
hiddenIds,
|
|
287
|
+
locked,
|
|
288
|
+
hidden,
|
|
289
|
+
state,
|
|
290
|
+
ancestors,
|
|
291
|
+
);
|
|
292
|
+
ancestors.delete(node.id);
|
|
293
|
+
});
|
|
294
|
+
return state;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function bridgeSourceIdForCodeLayerNode(node: CodeLayerNode): string {
|
|
298
|
+
return (
|
|
299
|
+
node.dataAttributes["data-agent-native-node-id"] ??
|
|
300
|
+
node.dataAttributes["data-code-layer-id"] ??
|
|
301
|
+
node.dataAttributes["data-layer-id"] ??
|
|
302
|
+
node.dataAttributes["data-builder-id"] ??
|
|
303
|
+
node.dataAttributes["data-loc"] ??
|
|
304
|
+
(typeof node.attributes.id === "string" ? node.attributes.id : undefined) ??
|
|
305
|
+
node.id
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function elementInfoFromCodeLayerNode(node: CodeLayerNode): ElementInfo {
|
|
310
|
+
return {
|
|
311
|
+
tagName: node.tag,
|
|
312
|
+
id: typeof node.attributes.id === "string" ? node.attributes.id : undefined,
|
|
313
|
+
sourceId: bridgeSourceIdForCodeLayerNode(node),
|
|
314
|
+
selector: preferredCodeLayerSelector(node),
|
|
315
|
+
classes: node.classes,
|
|
316
|
+
computedStyles: Object.fromEntries(
|
|
317
|
+
Object.entries(node.style).filter(
|
|
318
|
+
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
319
|
+
),
|
|
320
|
+
),
|
|
321
|
+
boundingRect: { x: 0, y: 0, width: 0, height: 0 },
|
|
322
|
+
textContent: node.textSnippet ?? undefined,
|
|
323
|
+
childElementCount: node.children.length,
|
|
324
|
+
isFlexChild: node.layout.parentDisplay?.includes("flex") ? true : false,
|
|
325
|
+
isFlexContainer: node.layout.isFlexContainer,
|
|
326
|
+
parentDisplay: node.layout.parentDisplay,
|
|
327
|
+
confidence: node.confidence,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function camelCaseCssProperty(property: string): string {
|
|
332
|
+
return property.replace(/-([a-z])/g, (_, letter: string) =>
|
|
333
|
+
letter.toUpperCase(),
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function cssStyleAliases(
|
|
338
|
+
styles: Record<string, string>,
|
|
339
|
+
): Record<string, string> {
|
|
340
|
+
const result: Record<string, string> = {};
|
|
341
|
+
for (const [property, value] of Object.entries(styles)) {
|
|
342
|
+
result[property] = value;
|
|
343
|
+
if (property.includes("-")) {
|
|
344
|
+
result[camelCaseCssProperty(property)] = value;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function refreshedComputedStyles(
|
|
351
|
+
info: ElementInfo,
|
|
352
|
+
sourceStyles: Record<string, string>,
|
|
353
|
+
sourceClasses: readonly string[],
|
|
354
|
+
): Record<string, string> {
|
|
355
|
+
const sourceWithAliases = cssStyleAliases(sourceStyles);
|
|
356
|
+
return sourceClasses.length > 0
|
|
357
|
+
? { ...info.computedStyles, ...sourceWithAliases }
|
|
358
|
+
: sourceWithAliases;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function codeLayerNodeMatchesBridgeTarget(
|
|
362
|
+
node: CodeLayerNode,
|
|
363
|
+
selector?: string,
|
|
364
|
+
sourceId?: string,
|
|
365
|
+
): boolean {
|
|
366
|
+
if (sourceId) {
|
|
367
|
+
if (node.id === sourceId) return true;
|
|
368
|
+
if (
|
|
369
|
+
node.dataAttributes["data-agent-native-node-id"] === sourceId ||
|
|
370
|
+
node.dataAttributes["data-code-layer-id"] === sourceId ||
|
|
371
|
+
node.dataAttributes["data-layer-id"] === sourceId ||
|
|
372
|
+
node.dataAttributes["data-builder-id"] === sourceId ||
|
|
373
|
+
node.dataAttributes["data-loc"] === sourceId ||
|
|
374
|
+
node.attributes.id === sourceId
|
|
375
|
+
) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return codeLayerSelectorMatches(node, selector);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function resolveCodeLayerNodeFromBridge(
|
|
383
|
+
projection: { nodes: CodeLayerNode[] },
|
|
384
|
+
selector?: string,
|
|
385
|
+
sourceId?: string,
|
|
386
|
+
): CodeLayerNode | null {
|
|
387
|
+
return (
|
|
388
|
+
projection.nodes.find((node) =>
|
|
389
|
+
codeLayerNodeMatchesBridgeTarget(node, selector, sourceId),
|
|
390
|
+
) ?? null
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function collapsedElementText(value: string | null | undefined): string {
|
|
395
|
+
return value?.replace(/\s+/g, " ").trim() ?? "";
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function resolveCodeLayerNodeFromElementInfo(
|
|
399
|
+
projection: { nodes: CodeLayerNode[] },
|
|
400
|
+
info: ElementInfo | null | undefined,
|
|
401
|
+
): CodeLayerNode | null {
|
|
402
|
+
if (!info) return null;
|
|
403
|
+
const direct = resolveCodeLayerNodeFromBridge(
|
|
404
|
+
projection,
|
|
405
|
+
info.selector,
|
|
406
|
+
info.sourceId ?? info.id,
|
|
407
|
+
);
|
|
408
|
+
if (direct) return direct;
|
|
409
|
+
|
|
410
|
+
const tagName = info.tagName.toLowerCase();
|
|
411
|
+
const text = collapsedElementText(info.textContent);
|
|
412
|
+
const classes = new Set(info.classes);
|
|
413
|
+
const scored = projection.nodes
|
|
414
|
+
.filter((node) => node.tag === tagName)
|
|
415
|
+
.map((node) => {
|
|
416
|
+
let score = 0;
|
|
417
|
+
const nodeText = collapsedElementText(node.textSnippet);
|
|
418
|
+
if (text && nodeText) {
|
|
419
|
+
if (nodeText === text) score += 8;
|
|
420
|
+
else if (nodeText.includes(text) || text.includes(nodeText)) score += 4;
|
|
421
|
+
}
|
|
422
|
+
if (classes.size > 0) {
|
|
423
|
+
const matchingClasses = node.classes.filter((className) =>
|
|
424
|
+
classes.has(className),
|
|
425
|
+
).length;
|
|
426
|
+
if (matchingClasses === classes.size) score += 4;
|
|
427
|
+
else if (matchingClasses > 0) score += matchingClasses;
|
|
428
|
+
}
|
|
429
|
+
if (info.id && node.attributes.id === info.id) score += 6;
|
|
430
|
+
return { node, score };
|
|
431
|
+
})
|
|
432
|
+
.filter((candidate) => candidate.score >= 4)
|
|
433
|
+
.sort((a, b) => b.score - a.score);
|
|
434
|
+
|
|
435
|
+
if (scored.length === 0) return null;
|
|
436
|
+
const [best, next] = scored;
|
|
437
|
+
if (!best) return null;
|
|
438
|
+
if (next && next.score === best.score) return null;
|
|
439
|
+
return best.node;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export function canonicalElementInfoForCodeLayerNode(
|
|
443
|
+
info: ElementInfo,
|
|
444
|
+
node: CodeLayerNode,
|
|
445
|
+
): ElementInfo {
|
|
446
|
+
return {
|
|
447
|
+
...info,
|
|
448
|
+
sourceId: bridgeSourceIdForCodeLayerNode(node),
|
|
449
|
+
selector: preferredCodeLayerSelector(node),
|
|
450
|
+
classes: node.classes,
|
|
451
|
+
confidence: node.confidence,
|
|
452
|
+
childElementCount: node.children.length,
|
|
453
|
+
editCapabilities: info.editCapabilities?.some((capability) =>
|
|
454
|
+
capability.kind.startsWith("deterministic"),
|
|
455
|
+
)
|
|
456
|
+
? info.editCapabilities
|
|
457
|
+
: [
|
|
458
|
+
{
|
|
459
|
+
kind: "deterministic-style-edit",
|
|
460
|
+
label: "deterministic-style-edit",
|
|
461
|
+
confidence: 0.88,
|
|
462
|
+
reason: "Selection resolved to a unique source code layer.",
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export function canonicalizeElementInfoFromProjection(
|
|
469
|
+
projection: { nodes: CodeLayerNode[] },
|
|
470
|
+
info: ElementInfo,
|
|
471
|
+
): ElementInfo {
|
|
472
|
+
const node = resolveCodeLayerNodeFromElementInfo(projection, info);
|
|
473
|
+
return node ? canonicalElementInfoForCodeLayerNode(info, node) : info;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function elementInfoIsRuntimeOnly(
|
|
477
|
+
info: ElementInfo | null | undefined,
|
|
478
|
+
): boolean {
|
|
479
|
+
return Boolean(
|
|
480
|
+
info?.editCapabilities?.some(
|
|
481
|
+
(capability) => capability.kind === "unsupported",
|
|
482
|
+
),
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export function codeLayerPatchMessage(
|
|
487
|
+
message: string | null | undefined,
|
|
488
|
+
fallback: string,
|
|
489
|
+
): string {
|
|
490
|
+
if (!message) return fallback;
|
|
491
|
+
return message.includes("did not match a code layer node")
|
|
492
|
+
? fallback
|
|
493
|
+
: message;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// T16: known Google Font families offered by the inspector's font-family
|
|
497
|
+
// picker (FONT_FAMILY_OPTIONS in EditPanel.tsx — kept in sync manually since
|
|
498
|
+
// that file isn't editable from here). Maps the exact display family name to
|
|
499
|
+
// the Google Fonts CSS2 API family query param (weight range 400-700 covers
|
|
500
|
+
// the FONT_WEIGHT_OPTIONS range without over-fetching every weight).
|
|
501
|
+
export const KNOWN_GOOGLE_FONTS: Record<string, string> = {
|
|
502
|
+
Inter: "Inter:wght@400;500;600;700",
|
|
503
|
+
Poppins: "Poppins:wght@400;500;600;700",
|
|
504
|
+
"Playfair Display": "Playfair+Display:wght@400;500;600;700",
|
|
505
|
+
"JetBrains Mono": "JetBrains+Mono:wght@400;500;600;700",
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* T16: extract the primary (first) font-family name from a CSS font-family
|
|
510
|
+
* value, stripping quotes. Deliberately simple (no full CSS-value tokenizer)
|
|
511
|
+
* since this only needs to recognize the small known-Google-Fonts set above,
|
|
512
|
+
* not arbitrary font stacks.
|
|
513
|
+
*/
|
|
514
|
+
export function primaryFontFamilyName(value: string): string {
|
|
515
|
+
const first = value.split(",")[0]?.trim() ?? "";
|
|
516
|
+
if (
|
|
517
|
+
(first.startsWith('"') && first.endsWith('"')) ||
|
|
518
|
+
(first.startsWith("'") && first.endsWith("'"))
|
|
519
|
+
) {
|
|
520
|
+
return first.slice(1, -1).trim();
|
|
521
|
+
}
|
|
522
|
+
return first;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* T16: EditPanel's font-family picker lets a user choose Inter/Poppins/
|
|
527
|
+
* Playfair Display/JetBrains Mono, but EditPanel only calls onStyleChange —
|
|
528
|
+
* it has no way to also load the webfont, so picking one just silently fell
|
|
529
|
+
* back to the browser's default font (the family was never actually
|
|
530
|
+
* available). Injects a Google Fonts <link> into the screen's <head> when
|
|
531
|
+
* the committed fontFamily's primary family is a KNOWN_GOOGLE_FONTS entry
|
|
532
|
+
* and no link for that family is already present. Conservative by design:
|
|
533
|
+
* exact-match family name only (case-sensitive, matching the picker's own
|
|
534
|
+
* option values), and skips if ANY existing <link> already mentions the
|
|
535
|
+
* family (avoids duplicate/near-duplicate <link> tags on repeated edits).
|
|
536
|
+
*/
|
|
537
|
+
export function ensureGoogleFontLinkInHtml(
|
|
538
|
+
content: string,
|
|
539
|
+
fontFamilyValue: string,
|
|
540
|
+
): string {
|
|
541
|
+
if (typeof window === "undefined") return content;
|
|
542
|
+
const family = primaryFontFamilyName(fontFamilyValue);
|
|
543
|
+
const fontQuery = family ? KNOWN_GOOGLE_FONTS[family] : undefined;
|
|
544
|
+
if (!fontQuery) return content;
|
|
545
|
+
try {
|
|
546
|
+
const doc = new DOMParser().parseFromString(content, "text/html");
|
|
547
|
+
const head = doc.head;
|
|
548
|
+
if (!head) return content;
|
|
549
|
+
const existingLinks = Array.from(
|
|
550
|
+
head.querySelectorAll('link[href*="fonts.googleapis.com"]'),
|
|
551
|
+
);
|
|
552
|
+
const alreadyLoaded = existingLinks.some((link) => {
|
|
553
|
+
const href = link.getAttribute("href") ?? "";
|
|
554
|
+
return href.includes(encodeURIComponent(family)) || href.includes(family);
|
|
555
|
+
});
|
|
556
|
+
if (alreadyLoaded) return content;
|
|
557
|
+
const preconnectGoogleapis = doc.createElement("link");
|
|
558
|
+
preconnectGoogleapis.setAttribute("rel", "preconnect");
|
|
559
|
+
preconnectGoogleapis.setAttribute("href", "https://fonts.googleapis.com");
|
|
560
|
+
const preconnectGstatic = doc.createElement("link");
|
|
561
|
+
preconnectGstatic.setAttribute("rel", "preconnect");
|
|
562
|
+
preconnectGstatic.setAttribute("href", "https://fonts.gstatic.com");
|
|
563
|
+
preconnectGstatic.setAttribute("crossorigin", "");
|
|
564
|
+
const fontLink = doc.createElement("link");
|
|
565
|
+
fontLink.setAttribute("rel", "stylesheet");
|
|
566
|
+
fontLink.setAttribute(
|
|
567
|
+
"href",
|
|
568
|
+
`https://fonts.googleapis.com/css2?family=${fontQuery}&display=swap`,
|
|
569
|
+
);
|
|
570
|
+
// Skip the preconnect hints if the head already has one for either host
|
|
571
|
+
// (avoids piling up duplicates across repeated font picks).
|
|
572
|
+
if (
|
|
573
|
+
!existingLinks.some((link) =>
|
|
574
|
+
(link.getAttribute("href") ?? "").includes("fonts.googleapis.com"),
|
|
575
|
+
) &&
|
|
576
|
+
!head.querySelector(
|
|
577
|
+
'link[href*="fonts.googleapis.com"][rel="preconnect"]',
|
|
578
|
+
)
|
|
579
|
+
) {
|
|
580
|
+
head.appendChild(preconnectGoogleapis);
|
|
581
|
+
}
|
|
582
|
+
if (
|
|
583
|
+
!head.querySelector('link[href*="fonts.gstatic.com"][rel="preconnect"]')
|
|
584
|
+
) {
|
|
585
|
+
head.appendChild(preconnectGstatic);
|
|
586
|
+
}
|
|
587
|
+
head.appendChild(fontLink);
|
|
588
|
+
return `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
|
589
|
+
} catch {
|
|
590
|
+
return content;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export function refreshElementInfoFromContent(
|
|
595
|
+
content: string,
|
|
596
|
+
info: ElementInfo | null,
|
|
597
|
+
): ElementInfo | null {
|
|
598
|
+
if (!info) return null;
|
|
599
|
+
const projection = buildCodeLayerProjection(content);
|
|
600
|
+
const node =
|
|
601
|
+
resolveCodeLayerNodeFromElementInfo(projection, info) ??
|
|
602
|
+
resolveCodeLayerNodeFromBridge(
|
|
603
|
+
projection,
|
|
604
|
+
info.selector,
|
|
605
|
+
info.sourceId ?? info.id,
|
|
606
|
+
);
|
|
607
|
+
if (node) {
|
|
608
|
+
const sourceInfo = elementInfoFromCodeLayerNode(node);
|
|
609
|
+
return {
|
|
610
|
+
...canonicalElementInfoForCodeLayerNode(info, node),
|
|
611
|
+
computedStyles: refreshedComputedStyles(
|
|
612
|
+
info,
|
|
613
|
+
sourceInfo.computedStyles,
|
|
614
|
+
sourceInfo.classes,
|
|
615
|
+
),
|
|
616
|
+
textContent: sourceInfo.textContent,
|
|
617
|
+
childElementCount: sourceInfo.childElementCount,
|
|
618
|
+
isFlexChild: sourceInfo.isFlexChild,
|
|
619
|
+
isFlexContainer: sourceInfo.isFlexContainer,
|
|
620
|
+
parentDisplay: sourceInfo.parentDisplay,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
if (!info.selector || typeof window === "undefined") return null;
|
|
624
|
+
try {
|
|
625
|
+
const doc = new DOMParser().parseFromString(content, "text/html");
|
|
626
|
+
const element = queryUniqueSelector(doc, info.selector);
|
|
627
|
+
if (!element) return null;
|
|
628
|
+
const classes = Array.from(element.classList);
|
|
629
|
+
return {
|
|
630
|
+
...info,
|
|
631
|
+
classes,
|
|
632
|
+
computedStyles: refreshedComputedStyles(
|
|
633
|
+
info,
|
|
634
|
+
parseInlineStyleAttribute(element.getAttribute("style")),
|
|
635
|
+
classes,
|
|
636
|
+
),
|
|
637
|
+
textContent: element.textContent?.slice(0, 200) ?? info.textContent,
|
|
638
|
+
childElementCount: element.children.length,
|
|
639
|
+
};
|
|
640
|
+
} catch {
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// U18: undo/redo already refreshes selectedElement via
|
|
646
|
+
// refreshElementInfoFromContent, but selectedLayerIdsState (the layers-panel
|
|
647
|
+
// multi-highlight array) was never touched — after an undo/redo that removes
|
|
648
|
+
// or re-ids a node, the panel kept highlighting rows for ids that no longer
|
|
649
|
+
// exist in the new content. Drops any id not present (as either the
|
|
650
|
+
// projection node's own .id or its data-agent-native-node-id) in the new
|
|
651
|
+
// content; returns the SAME array reference when nothing changed so callers
|
|
652
|
+
// can skip the state update.
|
|
653
|
+
export function refreshSelectedLayerIdsFromContent(
|
|
654
|
+
content: string,
|
|
655
|
+
layerIds: readonly string[],
|
|
656
|
+
): string[] {
|
|
657
|
+
if (layerIds.length === 0) return layerIds as string[];
|
|
658
|
+
const projection = buildCodeLayerProjection(content);
|
|
659
|
+
const validIds = new Set<string>();
|
|
660
|
+
projection.nodes.forEach((node) => {
|
|
661
|
+
validIds.add(node.id);
|
|
662
|
+
const dataNodeId = node.dataAttributes["data-agent-native-node-id"];
|
|
663
|
+
if (dataNodeId) validIds.add(dataNodeId);
|
|
664
|
+
});
|
|
665
|
+
const next = layerIds.filter((id) => validIds.has(id));
|
|
666
|
+
return next.length === layerIds.length ? (layerIds as string[]) : next;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// L4: locate the DOM-order sibling id list that contains targetId (i.e. the
|
|
670
|
+
// children array of its parent, or the root list when targetId is top-level)
|
|
671
|
+
// plus its index within that list. Used by changeSelectedZIndex to compute
|
|
672
|
+
// bring/send-forward/backward moveNode placements among real siblings,
|
|
673
|
+
// instead of the previous z-index/position-hack approach. Returns null when
|
|
674
|
+
// targetId isn't found anywhere in the tree.
|
|
675
|
+
export function findCodeLayerSiblingOrder(
|
|
676
|
+
nodes: CodeLayerTreeNode[],
|
|
677
|
+
targetId: string,
|
|
678
|
+
): { siblingIds: string[]; index: number; parentId: string | null } | null {
|
|
679
|
+
const rootIndex = nodes.findIndex((node) => node.id === targetId);
|
|
680
|
+
if (rootIndex !== -1) {
|
|
681
|
+
return {
|
|
682
|
+
siblingIds: nodes.map((node) => node.id),
|
|
683
|
+
index: rootIndex,
|
|
684
|
+
parentId: null,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
for (const node of nodes) {
|
|
688
|
+
const childIndex = node.children.findIndex(
|
|
689
|
+
(child) => child.id === targetId,
|
|
690
|
+
);
|
|
691
|
+
if (childIndex !== -1) {
|
|
692
|
+
return {
|
|
693
|
+
siblingIds: node.children.map((child) => child.id),
|
|
694
|
+
index: childIndex,
|
|
695
|
+
parentId: node.id,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
const nested = findCodeLayerSiblingOrder(node.children, targetId);
|
|
699
|
+
if (nested) return nested;
|
|
700
|
+
}
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// L25: matches the auto-generated wrapper name pattern from
|
|
705
|
+
// nextSequentialGroupName in shared/code-layer.ts ("Group", "Group 2", ...).
|
|
706
|
+
// Used to identify wrappers that were CREATED by the group action (as opposed
|
|
707
|
+
// to a user's own named container) so we only auto-clean up ones we made.
|
|
708
|
+
export const GENERATED_GROUP_NAME_PATTERN = /^Group(?: \d+)?$/;
|
|
709
|
+
|
|
710
|
+
export function isGeneratedGroupWrapperNode(node: CodeLayerNode): boolean {
|
|
711
|
+
const layerNameAttr = node.dataAttributes["data-agent-native-layer-name"];
|
|
712
|
+
return Boolean(
|
|
713
|
+
layerNameAttr && GENERATED_GROUP_NAME_PATTERN.test(layerNameAttr.trim()),
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* L25: after a move or delete empties out a generated "Group"/"Group N"
|
|
719
|
+
* wrapper (created by the group action), remove the now-empty wrapper
|
|
720
|
+
* instead of leaving an invisible, pointless container behind in the layer
|
|
721
|
+
* tree. Only auto-generated group wrappers are cleaned up — a user's own
|
|
722
|
+
* named empty container is left alone. Checks each candidate former-parent
|
|
723
|
+
* id (by data-agent-native-node-id) against the CURRENT content, since by
|
|
724
|
+
* the time this runs other edits may have already changed the document.
|
|
725
|
+
* Returns the possibly-updated content (unchanged if nothing qualified).
|
|
726
|
+
*/
|
|
727
|
+
export function removeEmptyGeneratedGroupWrappers(
|
|
728
|
+
content: string,
|
|
729
|
+
candidateParentAttrIds: ReadonlySet<string>,
|
|
730
|
+
): string {
|
|
731
|
+
if (candidateParentAttrIds.size === 0) return content;
|
|
732
|
+
let next = content;
|
|
733
|
+
// Loop: removing one empty wrapper can itself empty out ITS parent (e.g.
|
|
734
|
+
// ungrouping down a chain of nested generated groups), so keep sweeping
|
|
735
|
+
// until a pass makes no changes.
|
|
736
|
+
let changedInPass = true;
|
|
737
|
+
let guard = 0;
|
|
738
|
+
while (changedInPass && guard < 10) {
|
|
739
|
+
changedInPass = false;
|
|
740
|
+
guard += 1;
|
|
741
|
+
for (const attrId of candidateParentAttrIds) {
|
|
742
|
+
const projection = buildCodeLayerProjection(next);
|
|
743
|
+
const node = projection.nodes.find(
|
|
744
|
+
(n) => n.dataAttributes["data-agent-native-node-id"] === attrId,
|
|
745
|
+
);
|
|
746
|
+
if (!node || node.children.length > 0) continue;
|
|
747
|
+
if (!isGeneratedGroupWrapperNode(node)) continue;
|
|
748
|
+
const removed = removeCodeLayerNodeFromHtml(next, node);
|
|
749
|
+
if (removed && removed !== next) {
|
|
750
|
+
next = removed;
|
|
751
|
+
changedInPass = true;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return next;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
export function collectCodeLayerAncestors(
|
|
759
|
+
nodes: CodeLayerTreeNode[],
|
|
760
|
+
targetId: string,
|
|
761
|
+
ancestors: string[] = [],
|
|
762
|
+
): string[] {
|
|
763
|
+
for (const node of nodes) {
|
|
764
|
+
if (node.id === targetId) return ancestors;
|
|
765
|
+
const match = collectCodeLayerAncestors(node.children, targetId, [
|
|
766
|
+
...ancestors,
|
|
767
|
+
node.id,
|
|
768
|
+
]);
|
|
769
|
+
if (match.length > 0) return match;
|
|
770
|
+
}
|
|
771
|
+
return [];
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// U14: collects the `data-agent-native-node-id` of targetId and every
|
|
775
|
+
// descendant beneath it in `tree`, using `nodesById` (keyed by projection
|
|
776
|
+
// node .id) to resolve each tree node's stamped id. Used to find every
|
|
777
|
+
// motion-track targetNodeId that becomes orphaned when a subtree is deleted
|
|
778
|
+
// — deleting a parent silently removes its children's node ids from the DOM
|
|
779
|
+
// too, and any track still targeting one of them would animate nothing.
|
|
780
|
+
export function collectCodeLayerSubtreeDataNodeIds(
|
|
781
|
+
tree: CodeLayerTreeNode[],
|
|
782
|
+
targetId: string,
|
|
783
|
+
nodesById: Map<string, CodeLayerNode>,
|
|
784
|
+
): Set<string> {
|
|
785
|
+
const ids = new Set<string>();
|
|
786
|
+
const collectSubtree = (nodes: CodeLayerTreeNode[]) => {
|
|
787
|
+
for (const node of nodes) {
|
|
788
|
+
const dataNodeId = nodesById.get(node.id)?.dataAttributes[
|
|
789
|
+
"data-agent-native-node-id"
|
|
790
|
+
];
|
|
791
|
+
if (dataNodeId) ids.add(dataNodeId);
|
|
792
|
+
collectSubtree(node.children);
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
const findAndCollect = (nodes: CodeLayerTreeNode[]): boolean => {
|
|
796
|
+
for (const node of nodes) {
|
|
797
|
+
if (node.id === targetId) {
|
|
798
|
+
const dataNodeId = nodesById.get(node.id)?.dataAttributes[
|
|
799
|
+
"data-agent-native-node-id"
|
|
800
|
+
];
|
|
801
|
+
if (dataNodeId) ids.add(dataNodeId);
|
|
802
|
+
collectSubtree(node.children);
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
if (findAndCollect(node.children)) return true;
|
|
806
|
+
}
|
|
807
|
+
return false;
|
|
808
|
+
};
|
|
809
|
+
findAndCollect(tree);
|
|
810
|
+
return ids;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
export function sortCodeLayerIdsByTreeOrder(
|
|
814
|
+
ids: readonly string[],
|
|
815
|
+
tree: readonly CodeLayerTreeNode[],
|
|
816
|
+
): string[] {
|
|
817
|
+
const treeOrder = new Map<string, number>();
|
|
818
|
+
let index = 0;
|
|
819
|
+
const visit = (nodes: readonly CodeLayerTreeNode[]) => {
|
|
820
|
+
for (const node of nodes) {
|
|
821
|
+
treeOrder.set(node.id, index);
|
|
822
|
+
index += 1;
|
|
823
|
+
visit(node.children);
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
visit(tree);
|
|
827
|
+
|
|
828
|
+
const originalOrder = new Map(
|
|
829
|
+
ids.map((id, originalIndex) => [id, originalIndex]),
|
|
830
|
+
);
|
|
831
|
+
return [...ids].sort((a, b) => {
|
|
832
|
+
const aOrder = treeOrder.get(a);
|
|
833
|
+
const bOrder = treeOrder.get(b);
|
|
834
|
+
if (aOrder === undefined && bOrder === undefined) {
|
|
835
|
+
return (originalOrder.get(a) ?? 0) - (originalOrder.get(b) ?? 0);
|
|
836
|
+
}
|
|
837
|
+
if (aOrder === undefined) return 1;
|
|
838
|
+
if (bOrder === undefined) return -1;
|
|
839
|
+
return aOrder - bOrder;
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
export function findCodeLayerNodeInProjection(
|
|
844
|
+
projection: CodeLayerProjection,
|
|
845
|
+
previousNode: CodeLayerNode,
|
|
846
|
+
): CodeLayerNode | null {
|
|
847
|
+
const stableSourceIds = [
|
|
848
|
+
previousNode.dataAttributes["data-agent-native-node-id"],
|
|
849
|
+
previousNode.dataAttributes["data-code-layer-id"],
|
|
850
|
+
previousNode.dataAttributes["data-layer-id"],
|
|
851
|
+
previousNode.dataAttributes["data-builder-id"],
|
|
852
|
+
previousNode.dataAttributes["data-loc"],
|
|
853
|
+
typeof previousNode.attributes.id === "string"
|
|
854
|
+
? previousNode.attributes.id
|
|
855
|
+
: undefined,
|
|
856
|
+
].filter((id): id is string => Boolean(id));
|
|
857
|
+
|
|
858
|
+
for (const sourceId of stableSourceIds) {
|
|
859
|
+
const stableMatch = projection.nodes.find(
|
|
860
|
+
(node) =>
|
|
861
|
+
node.dataAttributes["data-agent-native-node-id"] === sourceId ||
|
|
862
|
+
node.dataAttributes["data-code-layer-id"] === sourceId ||
|
|
863
|
+
node.dataAttributes["data-layer-id"] === sourceId ||
|
|
864
|
+
node.dataAttributes["data-builder-id"] === sourceId ||
|
|
865
|
+
node.dataAttributes["data-loc"] === sourceId ||
|
|
866
|
+
node.attributes.id === sourceId,
|
|
867
|
+
);
|
|
868
|
+
if (stableMatch) return stableMatch;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const exactMatch = projection.nodes.find(
|
|
872
|
+
(node) => node.id === previousNode.id,
|
|
873
|
+
);
|
|
874
|
+
if (exactMatch) return exactMatch;
|
|
875
|
+
|
|
876
|
+
const fallbackMatches = projection.nodes.filter(
|
|
877
|
+
(node) =>
|
|
878
|
+
node.tag === previousNode.tag &&
|
|
879
|
+
node.layerName === previousNode.layerName &&
|
|
880
|
+
(node.textSnippet ?? "") === (previousNode.textSnippet ?? ""),
|
|
881
|
+
);
|
|
882
|
+
return fallbackMatches.length === 1 ? (fallbackMatches[0] ?? null) : null;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
export function findMovedCodeLayerNodeInProjection(
|
|
886
|
+
projection: CodeLayerProjection,
|
|
887
|
+
previousNode: CodeLayerNode,
|
|
888
|
+
movedNodeId?: string | null,
|
|
889
|
+
): CodeLayerNode | null {
|
|
890
|
+
if (movedNodeId) {
|
|
891
|
+
const movedMatch = projection.nodes.find(
|
|
892
|
+
(node) =>
|
|
893
|
+
node.id === movedNodeId ||
|
|
894
|
+
node.dataAttributes["data-agent-native-node-id"] === movedNodeId ||
|
|
895
|
+
node.dataAttributes["data-code-layer-id"] === movedNodeId ||
|
|
896
|
+
node.dataAttributes["data-layer-id"] === movedNodeId ||
|
|
897
|
+
node.dataAttributes["data-builder-id"] === movedNodeId ||
|
|
898
|
+
node.dataAttributes["data-loc"] === movedNodeId ||
|
|
899
|
+
node.attributes.id === movedNodeId,
|
|
900
|
+
);
|
|
901
|
+
if (movedMatch) return movedMatch;
|
|
902
|
+
}
|
|
903
|
+
return findCodeLayerNodeInProjection(projection, previousNode);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
export function parseInlineStyleAttribute(
|
|
907
|
+
style: string | null | undefined,
|
|
908
|
+
): Record<string, string> {
|
|
909
|
+
const result: Record<string, string> = {};
|
|
910
|
+
for (const declaration of (style ?? "").split(";")) {
|
|
911
|
+
const separator = declaration.indexOf(":");
|
|
912
|
+
if (separator <= 0) continue;
|
|
913
|
+
const property = declaration.slice(0, separator).trim();
|
|
914
|
+
const value = declaration.slice(separator + 1).trim();
|
|
915
|
+
if (property && value) result[property] = value;
|
|
916
|
+
}
|
|
917
|
+
return result;
|
|
918
|
+
}
|