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.
- package/bin/nudge-ui.mjs +3 -1
- package/dist/cli/agentCommand.d.ts +8 -0
- package/dist/cli/agentCommand.js +59 -0
- package/dist/client.mjs +878 -11
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +63 -11
- package/dist/hosts/astro/integration.d.ts +2 -2
- package/dist/hosts/astro/integration.js +4 -3
- package/dist/hosts/environment.d.ts +15 -0
- package/dist/hosts/environment.js +16 -0
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +56 -7
- package/dist/hosts/next/wrapper.d.ts +2 -0
- package/dist/hosts/next/wrapper.js +32 -3
- package/dist/hosts/projectBridge.d.ts +21 -0
- package/dist/hosts/projectBridge.js +98 -0
- package/dist/hosts/static/cli.d.ts +1 -1
- package/dist/hosts/static/cli.js +6 -1
- package/dist/hosts/static/manifest.d.ts +1 -1
- package/dist/hosts/static/manifest.js +3 -1
- package/dist/hosts/static/server.js +45 -3
- package/dist/hosts/vite/vite.d.ts +6 -1
- package/dist/hosts/vite/vite.js +82 -15
- package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
- package/dist/inspector/agent/McpConnectionDialog.js +20 -37
- package/dist/inspector/agent/client.d.ts +16 -3
- package/dist/inspector/agent/client.js +114 -11
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +11 -4
- package/dist/inspector/agent/httpTransport.d.ts +8 -0
- package/dist/inspector/agent/httpTransport.js +78 -18
- package/dist/inspector/agent/protocol.d.ts +11 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +1 -1
- package/dist/inspector/agent/verification.js +69 -12
- package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
- package/dist/inspector/canvas/CanvasCard.js +181 -91
- package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
- package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
- package/dist/inspector/canvas/CanvasToolbar.js +24 -0
- package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
- package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
- package/dist/inspector/canvas/canvasResize.d.ts +30 -0
- package/dist/inspector/canvas/canvasResize.js +52 -0
- package/dist/inspector/canvas/canvasStore.d.ts +30 -6
- package/dist/inspector/canvas/canvasStore.js +128 -61
- package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
- package/dist/inspector/canvas/frameProtocol.js +63 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +16 -0
- package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
- package/dist/inspector/canvas/linkEligibility.js +0 -29
- package/dist/inspector/canvas/projection.d.ts +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +42 -29
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +385 -58
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
- package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
- package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
- package/dist/inspector/canvas/sessionStore.d.ts +1 -0
- package/dist/inspector/canvas/sessionStore.js +12 -4
- package/dist/inspector/canvas/staleChangeDetector.js +16 -14
- package/dist/inspector/changes/changesLog.js +31 -1
- package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
- package/dist/inspector/changes/managedStyleProjection.js +3 -3
- package/dist/inspector/changes/structuralTypes.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.js +29 -3
- package/dist/inspector/clientManifest.d.ts +7 -0
- package/dist/inspector/clientManifest.js +12 -0
- package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
- package/dist/inspector/index.d.ts +2 -0
- package/dist/inspector/index.js +80 -32
- package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
- package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
- package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
- package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
- package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
- package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
- package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
- package/dist/inspector/inline-text/textEditChange.js +165 -0
- package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
- package/dist/inspector/inspection/selectionProperty.js +1 -0
- package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
- package/dist/inspector/overlay/DomNavigator.js +40 -0
- package/dist/inspector/overlay/InspectorOverlay.js +6 -2
- package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
- package/dist/inspector/overlay/clickPolicy.js +34 -3
- package/dist/inspector/overlay/elementSelector.d.ts +1 -1
- package/dist/inspector/overlay/elementSelector.js +18 -7
- package/dist/inspector/overlay/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +351 -0
- package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
- package/dist/inspector/overlay/structuralGestures.js +12 -10
- package/dist/inspector/projection/managedStylesheet.js +4 -1
- package/dist/inspector/projection/renderedInstance.d.ts +2 -0
- package/dist/inspector/projection/renderedInstance.js +5 -1
- package/dist/inspector/projection/structuralProjection.d.ts +2 -0
- package/dist/inspector/projection/structuralProjection.js +85 -1
- package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
- package/dist/inspector/projection/textProjection.d.ts +2 -0
- package/dist/inspector/projection/textProjection.js +10 -4
- package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
- package/dist/inspector/prompt/clipboardHandoff.js +3 -3
- package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
- package/dist/inspector/prompt/generatePrompt.js +19 -2
- package/dist/inspector/runtime/editorShell.d.ts +2 -0
- package/dist/inspector/runtime/editorShell.js +4 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/hierarchy.d.ts +6 -0
- package/dist/inspector/selection/hierarchy.js +8 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
- package/dist/inspector/settings/SettingsDialog.js +12 -2
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +26 -22
- package/dist/inspector/shell/CopyPromptButton.js +102 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +38 -42
- package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalButton.js +57 -0
- package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
- package/dist/inspector/shell/originalPreview.d.ts +9 -0
- package/dist/inspector/shell/originalPreview.js +61 -0
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +15 -0
- package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
- package/dist/inspector/sketch/SketchChanges.js +72 -0
- package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
- package/dist/inspector/sketch/SketchEditor.js +402 -0
- package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
- package/dist/inspector/sketch/SketchEntryButton.js +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
- package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
- package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
- package/dist/inspector/sketch/SketchOverlay.js +420 -0
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
- package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
- package/dist/inspector/sketch/SketchWorkspace.js +152 -0
- package/dist/inspector/sketch/capture.d.ts +55 -0
- package/dist/inspector/sketch/capture.js +350 -0
- package/dist/inspector/sketch/freehand.d.ts +13 -0
- package/dist/inspector/sketch/freehand.js +64 -0
- package/dist/inspector/sketch/geometry.d.ts +17 -0
- package/dist/inspector/sketch/geometry.js +98 -0
- package/dist/inspector/sketch/handoff.d.ts +21 -0
- package/dist/inspector/sketch/handoff.js +72 -0
- package/dist/inspector/sketch/interaction.d.ts +21 -0
- package/dist/inspector/sketch/interaction.js +98 -0
- package/dist/inspector/sketch/model.d.ts +104 -0
- package/dist/inspector/sketch/model.js +282 -0
- package/dist/inspector/sketch/persistence.d.ts +21 -0
- package/dist/inspector/sketch/persistence.js +205 -0
- package/dist/inspector/sketch/raster.d.ts +22 -0
- package/dist/inspector/sketch/raster.js +203 -0
- package/dist/inspector/sketch/sketchNote.d.ts +10 -0
- package/dist/inspector/sketch/sketchNote.js +30 -0
- package/dist/inspector/sketch/store.d.ts +47 -0
- package/dist/inspector/sketch/store.js +434 -0
- package/dist/inspector/styleEditors/BorderEditor.js +15 -19
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
- package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
- package/dist/inspector/styleEditors/ColorPicker.js +11 -36
- package/dist/inspector/styleEditors/GapField.d.ts +3 -2
- package/dist/inspector/styleEditors/GapField.js +2 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +11 -8
- package/dist/inspector/styleEditors/SpacingBox.js +31 -33
- package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
- package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
- package/dist/inspector/styleEditors/layoutValue.js +3 -0
- package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
- package/dist/inspector/styleEditors/nudgeValue.js +93 -5
- package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
- package/dist/inspector/styleEditors/stylePresence.js +22 -0
- package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
- package/dist/inspector/styleEditors/textRelevance.js +35 -0
- package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
- package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
- package/dist/inspector/tokens/TokenField.js +130 -7
- package/dist/inspector/tokens/TokensPanel.js +2 -1
- package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
- package/dist/inspector/tokens/dragPointerLock.js +70 -0
- package/dist/inspector/tokens/editActions.d.ts +2 -0
- package/dist/inspector/tokens/editActions.js +2 -2
- package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
- package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
- package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
- package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
- package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
- package/dist/inspector/tokens/resolution.d.ts +3 -26
- package/dist/inspector/tokens/resolution.js +44 -735
- package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
- package/dist/inspector/tokens/tokenAvailability.js +204 -0
- package/dist/inspector/ui/Button.d.ts +1 -1
- package/dist/inspector/ui/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- package/dist/inspector/ui/SideValuesField.js +2 -2
- package/dist/inspector/ui/StatusCallout.d.ts +4 -1
- package/dist/inspector/ui/StatusCallout.js +2 -2
- package/dist/inspector/ui/dragCursor.d.ts +8 -0
- package/dist/inspector/ui/dragCursor.js +21 -0
- package/dist/inspector/ui/styles.js +21 -17
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +276 -58
- package/dist/transport/editor.d.ts +47 -0
- package/dist/transport/editor.js +163 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +10 -0
- package/dist/transport/routes.js +10 -0
- package/package.json +3 -1
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- 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 {
|
|
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,
|
|
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
|
|
35
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
861
|
+
return getTextEditBinding(candidate, editDecision, capturedTarget);
|
|
987
862
|
},
|
|
988
863
|
get bindingChoices() {
|
|
989
864
|
return candidate.bindingChoices ?? [];
|
|
990
865
|
},
|
|
991
866
|
get selectedBindingIndex() {
|
|
992
|
-
|
|
993
|
-
return null;
|
|
994
|
-
return candidate.bindingChoices?.indexOf(selectedChoice) ?? null;
|
|
867
|
+
return editDecision.bindingIndex;
|
|
995
868
|
},
|
|
996
869
|
get scope() {
|
|
997
|
-
return
|
|
870
|
+
return editDecision.scope;
|
|
998
871
|
},
|
|
999
872
|
get scopeChoices() {
|
|
1000
|
-
return
|
|
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
|
|
1008
|
-
if (!
|
|
880
|
+
const decision = chooseTextEditBinding(candidate, index);
|
|
881
|
+
if (!decision)
|
|
1009
882
|
return;
|
|
1010
|
-
|
|
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 || !
|
|
887
|
+
if (finished || !getTextEditScopeChoices(candidate, editDecision).includes(scope))
|
|
1018
888
|
return;
|
|
1019
|
-
|
|
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 &&
|
|
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 :
|
|
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" &&
|
|
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,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;
|