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
@@ -0,0 +1,165 @@
1
+ import { createComponentPropChange } from "../componentSemantics/changeModel.js";
2
+ import { captureTextProjectionTarget, textProjectionSelector } from "../projection/textProjection.js";
3
+ export function initialTextEditDecision(candidate) {
4
+ return {
5
+ bindingIndex: candidate.bindingChoices?.length === 1 ? 0 : null,
6
+ scope: candidate.scope ?? (candidate.binding.kind === "rendered-text" ? "rendered-instance" : "source-site"),
7
+ };
8
+ }
9
+ export function chooseTextEditBinding(candidate, index) {
10
+ const choice = candidate.bindingChoices?.[index];
11
+ if (!choice)
12
+ return null;
13
+ return {
14
+ bindingIndex: index,
15
+ scope: choice.mountedCount > 1 ? "rendered-instance" : "source-site",
16
+ };
17
+ }
18
+ function selectedBinding(candidate, decision) {
19
+ return decision.bindingIndex === null ? null : candidate.bindingChoices?.[decision.bindingIndex] ?? null;
20
+ }
21
+ export function getTextEditScopeChoices(candidate, decision) {
22
+ const choice = selectedBinding(candidate, decision);
23
+ if (!choice)
24
+ return candidate.scopeChoices ?? [];
25
+ return choice.mountedCount > 1 && choice.authoredAs === "literal"
26
+ ? ["rendered-instance", "source-site"]
27
+ : [];
28
+ }
29
+ export function getTextEditBinding(candidate, decision, capturedTarget) {
30
+ const choice = selectedBinding(candidate, decision);
31
+ if (!choice)
32
+ return candidate.binding;
33
+ if (choice.mountedCount > 1 && choice.authoredAs !== "literal") {
34
+ const target = choice.renderedTarget ?? candidate.renderedTarget ?? capturedTarget;
35
+ return target ? { kind: "rendered-text", target } : choice.binding;
36
+ }
37
+ return choice.binding;
38
+ }
39
+ function sourceForChoice(candidate, choice) {
40
+ if (choice?.renderedSource)
41
+ return choice.renderedSource;
42
+ if (candidate.renderedSource
43
+ && (!choice || candidate.renderedSource.evidence?.callsiteId === choice.binding.target.callsiteId
44
+ && candidate.renderedSource.evidence.property === choice.binding.property)) {
45
+ return candidate.renderedSource;
46
+ }
47
+ const parsed = /^(.*):(\d+):(\d+)$/.exec(candidate.element.getAttribute("data-src") ?? "");
48
+ const target = choice?.binding.target;
49
+ const authoredAs = choice?.authoredAs === "literal"
50
+ ? "literal"
51
+ : choice?.authoredAs === "expression" || choice?.authoredAs === "spread"
52
+ ? "expression"
53
+ : "unknown";
54
+ return {
55
+ file: target?.file ?? parsed?.[1] ?? candidate.element.getAttribute("data-src") ?? "",
56
+ line: target?.line ?? Number(parsed?.[2] ?? 0),
57
+ column: target?.column ?? Number(parsed?.[3] ?? 0),
58
+ component: target?.componentName ?? candidate.element.getAttribute("data-cid") ?? "Rendered text",
59
+ selector: candidate.binding.kind === "rendered-text"
60
+ ? textProjectionSelector(candidate.binding.target) ?? ""
61
+ : "",
62
+ authoredAs,
63
+ evidence: choice ? {
64
+ callsiteId: choice.binding.target.callsiteId,
65
+ componentName: choice.binding.target.componentName,
66
+ property: choice.binding.property,
67
+ mountedCount: choice.mountedCount,
68
+ } : undefined,
69
+ };
70
+ }
71
+ function componentEvidence(candidate, choice) {
72
+ const target = choice.renderedTarget
73
+ ?? candidate.renderedTarget
74
+ ?? captureTextProjectionTarget(candidate.element, candidate.before, candidate.textNode);
75
+ return {
76
+ occurrence: target?.occurrence ?? 0,
77
+ props: target?.props ?? candidate.element.getAttribute("data-cprops"),
78
+ ariaLabel: target?.ariaLabel ?? candidate.element.getAttribute("aria-label"),
79
+ beforeText: candidate.before,
80
+ mountedCount: choice.mountedCount,
81
+ };
82
+ }
83
+ function textTargetFor(candidate, binding, choice) {
84
+ if (choice?.renderedTarget)
85
+ return choice.renderedTarget;
86
+ if (candidate.renderedTarget)
87
+ return candidate.renderedTarget;
88
+ if (binding.kind === "rendered-text")
89
+ return binding.target;
90
+ return captureTextProjectionTarget(candidate.element, candidate.before, candidate.textNode);
91
+ }
92
+ function choiceForCommit(candidate, selectedChoice) {
93
+ const choice = selectedChoice ?? candidate.bindingChoices?.[0];
94
+ const target = candidate.editableTarget;
95
+ if (choice || !target || candidate.mountedCount === undefined)
96
+ return choice ?? null;
97
+ const property = candidate.binding.kind === "component-prop"
98
+ ? candidate.binding.property
99
+ : "children";
100
+ return {
101
+ binding: candidate.binding.kind === "component-prop"
102
+ ? candidate.binding
103
+ : {
104
+ kind: "component-prop",
105
+ property,
106
+ target: {
107
+ framework: target.framework,
108
+ componentId: target.contract.componentId,
109
+ callsiteId: target.meta.callsiteId,
110
+ componentName: target.meta.componentName,
111
+ file: target.meta.file,
112
+ line: target.meta.line,
113
+ column: target.meta.column,
114
+ },
115
+ },
116
+ editableTarget: target,
117
+ mountedCount: candidate.mountedCount,
118
+ authoredAs: target.meta.authoredProps[property] ?? "default",
119
+ renderedTarget: candidate.renderedTarget ?? null,
120
+ renderedSource: candidate.renderedSource,
121
+ };
122
+ }
123
+ /** Builds a canonical record without changing the DOM, appending history, or ending a draft. */
124
+ export function prepareTextEditChange(candidate, decision, capturedTarget, after) {
125
+ if ((candidate.bindingChoices?.length ?? 0) > 1 && decision.bindingIndex === null)
126
+ return null;
127
+ const binding = getTextEditBinding(candidate, decision, capturedTarget);
128
+ const choice = choiceForCommit(candidate, selectedBinding(candidate, decision));
129
+ if (binding.kind === "component-prop" && decision.scope === "source-site") {
130
+ const editableTarget = choice?.editableTarget ?? candidate.editableTarget;
131
+ const prop = editableTarget?.contract.props.find((item) => item.name === binding.property && item.control === "text");
132
+ if (!editableTarget || !prop)
133
+ return null;
134
+ return createComponentPropChange(editableTarget, prop, after, {
135
+ scope: "source-site",
136
+ evidence: choice && choice.mountedCount > 1
137
+ ? componentEvidence(candidate, choice)
138
+ : undefined,
139
+ });
140
+ }
141
+ const textTarget = textTargetFor(candidate, binding, choice);
142
+ const source = sourceForChoice(candidate, choice);
143
+ const renderedSource = source.selector || !textTarget
144
+ ? source
145
+ : { ...source, selector: textProjectionSelector(textTarget) ?? "" };
146
+ if (!textTarget || !renderedSource.selector)
147
+ return null;
148
+ return {
149
+ kind: "text-content",
150
+ id: `text-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`,
151
+ target: textTarget,
152
+ source: {
153
+ file: renderedSource.file,
154
+ line: renderedSource.line,
155
+ column: renderedSource.column,
156
+ component: renderedSource.component,
157
+ },
158
+ selector: renderedSource.selector,
159
+ before: candidate.before,
160
+ after,
161
+ authoredAs: renderedSource.authoredAs,
162
+ scope: decision.scope,
163
+ evidence: renderedSource.evidence,
164
+ };
165
+ }
@@ -19,6 +19,7 @@ export interface SelectionProperty {
19
19
  value: SelectionValue;
20
20
  token: SelectionToken;
21
21
  primaryRow: ResolvedProperty | null;
22
+ rows: readonly (ResolvedProperty | null)[];
22
23
  }
