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
@@ -2,9 +2,11 @@ import { useSyncExternalStore } from "react";
2
2
  import { appendChange } from "../changes/changesLog.js";
3
3
  import { registerInlineTextClearHandler } from "./inlineTextLifecycle.js";
4
4
  import { isNudgeUiDev } from "../runtime/devFlag.js";
5
- import { createComponentPropChange, } from "../componentSemantics/changeModel.js";
5
+ import { initialTextEditDecision, chooseTextEditBinding, getTextEditScopeChoices, getTextEditBinding, prepareTextEditChange, } from "./textEditChange.js";
6
6
  import { resolveTextBinding, } from "../componentSemantics/textBinding.js";
7
- import { captureTextProjectionTarget, EMPTY_TEXT_PROJECTION_ATTR, EMPTY_TEXT_PROJECTION_PATH_ATTR, getTextContentChangeById, resolveTextProjectionTarget, resolveTextProjectionTextNode, textProjectionSelector, } from "../projection/textProjection.js";
7
+ import { captureTextProjectionTarget, EMPTY_TEXT_PROJECTION_ATTR, EMPTY_TEXT_PROJECTION_PATH_ATTR, getTextContentChangeById, resolveTextProjectionTarget, resolveTextProjectionTextNode, } from "../projection/textProjection.js";
8
+ import { clearInlineTextDiagnostics, recordInlineTextDiagnostic, } from "./inlineTextDiagnostics.js";
9
+ export { clearInlineTextDiagnostics, getInlineTextDiagnostic, getInlineTextDiagnostics, subscribeInlineTextDiagnostics, useInlineTextDiagnostic, } from "./inlineTextDiagnostics.js";
8
10
  const listeners = new Set();
9
11
  let activeSession = null;
10
12
  let activeSessionDocument = null;
@@ -14,8 +16,6 @@ let pendingEditIntent = null;
14
16
  let handoffPointerArmed = false;
15
17
  let pendingReplayScheduled = false;
16
18
  let sessionRevision = 0;
