nudge-ui 0.2.0 → 0.2.1
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/bin/nudge-ui.mjs +3 -1
- package/dist/cli/agentCommand.d.ts +8 -0
- package/dist/cli/agentCommand.js +59 -0
- package/dist/client.mjs +872 -12
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +60 -11
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +49 -6
- package/dist/hosts/next/wrapper.js +30 -2
- package/dist/hosts/projectBridge.d.ts +20 -0
- package/dist/hosts/projectBridge.js +97 -0
- package/dist/hosts/static/cli.d.ts +1 -1
- package/dist/hosts/static/cli.js +6 -1
- package/dist/hosts/static/manifest.d.ts +1 -1
- package/dist/hosts/static/manifest.js +1 -1
- package/dist/hosts/static/server.js +44 -3
- package/dist/hosts/vite/vite.d.ts +5 -1
- package/dist/hosts/vite/vite.js +78 -14
- package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
- package/dist/inspector/agent/McpConnectionDialog.js +11 -35
- package/dist/inspector/agent/client.d.ts +10 -3
- package/dist/inspector/agent/client.js +80 -9
- package/dist/inspector/agent/connectionStatus.js +3 -3
- package/dist/inspector/agent/httpTransport.d.ts +2 -0
- package/dist/inspector/agent/httpTransport.js +37 -17
- package/dist/inspector/agent/protocol.d.ts +8 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +1 -1
- package/dist/inspector/agent/verification.js +69 -12
- package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
- package/dist/inspector/canvas/CanvasCard.js +181 -91
- package/dist/inspector/canvas/CanvasElementOverlay.js +246 -52
- package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
- package/dist/inspector/canvas/CanvasToolbar.js +24 -0
- package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +392 -73
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
- package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
- package/dist/inspector/canvas/canvasResize.d.ts +30 -0
- package/dist/inspector/canvas/canvasResize.js +52 -0
- package/dist/inspector/canvas/canvasStore.d.ts +30 -6
- package/dist/inspector/canvas/canvasStore.js +128 -61
- package/dist/inspector/canvas/frameProtocol.d.ts +69 -5
- package/dist/inspector/canvas/frameProtocol.js +56 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
- package/dist/inspector/canvas/projection.d.ts +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +29 -29
- package/dist/inspector/canvas/rendererElementSelector.js +341 -30
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
- package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
- package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
- package/dist/inspector/canvas/sessionStore.d.ts +1 -0
- package/dist/inspector/canvas/sessionStore.js +12 -4
- package/dist/inspector/canvas/staleChangeDetector.js +16 -14
- package/dist/inspector/changes/changesLog.js +31 -1
- package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
- package/dist/inspector/changes/managedStyleProjection.js +3 -3
- package/dist/inspector/changes/structuralTypes.d.ts +4 -0
- package/dist/inspector/clientManifest.d.ts +5 -0
- package/dist/inspector/clientManifest.js +11 -0
- package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
- package/dist/inspector/index.d.ts +2 -0
- package/dist/inspector/index.js +74 -24
- package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
- package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
- package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
- package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
- package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
- package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
- package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
- package/dist/inspector/inline-text/textEditChange.js +165 -0
- package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
- package/dist/inspector/inspection/selectionProperty.js +1 -0
- package/dist/inspector/overlay/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +353 -0
- package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
- package/dist/inspector/overlay/structuralGestures.js +12 -10
- package/dist/inspector/projection/managedStylesheet.js +4 -1
- package/dist/inspector/projection/renderedInstance.d.ts +2 -0
- package/dist/inspector/projection/renderedInstance.js +5 -1
- package/dist/inspector/projection/structuralProjection.d.ts +2 -0
- package/dist/inspector/projection/structuralProjection.js +85 -1
- package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
- package/dist/inspector/projection/textProjection.d.ts +2 -0
- package/dist/inspector/projection/textProjection.js +10 -4
- package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
- package/dist/inspector/prompt/clipboardHandoff.js +3 -3
- package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
- package/dist/inspector/prompt/generatePrompt.js +19 -2
- package/dist/inspector/runtime/editorShell.d.ts +2 -0
- package/dist/inspector/runtime/editorShell.js +4 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.js +10 -0
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +8 -4
- package/dist/inspector/shell/CopyPromptButton.js +95 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +37 -38
- package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalButton.js +57 -0
- package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
- package/dist/inspector/shell/originalPreview.d.ts +9 -0
- package/dist/inspector/shell/originalPreview.js +61 -0
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +15 -0
- package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
- package/dist/inspector/sketch/SketchChanges.js +72 -0
- package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
- package/dist/inspector/sketch/SketchEditor.js +402 -0
- package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
- package/dist/inspector/sketch/SketchEntryButton.js +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
- package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
- package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
- package/dist/inspector/sketch/SketchOverlay.js +420 -0
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
- package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
- package/dist/inspector/sketch/SketchWorkspace.js +152 -0
- package/dist/inspector/sketch/capture.d.ts +55 -0
- package/dist/inspector/sketch/capture.js +350 -0
- package/dist/inspector/sketch/freehand.d.ts +13 -0
- package/dist/inspector/sketch/freehand.js +64 -0
- package/dist/inspector/sketch/geometry.d.ts +17 -0
- package/dist/inspector/sketch/geometry.js +98 -0
- package/dist/inspector/sketch/handoff.d.ts +21 -0
- package/dist/inspector/sketch/handoff.js +72 -0
- package/dist/inspector/sketch/interaction.d.ts +21 -0
- package/dist/inspector/sketch/interaction.js +98 -0
- package/dist/inspector/sketch/model.d.ts +104 -0
- package/dist/inspector/sketch/model.js +282 -0
- package/dist/inspector/sketch/persistence.d.ts +21 -0
- package/dist/inspector/sketch/persistence.js +205 -0
- package/dist/inspector/sketch/raster.d.ts +22 -0
- package/dist/inspector/sketch/raster.js +203 -0
- package/dist/inspector/sketch/sketchNote.d.ts +10 -0
- package/dist/inspector/sketch/sketchNote.js +30 -0
- package/dist/inspector/sketch/store.d.ts +47 -0
- package/dist/inspector/sketch/store.js +409 -0
- package/dist/inspector/styleEditors/BorderEditor.js +15 -19
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
- package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
- package/dist/inspector/styleEditors/ColorPicker.js +11 -36
- package/dist/inspector/styleEditors/GapField.d.ts +3 -2
- package/dist/inspector/styleEditors/GapField.js +2 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +11 -8
- package/dist/inspector/styleEditors/SpacingBox.js +23 -30
- package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
- package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
- package/dist/inspector/styleEditors/layoutValue.js +3 -0
- package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
- package/dist/inspector/styleEditors/nudgeValue.js +91 -5
- package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
- package/dist/inspector/styleEditors/stylePresence.js +22 -0
- package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
- package/dist/inspector/styleEditors/textRelevance.js +35 -0
- package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
- package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
- package/dist/inspector/tokens/TokenField.js +111 -7
- package/dist/inspector/tokens/editActions.d.ts +2 -0
- package/dist/inspector/tokens/editActions.js +2 -2
- package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
- package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
- package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
- package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
- package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
- package/dist/inspector/tokens/resolution.d.ts +3 -26
- package/dist/inspector/tokens/resolution.js +44 -735
- package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
- package/dist/inspector/tokens/tokenAvailability.js +204 -0
- package/dist/inspector/ui/Button.d.ts +1 -1
- package/dist/inspector/ui/SideValuesField.js +2 -2
- package/dist/inspector/ui/StatusCallout.d.ts +4 -1
- package/dist/inspector/ui/StatusCallout.js +2 -2
- package/dist/inspector/ui/styles.js +13 -11
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +261 -57
- package/dist/transport/editor.d.ts +28 -0
- package/dist/transport/editor.js +132 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +7 -0
- package/dist/transport/routes.js +7 -0
- package/package.json +3 -1
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- package/dist/inspector/shell/panelLayout.js +0 -29
|
@@ -1,30 +1,66 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { IconCornerLeftUp } from "@tabler/icons-react";
|
|
4
|
+
import { useCanvasCards, activateIframeWorkspace, removeCanvasCard, getSelectedCardId, getFocusedCardId, getCanvasCards, setBoardCamera, getBoardCamera, fitAllCards, setCanvasPresentation, focusCard, hasFitAllRan, useBoardCamera, useCanvasPresentation, useCanvasPresentationTransitioning, useSelectedCardId, useFocusedCardId, resizeCard, setCardPosition, updateCardTitle, CARD_GAP, updateCardUrl, } from "./canvasStore.js";
|
|
4
5
|
import { CanvasCard } from "./CanvasCard.js";
|
|
6
|
+
import { Button } from "../ui/Button.js";
|
|
5
7
|
import { useCanvasMode } from "./canvasStore.js";
|
|
6
8
|
import { subscribeChanges } from "../changes/changesLog.js";
|
|
9
|
+
import { subscribeOriginalPreview, isOriginalPreviewActive } from "../shell/originalPreview.js";
|
|
7
10
|
import { recordCanvasStructuralProjectionReports } from "../projection/structuralProjection.js";
|
|
8
11
|
import { recordCanvasRenderedInstanceProjectionReports } from "../projection/renderedInstance.js";
|
|
9
12
|
import { recordCanvasTextProjectionReports } from "../projection/textProjection.js";
|
|
10
|
-
import {
|
|
13
|
+
import { getChangesList, isPreviewableChange } from "../changes/changesLog.js";
|
|
14
|
+
import { changeKey } from "../changes/model.js";
|
|
15
|
+
import { getWorkspaceChanges } from "../changes/workspaceChanges.js";
|
|
16
|
+
import { beginPreviewAttempt, publishPreviewDiagnostic } from "../changes/previewDiagnostics.js";
|
|
17
|
+
import { verifyManagedStyleProjection } from "../changes/managedStyleProjection.js";
|
|
18
|
+
import { getCanvasPreviewDocument, getRegisteredFrames, isCanvasCanonicalProjectionRevisionCurrent, PROJECT_ID, projectToAllReadyCards, recordCanvasProjectionApplied, WORKSPACE_ID, } from "./projection.js";
|
|
11
19
|
import { normalizeUrl } from "./normalizeUrl.js";
|
|
12
|
-
import {
|
|
20
|
+
import { createNudgeUiEditorUrl } from "../../transport/editor.js";
|
|
21
|
+
import { disposeInlineTextEdit } from "../inline-text/inlineTextEditor.js";
|
|
22
|
+
import { PROTOCOL_VERSION, isProjectionAppliedMessage, isKeyboardShortcutMessage, isRenderedInstanceProjectionReportMessage, isTextProjectionReportMessage, isStructuralProjectionReportMessage, } from "./frameProtocol.js";
|
|
13
23
|
import { iframePointToClientPoint, zoomCameraAtPointer } from "./canvasGestures.js";
|
|
14
|
-
const canvasWorkspaceStyles = ".canvas-workspace {\n position: fixed;\n inset: 0;\n display: flex;\n flex-direction: column;\n background:
|
|
15
|
-
const canvasCardStyles = ".canvas-card {\n position: relative;\n}\n\n.canvas-card__frame {\n position: relative;\n width: 100%;\n height: 100%;\n background: var(--surface-panel);\n border-radius:
|
|
16
|
-
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:
|
|
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-8));\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(--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 .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(--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__toolbar {\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-start;\n z-index: 1;\n cursor: grab;\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transition: opacity 120ms ease;\n}\n\n.canvas-card:hover .canvas-card__toolbar,\n.canvas-card:focus-within .canvas-card__toolbar,\n.canvas-card.is-selected .canvas-card__toolbar,\n.canvas-card.is-focused .canvas-card__toolbar {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n}\n\n.canvas-card.is-dragging .canvas-card__toolbar {\n cursor: grabbing;\n}\n\n.canvas-card__drag-surface {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--space-8);\n flex: 1 1 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__dimensions {\n position: absolute;\n left: 50%;\n bottom: 0;\n color: var(--text-secondary);\n font-size: var(--font-size-base);\n font-variant-numeric: tabular-nums;\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";
|
|
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";
|
|
17
27
|
import { useInspectorOpen } from "../shell/openStore.js";
|
|
18
28
|
import { isEditableEvent } from "../shell/shortcuts.js";
|
|
19
29
|
import { acknowledgeAgentRendererReady } from "./agentPresentation.js";
|
|
20
30
|
import { useInspectorSession } from "../session/sessionContext.js";
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
|
|
32
|
+
import { subscribeCanvasRendererMessages } from "./rendererMessageRouter.js";
|
|
33
|
+
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";
|
|
36
|
+
import { getActiveCanvasFrame } from "./activeCanvasDocument.js";
|
|
37
|
+
import { startSketchCapture } from "../sketch/SketchWorkspace.js";
|
|
38
|
+
import { cancelSketchInteraction, useSketchInteractionActive } from "../sketch/interaction.js";
|
|
39
|
+
import { isNudgeUiDev } from "../runtime/devFlag.js";
|
|
40
|
+
const WORKSPACE_STYLES = [foundationStyles, canvasWorkspaceStyles, canvasCardStyles, canvasToolbarStyles].join("\n");
|
|
41
|
+
const PRESENTATION_TRANSITION_MS = 200;
|
|
42
|
+
function snapshotRect(rect) {
|
|
43
|
+
return {
|
|
44
|
+
left: rect.left,
|
|
45
|
+
top: rect.top,
|
|
46
|
+
width: rect.width,
|
|
47
|
+
height: rect.height,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function CanvasWorkspace({ primaryUrl }) {
|
|
23
51
|
const mode = useCanvasMode();
|
|
52
|
+
const runtimeConfig = useNudgeUiRuntimeConfig();
|
|
24
53
|
const inspectorSession = useInspectorSession();
|
|
25
54
|
const inspectorOpen = useInspectorOpen();
|
|
26
55
|
const cards = useCanvasCards();
|
|
27
56
|
const camera = useBoardCamera();
|
|
57
|
+
const presentation = useCanvasPresentation();
|
|
58
|
+
const presentationTransitioning = useCanvasPresentationTransitioning();
|
|
59
|
+
const selectedCardId = useSelectedCardId();
|
|
60
|
+
const focusedCardId = useFocusedCardId();
|
|
61
|
+
const sketchActive = useSketchInteractionActive();
|
|
62
|
+
const sketchEnabled = isNudgeUiDev() && runtimeConfig.demo !== true;
|
|
63
|
+
const sketchAvailable = sketchEnabled && getActiveCanvasFrame() !== null;
|
|
28
64
|
const boardRef = useRef(null);
|
|
29
65
|
const panningRef = useRef(false);
|
|
30
66
|
const panOriginRef = useRef({ x: 0, y: 0 });
|
|
@@ -32,7 +68,150 @@ export function CanvasWorkspace() {
|
|
|
32
68
|
const framePanContextRef = useRef(null);
|
|
33
69
|
const spaceHeldRef = useRef(false);
|
|
34
70
|
const fitAllScheduledRef = useRef(false);
|
|
71
|
+
const activatedTargetRef = useRef(null);
|
|
72
|
+
const demoSeededRef = useRef(false);
|
|
73
|
+
const previousPresentationRef = useRef(presentation);
|
|
74
|
+
const focusCardRectRef = useRef(null);
|
|
75
|
+
const canvasEntryAnimationRef = useRef(null);
|
|
35
76
|
const [boardCursorClass, setBoardCursorClass] = useState("");
|
|
77
|
+
const [interactionTool, setInteractionTool] = useState("move");
|
|
78
|
+
const presentationCardId = selectedCardId ?? focusedCardId ?? cards[0]?.id ?? null;
|
|
79
|
+
useLayoutEffect(() => {
|
|
80
|
+
const board = boardRef.current;
|
|
81
|
+
const cardElement = presentationCardId
|
|
82
|
+
? board?.querySelector(`[data-card-id="${presentationCardId}"]`)
|
|
83
|
+
: null;
|
|
84
|
+
const boardContent = board?.querySelector('[data-test="canvas-board-content"]');
|
|
85
|
+
if (presentation === "focus") {
|
|
86
|
+
canvasEntryAnimationRef.current?.cancel();
|
|
87
|
+
canvasEntryAnimationRef.current = null;
|
|
88
|
+
if (boardContent)
|
|
89
|
+
boardContent.style.transition = "";
|
|
90
|
+
if (cardElement)
|
|
91
|
+
cardElement.style.transition = "";
|
|
92
|
+
focusCardRectRef.current = cardElement ? snapshotRect(cardElement.getBoundingClientRect()) : null;
|
|
93
|
+
}
|
|
94
|
+
else if (presentation === "canvas"
|
|
95
|
+
&& previousPresentationRef.current === "focus"
|
|
96
|
+
&& presentationTransitioning
|
|
97
|
+
&& cardElement
|
|
98
|
+
&& boardContent
|
|
99
|
+
&& focusCardRectRef.current
|
|
100
|
+
&& typeof cardElement.animate === "function") {
|
|
101
|
+
// Commit the final layout before measuring it. The old implementation
|
|
102
|
+
// transitioned both the camera and the card's position, which made the
|
|
103
|
+
// nested scale and translation produce a sideways drift.
|
|
104
|
+
boardContent.style.transition = "none";
|
|
105
|
+
cardElement.style.transition = "none";
|
|
106
|
+
const oldRect = focusCardRectRef.current;
|
|
107
|
+
const newRect = snapshotRect(cardElement.getBoundingClientRect());
|
|
108
|
+
if (newRect.width > 0 && newRect.height > 0 && camera.zoom > 0) {
|
|
109
|
+
const oldCenterX = oldRect.left + oldRect.width / 2;
|
|
110
|
+
const oldCenterY = oldRect.top + oldRect.height / 2;
|
|
111
|
+
const newCenterX = newRect.left + newRect.width / 2;
|
|
112
|
+
const newCenterY = newRect.top + newRect.height / 2;
|
|
113
|
+
const localTranslateX = (oldCenterX - newCenterX) / camera.zoom;
|
|
114
|
+
const localTranslateY = (oldCenterY - newCenterY) / camera.zoom;
|
|
115
|
+
const scaleX = oldRect.width / newRect.width;
|
|
116
|
+
const scaleY = oldRect.height / newRect.height;
|
|
117
|
+
const animation = cardElement.animate([
|
|
118
|
+
{
|
|
119
|
+
transformOrigin: "center center",
|
|
120
|
+
transform: `translate(${localTranslateX}px, ${localTranslateY}px) scale(${scaleX}, ${scaleY})`,
|
|
121
|
+
},
|
|
122
|
+
{ transformOrigin: "center center", transform: "none" },
|
|
123
|
+
], { duration: PRESENTATION_TRANSITION_MS, easing: "ease", fill: "both" });
|
|
124
|
+
canvasEntryAnimationRef.current = animation;
|
|
125
|
+
void animation.finished.then(() => {
|
|
126
|
+
if (canvasEntryAnimationRef.current !== animation)
|
|
127
|
+
return;
|
|
128
|
+
canvasEntryAnimationRef.current = null;
|
|
129
|
+
animation.cancel();
|
|
130
|
+
boardContent.style.transition = "";
|
|
131
|
+
cardElement.style.transition = "";
|
|
132
|
+
}).catch(() => {
|
|
133
|
+
if (canvasEntryAnimationRef.current !== animation)
|
|
134
|
+
return;
|
|
135
|
+
canvasEntryAnimationRef.current = null;
|
|
136
|
+
boardContent.style.transition = "";
|
|
137
|
+
cardElement.style.transition = "";
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
boardContent.style.transition = "";
|
|
142
|
+
cardElement.style.transition = "";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
previousPresentationRef.current = presentation;
|
|
146
|
+
}, [camera.zoom, presentation, presentationCardId, presentationTransitioning]);
|
|
147
|
+
useLayoutEffect(() => {
|
|
148
|
+
const board = boardRef.current;
|
|
149
|
+
if (!primaryUrl || !board)
|
|
150
|
+
return;
|
|
151
|
+
if (activatedTargetRef.current === primaryUrl && cards.length > 0)
|
|
152
|
+
return;
|
|
153
|
+
activatedTargetRef.current = primaryUrl;
|
|
154
|
+
const viewport = {
|
|
155
|
+
width: board.clientWidth,
|
|
156
|
+
height: board.clientHeight,
|
|
157
|
+
};
|
|
158
|
+
const primaryCard = activateIframeWorkspace(primaryUrl, viewport);
|
|
159
|
+
if (runtimeConfig.demo === true && primaryCard && !demoSeededRef.current) {
|
|
160
|
+
demoSeededRef.current = true;
|
|
161
|
+
const demoPages = runtimeConfig.demoPages ?? [];
|
|
162
|
+
const demoCardLabels = runtimeConfig.demoCardLabels ?? [];
|
|
163
|
+
const orderedDemoCards = demoCardLabels.length > 0;
|
|
164
|
+
const seededCards = [];
|
|
165
|
+
const demoCardWidth = primaryCard.width;
|
|
166
|
+
let nextX = 0;
|
|
167
|
+
for (const [index, route] of demoPages.entries()) {
|
|
168
|
+
const url = new URL(route, primaryUrl);
|
|
169
|
+
if (url.origin !== window.location.origin)
|
|
170
|
+
continue;
|
|
171
|
+
const card = activateIframeWorkspace(url.href, viewport);
|
|
172
|
+
if (!card)
|
|
173
|
+
continue;
|
|
174
|
+
resizeCard(card.id, demoCardWidth, 900);
|
|
175
|
+
seededCards.push(card);
|
|
176
|
+
if (!orderedDemoCards) {
|
|
177
|
+
setCardPosition(card.id, nextX, 0);
|
|
178
|
+
nextX += demoCardWidth + CARD_GAP;
|
|
179
|
+
}
|
|
180
|
+
if (orderedDemoCards && demoCardLabels[index]) {
|
|
181
|
+
updateCardTitle(card.id, demoCardLabels[index]);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (orderedDemoCards) {
|
|
185
|
+
resizeCard(primaryCard.id, demoCardWidth, primaryCard.height);
|
|
186
|
+
let layoutX = 0;
|
|
187
|
+
// Read each card's final width so restored or resized cards cannot overlap.
|
|
188
|
+
const orderedCardIds = new Set([...seededCards.map((card) => card.id), primaryCard.id]);
|
|
189
|
+
for (const cardId of orderedCardIds) {
|
|
190
|
+
const card = getCanvasCards().find((candidate) => candidate.id === cardId);
|
|
191
|
+
if (!card)
|
|
192
|
+
continue;
|
|
193
|
+
setCardPosition(card.id, layoutX, 0);
|
|
194
|
+
layoutX += card.width + CARD_GAP;
|
|
195
|
+
}
|
|
196
|
+
const primaryLabel = demoCardLabels[demoPages.length];
|
|
197
|
+
if (primaryLabel)
|
|
198
|
+
updateCardTitle(primaryCard.id, primaryLabel);
|
|
199
|
+
}
|
|
200
|
+
focusCard(primaryCard.id);
|
|
201
|
+
}
|
|
202
|
+
}, [primaryUrl, cards.length, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
if (primaryUrl && activatedTargetRef.current !== primaryUrl)
|
|
205
|
+
return;
|
|
206
|
+
const activeCardId = selectedCardId ?? focusedCardId ?? cards[0]?.id;
|
|
207
|
+
const activeCard = cards.find((card) => card.id === activeCardId);
|
|
208
|
+
if (!activeCard)
|
|
209
|
+
return;
|
|
210
|
+
const editorUrl = createNudgeUiEditorUrl(activeCard.url);
|
|
211
|
+
if (window.location.href !== editorUrl) {
|
|
212
|
+
window.history.replaceState(window.history.state, "", editorUrl);
|
|
213
|
+
}
|
|
214
|
+
}, [cards, focusedCardId, primaryUrl, selectedCardId]);
|
|
36
215
|
const sendPanModifier = useCallback((iframe, cardId, spaceHeld) => {
|
|
37
216
|
iframe.contentWindow?.postMessage({
|
|
38
217
|
type: "pan-modifier",
|
|
@@ -43,6 +222,33 @@ export function CanvasWorkspace() {
|
|
|
43
222
|
spaceHeld,
|
|
44
223
|
}, window.location.origin);
|
|
45
224
|
}, []);
|
|
225
|
+
const sendInspectorInteractionState = useCallback((iframe, cardId, open) => {
|
|
226
|
+
iframe.contentWindow?.postMessage({
|
|
227
|
+
type: "inspector-interaction-state",
|
|
228
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
229
|
+
projectId: PROJECT_ID,
|
|
230
|
+
workspaceId: WORKSPACE_ID,
|
|
231
|
+
cardId,
|
|
232
|
+
open,
|
|
233
|
+
interactionsEnabled: true,
|
|
234
|
+
}, window.location.origin);
|
|
235
|
+
}, []);
|
|
236
|
+
const sendBoardGestureState = useCallback((iframe, cardId, enabled) => {
|
|
237
|
+
iframe.contentWindow?.postMessage({
|
|
238
|
+
type: "board-gesture-state",
|
|
239
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
240
|
+
projectId: PROJECT_ID,
|
|
241
|
+
workspaceId: WORKSPACE_ID,
|
|
242
|
+
cardId,
|
|
243
|
+
enabled,
|
|
244
|
+
}, window.location.origin);
|
|
245
|
+
}, []);
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
for (const [cardId, iframe] of getRegisteredFrames()) {
|
|
248
|
+
sendInspectorInteractionState(iframe, cardId, inspectorOpen);
|
|
249
|
+
sendBoardGestureState(iframe, cardId, presentation === "canvas");
|
|
250
|
+
}
|
|
251
|
+
}, [inspectorOpen, presentation, sendBoardGestureState, sendInspectorInteractionState]);
|
|
46
252
|
const broadcastPanModifier = useCallback((spaceHeld) => {
|
|
47
253
|
for (const [cardId, iframe] of getRegisteredFrames()) {
|
|
48
254
|
sendPanModifier(iframe, cardId, spaceHeld);
|
|
@@ -84,56 +290,176 @@ export function CanvasWorkspace() {
|
|
|
84
290
|
panningRef.current = false;
|
|
85
291
|
setBoardCursorClass(spaceHeldRef.current ? "is-grabbable" : "");
|
|
86
292
|
}, []);
|
|
293
|
+
const stopPanMode = useCallback(() => {
|
|
294
|
+
spaceHeldRef.current = false;
|
|
295
|
+
broadcastPanModifier(false);
|
|
296
|
+
endPanning();
|
|
297
|
+
setBoardCursorClass("");
|
|
298
|
+
}, [broadcastPanModifier, endPanning]);
|
|
299
|
+
const handleToolChange = useCallback((nextTool) => {
|
|
300
|
+
if (nextTool === "sketch") {
|
|
301
|
+
if (!sketchEnabled)
|
|
302
|
+
return;
|
|
303
|
+
const hostElement = getActiveCanvasFrame();
|
|
304
|
+
if (!hostElement)
|
|
305
|
+
return;
|
|
306
|
+
stopPanMode();
|
|
307
|
+
setInteractionTool(nextTool);
|
|
308
|
+
startSketchCapture(hostElement);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (sketchActive)
|
|
312
|
+
cancelSketchInteraction();
|
|
313
|
+
if (nextTool === "pan") {
|
|
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
|
+
}
|
|
325
|
+
setInteractionTool(nextTool);
|
|
326
|
+
stopPanMode();
|
|
327
|
+
}, [broadcastPanModifier, presentation, sketchActive, sketchEnabled, stopPanMode]);
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
if (mode !== "canvas")
|
|
330
|
+
return;
|
|
331
|
+
function onKeyDown(event) {
|
|
332
|
+
if (isEditableEvent(event))
|
|
333
|
+
return;
|
|
334
|
+
const nextTool = canvasToolForEvent(event);
|
|
335
|
+
if (!nextTool || (nextTool === "sketch" && !sketchAvailable))
|
|
336
|
+
return;
|
|
337
|
+
event.preventDefault();
|
|
338
|
+
handleToolChange(nextTool);
|
|
339
|
+
}
|
|
340
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
341
|
+
return () => window.removeEventListener("keydown", onKeyDown, true);
|
|
342
|
+
}, [handleToolChange, mode, sketchAvailable]);
|
|
343
|
+
const handleZoomStep = useCallback((direction) => {
|
|
344
|
+
if (presentation === "focus") {
|
|
345
|
+
if (direction < 0) {
|
|
346
|
+
const board = boardRef.current;
|
|
347
|
+
setCanvasPresentation("canvas", board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
|
|
348
|
+
}
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const nextZoom = adjacentCanvasZoomLevel(getBoardCamera().zoom, direction);
|
|
352
|
+
if (nextZoom === null)
|
|
353
|
+
return;
|
|
354
|
+
setBoardCamera({ ...getBoardCamera(), zoom: nextZoom });
|
|
355
|
+
}, [presentation]);
|
|
356
|
+
const showCanvas = useCallback(() => {
|
|
357
|
+
handleZoomStep(-1);
|
|
358
|
+
}, [handleZoomStep]);
|
|
359
|
+
const showFocus = useCallback((cardId) => {
|
|
360
|
+
focusCard(cardId);
|
|
361
|
+
setCanvasPresentation("focus");
|
|
362
|
+
}, []);
|
|
87
363
|
useEffect(() => {
|
|
88
|
-
|
|
364
|
+
if (!sketchActive && interactionTool === "sketch") {
|
|
365
|
+
setInteractionTool("move");
|
|
366
|
+
}
|
|
367
|
+
}, [interactionTool, sketchActive]);
|
|
368
|
+
useLayoutEffect(() => {
|
|
369
|
+
if (presentation !== "focus")
|
|
370
|
+
return;
|
|
371
|
+
endPanning();
|
|
372
|
+
spaceHeldRef.current = false;
|
|
373
|
+
setBoardCursorClass("");
|
|
374
|
+
broadcastPanModifier(false);
|
|
375
|
+
}, [broadcastPanModifier, endPanning, presentation]);
|
|
376
|
+
useEffect(() => {
|
|
377
|
+
const stopChanges = subscribeChanges(() => {
|
|
89
378
|
projectToAllReadyCards();
|
|
90
379
|
});
|
|
380
|
+
const stopPreview = subscribeOriginalPreview(() => {
|
|
381
|
+
projectToAllReadyCards();
|
|
382
|
+
});
|
|
383
|
+
return () => {
|
|
384
|
+
stopChanges();
|
|
385
|
+
stopPreview();
|
|
386
|
+
};
|
|
91
387
|
}, []);
|
|
92
388
|
useEffect(() => {
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
return;
|
|
96
|
-
if (!event.data || typeof event.data !== "object")
|
|
97
|
-
return;
|
|
98
|
-
const frame = findCanvasFrameBySource(event.source);
|
|
99
|
-
if (!frame)
|
|
389
|
+
return subscribeCanvasRendererMessages(({ cardId, iframe, identity, message: msg }) => {
|
|
390
|
+
if (msg.type === "renderer-hello")
|
|
100
391
|
return;
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
392
|
+
if (isKeyboardShortcutMessage(msg, identity)) {
|
|
393
|
+
if (msg.phase === "keydown") {
|
|
394
|
+
const nextTool = canvasToolForCode(msg.code);
|
|
395
|
+
if (nextTool && (nextTool !== "sketch" || sketchAvailable))
|
|
396
|
+
handleToolChange(nextTool);
|
|
397
|
+
}
|
|
106
398
|
return;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
399
|
+
}
|
|
400
|
+
const frame = { cardId, iframe };
|
|
401
|
+
const syncActiveFrameUrl = (url) => {
|
|
402
|
+
updateCardUrl(frame.cardId, url);
|
|
403
|
+
const activeCardId = getSelectedCardId() ?? getFocusedCardId() ?? getCanvasCards()[0]?.id;
|
|
404
|
+
if (activeCardId !== frame.cardId && getCanvasCards().length !== 1)
|
|
405
|
+
return;
|
|
406
|
+
window.history.replaceState(window.history.state, "", createNudgeUiEditorUrl(url));
|
|
111
407
|
};
|
|
112
|
-
if (isProjectionAppliedMessage(
|
|
113
|
-
recordCanvasProjectionApplied(frame.cardId,
|
|
408
|
+
if (isProjectionAppliedMessage(msg, identity)) {
|
|
409
|
+
recordCanvasProjectionApplied(frame.cardId, msg.revision);
|
|
410
|
+
// While hold-to-view-original is active the frame intentionally shows
|
|
411
|
+
// the page without inspector changes; skip conflict verification.
|
|
412
|
+
if (isOriginalPreviewActive())
|
|
413
|
+
return;
|
|
414
|
+
const frameDocument = frame.iframe.contentDocument;
|
|
415
|
+
if (frameDocument && isCanvasCanonicalProjectionRevisionCurrent(frameDocument, msg.revision)) {
|
|
416
|
+
const attempt = beginPreviewAttempt(getCanvasPreviewDocument(frame.cardId), getWorkspaceChanges().revision);
|
|
417
|
+
if (attempt) {
|
|
418
|
+
for (const change of getChangesList()) {
|
|
419
|
+
if (!isPreviewableChange(change))
|
|
420
|
+
continue;
|
|
421
|
+
const result = verifyManagedStyleProjection(change, null, frameDocument);
|
|
422
|
+
if (result) {
|
|
423
|
+
publishPreviewDiagnostic(attempt, changeKey(change), result);
|
|
424
|
+
if (result.status === "conflict" && result.reason === "animation") {
|
|
425
|
+
window.setTimeout(() => {
|
|
426
|
+
if (!isCanvasCanonicalProjectionRevisionCurrent(frameDocument, msg.revision))
|
|
427
|
+
return;
|
|
428
|
+
const settled = verifyManagedStyleProjection(change, null, frameDocument);
|
|
429
|
+
if (settled)
|
|
430
|
+
publishPreviewDiagnostic(attempt, changeKey(change), settled);
|
|
431
|
+
}, 300);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
114
437
|
return;
|
|
115
438
|
}
|
|
116
|
-
if (isStructuralProjectionReportMessage(
|
|
117
|
-
recordCanvasStructuralProjectionReports(frame.cardId,
|
|
439
|
+
if (isStructuralProjectionReportMessage(msg, identity)) {
|
|
440
|
+
recordCanvasStructuralProjectionReports(frame.cardId, msg.revision, msg.reports);
|
|
118
441
|
return;
|
|
119
442
|
}
|
|
120
|
-
if (isRenderedInstanceProjectionReportMessage(
|
|
121
|
-
recordCanvasRenderedInstanceProjectionReports(frame.cardId,
|
|
443
|
+
if (isRenderedInstanceProjectionReportMessage(msg, identity)) {
|
|
444
|
+
recordCanvasRenderedInstanceProjectionReports(frame.cardId, msg.revision, msg.reports);
|
|
122
445
|
return;
|
|
123
446
|
}
|
|
124
|
-
if (isTextProjectionReportMessage(
|
|
125
|
-
recordCanvasTextProjectionReports(frame.cardId,
|
|
447
|
+
if (isTextProjectionReportMessage(msg, identity)) {
|
|
448
|
+
recordCanvasTextProjectionReports(frame.cardId, msg.revision, msg.reports);
|
|
126
449
|
return;
|
|
127
450
|
}
|
|
128
|
-
// SAFETY: isRendererMessageFor validated the frame identity and message shape above.
|
|
129
|
-
const msg = event.data;
|
|
130
451
|
if (msg.type === "frame-ready") {
|
|
131
452
|
acknowledgeAgentRendererReady(frame.cardId);
|
|
453
|
+
syncActiveFrameUrl(msg.url);
|
|
132
454
|
// A card can finish loading after Space was pressed on the controller.
|
|
133
455
|
// Seed it with the current modifier state before its first pointer event.
|
|
134
456
|
sendPanModifier(frame.iframe, frame.cardId, spaceHeldRef.current);
|
|
457
|
+
sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen);
|
|
458
|
+
sendBoardGestureState(frame.iframe, frame.cardId, presentation === "canvas");
|
|
135
459
|
}
|
|
136
460
|
if (msg.type === "pan-modifier") {
|
|
461
|
+
if (presentation !== "canvas")
|
|
462
|
+
return;
|
|
137
463
|
spaceHeldRef.current = msg.spaceHeld;
|
|
138
464
|
if (!panningRef.current) {
|
|
139
465
|
setBoardCursorClass(msg.spaceHeld ? "is-grabbable" : "");
|
|
@@ -142,6 +468,8 @@ export function CanvasWorkspace() {
|
|
|
142
468
|
return;
|
|
143
469
|
}
|
|
144
470
|
if (msg.type === "zoom") {
|
|
471
|
+
if (presentation !== "canvas")
|
|
472
|
+
return;
|
|
145
473
|
const iframeRect = frame.iframe.getBoundingClientRect();
|
|
146
474
|
const point = iframePointToClientPoint(iframeRect, msg.point, getBoardCamera().zoom);
|
|
147
475
|
zoomAtPointer(point, msg.deltaY);
|
|
@@ -179,12 +507,12 @@ export function CanvasWorkspace() {
|
|
|
179
507
|
endPanning();
|
|
180
508
|
return;
|
|
181
509
|
}
|
|
182
|
-
if (msg.type === "
|
|
183
|
-
const
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
510
|
+
if (msg.type === "frame-metadata") {
|
|
511
|
+
const frameDocument = frame.iframe.contentDocument;
|
|
512
|
+
if (frameDocument)
|
|
513
|
+
disposeInlineTextEdit("route-disposed", frameDocument);
|
|
514
|
+
if (msg.url)
|
|
515
|
+
syncActiveFrameUrl(msg.url);
|
|
188
516
|
return;
|
|
189
517
|
}
|
|
190
518
|
if (msg.type !== "navigation-intent")
|
|
@@ -192,17 +520,11 @@ export function CanvasWorkspace() {
|
|
|
192
520
|
const normalized = normalizeUrl(msg.url);
|
|
193
521
|
if (!normalized || normalized.origin !== window.location.origin)
|
|
194
522
|
return;
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
addCanvasCard(msg.url);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
window.addEventListener("message", onMessage);
|
|
204
|
-
return () => window.removeEventListener("message", onMessage);
|
|
205
|
-
}, [broadcastPanModifier, endPanning, movePanning, sendPanModifier, startPanning, zoomAtPointer]);
|
|
523
|
+
const frameDocument = frame.iframe.contentDocument;
|
|
524
|
+
if (frameDocument)
|
|
525
|
+
disposeInlineTextEdit("route-disposed", frameDocument);
|
|
526
|
+
});
|
|
527
|
+
}, [broadcastPanModifier, endPanning, handleToolChange, inspectorOpen, movePanning, presentation, sendBoardGestureState, sendInspectorInteractionState, sendPanModifier, sketchAvailable, startPanning, zoomAtPointer]);
|
|
206
528
|
useEffect(() => {
|
|
207
529
|
if (mode === "canvas" && !hasFitAllRan()) {
|
|
208
530
|
if (!fitAllScheduledRef.current) {
|
|
@@ -216,7 +538,7 @@ export function CanvasWorkspace() {
|
|
|
216
538
|
}, [mode, cards.length, inspectorOpen, fitCanvasToBoard]);
|
|
217
539
|
useEffect(() => {
|
|
218
540
|
function onKeyDown(e) {
|
|
219
|
-
if (mode !== "canvas")
|
|
541
|
+
if (mode !== "canvas" || presentation !== "canvas" || sketchActive)
|
|
220
542
|
return;
|
|
221
543
|
if ((e.key === "Delete" || e.key === "Backspace") && !isEditableEvent(e)) {
|
|
222
544
|
const selectedCardId = getSelectedCardId();
|
|
@@ -227,9 +549,7 @@ export function CanvasWorkspace() {
|
|
|
227
549
|
return;
|
|
228
550
|
}
|
|
229
551
|
if (e.code === "Space" && !e.repeat) {
|
|
230
|
-
|
|
231
|
-
const target = e.target;
|
|
232
|
-
if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable))
|
|
552
|
+
if (isEditableEvent(e))
|
|
233
553
|
return;
|
|
234
554
|
e.preventDefault();
|
|
235
555
|
spaceHeldRef.current = true;
|
|
@@ -241,10 +561,10 @@ export function CanvasWorkspace() {
|
|
|
241
561
|
}
|
|
242
562
|
function onKeyUp(e) {
|
|
243
563
|
if (e.code === "Space") {
|
|
244
|
-
spaceHeldRef.current =
|
|
245
|
-
broadcastPanModifier(
|
|
564
|
+
spaceHeldRef.current = interactionTool === "pan";
|
|
565
|
+
broadcastPanModifier(spaceHeldRef.current);
|
|
246
566
|
if (!panningRef.current) {
|
|
247
|
-
setBoardCursorClass("");
|
|
567
|
+
setBoardCursorClass(spaceHeldRef.current ? "is-grabbable" : "");
|
|
248
568
|
}
|
|
249
569
|
}
|
|
250
570
|
}
|
|
@@ -254,8 +574,10 @@ export function CanvasWorkspace() {
|
|
|
254
574
|
window.removeEventListener("keydown", onKeyDown, true);
|
|
255
575
|
window.removeEventListener("keyup", onKeyUp, true);
|
|
256
576
|
};
|
|
257
|
-
}, [broadcastPanModifier, mode]);
|
|
577
|
+
}, [broadcastPanModifier, interactionTool, mode, presentation, sketchActive]);
|
|
258
578
|
const handleBoardPointerDown = useCallback((e) => {
|
|
579
|
+
if (presentation !== "canvas" || sketchActive)
|
|
580
|
+
return;
|
|
259
581
|
if (panningRef.current)
|
|
260
582
|
return;
|
|
261
583
|
if (!spaceHeldRef.current)
|
|
@@ -275,9 +597,9 @@ export function CanvasWorkspace() {
|
|
|
275
597
|
}
|
|
276
598
|
window.addEventListener("pointermove", onMove);
|
|
277
599
|
window.addEventListener("pointerup", onUp);
|
|
278
|
-
}, [endPanning, movePanning, startPanning]);
|
|
600
|
+
}, [endPanning, movePanning, presentation, sketchActive, startPanning]);
|
|
279
601
|
useEffect(() => {
|
|
280
|
-
if (mode !== "canvas")
|
|
602
|
+
if (mode !== "canvas" || presentation !== "canvas" || sketchActive)
|
|
281
603
|
return;
|
|
282
604
|
function handleGlobalWheel(e) {
|
|
283
605
|
if (!e.ctrlKey && !e.metaKey)
|
|
@@ -288,14 +610,11 @@ export function CanvasWorkspace() {
|
|
|
288
610
|
}
|
|
289
611
|
window.addEventListener("wheel", handleGlobalWheel, { capture: true, passive: false });
|
|
290
612
|
return () => window.removeEventListener("wheel", handleGlobalWheel, { capture: true });
|
|
291
|
-
}, [mode, zoomAtPointer]);
|
|
292
|
-
|
|
293
|
-
exitCanvasToCard(card);
|
|
294
|
-
}
|
|
295
|
-
if (mode !== "canvas" || cards.length === 0)
|
|
613
|
+
}, [mode, presentation, sketchActive, zoomAtPointer]);
|
|
614
|
+
if (mode !== "canvas")
|
|
296
615
|
return null;
|
|
297
|
-
return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "canvas-styles", children: WORKSPACE_STYLES }), _jsx("div", { className:
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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: {
|
|
617
|
+
transform: `translate(${camera.x}px, ${camera.y}px) scale(${camera.zoom})`,
|
|
618
|
+
transformOrigin: "0 0",
|
|
619
|
+
}, "data-test": "canvas-board-content", children: cards.map((card) => (_jsx(CanvasCard, { card: card, presentation: presentation, presentationCard: card.id === presentationCardId, onShowFocus: showFocus, documentOwner: inspectorSession }, card.id))) }), !sketchActive ? (_jsx(CanvasToolbar, { tool: interactionTool, sketchEnabled: sketchAvailable, onToolChange: handleToolChange })) : null] }) }), 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] }));
|
|
301
620
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Resolves only the selected or focused frame. */
|
|
2
|
+
export declare function resolveActiveCanvasFrame(frames: ReadonlyMap<string, HTMLIFrameElement>, selectedCardId: string | null, focusedCardId: string | null): HTMLIFrameElement | null;
|
|
3
|
+
export declare function getActiveCanvasDocument(): Document | null;
|
|
4
|
+
/** Returns the iframe that owns the active renderer document. */
|
|
5
|
+
export declare function getActiveCanvasFrame(): HTMLIFrameElement | null;
|