@agent-native/core 0.92.6 → 0.92.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/pure-agent-apps.mdx +22 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builtin.ts +1 -1
- package/corpus/core/src/agent/model-config.ts +30 -30
- package/corpus/core/src/agent/production-agent.ts +129 -7
- package/corpus/core/src/cli/design-connect.ts +438 -83
- package/corpus/core/src/cli/recap.ts +2 -2
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +8 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
- package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
- package/corpus/core/src/client/index.ts +4 -0
- package/corpus/core/src/client/org/TeamPage.tsx +41 -26
- package/corpus/core/src/client/use-action.ts +113 -1
- package/corpus/core/src/db/client.ts +12 -2
- package/corpus/core/src/db/create-get-db.ts +4 -1
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
- package/corpus/core/src/server/transcribe-voice.ts +1 -1
- package/corpus/core/src/usage/store.ts +13 -4
- package/corpus/core/src/vite/client.ts +38 -1
- package/corpus/templates/analytics/AGENTS.md +3 -0
- package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
- package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
- package/corpus/templates/analytics/app/global.css +11 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +20 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
- package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
- package/corpus/templates/analytics/server/db/index.ts +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
- package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
- package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
- package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/content/app/i18n-data.ts +28 -0
- package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
- package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
- package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
- package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
- package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
- package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
- package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
- package/corpus/templates/design/actions/capture-design-state.ts +2 -2
- package/corpus/templates/design/actions/connect-localhost.ts +58 -12
- package/corpus/templates/design/actions/create-component.ts +2 -18
- package/corpus/templates/design/actions/create-design-branch.ts +38 -18
- package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
- package/corpus/templates/design/actions/delete-file.ts +19 -34
- package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
- package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
- package/corpus/templates/design/actions/export-pdf.ts +2 -1
- package/corpus/templates/design/actions/export-zip.ts +9 -3
- package/corpus/templates/design/actions/generate-design.ts +107 -70
- package/corpus/templates/design/actions/get-component-details.ts +2 -18
- package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
- package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
- package/corpus/templates/design/actions/get-design.ts +2 -1
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
- package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
- package/corpus/templates/design/actions/index-components.ts +2 -18
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
- package/corpus/templates/design/actions/list-local-files.ts +9 -2
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
- package/corpus/templates/design/actions/open-component-source.ts +2 -18
- package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
- package/corpus/templates/design/actions/present-design-variants.ts +70 -70
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
- package/corpus/templates/design/actions/read-local-file.ts +9 -2
- package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
- package/corpus/templates/design/actions/rename-screen.ts +425 -0
- package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
- package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
- package/corpus/templates/design/actions/update-design.ts +376 -61
- package/corpus/templates/design/actions/update-file.ts +455 -188
- package/corpus/templates/design/actions/write-local-file.ts +10 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
- package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
- package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
- package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
- package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
- package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
- package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
- package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
- package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
- package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
- package/corpus/templates/design/app/components/design/index.ts +2 -2
- package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
- package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
- package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
- package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
- package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
- package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
- package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
- package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
- package/corpus/templates/design/app/i18n-data.ts +223 -0
- package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
- package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
- package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
- package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
- package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
- package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
- package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
- package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
- package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
- package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
- package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
- package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
- package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
- package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
- package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
- package/corpus/templates/design/server/db/index.ts +2 -52
- package/corpus/templates/design/server/db/schema.ts +16 -0
- package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
- package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
- package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
- package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
- package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
- package/corpus/templates/design/server/plugins/db.ts +19 -0
- package/corpus/templates/design/server/source-workspace.ts +12 -13
- package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
- package/corpus/templates/design/shared/screen-rename.ts +42 -0
- package/corpus/templates/design/shared/source-mode.ts +84 -4
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builtin.js +1 -1
- package/dist/agent/engine/builtin.js.map +1 -1
- package/dist/agent/model-config.d.ts +9 -9
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +30 -30
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts +60 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +106 -7
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts +30 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +359 -75
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/recap.js +2 -2
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.js +1 -1
- package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +10 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/TiptapComposer.js +1 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +6 -5
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/use-action.d.ts +32 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +68 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +3 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +7 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.js +1 -1
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +13 -4
- package/dist/usage/store.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +36 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/docs/content/pr-visual-recap.mdx +3 -3
- package/docs/content/pure-agent-apps.mdx +22 -0
- package/package.json +2 -2
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_CANVAS_MAX_ZOOM,
|
|
4
|
+
DEFAULT_CANVAS_MIN_ZOOM,
|
|
5
|
+
getCameraForBounds,
|
|
6
|
+
getFrameGroupBounds,
|
|
7
|
+
type FrameEntry,
|
|
8
|
+
} from "@shared/canvas-math";
|
|
9
|
+
import type { SetStateAction } from "react";
|
|
10
|
+
|
|
11
|
+
import { getInitialFrameGeometry } from "@/components/design/multi-screen/frame-geometry";
|
|
12
|
+
import { OVERVIEW_FRAME_WIDTH } from "@/components/design/multi-screen/overview-layout";
|
|
13
|
+
import type {
|
|
14
|
+
FrameGeometry,
|
|
15
|
+
Point,
|
|
16
|
+
} from "@/components/design/multi-screen/types";
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_OVERVIEW_ZOOM = 60;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Vector-edit foundations (P5 integration): resolves the canvas-space point
|
|
22
|
+
* where a screen's own screen-content-local (0,0) sits — i.e.
|
|
23
|
+
* `VectorEditOverlayState.originCanvas` — for a committed pen path owned by
|
|
24
|
+
* `screenId`.
|
|
25
|
+
*
|
|
26
|
+
* Mirrors the exact same fallback-merge (`getInitialFrameGeometry(index,
|
|
27
|
+
* ...)` overridden by any persisted `canvasFrameGeometryById` entry) that
|
|
28
|
+
* `getSelectedScreenGeometryForInspector` above and MultiScreenCanvas's own
|
|
29
|
+
* internal frame-geometry resolution both use, so the overlay lines up with
|
|
30
|
+
* whatever frame position is actually rendered on screen.
|
|
31
|
+
*
|
|
32
|
+
* The reserved board file (`__board__.html`, see shared/board-file.ts) is the
|
|
33
|
+
* one exception: `handleBoardDrawPrimitive`/`persistDraftPrimitive` commit
|
|
34
|
+
* board primitives with a 1:1 `{x:0,y:0,width:1,height:1}` frame (no offset
|
|
35
|
+
* subtraction — see MultiScreenCanvas's persistDraftPrimitive), so a board
|
|
36
|
+
* pen path's nodes are already in absolute canvas coordinates and
|
|
37
|
+
* `originCanvas` is `{0, 0}`.
|
|
38
|
+
*
|
|
39
|
+
* Returns `null` when the screen can't be resolved (not in `overviewScreens`
|
|
40
|
+
* and not the board file) — the caller should not enter vector-edit mode in
|
|
41
|
+
* that case.
|
|
42
|
+
*/
|
|
43
|
+
export function getScreenFrameOriginCanvas(args: {
|
|
44
|
+
screenId: string;
|
|
45
|
+
overviewScreens: Array<{
|
|
46
|
+
id: string;
|
|
47
|
+
width?: number;
|
|
48
|
+
height?: number;
|
|
49
|
+
}>;
|
|
50
|
+
canvasFrameGeometryById: CanvasFrameGeometryById;
|
|
51
|
+
boardFileId?: string | null;
|
|
52
|
+
}): Point | null {
|
|
53
|
+
if (args.boardFileId && args.screenId === args.boardFileId) {
|
|
54
|
+
return { x: 0, y: 0 };
|
|
55
|
+
}
|
|
56
|
+
const screenIndex = args.overviewScreens.findIndex(
|
|
57
|
+
(screen) => screen.id === args.screenId,
|
|
58
|
+
);
|
|
59
|
+
if (screenIndex < 0) return null;
|
|
60
|
+
const screen = args.overviewScreens[screenIndex];
|
|
61
|
+
if (!screen) return null;
|
|
62
|
+
const fallbackGeometry = getInitialFrameGeometry(screenIndex, {
|
|
63
|
+
width: screen.width ?? 1280,
|
|
64
|
+
height: screen.height ?? 2560,
|
|
65
|
+
});
|
|
66
|
+
const persistedGeometry = args.canvasFrameGeometryById[args.screenId] ?? {};
|
|
67
|
+
return {
|
|
68
|
+
x: persistedGeometry.x ?? fallbackGeometry.x,
|
|
69
|
+
y: persistedGeometry.y ?? fallbackGeometry.y,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolves every overview screen's effective canvas-space frame geometry —
|
|
75
|
+
* persisted `canvasFrameGeometryById` entry merged over the same
|
|
76
|
+
* `getInitialFrameGeometry(index, ...)` fallback `getSelectedScreenGeometryForInspector`
|
|
77
|
+
* and `getScreenFrameOriginCanvas` above already use — as `FrameEntry[]` for
|
|
78
|
+
* `@shared/canvas-math` bounds/fit helpers (`getFrameGroupBounds`,
|
|
79
|
+
* `getCameraForBounds`). Optionally includes the board frame (real Figma has
|
|
80
|
+
* no board-file equivalent, but a design with only board primitives and no
|
|
81
|
+
* screens should still have something to fit to).
|
|
82
|
+
*/
|
|
83
|
+
export function getAllScreenFrameEntries(args: {
|
|
84
|
+
overviewScreens: Array<{
|
|
85
|
+
id: string;
|
|
86
|
+
width?: number;
|
|
87
|
+
height?: number;
|
|
88
|
+
}>;
|
|
89
|
+
canvasFrameGeometryById: CanvasFrameGeometryById;
|
|
90
|
+
boardFrameGeometry?: FrameGeometry;
|
|
91
|
+
boardFileId?: string | null;
|
|
92
|
+
}): FrameEntry[] {
|
|
93
|
+
const entries: FrameEntry[] = args.overviewScreens.map((screen, index) => {
|
|
94
|
+
const fallbackGeometry = getInitialFrameGeometry(index, {
|
|
95
|
+
width: screen.width ?? 1280,
|
|
96
|
+
height: screen.height ?? 2560,
|
|
97
|
+
});
|
|
98
|
+
const persistedGeometry = args.canvasFrameGeometryById[screen.id] ?? {};
|
|
99
|
+
return {
|
|
100
|
+
id: screen.id,
|
|
101
|
+
geometry: { ...fallbackGeometry, ...persistedGeometry },
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
if (
|
|
105
|
+
args.boardFileId &&
|
|
106
|
+
args.boardFrameGeometry &&
|
|
107
|
+
!entries.some((entry) => entry.id === args.boardFileId)
|
|
108
|
+
) {
|
|
109
|
+
entries.push({ id: args.boardFileId, geometry: args.boardFrameGeometry });
|
|
110
|
+
}
|
|
111
|
+
return entries;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Hit-tests a canvas-space point (the same coordinate system
|
|
116
|
+
* `getAllScreenFrameEntries` resolves frame geometry into) against every real
|
|
117
|
+
* screen frame — excluding `excludeFileId` (the board file itself, which is
|
|
118
|
+
* only a paste-target FALLBACK, never a hit-testable "screen" to drop into).
|
|
119
|
+
* Used by overview image/content paste (see handlePastedImageFiles) to decide
|
|
120
|
+
* whether a paste anchor point lands on a screen — and if so, which one — so
|
|
121
|
+
* the paste can be inserted into that screen's own local coordinate space
|
|
122
|
+
* instead of always landing on the shared board file.
|
|
123
|
+
*
|
|
124
|
+
* When multiple frames overlap at the point (frames can be freely dragged on
|
|
125
|
+
* top of each other), the LAST matching entry wins — `overviewScreens`/
|
|
126
|
+
* `getAllScreenFrameEntries` order screens back-to-front by z, matching
|
|
127
|
+
* MultiScreenCanvas's own render order, so this picks the topmost screen at
|
|
128
|
+
* that point, same as a real click would hit.
|
|
129
|
+
*/
|
|
130
|
+
export function findScreenFrameAtCanvasPoint(
|
|
131
|
+
point: { x: number; y: number },
|
|
132
|
+
frames: FrameEntry[],
|
|
133
|
+
excludeFileId?: string | null,
|
|
134
|
+
): FrameEntry | null {
|
|
135
|
+
let match: FrameEntry | null = null;
|
|
136
|
+
for (const frame of frames) {
|
|
137
|
+
if (excludeFileId && frame.id === excludeFileId) continue;
|
|
138
|
+
const { x, y, width, height } = frame.geometry;
|
|
139
|
+
if (
|
|
140
|
+
point.x >= x &&
|
|
141
|
+
point.x <= x + width &&
|
|
142
|
+
point.y >= y &&
|
|
143
|
+
point.y <= y + height
|
|
144
|
+
) {
|
|
145
|
+
match = frame;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return match;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Figma's Shift+1 ("Zoom to fit") / Shift+2 ("Zoom to selection"): compute the
|
|
153
|
+
* real fit zoom + canvas offset for a set of frames within the given viewport
|
|
154
|
+
* size, via the shared `getCameraForBounds` fit-viewport math (already used
|
|
155
|
+
* nowhere else in this app — this is the first caller — so this is a thin
|
|
156
|
+
* wrapper that resolves the frame-entries-to-bounds step and clamps to the
|
|
157
|
+
* shared canvas zoom range). Returns null when there is nothing to fit
|
|
158
|
+
* (no frames, or a degenerate/zero-size viewport).
|
|
159
|
+
*/
|
|
160
|
+
export function computeFitCameraForFrames(
|
|
161
|
+
frames: readonly FrameEntry[],
|
|
162
|
+
viewport: { width: number; height: number },
|
|
163
|
+
options?: { paddingScreenPx?: number },
|
|
164
|
+
) {
|
|
165
|
+
if (frames.length === 0) return null;
|
|
166
|
+
if (viewport.width <= 0 || viewport.height <= 0) return null;
|
|
167
|
+
const bounds = getFrameGroupBounds(frames);
|
|
168
|
+
if (!bounds) return null;
|
|
169
|
+
return getCameraForBounds(bounds, viewport, {
|
|
170
|
+
paddingScreenPx: options?.paddingScreenPx ?? 64,
|
|
171
|
+
minZoom: DEFAULT_CANVAS_MIN_ZOOM,
|
|
172
|
+
maxZoom: DEFAULT_CANVAS_MAX_ZOOM,
|
|
173
|
+
fallbackZoom: 100,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function getOverviewZoomScale(args: {
|
|
178
|
+
frameWidth: number | null | undefined;
|
|
179
|
+
sourceWidth: number | null | undefined;
|
|
180
|
+
}) {
|
|
181
|
+
const frameWidth =
|
|
182
|
+
typeof args.frameWidth === "number" && args.frameWidth > 0
|
|
183
|
+
? args.frameWidth
|
|
184
|
+
: OVERVIEW_FRAME_WIDTH;
|
|
185
|
+
const sourceWidth =
|
|
186
|
+
typeof args.sourceWidth === "number" && args.sourceWidth > 0
|
|
187
|
+
? args.sourceWidth
|
|
188
|
+
: 1280;
|
|
189
|
+
return frameWidth / sourceWidth;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function getOverviewDisplayZoom(
|
|
193
|
+
canvasZoom: number,
|
|
194
|
+
overviewZoomScale: number,
|
|
195
|
+
) {
|
|
196
|
+
const scale = overviewZoomScale > 0 ? overviewZoomScale : 1;
|
|
197
|
+
return canvasZoom * scale;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function getOverviewCanvasZoom(
|
|
201
|
+
displayZoom: number,
|
|
202
|
+
overviewZoomScale: number,
|
|
203
|
+
) {
|
|
204
|
+
const scale = overviewZoomScale > 0 ? overviewZoomScale : 1;
|
|
205
|
+
return displayZoom / scale;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function getDefaultOverviewCanvasZoom(overviewZoomScale: number) {
|
|
209
|
+
return getOverviewCanvasZoom(DEFAULT_OVERVIEW_ZOOM, overviewZoomScale);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Board-zoom-corruption fix (observed in the wild as a 10241.49% displayed
|
|
214
|
+
* zoom): the overview zoom SCALE basis must always be a real overview screen.
|
|
215
|
+
* When `activeFileId` flips to the board file (creating a text primitive on
|
|
216
|
+
* the empty board, clicking a board element, …) or to any non-screen file
|
|
217
|
+
* (e.g. a CSS support file), the old `activeFile?.id ?? activeFileId ??
|
|
218
|
+
* overviewScreens[0]?.id` resolution made that file the basis: it has no
|
|
219
|
+
* entry in `overviewScreens` OR `canvasFrames`, so BOTH getOverviewZoomScale
|
|
220
|
+
* inputs fell back (320/1280 = 0.25) while `explicitOverviewCanvasZoom`
|
|
221
|
+
* stayed pinned to a value established under the real screen's scale —
|
|
222
|
+
* garbage displayed zoom. This helper only accepts a candidate that is a
|
|
223
|
+
* known overview screen (and never the board, even if a future
|
|
224
|
+
* `overviewScreens` regression let the board leak into the list), otherwise
|
|
225
|
+
* it falls back to the first real overview screen.
|
|
226
|
+
*/
|
|
227
|
+
export function resolveOverviewZoomBasisScreenId(args: {
|
|
228
|
+
candidateFileId: string | null | undefined;
|
|
229
|
+
boardFileId: string | null | undefined;
|
|
230
|
+
overviewScreenIds: readonly string[];
|
|
231
|
+
}): string | null {
|
|
232
|
+
const boardId = args.boardFileId ?? null;
|
|
233
|
+
const candidate = args.candidateFileId ?? null;
|
|
234
|
+
if (
|
|
235
|
+
candidate &&
|
|
236
|
+
candidate !== boardId &&
|
|
237
|
+
args.overviewScreenIds.includes(candidate)
|
|
238
|
+
) {
|
|
239
|
+
return candidate;
|
|
240
|
+
}
|
|
241
|
+
return (
|
|
242
|
+
args.overviewScreenIds.find((screenId) => screenId !== boardId) ?? null
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Displayed overview zooms below this are unrenderable garbage, not a
|
|
248
|
+
* deliberate camera position — the wheel/pinch path can't legitimately reach
|
|
249
|
+
* them (MultiScreenCanvas clamps its canvas zoom well above the product of
|
|
250
|
+
* this with any real screen scale).
|
|
251
|
+
*/
|
|
252
|
+
export const MIN_RENDERABLE_OVERVIEW_DISPLAY_ZOOM = 0.01;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Board-zoom-corruption fix, defensive layer: when the zoom-scale BASIS
|
|
256
|
+
* IDENTITY changes (a different screen becomes the basis), a pinned
|
|
257
|
+
* `explicitOverviewCanvasZoom` was established under the OLD basis' scale.
|
|
258
|
+
* A normal screen-to-screen basis change only shifts the displayed zoom
|
|
259
|
+
* label by the two screens' native-scale ratio (camera untouched) — that is
|
|
260
|
+
* intended Figma-like behavior and must NOT reset the camera. But if the new
|
|
261
|
+
* basis' scale turns the pinned value into a displayed zoom outside the
|
|
262
|
+
* editor's absolute zoom range (non-finite, unrenderably small, or above
|
|
263
|
+
* DEFAULT_CANVAS_MAX_ZOOM), the pin is provably garbage for this basis:
|
|
264
|
+
* invalidate it so the derivation re-anchors at the default overview zoom
|
|
265
|
+
* instead of showing a corrupted percentage.
|
|
266
|
+
*/
|
|
267
|
+
export function shouldResetExplicitOverviewZoomOnBasisChange(args: {
|
|
268
|
+
previousBasisScreenId: string | null;
|
|
269
|
+
nextBasisScreenId: string | null;
|
|
270
|
+
explicitOverviewCanvasZoom: number | null;
|
|
271
|
+
nextOverviewZoomScale: number;
|
|
272
|
+
}): boolean {
|
|
273
|
+
if (args.explicitOverviewCanvasZoom === null) return false;
|
|
274
|
+
if (args.previousBasisScreenId === args.nextBasisScreenId) return false;
|
|
275
|
+
const nextDisplayZoom = getOverviewDisplayZoom(
|
|
276
|
+
args.explicitOverviewCanvasZoom,
|
|
277
|
+
args.nextOverviewZoomScale,
|
|
278
|
+
);
|
|
279
|
+
return (
|
|
280
|
+
!Number.isFinite(nextDisplayZoom) ||
|
|
281
|
+
nextDisplayZoom < MIN_RENDERABLE_OVERVIEW_DISPLAY_ZOOM ||
|
|
282
|
+
nextDisplayZoom > DEFAULT_CANVAS_MAX_ZOOM
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Final sanity clamp on the DISPLAYED overview zoom (the number the zoom
|
|
288
|
+
* field shows and zoom-relative flows consume). Non-finite/non-positive
|
|
289
|
+
* products (a corrupted basis flip) fall back to the default overview zoom;
|
|
290
|
+
* anything above the editor's absolute max zoom is capped there. The lower
|
|
291
|
+
* bound is deliberately NOT floored to DEFAULT_CANVAS_MIN_ZOOM: a legit
|
|
292
|
+
* canvas zoom near the minimum times a sub-1 screen scale can display below
|
|
293
|
+
* 2% and must not be misreported (it would desync the displayed value from
|
|
294
|
+
* the real camera and drift zoom-relative round-trips).
|
|
295
|
+
*/
|
|
296
|
+
export function clampOverviewDisplayZoom(displayZoom: number): number {
|
|
297
|
+
if (!Number.isFinite(displayZoom) || displayZoom <= 0) {
|
|
298
|
+
return DEFAULT_OVERVIEW_ZOOM;
|
|
299
|
+
}
|
|
300
|
+
return Math.min(displayZoom, DEFAULT_CANVAS_MAX_ZOOM);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function resolveZoomUpdate(
|
|
304
|
+
update: SetStateAction<number>,
|
|
305
|
+
current: number,
|
|
306
|
+
) {
|
|
307
|
+
return typeof update === "function" ? update(current) : update;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Figma-style zoom stepping: doubling/halving anchors relative to 100%
|
|
312
|
+
* (…6.25, 12.5, 25, 50, 100, 200, 400, 800…) instead of a small fixed preset
|
|
313
|
+
* list that stalls once it runs out of entries. Used by the keyboard/menu
|
|
314
|
+
* zoom in/out handlers so they never stop stepping above/below the old
|
|
315
|
+
* ZOOM_PRESETS bounds — clamped to the shared canvas zoom range so the
|
|
316
|
+
* result always matches what the wheel/pinch zoom path allows.
|
|
317
|
+
*/
|
|
318
|
+
export function getNextZoomStepUp(
|
|
319
|
+
current: number,
|
|
320
|
+
{ min = DEFAULT_CANVAS_MIN_ZOOM, max = DEFAULT_CANVAS_MAX_ZOOM } = {},
|
|
321
|
+
): number {
|
|
322
|
+
if (!Number.isFinite(current) || current <= 0) return Math.min(100, max);
|
|
323
|
+
// Anchor exponent: how many doublings 100 * 2^n is from `current`. Round up
|
|
324
|
+
// (with a small epsilon so an exact anchor doesn't get skipped by float
|
|
325
|
+
// error) to find the next anchor strictly greater than current.
|
|
326
|
+
const exponent = Math.floor(Math.log2(current / 100) + 1e-9) + 1;
|
|
327
|
+
const next = 100 * Math.pow(2, exponent);
|
|
328
|
+
return clampZoom(next, min, max);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function getNextZoomStepDown(
|
|
332
|
+
current: number,
|
|
333
|
+
{ min = DEFAULT_CANVAS_MIN_ZOOM, max = DEFAULT_CANVAS_MAX_ZOOM } = {},
|
|
334
|
+
): number {
|
|
335
|
+
if (!Number.isFinite(current) || current <= 0) return Math.max(100, min);
|
|
336
|
+
const exponent = Math.ceil(Math.log2(current / 100) - 1e-9) - 1;
|
|
337
|
+
const prev = 100 * Math.pow(2, exponent);
|
|
338
|
+
return clampZoom(prev, min, max);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function clampZoom(
|
|
342
|
+
zoom: number,
|
|
343
|
+
min: number = DEFAULT_CANVAS_MIN_ZOOM,
|
|
344
|
+
max: number = DEFAULT_CANVAS_MAX_ZOOM,
|
|
345
|
+
): number {
|
|
346
|
+
if (!Number.isFinite(zoom)) return min;
|
|
347
|
+
return Math.min(max, Math.max(min, zoom));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Camera-restore sanity ceiling (item 5): the shared canvas zoom range
|
|
352
|
+
* (DEFAULT_CANVAS_MIN_ZOOM..DEFAULT_CANVAS_MAX_ZOOM, up to 25600%) is sized
|
|
353
|
+
* for a deliberate "zoom in on this one selection" gesture, not for restoring
|
|
354
|
+
* a "where I left off" per-screen memory. A corrupted/degenerate remembered
|
|
355
|
+
* zoom (e.g. 1506%/3968%, observed in the field) is technically within that
|
|
356
|
+
* absolute range but re-entering a screen at it shows an unrecognizable
|
|
357
|
+
* close-up of whatever happens to sit at the scroll container's default
|
|
358
|
+
* (0,0) origin — effectively "lands on empty canvas" from the user's
|
|
359
|
+
* perspective, since single-screen mode has no separate persisted pan
|
|
360
|
+
* position to also restore in sync. Restoring a saner default here is this
|
|
361
|
+
* app's equivalent of a fit-all fallback.
|
|
362
|
+
*/
|
|
363
|
+
export const MAX_SANE_SCREEN_ENTRY_ZOOM = 400;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Per-screen zoom memory: resolve which zoom `enterSingleScreen` should
|
|
367
|
+
* restore for a given target screen id. Looks up the screen's last-
|
|
368
|
+
* remembered zoom in the `screenZoomById` map (populated as the user zooms
|
|
369
|
+
* while a screen is focused); falls back to `defaultZoom` (FOCUSED_SCREEN_ZOOM
|
|
370
|
+
* in practice) for a screen's first visit, when no target id is known, or
|
|
371
|
+
* when the remembered value is missing/non-finite/outside the shared canvas
|
|
372
|
+
* zoom range, or so extreme (see MAX_SANE_SCREEN_ENTRY_ZOOM) that restoring it
|
|
373
|
+
* would show unrecognizable content instead of the screen the user expects.
|
|
374
|
+
* Pure/extracted so this lookup rule is unit-testable without mounting the
|
|
375
|
+
* full DesignEditor component.
|
|
376
|
+
*/
|
|
377
|
+
export function resolveScreenEntryZoom(
|
|
378
|
+
targetFileId: string | null | undefined,
|
|
379
|
+
screenZoomById: ReadonlyMap<string, number>,
|
|
380
|
+
defaultZoom: number,
|
|
381
|
+
): number {
|
|
382
|
+
if (!targetFileId) return defaultZoom;
|
|
383
|
+
const remembered = screenZoomById.get(targetFileId);
|
|
384
|
+
if (remembered === undefined) return defaultZoom;
|
|
385
|
+
if (!Number.isFinite(remembered) || remembered <= 0) return defaultZoom;
|
|
386
|
+
const clamped = clampZoom(remembered);
|
|
387
|
+
return clamped > MAX_SANE_SCREEN_ENTRY_ZOOM ? defaultZoom : clamped;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* BP-DEEP v2 item 2 (full-view mode flicker) — the Figma-style "zoom far
|
|
392
|
+
* enough out of a focused screen and you pop back to the overview" heuristic
|
|
393
|
+
* must only fire when the user actually ZOOMS OUT ACROSS the threshold while
|
|
394
|
+
* already settled in single-screen view. The previous level-triggered check
|
|
395
|
+
* (`zoom < threshold` → pop) also fired on ENTRY: enterSingleScreen restores
|
|
396
|
+
* the screen's remembered zoom, and when that remembered value was below the
|
|
397
|
+
* threshold the editor flashed into full view for one frame and immediately
|
|
398
|
+
* bounced back to overview — Steve's "Full view flickers then bounces" bug.
|
|
399
|
+
* Edge-triggering on the previous observed single-view zoom means entry
|
|
400
|
+
* (previousZoom === null — the tracking ref resets whenever single view is
|
|
401
|
+
* left) can never bounce, and an entry that legitimately restores e.g. 30%
|
|
402
|
+
* stays a focused 30% view until the user crosses the threshold from above.
|
|
403
|
+
* Pure/extracted for unit tests.
|
|
404
|
+
*/
|
|
405
|
+
export function shouldPopToOverviewOnZoomOut(args: {
|
|
406
|
+
/** Last zoom observed while ALREADY in single view; null right after
|
|
407
|
+
* entering single view (or when single view isn't active/edit-mode). */
|
|
408
|
+
previousZoom: number | null;
|
|
409
|
+
zoom: number;
|
|
410
|
+
threshold: number;
|
|
411
|
+
}): boolean {
|
|
412
|
+
if (!Number.isFinite(args.zoom)) return false;
|
|
413
|
+
if (args.zoom >= args.threshold) return false;
|
|
414
|
+
return args.previousZoom !== null && args.previousZoom >= args.threshold;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Fix-wave (zoom presets) — an explicit destination zoom (a "Zoom to
|
|
419
|
+
* 50/100/200%" menu preset, or a typed zoom-% commit) crosses
|
|
420
|
+
* OVERVIEW_ZOOM_THRESHOLD from above just as easily as a real zoom-out
|
|
421
|
+
* gesture does (e.g. the default single-view zoom is 100%, and "Zoom to 50%"
|
|
422
|
+
* lands under the 60% threshold) — `shouldPopToOverviewOnZoomOut` alone can't
|
|
423
|
+
* tell the two apart from the raw before/after zoom numbers. Callers that
|
|
424
|
+
* fire an explicit destination zoom mark the NEXT zoom-change observation as
|
|
425
|
+
* suppressed; this combines that one-shot flag with the edge-trigger check so
|
|
426
|
+
* "Zoom to 50%" always stays in single view regardless of the zoom it
|
|
427
|
+
* started from, while continuous zoom-out (scroll/pinch/the zoom-out button)
|
|
428
|
+
* is untouched and still pops at the threshold like Figma.
|
|
429
|
+
*/
|
|
430
|
+
export function shouldPopToOverviewOnZoomChange(args: {
|
|
431
|
+
previousZoom: number | null;
|
|
432
|
+
zoom: number;
|
|
433
|
+
threshold: number;
|
|
434
|
+
suppressExplicitZoom: boolean;
|
|
435
|
+
}): boolean {
|
|
436
|
+
if (args.suppressExplicitZoom) return false;
|
|
437
|
+
return shouldPopToOverviewOnZoomOut(args);
|
|
438
|
+
}
|