nudge-ui 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/README.md +164 -46
  2. package/dist/client.mjs +252 -61
  3. package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
  4. package/dist/css/value-semantics/colorSemantics.js +17 -0
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
  6. package/dist/hosts/astro/clientTransport.js +38 -32
  7. package/dist/hosts/astro/integration.js +5 -1
  8. package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
  9. package/dist/hosts/astro/markdownIdentity.js +81 -0
  10. package/dist/hosts/next/manifest.js +1 -1
  11. package/dist/hosts/next/sidecar.js +5 -1
  12. package/dist/hosts/static/manifest.js +1 -1
  13. package/dist/hosts/static/server.js +3 -0
  14. package/dist/hosts/vite/vite.d.ts +3 -0
  15. package/dist/hosts/vite/vite.js +24 -4
  16. package/dist/inspector/agent/McpConnectionDialog.js +13 -19
  17. package/dist/inspector/agent/activity.d.ts +18 -0
  18. package/dist/inspector/agent/activity.js +70 -0
  19. package/dist/inspector/agent/client.d.ts +2 -0
  20. package/dist/inspector/agent/client.js +29 -1
  21. package/dist/inspector/agent/connectionStatus.js +1 -1
  22. package/dist/inspector/agent/httpTransport.js +9 -2
  23. package/dist/inspector/agent/protocol.d.ts +6 -3
  24. package/dist/inspector/agent/protocol.js +1 -1
  25. package/dist/inspector/agent/verification.d.ts +11 -1
  26. package/dist/inspector/agent/verification.js +53 -25
  27. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
  28. package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
  29. package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
  30. package/dist/inspector/canvas/CanvasCard.js +133 -38
  31. package/dist/inspector/canvas/CanvasElementOverlay.js +152 -16
  32. package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
  33. package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +8 -4
  35. package/dist/inspector/canvas/CanvasWorkspace.js +225 -57
  36. package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
  37. package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
  38. package/dist/inspector/canvas/agentPresentation.js +64 -52
  39. package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
  40. package/dist/inspector/canvas/canvasGestures.js +1 -1
  41. package/dist/inspector/canvas/canvasStore.d.ts +46 -82
  42. package/dist/inspector/canvas/canvasStore.js +251 -284
  43. package/dist/inspector/canvas/frameContent.d.ts +21 -0
  44. package/dist/inspector/canvas/frameContent.js +44 -0
  45. package/dist/inspector/canvas/frameProtocol.d.ts +21 -4
  46. package/dist/inspector/canvas/frameProtocol.js +3 -3
  47. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
  48. package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
  49. package/dist/inspector/canvas/layout.d.ts +52 -0
  50. package/dist/inspector/canvas/layout.js +106 -0
  51. package/dist/inspector/canvas/projection.d.ts +15 -5
  52. package/dist/inspector/canvas/projection.js +69 -13
  53. package/dist/inspector/canvas/rendererBootstrap.js +32 -4
  54. package/dist/inspector/canvas/rendererElementSelector.js +47 -6
  55. package/dist/inspector/canvas/rendererStylesheet.js +2 -2
  56. package/dist/inspector/canvas/resizeHandleScale.js +1 -1
  57. package/dist/inspector/canvas/sessionStore.d.ts +11 -26
  58. package/dist/inspector/canvas/sessionStore.js +43 -183
  59. package/dist/inspector/canvas/staleChangeDetector.js +9 -3
  60. package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
  61. package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
  62. package/dist/inspector/canvas/viewStore.d.ts +38 -0
  63. package/dist/inspector/canvas/viewStore.js +104 -0
  64. package/dist/inspector/canvas/wheelPan.d.ts +8 -0
  65. package/dist/inspector/canvas/wheelPan.js +8 -0
  66. package/dist/inspector/changes/changesLog.d.ts +6 -5
  67. package/dist/inspector/changes/changesLog.js +40 -32
  68. package/dist/inspector/changes/draftChanges.d.ts +56 -0
  69. package/dist/inspector/changes/draftChanges.js +300 -0
  70. package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
  71. package/dist/inspector/changes/previewDiagnostics.js +11 -25
  72. package/dist/inspector/clientManifest.d.ts +1 -1
  73. package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
  74. package/dist/inspector/comments/CommentsOverlay.js +250 -0
  75. package/dist/inspector/comments/element.d.ts +6 -0
  76. package/dist/inspector/comments/element.js +47 -0
  77. package/dist/inspector/comments/store.d.ts +29 -0
  78. package/dist/inspector/comments/store.js +121 -0
  79. package/dist/inspector/comments/verification.d.ts +5 -0
  80. package/dist/inspector/comments/verification.js +52 -0
  81. package/dist/inspector/drafts/model.d.ts +35 -0
  82. package/dist/inspector/drafts/model.js +45 -0
  83. package/dist/inspector/drafts/store.d.ts +28 -0
  84. package/dist/inspector/drafts/store.js +231 -0
  85. package/dist/inspector/index.js +20 -27
  86. package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
  87. package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
  88. package/dist/inspector/iterations/capture.d.ts +2 -0
  89. package/dist/inspector/iterations/capture.js +173 -0
  90. package/dist/inspector/iterations/client.d.ts +7 -0
  91. package/dist/inspector/iterations/client.js +52 -0
  92. package/dist/inspector/iterations/coordinator.d.ts +15 -0
  93. package/dist/inspector/iterations/coordinator.js +130 -0
  94. package/dist/inspector/iterations/prompt.d.ts +3 -0
  95. package/dist/inspector/iterations/prompt.js +26 -0
  96. package/dist/inspector/overlay/DomNavigator.js +21 -15
  97. package/dist/inspector/overlay/interactionStyles.js +3 -1
  98. package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
  99. package/dist/inspector/overlay/spacingGestures.js +82 -5
  100. package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
  101. package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
  102. package/dist/inspector/projection/renderedInstance.d.ts +2 -1
  103. package/dist/inspector/projection/renderedInstance.js +9 -5
  104. package/dist/inspector/projection/structuralProjection.d.ts +2 -1
  105. package/dist/inspector/projection/structuralProjection.js +19 -15
  106. package/dist/inspector/projection/textProjection.d.ts +2 -1
  107. package/dist/inspector/projection/textProjection.js +8 -4
  108. package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
  109. package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
  110. package/dist/inspector/prompt/clipboardHandoff.js +92 -143
  111. package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
  112. package/dist/inspector/prompt/generatePrompt.js +17 -16
  113. package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
  114. package/dist/inspector/runtime/runtimeConfig.js +24 -0
  115. package/dist/inspector/settings/SettingsDialog.js +21 -8
  116. package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
  117. package/dist/inspector/shell/AgentStatusButton.js +36 -0
  118. package/dist/inspector/shell/AppShell.js +4 -2
  119. package/dist/inspector/shell/ChangesLog.js +46 -26
  120. package/dist/inspector/shell/CopyPromptButton.js +138 -89
  121. package/dist/inspector/shell/EmptyState.js +9 -8
  122. package/dist/inspector/shell/InspectorShell.js +13 -21
  123. package/dist/inspector/shell/PeekOriginalButton.js +24 -23
  124. package/dist/inspector/shell/originalPreview.js +5 -5
  125. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  126. package/dist/inspector/shell/shortcuts.js +11 -0
  127. package/dist/inspector/sketch/SketchEditor.js +14 -14
  128. package/dist/inspector/sketch/SketchEntryButton.js +1 -1
  129. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
  130. package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
  131. package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
  132. package/dist/inspector/sketch/SketchOverlay.js +7 -7
  133. package/dist/inspector/sketch/SketchWorkspace.js +18 -12
  134. package/dist/inspector/sketch/interaction.d.ts +1 -0
  135. package/dist/inspector/sketch/interaction.js +8 -4
  136. package/dist/inspector/sketch/model.d.ts +5 -0
  137. package/dist/inspector/sketch/model.js +6 -2
  138. package/dist/inspector/sketch/raster.d.ts +1 -2
  139. package/dist/inspector/sketch/raster.js +68 -4
  140. package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
  141. package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
  142. package/dist/inspector/styleEditors/BorderEditor.js +49 -78
  143. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
  144. package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
  145. package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
  146. package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
  147. package/dist/inspector/styleEditors/ColorPicker.js +10 -4
  148. package/dist/inspector/styleEditors/GapField.js +3 -5
  149. package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
  150. package/dist/inspector/styleEditors/GridChildSection.js +18 -9
  151. package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
  152. package/dist/inspector/styleEditors/GridPicker.js +8 -2
  153. package/dist/inspector/styleEditors/GridSection.js +1 -1
  154. package/dist/inspector/styleEditors/GridValueField.js +19 -14
  155. package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
  156. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
  157. package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
  158. package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
  159. package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
  160. package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
  161. package/dist/inspector/styleEditors/LayoutSection.js +32 -11
  162. package/dist/inspector/styleEditors/SpacingBox.js +7 -5
  163. package/dist/inspector/styleEditors/Typography.js +11 -3
  164. package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
  165. package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
  166. package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
  167. package/dist/inspector/styleEditors/gradientValue.js +114 -0
  168. package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
  169. package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
  170. package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
  171. package/dist/inspector/styleEditors/layoutValue.js +0 -42
  172. package/dist/inspector/styleEditors/nudgeValue.d.ts +2 -2
  173. package/dist/inspector/styleEditors/nudgeValue.js +36 -8
  174. package/dist/inspector/tokens/TokenField.d.ts +7 -0
  175. package/dist/inspector/tokens/TokenField.js +29 -21
  176. package/dist/inspector/tokens/TokenLabel.js +5 -14
  177. package/dist/inspector/tokens/editActions.d.ts +1 -1
  178. package/dist/inspector/tokens/editActions.js +1 -1
  179. package/dist/inspector/ui/AtRuleContext.js +4 -6
  180. package/dist/inspector/ui/Button.d.ts +6 -2
  181. package/dist/inspector/ui/Button.js +4 -3
  182. package/dist/inspector/ui/ControlSurface.d.ts +2 -2
  183. package/dist/inspector/ui/ControlSurface.js +4 -3
  184. package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
  185. package/dist/inspector/ui/InlineStyleWarning.js +13 -0
  186. package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
  187. package/dist/inspector/ui/InspectorPopover.js +4 -5
  188. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  189. package/dist/inspector/ui/PopoverListbox.js +8 -7
  190. package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
  191. package/dist/inspector/ui/SegmentedControl.js +2 -2
  192. package/dist/inspector/ui/Select.d.ts +6 -1
  193. package/dist/inspector/ui/Select.js +7 -11
  194. package/dist/inspector/ui/SideValuesField.js +1 -1
  195. package/dist/inspector/ui/Tooltip.d.ts +26 -0
  196. package/dist/inspector/ui/Tooltip.js +32 -0
  197. package/dist/inspector/ui/classNames.d.ts +2 -0
  198. package/dist/inspector/ui/classNames.js +4 -0
  199. package/dist/inspector/ui/customCursors.d.ts +23 -0
  200. package/dist/inspector/ui/customCursors.js +122 -0
  201. package/dist/inspector/ui/dragCursor.d.ts +0 -2
  202. package/dist/inspector/ui/dragCursor.js +2 -10
  203. package/dist/inspector/ui/portalContainer.d.ts +2 -0
  204. package/dist/inspector/ui/portalContainer.js +6 -0
  205. package/dist/inspector/ui/styles.js +29 -21
  206. package/dist/inspector/workspace/commands.d.ts +9 -0
  207. package/dist/inspector/workspace/commands.js +82 -0
  208. package/dist/inspector/workspace/controller.d.ts +4 -0
  209. package/dist/inspector/workspace/controller.js +66 -0
  210. package/dist/inspector/workspace/handoff.d.ts +42 -0
  211. package/dist/inspector/workspace/handoff.js +115 -0
  212. package/dist/inspector/workspace/timeline.d.ts +30 -0
  213. package/dist/inspector/workspace/timeline.js +108 -0
  214. package/dist/internal/agent-protocol.d.ts +19 -2
  215. package/dist/internal/agent-protocol.js +13 -0
  216. package/dist/nudge-ui.mjs +558 -311
  217. package/dist/project/artifacts.d.ts +13 -0
  218. package/dist/project/artifacts.js +253 -0
  219. package/dist/transport/artifacts.d.ts +8 -0
  220. package/dist/transport/artifacts.js +9 -0
  221. package/dist/transport/editor.js +3 -1
  222. package/dist/transport/routes.d.ts +2 -0
  223. package/dist/transport/routes.js +2 -0
  224. package/package.json +6 -2
  225. package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
  226. package/dist/inspector/changes/workspaceChanges.js +0 -265