23
24
  /**
24
25
  * Projects browser values and authored inspection rows into group-safe facts.
@@ -22,6 +22,7 @@ export function projectSelectionProperty(property, rows, values, primaryIndex) {
22
22
  value: common ? { kind: "common", value: first } : { kind: "mixed" },
23
23
  token: tokenFor(rows),
24
24
  primaryRow: rows[primaryIndex] ?? null,
25
+ rows,
25
26
  };
26
27
  }
27
28
  /** Returns the token entries available to every inspected target. */
@@ -0,0 +1,10 @@
1
+ import { type ReactElement } from "react";
2
+ import type { SelectedElement } from "../selection/selectionStore.js";
3
+ import type { Rect } from "./overlayGeometry.js";
4
+ export interface DomNavigatorProps {
5
+ selected: SelectedElement;
6
+ hierarchy: HTMLElement[];
7
+ anchor: Rect;
8
+ project: (element: HTMLElement) => Rect | null;
9
+ }
10
+ export declare function DomNavigator({ selected, hierarchy, anchor, project }: DomNavigatorProps): ReactElement | null;
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { setSelectedElement } from "../selection/selectionStore.js";
4
+ import { computeNavigationNodes } from "../selection/hierarchy.js";
5
+ import { resolveSelectionFromElement } from "../selection/resolveSelection.js";
6
+ function nodeLabel(node) {
7
+ return node.getAttribute("data-cid") ?? node.localName;
8
+ }
9
+ function nodeDescription(node) {
10
+ const id = node.id ? `#${node.id}` : "";
11
+ const className = typeof node.className === "string"
12
+ ? node.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((name) => `.${name}`).join("")
13
+ : "";
14
+ const source = node.getAttribute("data-src");
15
+ return `${node.localName}${id}${className}${source ? ` — ${source}` : ""}`;
16
+ }
17
+ function selectNode(node) {
18
+ const resolved = resolveSelectionFromElement(node);
19
+ if (resolved)
20
+ setSelectedElement(resolved);
21
+ }
22
+ export function DomNavigator({ selected, hierarchy, anchor, project }) {
23
+ const nodes = computeNavigationNodes(selected.domElement, hierarchy);
24
+ const [expanded, setExpanded] = useState(false);
25
+ const [hovered, setHovered] = useState(null);
26
+ if (nodes.length === 0)
27
+ return null;
28
+ const preview = hovered ? project(hovered) : null;
29
+ const close = () => {
30
+ setExpanded(false);
31
+ setHovered(null);
32
+ };
33
+ return (_jsxs(_Fragment, { children: [preview ? _jsx("div", { className: "dom-navigator-preview", "data-test": "dom-navigator-preview", style: preview, "aria-hidden": "true" }) : null, _jsxs("div", { className: "dom-navigator", "data-test": "dom-navigator", "aria-label": "DOM navigator", style: { left: Math.max(4, anchor.left), top: Math.max(4, anchor.top) }, onMouseEnter: () => setExpanded(true), onMouseLeave: close, onFocus: () => setExpanded(true), onBlur: (event) => {
34
+ if (!(event.relatedTarget instanceof Node) || !event.currentTarget.contains(event.relatedTarget))
35
+ close();
36
+ }, children: [_jsxs("button", { type: "button", className: "dom-navigator__trigger", "data-test": "dom-navigator-trigger", "aria-expanded": expanded, "aria-haspopup": "menu", title: nodeDescription(selected.domElement), onMouseEnter: () => setHovered(null), children: [_jsx("span", { children: nodeLabel(selected.domElement) }), _jsx("span", { "aria-hidden": "true", children: "\u2304" })] }), expanded ? (_jsx("div", { className: "dom-navigator__menu", role: "menu", children: nodes.map((node) => (_jsxs("button", { type: "button", className: "dom-navigator__item", "data-test": "dom-navigator-item", "data-direction": node.direction, "data-depth": node.depth, "data-cid": node.element.getAttribute("data-cid") ?? undefined, title: nodeDescription(node.element), role: "menuitem", onMouseEnter: () => setHovered(node.element), onClick: () => {
37
+ selectNode(node.element);
38
+ close();
39
+ }, children: [_jsx("span", { "aria-hidden": "true", children: node.direction === "up" ? "↑" : "↓" }), _jsx("span", { children: nodeLabel(node.element) })] }, `${node.direction}-${node.depth}-${node.element.getAttribute("data-cid") ?? node.element.localName}`))) })) : null] })] }));
40
+ }
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { useInspectorOpen } from "../shell/openStore.js";
4
- import { getSelectedElements, useSelectedElement, useSelectedElements } from "../selection/selectionStore.js";
4
+ import { getSelectedElements, useHierarchy, useSelectedElement, useSelectedElements } from "../selection/selectionStore.js";
5
5
  import { installElementSelector } from "./elementSelector.js";
