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,16 +1,18 @@
1
- import { PROTOCOL_VERSION, getRendererIdentity, sendToParent, } from "./frameProtocol.js";
2
- import { findClosestAnchor, isEligibleNavigation, hasDifferentRoute, shouldPreserveNativeLinkActivation, } from "./linkEligibility.js";
1
+ import { PROTOCOL_VERSION, getRendererIdentity, sendToParent, isKeyboardShortcutCode, } from "./frameProtocol.js";
3
2
  import { readBorderWidths, readMargins } from "../overlay/overlayGeometry.js";
4
3
  import { installInteractionStyles } from "../overlay/interactionStyles.js";
5
4
  import { createFrameThrottle } from "../overlay/frameThrottle.js";
6
5
  import { createCidIndex } from "./rendererCidIndex.js";
7
6
  import { isNudgeUiDev } from "../runtime/devFlag.js";
8
- import { isEditableEvent } from "../shell/shortcuts.js";
7
+ import { isEditableEvent, isInspectorToggleShortcut, isSendPromptShortcut } from "../shell/shortcuts.js";
9
8
  import { resolveSelectionTarget, selectionTargetMode } from "../selection/selectionTarget.js";
10
9
  import { escapeCssString } from "../projection/cssEscapes.js";
11
- import { blockApplicationClick, isApplicationActivationClick } from "../overlay/clickPolicy.js";
10
+ import { APPLICATION_GESTURE_EVENTS, blockApplicationClick, blockApplicationGesture, isInlineEditorTarget, isUserClick, } from "../overlay/clickPolicy.js";
11
+ import { EMPTY_TEXT_PROJECTION_ATTR } from "../projection/textProjection.js";
12
+ import { getSpacingAffordanceAtPoint, toSpacingDescriptor, } from "../overlay/spacingGestures.js";
12
13
  const REACT_FIBER_KEY = /^__reactFiber\$/;
13
14
  const REACT_INTERNAL_KEY = /^__reactInternalInstance\$/;