@@ -0,0 +1,21 @@
1
+ import { type DraftTarget } from "../drafts/model.js";
2
+ /** Content identity is independent of frame geometry and its editable draft. */
3
+ export type FrameContent = {
4
+ kind: "route";
5
+ url: string;
6
+ navigationUrl?: string;
7
+ } | {
8
+ kind: "iteration";
9
+ artifactId: string;
10
+ sourceUrl: string;
11
+ };
12
+ export declare function contentSourceUrl(content: FrameContent | undefined): string;
13
+ export declare function iterationId(content: FrameContent | undefined): string | undefined;
14
+ export declare function contentNavigationUrl(content: FrameContent): string | undefined;
15
+ export declare function contentDocumentUrl(content: FrameContent): string;
16
+ export declare function contentEditTarget(content: FrameContent): DraftTarget;
17
+ export declare function getLinkedFrameIds(frames: readonly {
18
+ id: string;
19
+ content: FrameContent;
20
+ }[], activeId: string | null): readonly string[];
21
+ export declare function isFrameContent(value: unknown, origin: string): value is FrameContent;
@@ -0,0 +1,44 @@
1
+ import { applicationTarget, htmlTarget } from "../drafts/model.js";
2
+ import { artifactDocumentUrl } from "../iterations/client.js";
3
+ export function contentSourceUrl(content) {
4
+ return content ? content.kind === "route" ? content.url : content.sourceUrl : "";
5
+ }
6
+ export function iterationId(content) {
7
+ return content?.kind === "iteration" ? content.artifactId : undefined;
8
+ }
9
+ export function contentNavigationUrl(content) {
10
+ return content.kind === "route" ? content.navigationUrl : undefined;
11
+ }
12
+ export function contentDocumentUrl(content) {
13
+ return content.kind === "route" ? content.navigationUrl ?? content.url : artifactDocumentUrl(content.artifactId);
14
+ }
15
+ export function contentEditTarget(content) {
16
+ return content.kind === "route" ? applicationTarget(content.url) : htmlTarget(content.artifactId);
17
+ }
18
+ export function getLinkedFrameIds(frames, activeId) {
19
+ const active = frames.find((frame) => frame.id === activeId);
20
+ if (active?.content.kind !== "route")
21
+ return [];
22
+ const route = applicationTarget(active.content.url).route;
23
+ const matching = frames.filter((frame) => frame.content.kind === "route"
24
+ && applicationTarget(frame.content.url).route === route);
25
+ return matching.length > 1 ? matching.map((frame) => frame.id) : [];
26
+ }
27
+ export function isFrameContent(value, origin) {
28
+ if (!value || typeof value !== "object")
29
+ return false;
30
+ const content = value;
31
+ const url = content.kind === "route" ? content.url : content.sourceUrl;
32
+ if (typeof url !== "string")
33
+ return false;
34
+ try {
35
+ if (new URL(url).origin !== origin)
36
+ return false;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ if (content.kind === "iteration")
42
+ return typeof content.artifactId === "string" && /^[a-f0-9-]{36}$/i.test(content.artifactId);
43
+ return content.kind === "route" && content.navigationUrl === undefined;
44
+ }
@@ -2,7 +2,7 @@ import type { ComponentOverride } from "../componentSemantics/types.js";
2
2
  import type { RenderedInstanceOverride } from "../changes/editModel.js";
3
3
  import type { NudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
4
4
  import type { SpacingDescriptor } from "../overlay/spacingGestures.js";
5
- export declare const PROTOCOL_VERSION = 20;
5
+ export declare const PROTOCOL_VERSION = 21;
6
6
  export interface FrameMessage {
7
7
  type: string;
8
8
  protocolVersion: number;
@@ -130,8 +130,12 @@ export interface ElementHoverMessage extends RendererMessage {
130
130
  x: number;
131
131
  y: number;
132
132
  } | null;
133
- /** The padding or layout gap under the pointer, when one is draggable. */
133
+ /** The spacing handle under the pointer. Drives the resize cursor and drag
134
+ * start; only present when the pointer is inside the handle hit area. */
134
135
  spacing?: SpacingDescriptor | null;
136
+ /** The padding bar or layout gap area under the pointer, regardless of the
137
+ * handle. Drives the hover fill overlay across the whole area. */
138
+ hoverSpacing?: SpacingDescriptor | null;
135
139
  }
136
140
  /** Modifier state stays inside one renderer frame; the parent never infers it
137
141
  * from its own keyboard events because focus lives in the iframe. */
@@ -235,7 +239,7 @@ export interface InspectorToggleRequestMessage extends RendererMessage {
235
239
  export interface InspectorOpenRequestMessage extends RendererMessage {
236
240
  type: "inspector-open-request";
237
241
  }
238
- export type KeyboardShortcutCode = "Backslash" | "KeyI" | "KeyV" | "KeyH" | "KeyP" | "KeyS";
242
+ export type KeyboardShortcutCode = "Backslash" | "KeyI" | "KeyV" | "KeyC" | "KeyP" | "KeyS";
239
243
  export type KeyboardShortcutPhase = "keydown" | "keyup";
240
244
  /** Forwards controller-owned keyboard shortcuts from an iframe document. */
241
245
  export interface KeyboardShortcutMessage extends RendererMessage {
@@ -274,6 +278,8 @@ export interface MeasureModifierMessage extends RendererMessage {
274
278
  export interface BoardGestureStateMessage extends RendererMessage {
275
279
  type: "board-gesture-state";
276
280
  enabled: boolean;
281
+ /** Ordinary wheel gestures pan the board instead of scrolling the app. */
282
+ panScroll?: boolean;
277
283
  }
278
284
  /** Proxies modified wheel gestures that originate inside an iframe card. */
279
285
  export interface ZoomMessage extends RendererMessage {
@@ -284,7 +290,18 @@ export interface ZoomMessage extends RendererMessage {
284
290
  y: number;
285
291
  };
286
292
  }
287
- export type FrameProtocolMessage = ParentReadyMessage | RendererHelloMessage | FrameReadyMessage | FrameRuntimeMessage | FrameMetadataMessage | FrameLoadError | ReplaceStylesMessage | ProjectionAppliedMessage | StructuralProjectionReportMessage | RenderedInstanceProjectionReportMessage | TextProjectionReportMessage | NavigationIntentMessage | LinkTargetStateMessage | ElementHoverMessage | ElementMeasureStateMessage | ElementClickMessage | InlineTextIntentMessage | ElementDeselectMessage | ElementDragStartMessage | ElementDragMoveMessage | ElementDragEndMessage | ElementDeleteMessage | ElementNudgeMessage | HistoryRequestMessage | InspectorToggleRequestMessage | InspectorOpenRequestMessage | KeyboardShortcutMessage | PanStartMessage | PanMoveMessage | PanEndMessage | PanModifierMessage | MeasureModifierMessage | BoardGestureStateMessage | ZoomMessage;
293
+ /** Proxies ordinary wheel gestures as normalized pixel distances. */
294
+ export interface WheelPanMessage extends RendererMessage {
295
+ type: "wheel-pan";
296
+ deltaX: number;
297
+ deltaY: number;
298
+ }
299
+ /** Keeps the A key state synchronized when keyboard focus moves into an iframe. */
300
+ export interface AppInteractionModifierMessage extends RendererMessage {
301
+ type: "app-interaction-modifier";
302
+ held: boolean;
303
+ }
304
+ export type FrameProtocolMessage = ParentReadyMessage | RendererHelloMessage | FrameReadyMessage | FrameRuntimeMessage | FrameMetadataMessage | FrameLoadError | ReplaceStylesMessage | ProjectionAppliedMessage | StructuralProjectionReportMessage | RenderedInstanceProjectionReportMessage | TextProjectionReportMessage | NavigationIntentMessage | LinkTargetStateMessage | ElementHoverMessage | ElementMeasureStateMessage | ElementClickMessage | InlineTextIntentMessage | ElementDeselectMessage | ElementDragStartMessage | ElementDragMoveMessage | ElementDragEndMessage | ElementDeleteMessage | ElementNudgeMessage | HistoryRequestMessage | InspectorToggleRequestMessage | InspectorOpenRequestMessage | KeyboardShortcutMessage | PanStartMessage | PanMoveMessage | PanEndMessage | PanModifierMessage | MeasureModifierMessage | BoardGestureStateMessage | ZoomMessage | WheelPanMessage | AppInteractionModifierMessage;
288
305
  export declare function setRendererIdentity(identity: FrameIdentity): void;
289
306
  export declare function clearRendererIdentity(expected?: FrameIdentity): void;
290
307
  export declare function getRendererIdentity(): FrameIdentity | null;
@@ -1,6 +1,7 @@
1
1
  import { isDocumentProjectionReport } from "../projection/renderedInstance.js";
2
2
  import { isTextProjectionReport } from "../projection/textProjection.js";
3
3
  import { isStructuralProjectionReport } from "../projection/structuralProjectionBoundary.js";
4
+ // v21 adds the Comment tool cursor and C shortcut.
4
5
  // v20 adds Shift state to element drag messages so spacing handles can snap
5
6
  // changes to 8px increments.
6
7
  // v19 adds spacing drag gestures (hover point, spacing descriptor, start
@@ -17,7 +18,7 @@ import { isStructuralProjectionReport } from "../projection/structuralProjection
17
18
  // rereading a Canvas iframe until the renderer has applied its revision. v11
18
19
  // added the renderer-hello handshake solicitation for runtimes whose boot
19
20
  // completes after the controller's load-time parent-ready.
20
- export const PROTOCOL_VERSION = 20;
21
+ export const PROTOCOL_VERSION = 21;
21
22
  let rendererIdentity = null;
22
23
  export function setRendererIdentity(identity) {
23
24
  rendererIdentity = identity;
@@ -40,9 +41,8 @@ export function isRendererMessageFor(value, identity) {
40
41
  }
41
42
  export function isKeyboardShortcutCode(value) {
42
43
  return value === "Backslash"
43
- || value === "KeyI"
44
44
  || value === "KeyV"
45
- || value === "KeyH"
45
+ || value === "KeyC"
46
46
  || value === "KeyP"
47
47
  || value === "KeyS";
48
48
  }
@@ -1,4 +1,6 @@
1
1
  /** `select` leaves the application interactive; `design` turns clicks into inspector selection. */
2
- export type CanvasInteractionTool = "select" | "design" | "pan" | "sketch";
2
+ export type CanvasInteractionTool = "select" | "design" | "comment" | "sketch";
3
+ /** Physical key used to temporarily interact with the app in the canvas. */
4
+ export declare const APP_INTERACTION_KEY = "KeyA";
3
5
  export declare function canvasToolForCode(code: string): CanvasInteractionTool | null;
4
6
  export declare function canvasToolForEvent(event: KeyboardEvent): CanvasInteractionTool | null;
@@ -1,10 +1,10 @@
1
+ /** Physical key used to temporarily interact with the app in the canvas. */
2
+ export const APP_INTERACTION_KEY = "KeyA";
1
3
  export function canvasToolForCode(code) {
2
- if (code === "KeyI")
3
- return "select";
4
4
  if (code === "KeyV")
5
5
  return "design";
6
- if (code === "KeyH")
7
- return "pan";
6
+ if (code === "KeyC")
7
+ return "comment";
8
8
  if (code === "KeyP")
9
9
  return "sketch";
10
10
  return null;
@@ -0,0 +1,52 @@
1
+ export interface LayoutCard {
2
+ readonly id: string;
3
+ readonly x: number;
4
+ readonly y: number;
5
+ readonly width: number;
6
+ readonly height: number;
7
+ }
8
+ export interface Bounds {
9
+ readonly minX: number;
10
+ readonly minY: number;
11
+ readonly maxX: number;
12
+ readonly maxY: number;
13
+ }
14
+ /** The layout fields a creation may change on existing frames. */
15
+ export interface LayoutShift {
16
+ readonly id: string;
17
+ readonly from: Pick<LayoutCard, "x" | "y">;
18
+ readonly to: Pick<LayoutCard, "x" | "y">;
19
+ }
20
+ export declare function boundsOf(cards: readonly LayoutCard[]): Bounds;
21
+ /** Places a new frame to the right of every frame, on the first frame's row. */
22
+ export declare function nextCardPosition(cards: readonly LayoutCard[], gap: number): {
23
+ x: number;
24
+ y: number;
25
+ };
26
+ export declare function pushOverlappingCards<T extends LayoutCard>(cards: readonly T[], protectedIds: ReadonlySet<string>, gap: number): T[];
27
+ export declare function iterationPosition(cards: readonly LayoutCard[], source: LayoutCard, gap: number): {
28
+ x: number;
29
+ y: number;
30
+ };
31
+ /** Zooms and centers content inside the padded viewport; null for empty content. */
32
+ export declare function fitCamera(bounds: Bounds, viewport: {
33
+ width: number;
34
+ height: number;
35
+ }, padding: number): {
36
+ x: number;
37
+ y: number;
38
+ zoom: number;
39
+ } | null;
40
+ export declare function centerCamera(bounds: Bounds, zoom: number, viewport: {
41
+ width: number;
42
+ height: number;
43
+ }): {
44
+ x: number;
45
+ y: number;
46
+ zoom: number;
47
+ };
48
+ export declare function layoutShifts(before: readonly LayoutCard[], after: readonly LayoutCard[]): LayoutShift[];
49
+ /** Replays recorded shifts as deltas, so frames moved since the creation keep their later moves. */
50
+ export declare function applyLayoutShifts<T extends LayoutCard>(cards: readonly T[], shifts: readonly LayoutShift[], forward: boolean): T[];
51
+ /** Reinserts recorded frames after their nearest recorded predecessor that still exists. */
52
+ export declare function reinsertInRecordedOrder<T extends LayoutCard>(cards: readonly T[], recorded: readonly T[], ids: ReadonlySet<string>): T[];
@@ -0,0 +1,106 @@
1
+ import { MAX_CAMERA_ZOOM, MIN_CAMERA_ZOOM } from "./viewStore.js";
2
+ export function boundsOf(cards) {
3
+ let minX = Infinity;
4
+ let minY = Infinity;
5
+ let maxX = -Infinity;
6
+ let maxY = -Infinity;
7
+ for (const card of cards) {
8
+ minX = Math.min(minX, card.x);
9
+ minY = Math.min(minY, card.y);
10
+ maxX = Math.max(maxX, card.x + card.width);
11
+ maxY = Math.max(maxY, card.y + card.height);
12
+ }
13
+ return { minX, minY, maxX, maxY };
14
+ }
15
+ /** Places a new frame to the right of every frame, on the first frame's row. */
16
+ export function nextCardPosition(cards, gap) {
17
+ if (cards.length === 0)
18
+ return { x: 0, y: 0 };
19
+ return { x: Math.max(...cards.map((card) => card.x + card.width + gap)), y: cards[0].y };
20
+ }
21
+ export function pushOverlappingCards(cards, protectedIds, gap) {
22
+ const fixed = cards.filter((card) => protectedIds.has(card.id)).map((card) => boundsOf([card]));
23
+ const moving = cards.filter((card) => !protectedIds.has(card.id)).sort((a, b) => a.x - b.x);
24
+ const shifts = new Map();
25
+ for (const card of moving) {
26
+ let bounds = boundsOf([card]);
27
+ let dx = 0;
28
+ let collision = true;
29
+ while (collision) {
30
+ collision = false;
31
+ for (const other of fixed) {
32
+ if (bounds.minY >= other.maxY || bounds.maxY <= other.minY
33
+ || bounds.minX >= other.maxX + gap || bounds.maxX + gap <= other.minX)
34
+ continue;
35
+ const shift = other.maxX + gap - bounds.minX;
36
+ dx += shift;
37
+ bounds = { ...bounds, minX: bounds.minX + shift, maxX: bounds.maxX + shift };
38
+ collision = true;
39
+ }
40
+ }
41
+ if (dx)
42
+ shifts.set(card.id, dx);
43
+ fixed.push(bounds);
44
+ }
45
+ return shifts.size ? cards.map((card) => shifts.has(card.id) ? { ...card, x: card.x + shifts.get(card.id) } : card) : [...cards];
46
+ }
47
+ export function iterationPosition(cards, source, gap) {
48
+ const x = source.x;
49
+ let y = source.y + source.height + gap * 2;
50
+ for (const neighbor of [...cards].sort((a, b) => a.y - b.y)) {
51
+ if (x >= neighbor.x + neighbor.width + gap || x + source.width + gap <= neighbor.x)
52
+ continue;
53
+ if (y < neighbor.y + neighbor.height + gap * 2 && y + source.height + gap * 2 > neighbor.y)
54
+ y = neighbor.y + neighbor.height + gap * 2;
55
+ }
56
+ return { x, y };
57
+ }
58
+ /** Zooms and centers content inside the padded viewport; null for empty content. */
59
+ export function fitCamera(bounds, viewport, padding) {
60
+ const width = bounds.maxX - bounds.minX;
61
+ const height = bounds.maxY - bounds.minY;
62
+ if (width <= 0 || height <= 0)
63
+ return null;
64
+ const zoom = Math.max(MIN_CAMERA_ZOOM, Math.min(Math.max(1, viewport.width - padding * 2) / width, Math.max(1, viewport.height - padding * 2) / height, MAX_CAMERA_ZOOM));
65
+ return centerCamera(bounds, zoom, viewport);
66
+ }
67
+ export function centerCamera(bounds, zoom, viewport) {
68
+ return {
69
+ x: -((bounds.minX + bounds.maxX) / 2) * zoom + viewport.width / 2,
70
+ y: -((bounds.minY + bounds.maxY) / 2) * zoom + viewport.height / 2,
71
+ zoom,
72
+ };
73
+ }
74
+ export function layoutShifts(before, after) {
75
+ const previous = new Map(before.map((card) => [card.id, card]));
76
+ return after.flatMap((card) => {
77
+ const old = previous.get(card.id);
78
+ if (!old || (old.x === card.x && old.y === card.y))
79
+ return [];
80
+ return [{ id: card.id, from: { x: old.x, y: old.y }, to: { x: card.x, y: card.y } }];
81
+ });
82
+ }
83
+ /** Replays recorded shifts as deltas, so frames moved since the creation keep their later moves. */
84
+ export function applyLayoutShifts(cards, shifts, forward) {
85
+ const byId = new Map(shifts.map((shift) => [shift.id, shift]));
86
+ return cards.map((card) => {
87
+ const shift = byId.get(card.id);
88
+ if (!shift)
89
+ return card;
90
+ const from = forward ? shift.from : shift.to;
91
+ const to = forward ? shift.to : shift.from;
92
+ return { ...card, x: card.x + to.x - from.x, y: card.y + to.y - from.y };
93
+ });
94
+ }
95
+ /** Reinserts recorded frames after their nearest recorded predecessor that still exists. */
96
+ export function reinsertInRecordedOrder(cards, recorded, ids) {
97
+ let next = [...cards];
98
+ for (const [index, added] of recorded.entries()) {
99
+ if (!ids.has(added.id))
100
+ continue;
101
+ const preceding = recorded.slice(0, index).reverse().find((card) => next.some((item) => item.id === card.id));
102
+ const insertion = preceding ? next.findIndex((card) => card.id === preceding.id) + 1 : 0;
103
+ next = [...next.slice(0, insertion), { ...added }, ...next.slice(insertion)];
104
+ }
105
+ return next;
106
+ }
@@ -1,9 +1,15 @@
1
- import { type WorkspaceContents } from "../changes/workspaceChanges.js";
1
+ import { type DraftContents } from "../changes/draftChanges.js";
2
2
  import type { CanvasCard } from "./canvasStore.js";
3
3
  import { type PreviewDocument } from "../changes/previewDiagnostics.js";
4
- import { type CompiledManagedStyles } from "../projection/workspaceProjection.js";
4
+ import { type CompiledManagedStyles, type DraftProjectionPlan } from "../projection/draftProjection.js";
5
5
  export declare const PROJECT_ID: string;
6
- export declare const WORKSPACE_ID: `${string}-${string}-${string}-${string}-${string}`;
6
+ export declare const WORKSPACE_ID: string;
7
+ export interface FrameProjection extends DraftProjectionPlan<CompiledManagedStyles> {
8
+ readonly draftId: string;
9
+ readonly draftRevision: number;
10
+ readonly revision: number;
11
+ readonly css: string;
12
+ }
7
13
  export interface CanvasProjectionStatus {
8
14
  sentRevision: number;
9
15
  appliedRevision: number;
@@ -22,10 +28,14 @@ export declare function computeProjection(): {
22
28
  textContentChanges: readonly import("../changes/editModel.js").TextContentChangeRecord[];
23
29
  componentOverrides: readonly import("../componentSemantics/types.js").ComponentOverride[];
24
30
  };
31
+ export declare function computeProjectionForCard(cardId: string): FrameProjection;
32
+ export declare function getCanonicalFrameProjection(cardId: string, expectedRevision: number): FrameProjection | null;
33
+ export declare function restoreCanonicalFrameProjection(doc: Document): void;
34
+ export declare function getCanvasCardIdForDocument(doc: Document): string | null;
25
35
  export declare function resetProjectionRevision(): void;
26
- export declare function sendProjectionToCard(card: CanvasCard, iframe: HTMLIFrameElement): void;
36
+ export declare function sendProjectionToCard(card: Pick<CanvasCard, "id">, iframe: HTMLIFrameElement): void;
27
37
  /** Temporarily projects a snapshot through the renderer that owns the document. */
28
- export declare function projectWorkspaceSnapshotToDocument(doc: Document, snapshot: WorkspaceContents): Promise<number | null>;
38
+ export declare function projectDraftToDocument(doc: Document, snapshot: DraftContents): Promise<number | null>;
29
39
  /** Returns whether an acknowledged temporary projection is still authoritative. */
30
40
  export declare function isCanvasProjectionRevisionCurrent(doc: Document, expectedRevision: number): boolean;
31
41
  /** Returns whether the live frame acknowledged its latest canonical workspace projection. */
@@ -1,18 +1,25 @@
1
- import { getWorkspaceChanges } from "../changes/workspaceChanges.js";
2
- import { getCanvasMode } from "./canvasStore.js";
1
+ import { draftIdForCard } from "../drafts/store.js";
2
+ import { getDraftChanges, getActiveDraftChanges } from "../changes/draftChanges.js";
3
+ import { getCanvasCards } from "./canvasStore.js";
4
+ import { getCanvasMode } from "./viewStore.js";
3
5
  import { PROTOCOL_VERSION } from "./frameProtocol.js";
4
6
  import { clearCanvasRenderedInstanceProjectionReports, setCanonicalRenderedInstanceOverrides, } from "../projection/renderedInstance.js";
5
7
  import { clearCanvasTextProjectionReports, setCanonicalTextContentChanges, } from "../projection/textProjection.js";
6
8
  import { clearCanvasStructuralProjectionReports } from "../projection/structuralProjection.js";
7
9
  import { invalidatePreviewDocumentSession, startPreviewDocumentSession, } from "../changes/previewDiagnostics.js";
8
- import { compileWorkspaceProjection, } from "../projection/workspaceProjection.js";
10
+ import { compileDraftProjection, } from "../projection/draftProjection.js";
9
11
  import { isOriginalPreviewActive } from "../shell/originalPreview.js";
10
12
  import { disposeBrowserCssInspection } from "../inspection/browserCssInspectionRegistry.js";
11
- export const PROJECT_ID = window.location.origin;
12
- export const WORKSPACE_ID = crypto.randomUUID?.() ?? `ws-${Date.now()}`;
13
+ export const PROJECT_ID = typeof window !== "undefined" && window.location?.origin
14
+ ? window.location.origin
15
+ : "";
16
+ export const WORKSPACE_ID = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
17
+ ? crypto.randomUUID()
18
+ : `ws-${Date.now()}`;
13
19
  let revision = 0;
14
20
  let lastRulesKey = null;
15
21
  let lastPeekActive = false;
22
+ const draftPlans = new Map();
16
23
  const frameProjectionStates = new Map();
17
24
  const previewDocuments = new Map();
18
25
  const projectionAcknowledgementListeners = new Set();
@@ -22,6 +29,7 @@ function createPreviewDocumentIdentity(cardId) {
22
29
  const previewDocument = {
23
30
  logicalDocument: `canvas:${cardId}`,
24
31
  sessionId: `canvas-session-${nextPreviewSessionId++}`,
32
+ draftId: draftIdForCard(cardId) ?? getActiveDraftChanges().draftId,
25
33
  };
26
34
  previewDocuments.set(cardId, previewDocument);
27
35
  return previewDocument;
@@ -50,7 +58,7 @@ function projectionKey(plan) {
50
58
  return `${plan.managedStyles.css}\u0000${JSON.stringify(plan.instanceOverrides)}\u0000${JSON.stringify(plan.structuralChanges)}\u0000${JSON.stringify(plan.textContentChanges)}\u0000${JSON.stringify(plan.componentOverrides)}`;
51
59
  }
52
60
  export function computeProjection() {
53
- const plan = compileWorkspaceProjection(getWorkspaceChanges());
61
+ const plan = compileDraftProjection(getActiveDraftChanges());
54
62
  if (isOriginalPreviewActive()) {
55
63
  // Hold-to-view-original: frames show the page without inspector changes
56
64
  // while canonical intent (and its projection key) stays untouched.
@@ -84,14 +92,53 @@ export function computeProjection() {
84
92
  }
85
93
  return { ...plan, css: plan.managedStyles.css, revision };
86
94
  }
95
+ export function computeProjectionForCard(cardId) {
96
+ const draftId = draftIdForCard(cardId);
97
+ const workspace = draftId ? getDraftChanges(draftId) : getActiveDraftChanges();
98
+ const peek = isOriginalPreviewActive();
99
+ const cached = draftPlans.get(workspace.draftId);
100
+ if (cached && cached.plan.draftRevision === workspace.revision && cached.peek === peek)
101
+ return cached.plan;
102
+ const source = peek ? { ...workspace, changes: [], structuralChanges: [] } : workspace;
103
+ const compiled = compileDraftProjection(source);
104
+ const plan = { ...compiled, draftId: workspace.draftId, draftRevision: workspace.revision, css: compiled.managedStyles.css, revision: ++revision };
105
+ draftPlans.set(workspace.draftId, { peek, plan });
106
+ return plan;
107
+ }
108
+ export function getCanonicalFrameProjection(cardId, expectedRevision) {
109
+ const state = frameProjectionStates.get(cardId);
110
+ const plan = state?.canonicalProjection;
111
+ const draftId = draftIdForCard(cardId);
112
+ const draft = draftId ? getDraftChanges(draftId) : null;
113
+ if (!state || !previewDocuments.has(cardId) || state.iframe.contentDocument !== state.document
114
+ || !plan || plan.revision !== expectedRevision || state.sentRevision !== expectedRevision
115
+ || (draft && (draft.draftId !== plan.draftId || draft.revision !== plan.draftRevision)))
116
+ return null;
117
+ return plan;
118
+ }
119
+ export function restoreCanonicalFrameProjection(doc) {
120
+ const id = getCanvasCardIdForDocument(doc);
121
+ const card = getCanvasCards().find((card) => card.id === id);
122
+ const frame = id ? frameRegistry.get(id) : undefined;
123
+ if (card && frame)
124
+ sendProjectionToCard(card, frame);
125
+ }
126
+ export function getCanvasCardIdForDocument(doc) {
127
+ for (const [id, state] of frameProjectionStates)
128
+ if (state.document === doc && state.iframe.contentDocument === doc)
129
+ return id;
130
+ return null;
131
+ }
87
132
  export function resetProjectionRevision() {
88
133
  revision = 0;
89
134
  lastRulesKey = null;
90
135
  lastPeekActive = false;
136
+ draftPlans.clear();
91
137
  for (const state of frameProjectionStates.values()) {
92
138
  state.sentRevision = -1;
93
139
  state.appliedRevision = -1;
94
140
  state.canonicalSentRevision = -1;
141
+ state.canonicalProjection = null;
95
142
  }
96
143
  projectionAcknowledgementVersion += 1;
97
144
  notifyProjectionAcknowledgementListeners();
@@ -100,7 +147,15 @@ export function sendProjectionToCard(card, iframe) {
100
147
  const win = iframe.contentWindow;
101
148
  if (!win)
102
149
  return;
103
- const { css, revision: rev, instanceOverrides, structuralChanges, textContentChanges, componentOverrides } = computeProjection();
150
+ let plan = computeProjectionForCard(card.id);
151
+ const state = frameProjectionStates.get(card.id);
152
+ if (state && plan.revision < state.sentRevision) {
153
+ plan = { ...plan, revision: ++revision };
154
+ draftPlans.set(plan.draftId, { peek: isOriginalPreviewActive(), plan });
155
+ }
156
+ if (state)
157
+ state.canonicalProjection = plan;
158
+ const { css, revision: rev, instanceOverrides, structuralChanges, textContentChanges, componentOverrides } = plan;
104
159
  sendProjectionMessage(card.id, iframe, rev, css, instanceOverrides, structuralChanges, textContentChanges, componentOverrides, true);
105
160
  }
106
161
  function sendProjectionMessage(cardId, iframe, rev, css, instanceOverrides, structuralChanges, textContentChanges, componentOverrides, canonical = false) {
@@ -124,12 +179,12 @@ function sendProjectionMessage(cardId, iframe, rev, css, instanceOverrides, stru
124
179
  win.postMessage(msg, window.location.origin);
125
180
  }
126
181
  /** Temporarily projects a snapshot through the renderer that owns the document. */
127
- export async function projectWorkspaceSnapshotToDocument(doc, snapshot) {
182
+ export async function projectDraftToDocument(doc, snapshot) {
128
183
  const entry = [...frameProjectionStates.entries()].find(([, state]) => state.document === doc);
129
184
  if (!entry)
130
185
  return null;
131
186
  const [cardId, state] = entry;
132
- const plan = compileWorkspaceProjection({ ...snapshot, revision: 0 });
187
+ const plan = compileDraftProjection({ ...snapshot, revision: 0 });
133
188
  revision += 1;
134
189
  sendProjectionMessage(cardId, state.iframe, revision, plan.managedStyles.css, plan.instanceOverrides, plan.structuralChanges, plan.textContentChanges, plan.componentOverrides);
135
190
  if (state.appliedRevision === revision && state.sentRevision === revision)
@@ -203,6 +258,8 @@ export function registerCardFrame(cardId, iframe) {
203
258
  const existing = frameProjectionStates.get(cardId);
204
259
  if (!existing || existing.iframe !== iframe || existing.document !== frameDocument) {
205
260
  if (existing) {
261
+ if (existing.canonicalProjection)
262
+ draftPlans.delete(existing.canonicalProjection.draftId);
206
263
  invalidatePreviewDocumentSession(existing.previewDocument.logicalDocument, existing.previewDocument.sessionId);
207
264
  previewDocuments.delete(cardId);
208
265
  }
@@ -215,6 +272,7 @@ export function registerCardFrame(cardId, iframe) {
215
272
  sentRevision: -1,
216
273
  appliedRevision: -1,
217
274
  canonicalSentRevision: -1,
275
+ canonicalProjection: null,
218
276
  });
219
277
  }
220
278
  else {
@@ -318,8 +376,6 @@ function notifyFrameRegistryListeners() {
318
376
  export function projectToAllReadyCards() {
319
377
  if (getCanvasMode() !== "canvas")
320
378
  return;
321
- const { css, revision: rev, instanceOverrides, structuralChanges, textContentChanges, componentOverrides } = computeProjection();
322
- for (const [cardId, iframe] of frameRegistry) {
323
- sendProjectionMessage(cardId, iframe, rev, css, instanceOverrides, structuralChanges, textContentChanges, componentOverrides, true);
324
- }
379
+ for (const [id, iframe] of frameRegistry)
380
+ sendProjectionToCard({ id }, iframe);
325
381
  }
@@ -1,10 +1,12 @@
1
1
  import { PROTOCOL_VERSION, clearRendererIdentity, getRendererIdentity, isRendererMessageFor, sendToParent, setRendererIdentity, } from "./frameProtocol.js";
2
+ import { wheelPanDelta } from "./wheelPan.js";
2
3
  import { handleReplaceStyles, startRendererProjectionDiagnostics } from "./rendererStylesheet.js";
3
4
  import { findClosestAnchor, isEligibleNavigation, hasDifferentRoute } from "./linkEligibility.js";
4
5
  import { installRendererElementSelector } from "./rendererElementSelector.js";
5
6
  import { createFrameThrottle } from "../overlay/frameThrottle.js";
6
7
  import { isNudgeUiDev } from "../runtime/devFlag.js";
7
8
  import { getNudgeUiRuntimeConfig, subscribeNudgeUiRuntime } from "../runtime/runtimeConfig.js";
9
+ import { APP_INTERACTION_KEY } from "./keyboardShortcuts.js";
8
10
  let activeRendererBootstrap = null;
9
11
  function ownsRendererBootstrap(owner) {
10
12
  return owner.active && activeRendererBootstrap?.owner === owner;
@@ -252,6 +254,8 @@ function installRendererPanProxy(owner) {
252
254
  let spaceHeld = false;
253
255
  let panning = false;
254
256
  let boardGesturesEnabled = false;
257
+ let boardScrollPans = false;
258
+ let nativeAppInteraction = false;
255
259
  const panMoveUpdate = createFrameThrottle((point) => {
256
260
  if (!ownsRendererBootstrap(owner))
257
261
  return;
@@ -298,7 +302,9 @@ function installRendererPanProxy(owner) {
298
302
  }
299
303
  if (event.data.type === "board-gesture-state") {
300
304
  boardGesturesEnabled = event.data.enabled;
301
- if (!boardGesturesEnabled) {
305
+ boardScrollPans = event.data.panScroll === true;
306
+ nativeAppInteraction = event.data.panScroll === false;
307
+ if (!boardGesturesEnabled || nativeAppInteraction) {
302
308
  spaceHeld = false;
303
309
  endPan();
304
310
  }
@@ -306,10 +312,18 @@ function installRendererPanProxy(owner) {
306
312
  };
307
313
  window.addEventListener("message", onMessage);
308
314
  owner.listenerRemovers.push(() => window.removeEventListener("message", onMessage));
315
+ function sendAppInteractionModifier(held) {
316
+ const identity = getRendererIdentity();
317
+ if (!identity)
318
+ return;
319
+ sendToParent({ type: "app-interaction-modifier", protocolVersion: PROTOCOL_VERSION, held, ...identity });
320
+ }
309
321
  const onKeyDown = (event) => {
310
322
  if (!ownsRendererBootstrap(owner))
311
323
  return;
312
- if (boardGesturesEnabled && event.code === "Space" && !event.repeat && !isEditableTarget(event.target)) {
324
+ if (event.code === APP_INTERACTION_KEY && !event.repeat && !isEditableTarget(event.target))
325
+ sendAppInteractionModifier(true);
326
+ if (boardGesturesEnabled && !nativeAppInteraction && event.code === "Space" && !event.repeat && !isEditableTarget(event.target)) {
313
327
  spaceHeld = true;
314
328
  if (getRendererIdentity())
315
329
  event.preventDefault();
@@ -321,6 +335,8 @@ function installRendererPanProxy(owner) {
321
335
  const onKeyUp = (event) => {
322
336
  if (!ownsRendererBootstrap(owner))
323
337
  return;
338
+ if (event.code === APP_INTERACTION_KEY)
339
+ sendAppInteractionModifier(false);
324
340
  if (event.code === "Space") {
325
341
  spaceHeld = false;
326
342
  sendSpaceState();
@@ -355,7 +371,7 @@ function installRendererPanProxy(owner) {
355
371
  const onPointerDown = (event) => {
356
372
  if (!ownsRendererBootstrap(owner))
357
373
  return;
358
- if (!boardGesturesEnabled || !spaceHeld || event.button !== 0 || isEditableTarget(event.target))
374
+ if (!boardGesturesEnabled || nativeAppInteraction || !spaceHeld || event.button !== 0 || isEditableTarget(event.target))
359
375
  return;
360
376
  const identity = getRendererIdentity();
361
377
  if (!identity)
@@ -392,6 +408,8 @@ function installRendererPanProxy(owner) {
392
408
  const onBlur = () => {
393
409
  if (!ownsRendererBootstrap(owner))
394
410
  return;
411
+ if (!window.parent.document.hasFocus())
412
+ sendAppInteractionModifier(false);
395
413
  spaceHeld = false;
396
414
  sendSpaceState();
397
415
  endPan();
@@ -401,13 +419,23 @@ function installRendererPanProxy(owner) {
401
419
  const onWheel = (event) => {
402
420
  if (!ownsRendererBootstrap(owner))
403
421
  return;
404
- if (!boardGesturesEnabled || (!event.ctrlKey && !event.metaKey))
422
+ const zoom = event.ctrlKey || event.metaKey;
423
+ if (!boardGesturesEnabled || (!zoom && !boardScrollPans))
405
424
  return;
406
425
  const identity = getRendererIdentity();
407
426
  if (!identity)
408
427
  return;
409
428
  event.preventDefault();
410
429
  event.stopPropagation();
430
+ if (!zoom) {
431
+ const delta = wheelPanDelta(event, { width: window.innerWidth, height: window.innerHeight });
432
+ const message = {
433
+ type: "wheel-pan", protocolVersion: PROTOCOL_VERSION,
434
+ deltaX: delta.x, deltaY: delta.y, ...identity,
435
+ };
436
+ sendToParent(message);
437
+ return;
438
+ }
411
439
  const message = {
412
440
  type: "zoom",
413
441
  protocolVersion: PROTOCOL_VERSION,