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.
Files changed (201) 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 +872 -12
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +60 -11
  7. package/dist/hosts/next/sidecar.d.ts +4 -0
  8. package/dist/hosts/next/sidecar.js +49 -6
  9. package/dist/hosts/next/wrapper.js +30 -2
  10. package/dist/hosts/projectBridge.d.ts +20 -0
  11. package/dist/hosts/projectBridge.js +97 -0
  12. package/dist/hosts/static/cli.d.ts +1 -1
  13. package/dist/hosts/static/cli.js +6 -1
  14. package/dist/hosts/static/manifest.d.ts +1 -1
  15. package/dist/hosts/static/manifest.js +1 -1
  16. package/dist/hosts/static/server.js +44 -3
  17. package/dist/hosts/vite/vite.d.ts +5 -1
  18. package/dist/hosts/vite/vite.js +78 -14
  19. package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
  20. package/dist/inspector/agent/McpConnectionDialog.js +11 -35
  21. package/dist/inspector/agent/client.d.ts +10 -3
  22. package/dist/inspector/agent/client.js +80 -9
  23. package/dist/inspector/agent/connectionStatus.js +3 -3
  24. package/dist/inspector/agent/httpTransport.d.ts +2 -0
  25. package/dist/inspector/agent/httpTransport.js +37 -17
  26. package/dist/inspector/agent/protocol.d.ts +8 -3
  27. package/dist/inspector/agent/protocol.js +1 -1
  28. package/dist/inspector/agent/verification.d.ts +1 -1
  29. package/dist/inspector/agent/verification.js +69 -12
  30. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  31. package/dist/inspector/canvas/CanvasCard.js +181 -91
  32. package/dist/inspector/canvas/CanvasElementOverlay.js +246 -52
  33. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  35. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  36. package/dist/inspector/canvas/CanvasWorkspace.js +392 -73
  37. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  38. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  39. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  40. package/dist/inspector/canvas/canvasResize.js +52 -0
  41. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  42. package/dist/inspector/canvas/canvasStore.js +128 -61
  43. package/dist/inspector/canvas/frameProtocol.d.ts +69 -5
  44. package/dist/inspector/canvas/frameProtocol.js +56 -2
  45. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
  46. package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
  47. package/dist/inspector/canvas/projection.d.ts +7 -0
  48. package/dist/inspector/canvas/projection.js +118 -25
  49. package/dist/inspector/canvas/rendererBootstrap.js +29 -29
  50. package/dist/inspector/canvas/rendererElementSelector.js +341 -30
  51. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  52. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  53. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  54. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  55. package/dist/inspector/canvas/sessionStore.js +12 -4
  56. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  57. package/dist/inspector/changes/changesLog.js +31 -1
  58. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  59. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  60. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  61. package/dist/inspector/clientManifest.d.ts +5 -0
  62. package/dist/inspector/clientManifest.js +11 -0
  63. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  64. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  65. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  66. package/dist/inspector/index.d.ts +2 -0
  67. package/dist/inspector/index.js +74 -24
  68. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  69. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  70. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  71. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  72. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  73. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  74. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  75. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  76. package/dist/inspector/inline-text/textEditChange.js +165 -0
  77. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  78. package/dist/inspector/inspection/selectionProperty.js +1 -0
  79. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  80. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  81. package/dist/inspector/overlay/spacingGestures.js +353 -0
  82. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  83. package/dist/inspector/overlay/structuralGestures.js +12 -10
  84. package/dist/inspector/projection/managedStylesheet.js +4 -1
  85. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  86. package/dist/inspector/projection/renderedInstance.js +5 -1
  87. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  88. package/dist/inspector/projection/structuralProjection.js +85 -1
  89. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  90. package/dist/inspector/projection/textProjection.d.ts +2 -0
  91. package/dist/inspector/projection/textProjection.js +10 -4
  92. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  93. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  94. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  95. package/dist/inspector/prompt/generatePrompt.js +19 -2
  96. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  97. package/dist/inspector/runtime/editorShell.js +4 -0
  98. package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
  99. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  100. package/dist/inspector/selection/selectionStore.js +2 -0
  101. package/dist/inspector/settings/SettingsDialog.js +10 -0
  102. package/dist/inspector/shell/AppShell.js +5 -1
  103. package/dist/inspector/shell/ChangesLog.js +8 -4
  104. package/dist/inspector/shell/CopyPromptButton.js +95 -19
  105. package/dist/inspector/shell/EmptyState.js +1 -1
  106. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  107. package/dist/inspector/shell/InspectorShell.js +37 -38
  108. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  109. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  110. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  111. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  112. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  113. package/dist/inspector/shell/originalPreview.js +61 -0
  114. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  115. package/dist/inspector/shell/shortcuts.js +15 -0
  116. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  117. package/dist/inspector/sketch/SketchChanges.js +72 -0
  118. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  119. package/dist/inspector/sketch/SketchEditor.js +402 -0
  120. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  121. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  122. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  123. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  124. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  125. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  126. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  127. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  128. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  129. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  130. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  131. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  132. package/dist/inspector/sketch/capture.d.ts +55 -0
  133. package/dist/inspector/sketch/capture.js +350 -0
  134. package/dist/inspector/sketch/freehand.d.ts +13 -0
  135. package/dist/inspector/sketch/freehand.js +64 -0
  136. package/dist/inspector/sketch/geometry.d.ts +17 -0
  137. package/dist/inspector/sketch/geometry.js +98 -0
  138. package/dist/inspector/sketch/handoff.d.ts +21 -0
  139. package/dist/inspector/sketch/handoff.js +72 -0
  140. package/dist/inspector/sketch/interaction.d.ts +21 -0
  141. package/dist/inspector/sketch/interaction.js +98 -0
  142. package/dist/inspector/sketch/model.d.ts +104 -0
  143. package/dist/inspector/sketch/model.js +282 -0
  144. package/dist/inspector/sketch/persistence.d.ts +21 -0
  145. package/dist/inspector/sketch/persistence.js +205 -0
  146. package/dist/inspector/sketch/raster.d.ts +22 -0
  147. package/dist/inspector/sketch/raster.js +203 -0
  148. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  149. package/dist/inspector/sketch/sketchNote.js +30 -0
  150. package/dist/inspector/sketch/store.d.ts +47 -0
  151. package/dist/inspector/sketch/store.js +409 -0
  152. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  153. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  154. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  155. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  156. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  157. package/dist/inspector/styleEditors/GapField.js +2 -2
  158. package/dist/inspector/styleEditors/GridSection.js +1 -1
  159. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  160. package/dist/inspector/styleEditors/SpacingBox.js +23 -30
  161. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  162. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  163. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  164. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  165. package/dist/inspector/styleEditors/nudgeValue.js +91 -5
  166. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  167. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  168. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  169. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  170. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  171. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  172. package/dist/inspector/tokens/TokenField.js +111 -7
  173. package/dist/inspector/tokens/editActions.d.ts +2 -0
  174. package/dist/inspector/tokens/editActions.js +2 -2
  175. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  176. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  177. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  178. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  179. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  180. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  181. package/dist/inspector/tokens/resolution.d.ts +3 -26
  182. package/dist/inspector/tokens/resolution.js +44 -735
  183. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  184. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  185. package/dist/inspector/ui/Button.d.ts +1 -1
  186. package/dist/inspector/ui/SideValuesField.js +2 -2
  187. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  188. package/dist/inspector/ui/StatusCallout.js +2 -2
  189. package/dist/inspector/ui/styles.js +13 -11
  190. package/dist/internal/agent-protocol.d.ts +65 -1
  191. package/dist/internal/agent-protocol.js +183 -3
  192. package/dist/nudge-ui.mjs +261 -57
  193. package/dist/transport/editor.d.ts +28 -0
  194. package/dist/transport/editor.js +132 -0
  195. package/dist/transport/index.d.ts +1 -0
  196. package/dist/transport/index.js +1 -0
  197. package/dist/transport/routes.d.ts +7 -0
  198. package/dist/transport/routes.js +7 -0
  199. package/package.json +3 -1
  200. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  201. 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
