nudge-ui 0.2.0 → 0.2.2

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.
Files changed (231) hide show
  1. package/bin/nudge-ui.mjs +3 -1
  2. package/dist/cli/agentCommand.d.ts +8 -0
  3. package/dist/cli/agentCommand.js +59 -0
  4. package/dist/client.mjs +878 -11
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +63 -11
  7. package/dist/hosts/astro/integration.d.ts +2 -2
  8. package/dist/hosts/astro/integration.js +4 -3
  9. package/dist/hosts/environment.d.ts +15 -0
  10. package/dist/hosts/environment.js +16 -0
  11. package/dist/hosts/next/sidecar.d.ts +4 -0
  12. package/dist/hosts/next/sidecar.js +56 -7
  13. package/dist/hosts/next/wrapper.d.ts +2 -0
  14. package/dist/hosts/next/wrapper.js +32 -3
  15. package/dist/hosts/projectBridge.d.ts +21 -0
  16. package/dist/hosts/projectBridge.js +98 -0
  17. package/dist/hosts/static/cli.d.ts +1 -1
  18. package/dist/hosts/static/cli.js +6 -1
  19. package/dist/hosts/static/manifest.d.ts +1 -1
  20. package/dist/hosts/static/manifest.js +3 -1
  21. package/dist/hosts/static/server.js +45 -3
  22. package/dist/hosts/vite/vite.d.ts +6 -1
  23. package/dist/hosts/vite/vite.js +82 -15
  24. package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
  25. package/dist/inspector/agent/McpConnectionDialog.js +20 -37
  26. package/dist/inspector/agent/client.d.ts +16 -3
  27. package/dist/inspector/agent/client.js +114 -11
  28. package/dist/inspector/agent/connectionStatus.d.ts +3 -3
  29. package/dist/inspector/agent/connectionStatus.js +11 -4
  30. package/dist/inspector/agent/httpTransport.d.ts +8 -0
  31. package/dist/inspector/agent/httpTransport.js +78 -18
  32. package/dist/inspector/agent/protocol.d.ts +11 -3
  33. package/dist/inspector/agent/protocol.js +1 -1
  34. package/dist/inspector/agent/verification.d.ts +1 -1
  35. package/dist/inspector/agent/verification.js +69 -12
  36. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  37. package/dist/inspector/canvas/CanvasCard.js +181 -91
  38. package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
  39. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  40. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  41. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  42. package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
  43. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  44. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  45. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  46. package/dist/inspector/canvas/canvasResize.js +52 -0
  47. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  48. package/dist/inspector/canvas/canvasStore.js +128 -61
  49. package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
  50. package/dist/inspector/canvas/frameProtocol.js +63 -2
  51. package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
  52. package/dist/inspector/canvas/keyboardShortcuts.js +16 -0
  53. package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
  54. package/dist/inspector/canvas/linkEligibility.js +0 -29
  55. package/dist/inspector/canvas/projection.d.ts +7 -0
  56. package/dist/inspector/canvas/projection.js +118 -25
  57. package/dist/inspector/canvas/rendererBootstrap.js +42 -29
  58. package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
  59. package/dist/inspector/canvas/rendererElementSelector.js +385 -58
  60. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  61. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  62. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  63. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  64. package/dist/inspector/canvas/sessionStore.js +12 -4
  65. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  66. package/dist/inspector/changes/changesLog.js +31 -1
  67. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  68. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  69. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  70. package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
  71. package/dist/inspector/changes/workspaceChanges.js +29 -3
  72. package/dist/inspector/clientManifest.d.ts +7 -0
  73. package/dist/inspector/clientManifest.js +12 -0
  74. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  75. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  76. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  77. package/dist/inspector/index.d.ts +2 -0
  78. package/dist/inspector/index.js +80 -32
  79. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  80. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  81. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  82. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  83. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  84. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  85. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  86. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  87. package/dist/inspector/inline-text/textEditChange.js +165 -0
  88. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  89. package/dist/inspector/inspection/selectionProperty.js +1 -0
  90. package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
  91. package/dist/inspector/overlay/DomNavigator.js +40 -0
  92. package/dist/inspector/overlay/InspectorOverlay.js +6 -2
  93. package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
  94. package/dist/inspector/overlay/clickPolicy.js +34 -3
  95. package/dist/inspector/overlay/elementSelector.d.ts +1 -1
  96. package/dist/inspector/overlay/elementSelector.js +18 -7
  97. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  98. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  99. package/dist/inspector/overlay/spacingGestures.js +351 -0
  100. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  101. package/dist/inspector/overlay/structuralGestures.js +12 -10
  102. package/dist/inspector/projection/managedStylesheet.js +4 -1
  103. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  104. package/dist/inspector/projection/renderedInstance.js +5 -1
  105. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  106. package/dist/inspector/projection/structuralProjection.js +85 -1
  107. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  108. package/dist/inspector/projection/textProjection.d.ts +2 -0
  109. package/dist/inspector/projection/textProjection.js +10 -4
  110. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  111. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  112. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  113. package/dist/inspector/prompt/generatePrompt.js +19 -2
  114. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  115. package/dist/inspector/runtime/editorShell.js +4 -0
  116. package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
  117. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  118. package/dist/inspector/selection/hierarchy.d.ts +6 -0
  119. package/dist/inspector/selection/hierarchy.js +8 -0
  120. package/dist/inspector/selection/selectionStore.js +2 -0
  121. package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
  122. package/dist/inspector/settings/SettingsDialog.js +12 -2
  123. package/dist/inspector/shell/AppShell.js +5 -1
  124. package/dist/inspector/shell/ChangesLog.js +26 -22
  125. package/dist/inspector/shell/CopyPromptButton.js +102 -19
  126. package/dist/inspector/shell/EmptyState.js +1 -1
  127. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  128. package/dist/inspector/shell/InspectorShell.js +38 -42
  129. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  130. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  131. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  132. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  133. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  134. package/dist/inspector/shell/originalPreview.js +61 -0
  135. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  136. package/dist/inspector/shell/shortcuts.js +15 -0
  137. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  138. package/dist/inspector/sketch/SketchChanges.js +72 -0
  139. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  140. package/dist/inspector/sketch/SketchEditor.js +402 -0
  141. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  142. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  143. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  144. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  145. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  146. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  147. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  148. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  149. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  150. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  151. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  152. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  153. package/dist/inspector/sketch/capture.d.ts +55 -0
  154. package/dist/inspector/sketch/capture.js +350 -0
  155. package/dist/inspector/sketch/freehand.d.ts +13 -0
  156. package/dist/inspector/sketch/freehand.js +64 -0
  157. package/dist/inspector/sketch/geometry.d.ts +17 -0
  158. package/dist/inspector/sketch/geometry.js +98 -0
  159. package/dist/inspector/sketch/handoff.d.ts +21 -0
  160. package/dist/inspector/sketch/handoff.js +72 -0
  161. package/dist/inspector/sketch/interaction.d.ts +21 -0
  162. package/dist/inspector/sketch/interaction.js +98 -0
  163. package/dist/inspector/sketch/model.d.ts +104 -0
  164. package/dist/inspector/sketch/model.js +282 -0
  165. package/dist/inspector/sketch/persistence.d.ts +21 -0
  166. package/dist/inspector/sketch/persistence.js +205 -0
  167. package/dist/inspector/sketch/raster.d.ts +22 -0
  168. package/dist/inspector/sketch/raster.js +203 -0
  169. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  170. package/dist/inspector/sketch/sketchNote.js +30 -0
  171. package/dist/inspector/sketch/store.d.ts +47 -0
  172. package/dist/inspector/sketch/store.js +434 -0
  173. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  174. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  175. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  176. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  177. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  178. package/dist/inspector/styleEditors/GapField.js +2 -2
  179. package/dist/inspector/styleEditors/GridSection.js +1 -1
  180. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  181. package/dist/inspector/styleEditors/SpacingBox.js +31 -33
  182. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  183. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  184. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  185. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  186. package/dist/inspector/styleEditors/nudgeValue.js +93 -5
  187. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  188. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  189. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  190. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  191. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  192. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  193. package/dist/inspector/tokens/TokenField.js +130 -7
  194. package/dist/inspector/tokens/TokensPanel.js +2 -1
  195. package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
  196. package/dist/inspector/tokens/dragPointerLock.js +70 -0
  197. package/dist/inspector/tokens/editActions.d.ts +2 -0
  198. package/dist/inspector/tokens/editActions.js +2 -2
  199. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  200. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  201. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  202. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  203. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  204. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  205. package/dist/inspector/tokens/resolution.d.ts +3 -26
  206. package/dist/inspector/tokens/resolution.js +44 -735
  207. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  208. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  209. package/dist/inspector/ui/Button.d.ts +1 -1
  210. package/dist/inspector/ui/Disclosure.d.ts +22 -0
  211. package/dist/inspector/ui/Disclosure.js +13 -0
  212. package/dist/inspector/ui/SideValuesField.js +2 -2
  213. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  214. package/dist/inspector/ui/StatusCallout.js +2 -2
  215. package/dist/inspector/ui/dragCursor.d.ts +8 -0
  216. package/dist/inspector/ui/dragCursor.js +21 -0
  217. package/dist/inspector/ui/styles.js +21 -17
  218. package/dist/internal/agent-protocol.d.ts +65 -1
  219. package/dist/internal/agent-protocol.js +183 -3
  220. package/dist/nudge-ui.mjs +276 -58
  221. package/dist/transport/editor.d.ts +47 -0
  222. package/dist/transport/editor.js +163 -0
  223. package/dist/transport/index.d.ts +1 -0
  224. package/dist/transport/index.js +1 -0
  225. package/dist/transport/routes.d.ts +10 -0
  226. package/dist/transport/routes.js +10 -0
  227. package/package.json +3 -1
  228. package/dist/inspector/shell/DomNavigation.d.ts +0 -8
  229. package/dist/inspector/shell/DomNavigation.js +0 -30
  230. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  231. package/dist/inspector/shell/panelLayout.js +0 -29