6
6
  import { getMarginFills, getMarginGuides, readBorderWidths, readMargins, toRect, } from "./overlayGeometry.js";
7
7
  import { observeSelectedGeometry } from "./selectedGeometry.js";
@@ -17,6 +17,8 @@ import { MeasurementGuideOverlay } from "./MeasurementGuideOverlay.js";
17
17
  import { resolveSelectionTarget, selectionTargetMode } from "../selection/selectionTarget.js";
18
18
  import { isInlineTextEditingActive, useInlineTextSession } from "../inline-text/inlineTextEditor.js";
19
19
  import { EMPTY_TEXT_PROJECTION_ATTR } from "../projection/textProjection.js";
20
+ import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
21
+ import { DomNavigator } from "./DomNavigator.js";
20
22
  export { getMarginFills, getMarginGuides, } from "./overlayGeometry.js";
21
23
  function overlayStyle(rect) {
22
24
  return {
@@ -32,6 +34,8 @@ export function InspectorOverlay({ host }) {
32
34
  const open = useInspectorOpen();
33
35
  const selected = useSelectedElement();
34
36
  const selectedElements = useSelectedElements();
37
+ const hierarchy = useHierarchy();
38
+ const domNavigationEnabled = useNudgeUiRuntimeConfig().capabilities.domNavigation === true;
35
39
  const inlineTextSession = useInlineTextSession();
36
40
  const [hoverRect, setHoverRect] = useState(null);
37
41
  const [hoverMargins, setHoverMargins] = useState(null);
@@ -322,7 +326,7 @@ export function InspectorOverlay({ host }) {
322
326
  width: guide.width,
323
327
  height: guide.height,
324
328
  pointerEvents: "none",
325
- }, "aria-hidden": "true" }, guide.side)))] })) : null, open && !inlineTextSession ? selectedGeometry.map(({ element, rect }, index) => (_jsx("div", { className: "selected-outline", "data-test": "selected-outline", "data-selected-cid": element.cid, "data-selected-index": index, "data-selected-src": element.src, style: overlayStyle(rect), "aria-hidden": "true" }, `${element.domElement.getAttribute("data-cid") ?? "element"}-${index}`))) : null, open && selectedElements.length === 1 ? _jsx(DropGuideOverlay, { guide: dropGuide?.document === document
329
+ }, "aria-hidden": "true" }, guide.side)))] })) : null, open && !inlineTextSession ? selectedGeometry.map(({ element, rect }, index) => (_jsx("div", { className: "selected-outline", "data-test": "selected-outline", "data-selected-cid": element.cid, "data-selected-index": index, "data-selected-src": element.src, style: overlayStyle(rect), "aria-hidden": "true" }, `${element.domElement.getAttribute("data-cid") ?? "element"}-${index}`))) : null, open && domNavigationEnabled && !inlineTextSession && selectedElements.length === 1 && selected && selectedRect ? (_jsx(DomNavigator, { selected: selected, hierarchy: hierarchy, anchor: selectedRect, project: (element) => toRect(element.getBoundingClientRect()) })) : null, open && selectedElements.length === 1 ? _jsx(DropGuideOverlay, { guide: dropGuide?.document === document
326
330
  ? { orientation: dropGuide.orientation, line: dropGuide.line, target: dropGuide.target }
327
331
  : null, lineClassName: "dom-drop-line", lineTestId: "dom-drop-line", targetClassName: "dom-drop-target", targetTestId: "dom-drop-target" }) : null] }));