6
  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";
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,16 @@ 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";
21
28
  function projectRect(iframe, rect, zoom) {
22
29
  const frameRect = iframe.getBoundingClientRect();
23
30
  return {
@@ -27,14 +34,6 @@ function projectRect(iframe, rect, zoom) {
27
34
  height: rect.height * zoom,
28
35
  };
29
36
  }
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
37
  function overlayStyle(rect) {
39
38
  return {
40
39
  position: "fixed",
@@ -45,6 +44,17 @@ function overlayStyle(rect) {
45
44
  pointerEvents: "none",
46
45
  };
47
46
  }
47
+ function spacingGuideStyle(rect) {
48
+ const isHorizontal = rect.width >= rect.height;
49
+ const width = isHorizontal ? Math.min(rect.width, SPACING_GUIDE_HANDLE_LENGTH) : rect.width;
50
+ const height = isHorizontal ? rect.height : Math.min(rect.height, SPACING_GUIDE_HANDLE_LENGTH);
51
+ return overlayStyle({
52
+ left: rect.left + (rect.width - width) / 2,
53
+ top: rect.top + (rect.height - height) / 2,
54
+ width,
55
+ height,
56
+ });
57
+ }
48
58
  function findFrameElement(iframe, elementId, cid, src) {
49
59
  const doc = iframe.contentDocument;
50
60
  if (!doc)
@@ -55,7 +65,9 @@ function findFrameElement(iframe, elementId, cid, src) {
55
65
  if (candidates.length !== 1)
56
66
  return null;
57
67
  const candidate = candidates[0] ?? null;
58
- if (!candidate || candidate.getAttribute("data-cid") !== cid || candidate.getAttribute("data-src") !== src)
68
+ if (!candidate
69
+ || candidate.getAttribute("data-cid") !== cid
70
+ || (candidate.getAttribute("data-src") ?? "") !== src)
59
71
  return null;
60
72
  const frameWindow = doc.defaultView;
61
73
  return frameWindow && candidate instanceof frameWindow.HTMLElement ? candidate : null;
@@ -79,17 +91,77 @@ function projectGuideToCanvas(guide, zoom) {
79
91
  target: project(guide.target, true, true),
80
92
  };
81
93
  }
94
+ function spacingDescriptorMatches(affordance, descriptor) {
95
+ return Boolean(affordance
96
+ && descriptor
97
+ && affordance.kind === descriptor.kind
98
+ && affordance.property === descriptor.property
99
+ && affordance.side === descriptor.side);
100
+ }
101
+ function clearSpacingDrag(ref) {
102
+ const drag = ref.current;
103
+ ref.current = null;
104
+ if (drag)
105
+ restoreSpacingPreview(drag);
106
+ }
107
+ function applySpacingPreview(drag, point) {
108
+ const value = spacingValueForDrag(drag.affordance, drag.start, point);
109
+ drag.currentValue = value;
110
+ beginLayoutPreview(drag.element, drag.affordance.property);
111
+ drag.element.style.setProperty(drag.affordance.property, spacingValueCss(value));
112
+ }
113
+ function restoreSpacingPreview(drag) {
114
+ if (drag.originalInlineValue) {
115
+ drag.element.style.setProperty(drag.affordance.property, drag.originalInlineValue, drag.originalInlinePriority);
116
+ }
117
+ else {
118
+ drag.element.style.removeProperty(drag.affordance.property);
119
+ }
120
+ endLayoutPreview(drag.element, drag.affordance.property);
121
+ }
122
+ function commitSpacingDrag(drag) {
123
+ // Restore the authored declaration before committing the managed rule. The
124
+ // captured start value is the baseline because the pointer remains hovered
125
+ // and CSS inspection can still report the transient preview value.
126
+ restoreSpacingPreview(drag);
127
+ if (drag.currentValue === drag.startValue)
128
+ return;
129
+ setStyles(drag.element, [{
130
+ property: drag.affordance.property,
131
+ value: spacingValueCss(drag.currentValue),
132
+ oldRawValue: spacingValueCss(drag.startValue),
133
+ }]);
134
+ }
135
+ function sendMeasureModifier(iframe, cardId, altKey) {
136
+ iframe.contentWindow?.postMessage({
137
+ type: "measure-modifier",
138
+ protocolVersion: PROTOCOL_VERSION,
139
+ projectId: PROJECT_ID,
140
+ workspaceId: WORKSPACE_ID,
141
+ cardId,
142
+ altKey,
143
+ }, window.location.origin);
144
+ }
82
145
  export function CanvasElementOverlay() {
83
146
  const [hover, setHover] = useState(null);
84
147
  const [measureState, setMeasureState] = useState(null);
85
148
  const [, refreshSelectedGeometry] = useReducer((revision) => revision + 1, 0);
86
149
  const selected = useSelectedElement();
87
150
  const selectedElements = useSelectedElements();
151
+ const inlineTextSession = useInlineTextSession();
88
152
  const camera = useBoardCamera();
89
- useCanvasCards(); // Projected geometry must follow card drag and resize updates.
153
+ const presentation = useCanvasPresentation();
154
+ const presentationTransitioning = useCanvasPresentationTransitioning();
155
+ const projectionZoom = presentation === "focus" ? 1 : camera.zoom;
156
+ const cards = useCanvasCards(); // Projected geometry must follow card drag and resize updates.
157
+ const cardGeometryKey = cards.map((card) => `${card.id}:${card.x}:${card.y}:${card.width}:${card.height}`).join("|");
158
+ const projectionGeometryKey = `${presentation}:${camera.x}:${camera.y}:${projectionZoom}:${cardGeometryKey}`;
159
+ const previousProjectionGeometryKey = useRef(projectionGeometryKey);
90
160
  const dragRef = useRef(null);
161
+ const spacingDragRef = useRef(null);
162
+ const measureAltKeyRef = useRef(false);
91
163
  const dropGuide = useDropGuide("canvas");
92
- const projectedDropGuide = projectGuideToCanvas(dropGuide, camera.zoom);
164
+ const projectedDropGuide = projectGuideToCanvas(dropGuide, projectionZoom);
93
165
  const selectedFrame = selected?.domElement.ownerDocument.defaultView?.frameElement;
94
166
  const selectedInCanvas = selectedFrame instanceof HTMLIFrameElement
95
167
  && selectedFrame.hasAttribute("data-nudge-ui-canvas-renderer");
@@ -105,16 +177,25 @@ export function CanvasElementOverlay() {
105
177
  ? toRect(selectedFrame.getBoundingClientRect())
106
178
  : null;
107
179
  const selectedRect = selectedInCanvas && selectedLocalRect
108
- ? projectRect(selectedFrame, selectedLocalRect, camera.zoom)
180
+ ? projectRect(selectedFrame, selectedLocalRect, projectionZoom)
109
181
  : null;
110
182
  const projectedSelectedGeometry = selectedInCanvas
111
183
  ? selectedRects.flatMap((rect, index) => {
112
184
  const element = selectedElements[index];
113
185
  return rect && element
114
- ? [{ element, rect: projectRect(selectedFrame, rect, camera.zoom) }]
186
+ ? [{ element, rect: projectRect(selectedFrame, rect, projectionZoom) }]
115
187
  : [];
116
188
  })
117
189
  : [];
190
+ // Camera transforms commit on the board before iframe viewport geometry is
191
+ // readable. Re-render once after that commit so overlays use the transformed
192
+ // frame rectangle rather than the previous camera position.
193
+ useLayoutEffect(() => {
194
+ const geometryChanged = previousProjectionGeometryKey.current !== projectionGeometryKey;
195
+ previousProjectionGeometryKey.current = projectionGeometryKey;
196
+ if (selectedInCanvas && geometryChanged)
197
+ refreshSelectedGeometry();
198
+ }, [projectionGeometryKey, selectedInCanvas]);
118
199
  const selectedBorders = selectedInCanvas && selected
119
200
  ? readBorderWidths(selected.domElement)
120
201
  : null;
@@ -124,6 +205,32 @@ export function CanvasElementOverlay() {
124
205
  const stops = selectedElements.map((candidate) => observeSelectedGeometry(candidate.domElement, refreshSelectedGeometry));
125
206
  return () => stops.forEach((stop) => stop());
126
207
  }, [selected, selectedElements, selectedInCanvas]);
208
+ useEffect(() => {
209
+ const broadcastMeasureModifier = (altKey) => {
210
+ measureAltKeyRef.current = altKey;
211
+ for (const [cardId, iframe] of getRegisteredFrames()) {
212
+ sendMeasureModifier(iframe, cardId, altKey);
213
+ }
214
+ };
215
+ const onKeyDown = (event) => {
216
+ if (event.key !== "Alt" || measureAltKeyRef.current)
217
+ return;
218
+ broadcastMeasureModifier(true);
219
+ };
220
+ const onKeyUp = (event) => {
221
+ if (event.key === "Alt")
222
+ broadcastMeasureModifier(false);
223
+ };
224
+ const onBlur = () => broadcastMeasureModifier(false);
225
+ window.addEventListener("keydown", onKeyDown, true);
226
+ window.addEventListener("keyup", onKeyUp, true);
227
+ window.addEventListener("blur", onBlur);
228
+ return () => {
229
+ window.removeEventListener("keydown", onKeyDown, true);
230
+ window.removeEventListener("keyup", onKeyUp, true);
231
+ window.removeEventListener("blur", onBlur);
232
+ };
233
+ }, []);
127
234
  // The selected element's identity is computed once per selection change
128
235
  // (never per hover), so the measurement self-rulers exclusion can compare
129
236
  // identities without a per-hover scan of the frame document.
@@ -135,24 +242,12 @@ export function CanvasElementOverlay() {
135
242
  return elementId ? { elementId } : null;
136
243
  }, [selected?.domElement]);
137
244
  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,
151
- };
152
- if (!isRendererMessageFor(event.data, frameIdentity))
245
+ const unsubscribe = subscribeCanvasRendererMessages(({ cardId: sourceCardId, iframe: sourceIframe, identity: frameIdentity, message: data, }) => {
246
+ if (data.type === "renderer-hello" || data.type === "frame-ready") {
247
+ sendMeasureModifier(sourceIframe, sourceCardId, measureAltKeyRef.current);
248
+ }
249
+ if (data.type === "renderer-hello")
153
250
  return;
154
- // SAFETY: isRendererMessageFor validated the frame identity and message shape above.
155
- const data = event.data;
156
251
  if (data.type === "element-hover") {
157
252
  const msg = data;
158
253
  if (!msg.cid)
@@ -161,13 +256,30 @@ export function CanvasElementOverlay() {
161
256
  setHover((current) => current?.cardId === sourceCardId ? null : current);
162
257
  return;
163
258
  }
259
+ const spacingDescriptor = msg.spacing ?? null;
260
+ const hoverPoint = msg.point ?? null;
261
+ const hoverElement = spacingDescriptor
262
+ ? findFrameElement(sourceIframe, msg.elementId, msg.cid, msg.src)
263
+ : null;
264
+ const hoverAffordance = spacingDescriptor && hoverElement
265
+ ? getSpacingAffordanceForDescriptor(hoverElement, spacingDescriptor)
266
+ : spacingDescriptor && hoverPoint && sourceIframe.contentDocument
267
+ ? getSpacingAffordanceAtPoint(sourceIframe.contentDocument, hoverPoint.x, hoverPoint.y)
268
+ : null;
269
+ const matchingAffordance = spacingDescriptorMatches(hoverAffordance, spacingDescriptor)
270
+ ? hoverAffordance
271
+ : null;
272
+ const spacingGuides = matchingAffordance?.affectedGuides ?? [];
273
+ const spacingAreas = matchingAffordance?.affectedAreas ?? [];
164
274
  setHover({
165
275
  iframe: sourceIframe,
166
276
  identity: { elementId: msg.elementId },
167
277
  rect: msg.rect,
168
- margins: msg.margins ?? { top: 0, right: 0, bottom: 0, left: 0 },
169
278
  borders: msg.borders ?? { top: 0, right: 0, bottom: 0, left: 0 },
170
279
  cardId: sourceCardId,
280
+ spacing: spacingGuides.length > 0 ? spacingDescriptor : null,
281
+ spacingGuides,
282
+ spacingAreas,
171
283
  });
172
284
  }
173
285
  else if (data.type === "element-measure-state") {
@@ -179,14 +291,38 @@ export function CanvasElementOverlay() {
179
291
  });
180
292
  }
181
293
  else if (data.type === "element-click") {
182
- if (!isElementClickMessage(event.data, frameIdentity))
294
+ if (!isElementClickMessage(data, frameIdentity))
183
295
  return;
184
296
  const msg = data;
185
297
  if (!msg.cid)
186
298
  return;
187
299
  handleElementClick(msg, sourceIframe, sourceCardId);
188
300
  }
301
+ else if (data.type === "inline-text-intent") {
302
+ if (!isInlineTextIntentMessage(data, frameIdentity))
303
+ return;
304
+ if (getSelectedElements().length > 1)
305
+ return;
306
+ const emptyProjection = data.emptyProjectionId
307
+ ? Array.from(sourceIframe.contentDocument?.querySelectorAll(`[${EMPTY_TEXT_PROJECTION_ATTR}]`) ?? [])
308
+ .find((candidate) => candidate.getAttribute(EMPTY_TEXT_PROJECTION_ATTR) === data.emptyProjectionId)
309
+ : null;
310
+ const element = emptyProjection ?? findFrameElement(sourceIframe, data.elementId, data.cid, data.src);
311
+ if (!element)
312
+ return;
313
+ handleInlineTextEditIntent(data.intent === "double-click"
314
+ ? { kind: "double-click", target: element, point: data.point }
315
+ : {
316
+ kind: "pointer-down",
317
+ target: element,
318
+ point: data.point,
319
+ clickCount: data.clickCount ?? 0,
320
+ });
321
+ }
189
322
  else if (data.type === "element-deselect") {
323
+ clearSpacingDrag(spacingDragRef);
324
+ dragRef.current = null;
325
+ clearDropGuide("canvas");
190
326
  setSelectedElement(null);
191
327
  }
192
328
  else if (data.type === "element-drag-start") {
@@ -196,6 +332,34 @@ export function CanvasElementOverlay() {
196
332
  const element = findFrameElement(sourceIframe, msg.elementId, msg.cid, msg.src);
197
333
  if (!element)
198
334
  return;
335
+ clearSpacingDrag(spacingDragRef);
336
+ dragRef.current = null;
337
+ const startPoint = msg.startPoint ?? msg.point;
338
+ const spacing = msg.spacing
339
+ ? getSpacingAffordanceForDescriptor(element, msg.spacing)
340
+ : sourceIframe.contentDocument
341
+ ? getSpacingAffordanceAtPoint(sourceIframe.contentDocument, startPoint.x, startPoint.y)
342
+ : null;
343
+ const isSpacingDrag = spacing
344
+ && spacing.element === element
345
+ && (!msg.spacing || spacingDescriptorMatches(spacing, msg.spacing));
346
+ if (isSpacingDrag) {
347
+ spacingDragRef.current = {
348
+ iframe: sourceIframe,
349
+ element,
350
+ affordance: spacing,
351
+ start: startPoint,
352
+ startValue: spacing.value,
353
+ currentValue: spacing.value,
354
+ originalInlineValue: element.style.getPropertyValue(spacing.property),
355
+ originalInlinePriority: element.style.getPropertyPriority(spacing.property),
356
+ };
357
+ const selectedElement = resolveSelectionFromElement(element);
358
+ if (selectedElement)
359
+ setSelectedElement(selectedElement);
360
+ applySpacingPreview(spacingDragRef.current, msg.point);
361
+ return;
362
+ }
199
363
  dragRef.current = { iframe: sourceIframe, element };
200
364
  const selectedElement = resolveSelectionFromElement(element);
201
365
  if (selectedElement)
@@ -203,12 +367,35 @@ export function CanvasElementOverlay() {
203
367
  updateDropGuide(msg.point, sourceIframe);
204
368
  }
205
369
  else if (data.type === "element-drag-move") {
370
+ const spacing = spacingDragRef.current;
371
+ if (spacing && spacing.iframe === sourceIframe) {
372
+ applySpacingPreview(spacing, data.point);
373
+ return;
374
+ }
206
375
  updateDropGuide(data.point, sourceIframe);
207
376
  }
208
377
  else if (data.type === "element-drag-end") {
209
378
  const msg = data;
379
+ const spacing = spacingDragRef.current;
380
+ if (spacing && spacing.iframe === sourceIframe) {
381
+ spacingDragRef.current = null;
382
+ if (msg.cancelled) {
383
+ restoreSpacingPreview(spacing);
384
+ }
385
+ else {
386
+ applySpacingPreview(spacing, msg.point);
387
+ commitSpacingDrag(spacing);
388
+ }
389
+ clearDropGuide("canvas");
390
+ if (!msg.cancelled) {
391
+ const selectedElement = resolveSelectionFromElement(spacing.element);
392
+ if (selectedElement)
393
+ setSelectedElement(selectedElement);
394
+ }
395
+ return;
396
+ }
210
397
  const current = dragRef.current;
211
- if (current && current.iframe === sourceIframe && sourceIframe.contentDocument) {
398
+ if (!msg.cancelled && current && current.iframe === sourceIframe && sourceIframe.contentDocument) {
212
399
  const drop = getDropLocationAtPoint(sourceIframe.contentDocument, current.element, msg.point.x, msg.point.y);
213
400
  if (drop)
214
401
  moveElement(current.element, drop);
@@ -249,6 +436,12 @@ export function CanvasElementOverlay() {
249
436
  undo();
250
437
  }
251
438
  }
439
+ else if (data.type === "inspector-toggle-request") {
440
+ toggleInspector();
441
+ }
442
+ else if (data.type === "inspector-open-request") {
443
+ setInspectorOpen(true);
444
+ }
252
445
  function updateDropGuide(point, iframe) {
253
446
  const current = dragRef.current;
254
447
  if (!current || current.iframe !== iframe || !iframe.contentDocument)
@@ -260,23 +453,22 @@ export function CanvasElementOverlay() {
260
453
  }
261
454
  showDropGuide("canvas", iframe.contentDocument, drop);
262
455
  }
263
- }
264
- window.addEventListener("message", onMessage);
456
+ });
265
457
  return () => {
266
- window.removeEventListener("message", onMessage);
458
+ unsubscribe();
459
+ clearSpacingDrag(spacingDragRef);
460
+ dragRef.current = null;
267
461
  clearDropGuide("canvas");
268
462
  };
269
463
  }, []);
464
+ if (inlineTextSession && inlineTextSession.host.ownerDocument === selected?.domElement.ownerDocument)
465
+ return null;
270
466
  if (!hover && projectedSelectedGeometry.length === 0 && !projectedDropGuide)
271
467
  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
- : [];
468
+ const projectedHoverRect = hover ? projectRect(hover.iframe, hover.rect, projectionZoom) : null;
469
+ const projectedSpacingGuides = hover?.spacingGuides.map((guide) => projectRect(hover.iframe, guide, projectionZoom)) ?? [];
470
+ const projectedSpacingAreas = hover?.spacingAreas.map((area) => projectRect(hover.iframe, area, projectionZoom)) ?? [];
471
+ const hoverSpacing = hover?.spacing ?? null;
280
472
  const measureStateForSelectedFrame = measureState?.iframe === selectedFrame ? measureState : null;
281
473
  const hoverInSelectedFrame = hover?.iframe === selectedFrame ? hover : null;
282
474
  const showGuideOverlay = Boolean(selectedElements.length === 1
@@ -295,7 +487,9 @@ export function CanvasElementOverlay() {
295
487
  ? projectMeasurementSegments(selectedFrameRect, getMeasurementGeometry(selectedLocalRect, hoverInSelectedFrame.rect, {
296
488
  selectedBorders: selectedBorders ?? undefined,
297
489
  hoveredBorders: hoverInSelectedFrame.borders,
298
- }).segments, camera.zoom)
490
+ }).segments, projectionZoom)
299
491
  : [];
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" })] }));
492
+ if (presentationTransitioning)
493
+ return _jsx("style", { "data-test": "canvas-element-overlay-styles", children: overlayStyles });
494
+ 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, _jsx("div", { className: "canvas-element-overlay", "data-test": "canvas-hover-outline", style: overlayStyle(projectedHoverRect), "aria-hidden": "true" }), 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}`))), _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
495
  }
@@ -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, 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, icon, onClick, }) {
20
+ return (_jsx(IconButton, { variant: "quiet", size: "large", className: "canvas-toolbar__tool", label: label, title: 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 (_jsx("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 === "move", label: "Move", icon: _jsx(IconPointer2, { size: "var(--icon-size-large)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), onClick: () => onToolChange("move") }), _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") })] }) }));
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;