@@ -1,12 +1,11 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useReducer, useRef, useState } from "react";
3
- import { isElementClickMessage, isRendererMessageFor, } from "./frameProtocol.js";
4
- import { findCanvasFrameBySource, PROJECT_ID, WORKSPACE_ID } from "./projection.js";
5
- import { useBoardCamera, useCanvasCards } from "./canvasStore.js";
2
+ import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
3
+ import { PROTOCOL_VERSION, isElementClickMessage, isInlineTextIntentMessage, } from "./frameProtocol.js";
4
+ import { useBoardCamera, useCanvasCards, useCanvasPresentation, useCanvasPresentationTransitioning } from "./canvasStore.js";
6
5
  import { handleElementClick } from "./rendererSelectionProxy.js";
7
- import { getSelectedElements, useSelectedElement, useSelectedElements } from "../selection/selectionStore.js";
8
- import { getMarginFills, getMarginGuides, readBorderWidths, toRect, } from "../overlay/overlayGeometry.js";
9
- const overlayStyles = ".canvas-element-overlay {\n position: fixed;\n pointer-events: none;\n z-index: 3;\n background: rgb(59 130 246 / 10%);\n outline: 1.5px solid var(--accent);\n outline-offset: -1px;\n}\n\n.canvas-selected-outline {\n position: fixed;\n pointer-events: none;\n z-index: 3;\n outline: 2px solid var(--accent);\n outline-offset: -2px;\n}\n\n.canvas-hover-margin-fill {\n position: fixed;\n pointer-events: none;\n z-index: 2;\n background: rgb(59 130 246 / 14%);\n}\n\n.canvas-hover-margin {\n position: fixed;\n pointer-events: none;\n z-index: 2;\n border-color: rgb(59 130 246 / 52%);\n border-style: dotted;\n border-width: 0;\n opacity: 0.72;\n}\n\n.canvas-hover-margin[data-axis=\"horizontal\"] {\n border-top-width: 1px;\n}\n\n.canvas-hover-margin[data-axis=\"vertical\"] {\n border-left-width: 1px;\n}\n\n.canvas-dom-drop-line {\n z-index: 2147483647;\n border-radius: 999px;\n background: #3b82f6;\n box-shadow: 0 0 0 1px rgb(255 255 255 / 95%), 0 1px 5px rgb(37 99 235 / 72%);\n}\n\n.canvas-dom-drop-line::before,\n.canvas-dom-drop-line::after {\n position: absolute;\n top: 50%;\n width: 8px;\n height: 8px;\n border: 2px solid #ffffff;\n border-radius: 50%;\n background: #2563eb;\n content: \"\";\n transform: translateY(-50%);\n}\n\n.canvas-dom-drop-line::before { left: -3px; }\n.canvas-dom-drop-line::after { right: -3px; }\n\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::before,\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::after {\n left: 50%;\n right: auto;\n transform: translateX(-50%);\n}\n\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::before { top: -3px; }\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::after { top: auto; bottom: -3px; }\n\n.canvas-dom-drop-target {\n z-index: 2147483646;\n box-sizing: border-box;\n border: 1px solid rgb(59 130 246 / 72%);\n border-radius: 4px;\n background: rgb(59 130 246 / 5%);\n}\n";
6
+ import { getSelectedElements, useHierarchy, useSelectedElement, useSelectedElements } from "../selection/selectionStore.js";
7
+ import { readBorderWidths, toRect, } from "../overlay/overlayGeometry.js";
8
+ const overlayStyles = ".canvas-element-overlay {\n position: fixed;\n pointer-events: none;\n z-index: 1;\n background: transparent;\n outline: 1px solid var(--accent);\n outline-offset: -1px;\n}\n\n.canvas-spacing-guide {\n position: fixed;\n z-index: 1;\n border-radius: 999px;\n background: var(--accent);\n outline: none;\n box-shadow: none;\n pointer-events: none;\n}\n\n.canvas-spacing-fill {\n position: fixed;\n z-index: 1;\n pointer-events: none;\n background: rgb(59 130 246 / 14%);\n}\n\n.canvas-spacing-fill[data-kind=\"gap\"] {\n background: rgb(236 72 153 / 14%);\n}\n\n.canvas-spacing-guide[data-kind=\"gap\"] {\n background: #ec4899;\n}\n\n.canvas-selected-outline {\n position: fixed;\n pointer-events: none;\n z-index: 1;\n outline: 1px solid var(--accent);\n outline-offset: -1px;\n}\n\n.canvas-dom-drop-line {\n z-index: 2147483647;\n border-radius: 999px;\n background: #3b82f6;\n box-shadow: 0 0 0 1px rgb(255 255 255 / 95%), 0 1px 5px rgb(37 99 235 / 72%);\n}\n\n.canvas-dom-drop-line::before,\n.canvas-dom-drop-line::after {\n position: absolute;\n top: 50%;\n width: 8px;\n height: 8px;\n border: 2px solid #ffffff;\n border-radius: 50%;\n background: #2563eb;\n content: \"\";\n transform: translateY(-50%);\n}\n\n.canvas-dom-drop-line::before { left: -3px; }\n.canvas-dom-drop-line::after { right: -3px; }\n\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::before,\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::after {\n left: 50%;\n right: auto;\n transform: translateX(-50%);\n}\n\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::before { top: -3px; }\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::after { top: auto; bottom: -3px; }\n\n.canvas-dom-drop-target {\n z-index: 2147483646;\n box-sizing: border-box;\n border: 1px solid rgb(59 130 246 / 72%);\n border-radius: 4px;\n background: rgb(59 130 246 / 5%);\n}\n";
10
9
  import { deleteElement, getDropLocationAtPoint, moveElement, nudgeElement } from "../overlay/structuralGestures.js";
