nudge-ui 0.2.1 → 0.2.3
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 +164 -46
- package/dist/client.mjs +252 -54
- package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
- package/dist/css/value-semantics/colorSemantics.js +17 -0
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
- package/dist/hosts/astro/clientTransport.js +41 -32
- package/dist/hosts/astro/integration.d.ts +2 -2
- package/dist/hosts/astro/integration.js +9 -4
- package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
- package/dist/hosts/astro/markdownIdentity.js +81 -0
- package/dist/hosts/environment.d.ts +15 -0
- package/dist/hosts/environment.js +16 -0
- package/dist/hosts/next/manifest.js +1 -1
- package/dist/hosts/next/sidecar.js +12 -2
- package/dist/hosts/next/wrapper.d.ts +2 -0
- package/dist/hosts/next/wrapper.js +2 -1
- package/dist/hosts/projectBridge.d.ts +1 -0
- package/dist/hosts/projectBridge.js +1 -0
- package/dist/hosts/static/manifest.js +3 -1
- package/dist/hosts/static/server.js +4 -0
- package/dist/hosts/vite/vite.d.ts +4 -0
- package/dist/hosts/vite/vite.js +28 -5
- package/dist/inspector/agent/McpConnectionDialog.d.ts +3 -2
- package/dist/inspector/agent/McpConnectionDialog.js +23 -22
- package/dist/inspector/agent/activity.d.ts +18 -0
- package/dist/inspector/agent/activity.js +70 -0
- package/dist/inspector/agent/client.d.ts +8 -0
- package/dist/inspector/agent/client.js +65 -5
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +9 -2
- package/dist/inspector/agent/httpTransport.d.ts +6 -0
- package/dist/inspector/agent/httpTransport.js +50 -3
- package/dist/inspector/agent/protocol.d.ts +9 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +11 -1
- package/dist/inspector/agent/verification.js +53 -25
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
- package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
- package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
- package/dist/inspector/canvas/CanvasCard.js +133 -38
- package/dist/inspector/canvas/CanvasElementOverlay.js +200 -22
- package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
- package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
- package/dist/inspector/canvas/CanvasToolbar.js +8 -4
- package/dist/inspector/canvas/CanvasWorkspace.js +243 -56
- package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
- package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
- package/dist/inspector/canvas/agentPresentation.js +64 -52
- package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
- package/dist/inspector/canvas/canvasGestures.js +1 -1
- package/dist/inspector/canvas/canvasStore.d.ts +46 -82
- package/dist/inspector/canvas/canvasStore.js +251 -284
- package/dist/inspector/canvas/frameContent.d.ts +21 -0
- package/dist/inspector/canvas/frameContent.js +44 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +34 -4
- package/dist/inspector/canvas/frameProtocol.js +9 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +5 -3
- package/dist/inspector/canvas/layout.d.ts +52 -0
- package/dist/inspector/canvas/layout.js +106 -0
- package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
- package/dist/inspector/canvas/linkEligibility.js +0 -29
- package/dist/inspector/canvas/projection.d.ts +15 -5
- package/dist/inspector/canvas/projection.js +69 -13
- package/dist/inspector/canvas/rendererBootstrap.js +45 -4
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +102 -45
- package/dist/inspector/canvas/rendererStylesheet.js +2 -2
- package/dist/inspector/canvas/resizeHandleScale.js +1 -1
- package/dist/inspector/canvas/sessionStore.d.ts +11 -26
- package/dist/inspector/canvas/sessionStore.js +43 -183
- package/dist/inspector/canvas/staleChangeDetector.js +9 -3
- package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
- package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
- package/dist/inspector/canvas/viewStore.d.ts +38 -0
- package/dist/inspector/canvas/viewStore.js +104 -0
- package/dist/inspector/canvas/wheelPan.d.ts +8 -0
- package/dist/inspector/canvas/wheelPan.js +8 -0
- package/dist/inspector/changes/changesLog.d.ts +6 -5
- package/dist/inspector/changes/changesLog.js +40 -32
- package/dist/inspector/changes/draftChanges.d.ts +56 -0
- package/dist/inspector/changes/draftChanges.js +300 -0
- package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
- package/dist/inspector/changes/previewDiagnostics.js +11 -25
- package/dist/inspector/clientManifest.d.ts +3 -1
- package/dist/inspector/clientManifest.js +1 -0
- package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
- package/dist/inspector/comments/CommentsOverlay.js +250 -0
- package/dist/inspector/comments/element.d.ts +6 -0
- package/dist/inspector/comments/element.js +47 -0
- package/dist/inspector/comments/store.d.ts +29 -0
- package/dist/inspector/comments/store.js +121 -0
- package/dist/inspector/comments/verification.d.ts +5 -0
- package/dist/inspector/comments/verification.js +52 -0
- package/dist/inspector/drafts/model.d.ts +35 -0
- package/dist/inspector/drafts/model.js +45 -0
- package/dist/inspector/drafts/store.d.ts +28 -0
- package/dist/inspector/drafts/store.js +231 -0
- package/dist/inspector/index.js +26 -35
- package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
- package/dist/inspector/iterations/capture.d.ts +2 -0
- package/dist/inspector/iterations/capture.js +173 -0
- package/dist/inspector/iterations/client.d.ts +7 -0
- package/dist/inspector/iterations/client.js +52 -0
- package/dist/inspector/iterations/coordinator.d.ts +15 -0
- package/dist/inspector/iterations/coordinator.js +130 -0
- package/dist/inspector/iterations/prompt.d.ts +3 -0
- package/dist/inspector/iterations/prompt.js +26 -0
- package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
- package/dist/inspector/overlay/DomNavigator.js +46 -0
- package/dist/inspector/overlay/InspectorOverlay.js +6 -2
- package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
- package/dist/inspector/overlay/clickPolicy.js +34 -3
- package/dist/inspector/overlay/elementSelector.d.ts +1 -1
- package/dist/inspector/overlay/elementSelector.js +18 -7
- package/dist/inspector/overlay/interactionStyles.js +3 -1
- package/dist/inspector/overlay/spacingGestures.d.ts +15 -3
- package/dist/inspector/overlay/spacingGestures.js +88 -13
- package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
- package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
- package/dist/inspector/projection/renderedInstance.d.ts +2 -1
- package/dist/inspector/projection/renderedInstance.js +9 -5
- package/dist/inspector/projection/structuralProjection.d.ts +2 -1
- package/dist/inspector/projection/structuralProjection.js +19 -15
- package/dist/inspector/projection/textProjection.d.ts +2 -1
- package/dist/inspector/projection/textProjection.js +8 -4
- package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
- package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
- package/dist/inspector/prompt/clipboardHandoff.js +92 -143
- package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
- package/dist/inspector/prompt/generatePrompt.js +17 -16
- package/dist/inspector/runtime/runtimeConfig.d.ts +10 -1
- package/dist/inspector/runtime/runtimeConfig.js +24 -0
- package/dist/inspector/selection/hierarchy.d.ts +6 -0
- package/dist/inspector/selection/hierarchy.js +8 -0
- package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
- package/dist/inspector/settings/SettingsDialog.js +22 -9
- package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
- package/dist/inspector/shell/AgentStatusButton.js +36 -0
- package/dist/inspector/shell/AppShell.js +4 -2
- package/dist/inspector/shell/ChangesLog.js +47 -27
- package/dist/inspector/shell/CopyPromptButton.js +146 -90
- package/dist/inspector/shell/EmptyState.js +9 -8
- package/dist/inspector/shell/InspectorShell.js +14 -25
- package/dist/inspector/shell/PeekOriginalButton.js +24 -23
- package/dist/inspector/shell/originalPreview.js +5 -5
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +11 -0
- package/dist/inspector/sketch/SketchEditor.js +14 -14
- package/dist/inspector/sketch/SketchEntryButton.js +1 -1
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
- package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
- package/dist/inspector/sketch/SketchOverlay.js +7 -7
- package/dist/inspector/sketch/SketchWorkspace.js +18 -12
- package/dist/inspector/sketch/interaction.d.ts +1 -0
- package/dist/inspector/sketch/interaction.js +8 -4
- package/dist/inspector/sketch/model.d.ts +5 -0
- package/dist/inspector/sketch/model.js +6 -2
- package/dist/inspector/sketch/raster.d.ts +1 -2
- package/dist/inspector/sketch/raster.js +68 -4
- package/dist/inspector/sketch/store.js +48 -23
- package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
- package/dist/inspector/styleEditors/BorderEditor.js +49 -78
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
- package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
- package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
- package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
- package/dist/inspector/styleEditors/ColorPicker.js +10 -4
- package/dist/inspector/styleEditors/GapField.js +3 -5
- package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
- package/dist/inspector/styleEditors/GridChildSection.js +18 -9
- package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
- package/dist/inspector/styleEditors/GridPicker.js +8 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/GridValueField.js +19 -14
- package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
- package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
- package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
- package/dist/inspector/styleEditors/LayoutSection.js +32 -11
- package/dist/inspector/styleEditors/SpacingBox.js +16 -9
- package/dist/inspector/styleEditors/Typography.js +11 -3
- package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
- package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
- package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
- package/dist/inspector/styleEditors/gradientValue.js +114 -0
- package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
- package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
- package/dist/inspector/styleEditors/layoutValue.js +0 -42
- package/dist/inspector/styleEditors/nudgeValue.d.ts +4 -4
- package/dist/inspector/styleEditors/nudgeValue.js +41 -11
- package/dist/inspector/tokens/TokenField.d.ts +7 -0
- package/dist/inspector/tokens/TokenField.js +49 -22
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- package/dist/inspector/tokens/TokensPanel.js +2 -1
- package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
- package/dist/inspector/tokens/dragPointerLock.js +70 -0
- package/dist/inspector/tokens/editActions.d.ts +1 -1
- package/dist/inspector/tokens/editActions.js +1 -1
- package/dist/inspector/ui/AtRuleContext.js +4 -6
- package/dist/inspector/ui/Button.d.ts +6 -2
- package/dist/inspector/ui/Button.js +4 -3
- package/dist/inspector/ui/ControlSurface.d.ts +2 -2
- package/dist/inspector/ui/ControlSurface.js +4 -3
- package/dist/inspector/ui/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
- package/dist/inspector/ui/InlineStyleWarning.js +13 -0
- package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
- package/dist/inspector/ui/InspectorPopover.js +4 -5
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +8 -7
- package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
- package/dist/inspector/ui/SegmentedControl.js +2 -2
- package/dist/inspector/ui/Select.d.ts +6 -1
- package/dist/inspector/ui/Select.js +7 -11
- package/dist/inspector/ui/SideValuesField.js +1 -1
- package/dist/inspector/ui/Tooltip.d.ts +26 -0
- package/dist/inspector/ui/Tooltip.js +32 -0
- package/dist/inspector/ui/classNames.d.ts +2 -0
- package/dist/inspector/ui/classNames.js +4 -0
- package/dist/inspector/ui/customCursors.d.ts +23 -0
- package/dist/inspector/ui/customCursors.js +122 -0
- package/dist/inspector/ui/dragCursor.d.ts +6 -0
- package/dist/inspector/ui/dragCursor.js +13 -0
- package/dist/inspector/ui/portalContainer.d.ts +2 -0
- package/dist/inspector/ui/portalContainer.js +6 -0
- package/dist/inspector/ui/styles.js +34 -24
- package/dist/inspector/workspace/commands.d.ts +9 -0
- package/dist/inspector/workspace/commands.js +82 -0
- package/dist/inspector/workspace/controller.d.ts +4 -0
- package/dist/inspector/workspace/controller.js +66 -0
- package/dist/inspector/workspace/handoff.d.ts +42 -0
- package/dist/inspector/workspace/handoff.js +115 -0
- package/dist/inspector/workspace/timeline.d.ts +30 -0
- package/dist/inspector/workspace/timeline.js +108 -0
- package/dist/internal/agent-protocol.d.ts +19 -2
- package/dist/internal/agent-protocol.js +13 -0
- package/dist/nudge-ui.mjs +560 -299
- package/dist/project/artifacts.d.ts +13 -0
- package/dist/project/artifacts.js +253 -0
- package/dist/transport/artifacts.d.ts +8 -0
- package/dist/transport/artifacts.js +9 -0
- package/dist/transport/editor.d.ts +20 -1
- package/dist/transport/editor.js +44 -11
- package/dist/transport/routes.d.ts +6 -1
- package/dist/transport/routes.js +6 -1
- package/package.json +6 -2
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -43
- package/dist/inspector/changes/workspaceChanges.js +0 -239
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { getCanonicalFrameProjection } from "./projection.js";
|
|
3
|
+
import { getDraftForCard } from "../drafts/store.js";
|
|
4
|
+
import { iterationId, contentSourceUrl, getLinkedFrameIds } from "./frameContent.js";
|
|
2
5
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
6
|
import { IconCornerLeftUp } from "@tabler/icons-react";
|
|
4
|
-
import { useCanvasCards, activateIframeWorkspace,
|
|
7
|
+
import { useCanvasCards, activateIframeWorkspace, getSelectedCardId, getFocusedCardId, getCanvasCards, batchCanvasChanges, fitAllCards, focusCanvasCards, focusCard, useSelectedCardId, useFocusedCardId, resizeCard, setCardPosition, updateCardTitle, addCanvasCard, selectCard, deselectCard, CARD_GAP, updateCardUrl, } from "./canvasStore.js";
|
|
8
|
+
import { setBoardCamera, getBoardCamera, setCanvasPresentation, hasFitAllRan, useBoardCamera, useCanvasPresentation, useCanvasPresentationTransitioning, useCanvasLayoutTransitioning, useTemporaryAppInteraction, setTemporaryAppInteraction, } from "./viewStore.js";
|
|
5
9
|
import { CanvasCard } from "./CanvasCard.js";
|
|
10
|
+
import { useCanvasLayoutAnimation } from "./useCanvasLayoutAnimation.js";
|
|
6
11
|
import { Button } from "../ui/Button.js";
|
|
7
|
-
import { useCanvasMode } from "./
|
|
12
|
+
import { useCanvasMode } from "./viewStore.js";
|
|
8
13
|
import { subscribeChanges } from "../changes/changesLog.js";
|
|
9
14
|
import { subscribeOriginalPreview, isOriginalPreviewActive } from "../shell/originalPreview.js";
|
|
10
15
|
import { recordCanvasStructuralProjectionReports } from "../projection/structuralProjection.js";
|
|
@@ -12,31 +17,37 @@ import { recordCanvasRenderedInstanceProjectionReports } from "../projection/ren
|
|
|
12
17
|
import { recordCanvasTextProjectionReports } from "../projection/textProjection.js";
|
|
13
18
|
import { getChangesList, isPreviewableChange } from "../changes/changesLog.js";
|
|
14
19
|
import { changeKey } from "../changes/model.js";
|
|
15
|
-
import {
|
|
20
|
+
import { getActiveDraftChanges } from "../changes/draftChanges.js";
|
|
16
21
|
import { beginPreviewAttempt, publishPreviewDiagnostic } from "../changes/previewDiagnostics.js";
|
|
17
22
|
import { verifyManagedStyleProjection } from "../changes/managedStyleProjection.js";
|
|
18
23
|
import { getCanvasPreviewDocument, getRegisteredFrames, isCanvasCanonicalProjectionRevisionCurrent, PROJECT_ID, projectToAllReadyCards, recordCanvasProjectionApplied, WORKSPACE_ID, } from "./projection.js";
|
|
19
|
-
import { normalizeUrl } from "./normalizeUrl.js";
|
|
24
|
+
import { normalizeUrl, normalizedUrlKey } from "./normalizeUrl.js";
|
|
20
25
|
import { createNudgeUiEditorUrl } from "../../transport/editor.js";
|
|
21
26
|
import { disposeInlineTextEdit } from "../inline-text/inlineTextEditor.js";
|
|
22
27
|
import { PROTOCOL_VERSION, isProjectionAppliedMessage, isKeyboardShortcutMessage, isRenderedInstanceProjectionReportMessage, isTextProjectionReportMessage, isStructuralProjectionReportMessage, } from "./frameProtocol.js";
|
|
28
|
+
import { wheelPanDelta } from "./wheelPan.js";
|
|
23
29
|
import { iframePointToClientPoint, zoomCameraAtPointer } from "./canvasGestures.js";
|
|
24
|
-
const canvasWorkspaceStyles = ".canvas-workspace {\n position: fixed;\n inset: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface-panel);\n pointer-events: auto;\n}\n\n.canvas-workspace__board {\n flex: 1;\n overflow: hidden;\n position: relative;\n background: var(--surface-panel);\n}\n\n.canvas-workspace__focus-action {\n position: absolute;\n top: max(12px, env(safe-area-inset-top));\n left: max(12px, env(safe-area-inset-left));\n z-index: calc(var(--z-index) - 1);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n pointer-events: auto;\n}\n\n.canvas-workspace__board.is-grabbable {\n cursor: grab;\n}\n\n.canvas-workspace__board.is-grabbing {\n cursor: grabbing;\n}\n\n/* Canvas gestures take precedence over every card interaction while Space is\n held. This also closes the short postMessage gap before an iframe receives\n its mirrored modifier state. */\n.canvas-workspace__board.is-grabbable .canvas-card,\n.canvas-workspace__board.is-grabbing .canvas-card {\n pointer-events: none;\n}\n\n.canvas-workspace__board-content {\n position: absolute;\n width: 100%;\n height: 100%;\n will-change: transform;\n}\n\n.canvas-workspace.is-presentation-transitioning .canvas-workspace__board-content {\n transition: transform 200ms ease;\n}\n\n.canvas-workspace[data-presentation=\"focus\"] .canvas-workspace__board-content {\n inset: 0;\n transform: none !important;\n}\n\n.canvas-workspace.is-presentation-transitioning .canvas-card.is-presentation-card {\n transition: left 200ms ease, top 200ms ease;\n}\n\n.canvas-workspace.is-presentation-transitioning .canvas-card:not(.is-presentation-card) {\n visibility: hidden !important;\n pointer-events: none !important;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .canvas-workspace.is-presentation-transitioning .canvas-workspace__board-content,\n .canvas-workspace.is-presentation-transitioning .canvas-card.is-presentation-card {\n transition: none;\n }\n}\n\n.canvas-workspace__target-error {\n position: absolute;\n inset: 0;\n display: grid;\n place-items: center;\n color: #6b7280;\n font: 500 14px/1.4 system-ui, sans-serif;\n}\n";
|
|
25
|
-
const canvasCardStyles = ".canvas-card {\n position: relative;\n}\n\n/* Extend the card's hover region to the toolbar above the iframe. */\n.canvas-card::before {\n content: \"\";\n position: absolute;\n right: 0;\n bottom: 100%;\n left: 0;\n height: calc(var(--control-height-large) + var(--space-
|
|
26
|
-
const foundationStyles = ":host {\n all: initial;\n --surface-panel: #F5F5F4;\n --surface-control: var(--surface-well);\n --surface-control-hover: var(--surface-well-2x);\n --surface-raised: #FAFAFA;\n --surface-raised-2x: #fff ;\n --surface-raised-hover: #F5F5F4;\n --surface-well: hsla(0, 0%, 0%, 0.05);\n --surface-well-2x: hsla(0, 0%, 0%, 0.10);\n\n --border-subtle: hsla(0, 0%, 0%, 0.1);\n --border-medium: hsla(0, 0%, 0%, 0.2);\n --border-strong: #d8d8d8;\n\n --text-primary: #242424;\n --text-secondary: #6f6f6f;\n --text-muted: #777777;\n --text-muted-subtle: #a1a1a1;\n\n --accent: #0096ff;\n --accent-surface: rgba(0, 176, 255, 0.15);\n --warning: #d97706;\n --warning-text: #9a5b00;\n --warning-surface: #fff6e5;\n --danger: #df4b4b;\n --danger-text: #bd3636;\n --danger-surface: rgba(223, 75, 75, 0.2);\n\n --space-0: 0;\n --space-4: 4px;\n --space-8: 8px;\n --space-12: 12px;\n --space-16: 16px;\n --space-20: 20px;\n --space-24: 24px;\n --space-32: 32px;\n --space-40: 40px;\n --space-48: 48px;\n\n /* Radius */\n --radius-0: 0;\n --radius-4: 4px;\n --radius-8: 8px;\n --radius-12: 12px;\n --radius-16: 16px;\n --radius-20: 20px;\n --radius-24: 24px;\n --radius-pill: 999px;\n\n --radius-control: var(--radius-8);\n --shadow-outline: 0 0 0 1px rgba(0, 0, 0, 0.05);\n --shadow-popup: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 16px rgba(0, 0, 0, 0.10);\n --shadow-control: inset 0 -2px 0 rgba(0, 0, 0, 0.03), 0 0 0 0.5px rgba(0, 0, 0, 0.25);\n --z-index: 2147483647;\n\n --font-family: \"Inter Variable\", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n --font-size-base: 12px;\n --font-size-small: 10px;\n --font-weight-base: 400;\n --font-weight-label: 600;\n\n /* Control semantic sizes */\n --control-height-small: 24px;\n --control-height-large: 32px;\n --control-height-xlarge: 40px;\n --label-column: 104px;\n\n --icon-size-small: 16px;\n --icon-size-large: 20px;\n --icon-stroke-width: 1.5px;\n\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n font-family: var(--font-family);\n background: transparent;\n box-sizing: border-box;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nbutton,\ninput,\nselect {\n font-family: inherit;\n}\n";
|
|
30
|
+
const canvasWorkspaceStyles = ".canvas-workspace {\n position: fixed;\n inset: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface-panel);\n pointer-events: auto;\n}\n\n.canvas-workspace__board {\n flex: 1;\n overflow: hidden;\n position: relative;\n background: var(--surface-panel);\n}\n\n.canvas-workspace__focus-action {\n position: absolute;\n top: max(12px, env(safe-area-inset-top));\n left: max(12px, env(safe-area-inset-left));\n z-index: calc(var(--z-index) - 1);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n pointer-events: auto;\n}\n\n.canvas-workspace__board.is-grabbable {\n cursor: grab;\n}\n\n.canvas-workspace__board.is-grabbing {\n cursor: grabbing;\n}\n\n/* Canvas gestures take precedence over every card interaction while Space is\n held. This also closes the short postMessage gap before an iframe receives\n its mirrored modifier state. */\n.canvas-workspace__board.is-grabbable .canvas-card,\n.canvas-workspace__board.is-grabbing .canvas-card {\n pointer-events: none;\n}\n\n.canvas-workspace__board-content {\n position: absolute;\n width: 100%;\n height: 100%;\n /*\n * Leave `will-change` unset so the browser can re-rasterize this subtree at\n * the current camera scale. A permanent `will-change: transform` promotes\n * it to a fixed bitmap, which makes iframe content look soft when zoomed.\n */\n}\n\n.canvas-workspace__board.is-grabbing .canvas-workspace__board-content {\n /* Panning benefits from a cached composited layer; zoom restores the crisp\n * default path as soon as the gesture ends. */\n will-change: transform;\n}\n\n.canvas-workspace.is-presentation-transitioning .canvas-workspace__board-content {\n transition: transform 200ms ease;\n}\n\n.canvas-workspace[data-presentation=\"focus\"] .canvas-workspace__board-content {\n inset: 0;\n transform: none !important;\n}\n\n.canvas-workspace.is-presentation-transitioning .canvas-card.is-presentation-card {\n transition: left 200ms ease, top 200ms ease;\n}\n\n.canvas-workspace.is-presentation-transitioning .canvas-card:not(.is-presentation-card) {\n visibility: hidden !important;\n pointer-events: none !important;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .canvas-workspace.is-presentation-transitioning .canvas-workspace__board-content,\n .canvas-workspace.is-presentation-transitioning .canvas-card.is-presentation-card {\n transition: none;\n }\n}\n\n.canvas-workspace__target-error {\n position: absolute;\n inset: 0;\n display: grid;\n place-items: center;\n color: #6b7280;\n font: 500 14px/1.4 system-ui, sans-serif;\n}\n\n/* Moving iframes must not acquire hover state beneath a stationary pointer. */\n.canvas-workspace.is-presentation-transitioning .canvas-card__iframe,\n.canvas-workspace__board.is-layout-transitioning .canvas-card__iframe {\n pointer-events: none;\n}\n";
|
|
31
|
+
const canvasCardStyles = ".canvas-card {\n position: relative;\n}\n\n/* Extend the card's hover region to the toolbar above the iframe. */\n.canvas-card::before {\n content: \"\";\n position: absolute;\n right: 0;\n bottom: 100%;\n left: 0;\n height: calc((var(--control-height-large) + var(--space-16)) * var(--canvas-toolbar-scale, 1));\n pointer-events: auto;\n}\n\n.canvas-card__frame {\n position: relative;\n width: 100%;\n height: 100%;\n background: var(--surface-panel);\n border-radius: 0;\n overflow: hidden;\n}\n\n.canvas-card.is-selected .canvas-card__frame {\n outline: 2px solid var(--frame-accent, var(--accent));\n outline-offset: -2px;\n}\n\n.canvas-card.is-focus-presentation .canvas-card__frame {\n outline: none;\n}\n\n.canvas-card.is-focused.is-selected:not(.is-linked) .canvas-card__frame {\n animation: canvas-card-focus 900ms ease-out;\n}\n\n@keyframes canvas-card-focus {\n 0%, 35% { outline: 3px solid var(--frame-accent, var(--accent)); outline-offset: -3px; }\n 100% { outline: 0 solid transparent; outline-offset: -2px; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .canvas-card.is-focused .canvas-card__frame {\n animation: none;\n }\n}\n\n.canvas-card__header {\n display: flex;\n align-items: center;\n position: absolute;\n left: 0;\n right: 0;\n bottom: calc(100% + var(--space-8));\n justify-content: flex-end;\n z-index: 3;\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n}\n\n.canvas-card__drag-surface {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--space-8);\n flex: 0 1 auto;\n margin-right: auto;\n min-width: 0;\n min-height: var(--control-height-large);\n padding: 0;\n cursor: grab;\n user-select: none;\n touch-action: none;\n}\n\n.canvas-card.is-dragging .canvas-card__drag-surface {\n cursor: grabbing;\n}\n\n.canvas-card__identity {\n display: flex;\n align-items: center;\n gap: var(--space-8);\n min-width: 0;\n}\n\n.canvas-card__dimensions {\n display: flex;\n align-items: center;\n gap: var(--space-8);\n position: relative;\n flex: 0 1 auto;\n min-width: 0;\n max-width: 260px;\n color: var(--text-secondary);\n font-size: var(--font-size-base);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n pointer-events: none;\n}\n\n.canvas-card__title {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.canvas-card__live-badge,\n.canvas-card__iteration-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 20px;\n height: 20px;\n background: var(--accent-surface);\n color: var(--accent);\n border-radius: var(--radius-4);\n pointer-events: auto;\n}\n\n.canvas-card__floating-toolbar {\n position: absolute;\n left: 50%;\n flex-shrink: 0;\n width: max-content;\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transform: translate(-50%, calc(-24px + var(--space-4)));\n transition: opacity 140ms ease-out, transform 140ms ease-out, visibility 140ms;\n}\n\n.canvas-card:hover .canvas-card__floating-toolbar,\n.canvas-card:focus-within .canvas-card__floating-toolbar,\n.canvas-card__floating-toolbar[data-busy=\"true\"] {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n transform: translate(-50%, -24px);\n}\n\n.canvas-card__actions.canvas-toolbar {\n position: static;\n width: max-content;\n transform: none;\n white-space: nowrap;\n}\n\n.canvas-card__actions .canvas-toolbar__divider {\n margin-inline: 0;\n}\n\n@media (hover: none) {\n .canvas-card.is-selected .canvas-card__floating-toolbar {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n transform: translate(-50%, -24px);\n }\n}\n\n.canvas-card.is-dragging .canvas-card__floating-toolbar,\n.canvas-workspace__board.is-grabbable .canvas-card__floating-toolbar,\n.canvas-workspace__board.is-grabbing .canvas-card__floating-toolbar,\n.canvas-workspace.is-presentation-transitioning .canvas-card__floating-toolbar,\n.canvas-workspace__board.is-layout-transitioning .canvas-card__floating-toolbar {\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transition: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .canvas-card__floating-toolbar { transition: none; }\n}\n\n.canvas-card__iteration-error {\n position: absolute;\n right: 0;\n bottom: calc(100% + var(--space-8));\n color: var(--text-danger, #b42318);\n font-size: 12px;\n white-space: nowrap;\n}\n\n.canvas-card__iframe {\n width: 100%;\n height: 100%;\n border: none;\n display: block;\n}\n\n.canvas-card__sketch-overlay {\n position: absolute;\n inset: 0;\n z-index: 2;\n display: block;\n pointer-events: none;\n}\n\n.canvas-card__loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--text-muted, #777777);\n font-size: 12px;\n font-weight: 500;\n}\n\n.canvas-card__error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n height: 100%;\n padding: 20px;\n text-align: center;\n}\n\n.canvas-card__error p {\n margin: 0;\n color: var(--text-muted, #777777);\n font-size: 12px;\n font-weight: 500;\n}\n\n.canvas-card__error-actions {\n display: flex;\n gap: 8px;\n}\n\n.canvas-card__resize-handle {\n position: absolute;\n z-index: 3;\n display: flex;\n align-items: flex-end;\n justify-content: flex-end;\n padding: 4px;\n color: var(--text-muted, #777777);\n opacity: 0;\n transition: opacity 150ms ease;\n touch-action: none;\n user-select: none;\n}\n\n.canvas-card__resize-handle--top,\n.canvas-card__resize-handle--bottom {\n right: 16px;\n left: 16px;\n height: 16px;\n cursor: ns-resize;\n}\n\n.canvas-card__resize-handle--top {\n top: 0;\n}\n\n.canvas-card__resize-handle--bottom {\n bottom: 0;\n}\n\n.canvas-card__resize-handle--left,\n.canvas-card__resize-handle--right {\n top: 16px;\n bottom: 16px;\n width: 16px;\n cursor: ew-resize;\n}\n\n.canvas-card__resize-handle--left {\n left: 0;\n}\n\n.canvas-card__resize-handle--right {\n right: 0;\n}\n\n.canvas-card__resize-handle--top-left,\n.canvas-card__resize-handle--top-right,\n.canvas-card__resize-handle--bottom-right,\n.canvas-card__resize-handle--bottom-left {\n z-index: 4;\n width: 24px;\n height: 24px;\n cursor: nwse-resize;\n}\n\n.canvas-card__resize-handle--top-left {\n top: 0;\n left: 0;\n}\n\n.canvas-card__resize-handle--top-right {\n top: 0;\n right: 0;\n cursor: nesw-resize;\n}\n\n.canvas-card__resize-handle--bottom-right {\n right: 0;\n bottom: 0;\n}\n\n.canvas-card__resize-handle--bottom-left {\n bottom: 0;\n left: 0;\n cursor: nesw-resize;\n}\n\n.canvas-card:hover .canvas-card__resize-handle,\n.canvas-card:focus-within .canvas-card__resize-handle {\n opacity: 1;\n}\n\n.canvas-card__resize-handle:hover {\n color: var(--accent);\n}\n\n.canvas-card__resize-handle:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n opacity: 1;\n}\n\n.canvas-card.is-linked {\n --frame-accent: #a855f7;\n}\n\n.canvas-card.is-linked:not(.is-focus-presentation) .canvas-card__frame {\n outline: 2px solid var(--frame-accent);\n outline-offset: -2px;\n}\n\n.canvas-card__iteration-badge {\n color: var(--text-secondary);\n background: var(--surface-raised-2x);\n}\n";
|
|
32
|
+
const foundationStyles = ":host {\n all: initial;\n --surface-panel: #F5F5F4;\n --surface-control: var(--surface-well);\n --surface-control-hover: var(--surface-well-2x);\n --surface-raised: #FAFAFA;\n --surface-raised-2x: #fff ;\n --surface-raised-hover: #F5F5F4;\n --surface-well: hsla(0, 0%, 0%, 0.05);\n --surface-well-2x: hsla(0, 0%, 0%, 0.10);\n\n --border-subtle: hsla(0, 0%, 0%, 0.1);\n --border-medium: hsla(0, 0%, 0%, 0.2);\n --border-strong: #d8d8d8;\n\n --text-primary: #242424;\n --text-secondary: #6f6f6f;\n --text-muted: #777777;\n --text-muted-subtle: #a1a1a1;\n\n --accent: #0096ff;\n --accent-surface: rgba(0, 176, 255, 0.15);\n --warning: #d97706;\n --warning-text: #9a5b00;\n --warning-surface: #fff6e5;\n --danger: #df4b4b;\n --danger-text: #bd3636;\n --danger-surface: rgba(223, 75, 75, 0.2);\n --success: #2e7d52;\n\n --space-0: 0;\n --space-4: 4px;\n --space-8: 8px;\n --space-12: 12px;\n --space-16: 16px;\n --space-20: 20px;\n --space-24: 24px;\n --space-32: 32px;\n --space-40: 40px;\n --space-48: 48px;\n\n /* Radius */\n --radius-0: 0;\n --radius-4: 4px;\n --radius-8: 8px;\n --radius-12: 12px;\n --radius-16: 16px;\n --radius-20: 20px;\n --radius-24: 24px;\n --radius-pill: 999px;\n\n --radius-control: var(--radius-8);\n --shadow-outline: 0 0 0 1px rgba(0, 0, 0, 0.05);\n --shadow-popup: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 16px rgba(0, 0, 0, 0.10);\n --shadow-control: inset 0 -2px 0 rgba(0, 0, 0, 0.03), 0 0 0 0.5px rgba(0, 0, 0, 0.25);\n --z-index: 2147483647;\n\n --font-family: \"Inter Variable\", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n --font-size-base: 12px;\n --font-size-small: 10px;\n --font-weight-base: 400;\n --font-weight-label: 600;\n\n /* Control semantic sizes */\n --control-height-small: 24px;\n --control-height-large: 32px;\n --control-height-xlarge: 40px;\n --label-column: 104px;\n\n --icon-size-small: 16px;\n --icon-size-large: 20px;\n --icon-stroke-width: 1.5px;\n\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n font-family: var(--font-family);\n background: transparent;\n box-sizing: border-box;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nbutton,\ninput,\nselect {\n font-family: inherit;\n}\n";
|
|
27
33
|
import { useInspectorOpen } from "../shell/openStore.js";
|
|
28
34
|
import { isEditableEvent } from "../shell/shortcuts.js";
|
|
29
35
|
import { acknowledgeAgentRendererReady } from "./agentPresentation.js";
|
|
30
36
|
import { useInspectorSession } from "../session/sessionContext.js";
|
|
37
|
+
import { isNudgeUiFeatureEnabled } from "../runtime/runtimeConfig.js";
|
|
31
38
|
import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
|
|
32
39
|
import { subscribeCanvasRendererMessages } from "./rendererMessageRouter.js";
|
|
33
40
|
import { adjacentCanvasZoomLevel, CanvasToolbar, } from "./CanvasToolbar.js";
|
|
34
|
-
import { canvasToolForEvent, canvasToolForCode } from "./keyboardShortcuts.js";
|
|
35
|
-
const canvasToolbarStyles = ".canvas-toolbar {\n position: absolute;\n left: 50%;\n bottom: var(--space-20);\n /* Keep the persistent controls above the live sketch surface. The sketch\n toolbar gets the next layer so both toolbars remain available together. */\n z-index: calc(var(--z-index) - 2);\n display: flex;\n align-items: center;\n gap: var(--space-4);\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n transform: translateX(-50%);\n pointer-events: auto;\n}\n\n/* The live sketch surface is a separate stacking context. Keep a copy of the\n toolbar at the root layer while sketching so pointer events reach the\n controls instead of the full-screen SVG drawing surface. */\n.canvas-toolbar__portal {\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n}\n\n.canvas-toolbar__portal > .canvas-toolbar {\n pointer-events: auto;\n}\n\n.canvas-toolbar__tools {\n display: flex;\n align-items: center;\n gap: var(--space-4);\n}\n\n.canvas-toolbar__tool[data-active=\"true\"] {\n background: var(--accent-surface);\n color: var(--accent);\n}\n";
|
|
41
|
+
import { APP_INTERACTION_KEY, canvasToolForEvent, canvasToolForCode } from "./keyboardShortcuts.js";
|
|
42
|
+
const canvasToolbarStyles = ".canvas-toolbar {\n position: absolute;\n left: 50%;\n bottom: var(--space-20);\n /* Keep the persistent controls above the live sketch surface. The sketch\n toolbar gets the next layer so both toolbars remain available together. */\n z-index: calc(var(--z-index) - 2);\n display: flex;\n align-items: center;\n gap: var(--space-4);\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n transform: translateX(-50%);\n pointer-events: auto;\n}\n\n/* The live sketch surface is a separate stacking context. Keep a copy of the\n toolbar at the root layer while sketching so pointer events reach the\n controls instead of the full-screen SVG drawing surface. */\n.canvas-toolbar__portal {\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n}\n\n.canvas-toolbar__portal > .canvas-toolbar {\n pointer-events: auto;\n}\n\n.canvas-toolbar__tools {\n display: flex;\n align-items: center;\n gap: var(--space-4);\n}\n\n.canvas-toolbar__divider {\n flex-shrink: 0;\n width: 1px;\n height: var(--control-height-small);\n margin-inline: 0;\n background: var(--border-subtle);\n}\n\n.canvas-toolbar__tool[data-active=\"true\"] {\n background: var(--accent-surface);\n color: var(--accent);\n}\n";
|
|
36
43
|
import { getActiveCanvasFrame } from "./activeCanvasDocument.js";
|
|
37
44
|
import { startSketchCapture } from "../sketch/SketchWorkspace.js";
|
|
38
45
|
import { cancelSketchInteraction, useSketchInteractionActive } from "../sketch/interaction.js";
|
|
39
46
|
import { isNudgeUiDev } from "../runtime/devFlag.js";
|
|
47
|
+
import { createCommentCursor, DESIGN_SELECT_CURSOR, PAN_CURSOR } from "../ui/customCursors.js";
|
|
48
|
+
import { addLinkedFrame, createIteration as createWorkspaceIteration, removeFrame } from "../workspace/commands.js";
|
|
49
|
+
import { getSelectedElements, setSelectedElement } from "../selection/selectionStore.js";
|
|
50
|
+
import { setCommentToolActive } from "../comments/store.js";
|
|
40
51
|
const WORKSPACE_STYLES = [foundationStyles, canvasWorkspaceStyles, canvasCardStyles, canvasToolbarStyles].join("\n");
|
|
41
52
|
const PRESENTATION_TRANSITION_MS = 200;
|
|
42
53
|
function snapshotRect(rect) {
|
|
@@ -60,8 +71,10 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
60
71
|
const focusedCardId = useFocusedCardId();
|
|
61
72
|
const sketchActive = useSketchInteractionActive();
|
|
62
73
|
const sketchEnabled = isNudgeUiDev() && runtimeConfig.demo !== true;
|
|
74
|
+
const canvasIterationsEnabled = isNudgeUiFeatureEnabled(runtimeConfig.features, "canvasIterations");
|
|
63
75
|
const sketchAvailable = sketchEnabled && getActiveCanvasFrame() !== null;
|
|
64
76
|
const boardRef = useRef(null);
|
|
77
|
+
useCanvasLayoutAnimation(boardRef, cards, camera, presentation);
|
|
65
78
|
const panningRef = useRef(false);
|
|
66
79
|
const panOriginRef = useRef({ x: 0, y: 0 });
|
|
67
80
|
const cameraAtPanStartRef = useRef({ x: 0, y: 0 });
|
|
@@ -74,8 +87,24 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
74
87
|
const focusCardRectRef = useRef(null);
|
|
75
88
|
const canvasEntryAnimationRef = useRef(null);
|
|
76
89
|
const [boardCursorClass, setBoardCursorClass] = useState("");
|
|
77
|
-
const [
|
|
90
|
+
const [selectedTool, setInteractionTool] = useState("design");
|
|
91
|
+
const temporaryAppInteraction = useTemporaryAppInteraction();
|
|
92
|
+
const interactionTool = temporaryAppInteraction ? "select" : selectedTool;
|
|
93
|
+
const layoutTransitioning = useCanvasLayoutTransitioning();
|
|
94
|
+
const rendererInteractionsEnabled = interactionTool !== "select" && !layoutTransitioning;
|
|
95
|
+
const rendererCursor = interactionTool === "comment" ? "comment" : "design";
|
|
96
|
+
const boardCursor = boardCursorClass === "is-grabbing"
|
|
97
|
+
? "grabbing"
|
|
98
|
+
: boardCursorClass !== ""
|
|
99
|
+
? PAN_CURSOR
|
|
100
|
+
: interactionTool === "comment"
|
|
101
|
+
? createCommentCursor(boardRef.current)
|
|
102
|
+
: interactionTool === "design"
|
|
103
|
+
? DESIGN_SELECT_CURSOR
|
|
104
|
+
: undefined;
|
|
105
|
+
const linksOpenInCards = interactionTool === "select" && presentation === "canvas";
|
|
78
106
|
const presentationCardId = selectedCardId ?? focusedCardId ?? cards[0]?.id ?? null;
|
|
107
|
+
const linkedFrameIds = getLinkedFrameIds(cards, selectedCardId);
|
|
79
108
|
useLayoutEffect(() => {
|
|
80
109
|
const board = boardRef.current;
|
|
81
110
|
const cardElement = presentationCardId
|
|
@@ -155,7 +184,16 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
155
184
|
width: board.clientWidth,
|
|
156
185
|
height: board.clientHeight,
|
|
157
186
|
};
|
|
158
|
-
const primaryCard = activateIframeWorkspace(primaryUrl, viewport
|
|
187
|
+
const primaryCard = activateIframeWorkspace(primaryUrl, viewport, {
|
|
188
|
+
replaceActiveCard: presentation === "focus" && runtimeConfig.demo !== true,
|
|
189
|
+
preserveIterationFocus: true,
|
|
190
|
+
});
|
|
191
|
+
if (primaryCard && cards.length === 0 && runtimeConfig.demo !== true) {
|
|
192
|
+
setBoardCamera({
|
|
193
|
+
x: 40, y: 120,
|
|
194
|
+
zoom: Math.max(0.25, Math.min(0.9, (viewport.width - 80) / primaryCard.width, (viewport.height - 160) / primaryCard.height)),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
159
197
|
if (runtimeConfig.demo === true && primaryCard && !demoSeededRef.current) {
|
|
160
198
|
demoSeededRef.current = true;
|
|
161
199
|
const demoPages = runtimeConfig.demoPages ?? [];
|
|
@@ -199,7 +237,7 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
199
237
|
}
|
|
200
238
|
focusCard(primaryCard.id);
|
|
201
239
|
}
|
|
202
|
-
}, [primaryUrl, cards.length, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
|
|
240
|
+
}, [primaryUrl, cards.length, presentation, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
|
|
203
241
|
useEffect(() => {
|
|
204
242
|
if (primaryUrl && activatedTargetRef.current !== primaryUrl)
|
|
205
243
|
return;
|
|
@@ -207,7 +245,7 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
207
245
|
const activeCard = cards.find((card) => card.id === activeCardId);
|
|
208
246
|
if (!activeCard)
|
|
209
247
|
return;
|
|
210
|
-
const editorUrl = createNudgeUiEditorUrl(activeCard.
|
|
248
|
+
const editorUrl = createNudgeUiEditorUrl(contentSourceUrl(activeCard.content));
|
|
211
249
|
if (window.location.href !== editorUrl) {
|
|
212
250
|
window.history.replaceState(window.history.state, "", editorUrl);
|
|
213
251
|
}
|
|
@@ -220,9 +258,10 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
220
258
|
workspaceId: WORKSPACE_ID,
|
|
221
259
|
cardId,
|
|
222
260
|
spaceHeld,
|
|
261
|
+
cursor: spaceHeld ? "drag" : rendererCursor,
|
|
223
262
|
}, window.location.origin);
|
|
224
|
-
}, []);
|
|
225
|
-
const sendInspectorInteractionState = useCallback((iframe, cardId, open) => {
|
|
263
|
+
}, [rendererCursor]);
|
|
264
|
+
const sendInspectorInteractionState = useCallback((iframe, cardId, open, interactionsEnabled, cursor) => {
|
|
226
265
|
iframe.contentWindow?.postMessage({
|
|
227
266
|
type: "inspector-interaction-state",
|
|
228
267
|
protocolVersion: PROTOCOL_VERSION,
|
|
@@ -230,7 +269,19 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
230
269
|
workspaceId: WORKSPACE_ID,
|
|
231
270
|
cardId,
|
|
232
271
|
open,
|
|
233
|
-
interactionsEnabled
|
|
272
|
+
interactionsEnabled,
|
|
273
|
+
cursor,
|
|
274
|
+
commentCursor: cursor === "comment" ? createCommentCursor(boardRef.current) : undefined,
|
|
275
|
+
}, window.location.origin);
|
|
276
|
+
}, []);
|
|
277
|
+
const sendLinkTargetState = useCallback((iframe, cardId, openInCard) => {
|
|
278
|
+
iframe.contentWindow?.postMessage({
|
|
279
|
+
type: "link-target-state",
|
|
280
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
281
|
+
projectId: PROJECT_ID,
|
|
282
|
+
workspaceId: WORKSPACE_ID,
|
|
283
|
+
cardId,
|
|
284
|
+
openInCard,
|
|
234
285
|
}, window.location.origin);
|
|
235
286
|
}, []);
|
|
236
287
|
const sendBoardGestureState = useCallback((iframe, cardId, enabled) => {
|
|
@@ -241,14 +292,16 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
241
292
|
workspaceId: WORKSPACE_ID,
|
|
242
293
|
cardId,
|
|
243
294
|
enabled,
|
|
295
|
+
panScroll: enabled && interactionTool !== "select",
|
|
244
296
|
}, window.location.origin);
|
|
245
|
-
}, []);
|
|
297
|
+
}, [interactionTool]);
|
|
246
298
|
useEffect(() => {
|
|
247
299
|
for (const [cardId, iframe] of getRegisteredFrames()) {
|
|
248
|
-
sendInspectorInteractionState(iframe, cardId, inspectorOpen);
|
|
300
|
+
sendInspectorInteractionState(iframe, cardId, inspectorOpen, rendererInteractionsEnabled, rendererCursor);
|
|
249
301
|
sendBoardGestureState(iframe, cardId, presentation === "canvas");
|
|
302
|
+
sendLinkTargetState(iframe, cardId, linksOpenInCards);
|
|
250
303
|
}
|
|
251
|
-
}, [inspectorOpen, presentation, sendBoardGestureState, sendInspectorInteractionState]);
|
|
304
|
+
}, [inspectorOpen, linksOpenInCards, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState]);
|
|
252
305
|
const broadcastPanModifier = useCallback((spaceHeld) => {
|
|
253
306
|
for (const [cardId, iframe] of getRegisteredFrames()) {
|
|
254
307
|
sendPanModifier(iframe, cardId, spaceHeld);
|
|
@@ -260,6 +313,12 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
260
313
|
return;
|
|
261
314
|
setBoardCamera(zoomCameraAtPointer(getBoardCamera(), point, board.getBoundingClientRect(), deltaY));
|
|
262
315
|
}, []);
|
|
316
|
+
const panCanvasByWheel = useCallback((deltaX, deltaY) => {
|
|
317
|
+
if (!Number.isFinite(deltaX) || !Number.isFinite(deltaY))
|
|
318
|
+
return;
|
|
319
|
+
const camera = getBoardCamera();
|
|
320
|
+
setBoardCamera({ ...camera, x: camera.x - deltaX, y: camera.y - deltaY });
|
|
321
|
+
}, []);
|
|
263
322
|
const fitCanvasToBoard = useCallback(() => {
|
|
264
323
|
const board = boardRef.current;
|
|
265
324
|
fitAllCards(board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
|
|
@@ -297,6 +356,14 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
297
356
|
setBoardCursorClass("");
|
|
298
357
|
}, [broadcastPanModifier, endPanning]);
|
|
299
358
|
const handleToolChange = useCallback((nextTool) => {
|
|
359
|
+
if (nextTool === "select" && selectedTool === "select") {
|
|
360
|
+
setInteractionTool("design");
|
|
361
|
+
stopPanMode();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (nextTool === "select") {
|
|
365
|
+
setSelectedElement(null);
|
|
366
|
+
}
|
|
300
367
|
if (nextTool === "sketch") {
|
|
301
368
|
if (!sketchEnabled)
|
|
302
369
|
return;
|
|
@@ -304,27 +371,52 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
304
371
|
if (!hostElement)
|
|
305
372
|
return;
|
|
306
373
|
stopPanMode();
|
|
374
|
+
setCommentToolActive(false);
|
|
307
375
|
setInteractionTool(nextTool);
|
|
308
376
|
startSketchCapture(hostElement);
|
|
309
377
|
return;
|
|
310
378
|
}
|
|
311
379
|
if (sketchActive)
|
|
312
380
|
cancelSketchInteraction();
|
|
313
|
-
|
|
314
|
-
if (presentation === "focus") {
|
|
315
|
-
const board = boardRef.current;
|
|
316
|
-
setCanvasPresentation("canvas", board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
|
|
317
|
-
}
|
|
318
|
-
setInteractionTool(nextTool);
|
|
319
|
-
spaceHeldRef.current = true;
|
|
320
|
-
broadcastPanModifier(true);
|
|
321
|
-
if (!panningRef.current)
|
|
322
|
-
setBoardCursorClass("is-grabbable");
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
381
|
+
setCommentToolActive(nextTool === "comment");
|
|
325
382
|
setInteractionTool(nextTool);
|
|
326
383
|
stopPanMode();
|
|
327
|
-
}, [
|
|
384
|
+
}, [selectedTool, sketchActive, sketchEnabled, stopPanMode]);
|
|
385
|
+
useEffect(() => () => setCommentToolActive(false), []);
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
setCommentToolActive(interactionTool === "comment");
|
|
388
|
+
if (temporaryAppInteraction)
|
|
389
|
+
stopPanMode();
|
|
390
|
+
}, [interactionTool, temporaryAppInteraction, stopPanMode]);
|
|
391
|
+
useEffect(() => {
|
|
392
|
+
const onKeyDown = (event) => {
|
|
393
|
+
if (mode === "canvas" && event.code === APP_INTERACTION_KEY && !event.repeat && !isEditableEvent(event))
|
|
394
|
+
setTemporaryAppInteraction(true);
|
|
395
|
+
};
|
|
396
|
+
const onKeyUp = (event) => {
|
|
397
|
+
if (event.code === APP_INTERACTION_KEY)
|
|
398
|
+
setTemporaryAppInteraction(false);
|
|
399
|
+
};
|
|
400
|
+
const onBlur = () => {
|
|
401
|
+
if (!document.hasFocus())
|
|
402
|
+
setTemporaryAppInteraction(false);
|
|
403
|
+
};
|
|
404
|
+
const onVisibility = () => {
|
|
405
|
+
if (document.visibilityState === "hidden")
|
|
406
|
+
setTemporaryAppInteraction(false);
|
|
407
|
+
};
|
|
408
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
409
|
+
window.addEventListener("keyup", onKeyUp, true);
|
|
410
|
+
window.addEventListener("blur", onBlur);
|
|
411
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
412
|
+
return () => {
|
|
413
|
+
window.removeEventListener("keydown", onKeyDown, true);
|
|
414
|
+
window.removeEventListener("keyup", onKeyUp, true);
|
|
415
|
+
window.removeEventListener("blur", onBlur);
|
|
416
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
417
|
+
setTemporaryAppInteraction(false);
|
|
418
|
+
};
|
|
419
|
+
}, [mode]);
|
|
328
420
|
useEffect(() => {
|
|
329
421
|
if (mode !== "canvas")
|
|
330
422
|
return;
|
|
@@ -343,8 +435,21 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
343
435
|
const handleZoomStep = useCallback((direction) => {
|
|
344
436
|
if (presentation === "focus") {
|
|
345
437
|
if (direction < 0) {
|
|
346
|
-
|
|
347
|
-
|
|
438
|
+
batchCanvasChanges(() => {
|
|
439
|
+
const board = boardRef.current;
|
|
440
|
+
const cards = getCanvasCards();
|
|
441
|
+
const activeId = getSelectedCardId() ?? getFocusedCardId();
|
|
442
|
+
const card = cards.find((candidate) => candidate.id === activeId) ?? cards[0];
|
|
443
|
+
if (board && card) {
|
|
444
|
+
const zoom = 0.9;
|
|
445
|
+
setBoardCamera({
|
|
446
|
+
x: board.clientWidth / 2 - (card.x + card.width / 2) * zoom,
|
|
447
|
+
y: board.clientHeight / 2 - (card.y + card.height / 2) * zoom,
|
|
448
|
+
zoom,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
setCanvasPresentation("canvas");
|
|
452
|
+
});
|
|
348
453
|
}
|
|
349
454
|
return;
|
|
350
455
|
}
|
|
@@ -360,9 +465,26 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
360
465
|
focusCard(cardId);
|
|
361
466
|
setCanvasPresentation("focus");
|
|
362
467
|
}, []);
|
|
468
|
+
const duplicateDesign = useCallback((cardId) => {
|
|
469
|
+
const duplicate = addLinkedFrame(cardId);
|
|
470
|
+
if (!duplicate)
|
|
471
|
+
return;
|
|
472
|
+
setSelectedElement(null);
|
|
473
|
+
setCanvasPresentation("canvas");
|
|
474
|
+
const board = boardRef.current;
|
|
475
|
+
focusCanvasCards([duplicate.id], board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
|
|
476
|
+
}, []);
|
|
477
|
+
const createIteration = useCallback(async (cardId, position) => {
|
|
478
|
+
const iteration = await createWorkspaceIteration(cardId, position);
|
|
479
|
+
setSelectedElement(null);
|
|
480
|
+
const board = boardRef.current;
|
|
481
|
+
if (board && !position)
|
|
482
|
+
focusCanvasCards([iteration.id], { width: board.clientWidth, height: board.clientHeight });
|
|
483
|
+
return iteration.id;
|
|
484
|
+
}, []);
|
|
363
485
|
useEffect(() => {
|
|
364
486
|
if (!sketchActive && interactionTool === "sketch") {
|
|
365
|
-
setInteractionTool("
|
|
487
|
+
setInteractionTool("design");
|
|
366
488
|
}
|
|
367
489
|
}, [interactionTool, sketchActive]);
|
|
368
490
|
useLayoutEffect(() => {
|
|
@@ -389,6 +511,11 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
389
511
|
return subscribeCanvasRendererMessages(({ cardId, iframe, identity, message: msg }) => {
|
|
390
512
|
if (msg.type === "renderer-hello")
|
|
391
513
|
return;
|
|
514
|
+
if (msg.type === "app-interaction-modifier") {
|
|
515
|
+
if (mode === "canvas")
|
|
516
|
+
setTemporaryAppInteraction(msg.held === true);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
392
519
|
if (isKeyboardShortcutMessage(msg, identity)) {
|
|
393
520
|
if (msg.phase === "keydown") {
|
|
394
521
|
const nextTool = canvasToolForCode(msg.code);
|
|
@@ -399,11 +526,14 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
399
526
|
}
|
|
400
527
|
const frame = { cardId, iframe };
|
|
401
528
|
const syncActiveFrameUrl = (url) => {
|
|
402
|
-
|
|
403
|
-
const activeCardId = getSelectedCardId() ?? getFocusedCardId() ?? getCanvasCards()[0]?.id;
|
|
404
|
-
if (activeCardId !== frame.cardId && getCanvasCards().length !== 1)
|
|
529
|
+
if (getCanvasCards().some((card) => card.id === frame.cardId && iterationId(card.content)))
|
|
405
530
|
return;
|
|
406
|
-
|
|
531
|
+
const activeCardId = getSelectedCardId() ?? getFocusedCardId() ?? getCanvasCards()[0]?.id;
|
|
532
|
+
if (activeCardId === frame.cardId || getCanvasCards().length === 1) {
|
|
533
|
+
activatedTargetRef.current = url;
|
|
534
|
+
window.history.replaceState(window.history.state, "", createNudgeUiEditorUrl(url));
|
|
535
|
+
}
|
|
536
|
+
updateCardUrl(frame.cardId, url);
|
|
407
537
|
};
|
|
408
538
|
if (isProjectionAppliedMessage(msg, identity)) {
|
|
409
539
|
recordCanvasProjectionApplied(frame.cardId, msg.revision);
|
|
@@ -412,10 +542,12 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
412
542
|
if (isOriginalPreviewActive())
|
|
413
543
|
return;
|
|
414
544
|
const frameDocument = frame.iframe.contentDocument;
|
|
415
|
-
|
|
416
|
-
|
|
545
|
+
const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
|
|
546
|
+
const draft = getDraftForCard(frame.cardId);
|
|
547
|
+
if (frameDocument && plan && draft && isCanvasCanonicalProjectionRevisionCurrent(frameDocument, msg.revision)) {
|
|
548
|
+
const attempt = beginPreviewAttempt(getCanvasPreviewDocument(frame.cardId), plan.draftRevision);
|
|
417
549
|
if (attempt) {
|
|
418
|
-
for (const change of
|
|
550
|
+
for (const change of draft.contents.changes) {
|
|
419
551
|
if (!isPreviewableChange(change))
|
|
420
552
|
continue;
|
|
421
553
|
const result = verifyManagedStyleProjection(change, null, frameDocument);
|
|
@@ -437,15 +569,21 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
437
569
|
return;
|
|
438
570
|
}
|
|
439
571
|
if (isStructuralProjectionReportMessage(msg, identity)) {
|
|
440
|
-
|
|
572
|
+
const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
|
|
573
|
+
if (plan)
|
|
574
|
+
recordCanvasStructuralProjectionReports(frame.cardId, msg.revision, msg.reports, plan);
|
|
441
575
|
return;
|
|
442
576
|
}
|
|
443
577
|
if (isRenderedInstanceProjectionReportMessage(msg, identity)) {
|
|
444
|
-
|
|
578
|
+
const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
|
|
579
|
+
if (plan)
|
|
580
|
+
recordCanvasRenderedInstanceProjectionReports(frame.cardId, msg.revision, msg.reports, plan);
|
|
445
581
|
return;
|
|
446
582
|
}
|
|
447
583
|
if (isTextProjectionReportMessage(msg, identity)) {
|
|
448
|
-
|
|
584
|
+
const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
|
|
585
|
+
if (plan)
|
|
586
|
+
recordCanvasTextProjectionReports(frame.cardId, msg.revision, msg.reports, plan);
|
|
449
587
|
return;
|
|
450
588
|
}
|
|
451
589
|
if (msg.type === "frame-ready") {
|
|
@@ -454,8 +592,9 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
454
592
|
// A card can finish loading after Space was pressed on the controller.
|
|
455
593
|
// Seed it with the current modifier state before its first pointer event.
|
|
456
594
|
sendPanModifier(frame.iframe, frame.cardId, spaceHeldRef.current);
|
|
457
|
-
sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen);
|
|
595
|
+
sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen, rendererInteractionsEnabled, spaceHeldRef.current ? "drag" : rendererCursor);
|
|
458
596
|
sendBoardGestureState(frame.iframe, frame.cardId, presentation === "canvas");
|
|
597
|
+
sendLinkTargetState(frame.iframe, frame.cardId, linksOpenInCards);
|
|
459
598
|
}
|
|
460
599
|
if (msg.type === "pan-modifier") {
|
|
461
600
|
if (presentation !== "canvas")
|
|
@@ -467,6 +606,11 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
467
606
|
broadcastPanModifier(msg.spaceHeld);
|
|
468
607
|
return;
|
|
469
608
|
}
|
|
609
|
+
if (msg.type === "wheel-pan") {
|
|
610
|
+
if (presentation === "canvas" && interactionTool !== "select")
|
|
611
|
+
panCanvasByWheel(msg.deltaX, msg.deltaY);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
470
614
|
if (msg.type === "zoom") {
|
|
471
615
|
if (presentation !== "canvas")
|
|
472
616
|
return;
|
|
@@ -517,14 +661,33 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
517
661
|
}
|
|
518
662
|
if (msg.type !== "navigation-intent")
|
|
519
663
|
return;
|
|
664
|
+
if (getCanvasCards().some((card) => card.id === frame.cardId && iterationId(card.content)))
|
|
665
|
+
return;
|
|
520
666
|
const normalized = normalizeUrl(msg.url);
|
|
521
667
|
if (!normalized || normalized.origin !== window.location.origin)
|
|
522
668
|
return;
|
|
669
|
+
if (msg.openInCard === true && presentation === "canvas") {
|
|
670
|
+
const card = getCanvasCards().find((candidate) => {
|
|
671
|
+
const route = !iterationId(candidate.content) && normalizeUrl(contentSourceUrl(candidate.content));
|
|
672
|
+
return route && normalizedUrlKey(route) === normalizedUrlKey(normalized);
|
|
673
|
+
}) ?? addCanvasCard(msg.url);
|
|
674
|
+
selectCard(card.id);
|
|
675
|
+
const board = boardRef.current;
|
|
676
|
+
if (board)
|
|
677
|
+
focusCanvasCards([card.id], { width: board.clientWidth, height: board.clientHeight });
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (msg.openInCard === true) {
|
|
681
|
+
// A delayed renderer message must not create a new frame after entering focus mode.
|
|
682
|
+
selectCard(frame.cardId);
|
|
683
|
+
activateIframeWorkspace(msg.url, { width: boardRef.current?.clientWidth ?? window.innerWidth, height: boardRef.current?.clientHeight ?? window.innerHeight }, { replaceActiveCard: true });
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
523
686
|
const frameDocument = frame.iframe.contentDocument;
|
|
524
687
|
if (frameDocument)
|
|
525
688
|
disposeInlineTextEdit("route-disposed", frameDocument);
|
|
526
689
|
});
|
|
527
|
-
}, [broadcastPanModifier, endPanning, handleToolChange, inspectorOpen, movePanning, presentation, sendBoardGestureState, sendInspectorInteractionState, sendPanModifier, sketchAvailable, startPanning, zoomAtPointer]);
|
|
690
|
+
}, [broadcastPanModifier, endPanning, handleToolChange, interactionTool, mode, panCanvasByWheel, inspectorOpen, linksOpenInCards, movePanning, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState, sendPanModifier, sketchAvailable, startPanning, zoomAtPointer]);
|
|
528
691
|
useEffect(() => {
|
|
529
692
|
if (mode === "canvas" && !hasFitAllRan()) {
|
|
530
693
|
if (!fitAllScheduledRef.current) {
|
|
@@ -538,14 +701,16 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
538
701
|
}, [mode, cards.length, inspectorOpen, fitCanvasToBoard]);
|
|
539
702
|
useEffect(() => {
|
|
540
703
|
function onKeyDown(e) {
|
|
541
|
-
if (mode !== "canvas" || presentation !== "canvas" || sketchActive)
|
|
704
|
+
if (mode !== "canvas" || presentation !== "canvas" || sketchActive || interactionTool === "select")
|
|
542
705
|
return;
|
|
543
706
|
if ((e.key === "Delete" || e.key === "Backspace") && !isEditableEvent(e)) {
|
|
707
|
+
if (e.defaultPrevented || getSelectedElements().length > 0 || getCanvasCards().length < 2)
|
|
708
|
+
return;
|
|
544
709
|
const selectedCardId = getSelectedCardId();
|
|
545
710
|
if (!selectedCardId)
|
|
546
711
|
return;
|
|
547
712
|
e.preventDefault();
|
|
548
|
-
|
|
713
|
+
removeFrame(selectedCardId);
|
|
549
714
|
return;
|
|
550
715
|
}
|
|
551
716
|
if (e.code === "Space" && !e.repeat) {
|
|
@@ -561,7 +726,7 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
561
726
|
}
|
|
562
727
|
function onKeyUp(e) {
|
|
563
728
|
if (e.code === "Space") {
|
|
564
|
-
spaceHeldRef.current =
|
|
729
|
+
spaceHeldRef.current = false;
|
|
565
730
|
broadcastPanModifier(spaceHeldRef.current);
|
|
566
731
|
if (!panningRef.current) {
|
|
567
732
|
setBoardCursorClass(spaceHeldRef.current ? "is-grabbable" : "");
|
|
@@ -575,6 +740,15 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
575
740
|
window.removeEventListener("keyup", onKeyUp, true);
|
|
576
741
|
};
|
|
577
742
|
}, [broadcastPanModifier, interactionTool, mode, presentation, sketchActive]);
|
|
743
|
+
const handleBoardClick = useCallback((event) => {
|
|
744
|
+
if (presentation !== "canvas" || sketchActive || spaceHeldRef.current || panningRef.current)
|
|
745
|
+
return;
|
|
746
|
+
if (event.target === event.currentTarget
|
|
747
|
+
|| (event.target instanceof HTMLElement && event.target.dataset.test === "canvas-board-content")) {
|
|
748
|
+
setSelectedElement(null);
|
|
749
|
+
deselectCard();
|
|
750
|
+
}
|
|
751
|
+
}, [presentation, sketchActive]);
|
|
578
752
|
const handleBoardPointerDown = useCallback((e) => {
|
|
579
753
|
if (presentation !== "canvas" || sketchActive)
|
|
580
754
|
return;
|
|
@@ -599,22 +773,35 @@ export function CanvasWorkspace({ primaryUrl }) {
|
|
|
599
773
|
window.addEventListener("pointerup", onUp);
|
|
600
774
|
}, [endPanning, movePanning, presentation, sketchActive, startPanning]);
|
|
601
775
|
useEffect(() => {
|
|
602
|
-
if (mode !== "canvas" || presentation !== "canvas"
|
|
776
|
+
if (mode !== "canvas" || presentation !== "canvas")
|
|
603
777
|
return;
|
|
604
778
|
function handleGlobalWheel(e) {
|
|
605
|
-
|
|
779
|
+
const zoom = e.ctrlKey || e.metaKey;
|
|
780
|
+
const board = boardRef.current;
|
|
781
|
+
if (zoom && sketchActive)
|
|
782
|
+
return;
|
|
783
|
+
const path = e.composedPath();
|
|
784
|
+
// Comment pins overlay the board without being its DOM descendants.
|
|
785
|
+
const overCommentPin = path.some((node) => node instanceof Element && node.matches(".comment-pin"));
|
|
786
|
+
if (!zoom && (interactionTool === "select" || !board || (!path.includes(board) && !overCommentPin)))
|
|
606
787
|
return;
|
|
607
788
|
e.preventDefault();
|
|
608
789
|
e.stopPropagation();
|
|
609
|
-
|
|
790
|
+
if (zoom)
|
|
791
|
+
zoomAtPointer({ x: e.clientX, y: e.clientY }, e.deltaY);
|
|
792
|
+
else if (board) {
|
|
793
|
+
const delta = wheelPanDelta(e, { width: board.clientWidth, height: board.clientHeight });
|
|
794
|
+
panCanvasByWheel(delta.x, delta.y);
|
|
795
|
+
}
|
|
610
796
|
}
|
|
611
797
|
window.addEventListener("wheel", handleGlobalWheel, { capture: true, passive: false });
|
|
612
798
|
return () => window.removeEventListener("wheel", handleGlobalWheel, { capture: true });
|
|
613
|
-
}, [mode, presentation, sketchActive, zoomAtPointer]);
|
|
799
|
+
}, [mode, presentation, sketchActive, interactionTool, panCanvasByWheel, zoomAtPointer]);
|
|
800
|
+
const renderCard = (card) => (_jsx(CanvasCard, { card: card, presentation: presentation, presentationCard: card.id === presentationCardId, linkedToActive: linkedFrameIds.includes(card.id), onShowFocus: showFocus, onDuplicate: duplicateDesign, onCreateIteration: canvasIterationsEnabled ? createIteration : undefined, documentOwner: inspectorSession }, card.id));
|
|
614
801
|
if (mode !== "canvas")
|
|
615
802
|
return null;
|
|
616
|
-
return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "canvas-styles", children: WORKSPACE_STYLES }), _jsx("div", { className: `canvas-workspace${presentationTransitioning ? " is-presentation-transitioning" : ""}`, "data-test": "canvas-workspace", "data-presentation": presentation, style: { right: inspectorOpen ? "min(320px, 100vw)" : 0 }, children: _jsxs("div", { className: `canvas-workspace__board ${boardCursorClass}`.trim(), "data-test": "canvas-board", ref: boardRef, onPointerDown: handleBoardPointerDown, children: [!primaryUrl ? (_jsx("div", { className: "canvas-workspace__target-error", "data-test": "canvas-target-error", children: "This editor URL does not identify an application page." })) : null, inspectorOpen && presentation === "focus" ? (_jsx("div", { className: "canvas-workspace__focus-action", "data-test": "canvas-show-canvas", children: _jsxs(Button, { variant: "quiet", size: "default", type: "button", onClick: showCanvas, children: [_jsx(IconCornerLeftUp, { size: "var(--icon-size-small)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), "Canvas"] }) })) : null, _jsx("div", { className: "canvas-workspace__board-content", style: {
|
|
803
|
+
return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "canvas-styles", children: WORKSPACE_STYLES }), _jsx("div", { className: `canvas-workspace${presentationTransitioning ? " is-presentation-transitioning" : ""}`, "data-test": "canvas-workspace", "data-presentation": presentation, style: { right: inspectorOpen ? "min(320px, 100vw)" : 0 }, children: _jsxs("div", { className: `canvas-workspace__board ${boardCursorClass}${layoutTransitioning ? " is-layout-transitioning" : ""}`.trim(), "data-test": "canvas-board", ref: boardRef, style: { cursor: boardCursor }, onPointerDown: handleBoardPointerDown, onClick: handleBoardClick, children: [!primaryUrl ? (_jsx("div", { className: "canvas-workspace__target-error", "data-test": "canvas-target-error", children: "This editor URL does not identify an application page." })) : null, inspectorOpen && presentation === "focus" ? (_jsx("div", { className: "canvas-workspace__focus-action", "data-test": "canvas-show-canvas", children: _jsxs(Button, { variant: "quiet", size: "default", type: "button", onClick: showCanvas, children: [_jsx(IconCornerLeftUp, { size: "var(--icon-size-small)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), "Canvas"] }) })) : null, _jsx("div", { className: "canvas-workspace__board-content", style: {
|
|
617
804
|
transform: `translate(${camera.x}px, ${camera.y}px) scale(${camera.zoom})`,
|
|
618
805
|
transformOrigin: "0 0",
|
|
619
|
-
}, "data-test": "canvas-board-content", children: cards.map(
|
|
806
|
+
}, "data-test": "canvas-board-content", children: cards.map(renderCard) }), inspectorOpen && !sketchActive ? (_jsx(CanvasToolbar, { tool: interactionTool, sketchEnabled: sketchAvailable, onToolChange: handleToolChange })) : null] }) }), inspectorOpen && sketchActive ? (_jsx("div", { className: "canvas-toolbar__portal", "data-test": "canvas-toolbar-portal", style: { right: inspectorOpen ? "min(320px, 100vw)" : 0 }, children: _jsx(CanvasToolbar, { tool: interactionTool, sketchEnabled: sketchAvailable, onToolChange: handleToolChange }) })) : null] }));
|
|
620
807
|
}
|