328
332
  }
@@ -1,7 +1,23 @@
1
1
  export type ClickModifiers = Pick<MouseEvent, "ctrlKey" | "metaKey" | "shiftKey">;
2
+ /**
3
+ * Press and release gestures that application widgets use to open menus,
4
+ * dialogs, and popovers before any `click` fires. The Design tool keeps them
5
+ * from reaching the application.
6
+ */
7
+ export declare const APPLICATION_GESTURE_EVENTS: readonly ["pointerdown", "pointerup", "mousedown", "mouseup", "touchstart", "touchend", "auxclick", "contextmenu"];
2
8
  /** Command/Ctrl-click selects the deepest tracked element unless Shift is held. */
3
9
  export declare function isDeepSelectionClick(event: ClickModifiers): boolean;
4
- /** Command/Ctrl+Shift-click intentionally lets the application receive its click. */
5
- export declare function isApplicationActivationClick(event: ClickModifiers): boolean;
10
+ /**
11
+ * Only input from a person becomes inspector selection. Script events such
12
+ * as `element.click()` keep reaching the application.
13
+ */
14
+ export declare function isUserClick(event: Pick<Event, "isTrusted">): boolean;
15
+ /** The active inline text editor needs native press events for caret placement. */
16
+ export declare function isInlineEditorTarget(target: EventTarget | null): boolean;
17
+ /**
18
+ * Keeps an application press gesture from reaching application listeners.
19
+ * Listeners on the document itself, including the inspector's own, still run.
20
+ */
21
+ export declare function blockApplicationGesture(event: Event): void;
6
22
  /** Prevent an inspected application click from reaching the host application. */