11
10
  import { redo, undo } from "../changes/changesLog.js";
12
11
  import { resolveSelectionFromElement } from "../selection/resolveSelection.js";
@@ -16,8 +15,18 @@ import { DropGuideOverlay } from "../overlay/DropGuideOverlay.js";
16
15
  import { getMeasurementGeometry } from "../overlay/measurementGeometry.js";
17
16
  import { MeasurementGuideOverlay } from "../overlay/MeasurementGuideOverlay.js";
18
17
  import { projectMeasurementSegments } from "./measurementProjection.js";
18
+ import { getRegisteredFrames, PROJECT_ID, WORKSPACE_ID } from "./projection.js";
19
19
  import { RENDERER_ELEMENT_ID_ATTR } from "./rendererCidIndex.js";
20
20
  import { observeSelectedGeometry } from "../overlay/selectedGeometry.js";
21
+ import { handleInlineTextEditIntent, useInlineTextSession } from "../inline-text/inlineTextEditor.js";
22
+ import { setInspectorOpen, toggleInspector } from "../shell/openStore.js";
23
+ import { EMPTY_TEXT_PROJECTION_ATTR } from "../projection/textProjection.js";
24
+ import { subscribeCanvasRendererMessages } from "./rendererMessageRouter.js";
25
+ import { setStyles } from "../tokens/editActions.js";
26
+ import { getSpacingAffordanceAtPoint, getSpacingAffordanceForDescriptor, SPACING_GUIDE_HANDLE_LENGTH, spacingValueCss, spacingValueForDrag, } from "../overlay/spacingGestures.js";
27
+ import { beginLayoutPreview, endLayoutPreview } from "../styleEditors/layoutPreviewState.js";
28
+ import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
29
+ import { DomNavigator } from "../overlay/DomNavigator.js";
21
30
  function projectRect(iframe, rect, zoom) {
22
31
  const frameRect = iframe.getBoundingClientRect();
23
32
  return {
@@ -27,14 +36,6 @@ function projectRect(iframe, rect, zoom) {
27
36
  height: rect.height * zoom,
28
37
  };
29
38
  }
30
- function scaleMargins(margins, zoom) {
31
- return {
32
- top: margins.top * zoom,
33
- right: margins.right * zoom,
34
- bottom: margins.bottom * zoom,
35
- left: margins.left * zoom,
36
- };
37
- }
38
39
  function overlayStyle(rect) {
39
40
  return {
40
41
  position: "fixed",
@@ -45,6 +46,17 @@ function overlayStyle(rect) {
45
46
  pointerEvents: "none",
46
47
  };
47
48
  }
49
+ function spacingGuideStyle(rect) {
50
+ const isHorizontal = rect.width >= rect.height;
51
+ const width = isHorizontal ? Math.min(rect.width, SPACING_GUIDE_HANDLE_LENGTH) : rect.width;
52
+ const height = isHorizontal ? rect.height : Math.min(rect.height, SPACING_GUIDE_HANDLE_LENGTH);
53
+ return overlayStyle({
54
+ left: rect.left + (rect.width - width) / 2,
55
+ top: rect.top + (rect.height - height) / 2,
56
+ width,
57
+ height,
58
+ });
59
+ }
48
60
  function findFrameElement(iframe, elementId, cid, src) {
49
61
  const doc = iframe.contentDocument;
50
62
  if (!doc)
@@ -55,7 +67,9 @@ function findFrameElement(iframe, elementId, cid, src) {
55
67
  if (candidates.length !== 1)
56
68
  return null;
57
69
  const candidate = candidates[0] ?? null;
58
- if (!candidate || candidate.getAttribute("data-cid") !== cid || candidate.getAttribute("data-src") !== src)
70
+ if (!candidate
71
+ || candidate.getAttribute("data-cid") !== cid
72
+ || (candidate.getAttribute("data-src") ?? "") !== src)
59
73
  return null;
60
74
  const frameWindow = doc.defaultView;
61
75
  return frameWindow && candidate instanceof frameWindow.HTMLElement ? candidate : null;
@@ -79,17 +93,79 @@ function projectGuideToCanvas(guide, zoom) {
79
93
  target: project(guide.target, true, true),
80
94
  };
81
95
  }
96
+ function spacingDescriptorMatches(affordance, descriptor) {
97
+ return Boolean(affordance
98
+ && descriptor
99
+ && affordance.kind === descriptor.kind
100
+ && affordance.property === descriptor.property
101
+ && affordance.side === descriptor.side);
102
+ }
103
+ function clearSpacingDrag(ref) {
104
+ const drag = ref.current;
105
+ ref.current = null;
106
+ if (drag)
107
+ restoreSpacingPreview(drag);
108
+ }
109
+ function applySpacingPreview(drag, point, shiftKey = false) {
110
+ const value = spacingValueForDrag(drag.affordance, drag.start, point, shiftKey);
111
+ drag.currentValue = value;
112
+ beginLayoutPreview(drag.element, drag.affordance.property);
113
+ drag.element.style.setProperty(drag.affordance.property, spacingValueCss(value));
114
+ }
115
+ function restoreSpacingPreview(drag) {
116
+ if (drag.originalInlineValue) {
117
+ drag.element.style.setProperty(drag.affordance.property, drag.originalInlineValue, drag.originalInlinePriority);
118
+ }
119
+ else {
120
+ drag.element.style.removeProperty(drag.affordance.property);
121
+ }
122
+ endLayoutPreview(drag.element, drag.affordance.property);
123
+ }
124
+ function commitSpacingDrag(drag) {
125
+ // Restore the authored declaration before committing the managed rule. The
126
+ // captured start value is the baseline because the pointer remains hovered
127
+ // and CSS inspection can still report the transient preview value.
128
+ restoreSpacingPreview(drag);
129
+ if (drag.currentValue === drag.startValue)
130
+ return;
131
+ setStyles(drag.element, [{
132
+ property: drag.affordance.property,
133
+ value: spacingValueCss(drag.currentValue),
134
+ oldRawValue: spacingValueCss(drag.startValue),
135
+ }]);
136
+ }
137
+ function sendMeasureModifier(iframe, cardId, altKey) {
138
+ iframe.contentWindow?.postMessage({
139
+ type: "measure-modifier",
140
+ protocolVersion: PROTOCOL_VERSION,
141
+ projectId: PROJECT_ID,
142
+ workspaceId: WORKSPACE_ID,
143
+ cardId,
144
+ altKey,
145
+ }, window.location.origin);
146
+ }
82
147
  export function CanvasElementOverlay() {
83
148
  const [hover, setHover] = useState(null);
84
149
  const [measureState, setMeasureState] = useState(null);
85
150
  const [, refreshSelectedGeometry] = useReducer((revision) => revision + 1, 0);
86
151
  const selected = useSelectedElement();
87
152
  const selectedElements = useSelectedElements();
153
+ const hierarchy = useHierarchy();
154
+ const domNavigationEnabled = useNudgeUiRuntimeConfig().capabilities.domNavigation === true;
155
+ const inlineTextSession = useInlineTextSession();
88
156
  const camera = useBoardCamera();
89
- useCanvasCards(); // Projected geometry must follow card drag and resize updates.
157
+ const presentation = useCanvasPresentation();
158
+ const presentationTransitioning = useCanvasPresentationTransitioning();
159
+ const projectionZoom = presentation === "focus" ? 1 : camera.zoom;
160
+ const cards = useCanvasCards(); // Projected geometry must follow card drag and resize updates.
161
+ const cardGeometryKey = cards.map((card) => `${card.id}:${card.x}:${card.y}:${card.width}:${card.height}`).join("|");
162
+ const projectionGeometryKey = `${presentation}:${camera.x}:${camera.y}:${projectionZoom}:${cardGeometryKey}`;
163
+ const previousProjectionGeometryKey = useRef(projectionGeometryKey);
90
164
  const dragRef = useRef(null);
165
+ const spacingDragRef = useRef(null);
166
+ const measureAltKeyRef = useRef(false);
91
167
  const dropGuide = useDropGuide("canvas");
92
- const projectedDropGuide = projectGuideToCanvas(dropGuide, camera.zoom);
168
+ const projectedDropGuide = projectGuideToCanvas(dropGuide, projectionZoom);
93
169
  const selectedFrame = selected?.domElement.ownerDocument.defaultView?.frameElement;
94
170
  const selectedInCanvas = selectedFrame instanceof HTMLIFrameElement
95
171
  && selectedFrame.hasAttribute("data-nudge-ui-canvas-renderer");
@@ -105,16 +181,25 @@ export function CanvasElementOverlay() {
105
181
  ? toRect(selectedFrame.getBoundingClientRect())
106
182
  : null;
107
183
  const selectedRect = selectedInCanvas && selectedLocalRect
108
- ? projectRect(selectedFrame, selectedLocalRect, camera.zoom)
184
+ ? projectRect(selectedFrame, selectedLocalRect, projectionZoom)
109
185
  : null;
110
186
  const projectedSelectedGeometry = selectedInCanvas
111
187
  ? selectedRects.flatMap((rect, index) => {
112
188
  const element = selectedElements[index];
113
189
  return rect && element
114
- ? [{ element, rect: projectRect(selectedFrame, rect, camera.zoom) }]
190
+ ? [{ element, rect: projectRect(selectedFrame, rect, projectionZoom) }]
115
191
  : [];
116
192
  })
117
193
  : [];
194
+ // Camera transforms commit on the board before iframe viewport geometry is
195
+ // readable. Re-render once after that commit so overlays use the transformed
196
+ // frame rectangle rather than the previous camera position.
197
+ useLayoutEffect(() => {
198
+ const geometryChanged = previousProjectionGeometryKey.current !== projectionGeometryKey;
199
+ previousProjectionGeometryKey.current = projectionGeometryKey;
200
+ if (selectedInCanvas && geometryChanged)
201
+ refreshSelectedGeometry();
202
+ }, [projectionGeometryKey, selectedInCanvas]);
118
203
  const selectedBorders = selectedInCanvas && selected
119
204
  ? readBorderWidths(selected.domElement)
120
205
  : null;
@@ -124,6 +209,32 @@ export function CanvasElementOverlay() {
124
209
  const stops = selectedElements.map((candidate) => observeSelectedGeometry(candidate.domElement, refreshSelectedGeometry));
125
210
  return () => stops.forEach((stop) => stop());
126
211
  }, [selected, selectedElements, selectedInCanvas]);
212
+ useEffect(() => {
213
+ const broadcastMeasureModifier = (altKey) => {
214
+ measureAltKeyRef.current = altKey;
215
+ for (const [cardId, iframe] of getRegisteredFrames()) {
216
+ sendMeasureModifier(iframe, cardId, altKey);
217
+ }
218
+ };
219
+ const onKeyDown = (event) => {
220
+ if (event.key !== "Alt" || measureAltKeyRef.current)
221
+ return;
222
+ broadcastMeasureModifier(true);
223
+ };
224
+ const onKeyUp = (event) => {
225
+ if (event.key === "Alt")
226
+ broadcastMeasureModifier(false);
227
+ };
228
+ const onBlur = () => broadcastMeasureModifier(false);
229
+ window.addEventListener("keydown", onKeyDown, true);
230
+ window.addEventListener("keyup", onKeyUp, true);
231
+ window.addEventListener("blur", onBlur);
232
+ return () => {
233
+ window.removeEventListener("keydown", onKeyDown, true);
234
+ window.removeEventListener("keyup", onKeyUp, true);
235
+ window.removeEventListener("blur", onBlur);
236
+ };
237
+ }, []);
127
238
  // The selected element's identity is computed once per selection change
128
239
  // (never per hover), so the measurement self-rulers exclusion can compare
129
240
  // identities without a per-hover scan of the frame document.
@@ -135,24 +246,41 @@ export function CanvasElementOverlay() {
135
246
  return elementId ? { elementId } : null;
136
247
  }, [selected?.domElement]);
137
248
  useEffect(() => {
138
- function onMessage(event) {
139
- if (event.origin !== window.location.origin)
140
- return;
141
- if (!event.data || typeof event.data !== "object")
142
- return;
143
- const sourceFrame = findCanvasFrameBySource(event.source);
144
- if (!sourceFrame)
145
- return;
146
- const { cardId: sourceCardId, iframe: sourceIframe } = sourceFrame;
147
- const frameIdentity = {
148
- projectId: PROJECT_ID,
149
- workspaceId: WORKSPACE_ID,
150
- cardId: sourceCardId,
249
+ const refreshSpacingPreviewOverlay = (drag) => {
250
+ const descriptor = {
251
+ kind: drag.affordance.kind,
252
+ property: drag.affordance.property,
253
+ side: drag.affordance.side,
151
254
  };
152
- if (!isRendererMessageFor(event.data, frameIdentity))
255
+ const affordance = getSpacingAffordanceForDescriptor(drag.element, descriptor);
256
+ const rect = toRect(drag.element.getBoundingClientRect());
257
+ const borders = readBorderWidths(drag.element);
258
+ const elementId = drag.element.getAttribute(RENDERER_ELEMENT_ID_ATTR);
259
+ setHover((current) => {
260
+ if (!current
261
+ || current.iframe !== drag.iframe
262
+ || current.identity.elementId !== elementId
263
+ || !current.spacing
264
+ || current.spacing.kind !== descriptor.kind
265
+ || current.spacing.property !== descriptor.property
266
+ || current.spacing.side !== descriptor.side) {
267
+ return current;
268
+ }
269
+ return {
270
+ ...current,
271
+ rect,
272
+ borders,
273
+ spacingGuides: affordance?.affectedGuides ?? [],
274
+ spacingAreas: affordance?.affectedAreas ?? [],
275
+ };
276
+ });
277
+ };
278
+ const unsubscribe = subscribeCanvasRendererMessages(({ cardId: sourceCardId, iframe: sourceIframe, identity: frameIdentity, message: data, }) => {
279
+ if (data.type === "renderer-hello" || data.type === "frame-ready") {
280
+ sendMeasureModifier(sourceIframe, sourceCardId, measureAltKeyRef.current);
281
+ }
282
+ if (data.type === "renderer-hello")
153
283
  return;
154
- // SAFETY: isRendererMessageFor validated the frame identity and message shape above.
155
- const data = event.data;
156
284
  if (data.type === "element-hover") {
157
285
  const msg = data;
158
286
  if (!msg.cid)
@@ -161,13 +289,30 @@ export function CanvasElementOverlay() {
161
289
  setHover((current) => current?.cardId === sourceCardId ? null : current);
162
290
  return;
163
291
  }
292
+ const spacingDescriptor = msg.spacing ?? null;
293
+ const hoverPoint = msg.point ?? null;
294
+ const hoverElement = spacingDescriptor
295
+ ? findFrameElement(sourceIframe, msg.elementId, msg.cid, msg.src)
296
+ : null;
297
+ const hoverAffordance = spacingDescriptor && hoverElement
298
+ ? getSpacingAffordanceForDescriptor(hoverElement, spacingDescriptor)
299
+ : spacingDescriptor && hoverPoint && sourceIframe.contentDocument
300
+ ? getSpacingAffordanceAtPoint(sourceIframe.contentDocument, hoverPoint.x, hoverPoint.y)
301
+ : null;
302
+ const matchingAffordance = spacingDescriptorMatches(hoverAffordance, spacingDescriptor)
303
+ ? hoverAffordance
304
+ : null;
305
+ const spacingGuides = matchingAffordance?.affectedGuides ?? [];
306
+ const spacingAreas = matchingAffordance?.affectedAreas ?? [];
164
307
  setHover({
165
308
  iframe: sourceIframe,
166
309
  identity: { elementId: msg.elementId },
167
310
  rect: msg.rect,
168
- margins: msg.margins ?? { top: 0, right: 0, bottom: 0, left: 0 },
169
311
  borders: msg.borders ?? { top: 0, right: 0, bottom: 0, left: 0 },
170
312
  cardId: sourceCardId,
313
+ spacing: spacingGuides.length > 0 ? spacingDescriptor : null,
314
+ spacingGuides,
315
+ spacingAreas,
171
316
  });
172
317
  }
173
318
  else if (data.type === "element-measure-state") {
@@ -179,14 +324,38 @@ export function CanvasElementOverlay() {
179
324
  });
180
325
  }
181
326
  else if (data.type === "element-click") {
182
- if (!isElementClickMessage(event.data, frameIdentity))
327
+ if (!isElementClickMessage(data, frameIdentity))
183
328
  return;
184
329
  const msg = data;
185
330
  if (!msg.cid)
186
331
  return;
187
332
  handleElementClick(msg, sourceIframe, sourceCardId);
188
333
  }
334
+ else if (data.type === "inline-text-intent") {
335
+ if (!isInlineTextIntentMessage(data, frameIdentity))
336
+ return;
337
+ if (getSelectedElements().length > 1)
338
+ return;
339
+ const emptyProjection = data.emptyProjectionId
340
+ ? Array.from(sourceIframe.contentDocument?.querySelectorAll(`[${EMPTY_TEXT_PROJECTION_ATTR}]`) ?? [])
341
+ .find((candidate) => candidate.getAttribute(EMPTY_TEXT_PROJECTION_ATTR) === data.emptyProjectionId)
342
+ : null;
343
+ const element = emptyProjection ?? findFrameElement(sourceIframe, data.elementId, data.cid, data.src);
344
+ if (!element)
345
+ return;
346
+ handleInlineTextEditIntent(data.intent === "double-click"
347
+ ? { kind: "double-click", target: element, point: data.point }
348
+ : {
349
+ kind: "pointer-down",
350
+ target: element,
351
+ point: data.point,
352
+ clickCount: data.clickCount ?? 0,
353
+ });
354
+ }
189
355
  else if (data.type === "element-deselect") {
356
+ clearSpacingDrag(spacingDragRef);
357
+ dragRef.current = null;
358
+ clearDropGuide("canvas");
190
359
  setSelectedElement(null);
191
360
  }
192
361
  else if (data.type === "element-drag-start") {
@@ -196,6 +365,35 @@ export function CanvasElementOverlay() {
196
365
  const element = findFrameElement(sourceIframe, msg.elementId, msg.cid, msg.src);
197
366
  if (!element)
198
367
  return;
368
+ clearSpacingDrag(spacingDragRef);
369
+ dragRef.current = null;
370
+ const startPoint = msg.startPoint ?? msg.point;
371
+ const spacing = msg.spacing
372
+ ? getSpacingAffordanceForDescriptor(element, msg.spacing)
373
+ : sourceIframe.contentDocument
374
+ ? getSpacingAffordanceAtPoint(sourceIframe.contentDocument, startPoint.x, startPoint.y)
375
+ : null;
376
+ const isSpacingDrag = spacing
377
+ && spacing.element === element
378
+ && (!msg.spacing || spacingDescriptorMatches(spacing, msg.spacing));
379
+ if (isSpacingDrag) {
380
+ spacingDragRef.current = {
381
+ iframe: sourceIframe,
382
+ element,
383
+ affordance: spacing,
384
+ start: startPoint,
385
+ startValue: spacing.value,
386
+ currentValue: spacing.value,
387
+ originalInlineValue: element.style.getPropertyValue(spacing.property),
388
+ originalInlinePriority: element.style.getPropertyPriority(spacing.property),
389
+ };
390
+ const selectedElement = resolveSelectionFromElement(element);
391
+ if (selectedElement)
392
+ setSelectedElement(selectedElement);
393
+ applySpacingPreview(spacingDragRef.current, msg.point, msg.shiftKey === true);
394
+ refreshSpacingPreviewOverlay(spacingDragRef.current);
395
+ return;
396
+ }
199
397
  dragRef.current = { iframe: sourceIframe, element };
200
398
  const selectedElement = resolveSelectionFromElement(element);
201
399
  if (selectedElement)
@@ -203,12 +401,38 @@ export function CanvasElementOverlay() {
203
401
  updateDropGuide(msg.point, sourceIframe);
204
402
  }
205
403
  else if (data.type === "element-drag-move") {
404
+ const spacing = spacingDragRef.current;
405
+ if (spacing && spacing.iframe === sourceIframe) {
406
+ applySpacingPreview(spacing, data.point, data.shiftKey === true);
407
+ refreshSpacingPreviewOverlay(spacing);
408
+ return;
409
+ }
206
410
  updateDropGuide(data.point, sourceIframe);
207
411
  }
208
412
  else if (data.type === "element-drag-end") {
209
413
  const msg = data;
414
+ const spacing = spacingDragRef.current;
415
+ if (spacing && spacing.iframe === sourceIframe) {
416
+ spacingDragRef.current = null;
417
+ if (msg.cancelled) {
418
+ restoreSpacingPreview(spacing);
419
+ refreshSpacingPreviewOverlay(spacing);
420
+ }
421
+ else {
422
+ applySpacingPreview(spacing, msg.point, msg.shiftKey === true);
423
+ commitSpacingDrag(spacing);
424
+ refreshSpacingPreviewOverlay(spacing);
425
+ }
426
+ clearDropGuide("canvas");
427
+ if (!msg.cancelled) {
428
+ const selectedElement = resolveSelectionFromElement(spacing.element);
429
+ if (selectedElement)
430
+ setSelectedElement(selectedElement);
431
+ }
432
+ return;
433
+ }
210
434
  const current = dragRef.current;
211
- if (current && current.iframe === sourceIframe && sourceIframe.contentDocument) {
435
+ if (!msg.cancelled && current && current.iframe === sourceIframe && sourceIframe.contentDocument) {
212
436
  const drop = getDropLocationAtPoint(sourceIframe.contentDocument, current.element, msg.point.x, msg.point.y);
213
437
  if (drop)
214
438
  moveElement(current.element, drop);
@@ -249,6 +473,12 @@ export function CanvasElementOverlay() {
249
473
  undo();
250
474
  }
251
475
  }
476
+ else if (data.type === "inspector-toggle-request") {
477
+ toggleInspector();
478
+ }
479
+ else if (data.type === "inspector-open-request") {
480
+ setInspectorOpen(true);
481
+ }
252
482
  function updateDropGuide(point, iframe) {
253
483
  const current = dragRef.current;
254
484
  if (!current || current.iframe !== iframe || !iframe.contentDocument)
@@ -260,23 +490,22 @@ export function CanvasElementOverlay() {
260
490
  }
261
491
  showDropGuide("canvas", iframe.contentDocument, drop);
262
492
  }
263
- }
264
- window.addEventListener("message", onMessage);
493
+ });
265
494
  return () => {
266
- window.removeEventListener("message", onMessage);
495
+ unsubscribe();
496
+ clearSpacingDrag(spacingDragRef);
497
+ dragRef.current = null;
267
498
  clearDropGuide("canvas");
268
499
  };
269
500
  }, []);
501
+ if (inlineTextSession && inlineTextSession.host.ownerDocument === selected?.domElement.ownerDocument)
502
+ return null;
270
503
  if (!hover && projectedSelectedGeometry.length === 0 && !projectedDropGuide)
271
504
  return null;
272
- const projectedHoverRect = hover ? projectRect(hover.iframe, hover.rect, camera.zoom) : null;
273
- const hoverMargins = hover ? scaleMargins(hover.margins, camera.zoom) : null;
274
- const hoverMarginGuides = projectedHoverRect && hoverMargins
275
- ? getMarginGuides(projectedHoverRect, hoverMargins)
276
- : [];
277
- const hoverMarginFills = projectedHoverRect && hoverMargins
278
- ? getMarginFills(projectedHoverRect, hoverMargins)
279
- : [];
505
+ const projectedHoverRect = hover ? projectRect(hover.iframe, hover.rect, projectionZoom) : null;
506
+ const projectedSpacingGuides = hover?.spacingGuides.map((guide) => projectRect(hover.iframe, guide, projectionZoom)) ?? [];
507
+ const projectedSpacingAreas = hover?.spacingAreas.map((area) => projectRect(hover.iframe, area, projectionZoom)) ?? [];
508
+ const hoverSpacing = hover?.spacing ?? null;
280
509
  const measureStateForSelectedFrame = measureState?.iframe === selectedFrame ? measureState : null;
281
510
  const hoverInSelectedFrame = hover?.iframe === selectedFrame ? hover : null;
282
511
  const showGuideOverlay = Boolean(selectedElements.length === 1
@@ -287,6 +516,9 @@ export function CanvasElementOverlay() {
287
516
  const isSelfHover = Boolean(hoverInSelectedFrame
288
517
  && selectedIdentity
289
518
  && hoverInSelectedFrame.identity.elementId === selectedIdentity.elementId);
519
+ // Selected elements already have a live geometry outline. The hover outline
520
+ // is based on a cached renderer message and can lag during spacing previews.
521
+ const showHoverOutline = Boolean(projectedHoverRect && !isSelfHover);
290
522
  const showMeasurement = Boolean(showGuideOverlay
291
523
  && selectedLocalRect
292
524
  && hoverInSelectedFrame
@@ -295,7 +527,11 @@ export function CanvasElementOverlay() {
295
527
  ? projectMeasurementSegments(selectedFrameRect, getMeasurementGeometry(selectedLocalRect, hoverInSelectedFrame.rect, {
296
528
  selectedBorders: selectedBorders ?? undefined,
297
529
  hoveredBorders: hoverInSelectedFrame.borders,
298
- }).segments, camera.zoom)
530
+ }).segments, projectionZoom)
299
531
  : [];
300
- return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "canvas-element-overlay-styles", children: overlayStyles }), showGuideOverlay && selectedRect && selectedFrameRect ? (_jsx(MeasurementGuideOverlay, { testId: "canvas-measurement-overlay", selectedRect: selectedRect, guideViewport: selectedFrameRect, segments: measurementSegments })) : null, projectedHoverRect ? (_jsxs(_Fragment, { children: [hoverMarginFills.map((fill) => (_jsx("div", { className: "canvas-hover-margin-fill", "data-side": fill.side, style: overlayStyle(fill), "aria-hidden": "true" }, fill.side))), _jsx("div", { className: "canvas-element-overlay", "data-test": "canvas-hover-outline", style: overlayStyle(projectedHoverRect), "aria-hidden": "true" }), hoverMarginGuides.map((guide) => (_jsx("div", { className: "canvas-hover-margin", "data-axis": guide.axis, "data-distance": guide.distance, "data-side": guide.side, style: overlayStyle(guide), "aria-hidden": "true" }, guide.side)))] })) : null, projectedSelectedGeometry.map(({ element, rect }, index) => (_jsx("div", { className: "canvas-selected-outline", "data-test": "canvas-selected-outline", "data-selected-cid": element.cid, "data-selected-index": index, "data-selected-src": element.src, style: overlayStyle(rect), "aria-hidden": "true" }, `${element.cid}-${index}`))), _jsx(DropGuideOverlay, { guide: projectedDropGuide, lineClassName: "canvas-dom-drop-line", lineTestId: "canvas-dom-drop-line", targetClassName: "canvas-dom-drop-target", targetTestId: "canvas-dom-drop-target" })] }));
532
+ if (presentationTransitioning)
533
+ return _jsx("style", { "data-test": "canvas-element-overlay-styles", children: overlayStyles });
534
+ return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "canvas-element-overlay-styles", children: overlayStyles }), showGuideOverlay && selectedRect && selectedFrameRect ? (_jsx(MeasurementGuideOverlay, { testId: "canvas-measurement-overlay", selectedRect: selectedRect, guideViewport: selectedFrameRect, segments: measurementSegments })) : null, projectedHoverRect ? (_jsxs(_Fragment, { children: [hoverSpacing ? projectedSpacingAreas.map((area, index) => (_jsx("div", { className: "canvas-spacing-fill", "data-test": "canvas-spacing-fill", "data-kind": hoverSpacing.kind, "data-property": hoverSpacing.property, "data-side": hoverSpacing.side ?? undefined, "data-area-index": index, style: overlayStyle(area), "aria-hidden": "true" }, `${hoverSpacing.property}-area-${index}`))) : null, showHoverOutline ? (_jsx("div", { className: "canvas-element-overlay", "data-test": "canvas-hover-outline", style: overlayStyle(projectedHoverRect), "aria-hidden": "true" })) : null, hoverSpacing ? projectedSpacingGuides.map((guide, index) => (_jsx("div", { className: "canvas-spacing-guide", "data-test": "canvas-spacing-guide", "data-kind": hoverSpacing.kind, "data-property": hoverSpacing.property, "data-side": hoverSpacing.side ?? undefined, "data-guide-index": index, style: spacingGuideStyle(guide), "aria-hidden": "true" }, `${hoverSpacing.property}-${index}`))) : null] })) : null, projectedSelectedGeometry.map(({ element, rect }, index) => (_jsx("div", { className: "canvas-selected-outline", "data-test": "canvas-selected-outline", "data-selected-cid": element.cid, "data-selected-index": index, "data-selected-src": element.src, style: overlayStyle(rect), "aria-hidden": "true" }, `${element.cid}-${index}`))), domNavigationEnabled && !inlineTextSession && selectedElements.length === 1 && selected && selectedRect && selectedFrame instanceof HTMLIFrameElement ? (_jsx(DomNavigator, { selected: selected, hierarchy: hierarchy, anchor: selectedRect, project: (element) => element.ownerDocument === selected.domElement.ownerDocument
535
+ ? projectRect(selectedFrame, toRect(element.getBoundingClientRect()), projectionZoom)
536
+ : null })) : null, _jsx(DropGuideOverlay, { guide: projectedDropGuide, lineClassName: "canvas-dom-drop-line", lineTestId: "canvas-dom-drop-line", targetClassName: "canvas-dom-drop-target", targetTestId: "canvas-dom-drop-target" })] }));
301
537
  }