17
- let inlineTextDiagnostics = [];
18
- const diagnosticListeners = new Set();
19
19
  function discardPendingInlineTextEdit(ownerDocument) {
20
20
  if (!ownerDocument || pendingEditIntent?.ownerDocument === ownerDocument) {
21
21
  pendingEditIntent = null;
@@ -31,23 +31,8 @@ function notify() {
31
31
  sessionRevision += 1;
32
32
  listeners.forEach((listener) => listener());
33
33
  }
34
- function notifyDiagnostics() {
35
- for (const listener of diagnosticListeners)
36
- listener();
37
- }
38
- function recordDiagnostic(diagnostic) {
39
- inlineTextDiagnostics = [...inlineTextDiagnostics.slice(-31), diagnostic];
40
- notifyDiagnostics();
41
- }
42
- export function getInlineTextDiagnostics() {
43
- return inlineTextDiagnostics;
44
- }
45
- export function getInlineTextDiagnostic() {
46
- return inlineTextDiagnostics[inlineTextDiagnostics.length - 1] ?? null;
47
- }
48
- export function subscribeInlineTextDiagnostics(listener) {
49
- diagnosticListeners.add(listener);
50
- return () => diagnosticListeners.delete(listener);
34
+ function recordDiagnostic(diagnostic, target) {
35
+ recordInlineTextDiagnostic(diagnostic, target?.ownerDocument, target);
51
36
  }
52
37
  export function getInlineTextSession() {
53
38
  return activeSession;
@@ -61,18 +46,27 @@ export function isInlineTextEditingActive() {
61
46
  }
62
47
  export function cancelInlineTextEdit() {
63
48
  discardPendingInlineTextEdit();
64
- activeSession?.cancel("cancel");
49
+ if (activeSession)
50
+ activeSession.cancel("cancel");
51
+ else
52
+ clearInlineTextDiagnostics();
65
53
  }
66
54
  /** End a session owned by a document/frame that is about to be disposed. */
67
55
  export function disposeInlineTextEdit(reason = "frame-disposed", ownerDocument) {
68
56
  if (!ownerDocument) {
69
57
  discardPendingInlineTextEdit();
70
- activeSession?.cancel(reason);
58
+ if (activeSession)
59
+ activeSession.cancel(reason);
60
+ else
61
+ clearInlineTextDiagnostics();
71
62
  return;
72
63
  }
73
64
  discardPendingInlineTextEdit(ownerDocument);
74
- if (activeSessionDocument === ownerDocument)
65
+ if (activeSessionDocument === ownerDocument) {
75
66
  activeSession?.cancel(reason);
67
+ return;
68
+ }
69
+ clearInlineTextDiagnostics(ownerDocument);
76
70
  }
77
71
  function ownerHTMLElement(element) {
78
72
  const OwnerHTMLElement = element.ownerDocument.defaultView?.HTMLElement;
@@ -83,9 +77,12 @@ function ownerHTMLElement(element) {
83
77
  : null;
84
78
  }
85
79
  function currentIntentTarget(intent) {
80
+ const pointed = intent.ownerDocument.elementFromPoint?.(intent.point.x, intent.point.y) ?? null;
81
+ if (pointed?.closest(`[${EMPTY_TEXT_PROJECTION_ATTR}]`))
82
+ return pointed;
86
83
  if (intent.target.isConnected)
87
84
  return intent.target;
88
- return intent.ownerDocument.elementFromPoint?.(intent.point.x, intent.point.y) ?? null;
85
+ return pointed;
89
86
  }
90
87
  function resolveInlineTextEditIntent(intent) {
91
88
  const eventTarget = currentIntentTarget(intent);
@@ -107,6 +104,12 @@ function resolveInlineTextEditIntent(intent) {
107
104
  }
108
105
  return null;
109
106
  }
107
+ function isApplicationOwnedEditingSurface(target) {
108
+ const host = target.closest("[contenteditable]");
109
+ return Boolean(host
110
+ && host.getAttribute("data-inline-editor") !== "true"
111
+ && host.getAttribute("contenteditable") !== "false");
112
+ }
110
113
  function beginInlineTextEditIntent(intent) {
111
114
  const resolved = resolveInlineTextEditIntent(intent);
112
115
  if (!resolved)
@@ -144,6 +147,12 @@ function requestInlineTextEdit(target, point) {
144
147
  return "stop-propagation";
145
148
  }
146
149
  }
150
+ // Application-owned contenteditable surfaces must retain native editing
151
+ // semantics. They are intentionally rejected by text binding, but that
152
+ // rejection must not become an inspector-level suppression when another
153
+ // inline edit is already active.
154
+ if (isApplicationOwnedEditingSurface(target))
155
+ return "pass-through";
147
156
  const intent = {
148
157
  ownerDocument: target.ownerDocument,
149
158
  target,
@@ -153,7 +162,7 @@ function requestInlineTextEdit(target, point) {
153
162
  return activeSession ? "suppress" : "pass-through";
154
163
  if (!activeSession) {
155
164
  const result = beginInlineTextEditIntent(intent);
156
- return result && !("kind" in result) ? "suppress" : "pass-through";
165
+ return result ? "suppress" : "pass-through";
157
166
  }
158
167
  pendingEditIntent = intent;
159
168
  activeSession.commit("blur");
@@ -182,6 +191,15 @@ export function handleInlineTextEditIntent(intent) {
182
191
  function isTextEditRejection(value) {
183
192
  return "kind" in value && value.kind === "rejected";
184
193
  }
194
+ function recordTextEditRejection(target, rejection) {
195
+ recordDiagnostic({
196
+ kind: "inline-text",
197
+ status: "rejected",
198
+ reason: rejection.reason,
199
+ before: target.textContent ?? "",
200
+ }, target);
201
+ return rejection;
202
+ }
185
203
  function captureSelection(doc) {
186
204
  const selection = doc.getSelection?.();
187
205
  if (!selection)
@@ -316,59 +334,6 @@ function flattenHostText(host) {
316
334
  host.replaceChildren(host.ownerDocument.createTextNode(text));
317
335
  return text;
318
336
  }
319
- function sourceForChoice(candidate, choice) {
320
- if (choice?.renderedSource)
321
- return choice.renderedSource;
322
- if (candidate.renderedSource
323
- && (!choice || candidate.renderedSource.evidence?.callsiteId === choice.binding.target.callsiteId
324
- && candidate.renderedSource.evidence.property === choice.binding.property)) {
325
- return candidate.renderedSource;
326
- }
327
- const parsed = /^(.*):(\d+):(\d+)$/.exec(candidate.element.getAttribute("data-src") ?? "");
328
- const target = choice?.binding.target;
329
- const authoredAs = choice?.authoredAs === "literal"
330
- ? "literal"
331
- : choice?.authoredAs === "expression" || choice?.authoredAs === "spread"
332
- ? "expression"
333
- : "unknown";
334
- return {
335
- file: target?.file ?? parsed?.[1] ?? candidate.element.getAttribute("data-src") ?? "",
336
- line: target?.line ?? Number(parsed?.[2] ?? 0),
337
- column: target?.column ?? Number(parsed?.[3] ?? 0),
338
- component: target?.componentName ?? candidate.element.getAttribute("data-cid") ?? "Rendered text",
339
- selector: candidate.binding.kind === "rendered-text"
340
- ? textProjectionSelector(candidate.binding.target) ?? ""
341
- : "",
342
- authoredAs,
343
- evidence: choice ? {
344
- callsiteId: choice.binding.target.callsiteId,
345
- componentName: choice.binding.target.componentName,
346
- property: choice.binding.property,
347
- mountedCount: choice.mountedCount,
348
- } : undefined,
349
- };
350
- }
351
- function componentEvidence(candidate, choice) {
352
- const target = choice.renderedTarget
353
- ?? candidate.renderedTarget
354
- ?? captureTextProjectionTarget(candidate.element, candidate.before, candidate.textNode);
355
- return {
356
- occurrence: target?.occurrence ?? 0,
357
- props: target?.props ?? candidate.element.getAttribute("data-cprops"),
358
- ariaLabel: target?.ariaLabel ?? candidate.element.getAttribute("aria-label"),
359
- beforeText: candidate.before,
360
- mountedCount: choice.mountedCount,
361
- };
362
- }
363
- function textTargetFor(candidate, binding, choice) {
364
- if (choice?.renderedTarget)
365
- return choice.renderedTarget;
366
- if (candidate.renderedTarget)
367
- return candidate.renderedTarget;
368
- if (binding.kind === "rendered-text")
369
- return binding.target;
370
- return captureTextProjectionTarget(candidate.element, candidate.before, candidate.textNode);
371
- }
372
337
  function resolveEmptyProjectionCandidate(marker) {
373
338
  if (!marker.isConnected) {
374
339
  return {
@@ -439,7 +404,8 @@ function resolveEmptyProjectionCandidate(marker) {
439
404
  function makeSession(candidate) {
440
405
  const doc = candidate.element.ownerDocument;
441
406
  const originalSelection = captureSelection(doc);
442
- const previousActiveElement = doc.activeElement instanceof HTMLElement
407
+ const OwnerHTMLElement = doc.defaultView?.HTMLElement;
408
+ const previousActiveElement = OwnerHTMLElement && doc.activeElement instanceof OwnerHTMLElement
443
409
  ? doc.activeElement
444
410
  : null;
445
411
  const originalParent = candidate.textNode.parentNode;
@@ -481,100 +447,7 @@ function makeSession(candidate) {
481
447
  const originalReplaceState = history?.replaceState;
482
448
  let patchedPushState = null;
483
449
  let patchedReplaceState = null;
484
- let selectedChoice = candidate.bindingChoices?.length === 1
485
- ? candidate.bindingChoices[0]
486
- : null;
487
- let selectedScope = candidate.scope
488
- ?? (candidate.binding.kind === "rendered-text" ? "rendered-instance" : "source-site");
489
- function currentBinding() {
490
- if (!selectedChoice)
491
- return candidate.binding;
492
- if (selectedChoice.mountedCount > 1 && selectedChoice.authoredAs !== "literal") {
493
- const target = selectedChoice.renderedTarget
494
- ?? candidate.renderedTarget
495
- ?? capturedTarget;
496
- return target ? { kind: "rendered-text", target } : selectedChoice.binding;
497
- }
498
- return selectedChoice.binding;
499
- }
500
- function currentScopeChoices() {
501
- if (selectedChoice) {
502
- if (selectedChoice.mountedCount > 1 && selectedChoice.authoredAs === "literal") {
503
- return ["rendered-instance", "source-site"];
504
- }
505
- return [];
506
- }
507
- return candidate.scopeChoices ?? [];
508
- }
509
- function choiceForCommit() {
510
- const choice = selectedChoice ?? candidate.bindingChoices?.[0];
511
- const target = candidate.editableTarget;
512
- if (choice || !target || candidate.mountedCount === undefined)
513
- return choice ?? null;
514
- const property = candidate.binding.kind === "component-prop"
515
- ? candidate.binding.property
516
- : "children";
517
- return {
518
- binding: candidate.binding.kind === "component-prop"
519
- ? candidate.binding
520
- : {
521
- kind: "component-prop",
522
- property,
523
- target: {
524
- framework: target.framework,
525
- componentId: target.contract.componentId,
526
- callsiteId: target.meta.callsiteId,
527
- componentName: target.meta.componentName,
528
- file: target.meta.file,
529
- line: target.meta.line,
530
- column: target.meta.column,
531
- },
532
- },
533
- editableTarget: target,
534
- mountedCount: candidate.mountedCount,
535
- authoredAs: target.meta.authoredProps[property] ?? "default",
536
- renderedTarget: candidate.renderedTarget ?? null,
537
- renderedSource: candidate.renderedSource,
538
- };
539
- }
540
- function prepareChange(after, binding, choice) {
541
- if (binding.kind === "component-prop" && selectedScope === "source-site") {
542
- const editableTarget = choice?.editableTarget ?? candidate.editableTarget;
543
- const prop = editableTarget?.contract.props.find((item) => item.name === binding.property && item.control === "text");
544
- if (!editableTarget || !prop)
545
- return null;
546
- return createComponentPropChange(editableTarget, prop, after, {
547
- scope: "source-site",
548
- evidence: choice && choice.mountedCount > 1
549
- ? componentEvidence(candidate, choice)
550
- : undefined,
551
- });
552
- }
553
- const textTarget = textTargetFor(candidate, binding, choice);
554
- const source = sourceForChoice(candidate, choice);
555
- const renderedSource = source.selector || !textTarget
556
- ? source
557
- : { ...source, selector: textProjectionSelector(textTarget) ?? "" };
558
- if (!textTarget || !renderedSource.selector)
559
- return null;
560
- return {
561
- kind: "text-content",
562
- id: `text-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`,
563
- target: textTarget,
564
- source: {
565
- file: renderedSource.file,
566
- line: renderedSource.line,
567
- column: renderedSource.column,
568
- component: renderedSource.component,
569
- },
570
- selector: renderedSource.selector,
571
- before: candidate.before,
572
- after,
573
- authoredAs: renderedSource.authoredAs,
574
- scope: selectedScope,
575
- evidence: renderedSource.evidence,
576
- };
577
- }
450
+ let editDecision = initialTextEditDecision(candidate);
578
451
  function restoreHostText(text) {
579
452
  host.replaceChildren(doc.createTextNode(text));
580
453
  lastSafeText = text;
@@ -602,11 +475,12 @@ function makeSession(candidate) {
602
475
  reason,
603
476
  before: candidate.before,
604
477
  after: host.textContent ?? "",
605
- });
478
+ }, candidate.element);
606
479
  }
607
480
  function editorOwnsFocus() {
608
481
  const active = doc.activeElement;
609
- return active === host || (active instanceof Node && host.contains(active));
482
+ const OwnerNode = doc.defaultView?.Node;
483
+ return active === host || Boolean(OwnerNode && active instanceof OwnerNode && host.contains(active));
610
484
  }
611
485
  function cancelForReconciliation(discardedDraft) {
612
486
  if (finished)
@@ -685,7 +559,7 @@ function makeSession(candidate) {
685
559
  if (candidate.emptyProjectionMarker?.isConnected) {
686
560
  candidate.emptyProjectionMarker.hidden = false;
687
561
  }
688
- recordDiagnostic({ kind: "inline-text", status, reason, before: candidate.before, after });
562
+ recordDiagnostic({ kind: "inline-text", status, reason, before: candidate.before, after }, candidate.element);
689
563
  schedulePendingInlineTextEdit();
690
564
  }
691
565
  function onKeyDown(event) {
@@ -899,7 +773,8 @@ function makeSession(candidate) {
899
773
  if (finished)
900
774
  return;
901
775
  const target = event.target;
902
- if (!(target instanceof Node))
776
+ const OwnerNode = doc.defaultView?.Node;
777
+ if (!OwnerNode || !(target instanceof OwnerNode))
903
778
  return;
904
779
  if (host.contains(target)) {
905
780
  // All down/up events must reach the native editing host so caret and
@@ -983,40 +858,35 @@ function makeSession(candidate) {
983
858
  }
984
859
  session = {
985
860
  get binding() {
986
- return currentBinding();
861
+ return getTextEditBinding(candidate, editDecision, capturedTarget);
987
862
  },
988
863
  get bindingChoices() {
989
864
  return candidate.bindingChoices ?? [];
990
865
  },
991
866
  get selectedBindingIndex() {
992
- if (!selectedChoice)
993
- return null;
994
- return candidate.bindingChoices?.indexOf(selectedChoice) ?? null;
867
+ return editDecision.bindingIndex;
995
868
  },
996
869
  get scope() {
997
- return selectedScope;
870
+ return editDecision.scope;
998
871
  },
999
872
  get scopeChoices() {
1000
- return currentScopeChoices();
873
+ return getTextEditScopeChoices(candidate, editDecision);
1001
874
  },
1002
875
  host,
1003
876
  before: candidate.before,
1004
877
  chooseBinding(index) {
1005
878
  if (finished)
1006
879
  return;
1007
- const choice = candidate.bindingChoices?.[index];
1008
- if (!choice)
880
+ const decision = chooseTextEditBinding(candidate, index);
881
+ if (!decision)
1009
882
  return;
1010
- selectedChoice = choice;
1011
- selectedScope = choice.mountedCount > 1 && choice.authoredAs === "literal"
1012
- ? "rendered-instance"
1013
- : choice.mountedCount > 1 ? "rendered-instance" : "source-site";
883
+ editDecision = decision;
1014
884
  notify();
1015
885
  },
1016
886
  chooseScope(scope) {
1017
- if (finished || !currentScopeChoices().includes(scope))
887
+ if (finished || !getTextEditScopeChoices(candidate, editDecision).includes(scope))
1018
888
  return;
1019
- selectedScope = scope;
889
+ editDecision = { ...editDecision, scope };
1020
890
  notify();
1021
891
  },
1022
892
  commit(reason = "commit") {
@@ -1026,7 +896,7 @@ function makeSession(candidate) {
1026
896
  blurPending = true;
1027
897
  return null;
1028
898
  }
1029
- if ((candidate.bindingChoices?.length ?? 0) > 1 && !selectedChoice)
899
+ if ((candidate.bindingChoices?.length ?? 0) > 1 && editDecision.bindingIndex === null)
1030
900
  return null;
1031
901
  if (!hostIsOwned(candidate, host, ownership)) {
1032
902
  endLifecycle(!candidate.element.isConnected ? "app-reconciled" : "host-removed");
@@ -1038,10 +908,8 @@ function makeSession(candidate) {
1038
908
  restoreHostText(lastSafeText);
1039
909
  return null;
1040
910
  }
1041
- const binding = currentBinding();
1042
- const choice = choiceForCommit();
1043
911
  const unchanged = after === candidate.before;
1044
- const change = unchanged ? null : prepareChange(after, binding, choice);
912
+ const change = unchanged ? null : prepareTextEditChange(candidate, editDecision, capturedTarget, after);
1045
913
  if (!unchanged && !change) {
1046
914
  endLifecycle("app-reconciled", after);
1047
915
  return null;
@@ -1053,7 +921,7 @@ function makeSession(candidate) {
1053
921
  cancelForReconciliation();
1054
922
  return null;
1055
923
  }
1056
- if (change?.kind === "text-content" && selectedScope === "rendered-instance") {
924
+ if (change?.kind === "text-content" && editDecision.scope === "rendered-instance") {
1057
925
  // The second text value is alternate evidence for re-entry after a
1058
926
  // previous projection. Validation happens after unwrapping because a
1059
927
  // mounted draft can make another identical root look unique.
@@ -1131,7 +999,7 @@ function makeSession(candidate) {
1131
999
  activeSessionDocument = doc;
1132
1000
  unregisterClearHandler = registerInlineTextClearHandler(cancelInlineTextEdit);
1133
1001
  notify();
1134
- recordDiagnostic({ kind: "inline-text", status: "started", reason: "start", before: candidate.before });
1002
+ recordDiagnostic({ kind: "inline-text", status: "started", reason: "start", before: candidate.before }, candidate.element);
1135
1003
  host.focus();
1136
1004
  selectAll(host);
1137
1005
  return session;
@@ -1146,15 +1014,15 @@ export const inlineTextEditor = {
1146
1014
  };
1147
1015
  }
1148
1016
  if (activeSession) {
1149
- return {
1017
+ return recordTextEditRejection(element, {
1150
1018
  kind: "rejected",
1151
1019
  reason: "editing-active",
1152
1020
  message: "Finish the current inline text edit first.",
1153
- };
1021
+ });
1154
1022
  }
1155
1023
  const candidate = resolveTextBinding(element, point);
1156
1024
  if (isTextEditRejection(candidate))
1157
- return candidate;
1025
+ return recordTextEditRejection(element, candidate);
1158
1026
  return makeSession(candidate);
1159
1027
  },
1160
1028
  };
@@ -1169,15 +1037,15 @@ export function beginInlineTextEditFromEmptyProjection(marker) {
1169
1037
  };
1170
1038
  }
1171
1039
  if (activeSession) {
1172
- return {
1040
+ return recordTextEditRejection(marker, {
1173
1041
  kind: "rejected",
1174
1042
  reason: "editing-active",
1175
1043
  message: "Finish the current inline text edit first.",
1176
- };
1044
+ });
1177
1045
  }
1178
1046
  const candidate = resolveEmptyProjectionCandidate(marker);
1179
1047
  if (isTextEditRejection(candidate))
1180
- return candidate;
1048
+ return recordTextEditRejection(marker, candidate);
1181
1049
  // Hide the affordance while its retained empty Text node is wrapped by the
1182
1050
  // native editor. The marker is restored on cancel and removed by projection
1183
1051
  // reconciliation on a successful non-empty commit.
@@ -1187,11 +1055,11 @@ export function beginInlineTextEditFromEmptyProjection(marker) {
1187
1055
  }
1188
1056
  catch {
1189
1057
  marker.hidden = false;
1190
- return {
1058
+ return recordTextEditRejection(marker, {
1191
1059
  kind: "rejected",
1192
1060
  reason: "no-binding",
1193
1061
  message: "The empty rendered text could not be reopened safely.",
1194
- };
1062
+ });
1195
1063
  }
1196
1064
  }
1197
1065
  export function beginInlineTextEdit(element, point) {
@@ -0,0 +1,5 @@
1
+ import type { InlineTextDiagnostic } from "./inlineTextEditor.js";
2
+ export interface InlineTextFeedback {
3
+ readonly message: string;
4
+ }
5
+ export declare function getInlineTextFeedback(diagnostic: InlineTextDiagnostic | null): InlineTextFeedback | null;
@@ -0,0 +1,27 @@
1
+ const FEEDBACK_BY_REASON = {
2
+ "no-text": {
3
+ message: "Cannot edit text - No visible text",
4
+ },
5
+ "no-binding": {
6
+ message: "Cannot edit text - No safe source binding",
7
+ },
8
+ "ambiguous-binding": {
9
+ message: "Cannot edit text - Ambiguous source binding",
10
+ },
11
+ "editing-active": {
12
+ message: "Cannot edit text - Another edit is active",
13
+ },
14
+ "unsafe-target": {
15
+ message: "Cannot edit text - Unsupported text markup",
16
+ },
17
+ };
18
+ function isTextEditRejectionReason(reason) {
19
+ return reason in FEEDBACK_BY_REASON;
20
+ }
21
+ export function getInlineTextFeedback(diagnostic) {
22
+ if (!diagnostic || diagnostic.status !== "rejected")
23
+ return null;
24
+ if (!isTextEditRejectionReason(diagnostic.reason))
25
+ return null;
26
+ return FEEDBACK_BY_REASON[diagnostic.reason];
27
+ }
@@ -4,6 +4,7 @@
4
4
  * changesLog can then cancel it without importing the editor back through a
5
5
  * circular module edge.
6
6
  */
7
+ import { clearInlineTextDiagnostics } from "./inlineTextDiagnostics.js";
7
8
  let clearHandler = null;
8
9
  export function registerInlineTextClearHandler(handler) {
9
10
  clearHandler = handler;
@@ -14,4 +15,5 @@ export function registerInlineTextClearHandler(handler) {
14
15
  }
15
16
  export function cancelInlineTextForClear() {
16
17
  clearHandler?.();
18
+ clearInlineTextDiagnostics();
17
19
  }
@@ -0,0 +1,14 @@
1
+ import type { TextBindingCandidate, TextEditBinding } from "../componentSemantics/textBinding.js";
2
+ import type { ChangeRecord } from "../changes/types.js";
3
+ import type { TextProjectionScope, TextProjectionTarget } from "./textChangeBoundary.js";
4
+ /** Binding and scope decisions, independent of native editor lifecycle state. */
5
+ export interface TextEditDecision {
6
+ bindingIndex: number | null;
7
+ scope: TextProjectionScope;
8
+ }
9
+ export declare function initialTextEditDecision(candidate: TextBindingCandidate): TextEditDecision;
10
+ export declare function chooseTextEditBinding(candidate: TextBindingCandidate, index: number): TextEditDecision | null;
11
+ export declare function getTextEditScopeChoices(candidate: TextBindingCandidate, decision: TextEditDecision): readonly TextProjectionScope[];
12
+ export declare function getTextEditBinding(candidate: TextBindingCandidate, decision: TextEditDecision, capturedTarget: TextProjectionTarget | null): TextEditBinding;
13
+ /** Builds a canonical record without changing the DOM, appending history, or ending a draft. */
14
+ export declare function prepareTextEditChange(candidate: TextBindingCandidate, decision: TextEditDecision, capturedTarget: TextProjectionTarget | null, after: string): ChangeRecord | null;