15
+ const HOVER_SCROLL_SETTLE_DELAY = 150;
14
16
  function findFiber(el) {
15
17
  const keys = Object.keys(el);
16
18
  for (const key of keys) {
@@ -75,24 +77,73 @@ export function buildSelector(el) {
75
77
  }
76
78
  let installed = false;
77
79
  const noopDisposal = () => undefined;
78
- export function installRendererElementSelector() {
80
+ export function installRendererElementSelector(acceptsClick = isUserClick) {
79
81
  if (!isNudgeUiDev())
80
82
  return noopDisposal;
81
83
  if (installed)
82
84
  return noopDisposal;
83
85
  installed = true;
84
- const removeInteractionStyles = installInteractionStyles();
86
+ let removeInteractionStyles = installInteractionStyles();
85
87
  const listenerCleanup = [];
86
88
  let disposed = false;
89
+ let interactionsSuspended = false;
90
+ let scrolling = false;
91
+ let scrollSettleTimer = null;
92
+ let activeHoverElement = null;
93
+ let suppressHoverDuringSpacingDrag = false;
94
+ let lastHoverPoint = null;
87
95
  function trackListener(target, type, listener, options) {
88
96
  const eventListener = (event) => listener(event);
89
97
  target.addEventListener(type, eventListener, options);
90
98
  listenerCleanup.push(() => target.removeEventListener(type, eventListener, options));
91
99
  }
92
100
  const cidIndex = createCidIndex(document);
93
- const hoverUpdate = createFrameThrottle((pending) => {
94
- if (disposed)
95
- return;
101
+ const initialCursor = document.documentElement.style.getPropertyValue("cursor");
102
+ const initialCursorPriority = document.documentElement.style.getPropertyPriority("cursor");
103
+ let appliedRootCursor = null;
104
+ let spacingCursor = null;
105
+ function clearSpacingCursor() {
106
+ const active = spacingCursor;
107
+ spacingCursor = null;
108
+ if (active
109
+ && active.element.style.getPropertyValue("cursor") === active.appliedValue
110
+ && active.element.style.getPropertyPriority("cursor") === "important") {
111
+ if (active.previousValue) {
112
+ active.element.style.setProperty("cursor", active.previousValue, active.previousPriority);
113
+ }
114
+ else {
115
+ active.element.style.removeProperty("cursor");
116
+ }
117
+ }
118
+ if (appliedRootCursor !== null
119
+ && document.documentElement.style.getPropertyValue("cursor") === appliedRootCursor) {
120
+ if (initialCursor) {
121
+ document.documentElement.style.setProperty("cursor", initialCursor, initialCursorPriority);
122
+ }
123
+ else {
124
+ document.documentElement.style.removeProperty("cursor");
125
+ }
126
+ }
127
+ appliedRootCursor = null;
128
+ }
129
+ function setSpacingCursor(element, cursor) {
130
+ if (spacingCursor?.element !== element) {
131
+ clearSpacingCursor();
132
+ spacingCursor = {
133
+ element,
134
+ previousValue: element.style.getPropertyValue("cursor"),
135
+ previousPriority: element.style.getPropertyPriority("cursor"),
136
+ appliedValue: cursor,
137
+ };
138
+ }
139
+ else {
140
+ spacingCursor.appliedValue = cursor;
141
+ }
142
+ element.style.setProperty("cursor", cursor, "important");
143
+ document.documentElement.style.setProperty("cursor", cursor);
144
+ appliedRootCursor = cursor;
145
+ }
146
+ function reportHover(pending) {
96
147
  const { element, clear } = pending;
97
148
  const identity = getRendererIdentity();
98
149
  if (!identity)
@@ -101,6 +152,17 @@ export function installRendererElementSelector() {
101
152
  const selector = buildSelector(element);
102
153
  const src = element.getAttribute("data-src") ?? "";
103
154
  const rect = clear ? null : element.getBoundingClientRect();
155
+ const rawSpacing = !clear && pending.point
156
+ ? getSpacingAffordanceAtPoint(document, pending.point.x, pending.point.y)
157
+ : null;
158
+ const spacing = rawSpacing && rawSpacing.element === element ? rawSpacing : null;
159
+ if (spacing) {
160
+ setSpacingCursor(spacing.element, spacing.cursor);
161
+ }
162
+ else {
163
+ clearSpacingCursor();
164
+ }
165
+ activeHoverElement = clear ? null : element;
104
166
  const msg = {
105
167
  type: "element-hover",
106
168
  protocolVersion: PROTOCOL_VERSION,
@@ -118,10 +180,49 @@ export function installRendererElementSelector() {
118
180
  : null,
119
181
  margins: rect ? readMargins(element) : null,
120
182
  borders: rect ? readBorderWidths(element) : null,
183
+ point: pending.point,
184
+ spacing: spacing ? toSpacingDescriptor(spacing) : null,
121
185
  ...identity,
122
186
  };
123
187
  sendToParent(msg);
188
+ }
189
+ const hoverUpdate = createFrameThrottle((pending) => {
190
+ if (disposed || scrolling || suppressHoverDuringSpacingDrag)
191
+ return;
192
+ reportHover(pending);
124
193
  });
194
+ function clearActiveHover() {
195
+ hoverUpdate.cancel();
196
+ clearSpacingCursor();
197
+ const element = activeHoverElement;
198
+ activeHoverElement = null;
199
+ if (element)
200
+ reportHover({ element, clear: true, point: null });
201
+ }
202
+ function resumeHoverAfterScroll() {
203
+ scrollSettleTimer = null;
204
+ scrolling = false;
205
+ if (disposed || interactionsSuspended || suppressHoverDuringSpacingDrag || !lastHoverPoint)
206
+ return;
207
+ const target = document.elementFromPoint?.(lastHoverPoint.x, lastHoverPoint.y);
208
+ const element = resolveSelectionTarget(target, lastHoverPoint.mode);
209
+ if (!element)
210
+ return;
211
+ hoverUpdate.schedule({
212
+ element,
213
+ clear: false,
214
+ point: { x: lastHoverPoint.x, y: lastHoverPoint.y },
215
+ });
216
+ }
217
+ function suppressHoverDuringScroll() {
218
+ if (disposed || interactionsSuspended)
219
+ return;
220
+ scrolling = true;
221
+ clearActiveHover();
222
+ if (scrollSettleTimer !== null)
223
+ window.clearTimeout(scrollSettleTimer);
224
+ scrollSettleTimer = window.setTimeout(resumeHoverAfterScroll, HOVER_SCROLL_SETTLE_DELAY);
225
+ }
125
226
  let measurePointerOverPage = false;
126
227
  let measureAltKey = false;
127
228
  function updateMeasureState(altKey, pointerOverPage) {
@@ -144,39 +245,189 @@ export function installRendererElementSelector() {
144
245
  sendToParent(msg);
145
246
  }
146
247
  trackListener(document, "mouseover", (event) => {
248
+ if (interactionsSuspended)
249
+ return;
147
250
  updateMeasureState(event.altKey, true);
251
+ if (suppressHoverDuringSpacingDrag)
252
+ return;
148
253
  const target = event.target;
149
254
  if (!(target instanceof Element))
150
255
  return;
151
256
  const el = resolveSelectionTarget(target, selectionTargetMode(event));
152
257
  if (!el)
153
258
  return;
154
- hoverUpdate.schedule({ element: el, clear: false });
259
+ lastHoverPoint = { x: event.clientX, y: event.clientY, mode: selectionTargetMode(event) };
260
+ if (scrolling)
261
+ return;
262
+ hoverUpdate.schedule({ element: el, clear: false, point: { x: event.clientX, y: event.clientY } });
155
263
  }, true);
264
+ trackListener(window, "scroll", suppressHoverDuringScroll, true);
156
265
  let pendingDrag = null;
157
266
  let dragging = false;
267
+ let lastDragPoint = null;
158
268
  let lastSelected = null;
159
- const dragMoveUpdate = createFrameThrottle((point) => {
269
+ trackListener(window, "nudge-ui:open", () => {
270
+ const identity = getRendererIdentity();
271
+ if (!identity)
272
+ return;
273
+ const message = {
274
+ type: "inspector-open-request",
275
+ protocolVersion: PROTOCOL_VERSION,
276
+ ...identity,
277
+ };
278
+ sendToParent(message);
279
+ });
280
+ trackListener(window, "message", (event) => {
281
+ if (event.origin !== window.location.origin || event.source !== window.parent)
282
+ return;
283
+ const identity = getRendererIdentity();
284
+ const message = event.data;
285
+ if (!identity || !message
286
+ || message.protocolVersion !== PROTOCOL_VERSION
287
+ || message.projectId !== identity.projectId
288
+ || message.workspaceId !== identity.workspaceId
289
+ || message.cardId !== identity.cardId)
290
+ return;
291
+ if (message.type === "measure-modifier" && typeof message.altKey === "boolean") {
292
+ updateMeasureState(message.altKey, measurePointerOverPage);
293
+ return;
294
+ }
295
+ if (message.type !== "inspector-interaction-state" || typeof message.open !== "boolean")
296
+ return;
297
+ const interactionsEnabled = message.interactionsEnabled === undefined
298
+ ? message.open
299
+ : message.interactionsEnabled === true;
300
+ interactionsSuspended = !interactionsEnabled;
301
+ if (message.open) {
302
+ document.documentElement.setAttribute("data-nudge-ui-panel", "open");
303
+ }
304
+ else {
305
+ document.documentElement.removeAttribute("data-nudge-ui-panel");
306
+ }
307
+ if (interactionsSuspended) {
308
+ cancelActiveDrag();
309
+ clearActiveHover();
310
+ if (scrollSettleTimer !== null)
311
+ window.clearTimeout(scrollSettleTimer);
312
+ scrollSettleTimer = null;
313
+ scrolling = false;
314
+ updateMeasureState(false, false);
315
+ removeInteractionStyles?.();
316
+ removeInteractionStyles = null;
317
+ }
318
+ else {
319
+ removeInteractionStyles ??= installInteractionStyles();
320
+ }
321
+ });
322
+ const dragMoveUpdate = createFrameThrottle((drag) => {
160
323
  if (disposed)
161
324
  return;
162
325
  const identity = getRendererIdentity();
163
- if (!point || !identity)
326
+ if (!drag || !identity)
164
327
  return;
165
- const msg = { type: "element-drag-move", protocolVersion: PROTOCOL_VERSION, point, ...identity };
328
+ const msg = {
329
+ type: "element-drag-move",
330
+ protocolVersion: PROTOCOL_VERSION,
331
+ point: drag.point,
332
+ shiftKey: drag.shiftKey,
333
+ ...identity,
334
+ };
166
335
  sendToParent(msg);
167
336
  });
337
+ function sendDragEnd(point, cancelled, shiftKey = false) {
338
+ if (!dragging || !point)
339
+ return;
340
+ const identity = getRendererIdentity();
341
+ if (!identity)
342
+ return;
343
+ const msg = {
344
+ type: "element-drag-end",
345
+ protocolVersion: PROTOCOL_VERSION,
346
+ point,
347
+ shiftKey,
348
+ ...(cancelled ? { cancelled: true } : {}),
349
+ ...identity,
350
+ };
351
+ sendToParent(msg);
352
+ }
353
+ function cancelActiveDrag() {
354
+ dragMoveUpdate.cancel();
355
+ sendDragEnd(lastDragPoint, true);
356
+ pendingDrag = null;
357
+ dragging = false;
358
+ suppressHoverDuringSpacingDrag = false;
359
+ }
168
360
  trackListener(document, "mousedown", (event) => {
361
+ if (interactionsSuspended)
362
+ return;
169
363
  if (event.button !== 0)
170
364
  return;
171
365
  const element = resolveSelectionTarget(event.target, selectionTargetMode(event));
172
366
  if (!element)
173
367
  return;
368
+ const spacingAffordance = getSpacingAffordanceAtPoint(document, event.clientX, event.clientY);
369
+ const spacing = spacingAffordance?.element === element
370
+ ? toSpacingDescriptor(spacingAffordance)
371
+ : null;
372
+ if (spacing) {
373
+ suppressHoverDuringSpacingDrag = true;
374
+ hoverUpdate.cancel();
375
+ lastHoverPoint = null;
376
+ }
377
+ const identity = getRendererIdentity();
378
+ // A spacing drag owns the pointer gesture. Do not start an inline-text
379
+ // attempt for the same down event; padding and gaps are intentionally not
380
+ // editable text targets.
381
+ if (identity && !spacing) {
382
+ const emptyProjectionId = event.target instanceof Element
383
+ ? event.target.closest(`[${EMPTY_TEXT_PROJECTION_ATTR}]`)?.getAttribute(EMPTY_TEXT_PROJECTION_ATTR) ?? undefined
384
+ : undefined;
385
+ const message = {
386
+ type: "inline-text-intent",
387
+ protocolVersion: PROTOCOL_VERSION,
388
+ intent: emptyProjectionId && event.detail >= 2 ? "double-click" : "pointer-down",
389
+ cid: element.getAttribute("data-cid"),
390
+ src: element.getAttribute("data-src") ?? "",
391
+ elementId: cidIndex.elementId(element),
392
+ point: { x: event.clientX, y: event.clientY },
393
+ clickCount: event.detail,
394
+ emptyProjectionId,
395
+ ...identity,
396
+ };
397
+ sendToParent(message);
398
+ }
174
399
  lastSelected = element;
175
- pendingDrag = { element, point: { x: event.clientX, y: event.clientY } };
400
+ pendingDrag = {
401
+ element,
402
+ point: { x: event.clientX, y: event.clientY },
403
+ spacing,
404
+ };
405
+ lastDragPoint = { x: event.clientX, y: event.clientY };
176
406
  }, true);
407
+ for (const type of APPLICATION_GESTURE_EVENTS) {
408
+ trackListener(document, type, (event) => {
409
+ if (interactionsSuspended || !acceptsClick(event) || isInlineEditorTarget(event.target))
410
+ return;
411
+ blockApplicationGesture(event);
412
+ }, true);
413
+ }
177
414
  trackListener(document, "mousemove", (event) => {
178
- if (!pendingDrag)
415
+ if (!pendingDrag) {
416
+ if (interactionsSuspended)
417
+ return;
418
+ const target = event.target;
419
+ if (target instanceof Element) {
420
+ const mode = selectionTargetMode(event);
421
+ const element = resolveSelectionTarget(target, mode);
422
+ if (element) {
423
+ lastHoverPoint = { x: event.clientX, y: event.clientY, mode };
424
+ if (scrolling)
425
+ return;
426
+ hoverUpdate.schedule({ element, clear: false, point: { x: event.clientX, y: event.clientY } });
427
+ }
428
+ }
179
429
  return;
430
+ }
180
431
  if (!dragging && Math.hypot(event.clientX - pendingDrag.point.x, event.clientY - pendingDrag.point.y) < 6)
181
432
  return;
182
433
  const identity = getRendererIdentity();
@@ -184,41 +435,93 @@ export function installRendererElementSelector() {
184
435
  return;
185
436
  event.preventDefault();
186
437
  const point = { x: event.clientX, y: event.clientY };
438
+ lastDragPoint = point;
187
439
  if (!dragging) {
188
440
  dragging = true;
189
441
  const msg = {
190
442
  type: "element-drag-start", protocolVersion: PROTOCOL_VERSION,
191
443
  cid: pendingDrag.element.getAttribute("data-cid"), src: pendingDrag.element.getAttribute("data-src") ?? "",
192
- elementId: cidIndex.elementId(pendingDrag.element), point, ...identity,
444
+ elementId: cidIndex.elementId(pendingDrag.element), point,
445
+ startPoint: pendingDrag.point,
446
+ spacing: pendingDrag.spacing,
447
+ shiftKey: event.shiftKey,
448
+ ...identity,
193
449
  };
194
450
  sendToParent(msg);
195
451
  }
196
452
  else {
197
- dragMoveUpdate.schedule(point);
453
+ dragMoveUpdate.schedule({ point, shiftKey: event.shiftKey });
198
454
  }
199
455
  }, true);
200
456
  function finishDrag(event) {
201
457
  dragMoveUpdate.cancel();
202
458
  if (dragging) {
203
- const identity = getRendererIdentity();
204
- if (identity) {
205
- event.preventDefault();
206
- const msg = {
207
- type: "element-drag-end", protocolVersion: PROTOCOL_VERSION,
208
- point: { x: event.clientX, y: event.clientY }, ...identity,
209
- };
210
- sendToParent(msg);
211
- }
459
+ event.preventDefault();
460
+ const point = { x: event.clientX, y: event.clientY };
461
+ lastDragPoint = point;
462
+ sendDragEnd(point, false, event.shiftKey);
212
463
  }
213
464
  pendingDrag = null;
214
465
  dragging = false;
466
+ suppressHoverDuringSpacingDrag = false;
215
467
  }
216
468
  trackListener(document, "mouseup", finishDrag, true);
217
- trackListener(document, "keydown", (event) => {
218
- if (event.key === "Alt")
219
- updateMeasureState(true, measurePointerOverPage);
469
+ function forwardKeyboardShortcut(event, phase) {
470
+ const code = isKeyboardShortcutCode(event.code) ? event.code : null;
471
+ if (!code)
472
+ return false;
473
+ if (code === "KeyS") {
474
+ if (phase !== "keydown" || !isSendPromptShortcut(event))
475
+ return false;
476
+ }
477
+ else if (code !== "Backslash" && interactionsSuspended) {
478
+ return false;
479
+ }
480
+ if (phase === "keydown") {
481
+ if (code !== "KeyS" && (event.repeat || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || isEditableEvent(event)))
482
+ return false;
483
+ }
484
+ else if (code !== "Backslash") {
485
+ return false;
486
+ }
487
+ const identity = getRendererIdentity();
488
+ if (!identity)
489
+ return false;
490
+ const message = {
491
+ type: "keyboard-shortcut",
492
+ protocolVersion: PROTOCOL_VERSION,
493
+ phase,
494
+ code,
495
+ ...identity,
496
+ };
497
+ sendToParent(message);
498
+ return phase === "keydown";
499
+ }
500
+ // Global tool shortcuts need to run before application capture handlers on
501
+ // the iframe document. A page-level window handler can otherwise stop the
502
+ // event before it reaches the document listener.
503
+ trackListener(window, "keydown", (event) => {
220
504
  if (isEditableEvent(event))
221
505
  return;
506
+ if (forwardKeyboardShortcut(event, "keydown"))
507
+ event.preventDefault();
508
+ if (isInspectorToggleShortcut(event)) {
509
+ const identity = getRendererIdentity();
510
+ if (!identity)
511
+ return;
512
+ event.preventDefault();
513
+ const msg = {
514
+ type: "inspector-toggle-request",
515
+ protocolVersion: PROTOCOL_VERSION,
516
+ ...identity,
517
+ };
518
+ sendToParent(msg);
519
+ return;
520
+ }
521
+ if (interactionsSuspended)
522
+ return;
523
+ if (event.key === "Alt")
524
+ updateMeasureState(true, measurePointerOverPage);
222
525
  const scrollKey = event.code === "Space"
223
526
  || event.key === "ArrowUp"
224
527
  || event.key === "ArrowDown"
@@ -242,6 +545,8 @@ export function installRendererElementSelector() {
242
545
  }
243
546
  if (event.key === "Escape" || event.key === "Esc") {
244
547
  event.preventDefault();
548
+ cancelActiveDrag();
549
+ clearSpacingCursor();
245
550
  lastSelected = null;
246
551
  const identity = getRendererIdentity();
247
552
  if (!identity)
@@ -279,21 +584,28 @@ export function installRendererElementSelector() {
279
584
  };
280
585
  sendToParent(msg);
281
586
  }, true);
282
- trackListener(document, "keyup", (event) => {
587
+ trackListener(window, "keyup", (event) => {
588
+ forwardKeyboardShortcut(event, "keyup");
283
589
  if (event.key === "Alt")
284
590
  updateMeasureState(false, measurePointerOverPage);
285
591
  }, true);
286
592
  trackListener(window, "blur", () => {
593
+ cancelActiveDrag();
287
594
  updateMeasureState(false, false);
288
595
  });
289
596
  trackListener(document, "mouseout", (event) => {
597
+ if (interactionsSuspended)
598
+ return;
290
599
  const target = event.target;
291
600
  if (!(target instanceof HTMLElement))
292
601
  return;
293
602
  const related = event.relatedTarget;
294
603
  if (!(related instanceof Node) || !document.contains(related)) {
295
604
  updateMeasureState(event.altKey, false);
605
+ lastHoverPoint = null;
296
606
  }
607
+ if (scrolling)
608
+ return;
297
609
  if (related instanceof Node && (target.contains(related) || target === related))
298
610
  return;
299
611
  if (!(target instanceof Element))
@@ -303,40 +615,47 @@ export function installRendererElementSelector() {
303
615
  return;
304
616
  if (resolveSelectionTarget(related, selectionTargetMode(event)))
305
617
  return;
306
- hoverUpdate.schedule({ element: el, clear: true });
618
+ clearSpacingCursor();
619
+ hoverUpdate.schedule({ element: el, clear: true, point: null });
620
+ }, true);
621
+ trackListener(document, "dblclick", (event) => {
622
+ if (interactionsSuspended)
623
+ return;
624
+ const element = resolveSelectionTarget(event.target, selectionTargetMode(event));
625
+ const identity = getRendererIdentity();
626
+ if (!element || !identity)
627
+ return;
628
+ blockApplicationClick(event);
629
+ const message = {
630
+ type: "inline-text-intent",
631
+ protocolVersion: PROTOCOL_VERSION,
632
+ intent: "double-click",
633
+ cid: element.getAttribute("data-cid"),
634
+ src: element.getAttribute("data-src") ?? "",
635
+ elementId: cidIndex.elementId(element),
636
+ point: { x: event.clientX, y: event.clientY },
637
+ emptyProjectionId: event.target instanceof Element
638
+ ? event.target.closest(`[${EMPTY_TEXT_PROJECTION_ATTR}]`)?.getAttribute(EMPTY_TEXT_PROJECTION_ATTR) ?? undefined
639
+ : undefined,
640
+ ...identity,
641
+ };
642
+ sendToParent(message);
307
643
  }, true);
308
644
  trackListener(document, "click", (event) => {
645
+ if (interactionsSuspended || !acceptsClick(event))
646
+ return;
309
647
  const target = event.target;
310
648
  if (!(target instanceof Element))
311
649
  return;
312
- // Defer to navigation-intent when the user clicked a navigable same-origin
313
- // anchor that points to a different route. In canvas mode, anchor clicks
314
- // should create (or focus) a sibling card rather than selecting the anchor
315
- // itself. The navigation-intent listener is registered AFTER this one, so
316
- // we must NOT stop propagation here. Left to its own default the click would
317
- // perform a full-frame navigation inside the iframe; the navigation-intent
318
- // handler calls preventDefault once it has gathered the anchor href.
319
- const anchor = findClosestAnchor(target);
320
- if (anchor && isEligibleNavigation(anchor, event) && hasDifferentRoute(anchor)) {
321
- return;
322
- }
323
- // Command/Ctrl+Shift-click is the deliberate escape hatch for running
324
- // the application action. All other selected canvas clicks are editing
325
- // gestures and must not reach the rendered application.
326
- if (isApplicationActivationClick(event))
327
- return;
328
- const preserveNativeLink = anchor
329
- ? shouldPreserveNativeLinkActivation(anchor, event)
330
- : false;
650
+ // Design-tool clicks are editing gestures and must not reach the
651
+ // rendered application; the Select tool is how the app receives them.
331
652
  const el = resolveSelectionTarget(target, selectionTargetMode(event));
332
653
  if (!el) {
333
- if (!preserveNativeLink)
334
- blockApplicationClick(event);
654
+ blockApplicationClick(event);
335
655
  return;
336
656
  }
337
657
  lastSelected = el;
338
- if (!preserveNativeLink)
339
- blockApplicationClick(event);
658
+ blockApplicationClick(event);
340
659
  const cid = el.getAttribute("data-cid");
341
660
  const selector = buildSelector(el);
342
661
  const { file, line, component, src } = getFiberInfo(el);
@@ -361,18 +680,26 @@ export function installRendererElementSelector() {
361
680
  return () => {
362
681
  if (disposed)
363
682
  return;
683
+ cancelActiveDrag();
364
684
  disposed = true;
365
685
  installed = false;
366
686
  hoverUpdate.cancel();
367
- dragMoveUpdate.cancel();
687
+ if (scrollSettleTimer !== null)
688
+ window.clearTimeout(scrollSettleTimer);
689
+ scrollSettleTimer = null;
368
690
  for (const cleanup of listenerCleanup)
369
691
  cleanup();
370
692
  listenerCleanup.length = 0;
371
- removeInteractionStyles();
372
- pendingDrag = null;
373
- dragging = false;
693
+ removeInteractionStyles?.();
694
+ removeInteractionStyles = null;
374
695
  lastSelected = null;
696
+ activeHoverElement = null;
697
+ lastHoverPoint = null;
698
+ scrolling = false;
699
+ suppressHoverDuringSpacingDrag = false;
375
700
  measurePointerOverPage = false;
376
701
  measureAltKey = false;
702
+ document.documentElement.removeAttribute("data-nudge-ui-panel");
703
+ clearSpacingCursor();
377
704
  };
378
705
  }
@@ -0,0 +1,11 @@
1
+ import { type FrameIdentity, type FrameProtocolMessage } from "./frameProtocol.js";
2
+ export interface CanvasRendererMessage {
3
+ cardId: string;
4
+ iframe: HTMLIFrameElement;
5
+ identity: FrameIdentity;
6
+ message: FrameProtocolMessage;
7
+ }
8
+ type CanvasRendererMessageListener = (event: CanvasRendererMessage) => void;
9
+ /** Subscribes to same-origin messages from registered Canvas renderer frames. */
10
+ export declare function subscribeCanvasRendererMessages(listener: CanvasRendererMessageListener): () => void;
11
+ export {};
@@ -0,0 +1,43 @@
1
+ import { isRendererMessageFor, PROTOCOL_VERSION, } from "./frameProtocol.js";
2
+ import { findCanvasFrameBySource, PROJECT_ID, WORKSPACE_ID } from "./projection.js";
3
+ const listeners = new Set();
4
+ function routeRendererMessage(event) {
5
+ if (event.origin !== window.location.origin || !event.data || typeof event.data !== "object")
6
+ return;
7
+ const frame = findCanvasFrameBySource(event.source);
8
+ if (!frame)
9
+ return;
10
+ const identity = {
11
+ projectId: PROJECT_ID,
12
+ workspaceId: WORKSPACE_ID,
13
+ cardId: frame.cardId,
14
+ };
15
+ // SAFETY: The object guard above makes the optional discriminator fields safe to inspect.
16
+ const candidate = event.data;
17
+ if (candidate.type === "renderer-hello") {
18
+ if (candidate.protocolVersion !== PROTOCOL_VERSION)
19
+ return;
20
+ }
21
+ else if (!isRendererMessageFor(event.data, identity)) {
22
+ return;
23
+ }
24
+ const routed = {
25
+ ...frame,
26
+ identity,
27
+ // SAFETY: Subscribers narrow the discriminated protocol union before reading message-specific fields.
28
+ message: event.data,
29
+ };
30
+ for (const listener of listeners)
31
+ listener(routed);
32
+ }
33
+ /** Subscribes to same-origin messages from registered Canvas renderer frames. */
34
+ export function subscribeCanvasRendererMessages(listener) {
35
+ if (listeners.size === 0)
36
+ window.addEventListener("message", routeRendererMessage);
37
+ listeners.add(listener);
38
+ return () => {
39
+ listeners.delete(listener);
40
+ if (listeners.size === 0)
41
+ window.removeEventListener("message", routeRendererMessage);
42
+ };
43
+ }