opendevbrowser 0.0.17 → 0.0.19
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/README.md +172 -73
- package/dist/annotate/agent-inbox-store.d.ts +58 -0
- package/dist/annotate/agent-inbox-store.d.ts.map +1 -0
- package/dist/annotate/agent-inbox.d.ts +25 -0
- package/dist/annotate/agent-inbox.d.ts.map +1 -0
- package/dist/annotate/direct-annotator.d.ts.map +1 -1
- package/dist/annotate/timeout-messages.d.ts +4 -0
- package/dist/annotate/timeout-messages.d.ts.map +1 -0
- package/dist/automation/coordinator.d.ts +55 -0
- package/dist/automation/coordinator.d.ts.map +1 -0
- package/dist/browser/annotation-manager.d.ts +4 -1
- package/dist/browser/annotation-manager.d.ts.map +1 -1
- package/dist/browser/browser-manager.d.ts +147 -47
- package/dist/browser/browser-manager.d.ts.map +1 -1
- package/dist/browser/canvas-client.d.ts +1 -0
- package/dist/browser/canvas-client.d.ts.map +1 -1
- package/dist/browser/canvas-code-sync-manager.d.ts +9 -1
- package/dist/browser/canvas-code-sync-manager.d.ts.map +1 -1
- package/dist/browser/canvas-manager.d.ts +29 -1
- package/dist/browser/canvas-manager.d.ts.map +1 -1
- package/dist/browser/global-challenge-coordinator.d.ts +27 -0
- package/dist/browser/global-challenge-coordinator.d.ts.map +1 -0
- package/dist/browser/manager-types.d.ts +167 -1
- package/dist/browser/manager-types.d.ts.map +1 -1
- package/dist/browser/ops-browser-manager.d.ts +103 -3
- package/dist/browser/ops-browser-manager.d.ts.map +1 -1
- package/dist/browser/ops-client.d.ts +17 -1
- package/dist/browser/ops-client.d.ts.map +1 -1
- package/dist/browser/playwright-runtime.d.ts +4 -0
- package/dist/browser/playwright-runtime.d.ts.map +1 -0
- package/dist/browser/review-surface.d.ts +9 -0
- package/dist/browser/review-surface.d.ts.map +1 -0
- package/dist/browser/screencast-recorder.d.ts +57 -0
- package/dist/browser/screencast-recorder.d.ts.map +1 -0
- package/dist/browser/session-inspector.d.ts +71 -0
- package/dist/browser/session-inspector.d.ts.map +1 -0
- package/dist/browser/session-store.d.ts +5 -1
- package/dist/browser/session-store.d.ts.map +1 -1
- package/dist/browser/system-chrome-cookies.d.ts +46 -0
- package/dist/browser/system-chrome-cookies.d.ts.map +1 -0
- package/dist/browser/target-manager.d.ts +1 -0
- package/dist/browser/target-manager.d.ts.map +1 -1
- package/dist/cache/chrome-locator.d.ts.map +1 -1
- package/dist/cache/chrome-user-data.d.ts +17 -0
- package/dist/cache/chrome-user-data.d.ts.map +1 -0
- package/dist/canvas/adapter-plugins/loader.d.ts +13 -0
- package/dist/canvas/adapter-plugins/loader.d.ts.map +1 -0
- package/dist/canvas/adapter-plugins/manifest.d.ts +146 -0
- package/dist/canvas/adapter-plugins/manifest.d.ts.map +1 -0
- package/dist/canvas/adapter-plugins/types.d.ts +83 -0
- package/dist/canvas/adapter-plugins/types.d.ts.map +1 -0
- package/dist/canvas/adapter-plugins/validator.d.ts +10 -0
- package/dist/canvas/adapter-plugins/validator.d.ts.map +1 -0
- package/dist/canvas/code-sync/apply-tsx.d.ts +3 -1
- package/dist/canvas/code-sync/apply-tsx.d.ts.map +1 -1
- package/dist/canvas/code-sync/import.d.ts +1 -0
- package/dist/canvas/code-sync/import.d.ts.map +1 -1
- package/dist/canvas/code-sync/manifest.d.ts +2 -1
- package/dist/canvas/code-sync/manifest.d.ts.map +1 -1
- package/dist/canvas/code-sync/tsx-adapter.d.ts.map +1 -1
- package/dist/canvas/code-sync/types.d.ts +102 -10
- package/dist/canvas/code-sync/types.d.ts.map +1 -1
- package/dist/canvas/document-store.d.ts +11 -1
- package/dist/canvas/document-store.d.ts.map +1 -1
- package/dist/canvas/export.d.ts.map +1 -1
- package/dist/canvas/framework-adapters/custom-elements-v1.d.ts +3 -0
- package/dist/canvas/framework-adapters/custom-elements-v1.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/html-static-v1.d.ts +3 -0
- package/dist/canvas/framework-adapters/html-static-v1.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/markup.d.ts +9 -0
- package/dist/canvas/framework-adapters/markup.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/react-tsx-v2.d.ts +3 -0
- package/dist/canvas/framework-adapters/react-tsx-v2.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/registry.d.ts +12 -0
- package/dist/canvas/framework-adapters/registry.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/svelte-sfc-v1.d.ts +3 -0
- package/dist/canvas/framework-adapters/svelte-sfc-v1.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/types.d.ts +57 -0
- package/dist/canvas/framework-adapters/types.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/vue-sfc-v1.d.ts +3 -0
- package/dist/canvas/framework-adapters/vue-sfc-v1.d.ts.map +1 -0
- package/dist/canvas/kits/catalog.d.ts +5 -0
- package/dist/canvas/kits/catalog.d.ts.map +1 -0
- package/dist/canvas/library-adapters/react/index.d.ts +3 -0
- package/dist/canvas/library-adapters/react/index.d.ts.map +1 -0
- package/dist/canvas/library-adapters/registry.d.ts +11 -0
- package/dist/canvas/library-adapters/registry.d.ts.map +1 -0
- package/dist/canvas/library-adapters/types.d.ts +43 -0
- package/dist/canvas/library-adapters/types.d.ts.map +1 -0
- package/dist/canvas/repo-store.d.ts +2 -0
- package/dist/canvas/repo-store.d.ts.map +1 -1
- package/dist/canvas/starters/catalog.d.ts +34 -0
- package/dist/canvas/starters/catalog.d.ts.map +1 -0
- package/dist/canvas/token-references.d.ts +22 -0
- package/dist/canvas/token-references.d.ts.map +1 -0
- package/dist/canvas/types.d.ts +345 -6
- package/dist/canvas/types.d.ts.map +1 -1
- package/dist/challenges/action-loop.d.ts +13 -0
- package/dist/challenges/action-loop.d.ts.map +1 -0
- package/dist/challenges/capability-matrix.d.ts +3 -0
- package/dist/challenges/capability-matrix.d.ts.map +1 -0
- package/dist/challenges/evidence-bundle.d.ts +48 -0
- package/dist/challenges/evidence-bundle.d.ts.map +1 -0
- package/dist/challenges/governed-adapter-gateway.d.ts +4 -0
- package/dist/challenges/governed-adapter-gateway.d.ts.map +1 -0
- package/dist/challenges/human-yield-gate.d.ts +20 -0
- package/dist/challenges/human-yield-gate.d.ts.map +1 -0
- package/dist/challenges/index.d.ts +15 -0
- package/dist/challenges/index.d.ts.map +1 -0
- package/dist/challenges/interpreter.d.ts +3 -0
- package/dist/challenges/interpreter.d.ts.map +1 -0
- package/dist/challenges/optional-computer-use-bridge.d.ts +9 -0
- package/dist/challenges/optional-computer-use-bridge.d.ts.map +1 -0
- package/dist/challenges/orchestrator.d.ts +32 -0
- package/dist/challenges/orchestrator.d.ts.map +1 -0
- package/dist/challenges/outcome-recorder.d.ts +8 -0
- package/dist/challenges/outcome-recorder.d.ts.map +1 -0
- package/dist/challenges/owned-environment-lane.d.ts +3 -0
- package/dist/challenges/owned-environment-lane.d.ts.map +1 -0
- package/dist/challenges/policy-gate.d.ts +9 -0
- package/dist/challenges/policy-gate.d.ts.map +1 -0
- package/dist/challenges/sanctioned-identity-lane.d.ts +3 -0
- package/dist/challenges/sanctioned-identity-lane.d.ts.map +1 -0
- package/dist/challenges/service-adapter-lane.d.ts +3 -0
- package/dist/challenges/service-adapter-lane.d.ts.map +1 -0
- package/dist/challenges/strategy-selector.d.ts +10 -0
- package/dist/challenges/strategy-selector.d.ts.map +1 -0
- package/dist/challenges/types.d.ts +277 -0
- package/dist/challenges/types.d.ts.map +1 -0
- package/dist/challenges/verification-gate.d.ts +15 -0
- package/dist/challenges/verification-gate.d.ts.map +1 -0
- package/dist/chunk-5FZQJRBQ.js +15256 -0
- package/dist/chunk-5FZQJRBQ.js.map +1 -0
- package/dist/chunk-W4IHGDXV.js +33519 -0
- package/dist/chunk-W4IHGDXV.js.map +1 -0
- package/dist/chunk-YBQECXZX.js +409 -0
- package/dist/chunk-YBQECXZX.js.map +1 -0
- package/dist/cli/args.d.ts +4 -4
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/commands/artifacts.d.ts.map +1 -1
- package/dist/cli/commands/canvas.d.ts +7 -7
- package/dist/cli/commands/canvas.d.ts.map +1 -1
- package/dist/cli/commands/daemon.d.ts +7 -0
- package/dist/cli/commands/daemon.d.ts.map +1 -1
- package/dist/cli/commands/desktop/accessibility-snapshot.d.ts +3 -0
- package/dist/cli/commands/desktop/accessibility-snapshot.d.ts.map +1 -0
- package/dist/cli/commands/desktop/active-window.d.ts +3 -0
- package/dist/cli/commands/desktop/active-window.d.ts.map +1 -0
- package/dist/cli/commands/desktop/capture-desktop.d.ts +3 -0
- package/dist/cli/commands/desktop/capture-desktop.d.ts.map +1 -0
- package/dist/cli/commands/desktop/capture-window.d.ts +3 -0
- package/dist/cli/commands/desktop/capture-window.d.ts.map +1 -0
- package/dist/cli/commands/desktop/shared.d.ts +19 -0
- package/dist/cli/commands/desktop/shared.d.ts.map +1 -0
- package/dist/cli/commands/desktop/status.d.ts +3 -0
- package/dist/cli/commands/desktop/status.d.ts.map +1 -0
- package/dist/cli/commands/desktop/windows.d.ts +3 -0
- package/dist/cli/commands/desktop/windows.d.ts.map +1 -0
- package/dist/cli/commands/devtools/dialog.d.ts +19 -0
- package/dist/cli/commands/devtools/dialog.d.ts.map +1 -0
- package/dist/cli/commands/devtools/screencast-start.d.ts +20 -0
- package/dist/cli/commands/devtools/screencast-start.d.ts.map +1 -0
- package/dist/cli/commands/devtools/screencast-stop.d.ts +17 -0
- package/dist/cli/commands/devtools/screencast-stop.d.ts.map +1 -0
- package/dist/cli/commands/devtools/screenshot.d.ts +2 -0
- package/dist/cli/commands/devtools/screenshot.d.ts.map +1 -1
- package/dist/cli/commands/interact/click.d.ts.map +1 -1
- package/dist/cli/commands/interact/pointer-down.d.ts +7 -0
- package/dist/cli/commands/interact/pointer-down.d.ts.map +1 -0
- package/dist/cli/commands/interact/pointer-drag.d.ts +7 -0
- package/dist/cli/commands/interact/pointer-drag.d.ts.map +1 -0
- package/dist/cli/commands/interact/pointer-move.d.ts +7 -0
- package/dist/cli/commands/interact/pointer-move.d.ts.map +1 -0
- package/dist/cli/commands/interact/pointer-shared.d.ts +6 -0
- package/dist/cli/commands/interact/pointer-shared.d.ts.map +1 -0
- package/dist/cli/commands/interact/pointer-up.d.ts +7 -0
- package/dist/cli/commands/interact/pointer-up.d.ts.map +1 -0
- package/dist/cli/commands/interact/upload.d.ts +18 -0
- package/dist/cli/commands/interact/upload.d.ts.map +1 -0
- package/dist/cli/commands/macro-resolve.d.ts +2 -0
- package/dist/cli/commands/macro-resolve.d.ts.map +1 -1
- package/dist/cli/commands/native.d.ts +10 -7
- package/dist/cli/commands/native.d.ts.map +1 -1
- package/dist/cli/commands/nav/review.d.ts +7 -0
- package/dist/cli/commands/nav/review.d.ts.map +1 -0
- package/dist/cli/commands/nav/snapshot.d.ts.map +1 -1
- package/dist/cli/commands/pages/open.d.ts.map +1 -1
- package/dist/cli/commands/product-video.d.ts +2 -0
- package/dist/cli/commands/product-video.d.ts.map +1 -1
- package/dist/cli/commands/research.d.ts +3 -0
- package/dist/cli/commands/research.d.ts.map +1 -1
- package/dist/cli/commands/run.d.ts +14 -0
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/serve.d.ts +1 -26
- package/dist/cli/commands/serve.d.ts.map +1 -1
- package/dist/cli/commands/session/connect.d.ts.map +1 -1
- package/dist/cli/commands/session/disconnect.d.ts.map +1 -1
- package/dist/cli/commands/session/inspector.d.ts +21 -0
- package/dist/cli/commands/session/inspector.d.ts.map +1 -0
- package/dist/cli/commands/session/launch.d.ts.map +1 -1
- package/dist/cli/commands/shopping.d.ts +5 -0
- package/dist/cli/commands/shopping.d.ts.map +1 -1
- package/dist/cli/commands/status.d.ts +2 -14
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/targets/new.d.ts.map +1 -1
- package/dist/cli/daemon-autostart.d.ts +11 -0
- package/dist/cli/daemon-autostart.d.ts.map +1 -1
- package/dist/cli/daemon-client.d.ts +3 -0
- package/dist/cli/daemon-client.d.ts.map +1 -1
- package/dist/cli/daemon-commands.d.ts.map +1 -1
- package/dist/cli/daemon-state.d.ts +16 -0
- package/dist/cli/daemon-state.d.ts.map +1 -1
- package/dist/cli/daemon-status.d.ts +7 -2
- package/dist/cli/daemon-status.d.ts.map +1 -1
- package/dist/cli/daemon.d.ts +1 -0
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/help.d.ts +15 -4
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/index.js +2476 -1036
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/install-autostart-output.d.ts +6 -0
- package/dist/cli/install-autostart-output.d.ts.map +1 -0
- package/dist/cli/install-autostart-reconciliation.d.ts +23 -0
- package/dist/cli/install-autostart-reconciliation.d.ts.map +1 -0
- package/dist/cli/installers/skills.d.ts +42 -6
- package/dist/cli/installers/skills.d.ts.map +1 -1
- package/dist/cli/output.d.ts +3 -0
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/remote-desktop-runtime.d.ts +15 -0
- package/dist/cli/remote-desktop-runtime.d.ts.map +1 -0
- package/dist/cli/remote-manager.d.ts +24 -2
- package/dist/cli/remote-manager.d.ts.map +1 -1
- package/dist/cli/transport-timeouts.d.ts +8 -0
- package/dist/cli/transport-timeouts.d.ts.map +1 -0
- package/dist/cli/utils/http.d.ts +9 -0
- package/dist/cli/utils/http.d.ts.map +1 -1
- package/dist/cli/utils/parse.d.ts +2 -0
- package/dist/cli/utils/parse.d.ts.map +1 -1
- package/dist/cli/utils/skills.d.ts +1 -2
- package/dist/cli/utils/skills.d.ts.map +1 -1
- package/dist/cli/utils/workflow-message.d.ts +2 -0
- package/dist/cli/utils/workflow-message.d.ts.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/core/bootstrap.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/logging.d.ts +3 -1
- package/dist/core/logging.d.ts.map +1 -1
- package/dist/core/runtime-assemblies.d.ts +22 -0
- package/dist/core/runtime-assemblies.d.ts.map +1 -0
- package/dist/core/types.d.ts +15 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/desktop/audit.d.ts +37 -0
- package/dist/desktop/audit.d.ts.map +1 -0
- package/dist/desktop/errors.d.ts +7 -0
- package/dist/desktop/errors.d.ts.map +1 -0
- package/dist/desktop/index.d.ts +6 -0
- package/dist/desktop/index.d.ts.map +1 -0
- package/dist/desktop/runtime.d.ts +26 -0
- package/dist/desktop/runtime.d.ts.map +1 -0
- package/dist/desktop/types.d.ts +76 -0
- package/dist/desktop/types.d.ts.map +1 -0
- package/dist/extension-extractor.d.ts +6 -0
- package/dist/extension-extractor.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1103 -467
- package/dist/index.js.map +1 -1
- package/dist/integrations/figma/assets.d.ts +13 -0
- package/dist/integrations/figma/assets.d.ts.map +1 -0
- package/dist/integrations/figma/auth.d.ts +3 -0
- package/dist/integrations/figma/auth.d.ts.map +1 -0
- package/dist/integrations/figma/client.d.ts +42 -0
- package/dist/integrations/figma/client.d.ts.map +1 -0
- package/dist/integrations/figma/mappers.d.ts +23 -0
- package/dist/integrations/figma/mappers.d.ts.map +1 -0
- package/dist/integrations/figma/normalize.d.ts +99 -0
- package/dist/integrations/figma/normalize.d.ts.map +1 -0
- package/dist/integrations/figma/url.d.ts +17 -0
- package/dist/integrations/figma/url.d.ts.map +1 -0
- package/dist/integrations/figma/variables.d.ts +21 -0
- package/dist/integrations/figma/variables.d.ts.map +1 -0
- package/dist/macros/execute-runtime.d.ts +19 -0
- package/dist/macros/execute-runtime.d.ts.map +1 -0
- package/dist/macros/execute.d.ts +3 -1
- package/dist/macros/execute.d.ts.map +1 -1
- package/dist/opendevbrowser.d.ts.map +1 -1
- package/dist/opendevbrowser.js +1103 -467
- package/dist/opendevbrowser.js.map +1 -1
- package/dist/providers/blocker.d.ts.map +1 -1
- package/dist/providers/browser-fallback.d.ts +30 -0
- package/dist/providers/browser-fallback.d.ts.map +1 -0
- package/dist/providers/constraint.d.ts +45 -0
- package/dist/providers/constraint.d.ts.map +1 -0
- package/dist/providers/index.d.ts +11 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/policy.d.ts.map +1 -1
- package/dist/providers/product-video-compiler.d.ts +92 -0
- package/dist/providers/product-video-compiler.d.ts.map +1 -0
- package/dist/providers/registry.d.ts +37 -1
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/renderer.d.ts.map +1 -1
- package/dist/providers/research-compiler.d.ts +64 -0
- package/dist/providers/research-compiler.d.ts.map +1 -0
- package/dist/providers/research-executor.d.ts +27 -0
- package/dist/providers/research-executor.d.ts.map +1 -0
- package/dist/providers/runtime-bundle.d.ts +26 -0
- package/dist/providers/runtime-bundle.d.ts.map +1 -0
- package/dist/providers/runtime-factory.d.ts +6 -1
- package/dist/providers/runtime-factory.d.ts.map +1 -1
- package/dist/providers/runtime-policy.d.ts +24 -0
- package/dist/providers/runtime-policy.d.ts.map +1 -0
- package/dist/providers/shared/anti-bot-policy.d.ts +3 -2
- package/dist/providers/shared/anti-bot-policy.d.ts.map +1 -1
- package/dist/providers/shopping/index.d.ts +11 -1
- package/dist/providers/shopping/index.d.ts.map +1 -1
- package/dist/providers/shopping-compiler.d.ts +51 -0
- package/dist/providers/shopping-compiler.d.ts.map +1 -0
- package/dist/providers/shopping-executor.d.ts +18 -0
- package/dist/providers/shopping-executor.d.ts.map +1 -0
- package/dist/providers/shopping-postprocess.d.ts +46 -0
- package/dist/providers/shopping-postprocess.d.ts.map +1 -0
- package/dist/providers/shopping-workflow.d.ts +33 -0
- package/dist/providers/shopping-workflow.d.ts.map +1 -0
- package/dist/providers/social/platform.d.ts +2 -1
- package/dist/providers/social/platform.d.ts.map +1 -1
- package/dist/providers/social/search-quality.d.ts +16 -0
- package/dist/providers/social/search-quality.d.ts.map +1 -0
- package/dist/providers/social/youtube-resolver.d.ts +2 -1
- package/dist/providers/social/youtube-resolver.d.ts.map +1 -1
- package/dist/providers/social/youtube.d.ts.map +1 -1
- package/dist/providers/types.d.ts +116 -4
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/web/crawl-worker.d.ts.map +1 -1
- package/dist/providers/web/extract.d.ts +16 -0
- package/dist/providers/web/extract.d.ts.map +1 -1
- package/dist/providers/web/index.d.ts.map +1 -1
- package/dist/providers/workflow-contracts.d.ts +53 -0
- package/dist/providers/workflow-contracts.d.ts.map +1 -0
- package/dist/providers/workflows.d.ts +30 -6
- package/dist/providers/workflows.d.ts.map +1 -1
- package/dist/{providers-G3LRHQXX.js → providers-G36AM3Z2.js} +2 -2
- package/dist/public-surface/generated-manifest.d.ts +1168 -0
- package/dist/public-surface/generated-manifest.d.ts.map +1 -0
- package/dist/public-surface/source.d.ts +437 -0
- package/dist/public-surface/source.d.ts.map +1 -0
- package/dist/relay/protocol.d.ts +25 -3
- package/dist/relay/protocol.d.ts.map +1 -1
- package/dist/relay/relay-endpoints.d.ts +21 -0
- package/dist/relay/relay-endpoints.d.ts.map +1 -1
- package/dist/relay/relay-server.d.ts +18 -0
- package/dist/relay/relay-server.d.ts.map +1 -1
- package/dist/skills/bundled-skill-directories.d.ts +8 -0
- package/dist/skills/bundled-skill-directories.d.ts.map +1 -0
- package/dist/skills/skill-loader.d.ts +9 -1
- package/dist/skills/skill-loader.d.ts.map +1 -1
- package/dist/skills/skill-loader.js +7 -0
- package/dist/skills/skill-nudge.d.ts.map +1 -1
- package/dist/skills/types.d.ts +31 -0
- package/dist/skills/types.d.ts.map +1 -1
- package/dist/snapshot/ops-snapshot.d.ts +1 -1
- package/dist/snapshot/ops-snapshot.d.ts.map +1 -1
- package/dist/snapshot/refs.d.ts +6 -1
- package/dist/snapshot/refs.d.ts.map +1 -1
- package/dist/snapshot/snapshotter.d.ts.map +1 -1
- package/dist/tools/connect.d.ts.map +1 -1
- package/dist/tools/deps.d.ts +4 -0
- package/dist/tools/deps.d.ts.map +1 -1
- package/dist/tools/desktop-shared.d.ts +6 -0
- package/dist/tools/desktop-shared.d.ts.map +1 -0
- package/dist/tools/desktop_accessibility_snapshot.d.ts +4 -0
- package/dist/tools/desktop_accessibility_snapshot.d.ts.map +1 -0
- package/dist/tools/desktop_active_window.d.ts +4 -0
- package/dist/tools/desktop_active_window.d.ts.map +1 -0
- package/dist/tools/desktop_capture_desktop.d.ts +4 -0
- package/dist/tools/desktop_capture_desktop.d.ts.map +1 -0
- package/dist/tools/desktop_capture_window.d.ts +4 -0
- package/dist/tools/desktop_capture_window.d.ts.map +1 -0
- package/dist/tools/desktop_status.d.ts +4 -0
- package/dist/tools/desktop_status.d.ts.map +1 -0
- package/dist/tools/desktop_windows.d.ts +4 -0
- package/dist/tools/desktop_windows.d.ts.map +1 -0
- package/dist/tools/dialog.d.ts +4 -0
- package/dist/tools/dialog.d.ts.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/launch.d.ts.map +1 -1
- package/dist/tools/macro_resolve.d.ts.map +1 -1
- package/dist/tools/pointer_down.d.ts +4 -0
- package/dist/tools/pointer_down.d.ts.map +1 -0
- package/dist/tools/pointer_drag.d.ts +4 -0
- package/dist/tools/pointer_drag.d.ts.map +1 -0
- package/dist/tools/pointer_move.d.ts +4 -0
- package/dist/tools/pointer_move.d.ts.map +1 -0
- package/dist/tools/pointer_up.d.ts +4 -0
- package/dist/tools/pointer_up.d.ts.map +1 -0
- package/dist/tools/product_video_run.d.ts.map +1 -1
- package/dist/tools/prompting_guide.d.ts.map +1 -1
- package/dist/tools/research_run.d.ts.map +1 -1
- package/dist/tools/review.d.ts +4 -0
- package/dist/tools/review.d.ts.map +1 -0
- package/dist/tools/screencast_start.d.ts +4 -0
- package/dist/tools/screencast_start.d.ts.map +1 -0
- package/dist/tools/screencast_stop.d.ts +4 -0
- package/dist/tools/screencast_stop.d.ts.map +1 -0
- package/dist/tools/screenshot.d.ts.map +1 -1
- package/dist/tools/session_inspector.d.ts +4 -0
- package/dist/tools/session_inspector.d.ts.map +1 -0
- package/dist/tools/shopping_run.d.ts.map +1 -1
- package/dist/tools/skill_list.d.ts.map +1 -1
- package/dist/tools/skill_load.d.ts.map +1 -1
- package/dist/tools/upload.d.ts +4 -0
- package/dist/tools/upload.d.ts.map +1 -0
- package/dist/tools/workflow-runtime.d.ts +4 -1
- package/dist/tools/workflow-runtime.d.ts.map +1 -1
- package/dist/utils/package-assets.d.ts +4 -0
- package/dist/utils/package-assets.d.ts.map +1 -0
- package/extension/canvas.html +379 -9
- package/extension/dist/annotate-content.js +62 -32
- package/extension/dist/annotation-payload.js +57 -21
- package/extension/dist/background.js +406 -61
- package/extension/dist/canvas/canvas-runtime.js +481 -52
- package/extension/dist/canvas/model.js +129 -1
- package/extension/dist/canvas-page.js +1882 -74
- package/extension/dist/ops/dom-bridge.js +139 -0
- package/extension/dist/ops/ops-runtime.js +2854 -295
- package/extension/dist/ops/ops-session-store.js +83 -5
- package/extension/dist/ops/snapshot-builder.js +2 -2
- package/extension/dist/ops/snapshot-shared.js +2 -2
- package/extension/dist/ops/target-session-coordinator.js +5 -3
- package/extension/dist/popup.js +50 -15
- package/extension/dist/services/CDPRouter.js +1567 -63
- package/extension/dist/services/ConnectionManager.js +436 -78
- package/extension/dist/services/RelayClient.js +70 -30
- package/extension/dist/services/TabManager.js +83 -10
- package/extension/dist/services/TargetSessionMap.js +127 -3
- package/extension/dist/services/attach-errors.js +20 -0
- package/extension/dist/services/cdp-router-commands.js +135 -8
- package/extension/dist/services/url-restrictions.js +9 -13
- package/extension/manifest.json +2 -2
- package/extension/popup.html +7 -6
- package/package.json +15 -7
- package/skills/AGENTS.md +9 -8
- package/skills/opendevbrowser-best-practices/SKILL.md +118 -9
- package/skills/opendevbrowser-best-practices/artifacts/browser-agent-known-issues-matrix.md +1 -0
- package/skills/opendevbrowser-best-practices/artifacts/command-channel-reference.md +26 -12
- package/skills/opendevbrowser-best-practices/artifacts/parity-gates.md +9 -2
- package/skills/opendevbrowser-best-practices/artifacts/provider-workflows.md +6 -0
- package/skills/opendevbrowser-best-practices/artifacts/skill-runtime-surface-matrix.md +58 -0
- package/skills/opendevbrowser-best-practices/assets/templates/skill-runtime-pack-matrix.json +674 -0
- package/skills/opendevbrowser-best-practices/assets/templates/surface-audit-checklist.json +9 -4
- package/skills/opendevbrowser-best-practices/scripts/odb-workflow.sh +89 -20
- package/skills/opendevbrowser-best-practices/scripts/resolve-odb-cli.sh +100 -0
- package/skills/opendevbrowser-best-practices/scripts/run-robustness-audit.sh +1 -0
- package/skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh +256 -116
- package/skills/opendevbrowser-best-practices/scripts/validator-fixture-cli.sh +208 -0
- package/skills/opendevbrowser-continuity-ledger/SKILL.md +14 -1
- package/skills/opendevbrowser-continuity-ledger/scripts/validate-skill-assets.sh +61 -0
- package/skills/opendevbrowser-data-extraction/SKILL.md +6 -0
- package/skills/opendevbrowser-data-extraction/scripts/validate-skill-assets.sh +112 -0
- package/skills/opendevbrowser-design-agent/SKILL.md +275 -0
- package/skills/opendevbrowser-design-agent/artifacts/app-shell-and-state-wiring.md +84 -0
- package/skills/opendevbrowser-design-agent/artifacts/async-search-state-ownership.md +58 -0
- package/skills/opendevbrowser-design-agent/artifacts/component-pattern-index.md +130 -0
- package/skills/opendevbrowser-design-agent/artifacts/design-contract-playbook.md +157 -0
- package/skills/opendevbrowser-design-agent/artifacts/design-release-gate.md +40 -0
- package/skills/opendevbrowser-design-agent/artifacts/design-workflows.md +153 -0
- package/skills/opendevbrowser-design-agent/artifacts/existing-surface-adaptation.md +56 -0
- package/skills/opendevbrowser-design-agent/artifacts/external-pattern-synthesis.md +103 -0
- package/skills/opendevbrowser-design-agent/artifacts/frontend-evaluation-rubric.md +61 -0
- package/skills/opendevbrowser-design-agent/artifacts/implementation-anti-patterns.md +163 -0
- package/skills/opendevbrowser-design-agent/artifacts/isolated-preview-validation.md +68 -0
- package/skills/opendevbrowser-design-agent/artifacts/loading-and-feedback-surfaces.md +56 -0
- package/skills/opendevbrowser-design-agent/artifacts/opendevbrowser-ui-example-map.md +44 -0
- package/skills/opendevbrowser-design-agent/artifacts/performance-audit-playbook.md +70 -0
- package/skills/opendevbrowser-design-agent/artifacts/research-harvest-workflow.md +81 -0
- package/skills/opendevbrowser-design-agent/artifacts/scroll-reveal-surface-planning.md +64 -0
- package/skills/opendevbrowser-design-agent/artifacts/state-ownership-matrix.md +36 -0
- package/skills/opendevbrowser-design-agent/artifacts/theming-and-token-ownership.md +43 -0
- package/skills/opendevbrowser-design-agent/assets/templates/canvas-generation-plan.design.v1.json +58 -0
- package/skills/opendevbrowser-design-agent/assets/templates/design-audit-report.v1.md +34 -0
- package/skills/opendevbrowser-design-agent/assets/templates/design-brief.v1.md +40 -0
- package/skills/opendevbrowser-design-agent/assets/templates/design-contract.v1.json +226 -0
- package/skills/opendevbrowser-design-agent/assets/templates/design-release-gate.v1.json +35 -0
- package/skills/opendevbrowser-design-agent/assets/templates/design-review-checklist.json +57 -0
- package/skills/opendevbrowser-design-agent/assets/templates/real-surface-design-matrix.json +32 -0
- package/skills/opendevbrowser-design-agent/assets/templates/reference-pattern-board.v1.json +31 -0
- package/skills/opendevbrowser-design-agent/scripts/design-workflow.sh +171 -0
- package/skills/opendevbrowser-design-agent/scripts/extract-canvas-plan.sh +56 -0
- package/skills/opendevbrowser-design-agent/scripts/validate-skill-assets.sh +223 -0
- package/skills/opendevbrowser-form-testing/SKILL.md +19 -3
- package/skills/opendevbrowser-form-testing/artifacts/form-workflows.md +5 -4
- package/skills/opendevbrowser-form-testing/assets/templates/challenge-decision-tree.json +2 -0
- package/skills/opendevbrowser-form-testing/scripts/validate-skill-assets.sh +109 -0
- package/skills/opendevbrowser-login-automation/SKILL.md +21 -3
- package/skills/opendevbrowser-login-automation/artifacts/login-workflows.md +5 -4
- package/skills/opendevbrowser-login-automation/assets/templates/auth-signals.json +5 -0
- package/skills/opendevbrowser-login-automation/assets/templates/login-scenario-matrix.json +3 -2
- package/skills/opendevbrowser-login-automation/scripts/run-login-workflow.sh +17 -1
- package/skills/opendevbrowser-login-automation/scripts/validate-skill-assets.sh +133 -0
- package/skills/opendevbrowser-product-presentation-asset/SKILL.md +23 -11
- package/skills/opendevbrowser-product-presentation-asset/artifacts/asset-pack-assembly.md +5 -3
- package/skills/opendevbrowser-product-presentation-asset/assets/templates/shot-list.md +2 -0
- package/skills/opendevbrowser-product-presentation-asset/assets/templates/video-assembly.md +3 -2
- package/skills/opendevbrowser-product-presentation-asset/scripts/capture-screenshots.sh +5 -1
- package/skills/opendevbrowser-product-presentation-asset/scripts/collect-product.sh +6 -2
- package/skills/opendevbrowser-product-presentation-asset/scripts/download-images.sh +5 -1
- package/skills/opendevbrowser-product-presentation-asset/scripts/render-video-brief.sh +20 -7
- package/skills/opendevbrowser-product-presentation-asset/scripts/validate-skill-assets.sh +39 -0
- package/skills/opendevbrowser-product-presentation-asset/scripts/write-manifest.sh +5 -1
- package/skills/opendevbrowser-research/SKILL.md +14 -6
- package/skills/opendevbrowser-research/scripts/render-output.sh +5 -1
- package/skills/opendevbrowser-research/scripts/run-research.sh +5 -1
- package/skills/opendevbrowser-research/scripts/validate-skill-assets.sh +45 -0
- package/skills/opendevbrowser-research/scripts/write-artifacts.sh +5 -1
- package/skills/opendevbrowser-shopping/SKILL.md +20 -1
- package/skills/opendevbrowser-shopping/scripts/normalize-offers.sh +6 -2
- package/skills/opendevbrowser-shopping/scripts/run-deal-hunt.sh +5 -1
- package/skills/opendevbrowser-shopping/scripts/run-shopping.sh +5 -1
- package/skills/opendevbrowser-shopping/scripts/validate-skill-assets.sh +54 -0
- package/dist/chunk-5J3IFL3X.js +0 -16706
- package/dist/chunk-5J3IFL3X.js.map +0 -1
- package/dist/chunk-D633UO34.js +0 -8149
- package/dist/chunk-D633UO34.js.map +0 -1
- package/dist/chunk-V7KUDHDG.js +0 -276
- package/dist/chunk-V7KUDHDG.js.map +0 -1
- package/dist/runtime-factory-BICHDPE7.js +0 -13
- /package/dist/{providers-G3LRHQXX.js.map → providers-G36AM3Z2.js.map} +0 -0
- /package/dist/{runtime-factory-BICHDPE7.js.map → skills/skill-loader.js.map} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { summarizeCanvasProjectionState, normalizeCanvasSessionSummary, normalizeCanvasTargetStateSummaries } from "./canvas/model.js";
|
|
2
|
-
import { buildCanvasAnnotationPayload, describeAnnotationItem } from "./annotation-payload.js";
|
|
1
|
+
import { summarizeCanvasProjectionState, summarizeCanvasHistoryState, readLatestImportProvenance, readSelectedBindingIdentity, normalizeCanvasSessionSummary, normalizeCanvasTargetStateSummaries } from "./canvas/model.js";
|
|
2
|
+
import { buildCanvasAnnotationPayload, describeAnnotationItem, formatAnnotationDispatchReceipt } from "./annotation-payload.js";
|
|
3
3
|
import { DEFAULT_EDITOR_VIEWPORT, computeFittedViewport, computeViewportCanvasCenter, isDefaultEditorViewport } from "./canvas/viewport-fit.js";
|
|
4
4
|
const DB_NAME = "opendevbrowser-canvas";
|
|
5
5
|
const DB_VERSION = 2;
|
|
@@ -13,17 +13,66 @@ const metaElement = requiredElement("canvas-meta");
|
|
|
13
13
|
const toolbarMetaElement = requiredElement("canvas-toolbar-meta");
|
|
14
14
|
const summaryElement = requiredElement("canvas-summary");
|
|
15
15
|
const feedbackElement = requiredElement("canvas-feedback");
|
|
16
|
+
const pageDetailsElement = requiredElement("canvas-page-details");
|
|
17
|
+
const pageSelectElement = requiredElement("canvas-page-select");
|
|
18
|
+
const layersTreeElement = requiredElement("canvas-layers-tree");
|
|
16
19
|
const selectionMetaElement = requiredElement("canvas-selection-meta");
|
|
17
20
|
const stageElement = requiredElement("canvas-stage");
|
|
18
21
|
const stageInnerElement = requiredElement("canvas-stage-inner");
|
|
22
|
+
const stageOverlayElement = requiredElement("canvas-stage-overlay");
|
|
19
23
|
const stageMetaElement = requiredElement("canvas-stage-meta");
|
|
24
|
+
const stageHintElement = requiredElement("canvas-stage-hint");
|
|
20
25
|
const previewElement = requiredElement("canvas-preview");
|
|
21
26
|
const emptyElement = requiredElement("canvas-empty");
|
|
27
|
+
const historyUndoButton = requiredElement("canvas-history-undo");
|
|
28
|
+
const historyRedoButton = requiredElement("canvas-history-redo");
|
|
29
|
+
const panelHistoryUndoButton = requiredElement("canvas-history-panel-undo");
|
|
30
|
+
const panelHistoryRedoButton = requiredElement("canvas-history-panel-redo");
|
|
31
|
+
const historyStatusElement = requiredElement("canvas-history-status");
|
|
22
32
|
const nameInput = requiredElement("canvas-node-name");
|
|
23
33
|
const textInput = requiredElement("canvas-node-text");
|
|
34
|
+
const nodeXInput = requiredElement("canvas-node-x");
|
|
35
|
+
const nodeYInput = requiredElement("canvas-node-y");
|
|
36
|
+
const nodeWidthInput = requiredElement("canvas-node-width");
|
|
37
|
+
const nodeHeightInput = requiredElement("canvas-node-height");
|
|
38
|
+
const paddingInput = requiredElement("canvas-style-padding");
|
|
39
|
+
const gapInput = requiredElement("canvas-style-gap");
|
|
40
|
+
const fontSizeInput = requiredElement("canvas-style-font-size");
|
|
41
|
+
const fontWeightInput = requiredElement("canvas-style-font-weight");
|
|
42
|
+
const lineHeightInput = requiredElement("canvas-style-line-height");
|
|
43
|
+
const colorInput = requiredElement("canvas-style-color");
|
|
44
|
+
const backgroundInput = requiredElement("canvas-style-background");
|
|
45
|
+
const borderColorInput = requiredElement("canvas-style-border-color");
|
|
46
|
+
const borderWidthInput = requiredElement("canvas-style-border-width");
|
|
47
|
+
const borderRadiusInput = requiredElement("canvas-style-border-radius");
|
|
48
|
+
const shadowInput = requiredElement("canvas-style-shadow");
|
|
49
|
+
const bindingKindInput = requiredElement("canvas-binding-kind");
|
|
50
|
+
const bindingComponentInput = requiredElement("canvas-binding-component");
|
|
51
|
+
const bindingSelectorInput = requiredElement("canvas-binding-selector");
|
|
52
|
+
const a11yRoleInput = requiredElement("canvas-a11y-role");
|
|
53
|
+
const a11yLabelInput = requiredElement("canvas-a11y-label");
|
|
54
|
+
const propertiesStatusElement = requiredElement("canvas-properties-status");
|
|
55
|
+
const tokenStatusElement = requiredElement("canvas-token-status");
|
|
56
|
+
const tokenCollectionSelect = requiredElement("canvas-token-collection-select");
|
|
57
|
+
const tokenCollectionNameInput = requiredElement("canvas-token-collection-name");
|
|
58
|
+
const tokenCollectionCreateButton = requiredElement("canvas-token-collection-create");
|
|
59
|
+
const tokenModeSelect = requiredElement("canvas-token-mode-select");
|
|
60
|
+
const tokenModeNameInput = requiredElement("canvas-token-mode-name");
|
|
61
|
+
const tokenModeCreateButton = requiredElement("canvas-token-mode-create");
|
|
62
|
+
const tokenPathInput = requiredElement("canvas-token-path");
|
|
63
|
+
const tokenValueInput = requiredElement("canvas-token-value");
|
|
64
|
+
const tokenAliasInput = requiredElement("canvas-token-alias");
|
|
65
|
+
const tokenBindingPropertySelect = requiredElement("canvas-token-binding-property");
|
|
66
|
+
const tokenSaveButton = requiredElement("canvas-token-save");
|
|
67
|
+
const tokenBindButton = requiredElement("canvas-token-bind");
|
|
68
|
+
const tokenBindingClearButton = requiredElement("canvas-token-binding-clear");
|
|
69
|
+
const tokenSummaryElement = requiredElement("canvas-token-summary");
|
|
70
|
+
const tokenUsageElement = requiredElement("canvas-token-usage");
|
|
71
|
+
const duplicateNodeButton = requiredElement("canvas-duplicate-node");
|
|
24
72
|
const addNoteButton = requiredElement("canvas-add-note");
|
|
25
73
|
const resetViewButton = requiredElement("canvas-reset-view");
|
|
26
74
|
const deleteNodeButton = requiredElement("canvas-delete-node");
|
|
75
|
+
const annotationModeSelect = requiredElement("canvas-annotation-mode");
|
|
27
76
|
const annotationAddButton = requiredElement("canvas-annotation-add");
|
|
28
77
|
const annotationCopyButton = requiredElement("canvas-annotation-copy");
|
|
29
78
|
const annotationSendButton = requiredElement("canvas-annotation-send");
|
|
@@ -36,9 +85,18 @@ let currentTabId = null;
|
|
|
36
85
|
let databasePromise = null;
|
|
37
86
|
let persistTimer = null;
|
|
38
87
|
let fitViewportFrame = null;
|
|
88
|
+
let activePageId = null;
|
|
89
|
+
let annotationMode = "selected";
|
|
39
90
|
let annotationDrafts = [];
|
|
91
|
+
let expandedLayerNodeIds = new Set();
|
|
92
|
+
let selectedTokenCollectionId = "__values__";
|
|
93
|
+
let selectedTokenModeId = "__base__";
|
|
94
|
+
let selectedTokenPath = "";
|
|
40
95
|
let draggingNode = null;
|
|
96
|
+
let layerDragState = null;
|
|
41
97
|
let panningState = null;
|
|
98
|
+
let marqueeState = null;
|
|
99
|
+
let spacePanActive = false;
|
|
42
100
|
void bootstrap();
|
|
43
101
|
async function bootstrap() {
|
|
44
102
|
currentTabId = await getCurrentTabId();
|
|
@@ -62,8 +120,11 @@ async function bootstrap() {
|
|
|
62
120
|
port.postMessage({ type: "canvas-page-ready" });
|
|
63
121
|
bindStageInteractions();
|
|
64
122
|
bindInspector();
|
|
123
|
+
bindTokenPanel();
|
|
65
124
|
bindToolbar();
|
|
66
125
|
bindAnnotationPanel();
|
|
126
|
+
bindPageSelector();
|
|
127
|
+
bindKeyboardShortcuts();
|
|
67
128
|
document.addEventListener("visibilitychange", () => {
|
|
68
129
|
if (document.visibilityState === "hidden") {
|
|
69
130
|
flushPersist();
|
|
@@ -74,11 +135,24 @@ async function bootstrap() {
|
|
|
74
135
|
});
|
|
75
136
|
}
|
|
76
137
|
function bindToolbar() {
|
|
138
|
+
const handleUndo = () => {
|
|
139
|
+
requestHistory("undo");
|
|
140
|
+
};
|
|
141
|
+
const handleRedo = () => {
|
|
142
|
+
requestHistory("redo");
|
|
143
|
+
};
|
|
144
|
+
historyUndoButton.addEventListener("click", handleUndo);
|
|
145
|
+
historyRedoButton.addEventListener("click", handleRedo);
|
|
146
|
+
panelHistoryUndoButton.addEventListener("click", handleUndo);
|
|
147
|
+
panelHistoryRedoButton.addEventListener("click", handleRedo);
|
|
148
|
+
duplicateNodeButton.addEventListener("click", () => {
|
|
149
|
+
duplicateSelectedNode();
|
|
150
|
+
});
|
|
77
151
|
addNoteButton.addEventListener("click", () => {
|
|
78
152
|
if (!currentState || currentState.pendingMutation) {
|
|
79
153
|
return;
|
|
80
154
|
}
|
|
81
|
-
const page =
|
|
155
|
+
const page = getActivePage();
|
|
82
156
|
if (!page) {
|
|
83
157
|
return;
|
|
84
158
|
}
|
|
@@ -129,7 +203,15 @@ function bindToolbar() {
|
|
|
129
203
|
});
|
|
130
204
|
}
|
|
131
205
|
function bindAnnotationPanel() {
|
|
206
|
+
annotationModeSelect.addEventListener("change", () => {
|
|
207
|
+
annotationMode = annotationModeSelect.value === "region" ? "region" : "selected";
|
|
208
|
+
renderState();
|
|
209
|
+
});
|
|
132
210
|
annotationAddButton.addEventListener("click", () => {
|
|
211
|
+
if (annotationMode === "region") {
|
|
212
|
+
setCanvasButtonFeedback(annotationAddButton, "Drag on stage");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
133
215
|
addSelectedAnnotationDraft();
|
|
134
216
|
});
|
|
135
217
|
annotationCopyButton.addEventListener("click", () => {
|
|
@@ -145,6 +227,71 @@ function bindAnnotationPanel() {
|
|
|
145
227
|
});
|
|
146
228
|
});
|
|
147
229
|
}
|
|
230
|
+
function bindPageSelector() {
|
|
231
|
+
pageSelectElement.addEventListener("change", () => {
|
|
232
|
+
if (!currentState) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const pageId = pageSelectElement.value || null;
|
|
236
|
+
setActivePage(pageId, { clearSelectionIfMissing: true, broadcast: true });
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function bindKeyboardShortcuts() {
|
|
240
|
+
document.addEventListener("keydown", (event) => {
|
|
241
|
+
if (!currentState) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (event.key === " ") {
|
|
245
|
+
if (!isEditableTarget(event.target)) {
|
|
246
|
+
spacePanActive = true;
|
|
247
|
+
stageElement.dataset.mode = "panning";
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (isEditableTarget(event.target)) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const modifier = event.metaKey || event.ctrlKey;
|
|
256
|
+
if (modifier && event.key.toLowerCase() === "z") {
|
|
257
|
+
event.preventDefault();
|
|
258
|
+
requestHistory(event.shiftKey ? "redo" : "undo");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (modifier && event.key.toLowerCase() === "d") {
|
|
262
|
+
event.preventDefault();
|
|
263
|
+
duplicateSelectedNode();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if ((event.key === "Delete" || event.key === "Backspace") && currentState.selection.nodeId) {
|
|
267
|
+
event.preventDefault();
|
|
268
|
+
deleteNodeButton.click();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (event.key.startsWith("Arrow") && currentState.selection.nodeId && !event.altKey) {
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
nudgeSelectedNode(event.key, event.shiftKey ? 10 : 1);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (event.key === "0") {
|
|
277
|
+
event.preventDefault();
|
|
278
|
+
fitActivePageViewport();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (event.key === "1") {
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
resetZoomToDefault();
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
document.addEventListener("keyup", (event) => {
|
|
287
|
+
if (event.key === " ") {
|
|
288
|
+
spacePanActive = false;
|
|
289
|
+
if (!panningState) {
|
|
290
|
+
stageElement.dataset.mode = "";
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
148
295
|
function bindInspector() {
|
|
149
296
|
nameInput.addEventListener("change", () => {
|
|
150
297
|
const node = getSelectedNode();
|
|
@@ -168,22 +315,142 @@ function bindInspector() {
|
|
|
168
315
|
changes: { "props.text": textInput.value }
|
|
169
316
|
}], currentState.selection);
|
|
170
317
|
});
|
|
318
|
+
bindFieldCommit(nodeXInput, () => {
|
|
319
|
+
commitSelectedNodeChanges({ "rect.x": readNumberInput(nodeXInput, getSelectedNode()?.rect.x ?? 0) });
|
|
320
|
+
});
|
|
321
|
+
bindFieldCommit(nodeYInput, () => {
|
|
322
|
+
commitSelectedNodeChanges({ "rect.y": readNumberInput(nodeYInput, getSelectedNode()?.rect.y ?? 0) });
|
|
323
|
+
});
|
|
324
|
+
bindFieldCommit(nodeWidthInput, () => {
|
|
325
|
+
commitSelectedNodeChanges({ "rect.width": Math.max(readNumberInput(nodeWidthInput, getSelectedNode()?.rect.width ?? 1), 1) });
|
|
326
|
+
});
|
|
327
|
+
bindFieldCommit(nodeHeightInput, () => {
|
|
328
|
+
commitSelectedNodeChanges({ "rect.height": Math.max(readNumberInput(nodeHeightInput, getSelectedNode()?.rect.height ?? 1), 1) });
|
|
329
|
+
});
|
|
330
|
+
bindFieldCommit(paddingInput, () => {
|
|
331
|
+
commitSelectedNodeChanges({ "style.padding": readTextInput(paddingInput) });
|
|
332
|
+
});
|
|
333
|
+
bindFieldCommit(gapInput, () => {
|
|
334
|
+
commitSelectedNodeChanges({ "style.gap": readTextInput(gapInput) });
|
|
335
|
+
});
|
|
336
|
+
bindFieldCommit(fontSizeInput, () => {
|
|
337
|
+
commitSelectedNodeChanges({ "style.fontSize": readTextInput(fontSizeInput) });
|
|
338
|
+
});
|
|
339
|
+
bindFieldCommit(fontWeightInput, () => {
|
|
340
|
+
commitSelectedNodeChanges({ "style.fontWeight": readTextInput(fontWeightInput) });
|
|
341
|
+
});
|
|
342
|
+
bindFieldCommit(lineHeightInput, () => {
|
|
343
|
+
commitSelectedNodeChanges({ "style.lineHeight": readTextInput(lineHeightInput) });
|
|
344
|
+
});
|
|
345
|
+
bindFieldCommit(colorInput, () => {
|
|
346
|
+
commitSelectedNodeChanges({ "style.color": readTextInput(colorInput) });
|
|
347
|
+
});
|
|
348
|
+
bindFieldCommit(backgroundInput, () => {
|
|
349
|
+
commitSelectedNodeChanges({ "style.backgroundColor": readTextInput(backgroundInput) });
|
|
350
|
+
});
|
|
351
|
+
bindFieldCommit(borderColorInput, () => {
|
|
352
|
+
commitSelectedNodeChanges({ "style.borderColor": readTextInput(borderColorInput) });
|
|
353
|
+
});
|
|
354
|
+
bindFieldCommit(borderWidthInput, () => {
|
|
355
|
+
commitSelectedNodeChanges({ "style.borderWidth": readTextInput(borderWidthInput) });
|
|
356
|
+
});
|
|
357
|
+
bindFieldCommit(borderRadiusInput, () => {
|
|
358
|
+
commitSelectedNodeChanges({ "style.borderRadius": readTextInput(borderRadiusInput) });
|
|
359
|
+
});
|
|
360
|
+
bindFieldCommit(shadowInput, () => {
|
|
361
|
+
commitSelectedNodeChanges({ "style.boxShadow": readTextInput(shadowInput) });
|
|
362
|
+
});
|
|
363
|
+
bindFieldCommit(a11yRoleInput, () => {
|
|
364
|
+
commitSelectedNodeChanges({ "metadata.accessibility.role": readTextInput(a11yRoleInput) });
|
|
365
|
+
});
|
|
366
|
+
bindFieldCommit(a11yLabelInput, () => {
|
|
367
|
+
commitSelectedNodeChanges({ "metadata.accessibility.label": readTextInput(a11yLabelInput) });
|
|
368
|
+
});
|
|
369
|
+
bindFieldCommit(bindingKindInput, () => {
|
|
370
|
+
commitSelectedBindingPatch();
|
|
371
|
+
});
|
|
372
|
+
bindFieldCommit(bindingComponentInput, () => {
|
|
373
|
+
commitSelectedBindingPatch();
|
|
374
|
+
});
|
|
375
|
+
bindFieldCommit(bindingSelectorInput, () => {
|
|
376
|
+
commitSelectedBindingPatch();
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function bindTokenPanel() {
|
|
380
|
+
tokenCollectionSelect.addEventListener("change", () => {
|
|
381
|
+
selectedTokenCollectionId = tokenCollectionSelect.value || "__values__";
|
|
382
|
+
selectedTokenModeId = selectedTokenCollectionId === "__values__" ? "__base__" : tokenModeSelect.value || "__base__";
|
|
383
|
+
syncTokenEditorSelection();
|
|
384
|
+
renderTokenSummary();
|
|
385
|
+
});
|
|
386
|
+
tokenCollectionCreateButton.addEventListener("click", () => {
|
|
387
|
+
createTokenCollection();
|
|
388
|
+
});
|
|
389
|
+
tokenModeSelect.addEventListener("change", () => {
|
|
390
|
+
selectedTokenModeId = tokenModeSelect.value || "__base__";
|
|
391
|
+
updateActiveTokenMode();
|
|
392
|
+
syncTokenEditorSelection();
|
|
393
|
+
renderTokenSummary();
|
|
394
|
+
});
|
|
395
|
+
tokenModeCreateButton.addEventListener("click", () => {
|
|
396
|
+
createTokenMode();
|
|
397
|
+
});
|
|
398
|
+
tokenPathInput.addEventListener("input", () => {
|
|
399
|
+
selectedTokenPath = normalizeTokenPath(tokenPathInput.value);
|
|
400
|
+
renderTokenSummary();
|
|
401
|
+
});
|
|
402
|
+
tokenSaveButton.addEventListener("click", () => {
|
|
403
|
+
saveTokenEditor();
|
|
404
|
+
});
|
|
405
|
+
tokenBindButton.addEventListener("click", () => {
|
|
406
|
+
bindSelectedNodeToToken();
|
|
407
|
+
});
|
|
408
|
+
tokenBindingClearButton.addEventListener("click", () => {
|
|
409
|
+
clearSelectedTokenBinding();
|
|
410
|
+
});
|
|
171
411
|
}
|
|
172
412
|
function bindStageInteractions() {
|
|
173
413
|
stageElement.addEventListener("pointerdown", (event) => {
|
|
174
414
|
const target = event.target instanceof HTMLElement ? event.target.closest("[data-node-id]") : null;
|
|
415
|
+
const targetNodeId = target?.dataset.nodeId ?? null;
|
|
416
|
+
if (!currentState) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (annotationMode === "region" && event.button === 0) {
|
|
420
|
+
marqueeState = {
|
|
421
|
+
originClientX: event.clientX,
|
|
422
|
+
originClientY: event.clientY,
|
|
423
|
+
currentClientX: event.clientX,
|
|
424
|
+
currentClientY: event.clientY,
|
|
425
|
+
targetNodeId
|
|
426
|
+
};
|
|
427
|
+
renderStageOverlay();
|
|
428
|
+
stageElement.setPointerCapture(event.pointerId);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (spacePanActive) {
|
|
432
|
+
panningState = {
|
|
433
|
+
originX: event.clientX,
|
|
434
|
+
originY: event.clientY,
|
|
435
|
+
startX: currentState.viewport.x,
|
|
436
|
+
startY: currentState.viewport.y
|
|
437
|
+
};
|
|
438
|
+
stageElement.dataset.mode = "panning";
|
|
439
|
+
stageElement.setPointerCapture(event.pointerId);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
175
442
|
if (target) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
if (!node || !currentState) {
|
|
443
|
+
const node = targetNodeId ? findNode(currentState.document, targetNodeId) : null;
|
|
444
|
+
if (!node) {
|
|
179
445
|
return;
|
|
180
446
|
}
|
|
181
447
|
currentState.selection = {
|
|
182
|
-
pageId: node.pageId ??
|
|
448
|
+
pageId: node.pageId ?? getActivePage()?.id ?? null,
|
|
183
449
|
nodeId: node.id,
|
|
184
450
|
targetId: currentState.selection.targetId,
|
|
185
451
|
updatedAt: new Date().toISOString()
|
|
186
452
|
};
|
|
453
|
+
activePageId = node.pageId ?? activePageId;
|
|
187
454
|
draggingNode = {
|
|
188
455
|
nodeId: node.id,
|
|
189
456
|
originX: event.clientX,
|
|
@@ -196,9 +463,6 @@ function bindStageInteractions() {
|
|
|
196
463
|
stageElement.setPointerCapture(event.pointerId);
|
|
197
464
|
return;
|
|
198
465
|
}
|
|
199
|
-
if (!currentState) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
466
|
panningState = {
|
|
203
467
|
originX: event.clientX,
|
|
204
468
|
originY: event.clientY,
|
|
@@ -212,6 +476,12 @@ function bindStageInteractions() {
|
|
|
212
476
|
if (!currentState) {
|
|
213
477
|
return;
|
|
214
478
|
}
|
|
479
|
+
if (marqueeState) {
|
|
480
|
+
marqueeState.currentClientX = event.clientX;
|
|
481
|
+
marqueeState.currentClientY = event.clientY;
|
|
482
|
+
renderStageOverlay();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
215
485
|
if (draggingNode) {
|
|
216
486
|
const node = findNode(currentState.document, draggingNode.nodeId);
|
|
217
487
|
if (!node) {
|
|
@@ -237,10 +507,20 @@ function bindStageInteractions() {
|
|
|
237
507
|
if (!currentState) {
|
|
238
508
|
draggingNode = null;
|
|
239
509
|
panningState = null;
|
|
510
|
+
marqueeState = null;
|
|
240
511
|
stageElement.dataset.mode = "";
|
|
512
|
+
renderStageOverlay();
|
|
241
513
|
return;
|
|
242
514
|
}
|
|
243
|
-
if (
|
|
515
|
+
if (marqueeState) {
|
|
516
|
+
const completedMarquee = marqueeState;
|
|
517
|
+
marqueeState = null;
|
|
518
|
+
renderStageOverlay();
|
|
519
|
+
if (annotationMode === "region") {
|
|
520
|
+
commitAnnotationCapture(completedMarquee);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
else if (draggingNode && !currentState.pendingMutation) {
|
|
244
524
|
const node = findNode(currentState.document, draggingNode.nodeId);
|
|
245
525
|
if (node) {
|
|
246
526
|
applyOptimisticPatch([{
|
|
@@ -258,7 +538,7 @@ function bindStageInteractions() {
|
|
|
258
538
|
}
|
|
259
539
|
draggingNode = null;
|
|
260
540
|
panningState = null;
|
|
261
|
-
stageElement.dataset.mode = "";
|
|
541
|
+
stageElement.dataset.mode = spacePanActive ? "panning" : "";
|
|
262
542
|
};
|
|
263
543
|
stageElement.addEventListener("pointerup", endPointerInteraction);
|
|
264
544
|
stageElement.addEventListener("pointercancel", endPointerInteraction);
|
|
@@ -486,6 +766,10 @@ function runCanvasPageAction(selector, action) {
|
|
|
486
766
|
}
|
|
487
767
|
function applyState(state, persist) {
|
|
488
768
|
currentState = state;
|
|
769
|
+
if (!activePageId || !state.document.pages.some((page) => page.id === activePageId)) {
|
|
770
|
+
activePageId = state.selection.pageId ?? state.document.pages[0]?.id ?? null;
|
|
771
|
+
}
|
|
772
|
+
annotationModeSelect.value = annotationMode;
|
|
489
773
|
titleElement.textContent = state.title;
|
|
490
774
|
renderBadges(state);
|
|
491
775
|
renderMeta(`Document ${state.documentId} updated ${formatTimestamp(state.updatedAt)}`);
|
|
@@ -503,22 +787,32 @@ function applyState(state, persist) {
|
|
|
503
787
|
function renderState() {
|
|
504
788
|
if (!currentState) {
|
|
505
789
|
renderAnnotationPanel();
|
|
790
|
+
renderHistoryState();
|
|
791
|
+
renderTokenSummary();
|
|
506
792
|
return;
|
|
507
793
|
}
|
|
508
794
|
const projectionSummary = summarizeCanvasProjectionState(currentState.summary, currentState.targets);
|
|
795
|
+
const activePage = getActivePage();
|
|
509
796
|
const syncFragments = [
|
|
510
797
|
currentState.pendingMutation ? "sync pending" : "live",
|
|
511
798
|
currentState.summary.codeSyncState ?? null,
|
|
512
799
|
projectionSummary.conflictCount > 0 ? `${projectionSummary.conflictCount} conflict${projectionSummary.conflictCount === 1 ? "" : "s"}` : null
|
|
513
800
|
].filter((entry) => typeof entry === "string");
|
|
514
|
-
stageMetaElement.textContent = `${
|
|
801
|
+
stageMetaElement.textContent = `${activePage?.nodes.length ?? 0} nodes • ${syncFragments.join(" • ")}`;
|
|
802
|
+
stageHintElement.textContent = annotationMode === "region"
|
|
803
|
+
? "Drag on the stage to capture a region, or click a node to capture it."
|
|
804
|
+
: "Drag to pan. Scroll to zoom. Drag nodes to move them.";
|
|
805
|
+
renderPagesAndLayers();
|
|
806
|
+
renderHistoryState();
|
|
515
807
|
renderStage();
|
|
516
808
|
renderInspector();
|
|
809
|
+
renderTokenSummary();
|
|
517
810
|
syncAnnotationDrafts();
|
|
518
811
|
}
|
|
519
812
|
function renderBadges(state) {
|
|
520
813
|
badgesElement.innerHTML = "";
|
|
521
814
|
const projectionSummary = summarizeCanvasProjectionState(state.summary, state.targets);
|
|
815
|
+
const latestImport = readLatestImportProvenance(state.summary, state.document);
|
|
522
816
|
for (const label of [
|
|
523
817
|
state.previewState,
|
|
524
818
|
state.previewMode,
|
|
@@ -526,7 +820,8 @@ function renderBadges(state) {
|
|
|
526
820
|
state.pendingMutation ? "sync pending" : "synced",
|
|
527
821
|
state.summary.codeSyncState,
|
|
528
822
|
projectionSummary.activeProjections[0],
|
|
529
|
-
projectionSummary.conflictCount > 0 ? `${projectionSummary.conflictCount} conflicts` : null
|
|
823
|
+
projectionSummary.conflictCount > 0 ? `${projectionSummary.conflictCount} conflicts` : null,
|
|
824
|
+
latestImport ? `import ${latestImport}` : null
|
|
530
825
|
]) {
|
|
531
826
|
if (typeof label !== "string" || label.trim().length === 0) {
|
|
532
827
|
continue;
|
|
@@ -547,6 +842,8 @@ function renderSummary(summary, state) {
|
|
|
547
842
|
const stylingLibraries = formatSummaryList(readSummaryLibraryList(summary, "styling"));
|
|
548
843
|
const inventorySources = formatSummaryList(readSummaryStringArray(summary.componentSourceKinds));
|
|
549
844
|
const projectionSummary = summarizeCanvasProjectionState(state.summary, state.targets);
|
|
845
|
+
const appliedStarter = formatAppliedStarterSummary(summary, state.document);
|
|
846
|
+
const latestImport = readLatestImportProvenance(summary, state.document);
|
|
550
847
|
const items = [
|
|
551
848
|
["Target", state.targetId],
|
|
552
849
|
["Session", formatSummaryValue(summary.canvasSessionId)],
|
|
@@ -562,10 +859,14 @@ function renderSummary(summary, state) {
|
|
|
562
859
|
["Icons", iconLibraries],
|
|
563
860
|
["Styling", stylingLibraries],
|
|
564
861
|
["Inventory", `${formatSummaryValue(summary.componentInventoryCount)} mapped`],
|
|
862
|
+
["Starters", typeof summary.availableStarterCount === "number" ? `${summary.availableStarterCount} available` : "n/a"],
|
|
863
|
+
["Applied starter", appliedStarter],
|
|
565
864
|
["Inventory sources", inventorySources],
|
|
566
865
|
["Targets", String(state.targets.length)],
|
|
567
866
|
["Overlays", String(state.overlayMounts.length)],
|
|
568
|
-
["Feedback", String(countFeedbackItems(state.feedback))]
|
|
867
|
+
["Feedback", String(countFeedbackItems(state.feedback))],
|
|
868
|
+
["History", summarizeCanvasHistoryState(summary)],
|
|
869
|
+
["Latest import", latestImport ?? "none"]
|
|
569
870
|
];
|
|
570
871
|
for (const [label, value] of items) {
|
|
571
872
|
const row = document.createElement("div");
|
|
@@ -580,6 +881,21 @@ function renderSummary(summary, state) {
|
|
|
580
881
|
summaryElement.append(row);
|
|
581
882
|
}
|
|
582
883
|
}
|
|
884
|
+
function formatAppliedStarterSummary(summary, documentState) {
|
|
885
|
+
const starter = isRecord(documentState.meta.starter) ? documentState.meta.starter : null;
|
|
886
|
+
const name = summary.starterName
|
|
887
|
+
?? (typeof starter?.template === "object" && starter.template && typeof starter.template.name === "string"
|
|
888
|
+
? starter.template.name
|
|
889
|
+
: null);
|
|
890
|
+
if (!name) {
|
|
891
|
+
return "none";
|
|
892
|
+
}
|
|
893
|
+
const frameworkId = summary.starterFrameworkId
|
|
894
|
+
?? (typeof starter?.frameworkId === "string" ? starter.frameworkId : null);
|
|
895
|
+
const appliedAt = summary.starterAppliedAt
|
|
896
|
+
?? (typeof starter?.appliedAt === "string" ? starter.appliedAt : null);
|
|
897
|
+
return [name, frameworkId, appliedAt].filter((entry) => typeof entry === "string" && entry.trim().length > 0).join(" • ");
|
|
898
|
+
}
|
|
583
899
|
function renderFeedback(events) {
|
|
584
900
|
feedbackElement.innerHTML = "";
|
|
585
901
|
const latest = events.slice(-8).reverse();
|
|
@@ -613,25 +929,325 @@ function renderFeedback(events) {
|
|
|
613
929
|
feedbackElement.append(row);
|
|
614
930
|
}
|
|
615
931
|
}
|
|
932
|
+
function renderHistoryState() {
|
|
933
|
+
const history = currentState?.summary.history;
|
|
934
|
+
const canUndo = Boolean(history?.canUndo) && !currentState?.pendingMutation;
|
|
935
|
+
const canRedo = Boolean(history?.canRedo) && !currentState?.pendingMutation;
|
|
936
|
+
historyUndoButton.disabled = !canUndo;
|
|
937
|
+
historyRedoButton.disabled = !canRedo;
|
|
938
|
+
panelHistoryUndoButton.disabled = !canUndo;
|
|
939
|
+
panelHistoryRedoButton.disabled = !canRedo;
|
|
940
|
+
historyStatusElement.textContent = currentState ? summarizeCanvasHistoryState(currentState.summary) : "No history yet";
|
|
941
|
+
}
|
|
942
|
+
function renderPagesAndLayers() {
|
|
943
|
+
pageSelectElement.innerHTML = "";
|
|
944
|
+
layersTreeElement.innerHTML = "";
|
|
945
|
+
if (!currentState) {
|
|
946
|
+
pageDetailsElement.textContent = "Waiting for canvas state.";
|
|
947
|
+
const empty = document.createElement("div");
|
|
948
|
+
empty.className = "canvas-empty-inline";
|
|
949
|
+
empty.textContent = "No pages loaded.";
|
|
950
|
+
layersTreeElement.append(empty);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
const activePage = getActivePage();
|
|
954
|
+
for (const page of currentState.document.pages) {
|
|
955
|
+
const option = document.createElement("option");
|
|
956
|
+
option.value = page.id;
|
|
957
|
+
option.textContent = `${page.name} (${page.nodes.length})`;
|
|
958
|
+
option.selected = page.id === activePage?.id;
|
|
959
|
+
pageSelectElement.append(option);
|
|
960
|
+
}
|
|
961
|
+
pageSelectElement.disabled = currentState.document.pages.length <= 1;
|
|
962
|
+
if (!activePage) {
|
|
963
|
+
pageDetailsElement.textContent = "No page selected.";
|
|
964
|
+
const empty = document.createElement("div");
|
|
965
|
+
empty.className = "canvas-empty-inline";
|
|
966
|
+
empty.textContent = "No nodes on this page.";
|
|
967
|
+
layersTreeElement.append(empty);
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
ensureExpandedNodePath(activePage, currentState.selection.nodeId);
|
|
971
|
+
pageDetailsElement.textContent = `${activePage.path} • ${activePage.nodes.length} nodes`;
|
|
972
|
+
const rootNodes = getRootNodes(activePage);
|
|
973
|
+
if (rootNodes.length === 0) {
|
|
974
|
+
const empty = document.createElement("div");
|
|
975
|
+
empty.className = "canvas-empty-inline";
|
|
976
|
+
empty.textContent = "No nodes on this page.";
|
|
977
|
+
layersTreeElement.append(empty);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
for (const node of rootNodes) {
|
|
981
|
+
layersTreeElement.append(renderLayerNode(activePage, node, 0));
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
function renderLayerNode(page, node, depth) {
|
|
985
|
+
const row = document.createElement("div");
|
|
986
|
+
row.className = "canvas-layer-row";
|
|
987
|
+
row.dataset.nodeId = node.id;
|
|
988
|
+
row.dataset.selected = String(currentState?.selection.nodeId === node.id);
|
|
989
|
+
row.dataset.hidden = String(isNodeHidden(node));
|
|
990
|
+
row.draggable = true;
|
|
991
|
+
row.style.marginLeft = `${depth * 14}px`;
|
|
992
|
+
const head = document.createElement("div");
|
|
993
|
+
head.className = "canvas-layer-head";
|
|
994
|
+
const toggleButton = document.createElement("button");
|
|
995
|
+
toggleButton.className = "canvas-button canvas-layer-toggle";
|
|
996
|
+
toggleButton.type = "button";
|
|
997
|
+
toggleButton.textContent = node.childIds.length > 0 ? (expandedLayerNodeIds.has(node.id) ? "−" : "+") : "·";
|
|
998
|
+
toggleButton.disabled = node.childIds.length === 0;
|
|
999
|
+
toggleButton.addEventListener("click", (event) => {
|
|
1000
|
+
event.stopPropagation();
|
|
1001
|
+
if (expandedLayerNodeIds.has(node.id)) {
|
|
1002
|
+
expandedLayerNodeIds.delete(node.id);
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
expandedLayerNodeIds.add(node.id);
|
|
1006
|
+
}
|
|
1007
|
+
renderPagesAndLayers();
|
|
1008
|
+
});
|
|
1009
|
+
const visibilityButton = document.createElement("button");
|
|
1010
|
+
visibilityButton.className = "canvas-button canvas-layer-visibility";
|
|
1011
|
+
visibilityButton.type = "button";
|
|
1012
|
+
visibilityButton.textContent = isNodeHidden(node) ? "🙈" : "👁";
|
|
1013
|
+
visibilityButton.addEventListener("click", (event) => {
|
|
1014
|
+
event.stopPropagation();
|
|
1015
|
+
if (!currentState?.pendingMutation) {
|
|
1016
|
+
applyOptimisticPatch([{ op: "node.visibility.set", nodeId: node.id, hidden: !isNodeHidden(node) }], {
|
|
1017
|
+
pageId: page.id,
|
|
1018
|
+
nodeId: node.id,
|
|
1019
|
+
targetId: currentState?.selection.targetId ?? null
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
const main = document.createElement("div");
|
|
1024
|
+
main.className = "canvas-layer-main";
|
|
1025
|
+
main.addEventListener("click", () => {
|
|
1026
|
+
selectNode(node.id, page.id);
|
|
1027
|
+
});
|
|
1028
|
+
const renameInput = document.createElement("input");
|
|
1029
|
+
renameInput.className = "canvas-layer-name-input";
|
|
1030
|
+
renameInput.value = node.name;
|
|
1031
|
+
renameInput.disabled = Boolean(currentState?.pendingMutation);
|
|
1032
|
+
renameInput.addEventListener("click", (event) => {
|
|
1033
|
+
event.stopPropagation();
|
|
1034
|
+
});
|
|
1035
|
+
renameInput.addEventListener("change", () => {
|
|
1036
|
+
if (!currentState?.pendingMutation && renameInput.value.trim().length > 0 && renameInput.value !== node.name) {
|
|
1037
|
+
applyOptimisticPatch([{ op: "node.update", nodeId: node.id, changes: { name: renameInput.value.trim() } }], {
|
|
1038
|
+
pageId: page.id,
|
|
1039
|
+
nodeId: node.id,
|
|
1040
|
+
targetId: currentState?.selection.targetId ?? null
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
const meta = document.createElement("div");
|
|
1045
|
+
meta.className = "canvas-layer-meta";
|
|
1046
|
+
meta.textContent = [node.kind, node.childIds.length > 0 ? `${node.childIds.length} children` : "leaf", isNodeHidden(node) ? "hidden" : "visible"].join(" • ");
|
|
1047
|
+
main.append(renameInput, meta);
|
|
1048
|
+
head.append(toggleButton, visibilityButton, main);
|
|
1049
|
+
row.append(head);
|
|
1050
|
+
row.addEventListener("dragstart", (event) => {
|
|
1051
|
+
layerDragState = { nodeId: node.id };
|
|
1052
|
+
event.dataTransfer?.setData("text/plain", node.id);
|
|
1053
|
+
});
|
|
1054
|
+
row.addEventListener("dragend", () => {
|
|
1055
|
+
layerDragState = null;
|
|
1056
|
+
});
|
|
1057
|
+
row.addEventListener("dragover", (event) => {
|
|
1058
|
+
event.preventDefault();
|
|
1059
|
+
});
|
|
1060
|
+
row.addEventListener("drop", (event) => {
|
|
1061
|
+
event.preventDefault();
|
|
1062
|
+
const draggedId = layerDragState?.nodeId;
|
|
1063
|
+
if (!draggedId || draggedId === node.id) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
commitLayerMove(draggedId, node.parentId ?? null, findSiblingInsertIndex(page, node.id));
|
|
1067
|
+
});
|
|
1068
|
+
if (node.childIds.length > 0) {
|
|
1069
|
+
const children = document.createElement("div");
|
|
1070
|
+
children.className = "canvas-layer-children";
|
|
1071
|
+
children.addEventListener("dragover", (event) => {
|
|
1072
|
+
event.preventDefault();
|
|
1073
|
+
});
|
|
1074
|
+
children.addEventListener("drop", (event) => {
|
|
1075
|
+
event.preventDefault();
|
|
1076
|
+
const draggedId = layerDragState?.nodeId;
|
|
1077
|
+
if (!draggedId || draggedId === node.id) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
commitLayerMove(draggedId, node.id, node.childIds.length);
|
|
1081
|
+
});
|
|
1082
|
+
if (expandedLayerNodeIds.has(node.id)) {
|
|
1083
|
+
for (const childId of node.childIds) {
|
|
1084
|
+
const child = page.nodes.find((entry) => entry.id === childId);
|
|
1085
|
+
if (child) {
|
|
1086
|
+
children.append(renderLayerNode(page, child, depth + 1));
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
row.append(children);
|
|
1091
|
+
}
|
|
1092
|
+
return row;
|
|
1093
|
+
}
|
|
1094
|
+
function renderTokenSummary() {
|
|
1095
|
+
populateTokenControls();
|
|
1096
|
+
tokenSummaryElement.innerHTML = "";
|
|
1097
|
+
tokenUsageElement.innerHTML = "";
|
|
1098
|
+
const controlsDisabled = !currentState || Boolean(currentState.pendingMutation);
|
|
1099
|
+
for (const control of [
|
|
1100
|
+
tokenCollectionSelect,
|
|
1101
|
+
tokenCollectionNameInput,
|
|
1102
|
+
tokenCollectionCreateButton,
|
|
1103
|
+
tokenModeSelect,
|
|
1104
|
+
tokenModeNameInput,
|
|
1105
|
+
tokenModeCreateButton,
|
|
1106
|
+
tokenPathInput,
|
|
1107
|
+
tokenValueInput,
|
|
1108
|
+
tokenAliasInput,
|
|
1109
|
+
tokenBindingPropertySelect,
|
|
1110
|
+
tokenSaveButton,
|
|
1111
|
+
tokenBindButton,
|
|
1112
|
+
tokenBindingClearButton
|
|
1113
|
+
]) {
|
|
1114
|
+
control.disabled = controlsDisabled;
|
|
1115
|
+
}
|
|
1116
|
+
if (!currentState) {
|
|
1117
|
+
tokenStatusElement.textContent = "Waiting for canvas state...";
|
|
1118
|
+
const empty = document.createElement("div");
|
|
1119
|
+
empty.className = "canvas-empty-inline";
|
|
1120
|
+
empty.textContent = "Waiting for canvas state...";
|
|
1121
|
+
tokenSummaryElement.append(empty.cloneNode(true));
|
|
1122
|
+
tokenUsageElement.append(empty);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
const node = getSelectedNode();
|
|
1126
|
+
const normalizedPath = normalizeTokenPath(selectedTokenPath || tokenPathInput.value);
|
|
1127
|
+
const resolvedValue = normalizedPath
|
|
1128
|
+
? resolveTokenValue(currentState.document.tokens, normalizedPath, selectedTokenModeId === "__base__" ? null : selectedTokenModeId)
|
|
1129
|
+
: null;
|
|
1130
|
+
const aliasTarget = normalizedPath
|
|
1131
|
+
? readTokenAliasTarget(currentState.document.tokens, normalizedPath, selectedTokenModeId === "__base__" ? null : selectedTokenModeId)
|
|
1132
|
+
: null;
|
|
1133
|
+
const bindingProperty = tokenBindingPropertySelect.value || "backgroundColor";
|
|
1134
|
+
tokenPathInput.value = selectedTokenPath;
|
|
1135
|
+
tokenValueInput.value = formatTokenEditorValue(resolvedValue);
|
|
1136
|
+
tokenAliasInput.value = aliasTarget ?? "";
|
|
1137
|
+
tokenBindButton.disabled = controlsDisabled || !node || normalizedPath.length === 0;
|
|
1138
|
+
tokenBindingClearButton.disabled = controlsDisabled || !node || !hasSelectedTokenBinding(node, bindingProperty);
|
|
1139
|
+
tokenStatusElement.textContent = normalizedPath
|
|
1140
|
+
? aliasTarget
|
|
1141
|
+
? `${normalizedPath} aliases ${aliasTarget}${resolvedValue !== null ? ` • ${formatTokenEditorValue(resolvedValue)}` : ""}`
|
|
1142
|
+
: `${normalizedPath}${resolvedValue !== null ? ` • ${formatTokenEditorValue(resolvedValue)}` : ""}`
|
|
1143
|
+
: "Edit collections, modes, aliases, and bindings.";
|
|
1144
|
+
if (!node) {
|
|
1145
|
+
const empty = document.createElement("div");
|
|
1146
|
+
empty.className = "canvas-empty-inline";
|
|
1147
|
+
empty.textContent = "Select a node to inspect token usage.";
|
|
1148
|
+
tokenSummaryElement.append(empty);
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
const tokenRefs = Object.entries(isRecord(node.tokenRefs) ? node.tokenRefs : {});
|
|
1152
|
+
if (tokenRefs.length === 0) {
|
|
1153
|
+
const empty = document.createElement("div");
|
|
1154
|
+
empty.className = "canvas-empty-inline";
|
|
1155
|
+
empty.textContent = "No token bindings on this node.";
|
|
1156
|
+
tokenSummaryElement.append(empty);
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
for (const [property, value] of tokenRefs) {
|
|
1160
|
+
const row = document.createElement("div");
|
|
1161
|
+
row.className = "canvas-token-item";
|
|
1162
|
+
const title = document.createElement("div");
|
|
1163
|
+
title.className = "canvas-summary-label";
|
|
1164
|
+
title.textContent = property;
|
|
1165
|
+
const body = document.createElement("div");
|
|
1166
|
+
body.className = "canvas-summary-value";
|
|
1167
|
+
const tokenPath = readTokenPath(value);
|
|
1168
|
+
const resolved = tokenPath
|
|
1169
|
+
? resolveTokenValue(currentState.document.tokens, tokenPath, readActiveTokenModeId(currentState.document.tokens))
|
|
1170
|
+
: null;
|
|
1171
|
+
body.textContent = tokenPath
|
|
1172
|
+
? resolved !== null ? `${tokenPath} → ${formatTokenEditorValue(resolved)}` : tokenPath
|
|
1173
|
+
: JSON.stringify(value);
|
|
1174
|
+
row.append(title, body);
|
|
1175
|
+
tokenSummaryElement.append(row);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
const usages = normalizedPath ? collectTokenUsages(currentState.document, normalizedPath) : [];
|
|
1180
|
+
if (usages.length === 0) {
|
|
1181
|
+
const empty = document.createElement("div");
|
|
1182
|
+
empty.className = "canvas-empty-inline";
|
|
1183
|
+
empty.textContent = normalizedPath ? "No bound nodes use this token yet." : "Choose a token path to inspect usage.";
|
|
1184
|
+
tokenUsageElement.append(empty);
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
for (const usage of usages) {
|
|
1188
|
+
const row = document.createElement("div");
|
|
1189
|
+
row.className = "canvas-summary-item";
|
|
1190
|
+
const title = document.createElement("div");
|
|
1191
|
+
title.className = "canvas-summary-label";
|
|
1192
|
+
title.textContent = `${usage.pageName} • ${usage.property}`;
|
|
1193
|
+
const body = document.createElement("div");
|
|
1194
|
+
body.className = "canvas-summary-value";
|
|
1195
|
+
body.textContent = `${usage.nodeName} (${usage.nodeId})${usage.resolvedValue !== null ? ` → ${formatTokenEditorValue(usage.resolvedValue)}` : ""}`;
|
|
1196
|
+
row.append(title, body);
|
|
1197
|
+
tokenUsageElement.append(row);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function populateTokenControls() {
|
|
1201
|
+
if (!currentState) {
|
|
1202
|
+
tokenCollectionSelect.innerHTML = "";
|
|
1203
|
+
tokenModeSelect.innerHTML = "";
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const tokens = currentState.document.tokens;
|
|
1207
|
+
const collections = [
|
|
1208
|
+
{ id: "__values__", name: "Values" },
|
|
1209
|
+
...tokens.collections.map((collection) => ({ id: collection.id, name: collection.name }))
|
|
1210
|
+
];
|
|
1211
|
+
if (!collections.some((entry) => entry.id === selectedTokenCollectionId)) {
|
|
1212
|
+
selectedTokenCollectionId = findTokenCollectionId(tokens, selectedTokenPath) ?? "__values__";
|
|
1213
|
+
}
|
|
1214
|
+
tokenCollectionSelect.innerHTML = collections
|
|
1215
|
+
.map((collection) => `<option value="${escapeHtmlAttribute(collection.id)}">${escapeHtmlText(collection.name)}</option>`)
|
|
1216
|
+
.join("");
|
|
1217
|
+
tokenCollectionSelect.value = selectedTokenCollectionId;
|
|
1218
|
+
const modes = listTokenModes(tokens, selectedTokenCollectionId);
|
|
1219
|
+
if (!modes.some((entry) => entry.id === selectedTokenModeId)) {
|
|
1220
|
+
const activeModeId = readActiveTokenModeId(tokens);
|
|
1221
|
+
selectedTokenModeId = activeModeId && modes.some((entry) => entry.id === activeModeId) ? activeModeId : "__base__";
|
|
1222
|
+
}
|
|
1223
|
+
tokenModeSelect.innerHTML = modes
|
|
1224
|
+
.map((mode) => `<option value="${escapeHtmlAttribute(mode.id)}">${escapeHtmlText(mode.name)}</option>`)
|
|
1225
|
+
.join("");
|
|
1226
|
+
tokenModeSelect.value = selectedTokenModeId;
|
|
1227
|
+
}
|
|
616
1228
|
function renderStage() {
|
|
617
1229
|
if (!currentState) {
|
|
618
1230
|
stageInnerElement.innerHTML = "";
|
|
1231
|
+
renderStageOverlay();
|
|
619
1232
|
return;
|
|
620
1233
|
}
|
|
621
|
-
const page =
|
|
1234
|
+
const page = getActivePage();
|
|
622
1235
|
if (!page) {
|
|
623
1236
|
stageInnerElement.innerHTML = "";
|
|
1237
|
+
renderStageOverlay();
|
|
624
1238
|
return;
|
|
625
1239
|
}
|
|
626
|
-
const
|
|
1240
|
+
const visibleNodes = page.nodes.filter((node) => !isNodeHidden(node));
|
|
1241
|
+
const bounds = computeDocumentBounds(visibleNodes);
|
|
627
1242
|
stageInnerElement.style.width = `${bounds.width}px`;
|
|
628
1243
|
stageInnerElement.style.height = `${bounds.height}px`;
|
|
629
1244
|
stageInnerElement.style.transform = `translate(${currentState.viewport.x}px, ${currentState.viewport.y}px) scale(${currentState.viewport.zoom})`;
|
|
630
1245
|
stageInnerElement.innerHTML = "";
|
|
631
|
-
const sortedNodes = [...
|
|
1246
|
+
const sortedNodes = [...visibleNodes].sort(compareStageNodes);
|
|
632
1247
|
for (const node of sortedNodes) {
|
|
633
1248
|
stageInnerElement.append(buildStageNodeElement(currentState.document, node, currentState.selection.nodeId === node.id));
|
|
634
1249
|
}
|
|
1250
|
+
renderStageOverlay();
|
|
635
1251
|
}
|
|
636
1252
|
function compareStageNodes(left, right) {
|
|
637
1253
|
const rootOrder = Number(left.parentId !== null) - Number(right.parentId !== null);
|
|
@@ -682,7 +1298,7 @@ function buildStageNodeElement(documentState, node, selected) {
|
|
|
682
1298
|
element.style.top = `${node.rect.y}px`;
|
|
683
1299
|
element.style.width = `${Math.max(node.rect.width, 40)}px`;
|
|
684
1300
|
element.style.minHeight = `${Math.max(node.rect.height, node.kind === "connector" ? 2 : componentKind === "badge" ? 28 : 40)}px`;
|
|
685
|
-
applyDeclaredStageStyles(element, node
|
|
1301
|
+
applyDeclaredStageStyles(element, documentState, node);
|
|
686
1302
|
if (text.includes("\n") && element.style.whiteSpace.length === 0) {
|
|
687
1303
|
element.style.whiteSpace = "pre-line";
|
|
688
1304
|
}
|
|
@@ -1083,8 +1699,8 @@ function buildStageFluentIcon(identifier) {
|
|
|
1083
1699
|
}
|
|
1084
1700
|
}
|
|
1085
1701
|
}
|
|
1086
|
-
function applyDeclaredStageStyles(element,
|
|
1087
|
-
for (const [key, value] of Object.entries(
|
|
1702
|
+
function applyDeclaredStageStyles(element, documentState, node) {
|
|
1703
|
+
for (const [key, value] of Object.entries(resolveStageStyle(documentState, node))) {
|
|
1088
1704
|
if (typeof value !== "string" && typeof value !== "number") {
|
|
1089
1705
|
continue;
|
|
1090
1706
|
}
|
|
@@ -1095,20 +1711,84 @@ function applyDeclaredStageStyles(element, style) {
|
|
|
1095
1711
|
}
|
|
1096
1712
|
function renderInspector() {
|
|
1097
1713
|
const node = getSelectedNode();
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1714
|
+
const disabled = !node || Boolean(currentState?.pendingMutation);
|
|
1715
|
+
const inputs = [
|
|
1716
|
+
nameInput,
|
|
1717
|
+
textInput,
|
|
1718
|
+
nodeXInput,
|
|
1719
|
+
nodeYInput,
|
|
1720
|
+
nodeWidthInput,
|
|
1721
|
+
nodeHeightInput,
|
|
1722
|
+
paddingInput,
|
|
1723
|
+
gapInput,
|
|
1724
|
+
fontSizeInput,
|
|
1725
|
+
fontWeightInput,
|
|
1726
|
+
lineHeightInput,
|
|
1727
|
+
colorInput,
|
|
1728
|
+
backgroundInput,
|
|
1729
|
+
borderColorInput,
|
|
1730
|
+
borderWidthInput,
|
|
1731
|
+
borderRadiusInput,
|
|
1732
|
+
shadowInput,
|
|
1733
|
+
bindingKindInput,
|
|
1734
|
+
bindingComponentInput,
|
|
1735
|
+
bindingSelectorInput,
|
|
1736
|
+
a11yRoleInput,
|
|
1737
|
+
a11yLabelInput
|
|
1738
|
+
];
|
|
1739
|
+
for (const input of inputs) {
|
|
1740
|
+
input.disabled = disabled;
|
|
1741
|
+
}
|
|
1742
|
+
duplicateNodeButton.disabled = disabled;
|
|
1743
|
+
deleteNodeButton.disabled = disabled;
|
|
1744
|
+
propertiesStatusElement.textContent = node
|
|
1745
|
+
? `${node.kind} • ${node.rect.width}×${node.rect.height}`
|
|
1746
|
+
: "No node selected.";
|
|
1101
1747
|
nameInput.value = node?.name ?? "";
|
|
1102
1748
|
textInput.value = node ? nodeText(node) : "";
|
|
1749
|
+
nodeXInput.value = node ? String(node.rect.x) : "";
|
|
1750
|
+
nodeYInput.value = node ? String(node.rect.y) : "";
|
|
1751
|
+
nodeWidthInput.value = node ? String(node.rect.width) : "";
|
|
1752
|
+
nodeHeightInput.value = node ? String(node.rect.height) : "";
|
|
1753
|
+
paddingInput.value = node ? readStyleText(node.style.padding) : "";
|
|
1754
|
+
gapInput.value = node ? readStyleText(node.style.gap) : "";
|
|
1755
|
+
fontSizeInput.value = node ? readStyleText(node.style.fontSize) : "";
|
|
1756
|
+
fontWeightInput.value = node ? readStyleText(node.style.fontWeight) : "";
|
|
1757
|
+
lineHeightInput.value = node ? readStyleText(node.style.lineHeight) : "";
|
|
1758
|
+
colorInput.value = node ? readStyleText(node.style.color) : "";
|
|
1759
|
+
backgroundInput.value = node ? readStyleText(node.style.backgroundColor) : "";
|
|
1760
|
+
borderColorInput.value = node ? readStyleText(node.style.borderColor) : "";
|
|
1761
|
+
borderWidthInput.value = node ? readStyleText(node.style.borderWidth) : "";
|
|
1762
|
+
borderRadiusInput.value = node ? readStyleText(node.style.borderRadius) : "";
|
|
1763
|
+
shadowInput.value = node ? readStyleText(node.style.boxShadow) : "";
|
|
1764
|
+
const binding = currentState && node ? readSelectedBindingIdentity(currentState.document, node.id) : null;
|
|
1765
|
+
bindingKindInput.value = binding?.bindingKind ?? "";
|
|
1766
|
+
bindingComponentInput.value = binding?.componentName ?? "";
|
|
1767
|
+
bindingSelectorInput.value = node && currentState
|
|
1768
|
+
? readBindingSelector(currentState.document, node.id)
|
|
1769
|
+
: "";
|
|
1770
|
+
const accessibility = node && isRecord(node.metadata.accessibility) ? node.metadata.accessibility : {};
|
|
1771
|
+
a11yRoleInput.value = typeof accessibility.role === "string" ? accessibility.role : "";
|
|
1772
|
+
a11yLabelInput.value = typeof accessibility.label === "string" ? accessibility.label : "";
|
|
1103
1773
|
renderSelectionMeta();
|
|
1774
|
+
syncTokenEditorSelection();
|
|
1104
1775
|
}
|
|
1105
1776
|
function renderSelectionMeta() {
|
|
1106
1777
|
selectionMetaElement.innerHTML = "";
|
|
1107
1778
|
const node = getSelectedNode();
|
|
1779
|
+
const bindingIdentity = currentState ? readSelectedBindingIdentity(currentState.document, node?.id ?? null) : null;
|
|
1780
|
+
const latestImport = currentState ? readLatestImportProvenance(currentState.summary, currentState.document) : null;
|
|
1108
1781
|
const items = [
|
|
1109
1782
|
["Selected", node?.id ?? "none"],
|
|
1783
|
+
["Page", getActivePage()?.name ?? "n/a"],
|
|
1110
1784
|
["Position", node ? `${node.rect.x}, ${node.rect.y}` : "n/a"],
|
|
1111
|
-
["Size", node ? `${node.rect.width} × ${node.rect.height}` : "n/a"]
|
|
1785
|
+
["Size", node ? `${node.rect.width} × ${node.rect.height}` : "n/a"],
|
|
1786
|
+
["Binding", bindingIdentity?.componentName ?? "none"],
|
|
1787
|
+
["Framework", bindingIdentity?.framework ?? "n/a"],
|
|
1788
|
+
["Adapter", bindingIdentity?.adapter ?? "n/a"],
|
|
1789
|
+
["Plugin", bindingIdentity?.plugin ?? "n/a"],
|
|
1790
|
+
["Variants", node ? String(node.variantPatches.length) : "0"],
|
|
1791
|
+
["Latest import", latestImport ?? "none"]
|
|
1112
1792
|
];
|
|
1113
1793
|
for (const [label, value] of items) {
|
|
1114
1794
|
const row = document.createElement("div");
|
|
@@ -1125,7 +1805,10 @@ function renderSelectionMeta() {
|
|
|
1125
1805
|
}
|
|
1126
1806
|
function renderAnnotationPanel() {
|
|
1127
1807
|
const node = getSelectedNode();
|
|
1128
|
-
annotationAddButton.
|
|
1808
|
+
annotationAddButton.textContent = annotationMode === "region" ? "Capture Region" : "Add Selected";
|
|
1809
|
+
annotationAddButton.disabled = annotationMode === "selected"
|
|
1810
|
+
? (!node || !currentState || annotationDrafts.some((entry) => entry.kind !== "region" && entry.nodeId === node.id))
|
|
1811
|
+
: !currentState;
|
|
1129
1812
|
annotationCopyButton.disabled = annotationDrafts.length === 0 || !currentState;
|
|
1130
1813
|
annotationSendButton.disabled = annotationDrafts.length === 0 || !currentState;
|
|
1131
1814
|
annotationListElement.innerHTML = "";
|
|
@@ -1139,7 +1822,11 @@ function renderAnnotationPanel() {
|
|
|
1139
1822
|
for (const draft of annotationDrafts) {
|
|
1140
1823
|
const itemPayload = buildCanvasAnnotationPayloadForDrafts([draft]);
|
|
1141
1824
|
const annotation = itemPayload?.annotations[0];
|
|
1142
|
-
const nodeLabel = annotation
|
|
1825
|
+
const nodeLabel = annotation
|
|
1826
|
+
? describeAnnotationItem(annotation)
|
|
1827
|
+
: draft.kind === "region"
|
|
1828
|
+
? draft.label ?? draft.regionId
|
|
1829
|
+
: draft.nodeId;
|
|
1143
1830
|
const row = document.createElement("div");
|
|
1144
1831
|
row.className = "canvas-annotation-item";
|
|
1145
1832
|
const head = document.createElement("div");
|
|
@@ -1150,22 +1837,26 @@ function renderAnnotationPanel() {
|
|
|
1150
1837
|
title.textContent = nodeLabel;
|
|
1151
1838
|
const meta = document.createElement("div");
|
|
1152
1839
|
meta.className = "canvas-annotation-meta";
|
|
1153
|
-
meta.textContent = annotation
|
|
1840
|
+
meta.textContent = annotation
|
|
1841
|
+
? `${annotation.tag} • ${Math.round(annotation.rect.width)}×${Math.round(annotation.rect.height)}`
|
|
1842
|
+
: draft.kind === "region"
|
|
1843
|
+
? `${Math.round(draft.rect.width)}×${Math.round(draft.rect.height)}`
|
|
1844
|
+
: draft.nodeId;
|
|
1154
1845
|
summary.append(title, meta);
|
|
1155
1846
|
const removeButton = document.createElement("button");
|
|
1156
1847
|
removeButton.className = "canvas-button";
|
|
1157
1848
|
removeButton.type = "button";
|
|
1158
1849
|
removeButton.textContent = "Remove";
|
|
1159
1850
|
removeButton.addEventListener("click", () => {
|
|
1160
|
-
removeAnnotationDraft(draft
|
|
1851
|
+
removeAnnotationDraft(getDraftId(draft));
|
|
1161
1852
|
});
|
|
1162
1853
|
head.append(summary, removeButton);
|
|
1163
1854
|
const noteField = document.createElement("textarea");
|
|
1164
1855
|
noteField.className = "canvas-textarea";
|
|
1165
1856
|
noteField.value = draft.note ?? "";
|
|
1166
|
-
noteField.placeholder = "Add note for this node";
|
|
1857
|
+
noteField.placeholder = draft.kind === "region" ? "Add note for this captured region" : "Add note for this node";
|
|
1167
1858
|
noteField.addEventListener("input", () => {
|
|
1168
|
-
updateAnnotationDraft(draft
|
|
1859
|
+
updateAnnotationDraft(getDraftId(draft), { note: noteField.value });
|
|
1169
1860
|
});
|
|
1170
1861
|
const actions = document.createElement("div");
|
|
1171
1862
|
actions.className = "canvas-actions-grid";
|
|
@@ -1198,7 +1889,7 @@ function buildCanvasAnnotationPayloadForDrafts(drafts) {
|
|
|
1198
1889
|
if (!currentState) {
|
|
1199
1890
|
return null;
|
|
1200
1891
|
}
|
|
1201
|
-
const page =
|
|
1892
|
+
const page = getActivePage();
|
|
1202
1893
|
if (!page || drafts.length === 0) {
|
|
1203
1894
|
return null;
|
|
1204
1895
|
}
|
|
@@ -1211,18 +1902,20 @@ function buildCanvasAnnotationPayloadForDrafts(drafts) {
|
|
|
1211
1902
|
}
|
|
1212
1903
|
function addSelectedAnnotationDraft() {
|
|
1213
1904
|
const node = getSelectedNode();
|
|
1214
|
-
if (!node || annotationDrafts.some((entry) => entry.nodeId === node.id)) {
|
|
1905
|
+
if (!node || annotationDrafts.some((entry) => entry.kind !== "region" && entry.nodeId === node.id)) {
|
|
1215
1906
|
renderAnnotationPanel();
|
|
1216
1907
|
return;
|
|
1217
1908
|
}
|
|
1218
|
-
annotationDrafts = [...annotationDrafts, { nodeId: node.id, note: "" }];
|
|
1909
|
+
annotationDrafts = [...annotationDrafts, { kind: "node", nodeId: node.id, note: "" }];
|
|
1219
1910
|
renderAnnotationPanel();
|
|
1220
1911
|
}
|
|
1221
|
-
function updateAnnotationDraft(
|
|
1222
|
-
annotationDrafts = annotationDrafts.map((entry) => entry
|
|
1912
|
+
function updateAnnotationDraft(draftId, patch) {
|
|
1913
|
+
annotationDrafts = annotationDrafts.map((entry) => getDraftId(entry) === draftId
|
|
1914
|
+
? { ...entry, note: patch.note ?? entry.note }
|
|
1915
|
+
: entry);
|
|
1223
1916
|
}
|
|
1224
|
-
function removeAnnotationDraft(
|
|
1225
|
-
annotationDrafts = annotationDrafts.filter((entry) => entry
|
|
1917
|
+
function removeAnnotationDraft(draftId) {
|
|
1918
|
+
annotationDrafts = annotationDrafts.filter((entry) => getDraftId(entry) !== draftId);
|
|
1226
1919
|
renderAnnotationPanel();
|
|
1227
1920
|
}
|
|
1228
1921
|
function syncAnnotationDrafts() {
|
|
@@ -1231,9 +1924,9 @@ function syncAnnotationDrafts() {
|
|
|
1231
1924
|
renderAnnotationPanel();
|
|
1232
1925
|
return;
|
|
1233
1926
|
}
|
|
1234
|
-
const page =
|
|
1927
|
+
const page = getActivePage();
|
|
1235
1928
|
const validIds = new Set(page?.nodes.map((node) => node.id) ?? []);
|
|
1236
|
-
const next = annotationDrafts.filter((entry) => validIds.has(entry.nodeId));
|
|
1929
|
+
const next = annotationDrafts.filter((entry) => entry.kind === "region" || validIds.has(entry.nodeId));
|
|
1237
1930
|
if (next.length !== annotationDrafts.length) {
|
|
1238
1931
|
annotationDrafts = next;
|
|
1239
1932
|
}
|
|
@@ -1272,7 +1965,7 @@ async function sendCanvasAnnotation(drafts, source, label, button) {
|
|
|
1272
1965
|
if (!response.ok) {
|
|
1273
1966
|
throw new Error(response.error?.message ?? "Canvas annotation send failed.");
|
|
1274
1967
|
}
|
|
1275
|
-
setCanvasButtonFeedback(button,
|
|
1968
|
+
setCanvasButtonFeedback(button, formatAnnotationDispatchReceipt(response.receipt));
|
|
1276
1969
|
}
|
|
1277
1970
|
async function writeTextToClipboard(text) {
|
|
1278
1971
|
if (navigator.clipboard?.writeText) {
|
|
@@ -1327,13 +2020,15 @@ function applyOptimisticPatch(patches, selection, options = {}) {
|
|
|
1327
2020
|
targetId: selection?.targetId ?? currentState.selection.targetId,
|
|
1328
2021
|
updatedAt: new Date().toISOString()
|
|
1329
2022
|
};
|
|
2023
|
+
activePageId = currentState.selection.pageId ?? activePageId;
|
|
1330
2024
|
renderState();
|
|
1331
2025
|
schedulePersist(currentState);
|
|
1332
2026
|
port.postMessage({
|
|
1333
2027
|
type: "canvas-page-patch-request",
|
|
1334
2028
|
baseRevision: currentState.documentRevision,
|
|
1335
2029
|
patches,
|
|
1336
|
-
selection: currentState.selection
|
|
2030
|
+
selection: currentState.selection,
|
|
2031
|
+
viewport: currentState.viewport
|
|
1337
2032
|
});
|
|
1338
2033
|
}
|
|
1339
2034
|
function applyLocalPatchMutation(document, patches) {
|
|
@@ -1362,7 +2057,9 @@ function applyLocalPatchMutation(document, patches) {
|
|
|
1362
2057
|
} : { x: 0, y: 0, width: 240, height: 120 },
|
|
1363
2058
|
props: isRecord(node.props) ? { ...node.props } : {},
|
|
1364
2059
|
style: isRecord(node.style) ? { ...node.style } : {},
|
|
2060
|
+
tokenRefs: isRecord(node.tokenRefs) ? { ...node.tokenRefs } : {},
|
|
1365
2061
|
bindingRefs: isRecord(node.bindingRefs) ? { ...node.bindingRefs } : {},
|
|
2062
|
+
variantPatches: Array.isArray(node.variantPatches) ? node.variantPatches.filter(isRecord) : [],
|
|
1366
2063
|
metadata: isRecord(node.metadata) ? { ...node.metadata } : {}
|
|
1367
2064
|
};
|
|
1368
2065
|
page.nodes.push(inserted);
|
|
@@ -1379,11 +2076,16 @@ function applyLocalPatchMutation(document, patches) {
|
|
|
1379
2076
|
}
|
|
1380
2077
|
if (patch.op === "node.remove" && typeof patch.nodeId === "string") {
|
|
1381
2078
|
for (const page of document.pages) {
|
|
1382
|
-
|
|
2079
|
+
const removedIds = collectNodeSubtreeIds(page, patch.nodeId);
|
|
2080
|
+
if (removedIds.length === 0) {
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
const removedSet = new Set(removedIds);
|
|
2084
|
+
page.nodes = page.nodes.filter((entry) => !removedSet.has(entry.id));
|
|
1383
2085
|
for (const node of page.nodes) {
|
|
1384
|
-
node.childIds = node.childIds.filter((entry) => entry
|
|
2086
|
+
node.childIds = node.childIds.filter((entry) => !removedSet.has(entry));
|
|
1385
2087
|
}
|
|
1386
|
-
if (page.rootNodeId
|
|
2088
|
+
if (page.rootNodeId && removedSet.has(page.rootNodeId)) {
|
|
1387
2089
|
page.rootNodeId = null;
|
|
1388
2090
|
}
|
|
1389
2091
|
}
|
|
@@ -1397,42 +2099,1003 @@ function applyLocalPatchMutation(document, patches) {
|
|
|
1397
2099
|
for (const [path, value] of Object.entries(patch.changes)) {
|
|
1398
2100
|
setNestedValue(node, path, value);
|
|
1399
2101
|
}
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
2104
|
+
if (patch.op === "node.visibility.set" && typeof patch.nodeId === "string" && typeof patch.hidden === "boolean") {
|
|
2105
|
+
const node = findNode(document, patch.nodeId);
|
|
2106
|
+
if (!node) {
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
const visibility = isRecord(node.metadata.visibility) ? { ...node.metadata.visibility } : {};
|
|
2110
|
+
visibility.hidden = patch.hidden;
|
|
2111
|
+
node.metadata.visibility = visibility;
|
|
2112
|
+
continue;
|
|
2113
|
+
}
|
|
2114
|
+
if (patch.op === "node.reorder" && typeof patch.nodeId === "string" && typeof patch.index === "number") {
|
|
2115
|
+
const page = findPageForNode(document, patch.nodeId);
|
|
2116
|
+
const node = page?.nodes.find((entry) => entry.id === patch.nodeId);
|
|
2117
|
+
if (!page || !node) {
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
2120
|
+
const siblings = getSiblingIds(page, node.parentId ?? null);
|
|
2121
|
+
const currentIndex = siblings.indexOf(node.id);
|
|
2122
|
+
if (currentIndex === -1) {
|
|
2123
|
+
continue;
|
|
2124
|
+
}
|
|
2125
|
+
siblings.splice(currentIndex, 1);
|
|
2126
|
+
const nextIndex = clampIndex(patch.index, siblings.length);
|
|
2127
|
+
siblings.splice(nextIndex, 0, node.id);
|
|
2128
|
+
assignSiblingIds(page, node.parentId ?? null, siblings);
|
|
2129
|
+
continue;
|
|
2130
|
+
}
|
|
2131
|
+
if (patch.op === "node.reparent" && typeof patch.nodeId === "string" && typeof patch.index === "number") {
|
|
2132
|
+
const page = findPageForNode(document, patch.nodeId);
|
|
2133
|
+
const node = page?.nodes.find((entry) => entry.id === patch.nodeId);
|
|
2134
|
+
if (!page || !node) {
|
|
2135
|
+
continue;
|
|
2136
|
+
}
|
|
2137
|
+
const previousParentId = node.parentId ?? null;
|
|
2138
|
+
const currentSiblings = getSiblingIds(page, previousParentId).filter((entry) => entry !== node.id);
|
|
2139
|
+
assignSiblingIds(page, previousParentId, currentSiblings);
|
|
2140
|
+
node.parentId = typeof patch.parentId === "string" ? patch.parentId : null;
|
|
2141
|
+
const nextSiblings = getSiblingIds(page, node.parentId ?? null);
|
|
2142
|
+
const nextIndex = clampIndex(patch.index, nextSiblings.length);
|
|
2143
|
+
nextSiblings.splice(nextIndex, 0, node.id);
|
|
2144
|
+
assignSiblingIds(page, node.parentId ?? null, nextSiblings);
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2147
|
+
if (patch.op === "node.duplicate" && typeof patch.nodeId === "string") {
|
|
2148
|
+
const page = findPageForNode(document, patch.nodeId);
|
|
2149
|
+
const sourceNode = page?.nodes.find((entry) => entry.id === patch.nodeId);
|
|
2150
|
+
if (!page || !sourceNode) {
|
|
2151
|
+
continue;
|
|
2152
|
+
}
|
|
2153
|
+
const idMap = isRecord(patch.idMap) ? patch.idMap : {};
|
|
2154
|
+
const sourceIds = collectNodeSubtreeIds(page, sourceNode.id);
|
|
2155
|
+
for (const sourceId of sourceIds) {
|
|
2156
|
+
if (typeof idMap[sourceId] !== "string") {
|
|
2157
|
+
idMap[sourceId] = `node_${crypto.randomUUID().slice(0, 8)}`;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
const clones = [];
|
|
2161
|
+
for (const sourceId of sourceIds) {
|
|
2162
|
+
const original = page.nodes.find((entry) => entry.id === sourceId);
|
|
2163
|
+
if (!original) {
|
|
2164
|
+
continue;
|
|
2165
|
+
}
|
|
2166
|
+
const cloneNode = structuredCloneNode(original);
|
|
2167
|
+
cloneNode.id = String(idMap[sourceId]);
|
|
2168
|
+
cloneNode.pageId = page.id;
|
|
2169
|
+
cloneNode.parentId = sourceId === sourceNode.id
|
|
2170
|
+
? (typeof patch.parentId === "string" ? patch.parentId : sourceNode.parentId)
|
|
2171
|
+
: (original.parentId ? String(idMap[original.parentId] ?? original.parentId) : null);
|
|
2172
|
+
cloneNode.childIds = original.childIds.map((childId) => String(idMap[childId] ?? childId));
|
|
2173
|
+
cloneNode.rect = {
|
|
2174
|
+
...cloneNode.rect,
|
|
2175
|
+
x: cloneNode.rect.x + 24,
|
|
2176
|
+
y: cloneNode.rect.y + 24
|
|
2177
|
+
};
|
|
2178
|
+
if (cloneNode.bindingRefs.primary && typeof cloneNode.bindingRefs.primary === "string") {
|
|
2179
|
+
cloneNode.bindingRefs.primary = `${cloneNode.bindingRefs.primary}_${cloneNode.id}`;
|
|
2180
|
+
}
|
|
2181
|
+
clones.push(cloneNode);
|
|
2182
|
+
}
|
|
2183
|
+
page.nodes.push(...clones);
|
|
2184
|
+
const duplicateRootId = String(idMap[sourceNode.id]);
|
|
2185
|
+
const parentId = typeof patch.parentId === "string" ? patch.parentId : sourceNode.parentId ?? null;
|
|
2186
|
+
const siblings = getSiblingIds(page, parentId);
|
|
2187
|
+
const nextIndex = clampIndex(typeof patch.index === "number" ? patch.index : siblings.length, siblings.length);
|
|
2188
|
+
siblings.splice(nextIndex, 0, duplicateRootId);
|
|
2189
|
+
assignSiblingIds(page, parentId, siblings);
|
|
2190
|
+
continue;
|
|
2191
|
+
}
|
|
2192
|
+
if (patch.op === "token.set" && typeof patch.path === "string") {
|
|
2193
|
+
setNestedValue(document.tokens.values, normalizeTokenPath(patch.path), patch.value);
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
if (patch.op === "tokens.merge" && isRecord(patch.tokens)) {
|
|
2197
|
+
document.tokens = normalizeTokenStore({
|
|
2198
|
+
...structuredClone(document.tokens),
|
|
2199
|
+
...patch.tokens
|
|
2200
|
+
});
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
if (patch.op === "tokens.replace" && isRecord(patch.tokens)) {
|
|
2204
|
+
document.tokens = normalizeTokenStore(patch.tokens);
|
|
2205
|
+
continue;
|
|
1400
2206
|
}
|
|
1401
2207
|
}
|
|
1402
2208
|
}
|
|
1403
|
-
function
|
|
2209
|
+
function getActivePage() {
|
|
2210
|
+
if (!currentState) {
|
|
2211
|
+
return null;
|
|
2212
|
+
}
|
|
2213
|
+
const pageId = activePageId ?? currentState.selection.pageId ?? currentState.document.pages[0]?.id ?? null;
|
|
2214
|
+
return currentState.document.pages.find((entry) => entry.id === pageId) ?? currentState.document.pages[0] ?? null;
|
|
2215
|
+
}
|
|
2216
|
+
function setActivePage(pageId, options = {}) {
|
|
1404
2217
|
if (!currentState) {
|
|
2218
|
+
activePageId = pageId;
|
|
1405
2219
|
return;
|
|
1406
2220
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
2221
|
+
const page = currentState.document.pages.find((entry) => entry.id === pageId) ?? currentState.document.pages[0] ?? null;
|
|
2222
|
+
activePageId = page?.id ?? null;
|
|
2223
|
+
if (!page) {
|
|
2224
|
+
renderState();
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
const selectedNode = currentState.selection.nodeId ? findNode(currentState.document, currentState.selection.nodeId) : null;
|
|
2228
|
+
if (!selectedNode || selectedNode.pageId !== page.id || options.clearSelectionIfMissing) {
|
|
2229
|
+
currentState.selection = {
|
|
2230
|
+
pageId: page.id,
|
|
2231
|
+
nodeId: selectedNode?.pageId === page.id ? selectedNode.id : null,
|
|
2232
|
+
targetId: currentState.selection.targetId,
|
|
2233
|
+
updatedAt: new Date().toISOString()
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
else {
|
|
2237
|
+
currentState.selection.pageId = page.id;
|
|
2238
|
+
}
|
|
2239
|
+
renderState();
|
|
2240
|
+
if (options.broadcast) {
|
|
2241
|
+
postViewState();
|
|
2242
|
+
schedulePersist(currentState);
|
|
2243
|
+
}
|
|
1412
2244
|
}
|
|
1413
|
-
function
|
|
1414
|
-
if (
|
|
1415
|
-
|
|
2245
|
+
function selectNode(nodeId, pageId) {
|
|
2246
|
+
if (!currentState) {
|
|
2247
|
+
return;
|
|
1416
2248
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
2249
|
+
activePageId = pageId;
|
|
2250
|
+
currentState.selection = {
|
|
2251
|
+
pageId,
|
|
2252
|
+
nodeId,
|
|
2253
|
+
targetId: currentState.selection.targetId,
|
|
2254
|
+
updatedAt: new Date().toISOString()
|
|
2255
|
+
};
|
|
2256
|
+
renderState();
|
|
2257
|
+
postViewState();
|
|
2258
|
+
schedulePersist(currentState);
|
|
1420
2259
|
}
|
|
1421
|
-
|
|
1422
|
-
if (!
|
|
2260
|
+
function requestHistory(direction) {
|
|
2261
|
+
if (!currentState || currentState.pendingMutation) {
|
|
1423
2262
|
return;
|
|
1424
2263
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2264
|
+
const history = currentState.summary.history;
|
|
2265
|
+
if ((direction === "undo" && !history?.canUndo) || (direction === "redo" && !history?.canRedo)) {
|
|
2266
|
+
return;
|
|
1428
2267
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
2268
|
+
currentState.pendingMutation = true;
|
|
2269
|
+
renderState();
|
|
2270
|
+
port.postMessage({
|
|
2271
|
+
type: "canvas-page-history-request",
|
|
2272
|
+
direction
|
|
2273
|
+
});
|
|
1431
2274
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
2275
|
+
function duplicateSelectedNode() {
|
|
2276
|
+
if (!currentState || currentState.pendingMutation) {
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
const node = getSelectedNode();
|
|
2280
|
+
const page = getActivePage();
|
|
2281
|
+
if (!node || !page) {
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
const siblingIds = getSiblingIds(page, node.parentId ?? null);
|
|
2285
|
+
const currentIndex = siblingIds.indexOf(node.id);
|
|
2286
|
+
const idMap = buildDuplicateIdMap(page, node.id);
|
|
2287
|
+
const duplicateRootId = idMap[node.id];
|
|
2288
|
+
if (!duplicateRootId) {
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
applyOptimisticPatch([{
|
|
2292
|
+
op: "node.duplicate",
|
|
2293
|
+
nodeId: node.id,
|
|
2294
|
+
parentId: node.parentId ?? null,
|
|
2295
|
+
index: currentIndex >= 0 ? currentIndex + 1 : siblingIds.length,
|
|
2296
|
+
idMap
|
|
2297
|
+
}], {
|
|
2298
|
+
pageId: page.id,
|
|
2299
|
+
nodeId: duplicateRootId,
|
|
2300
|
+
targetId: currentState.selection.targetId
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
function nudgeSelectedNode(key, delta) {
|
|
2304
|
+
const node = getSelectedNode();
|
|
2305
|
+
if (!node || !currentState || currentState.pendingMutation) {
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
const changes = {};
|
|
2309
|
+
if (key === "ArrowUp") {
|
|
2310
|
+
changes["rect.y"] = node.rect.y - delta;
|
|
2311
|
+
}
|
|
2312
|
+
else if (key === "ArrowDown") {
|
|
2313
|
+
changes["rect.y"] = node.rect.y + delta;
|
|
2314
|
+
}
|
|
2315
|
+
else if (key === "ArrowLeft") {
|
|
2316
|
+
changes["rect.x"] = node.rect.x - delta;
|
|
2317
|
+
}
|
|
2318
|
+
else if (key === "ArrowRight") {
|
|
2319
|
+
changes["rect.x"] = node.rect.x + delta;
|
|
2320
|
+
}
|
|
2321
|
+
if (Object.keys(changes).length > 0) {
|
|
2322
|
+
applyOptimisticPatch([{ op: "node.update", nodeId: node.id, changes }], currentState.selection);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
function fitActivePageViewport() {
|
|
2326
|
+
if (!currentState) {
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2329
|
+
currentState.viewport = resolvePreferredViewport(currentState);
|
|
2330
|
+
renderState();
|
|
2331
|
+
postViewState();
|
|
2332
|
+
schedulePersist(currentState);
|
|
2333
|
+
}
|
|
2334
|
+
function resetZoomToDefault() {
|
|
2335
|
+
if (!currentState) {
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
currentState.viewport = {
|
|
2339
|
+
...currentState.viewport,
|
|
2340
|
+
zoom: DEFAULT_EDITOR_VIEWPORT.zoom
|
|
2341
|
+
};
|
|
2342
|
+
renderState();
|
|
2343
|
+
postViewState();
|
|
2344
|
+
schedulePersist(currentState);
|
|
2345
|
+
}
|
|
2346
|
+
function commitSelectedNodeChanges(changes) {
|
|
2347
|
+
const node = getSelectedNode();
|
|
2348
|
+
if (!node || !currentState || currentState.pendingMutation) {
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
applyOptimisticPatch([{ op: "node.update", nodeId: node.id, changes }], currentState.selection);
|
|
2352
|
+
}
|
|
2353
|
+
function commitSelectedBindingPatch() {
|
|
2354
|
+
const node = getSelectedNode();
|
|
2355
|
+
if (!node || !currentState || currentState.pendingMutation) {
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
const page = getActivePage();
|
|
2359
|
+
if (!page) {
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
const bindingIdentity = readSelectedBindingIdentity(currentState.document, node.id);
|
|
2363
|
+
const bindingId = bindingIdentity.bindingId ?? `binding_${crypto.randomUUID().slice(0, 8)}`;
|
|
2364
|
+
applyOptimisticPatch([{
|
|
2365
|
+
op: "binding.set",
|
|
2366
|
+
nodeId: node.id,
|
|
2367
|
+
binding: {
|
|
2368
|
+
id: bindingId,
|
|
2369
|
+
kind: readTextInput(bindingKindInput) || bindingIdentity.bindingKind || "component",
|
|
2370
|
+
selector: readTextInput(bindingSelectorInput) || undefined,
|
|
2371
|
+
componentName: readTextInput(bindingComponentInput) || undefined,
|
|
2372
|
+
metadata: {
|
|
2373
|
+
...(readExistingBindingMetadata(currentState.document, bindingId) ?? {}),
|
|
2374
|
+
sourceKind: bindingIdentity.sourceKind ?? undefined
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
}], {
|
|
2378
|
+
pageId: page.id,
|
|
2379
|
+
nodeId: node.id,
|
|
2380
|
+
targetId: currentState.selection.targetId
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
function commitLayerMove(nodeId, parentId, index) {
|
|
2384
|
+
if (!currentState || currentState.pendingMutation) {
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
const page = getActivePage();
|
|
2388
|
+
const node = findNode(currentState.document, nodeId);
|
|
2389
|
+
if (!page || !node || parentId === node.id) {
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
const patch = node.parentId === parentId
|
|
2393
|
+
? { op: "node.reorder", nodeId, index }
|
|
2394
|
+
: { op: "node.reparent", nodeId, parentId, index };
|
|
2395
|
+
applyOptimisticPatch([patch], {
|
|
2396
|
+
pageId: page.id,
|
|
2397
|
+
nodeId,
|
|
2398
|
+
targetId: currentState.selection.targetId
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2401
|
+
function commitAnnotationCapture(marquee) {
|
|
2402
|
+
const page = getActivePage();
|
|
2403
|
+
if (!currentState || !page) {
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
const dx = Math.abs(marquee.currentClientX - marquee.originClientX);
|
|
2407
|
+
const dy = Math.abs(marquee.currentClientY - marquee.originClientY);
|
|
2408
|
+
if (dx < 6 && dy < 6) {
|
|
2409
|
+
if (marquee.targetNodeId && !annotationDrafts.some((entry) => entry.kind !== "region" && entry.nodeId === marquee.targetNodeId)) {
|
|
2410
|
+
annotationDrafts = [...annotationDrafts, { kind: "node", nodeId: marquee.targetNodeId, note: "" }];
|
|
2411
|
+
renderAnnotationPanel();
|
|
2412
|
+
}
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
const rect = marqueeClientRectToCanvasRect(marquee);
|
|
2416
|
+
const intersectingNodes = page.nodes.filter((node) => rectsIntersect(node.rect, rect));
|
|
2417
|
+
const regionId = `region_${crypto.randomUUID().slice(0, 8)}`;
|
|
2418
|
+
annotationDrafts = [
|
|
2419
|
+
...annotationDrafts,
|
|
2420
|
+
{
|
|
2421
|
+
kind: "region",
|
|
2422
|
+
regionId,
|
|
2423
|
+
rect,
|
|
2424
|
+
pageId: page.id,
|
|
2425
|
+
label: intersectingNodes.length > 0 ? `Region • ${intersectingNodes.length} nodes` : "Region",
|
|
2426
|
+
note: ""
|
|
2427
|
+
}
|
|
2428
|
+
];
|
|
2429
|
+
renderAnnotationPanel();
|
|
2430
|
+
}
|
|
2431
|
+
function renderStageOverlay() {
|
|
2432
|
+
stageOverlayElement.innerHTML = "";
|
|
2433
|
+
if (!marqueeState) {
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
const marquee = document.createElement("div");
|
|
2437
|
+
marquee.className = "canvas-stage-marquee";
|
|
2438
|
+
const bounds = marqueeClientRectToStageRect(marqueeState);
|
|
2439
|
+
marquee.style.left = `${bounds.left}px`;
|
|
2440
|
+
marquee.style.top = `${bounds.top}px`;
|
|
2441
|
+
marquee.style.width = `${bounds.width}px`;
|
|
2442
|
+
marquee.style.height = `${bounds.height}px`;
|
|
2443
|
+
stageOverlayElement.append(marquee);
|
|
2444
|
+
}
|
|
2445
|
+
function bindFieldCommit(input, callback) {
|
|
2446
|
+
input.addEventListener("change", callback);
|
|
2447
|
+
input.addEventListener("keydown", (event) => {
|
|
2448
|
+
const keyboardEvent = event;
|
|
2449
|
+
if (keyboardEvent.key === "Enter" && !(input instanceof HTMLTextAreaElement && !keyboardEvent.metaKey && !keyboardEvent.ctrlKey)) {
|
|
2450
|
+
keyboardEvent.preventDefault();
|
|
2451
|
+
callback();
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
function readTextInput(input) {
|
|
2456
|
+
return input.value.trim();
|
|
2457
|
+
}
|
|
2458
|
+
function readNumberInput(input, fallback) {
|
|
2459
|
+
const value = Number(input.value);
|
|
2460
|
+
return Number.isFinite(value) ? value : fallback;
|
|
2461
|
+
}
|
|
2462
|
+
function readStyleText(value) {
|
|
2463
|
+
if (typeof value === "string") {
|
|
2464
|
+
return value;
|
|
2465
|
+
}
|
|
2466
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2467
|
+
return String(value);
|
|
2468
|
+
}
|
|
2469
|
+
return "";
|
|
2470
|
+
}
|
|
2471
|
+
function readExistingBindingMetadata(document, bindingId) {
|
|
2472
|
+
const binding = document.bindings.find((entry) => entry.id === bindingId);
|
|
2473
|
+
return binding ? { ...binding.metadata } : null;
|
|
2474
|
+
}
|
|
2475
|
+
function readBindingSelector(document, nodeId) {
|
|
2476
|
+
const bindingIdentity = readSelectedBindingIdentity(document, nodeId);
|
|
2477
|
+
const binding = bindingIdentity.bindingId
|
|
2478
|
+
? document.bindings.find((entry) => entry.id === bindingIdentity.bindingId) ?? null
|
|
2479
|
+
: null;
|
|
2480
|
+
if (binding && typeof binding.selector === "string") {
|
|
2481
|
+
return binding.selector;
|
|
2482
|
+
}
|
|
2483
|
+
return binding && typeof binding.metadata.selector === "string"
|
|
2484
|
+
? binding.metadata.selector
|
|
2485
|
+
: "";
|
|
2486
|
+
}
|
|
2487
|
+
function getRootNodes(page) {
|
|
2488
|
+
if (page.rootNodeId) {
|
|
2489
|
+
const root = page.nodes.find((entry) => entry.id === page.rootNodeId);
|
|
2490
|
+
if (root) {
|
|
2491
|
+
return [root, ...page.nodes.filter((entry) => entry.parentId === null && entry.id !== root.id)];
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
return page.nodes.filter((entry) => entry.parentId === null);
|
|
2495
|
+
}
|
|
2496
|
+
function ensureExpandedNodePath(page, nodeId) {
|
|
2497
|
+
if (!page.rootNodeId) {
|
|
2498
|
+
return;
|
|
2499
|
+
}
|
|
2500
|
+
expandedLayerNodeIds.add(page.rootNodeId);
|
|
2501
|
+
let cursor = nodeId ? page.nodes.find((entry) => entry.id === nodeId) ?? null : null;
|
|
2502
|
+
while (cursor?.parentId) {
|
|
2503
|
+
expandedLayerNodeIds.add(cursor.parentId);
|
|
2504
|
+
cursor = page.nodes.find((entry) => entry.id === cursor?.parentId) ?? null;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
function getSiblingIds(page, parentId) {
|
|
2508
|
+
if (parentId) {
|
|
2509
|
+
return [...(page.nodes.find((entry) => entry.id === parentId)?.childIds ?? [])];
|
|
2510
|
+
}
|
|
2511
|
+
return page.nodes.filter((entry) => entry.parentId === null).map((entry) => entry.id);
|
|
2512
|
+
}
|
|
2513
|
+
function assignSiblingIds(page, parentId, ids) {
|
|
2514
|
+
if (parentId) {
|
|
2515
|
+
const parent = page.nodes.find((entry) => entry.id === parentId);
|
|
2516
|
+
if (parent) {
|
|
2517
|
+
parent.childIds = ids;
|
|
2518
|
+
}
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
for (const node of page.nodes) {
|
|
2522
|
+
if (ids.includes(node.id)) {
|
|
2523
|
+
node.parentId = null;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
page.rootNodeId = ids[0] ?? null;
|
|
2527
|
+
}
|
|
2528
|
+
function findSiblingInsertIndex(page, nodeId) {
|
|
2529
|
+
const node = page.nodes.find((entry) => entry.id === nodeId);
|
|
2530
|
+
if (!node) {
|
|
2531
|
+
return 0;
|
|
2532
|
+
}
|
|
2533
|
+
const siblings = getSiblingIds(page, node.parentId ?? null);
|
|
2534
|
+
const currentIndex = siblings.indexOf(node.id);
|
|
2535
|
+
return currentIndex >= 0 ? currentIndex : siblings.length;
|
|
2536
|
+
}
|
|
2537
|
+
function clampIndex(index, maxLength) {
|
|
2538
|
+
return Math.max(0, Math.min(index, maxLength));
|
|
2539
|
+
}
|
|
2540
|
+
function findPageForNode(document, nodeId) {
|
|
2541
|
+
for (const page of document.pages) {
|
|
2542
|
+
if (page.nodes.some((entry) => entry.id === nodeId)) {
|
|
2543
|
+
return page;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
return null;
|
|
2547
|
+
}
|
|
2548
|
+
function collectNodeSubtreeIds(page, nodeId) {
|
|
2549
|
+
const collected = [];
|
|
2550
|
+
const visit = (id) => {
|
|
2551
|
+
const node = page.nodes.find((entry) => entry.id === id);
|
|
2552
|
+
if (!node) {
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
collected.push(node.id);
|
|
2556
|
+
for (const childId of node.childIds) {
|
|
2557
|
+
visit(childId);
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2560
|
+
visit(nodeId);
|
|
2561
|
+
return collected;
|
|
2562
|
+
}
|
|
2563
|
+
function buildDuplicateIdMap(page, nodeId) {
|
|
2564
|
+
return Object.fromEntries(collectNodeSubtreeIds(page, nodeId).map((id) => [id, `node_${crypto.randomUUID().slice(0, 8)}`]));
|
|
2565
|
+
}
|
|
2566
|
+
function structuredCloneNode(node) {
|
|
2567
|
+
return {
|
|
2568
|
+
...node,
|
|
2569
|
+
rect: { ...node.rect },
|
|
2570
|
+
props: { ...node.props },
|
|
2571
|
+
style: { ...node.style },
|
|
2572
|
+
tokenRefs: { ...node.tokenRefs },
|
|
2573
|
+
bindingRefs: { ...node.bindingRefs },
|
|
2574
|
+
variantPatches: node.variantPatches.map((entry) => ({ ...entry })),
|
|
2575
|
+
metadata: { ...node.metadata }
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2578
|
+
function marqueeClientRectToStageRect(marquee) {
|
|
2579
|
+
const stageRect = stageElement.getBoundingClientRect();
|
|
2580
|
+
const left = Math.min(marquee.originClientX, marquee.currentClientX) - stageRect.left;
|
|
2581
|
+
const top = Math.min(marquee.originClientY, marquee.currentClientY) - stageRect.top;
|
|
2582
|
+
const width = Math.abs(marquee.currentClientX - marquee.originClientX);
|
|
2583
|
+
const height = Math.abs(marquee.currentClientY - marquee.originClientY);
|
|
2584
|
+
return { left, top, width, height };
|
|
2585
|
+
}
|
|
2586
|
+
function marqueeClientRectToCanvasRect(marquee) {
|
|
2587
|
+
const stageRect = marqueeClientRectToStageRect(marquee);
|
|
2588
|
+
const viewport = currentState?.viewport ?? DEFAULT_EDITOR_VIEWPORT;
|
|
2589
|
+
return {
|
|
2590
|
+
x: Math.round((stageRect.left - viewport.x) / viewport.zoom),
|
|
2591
|
+
y: Math.round((stageRect.top - viewport.y) / viewport.zoom),
|
|
2592
|
+
width: Math.round(stageRect.width / viewport.zoom),
|
|
2593
|
+
height: Math.round(stageRect.height / viewport.zoom)
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
function rectsIntersect(left, right) {
|
|
2597
|
+
return left.x < right.x + right.width
|
|
2598
|
+
&& left.x + left.width > right.x
|
|
2599
|
+
&& left.y < right.y + right.height
|
|
2600
|
+
&& left.y + left.height > right.y;
|
|
2601
|
+
}
|
|
2602
|
+
function isNodeHidden(node) {
|
|
2603
|
+
return isRecord(node.metadata.visibility) && node.metadata.visibility.hidden === true;
|
|
2604
|
+
}
|
|
2605
|
+
function readTokenPath(value) {
|
|
2606
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
2607
|
+
return normalizeTokenPath(value);
|
|
2608
|
+
}
|
|
2609
|
+
if (isRecord(value)) {
|
|
2610
|
+
if (typeof value.path === "string" && value.path.trim().length > 0) {
|
|
2611
|
+
return normalizeTokenPath(value.path);
|
|
2612
|
+
}
|
|
2613
|
+
if (typeof value.tokenPath === "string" && value.tokenPath.trim().length > 0) {
|
|
2614
|
+
return normalizeTokenPath(value.tokenPath);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
return null;
|
|
2618
|
+
}
|
|
2619
|
+
function normalizeTokenPath(path) {
|
|
2620
|
+
return path.trim().replace(/^tokens\./, "");
|
|
2621
|
+
}
|
|
2622
|
+
function escapeHtmlText(value) {
|
|
2623
|
+
return value
|
|
2624
|
+
.replaceAll("&", "&")
|
|
2625
|
+
.replaceAll("<", "<")
|
|
2626
|
+
.replaceAll(">", ">");
|
|
2627
|
+
}
|
|
2628
|
+
function escapeHtmlAttribute(value) {
|
|
2629
|
+
return escapeHtmlText(value).replaceAll("\"", """);
|
|
2630
|
+
}
|
|
2631
|
+
function readActiveTokenModeId(tokens) {
|
|
2632
|
+
return typeof tokens.metadata.activeModeId === "string" && tokens.metadata.activeModeId.trim().length > 0
|
|
2633
|
+
? tokens.metadata.activeModeId
|
|
2634
|
+
: null;
|
|
2635
|
+
}
|
|
2636
|
+
function readNestedTokenValue(values, tokenPath) {
|
|
2637
|
+
const segments = normalizeTokenPath(tokenPath).split(".").filter(Boolean);
|
|
2638
|
+
let current = values;
|
|
2639
|
+
for (const segment of segments) {
|
|
2640
|
+
if (!isRecord(current) || !(segment in current)) {
|
|
2641
|
+
return null;
|
|
2642
|
+
}
|
|
2643
|
+
current = current[segment];
|
|
2644
|
+
}
|
|
2645
|
+
return current;
|
|
2646
|
+
}
|
|
2647
|
+
function findTokenCollectionId(tokens, tokenPath) {
|
|
2648
|
+
const normalized = normalizeTokenPath(tokenPath);
|
|
2649
|
+
for (const collection of tokens.collections) {
|
|
2650
|
+
if (collection.items.some((item) => item.path === normalized)) {
|
|
2651
|
+
return collection.id;
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
return null;
|
|
2655
|
+
}
|
|
2656
|
+
function findTokenItem(tokens, tokenPath) {
|
|
2657
|
+
const normalized = normalizeTokenPath(tokenPath);
|
|
2658
|
+
for (const collection of tokens.collections) {
|
|
2659
|
+
const item = collection.items.find((entry) => entry.path === normalized);
|
|
2660
|
+
if (item) {
|
|
2661
|
+
return { collection, item };
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
return null;
|
|
2665
|
+
}
|
|
2666
|
+
function readTokenAliasTarget(tokens, tokenPath, modeId) {
|
|
2667
|
+
const normalized = normalizeTokenPath(tokenPath);
|
|
2668
|
+
const exact = tokens.aliases.find((entry) => entry.path === normalized && (entry.modeId ?? null) === modeId);
|
|
2669
|
+
if (exact) {
|
|
2670
|
+
return exact.targetPath;
|
|
2671
|
+
}
|
|
2672
|
+
const shared = tokens.aliases.find((entry) => entry.path === normalized && (entry.modeId ?? null) === null);
|
|
2673
|
+
return shared?.targetPath ?? null;
|
|
2674
|
+
}
|
|
2675
|
+
function resolveTokenValue(tokens, tokenPath, modeId, seen = new Set()) {
|
|
2676
|
+
const normalized = normalizeTokenPath(tokenPath);
|
|
2677
|
+
const visitKey = `${normalized}:${modeId ?? ""}`;
|
|
2678
|
+
if (seen.has(visitKey)) {
|
|
2679
|
+
return null;
|
|
2680
|
+
}
|
|
2681
|
+
seen.add(visitKey);
|
|
2682
|
+
const aliasTarget = readTokenAliasTarget(tokens, normalized, modeId);
|
|
2683
|
+
if (aliasTarget) {
|
|
2684
|
+
const aliasedValue = resolveTokenValue(tokens, aliasTarget, modeId, seen);
|
|
2685
|
+
if (aliasedValue !== null && aliasedValue !== undefined) {
|
|
2686
|
+
return aliasedValue;
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
const location = findTokenItem(tokens, normalized);
|
|
2690
|
+
if (location) {
|
|
2691
|
+
if (modeId) {
|
|
2692
|
+
const mode = location.item.modes.find((entry) => entry.id === modeId);
|
|
2693
|
+
if (mode) {
|
|
2694
|
+
return mode.value;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
if (location.item.value !== undefined) {
|
|
2698
|
+
return location.item.value;
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
return readNestedTokenValue(tokens.values, normalized);
|
|
2702
|
+
}
|
|
2703
|
+
function formatTokenEditorValue(value) {
|
|
2704
|
+
if (typeof value === "string") {
|
|
2705
|
+
return value;
|
|
2706
|
+
}
|
|
2707
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2708
|
+
return String(value);
|
|
2709
|
+
}
|
|
2710
|
+
if (typeof value === "boolean") {
|
|
2711
|
+
return value ? "true" : "false";
|
|
2712
|
+
}
|
|
2713
|
+
return "";
|
|
2714
|
+
}
|
|
2715
|
+
function parseTokenEditorValue(raw) {
|
|
2716
|
+
const value = raw.trim();
|
|
2717
|
+
if (value === "true") {
|
|
2718
|
+
return true;
|
|
2719
|
+
}
|
|
2720
|
+
if (value === "false") {
|
|
2721
|
+
return false;
|
|
2722
|
+
}
|
|
2723
|
+
if (/^-?\d+(?:\.\d+)?$/.test(value)) {
|
|
2724
|
+
const parsed = Number(value);
|
|
2725
|
+
if (Number.isFinite(parsed)) {
|
|
2726
|
+
return parsed;
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
return value;
|
|
2730
|
+
}
|
|
2731
|
+
function collectLeafTokenPaths(value, prefix, target) {
|
|
2732
|
+
if (!isRecord(value)) {
|
|
2733
|
+
if (prefix.length > 0) {
|
|
2734
|
+
target.add(prefix);
|
|
2735
|
+
}
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2739
|
+
const nextPath = prefix ? `${prefix}.${key}` : key;
|
|
2740
|
+
if (isRecord(entry)) {
|
|
2741
|
+
collectLeafTokenPaths(entry, nextPath, target);
|
|
2742
|
+
continue;
|
|
2743
|
+
}
|
|
2744
|
+
if (!Array.isArray(entry)) {
|
|
2745
|
+
target.add(nextPath);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
function listTokenModes(tokens, collectionId) {
|
|
2750
|
+
const modeMap = new Map([["__base__", "Base value"]]);
|
|
2751
|
+
if (collectionId !== "__values__") {
|
|
2752
|
+
const collection = tokens.collections.find((entry) => entry.id === collectionId);
|
|
2753
|
+
for (const item of collection?.items ?? []) {
|
|
2754
|
+
for (const mode of item.modes) {
|
|
2755
|
+
modeMap.set(mode.id, mode.name);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
const activeModeId = readActiveTokenModeId(tokens);
|
|
2760
|
+
if (activeModeId && !modeMap.has(activeModeId)) {
|
|
2761
|
+
modeMap.set(activeModeId, activeModeId);
|
|
2762
|
+
}
|
|
2763
|
+
return [...modeMap.entries()].map(([id, name]) => ({ id, name }));
|
|
2764
|
+
}
|
|
2765
|
+
function collectTokenUsages(document, tokenPath) {
|
|
2766
|
+
const normalized = normalizeTokenPath(tokenPath);
|
|
2767
|
+
const modeId = readActiveTokenModeId(document.tokens);
|
|
2768
|
+
return document.pages.flatMap((page) => page.nodes.flatMap((node) => {
|
|
2769
|
+
const entries = Object.entries(node.tokenRefs)
|
|
2770
|
+
.flatMap(([property, value]) => {
|
|
2771
|
+
const refPath = readTokenPath(value);
|
|
2772
|
+
return refPath === normalized ? [{
|
|
2773
|
+
pageName: page.name,
|
|
2774
|
+
nodeId: node.id,
|
|
2775
|
+
nodeName: node.name,
|
|
2776
|
+
property,
|
|
2777
|
+
resolvedValue: resolveTokenValue(document.tokens, normalized, modeId)
|
|
2778
|
+
}] : [];
|
|
2779
|
+
});
|
|
2780
|
+
return entries;
|
|
2781
|
+
}));
|
|
2782
|
+
}
|
|
2783
|
+
function cloneTokenStore(tokens) {
|
|
2784
|
+
return structuredClone(tokens);
|
|
2785
|
+
}
|
|
2786
|
+
function hasSelectedTokenBinding(node, property) {
|
|
2787
|
+
return typeof readTokenPath(node.tokenRefs[property]) === "string";
|
|
2788
|
+
}
|
|
2789
|
+
function syncTokenEditorSelection() {
|
|
2790
|
+
if (!currentState) {
|
|
2791
|
+
selectedTokenPath = "";
|
|
2792
|
+
selectedTokenCollectionId = "__values__";
|
|
2793
|
+
selectedTokenModeId = "__base__";
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
const node = getSelectedNode();
|
|
2797
|
+
const selectedEntry = node
|
|
2798
|
+
? Object.entries(node.tokenRefs)
|
|
2799
|
+
.map(([property, value]) => ({ property, path: readTokenPath(value) }))
|
|
2800
|
+
.find((entry) => typeof entry.path === "string")
|
|
2801
|
+
: null;
|
|
2802
|
+
const knownPaths = new Set();
|
|
2803
|
+
collectLeafTokenPaths(currentState.document.tokens.values, "", knownPaths);
|
|
2804
|
+
for (const collection of currentState.document.tokens.collections) {
|
|
2805
|
+
for (const item of collection.items) {
|
|
2806
|
+
knownPaths.add(item.path);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
if (selectedEntry && selectedTokenPath.length === 0) {
|
|
2810
|
+
selectedTokenPath = selectedEntry.path;
|
|
2811
|
+
tokenBindingPropertySelect.value = selectedEntry.property;
|
|
2812
|
+
}
|
|
2813
|
+
else if (selectedTokenPath.length === 0 || !knownPaths.has(selectedTokenPath)) {
|
|
2814
|
+
selectedTokenPath = [...knownPaths][0] ?? "";
|
|
2815
|
+
}
|
|
2816
|
+
const collectionStillExists = selectedTokenCollectionId === "__values__"
|
|
2817
|
+
|| currentState.document.tokens.collections.some((entry) => entry.id === selectedTokenCollectionId);
|
|
2818
|
+
if (!collectionStillExists) {
|
|
2819
|
+
selectedTokenCollectionId = selectedTokenPath
|
|
2820
|
+
? findTokenCollectionId(currentState.document.tokens, selectedTokenPath) ?? "__values__"
|
|
2821
|
+
: "__values__";
|
|
2822
|
+
}
|
|
2823
|
+
const activeModeId = readActiveTokenModeId(currentState.document.tokens);
|
|
2824
|
+
selectedTokenModeId = activeModeId ?? "__base__";
|
|
2825
|
+
}
|
|
2826
|
+
function updateActiveTokenMode() {
|
|
2827
|
+
if (!currentState || currentState.pendingMutation) {
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
const nextTokens = cloneTokenStore(currentState.document.tokens);
|
|
2831
|
+
if (selectedTokenModeId === "__base__") {
|
|
2832
|
+
delete nextTokens.metadata.activeModeId;
|
|
2833
|
+
}
|
|
2834
|
+
else {
|
|
2835
|
+
nextTokens.metadata.activeModeId = selectedTokenModeId;
|
|
2836
|
+
}
|
|
2837
|
+
applyOptimisticPatch([{ op: "tokens.replace", tokens: nextTokens }], currentState.selection);
|
|
2838
|
+
}
|
|
2839
|
+
function createTokenCollection() {
|
|
2840
|
+
if (!currentState || currentState.pendingMutation) {
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
const rawName = tokenCollectionNameInput.value.trim();
|
|
2844
|
+
if (rawName.length === 0) {
|
|
2845
|
+
tokenStatusElement.textContent = "Enter a collection name first.";
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
const collectionId = rawName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") || `collection-${crypto.randomUUID().slice(0, 6)}`;
|
|
2849
|
+
const nextTokens = cloneTokenStore(currentState.document.tokens);
|
|
2850
|
+
if (!nextTokens.collections.some((entry) => entry.id === collectionId)) {
|
|
2851
|
+
nextTokens.collections.push({
|
|
2852
|
+
id: collectionId,
|
|
2853
|
+
name: rawName,
|
|
2854
|
+
items: [],
|
|
2855
|
+
metadata: {}
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
selectedTokenCollectionId = collectionId;
|
|
2859
|
+
tokenCollectionNameInput.value = "";
|
|
2860
|
+
applyOptimisticPatch([{ op: "tokens.replace", tokens: nextTokens }], currentState.selection);
|
|
2861
|
+
}
|
|
2862
|
+
function createTokenMode() {
|
|
2863
|
+
if (!currentState || currentState.pendingMutation) {
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
const rawName = tokenModeNameInput.value.trim();
|
|
2867
|
+
const tokenPath = normalizeTokenPath(tokenPathInput.value);
|
|
2868
|
+
if (rawName.length === 0 || tokenPath.length === 0) {
|
|
2869
|
+
tokenStatusElement.textContent = "Choose a token path and enter a mode name first.";
|
|
2870
|
+
return;
|
|
2871
|
+
}
|
|
2872
|
+
const nextTokens = cloneTokenStore(currentState.document.tokens);
|
|
2873
|
+
const collectionId = selectedTokenCollectionId === "__values__"
|
|
2874
|
+
? (findTokenCollectionId(nextTokens, tokenPath) ?? "__values__")
|
|
2875
|
+
: selectedTokenCollectionId;
|
|
2876
|
+
if (collectionId === "__values__") {
|
|
2877
|
+
tokenStatusElement.textContent = "Create or select a token collection before adding modes.";
|
|
2878
|
+
return;
|
|
2879
|
+
}
|
|
2880
|
+
const collection = nextTokens.collections.find((entry) => entry.id === collectionId);
|
|
2881
|
+
if (!collection) {
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
let item = collection.items.find((entry) => entry.path === tokenPath);
|
|
2885
|
+
if (!item) {
|
|
2886
|
+
item = {
|
|
2887
|
+
id: tokenPath.replace(/[^a-z0-9]+/gi, "_").toLowerCase(),
|
|
2888
|
+
path: tokenPath,
|
|
2889
|
+
value: parseTokenEditorValue(tokenValueInput.value || formatTokenEditorValue(resolveTokenValue(nextTokens, tokenPath, null))),
|
|
2890
|
+
modes: [],
|
|
2891
|
+
metadata: {}
|
|
2892
|
+
};
|
|
2893
|
+
collection.items.push(item);
|
|
2894
|
+
}
|
|
2895
|
+
const modeId = rawName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "") || `mode-${crypto.randomUUID().slice(0, 6)}`;
|
|
2896
|
+
if (!item.modes.some((entry) => entry.id === modeId)) {
|
|
2897
|
+
item.modes.push({
|
|
2898
|
+
id: modeId,
|
|
2899
|
+
name: rawName,
|
|
2900
|
+
value: parseTokenEditorValue(tokenValueInput.value || formatTokenEditorValue(item.value)),
|
|
2901
|
+
metadata: {}
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
nextTokens.metadata.activeModeId = modeId;
|
|
2905
|
+
selectedTokenModeId = modeId;
|
|
2906
|
+
tokenModeNameInput.value = "";
|
|
2907
|
+
applyOptimisticPatch([{ op: "tokens.replace", tokens: nextTokens }], currentState.selection);
|
|
2908
|
+
}
|
|
2909
|
+
function saveTokenEditor() {
|
|
2910
|
+
if (!currentState || currentState.pendingMutation) {
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
const tokenPath = normalizeTokenPath(tokenPathInput.value);
|
|
2914
|
+
if (tokenPath.length === 0) {
|
|
2915
|
+
tokenStatusElement.textContent = "Enter a token path first.";
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
const nextTokens = cloneTokenStore(currentState.document.tokens);
|
|
2919
|
+
const nextValue = parseTokenEditorValue(tokenValueInput.value);
|
|
2920
|
+
if (selectedTokenCollectionId === "__values__") {
|
|
2921
|
+
setNestedValue(nextTokens.values, tokenPath, nextValue);
|
|
2922
|
+
}
|
|
2923
|
+
else {
|
|
2924
|
+
const collection = nextTokens.collections.find((entry) => entry.id === selectedTokenCollectionId);
|
|
2925
|
+
if (!collection) {
|
|
2926
|
+
tokenStatusElement.textContent = "Choose a valid token collection.";
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
let item = collection.items.find((entry) => entry.path === tokenPath);
|
|
2930
|
+
if (!item) {
|
|
2931
|
+
item = {
|
|
2932
|
+
id: tokenPath.replace(/[^a-z0-9]+/gi, "_").toLowerCase(),
|
|
2933
|
+
path: tokenPath,
|
|
2934
|
+
value: nextValue,
|
|
2935
|
+
modes: [],
|
|
2936
|
+
metadata: {}
|
|
2937
|
+
};
|
|
2938
|
+
collection.items.push(item);
|
|
2939
|
+
}
|
|
2940
|
+
if (selectedTokenModeId === "__base__") {
|
|
2941
|
+
item.value = nextValue;
|
|
2942
|
+
}
|
|
2943
|
+
else {
|
|
2944
|
+
const modeName = tokenModeSelect.selectedOptions[0]?.textContent?.trim() || selectedTokenModeId;
|
|
2945
|
+
const existingMode = item.modes.find((entry) => entry.id === selectedTokenModeId);
|
|
2946
|
+
if (existingMode) {
|
|
2947
|
+
existingMode.value = nextValue;
|
|
2948
|
+
}
|
|
2949
|
+
else {
|
|
2950
|
+
item.modes.push({
|
|
2951
|
+
id: selectedTokenModeId,
|
|
2952
|
+
name: modeName,
|
|
2953
|
+
value: nextValue,
|
|
2954
|
+
metadata: {}
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
nextTokens.metadata.activeModeId = selectedTokenModeId;
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
const aliasTarget = normalizeTokenPath(tokenAliasInput.value);
|
|
2961
|
+
nextTokens.aliases = nextTokens.aliases.filter((entry) => !(entry.path === tokenPath && (entry.modeId ?? null) === (selectedTokenModeId === "__base__" ? null : selectedTokenModeId)));
|
|
2962
|
+
if (aliasTarget.length > 0) {
|
|
2963
|
+
nextTokens.aliases.push({
|
|
2964
|
+
path: tokenPath,
|
|
2965
|
+
targetPath: aliasTarget,
|
|
2966
|
+
modeId: selectedTokenModeId === "__base__" ? null : selectedTokenModeId,
|
|
2967
|
+
metadata: {}
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
selectedTokenPath = tokenPath;
|
|
2971
|
+
applyOptimisticPatch([{ op: "tokens.replace", tokens: nextTokens }], currentState.selection);
|
|
2972
|
+
}
|
|
2973
|
+
function bindSelectedNodeToToken() {
|
|
2974
|
+
const node = getSelectedNode();
|
|
2975
|
+
if (!node || !currentState || currentState.pendingMutation) {
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
const tokenPath = normalizeTokenPath(tokenPathInput.value);
|
|
2979
|
+
const property = tokenBindingPropertySelect.value || "backgroundColor";
|
|
2980
|
+
if (tokenPath.length === 0) {
|
|
2981
|
+
tokenStatusElement.textContent = "Choose a token path first.";
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
const nextTokenRefs = { ...node.tokenRefs, [property]: tokenPath };
|
|
2985
|
+
const nextStyle = { ...node.style };
|
|
2986
|
+
const resolvedValue = resolveTokenValue(currentState.document.tokens, tokenPath, readActiveTokenModeId(currentState.document.tokens));
|
|
2987
|
+
if (typeof resolvedValue === "string" || typeof resolvedValue === "number") {
|
|
2988
|
+
nextStyle[property] = resolvedValue;
|
|
2989
|
+
}
|
|
2990
|
+
const nextTokens = cloneTokenStore(currentState.document.tokens);
|
|
2991
|
+
const bindingIdentity = readSelectedBindingIdentity(currentState.document, node.id);
|
|
2992
|
+
nextTokens.bindings = nextTokens.bindings.filter((entry) => !(entry.nodeId === node.id && entry.property === property));
|
|
2993
|
+
nextTokens.bindings.push({
|
|
2994
|
+
path: tokenPath,
|
|
2995
|
+
nodeId: node.id,
|
|
2996
|
+
bindingId: bindingIdentity.bindingId ?? null,
|
|
2997
|
+
property,
|
|
2998
|
+
metadata: {}
|
|
2999
|
+
});
|
|
3000
|
+
selectedTokenPath = tokenPath;
|
|
3001
|
+
applyOptimisticPatch([
|
|
3002
|
+
{
|
|
3003
|
+
op: "node.update",
|
|
3004
|
+
nodeId: node.id,
|
|
3005
|
+
changes: {
|
|
3006
|
+
tokenRefs: nextTokenRefs,
|
|
3007
|
+
style: nextStyle
|
|
3008
|
+
}
|
|
3009
|
+
},
|
|
3010
|
+
{
|
|
3011
|
+
op: "tokens.replace",
|
|
3012
|
+
tokens: nextTokens
|
|
3013
|
+
}
|
|
3014
|
+
], currentState.selection);
|
|
3015
|
+
}
|
|
3016
|
+
function clearSelectedTokenBinding() {
|
|
3017
|
+
const node = getSelectedNode();
|
|
3018
|
+
if (!node || !currentState || currentState.pendingMutation) {
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
const property = tokenBindingPropertySelect.value || "backgroundColor";
|
|
3022
|
+
if (!hasSelectedTokenBinding(node, property)) {
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
const nextTokenRefs = { ...node.tokenRefs };
|
|
3026
|
+
delete nextTokenRefs[property];
|
|
3027
|
+
const nextTokens = cloneTokenStore(currentState.document.tokens);
|
|
3028
|
+
nextTokens.bindings = nextTokens.bindings.filter((entry) => !(entry.nodeId === node.id && entry.property === property));
|
|
3029
|
+
applyOptimisticPatch([
|
|
3030
|
+
{
|
|
3031
|
+
op: "node.update",
|
|
3032
|
+
nodeId: node.id,
|
|
3033
|
+
changes: {
|
|
3034
|
+
tokenRefs: nextTokenRefs
|
|
3035
|
+
}
|
|
3036
|
+
},
|
|
3037
|
+
{
|
|
3038
|
+
op: "tokens.replace",
|
|
3039
|
+
tokens: nextTokens
|
|
3040
|
+
}
|
|
3041
|
+
], currentState.selection);
|
|
3042
|
+
}
|
|
3043
|
+
function resolveStageStyle(documentState, node) {
|
|
3044
|
+
const style = { ...node.style };
|
|
3045
|
+
const modeId = readActiveTokenModeId(documentState.tokens);
|
|
3046
|
+
for (const [property, value] of Object.entries(node.tokenRefs)) {
|
|
3047
|
+
const tokenPath = readTokenPath(value);
|
|
3048
|
+
if (!tokenPath) {
|
|
3049
|
+
continue;
|
|
3050
|
+
}
|
|
3051
|
+
const resolvedValue = resolveTokenValue(documentState.tokens, tokenPath, modeId);
|
|
3052
|
+
if (typeof resolvedValue === "string" || typeof resolvedValue === "number") {
|
|
3053
|
+
style[property] = resolvedValue;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
return style;
|
|
3057
|
+
}
|
|
3058
|
+
function getDraftId(draft) {
|
|
3059
|
+
return draft.kind === "region" ? draft.regionId : draft.nodeId;
|
|
3060
|
+
}
|
|
3061
|
+
function isEditableTarget(target) {
|
|
3062
|
+
return target instanceof HTMLInputElement
|
|
3063
|
+
|| target instanceof HTMLTextAreaElement
|
|
3064
|
+
|| (target instanceof HTMLElement && target.isContentEditable);
|
|
3065
|
+
}
|
|
3066
|
+
function postViewState() {
|
|
3067
|
+
if (!currentState) {
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
port.postMessage({
|
|
3071
|
+
type: "canvas-page-view-state",
|
|
3072
|
+
viewport: currentState.viewport,
|
|
3073
|
+
selection: currentState.selection
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
function schedulePersist(state) {
|
|
3077
|
+
if (persistTimer !== null) {
|
|
3078
|
+
window.clearTimeout(persistTimer);
|
|
3079
|
+
}
|
|
3080
|
+
persistTimer = window.setTimeout(() => {
|
|
3081
|
+
void flushPersist(state);
|
|
3082
|
+
}, SAVE_DEBOUNCE_MS);
|
|
3083
|
+
}
|
|
3084
|
+
async function flushPersist(state = currentState) {
|
|
3085
|
+
if (!state) {
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3088
|
+
if (persistTimer !== null) {
|
|
3089
|
+
window.clearTimeout(persistTimer);
|
|
3090
|
+
persistTimer = null;
|
|
3091
|
+
}
|
|
3092
|
+
await saveCachedState(currentTabId, state);
|
|
3093
|
+
broadcastChannel?.postMessage({ type: "canvas-page:broadcast", state });
|
|
3094
|
+
}
|
|
3095
|
+
async function getCurrentTabId() {
|
|
3096
|
+
return await new Promise((resolve) => {
|
|
3097
|
+
chrome.tabs.getCurrent((tab) => {
|
|
3098
|
+
const tabId = tab?.id;
|
|
1436
3099
|
resolve(typeof tabId === "number" ? tabId : null);
|
|
1437
3100
|
});
|
|
1438
3101
|
});
|
|
@@ -1547,10 +3210,149 @@ function normalizeDocument(value) {
|
|
|
1547
3210
|
bindings: Array.isArray(value.bindings) ? value.bindings.flatMap((entry) => normalizeBinding(entry)) : [],
|
|
1548
3211
|
assets: Array.isArray(value.assets) ? value.assets.flatMap((entry) => normalizeAsset(entry)) : [],
|
|
1549
3212
|
componentInventory: Array.isArray(value.componentInventory)
|
|
1550
|
-
? value.componentInventory.
|
|
1551
|
-
: []
|
|
3213
|
+
? value.componentInventory.flatMap((entry, index) => normalizeComponentInventoryItem(entry, index))
|
|
3214
|
+
: [],
|
|
3215
|
+
tokens: normalizeTokenStore(value.tokens),
|
|
3216
|
+
meta: normalizeDocumentMeta(value.meta)
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
function normalizeComponentInventoryItem(value, index) {
|
|
3220
|
+
if (!isRecord(value)) {
|
|
3221
|
+
return [];
|
|
3222
|
+
}
|
|
3223
|
+
const id = typeof value.id === "string" ? value.id : `inventory_${index + 1}`;
|
|
3224
|
+
const name = typeof value.name === "string"
|
|
3225
|
+
? value.name
|
|
3226
|
+
: typeof value.componentName === "string"
|
|
3227
|
+
? value.componentName
|
|
3228
|
+
: id;
|
|
3229
|
+
return [{
|
|
3230
|
+
id,
|
|
3231
|
+
name,
|
|
3232
|
+
componentName: typeof value.componentName === "string" ? value.componentName : undefined,
|
|
3233
|
+
sourceKind: typeof value.sourceKind === "string" ? value.sourceKind : undefined,
|
|
3234
|
+
sourceFamily: typeof value.sourceFamily === "string" ? value.sourceFamily : undefined,
|
|
3235
|
+
origin: typeof value.origin === "string" ? value.origin : undefined,
|
|
3236
|
+
framework: normalizeComponentRef(value.framework),
|
|
3237
|
+
adapter: normalizeComponentRef(value.adapter),
|
|
3238
|
+
plugin: normalizeComponentRef(value.plugin),
|
|
3239
|
+
variants: Array.isArray(value.variants) ? value.variants.filter(isRecord) : [],
|
|
3240
|
+
props: Array.isArray(value.props) ? value.props.filter(isRecord) : [],
|
|
3241
|
+
slots: Array.isArray(value.slots) ? value.slots.filter(isRecord) : [],
|
|
3242
|
+
events: Array.isArray(value.events) ? value.events.filter(isRecord) : [],
|
|
3243
|
+
content: isRecord(value.content) ? value.content : {},
|
|
3244
|
+
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
3245
|
+
}];
|
|
3246
|
+
}
|
|
3247
|
+
function normalizeComponentRef(value) {
|
|
3248
|
+
if (!isRecord(value) || typeof value.id !== "string") {
|
|
3249
|
+
return null;
|
|
3250
|
+
}
|
|
3251
|
+
return {
|
|
3252
|
+
id: value.id,
|
|
3253
|
+
label: typeof value.label === "string" ? value.label : typeof value.name === "string" ? value.name : undefined,
|
|
3254
|
+
packageName: typeof value.packageName === "string" ? value.packageName : undefined,
|
|
3255
|
+
version: typeof value.version === "string" ? value.version : undefined,
|
|
3256
|
+
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
function normalizeTokenStore(value) {
|
|
3260
|
+
if (!isRecord(value)) {
|
|
3261
|
+
return { values: {}, collections: [], aliases: [], bindings: [], metadata: {} };
|
|
3262
|
+
}
|
|
3263
|
+
const structured = "values" in value || "collections" in value || "aliases" in value || "bindings" in value || "metadata" in value;
|
|
3264
|
+
return {
|
|
3265
|
+
values: structured && isRecord(value.values) ? value.values : structured ? {} : value,
|
|
3266
|
+
collections: Array.isArray(value.collections) ? value.collections.flatMap((entry) => normalizeTokenCollection(entry)) : [],
|
|
3267
|
+
aliases: Array.isArray(value.aliases) ? value.aliases.flatMap((entry) => normalizeTokenAlias(entry)) : [],
|
|
3268
|
+
bindings: Array.isArray(value.bindings) ? value.bindings.flatMap((entry) => normalizeTokenBinding(entry)) : [],
|
|
3269
|
+
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
3272
|
+
function normalizeTokenCollection(value) {
|
|
3273
|
+
if (!isRecord(value) || typeof value.id !== "string") {
|
|
3274
|
+
return [];
|
|
3275
|
+
}
|
|
3276
|
+
return [{
|
|
3277
|
+
id: value.id,
|
|
3278
|
+
name: typeof value.name === "string" ? value.name : value.id,
|
|
3279
|
+
items: Array.isArray(value.items) ? value.items.flatMap((entry) => normalizeTokenItem(entry)) : [],
|
|
3280
|
+
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
3281
|
+
}];
|
|
3282
|
+
}
|
|
3283
|
+
function normalizeTokenItem(value) {
|
|
3284
|
+
if (!isRecord(value) || typeof value.id !== "string" || typeof value.path !== "string") {
|
|
3285
|
+
return [];
|
|
3286
|
+
}
|
|
3287
|
+
return [{
|
|
3288
|
+
id: value.id,
|
|
3289
|
+
path: normalizeTokenPath(value.path),
|
|
3290
|
+
value: value.value,
|
|
3291
|
+
type: typeof value.type === "string" ? value.type : undefined,
|
|
3292
|
+
description: typeof value.description === "string" ? value.description : undefined,
|
|
3293
|
+
modes: Array.isArray(value.modes) ? value.modes.flatMap((entry) => normalizeTokenMode(entry)) : [],
|
|
3294
|
+
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
3295
|
+
}];
|
|
3296
|
+
}
|
|
3297
|
+
function normalizeTokenMode(value) {
|
|
3298
|
+
if (!isRecord(value) || typeof value.id !== "string" || typeof value.name !== "string") {
|
|
3299
|
+
return [];
|
|
3300
|
+
}
|
|
3301
|
+
return [{
|
|
3302
|
+
id: value.id,
|
|
3303
|
+
name: value.name,
|
|
3304
|
+
value: value.value,
|
|
3305
|
+
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
3306
|
+
}];
|
|
3307
|
+
}
|
|
3308
|
+
function normalizeTokenAlias(value) {
|
|
3309
|
+
if (!isRecord(value) || typeof value.path !== "string" || typeof value.targetPath !== "string") {
|
|
3310
|
+
return [];
|
|
3311
|
+
}
|
|
3312
|
+
return [{
|
|
3313
|
+
path: normalizeTokenPath(value.path),
|
|
3314
|
+
targetPath: normalizeTokenPath(value.targetPath),
|
|
3315
|
+
modeId: typeof value.modeId === "string" ? value.modeId : null,
|
|
3316
|
+
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
3317
|
+
}];
|
|
3318
|
+
}
|
|
3319
|
+
function normalizeTokenBinding(value) {
|
|
3320
|
+
if (!isRecord(value) || typeof value.path !== "string") {
|
|
3321
|
+
return [];
|
|
3322
|
+
}
|
|
3323
|
+
return [{
|
|
3324
|
+
path: normalizeTokenPath(value.path),
|
|
3325
|
+
nodeId: typeof value.nodeId === "string" ? value.nodeId : null,
|
|
3326
|
+
bindingId: typeof value.bindingId === "string" ? value.bindingId : null,
|
|
3327
|
+
property: typeof value.property === "string" ? value.property : null,
|
|
3328
|
+
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
3329
|
+
}];
|
|
3330
|
+
}
|
|
3331
|
+
function normalizeDocumentMeta(value) {
|
|
3332
|
+
if (!isRecord(value)) {
|
|
3333
|
+
return { imports: [], starter: null, adapterPlugins: [], pluginErrors: [], metadata: {} };
|
|
3334
|
+
}
|
|
3335
|
+
return {
|
|
3336
|
+
imports: Array.isArray(value.imports) ? value.imports.filter(isRecord) : [],
|
|
3337
|
+
starter: isRecord(value.starter) ? value.starter : null,
|
|
3338
|
+
adapterPlugins: Array.isArray(value.adapterPlugins) ? value.adapterPlugins.filter(isRecord) : [],
|
|
3339
|
+
pluginErrors: Array.isArray(value.pluginErrors)
|
|
3340
|
+
? value.pluginErrors.flatMap((entry) => normalizePluginError(entry))
|
|
3341
|
+
: [],
|
|
3342
|
+
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
1552
3343
|
};
|
|
1553
3344
|
}
|
|
3345
|
+
function normalizePluginError(value) {
|
|
3346
|
+
if (!isRecord(value) || typeof value.code !== "string" || typeof value.message !== "string") {
|
|
3347
|
+
return [];
|
|
3348
|
+
}
|
|
3349
|
+
return [{
|
|
3350
|
+
pluginId: typeof value.pluginId === "string" ? value.pluginId : undefined,
|
|
3351
|
+
code: value.code,
|
|
3352
|
+
message: value.message,
|
|
3353
|
+
details: isRecord(value.details) ? value.details : {}
|
|
3354
|
+
}];
|
|
3355
|
+
}
|
|
1554
3356
|
function normalizePage(value) {
|
|
1555
3357
|
if (!isRecord(value) || typeof value.id !== "string") {
|
|
1556
3358
|
return [];
|
|
@@ -1585,7 +3387,9 @@ function normalizeNode(value, pageId) {
|
|
|
1585
3387
|
} : { x: 0, y: 0, width: 240, height: 120 },
|
|
1586
3388
|
props: isRecord(value.props) ? value.props : {},
|
|
1587
3389
|
style: isRecord(value.style) ? value.style : {},
|
|
3390
|
+
tokenRefs: isRecord(value.tokenRefs) ? value.tokenRefs : {},
|
|
1588
3391
|
bindingRefs: isRecord(value.bindingRefs) ? value.bindingRefs : {},
|
|
3392
|
+
variantPatches: Array.isArray(value.variantPatches) ? value.variantPatches.filter(isRecord) : [],
|
|
1589
3393
|
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
1590
3394
|
}];
|
|
1591
3395
|
}
|
|
@@ -1597,6 +3401,7 @@ function normalizeBinding(value) {
|
|
|
1597
3401
|
id: value.id,
|
|
1598
3402
|
nodeId: value.nodeId,
|
|
1599
3403
|
kind: typeof value.kind === "string" ? value.kind : "component",
|
|
3404
|
+
selector: typeof value.selector === "string" ? value.selector : undefined,
|
|
1600
3405
|
componentName: typeof value.componentName === "string" ? value.componentName : undefined,
|
|
1601
3406
|
metadata: isRecord(value.metadata) ? value.metadata : {}
|
|
1602
3407
|
}];
|
|
@@ -1722,7 +3527,7 @@ function queueViewportFitIfNeeded() {
|
|
|
1722
3527
|
if (!currentState || !isDefaultEditorViewport(currentState.viewport)) {
|
|
1723
3528
|
return;
|
|
1724
3529
|
}
|
|
1725
|
-
const page =
|
|
3530
|
+
const page = getActivePage();
|
|
1726
3531
|
if (!page || page.nodes.length === 0) {
|
|
1727
3532
|
return;
|
|
1728
3533
|
}
|
|
@@ -1748,11 +3553,14 @@ function queueViewportFitIfNeeded() {
|
|
|
1748
3553
|
});
|
|
1749
3554
|
}
|
|
1750
3555
|
function resolvePreferredViewport(state) {
|
|
1751
|
-
const page = state.document.pages
|
|
1752
|
-
|
|
3556
|
+
const page = (activePageId ? state.document.pages.find((entry) => entry.id === activePageId) : null)
|
|
3557
|
+
?? state.document.pages.find((entry) => entry.id === state.selection.pageId)
|
|
3558
|
+
?? state.document.pages[0];
|
|
3559
|
+
const visibleNodes = page?.nodes.filter((node) => !isNodeHidden(node)) ?? [];
|
|
3560
|
+
if (!page || visibleNodes.length === 0) {
|
|
1753
3561
|
return { ...DEFAULT_EDITOR_VIEWPORT };
|
|
1754
3562
|
}
|
|
1755
|
-
return computeFittedViewport(
|
|
3563
|
+
return computeFittedViewport(visibleNodes, stageElement.clientWidth, stageElement.clientHeight);
|
|
1756
3564
|
}
|
|
1757
3565
|
function findNode(document, nodeId) {
|
|
1758
3566
|
if (!document) {
|