7
23
  export declare function blockApplicationClick(event: MouseEvent): void;
@@ -1,10 +1,41 @@
1
+ /**
2
+ * Press and release gestures that application widgets use to open menus,
3
+ * dialogs, and popovers before any `click` fires. The Design tool keeps them
4
+ * from reaching the application.
5
+ */
6
+ export const APPLICATION_GESTURE_EVENTS = [
7
+ "pointerdown",
8
+ "pointerup",
9
+ "mousedown",
10
+ "mouseup",
11
+ "touchstart",
12
+ "touchend",
13
+ "auxclick",
14
+ "contextmenu",
15
+ ];
1
16
  /** Command/Ctrl-click selects the deepest tracked element unless Shift is held. */
2
17
  export function isDeepSelectionClick(event) {
3
18
  return (event.ctrlKey || event.metaKey) && !event.shiftKey;
4
19
  }
5
- /** Command/Ctrl+Shift-click intentionally lets the application receive its click. */
6
- export function isApplicationActivationClick(event) {
7
- return (event.ctrlKey || event.metaKey) && event.shiftKey;
20
+ /**
21
+ * Only input from a person becomes inspector selection. Script events such
22
+ * as `element.click()` keep reaching the application.
23
+ */
24
+ export function isUserClick(event) {
25
+ return event.isTrusted;
26
+ }
27
+ /** The active inline text editor needs native press events for caret placement. */
28
+ export function isInlineEditorTarget(target) {
29
+ return typeof Element !== "undefined"
30
+ && target instanceof Element
31
+ && target.closest('[data-inline-editor="true"]') !== null;
32
+ }
33
+ /**
34
+ * Keeps an application press gesture from reaching application listeners.
35
+ * Listeners on the document itself, including the inspector's own, still run.
36
+ */
37
+ export function blockApplicationGesture(event) {
38
+ event.stopPropagation();
8
39
  }
9
40
  /** Prevent an inspected application click from reaching the host application. */
10
41
  export function blockApplicationClick(event) {
@@ -1 +1 @@
1
- export declare function installElementSelector(inspectorHost: HTMLElement): () => void;
1
+ export declare function installElementSelector(inspectorHost: HTMLElement, acceptsClick?: (event: Event) => boolean): () => void;
@@ -3,8 +3,8 @@ import { getOpen } from "../shell/openStore.js";
3
3
  import { resolveSelectionFromEvent } from "../selection/resolveSelection.js";
4
4
  import { handleInlineTextEditIntent, isInlineTextEditingActive, } from "../inline-text/inlineTextEditor.js";
5
5
  import { EMPTY_TEXT_PROJECTION_ATTR } from "../projection/textProjection.js";
6
- import { blockApplicationClick, isApplicationActivationClick } from "./clickPolicy.js";
7
- export function installElementSelector(inspectorHost) {
6
+ import { APPLICATION_GESTURE_EVENTS, blockApplicationClick, blockApplicationGesture, isInlineEditorTarget, isUserClick, } from "./clickPolicy.js";
7
+ export function installElementSelector(inspectorHost, acceptsClick = isUserClick) {
8
8
  function applyInlineTextDisposition(event, disposition) {
9
9
  if (disposition === "pass-through")
10
10
  return;
@@ -43,7 +43,7 @@ export function installElementSelector(inspectorHost) {
43
43
  return root instanceof ShadowRoot && root.host === inspectorHost;
44
44
  }
45
45
  function onClick(e) {
46
- if (!getOpen())
46
+ if (!getOpen() || !acceptsClick(e))
47
47
  return;
48
48
  if (isInsideInspectorUi(e)) {
49
49
  // The selector guards the inspected application only. Clicks on the
@@ -61,13 +61,10 @@ export function installElementSelector(inspectorHost) {
61
61
  blockApplicationClick(e);
62
62
  return;
63
63
  }
64
- if (isApplicationActivationClick(e))
65
- return;
66
64
  // The inspector is an editing surface while open. Capture every ordinary
67
65
  // application click so buttons, links, and untracked controls cannot run
68
66
  // alongside selection. Shift-click toggles the primary target in the
69
- // ordered group; Command/Ctrl-click still selects the deepest tracked
70
- // element, and Command/Ctrl+Shift-click remains the activation escape hatch.
67
+ // ordered group; Command/Ctrl-click selects the deepest tracked element.
71
68
  const sel = resolveSelectionFromEvent(e, inspectorHost);
72
69
  blockApplicationClick(e);
73
70
  if (sel) {
@@ -89,12 +86,26 @@ export function installElementSelector(inspectorHost) {
89
86
  clickCount: e.detail,
90
87
  }));
91
88
  }
89
+ function onApplicationGesture(e) {
90
+ if (!getOpen() || !acceptsClick(e) || isInlineEditorTarget(e.target))
91
+ return;
92
+ if (e.target instanceof Element
93
+ && (e.target === inspectorHost || inspectorHost.contains(e.target)))
94
+ return;
95
+ blockApplicationGesture(e);
96
+ }
92
97
  document.addEventListener("mousedown", onMouseDown, true);
93
98
  document.addEventListener("dblclick", onDoubleClick, true);
94
99
  document.addEventListener("click", onClick, true);
100
+ for (const type of APPLICATION_GESTURE_EVENTS) {
101
+ document.addEventListener(type, onApplicationGesture, true);
102
+ }
95
103
  return () => {
96
104
  document.removeEventListener("mousedown", onMouseDown, true);
97
105
  document.removeEventListener("dblclick", onDoubleClick, true);
98
106
  document.removeEventListener("click", onClick, true);
107
+ for (const type of APPLICATION_GESTURE_EVENTS) {
108
+ document.removeEventListener(type, onApplicationGesture, true);
109
+ }
99
110
  };
100
111
  }
@@ -20,6 +20,24 @@ export function observeSelectedGeometry(element, onChange) {
20
20
  onChange();
21
21
  });
22
22
  }
23
+ let scrollTargets = [];
24
+ function unbindScrollTargets() {
25
+ for (const target of scrollTargets)
26
+ target.removeEventListener("scroll", schedule, true);
27
+ scrollTargets = [];
28
+ }
29
+ function bindScrollTargets() {
30
+ unbindScrollTargets();
31
+ const targets = [view, view.document];
32
+ let ancestor = element.parentElement;
33
+ while (ancestor) {
34
+ targets.push(ancestor);
35
+ ancestor = ancestor.parentElement;
36
+ }
37
+ scrollTargets = [...new Set(targets)];
38
+ for (const target of scrollTargets)
39
+ target.addEventListener("scroll", schedule, true);
40
+ }
23
41
  const resizeObserver = ResizeObserverCtor
24
42
  ? new ResizeObserverCtor(schedule)
25
43
  : null;
@@ -32,6 +50,7 @@ export function observeSelectedGeometry(element, onChange) {
32
50
  mutationObserver?.disconnect();
33
51
  if (observedParent)
34
52
  mutationObserver?.observe(observedParent, { childList: true });
53
+ bindScrollTargets();
35
54
  }
36
55
  schedule();
37
56
  }) : null;