@@ -0,0 +1,12 @@
1
+ import type { ReactElement } from "react";
2
+ import type { CanvasInteractionTool } from "./keyboardShortcuts.js";
3
+ export type { CanvasInteractionTool } from "./keyboardShortcuts.js";
4
+ export declare const CANVAS_ZOOM_LEVELS: readonly [0.25, 0.5, 0.9, 1];
5
+ export declare function closestCanvasZoomLevel(zoom: number): number;
6
+ export declare function adjacentCanvasZoomLevel(zoom: number, direction: -1 | 1): number | null;
7
+ export interface CanvasToolbarProps {
8
+ readonly tool: CanvasInteractionTool;
9
+ readonly sketchEnabled: boolean;
10
+ readonly onToolChange: (tool: CanvasInteractionTool) => void;
11
+ }
12
+ export declare function CanvasToolbar({ tool, sketchEnabled, onToolChange, }: CanvasToolbarProps): ReactElement;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { IconHandStop, IconPlayerPlay, IconPointer2, IconSketching, } from "@tabler/icons-react";
3
+ import { IconButton } from "../ui/IconButton.js";
4
+ export const CANVAS_ZOOM_LEVELS = [0.25, 0.5, 0.9, 1];
5
+ export function closestCanvasZoomLevel(zoom) {
6
+ let closest = CANVAS_ZOOM_LEVELS[0];
7
+ for (const candidate of CANVAS_ZOOM_LEVELS) {
8
+ if (Math.abs(candidate - zoom) < Math.abs(closest - zoom))
9
+ closest = candidate;
10
+ }
11
+ return closest;
12
+ }
13
+ export function adjacentCanvasZoomLevel(zoom, direction) {
14
+ const current = closestCanvasZoomLevel(zoom);
15
+ const index = CANVAS_ZOOM_LEVELS.findIndex((candidate) => candidate === current);
16
+ const next = CANVAS_ZOOM_LEVELS[index + direction];
17
+ return next === undefined ? null : next;
18
+ }
19
+ function ToolButton({ active, disabled, label, description, icon, onClick, }) {
20
+ return (_jsx(IconButton, { variant: "quiet", size: "large", className: "canvas-toolbar__tool", label: label, title: description ?? label, "aria-pressed": active, "data-test": `canvas-tool-${label.toLowerCase()}`, "data-active": active ? "true" : "false", disabled: disabled, onClick: onClick, children: icon }));
21
+ }
22
+ export function CanvasToolbar({ tool, sketchEnabled, onToolChange, }) {
23
+ return (_jsxs("div", { className: "canvas-toolbar", "data-test": "canvas-toolbar", role: "toolbar", "aria-label": "Canvas tools", children: [_jsxs("div", { className: "canvas-toolbar__tools", role: "group", "aria-label": "Interaction tools", children: [_jsx(ToolButton, { active: tool === "design", label: "Design", description: "Design (V): click elements to inspect and edit them", icon: _jsx(IconPointer2, { size: "var(--icon-size-large)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), onClick: () => onToolChange("design") }), _jsx(ToolButton, { active: tool === "pan", label: "Pan", icon: _jsx(IconHandStop, { size: "var(--icon-size-large)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), onClick: () => onToolChange("pan") }), _jsx(ToolButton, { active: tool === "sketch", label: "Sketch", disabled: !sketchEnabled, icon: _jsx(IconSketching, { size: "var(--icon-size-large)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), onClick: () => onToolChange("sketch") })] }), _jsx("div", { className: "canvas-toolbar__divider", "aria-hidden": "true" }), _jsx("div", { className: "canvas-toolbar__tools", role: "group", "aria-label": "Preview tools", children: _jsx(ToolButton, { active: tool === "select", label: "Select", description: "Select (I): use the app normally", icon: _jsx(IconPlayerPlay, { size: "var(--icon-size-large)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), onClick: () => onToolChange("select") }) })] }));
24
+ }
@@ -1,2 +1,5 @@
1
1
  import { type ReactElement } from "react";
2
- export declare function CanvasWorkspace(): ReactElement | null;
2
+ export interface CanvasWorkspaceProps {
3
+ readonly primaryUrl: string | null;
4
+ }
5
+ export declare function CanvasWorkspace({ primaryUrl }: CanvasWorkspaceProps): ReactElement | null;