@@ -39,13 +58,13 @@ export function observeSelectedGeometry(element, onChange) {
39
58
  if (observedParent)
40
59
  mutationObserver?.observe(observedParent, { childList: true });
41
60
  view.addEventListener("resize", schedule);
42
- view.addEventListener("scroll", schedule, true);
61
+ bindScrollTargets();
43
62
  return () => {
44
63
  if (frame !== null)
45
64
  view.cancelAnimationFrame(frame);
46
65
  resizeObserver?.disconnect();
47
66
  mutationObserver?.disconnect();
48
67
  view.removeEventListener("resize", schedule);
49
- view.removeEventListener("scroll", schedule, true);
68
+ unbindScrollTargets();
50
69
  };
51
70
  }
@@ -0,0 +1,45 @@
1
+ import type { Rect } from "./overlayGeometry.js";
2
+ export type PaddingSide = "top" | "right" | "bottom" | "left";
3
+ export type SpacingKind = "padding" | "gap";
4
+ export type SpacingAxis = "horizontal" | "vertical";
5
+ export type SpacingDragAxis = "x" | "y";
6
+ export type SpacingProperty = `padding-${PaddingSide}` | "row-gap" | "column-gap";
7
+ export interface SpacingGuideData {
8
+ kind: SpacingKind;
9
+ property: SpacingProperty;
10
+ side: PaddingSide | null;
11
+ axis: SpacingAxis;
12
+ dragAxis: SpacingDragAxis;
13
+ value: number;
14
+ direction: 1 | -1;
15
+ cursor: "ew-resize" | "ns-resize";
16
+ guide: Rect;
17
+ affectedGuides: Rect[];
18
+ affectedAreas: Rect[];
19
+ /** A 16px-thick pointer target centred on the visible, up-to-40px guide handle. */
20
+ hit: Rect;
21
+ }
22
+ export interface SpacingAffordance extends SpacingGuideData {
23
+ element: HTMLElement;
24
+ }
25
+ export interface SpacingDescriptor {
26
+ kind: SpacingKind;
27
+ property: SpacingProperty;
28
+ side: PaddingSide | null;
29
+ }
30
+ export declare const SPACING_GUIDE_HANDLE_LENGTH = 40;
31
+ /** Finds the spacing target under a document-local pointer location. */
32
+ export declare function getSpacingAffordanceAtPoint(doc: Document, x: number, y: number): SpacingAffordance | null;
33
+ /** Re-resolves a renderer-reported spacing descriptor in its owner document. */
34
+ export declare function getSpacingAffordanceForDescriptor(element: HTMLElement, descriptor: SpacingDescriptor): SpacingAffordance | null;
35
+ /** Calculates a direct-manipulation value, optionally stepping changes by 8px. */
36
+ export declare function spacingValueForDrag(affordance: Pick<SpacingAffordance, "dragAxis" | "direction" | "value">, start: {
37
+ x: number;
38
+ y: number;
39
+ }, point: {
40
+ x: number;
41
+ y: number;
42
+ }, shiftKey?: boolean): number;
43
+ export declare function spacingValueCss(value: number): string;
44
+ /** Serializes the stable part of an affordance for renderer-to-controller messages. */
45
+ export declare function toSpacingDescriptor(affordance: SpacingAffordance): SpacingDescriptor;