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
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { getElementComputedStyle } from "../runtime/domRealm.js";
|
|
2
|
+
const SPACING_DRAG_TARGET_SIZE = 16;
|
|
3
|
+
export const SPACING_GUIDE_HANDLE_LENGTH = 40;
|
|
4
|
+
const SPACING_SHIFT_STEP = 8;
|
|
5
|
+
const EPSILON = 0.01;
|
|
6
|
+
function rightOf(rect) {
|
|
7
|
+
return Number.isFinite(rect.right) ? rect.right : rect.left + rect.width;
|
|
8
|
+
}
|
|
9
|
+
function bottomOf(rect) {
|
|
10
|
+
return Number.isFinite(rect.bottom) ? rect.bottom : rect.top + rect.height;
|
|
11
|
+
}
|
|
12
|
+
function boxEdges(element) {
|
|
13
|
+
const rect = element.getBoundingClientRect();
|
|
14
|
+
return {
|
|
15
|
+
left: rect.left,
|
|
16
|
+
top: rect.top,
|
|
17
|
+
right: rightOf(rect),
|
|
18
|
+
bottom: bottomOf(rect),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Computed style resolves to px in browsers, so drag math normalizes to px.
|
|
22
|
+
// An authored rem/% is intentionally rewritten as px on commit.
|
|
23
|
+
function numberValue(value) {
|
|
24
|
+
const parsed = Number.parseFloat(value);
|
|
25
|
+
return Number.isFinite(parsed) ? Math.max(0, parsed) : 0;
|
|
26
|
+
}
|
|
27
|
+
function boxModel(element) {
|
|
28
|
+
const style = getElementComputedStyle(element);
|
|
29
|
+
const padding = {
|
|
30
|
+
top: numberValue(style.paddingTop),
|
|
31
|
+
right: numberValue(style.paddingRight),
|
|
32
|
+
bottom: numberValue(style.paddingBottom),
|
|
33
|
+
left: numberValue(style.paddingLeft),
|
|
34
|
+
};
|
|
35
|
+
const border = {
|
|
36
|
+
top: numberValue(style.borderTopWidth),
|
|
37
|
+
right: numberValue(style.borderRightWidth),
|
|
38
|
+
bottom: numberValue(style.borderBottomWidth),
|
|
39
|
+
left: numberValue(style.borderLeftWidth),
|
|
40
|
+
};
|
|
41
|
+
const outer = boxEdges(element);
|
|
42
|
+
const inner = {
|
|
43
|
+
left: outer.left + border.left,
|
|
44
|
+
top: outer.top + border.top,
|
|
45
|
+
right: outer.right - border.right,
|
|
46
|
+
bottom: outer.bottom - border.bottom,
|
|
47
|
+
};
|
|
48
|
+
const content = {
|
|
49
|
+
left: inner.left + padding.left,
|
|
50
|
+
top: inner.top + padding.top,
|
|
51
|
+
right: inner.right - padding.right,
|
|
52
|
+
bottom: inner.bottom - padding.bottom,
|
|
53
|
+
};
|
|
54
|
+
return { outer, inner, content, padding };
|
|
55
|
+
}
|
|
56
|
+
function containsPoint(rect, x, y) {
|
|
57
|
+
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
|
|
58
|
+
}
|
|
59
|
+
function containsRectPoint(rect, x, y) {
|
|
60
|
+
return x >= rect.left
|
|
61
|
+
&& x <= rect.left + rect.width
|
|
62
|
+
&& y >= rect.top
|
|
63
|
+
&& y <= rect.top + rect.height;
|
|
64
|
+
}
|
|
65
|
+
function guideHit(guide, orientation) {
|
|
66
|
+
if (orientation === "horizontal") {
|
|
67
|
+
const height = Math.max(SPACING_DRAG_TARGET_SIZE, guide.height);
|
|
68
|
+
const width = Math.min(SPACING_GUIDE_HANDLE_LENGTH, guide.width);
|
|
69
|
+
return {
|
|
70
|
+
left: guide.left + (guide.width - width) / 2,
|
|
71
|
+
top: guide.top - (height - guide.height) / 2,
|
|
72
|
+
width,
|
|
73
|
+
height,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const width = Math.max(SPACING_DRAG_TARGET_SIZE, guide.width);
|
|
77
|
+
const height = Math.min(SPACING_GUIDE_HANDLE_LENGTH, guide.height);
|
|
78
|
+
return {
|
|
79
|
+
left: guide.left - (width - guide.width) / 2,
|
|
80
|
+
top: guide.top + (guide.height - height) / 2,
|
|
81
|
+
width,
|
|
82
|
+
height,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const PADDING_CONFIG = {
|
|
86
|
+
top: { axis: "horizontal", dragAxis: "y", direction: 1 },
|
|
87
|
+
right: { axis: "vertical", dragAxis: "x", direction: 1 },
|
|
88
|
+
bottom: { axis: "horizontal", dragAxis: "y", direction: 1 },
|
|
89
|
+
left: { axis: "vertical", dragAxis: "x", direction: 1 },
|
|
90
|
+
};
|
|
91
|
+
function paddingAffordanceForSide(element, side, model = boxModel(element)) {
|
|
92
|
+
const { inner, content, padding } = model;
|
|
93
|
+
const value = padding[side];
|
|
94
|
+
if (value <= EPSILON)
|
|
95
|
+
return null;
|
|
96
|
+
const config = PADDING_CONFIG[side];
|
|
97
|
+
const width = Math.max(0, inner.right - inner.left);
|
|
98
|
+
const height = Math.max(0, inner.bottom - inner.top);
|
|
99
|
+
const guide = config.axis === "horizontal"
|
|
100
|
+
? {
|
|
101
|
+
left: inner.left,
|
|
102
|
+
top: (side === "top" ? inner.top + value / 2 : inner.bottom - value / 2) - 1,
|
|
103
|
+
width,
|
|
104
|
+
height: 2,
|
|
105
|
+
}
|
|
106
|
+
: {
|
|
107
|
+
left: (side === "left" ? inner.left + value / 2 : inner.right - value / 2) - 1,
|
|
108
|
+
top: inner.top,
|
|
109
|
+
width: 2,
|
|
110
|
+
height,
|
|
111
|
+
};
|
|
112
|
+
const area = config.axis === "horizontal"
|
|
113
|
+
? {
|
|
114
|
+
left: inner.left,
|
|
115
|
+
top: side === "top" ? inner.top : content.bottom,
|
|
116
|
+
width,
|
|
117
|
+
height: Math.max(0, (side === "top" ? content.top : inner.bottom) - (side === "top" ? inner.top : content.bottom)),
|
|
118
|
+
}
|
|
119
|
+
: {
|
|
120
|
+
left: side === "left" ? inner.left : content.right,
|
|
121
|
+
top: inner.top,
|
|
122
|
+
width: Math.max(0, (side === "left" ? content.left : inner.right) - (side === "left" ? inner.left : content.right)),
|
|
123
|
+
height,
|
|
124
|
+
};
|
|
125
|
+
const hit = guideHit(guide, config.axis);
|
|
126
|
+
return {
|
|
127
|
+
kind: "padding",
|
|
128
|
+
property: `padding-${side}`,
|
|
129
|
+
side,
|
|
130
|
+
axis: config.axis,
|
|
131
|
+
dragAxis: config.dragAxis,
|
|
132
|
+
value,
|
|
133
|
+
cursor: config.axis === "horizontal" ? "ns-resize" : "ew-resize",
|
|
134
|
+
guide,
|
|
135
|
+
affectedGuides: [guide],
|
|
136
|
+
affectedAreas: [area],
|
|
137
|
+
hit,
|
|
138
|
+
direction: config.direction,
|
|
139
|
+
element,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function paddingAffordanceAtPoint(element, x, y) {
|
|
143
|
+
const model = boxModel(element);
|
|
144
|
+
if (!containsPoint(model.outer, x, y))
|
|
145
|
+
return null;
|
|
146
|
+
const sides = ["top", "right", "bottom", "left"];
|
|
147
|
+
for (const side of sides) {
|
|
148
|
+
const affordance = paddingAffordanceForSide(element, side, model);
|
|
149
|
+
if (affordance && containsRectPoint(affordance.hit, x, y))
|
|
150
|
+
return affordance;
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
function overlap(startA, endA, startB, endB) {
|
|
155
|
+
return Math.min(endA, endB) - Math.max(startA, startB) > EPSILON;
|
|
156
|
+
}
|
|
157
|
+
function layoutChildren(element) {
|
|
158
|
+
const HTMLElementConstructor = element.ownerDocument.defaultView?.HTMLElement;
|
|
159
|
+
if (!HTMLElementConstructor)
|
|
160
|
+
return [];
|
|
161
|
+
return Array.from(element.children)
|
|
162
|
+
.filter((child) => child instanceof HTMLElementConstructor)
|
|
163
|
+
.map((child) => ({ element: child, rect: boxEdges(child) }));
|
|
164
|
+
}
|
|
165
|
+
function gapSegments(element) {
|
|
166
|
+
const style = getElementComputedStyle(element);
|
|
167
|
+
if (style.display !== "flex"
|
|
168
|
+
&& style.display !== "inline-flex"
|
|
169
|
+
&& style.display !== "grid"
|
|
170
|
+
&& style.display !== "inline-grid")
|
|
171
|
+
return [];
|
|
172
|
+
const children = layoutChildren(element);
|
|
173
|
+
const segments = [];
|
|
174
|
+
const byLeft = [...children].sort((left, right) => left.rect.left - right.rect.left);
|
|
175
|
+
const byTop = [...children].sort((top, bottom) => top.rect.top - bottom.rect.top);
|
|
176
|
+
for (const left of byLeft) {
|
|
177
|
+
const next = byLeft.find((right) => right.rect.left >= left.rect.right - EPSILON
|
|
178
|
+
&& overlap(left.rect.top, left.rect.bottom, right.rect.top, right.rect.bottom));
|
|
179
|
+
if (!next)
|
|
180
|
+
continue;
|
|
181
|
+
segments.push({
|
|
182
|
+
property: "column-gap",
|
|
183
|
+
axis: "horizontal",
|
|
184
|
+
start: left.rect.right,
|
|
185
|
+
end: next.rect.left,
|
|
186
|
+
crossStart: Math.max(left.rect.top, next.rect.top),
|
|
187
|
+
crossEnd: Math.min(left.rect.bottom, next.rect.bottom),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
for (const top of byTop) {
|
|
191
|
+
const next = byTop.find((bottom) => bottom.rect.top >= top.rect.bottom - EPSILON
|
|
192
|
+
&& overlap(top.rect.left, top.rect.right, bottom.rect.left, bottom.rect.right));
|
|
193
|
+
if (!next)
|
|
194
|
+
continue;
|
|
195
|
+
segments.push({
|
|
196
|
+
property: "row-gap",
|
|
197
|
+
axis: "vertical",
|
|
198
|
+
start: top.rect.bottom,
|
|
199
|
+
end: next.rect.top,
|
|
200
|
+
crossStart: Math.max(top.rect.left, next.rect.left),
|
|
201
|
+
crossEnd: Math.min(top.rect.right, next.rect.right),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return segments;
|
|
205
|
+
}
|
|
206
|
+
function gapAffordanceAtPoint(element, x, y, property) {
|
|
207
|
+
const segments = gapSegments(element);
|
|
208
|
+
if (segments.length === 0)
|
|
209
|
+
return null;
|
|
210
|
+
const style = getElementComputedStyle(element);
|
|
211
|
+
const candidate = segments.find((segment) => {
|
|
212
|
+
if (property && segment.property !== property)
|
|
213
|
+
return false;
|
|
214
|
+
return containsRectPoint(gapDragHit(segment), x, y);
|
|
215
|
+
});
|
|
216
|
+
if (!candidate)
|
|
217
|
+
return null;
|
|
218
|
+
const value = gapValue(style, candidate.property);
|
|
219
|
+
const guide = gapGuide(candidate);
|
|
220
|
+
const matchingSegments = segments.filter((segment) => segment.property === candidate.property);
|
|
221
|
+
const hit = gapDragHit(candidate);
|
|
222
|
+
return {
|
|
223
|
+
kind: "gap",
|
|
224
|
+
property: candidate.property,
|
|
225
|
+
side: null,
|
|
226
|
+
axis: candidate.axis,
|
|
227
|
+
dragAxis: candidate.axis === "horizontal" ? "x" : "y",
|
|
228
|
+
value,
|
|
229
|
+
cursor: candidate.axis === "horizontal" ? "ew-resize" : "ns-resize",
|
|
230
|
+
guide,
|
|
231
|
+
affectedGuides: matchingSegments.map(gapGuide),
|
|
232
|
+
affectedAreas: matchingSegments.map(gapArea),
|
|
233
|
+
hit,
|
|
234
|
+
// Gap size is absolute screen space; dragging along +axis always grows it.
|
|
235
|
+
direction: 1,
|
|
236
|
+
element,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function gapGuide(segment) {
|
|
240
|
+
const midpoint = (segment.start + segment.end) / 2;
|
|
241
|
+
const crossSize = Math.max(0, segment.crossEnd - segment.crossStart);
|
|
242
|
+
return segment.axis === "horizontal"
|
|
243
|
+
? { left: midpoint - 1, top: segment.crossStart, width: 2, height: crossSize }
|
|
244
|
+
: { left: segment.crossStart, top: midpoint - 1, width: crossSize, height: 2 };
|
|
245
|
+
}
|
|
246
|
+
function gapArea(segment) {
|
|
247
|
+
const crossSize = Math.max(0, segment.crossEnd - segment.crossStart);
|
|
248
|
+
return segment.axis === "horizontal"
|
|
249
|
+
? {
|
|
250
|
+
left: segment.start,
|
|
251
|
+
top: segment.crossStart,
|
|
252
|
+
width: Math.max(0, segment.end - segment.start),
|
|
253
|
+
height: crossSize,
|
|
254
|
+
}
|
|
255
|
+
: {
|
|
256
|
+
left: segment.crossStart,
|
|
257
|
+
top: segment.start,
|
|
258
|
+
width: crossSize,
|
|
259
|
+
height: Math.max(0, segment.end - segment.start),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function gapDragHit(segment) {
|
|
263
|
+
const orientation = segment.axis === "horizontal" ? "vertical" : "horizontal";
|
|
264
|
+
return guideHit(gapGuide(segment), orientation);
|
|
265
|
+
}
|
|
266
|
+
function gapValue(style, property) {
|
|
267
|
+
const direct = style.getPropertyValue(property)
|
|
268
|
+
|| (property === "row-gap" ? style.rowGap : style.columnGap);
|
|
269
|
+
if (direct.trim())
|
|
270
|
+
return numberValue(direct);
|
|
271
|
+
// jsdom and a few older browser CSSOM implementations expose `gap` but not
|
|
272
|
+
// the longhands through getPropertyValue(). Keep the gesture model aligned
|
|
273
|
+
// with the browser's shorthand fallback in those realms.
|
|
274
|
+
const shorthand = style.getPropertyValue("gap") || style.gap;
|
|
275
|
+
const values = shorthand.trim().split(/\s+/).filter(Boolean);
|
|
276
|
+
if (values.length === 0)
|
|
277
|
+
return 0;
|
|
278
|
+
if (values.length === 1)
|
|
279
|
+
return numberValue(values[0]);
|
|
280
|
+
return numberValue(property === "row-gap" ? values[0] : values[1]);
|
|
281
|
+
}
|
|
282
|
+
function trackedElementsAtPoint(doc, x, y) {
|
|
283
|
+
if (typeof doc.elementFromPoint !== "function")
|
|
284
|
+
return [];
|
|
285
|
+
const target = doc.elementFromPoint(x, y);
|
|
286
|
+
if (!target)
|
|
287
|
+
return [];
|
|
288
|
+
const HTMLElementConstructor = doc.defaultView?.HTMLElement;
|
|
289
|
+
if (!HTMLElementConstructor)
|
|
290
|
+
return [];
|
|
291
|
+
const result = [];
|
|
292
|
+
let element = target;
|
|
293
|
+
while (element) {
|
|
294
|
+
if (element instanceof HTMLElementConstructor && element.hasAttribute("data-cid")) {
|
|
295
|
+
result.push(element);
|
|
296
|
+
}
|
|
297
|
+
element = element.parentElement;
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
/** Finds the spacing target under a document-local pointer location. */
|
|
302
|
+
export function getSpacingAffordanceAtPoint(doc, x, y) {
|
|
303
|
+
const candidates = trackedElementsAtPoint(doc, x, y);
|
|
304
|
+
for (const element of candidates) {
|
|
305
|
+
const padding = paddingAffordanceAtPoint(element, x, y);
|
|
306
|
+
if (padding)
|
|
307
|
+
return padding;
|
|
308
|
+
}
|
|
309
|
+
for (const element of candidates) {
|
|
310
|
+
const gap = gapAffordanceAtPoint(element, x, y);
|
|
311
|
+
if (gap)
|
|
312
|
+
return gap;
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
/** Re-resolves a renderer-reported spacing descriptor in its owner document. */
|
|
317
|
+
export function getSpacingAffordanceForDescriptor(element, descriptor) {
|
|
318
|
+
if (descriptor.kind === "padding") {
|
|
319
|
+
const side = descriptor.side;
|
|
320
|
+
if (!side || descriptor.property !== `padding-${side}`)
|
|
321
|
+
return null;
|
|
322
|
+
return paddingAffordanceForSide(element, side);
|
|
323
|
+
}
|
|
324
|
+
if (descriptor.property !== "row-gap" && descriptor.property !== "column-gap")
|
|
325
|
+
return null;
|
|
326
|
+
const segment = gapSegments(element).find((candidate) => candidate.property === descriptor.property);
|
|
327
|
+
if (!segment)
|
|
328
|
+
return null;
|
|
329
|
+
const guide = gapGuide(segment);
|
|
330
|
+
return gapAffordanceAtPoint(element, guide.left + guide.width / 2, guide.top + guide.height / 2, descriptor.property);
|
|
331
|
+
}
|
|
332
|
+
/** Calculates a direct-manipulation value, optionally stepping changes by 8px. */
|
|
333
|
+
export function spacingValueForDrag(affordance, start, point, shiftKey = false) {
|
|
334
|
+
const delta = affordance.dragAxis === "x" ? point.x - start.x : point.y - start.y;
|
|
335
|
+
const signedDelta = delta * affordance.direction;
|
|
336
|
+
const adjustedDelta = shiftKey
|
|
337
|
+
? Math.trunc(signedDelta / SPACING_SHIFT_STEP) * SPACING_SHIFT_STEP
|
|
338
|
+
: signedDelta;
|
|
339
|
+
return Math.max(0, Math.round(affordance.value + adjustedDelta));
|
|
340
|
+
}
|
|
341
|
+
export function spacingValueCss(value) {
|
|
342
|
+
return `${Math.max(0, Math.round(value))}px`;
|
|
343
|
+
}
|
|
344
|
+
/** Serializes the stable part of an affordance for renderer-to-controller messages. */
|
|
345
|
+
export function toSpacingDescriptor(affordance) {
|
|
346
|
+
return {
|
|
347
|
+
kind: affordance.kind,
|
|
348
|
+
property: affordance.property,
|
|
349
|
+
side: affordance.side,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
@@ -20,9 +20,9 @@ export declare function canContainElement(parent: HTMLElement, child: HTMLElemen
|
|
|
20
20
|
/** Derives a safe insertion point; its nodes never leave this gesture module. */
|
|
21
21
|
export declare function getDropLocationForElement(dragged: HTMLElement, target: HTMLElement, beforeTarget: boolean, preferContainer?: boolean): DropLocation | null;
|
|
22
22
|
export declare function getDropLocationAtPoint(doc: Document, dragged: HTMLElement, x: number, y: number): DropLocation | null;
|
|
23
|
-
/** Capture the move as canonical intent, then
|
|
23
|
+
/** Capture the move as canonical intent, then preview it in an inline host. */
|
|
24
24
|
export declare function moveElement(node: HTMLElement, destination: DropLocation): StructuralMove | null;
|
|
25
25
|
/** Reorder only same-parent siblings; horizontal movement needs a flex row. */
|
|
26
26
|
export declare function nudgeElement(node: HTMLElement, key: StructuralNudgeKey): StructuralMove | null;
|
|
27
|
-
/** Capture delete intent
|
|
27
|
+
/** Capture delete intent, then preview it in an inline host. */
|
|
28
28
|
export declare function deleteElement(selected: SelectedElement): StructuralDelete | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { applyStructuralProjection, canContainElement as canContainStructuralElement, createStructuralDelete, createStructuralMove, getStructuralMoveLegality, getStructuralChanges, } from "../projection/structuralProjection.js";
|
|
2
|
+
import { isEditorShellDocument } from "../runtime/editorShell.js";
|
|
2
3
|
/** Conservative HTML guard shared with canonical capture and document replay. */
|
|
3
4
|
export function canContainElement(parent, child) {
|
|
4
5
|
return canContainStructuralElement(parent, child);
|
|
@@ -141,16 +142,18 @@ export function getDropLocationAtPoint(doc, dragged, x, y) {
|
|
|
141
142
|
const inContainerInterior = y - rect.top > edgeBand && y - rect.top < rect.height - edgeBand;
|
|
142
143
|
return getDropLocationForElement(dragged, target, y < rect.top + rect.height / 2, inContainerInterior);
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
+
function projectInlineHost(node) {
|
|
146
|
+
if (node.ownerDocument !== document || isEditorShellDocument(document))
|
|
147
|
+
return;
|
|
148
|
+
applyStructuralProjection(document, getStructuralChanges());
|
|
149
|
+
}
|
|
150
|
+
/** Capture the move as canonical intent, then preview it in an inline host. */
|
|
145
151
|
export function moveElement(node, destination) {
|
|
146
152
|
if (!getStructuralMoveLegality(node, destination).valid)
|
|
147
153
|
return null;
|
|
148
154
|
const change = createStructuralMove(node, destination);
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
// `document` belongs to the controller runtime. A Canvas source element is
|
|
152
|
-
// deliberately not projected here; its renderer receives this snapshot once.
|
|
153
|
-
applyStructuralProjection(document, getStructuralChanges());
|
|
155
|
+
if (change)
|
|
156
|
+
projectInlineHost(node);
|
|
154
157
|
return change;
|
|
155
158
|
}
|
|
156
159
|
/** Reorder only same-parent siblings; horizontal movement needs a flex row. */
|
|
@@ -167,14 +170,13 @@ export function nudgeElement(node, key) {
|
|
|
167
170
|
const before = moveEarlier ? sibling : sibling.nextElementSibling;
|
|
168
171
|
return moveElement(node, { parent, before, ...lineFor(parent, before) });
|
|
169
172
|
}
|
|
170
|
-
/** Capture delete intent
|
|
173
|
+
/** Capture delete intent, then preview it in an inline host. */
|
|
171
174
|
export function deleteElement(selected) {
|
|
172
175
|
const node = selected.domElement;
|
|
173
176
|
if (!node.parentElement || !node.isConnected)
|
|
174
177
|
return null;
|
|
175
178
|
const change = createStructuralDelete(node);
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
applyStructuralProjection(document, getStructuralChanges());
|
|
179
|
+
if (change)
|
|
180
|
+
projectInlineHost(node);
|
|
179
181
|
return change;
|
|
180
182
|
}
|
|
@@ -171,6 +171,9 @@ function commaListIncludes(value, property) {
|
|
|
171
171
|
return value.split(",").map((part) => part.trim()).some((part) => part === "all" || part === property);
|
|
172
172
|
}
|
|
173
173
|
function hasImportantAuthorRule(el, property) {
|
|
174
|
+
const StyleRule = el.ownerDocument.defaultView?.CSSStyleRule;
|
|
175
|
+
if (!StyleRule)
|
|
176
|
+
return false;
|
|
174
177
|
for (const sheet of Array.from(el.ownerDocument.styleSheets)) {
|
|
175
178
|
let rules;
|
|
176
179
|
try {
|
|
@@ -180,7 +183,7 @@ function hasImportantAuthorRule(el, property) {
|
|
|
180
183
|
continue;
|
|
181
184
|
}
|
|
182
185
|
for (const rule of Array.from(rules)) {
|
|
183
|
-
if (!(rule instanceof
|
|
186
|
+
if (!(rule instanceof StyleRule))
|
|
184
187
|
continue;
|
|
185
188
|
try {
|
|
186
189
|
if (el.matches(rule.selectorText) && rule.style.getPropertyPriority(property) === "important")
|
|
@@ -44,6 +44,8 @@ export declare function collectRenderedInstanceOverrides(changes: ReadonlyArray<
|
|
|
44
44
|
* later reconciliation changes diagnostics but never starts a reapply loop.
|
|
45
45
|
*/
|
|
46
46
|
export declare function applyRenderedInstanceProjection(doc: Document, overrides: ReadonlyArray<RenderedInstanceOverride>): DocumentProjectionReport[];
|
|
47
|
+
/** Registers controller-owned overrides without projecting them into the editor shell. */
|
|
48
|
+
export declare function setCanonicalRenderedInstanceOverrides(overrides: ReadonlyArray<RenderedInstanceOverride>): void;
|
|
47
49
|
export declare function getRenderedInstanceProjectionReports(doc: Document): readonly DocumentProjectionReport[];
|
|
48
50
|
/** Diagnostics arrive independently when a renderer reports or React reconciles. */
|
|
49
51
|
export declare function subscribeRenderedInstanceDiagnostics(listener: () => void): () => void;
|
|
@@ -213,7 +213,7 @@ export function applyRenderedInstanceProjection(doc, overrides) {
|
|
|
213
213
|
if (!isNudgeUiDev())
|
|
214
214
|
return [];
|
|
215
215
|
const state = getDocumentState(doc);
|
|
216
|
-
|
|
216
|
+
setCanonicalRenderedInstanceOverrides(overrides);
|
|
217
217
|
const key = JSON.stringify(overrides);
|
|
218
218
|
if (state.snapshotKey === key) {
|
|
219
219
|
const reports = reportsForSnapshot(state, overrides);
|
|
@@ -239,6 +239,10 @@ export function applyRenderedInstanceProjection(doc, overrides) {
|
|
|
239
239
|
storeReports(doc, reports);
|
|
240
240
|
return reports;
|
|
241
241
|
}
|
|
242
|
+
/** Registers controller-owned overrides without projecting them into the editor shell. */
|
|
243
|
+
export function setCanonicalRenderedInstanceOverrides(overrides) {
|
|
244
|
+
canonicalOverrides = new Map(overrides.map((override) => [override.id, override]));
|
|
245
|
+
}
|
|
242
246
|
export function getRenderedInstanceProjectionReports(doc) {
|
|
243
247
|
return reportsByDocument.get(doc) ?? [];
|
|
244
248
|
}
|
|
@@ -29,6 +29,8 @@ export declare function getStructuralMoveLegality(element: HTMLElement, destinat
|
|
|
29
29
|
export interface StructuralChangeDiagnostic extends StructuralProjectionReport {
|
|
30
30
|
document: "Inspect" | `Canvas ${string}`;
|
|
31
31
|
}
|
|
32
|
+
/** Returns bounded view state used to verify a delete in the same rendered view. */
|
|
33
|
+
export declare function documentStateKey(doc: Document, excluded?: HTMLElement): string | undefined;
|
|
32
34
|
/** Captures a delete intent once, in the controller, at the user gesture. */
|
|
33
35
|
export declare function createStructuralDelete(element: HTMLElement, id?: string): StructuralDelete | null;
|
|
34
36
|
/**
|
|
@@ -95,6 +95,82 @@ function notifyDiagnostics() {
|
|
|
95
95
|
function structuralId() {
|
|
96
96
|
return `structural-${crypto.randomUUID?.() ?? nextStructuralId++}`;
|
|
97
97
|
}
|
|
98
|
+
function documentRoute(doc) {
|
|
99
|
+
if (!doc.location)
|
|
100
|
+
return undefined;
|
|
101
|
+
try {
|
|
102
|
+
const url = new URL(doc.location.href);
|
|
103
|
+
url.hash = "";
|
|
104
|
+
return url.href;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function stableStateValue(value, seen = new WeakSet()) {
|
|
111
|
+
if (!value || typeof value !== "object")
|
|
112
|
+
return value;
|
|
113
|
+
if (seen.has(value))
|
|
114
|
+
return "[Circular]";
|
|
115
|
+
seen.add(value);
|
|
116
|
+
if (Array.isArray(value))
|
|
117
|
+
return value.map((entry) => stableStateValue(entry, seen));
|
|
118
|
+
return Object.fromEntries(Object.entries(value)
|
|
119
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
120
|
+
.map(([key, entry]) => [key, stableStateValue(entry, seen)]));
|
|
121
|
+
}
|
|
122
|
+
function textWithoutExcluded(element, excluded) {
|
|
123
|
+
let text = "";
|
|
124
|
+
const visit = (node) => {
|
|
125
|
+
if (node === excluded)
|
|
126
|
+
return;
|
|
127
|
+
if (node.nodeType === node.TEXT_NODE) {
|
|
128
|
+
text += node.nodeValue ?? "";
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
node.childNodes.forEach(visit);
|
|
132
|
+
};
|
|
133
|
+
visit(element);
|
|
134
|
+
return text.replace(/\s+/g, " ").trim().slice(0, 120) || null;
|
|
135
|
+
}
|
|
136
|
+
function renderedInstanceState(doc, excluded) {
|
|
137
|
+
try {
|
|
138
|
+
return Array.from(doc.querySelectorAll("[data-cid][data-src]"))
|
|
139
|
+
.filter((element) => element !== excluded && !excluded?.contains(element))
|
|
140
|
+
.map((element) => ({
|
|
141
|
+
tag: element.tagName.toLowerCase(),
|
|
142
|
+
cid: element.getAttribute("data-cid"),
|
|
143
|
+
src: element.getAttribute("data-src"),
|
|
144
|
+
props: element.getAttribute("data-cprops"),
|
|
145
|
+
text: textWithoutExcluded(element, excluded),
|
|
146
|
+
ariaLabel: element.getAttribute("aria-label"),
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Returns bounded view state used to verify a delete in the same rendered view. */
|
|
154
|
+
export function documentStateKey(doc, excluded) {
|
|
155
|
+
const view = doc.defaultView;
|
|
156
|
+
if (!view || !doc.location)
|
|
157
|
+
return undefined;
|
|
158
|
+
try {
|
|
159
|
+
const url = new URL(doc.location.href);
|
|
160
|
+
const rendered = renderedInstanceState(doc, excluded);
|
|
161
|
+
if (!rendered)
|
|
162
|
+
return undefined;
|
|
163
|
+
const state = JSON.stringify({
|
|
164
|
+
hash: url.hash,
|
|
165
|
+
history: stableStateValue(view.history.state),
|
|
166
|
+
rendered,
|
|
167
|
+
});
|
|
168
|
+
return state.length <= 4096 ? state : undefined;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
98
174
|
function cloneSnapshot(snapshot) {
|
|
99
175
|
// Structural changes are small JSON-safe records. Cloning their top-level
|
|
100
176
|
// array is enough: no operation mutates a change or its nested references.
|
|
@@ -108,7 +184,15 @@ export function createStructuralDelete(element, id = structuralId()) {
|
|
|
108
184
|
const target = captureRenderedInstance(element);
|
|
109
185
|
if (!target)
|
|
110
186
|
return null;
|
|
111
|
-
const
|
|
187
|
+
const route = documentRoute(element.ownerDocument);
|
|
188
|
+
const state = documentStateKey(element.ownerDocument, element);
|
|
189
|
+
const change = {
|
|
190
|
+
id,
|
|
191
|
+
kind: "delete",
|
|
192
|
+
target,
|
|
193
|
+
...(route ? { route } : {}),
|
|
194
|
+
...(state ? { state } : {}),
|
|
195
|
+
};
|
|
112
196
|
if (!workspaceChangeStore.commitStructuralChange(change))
|
|
113
197
|
return null;
|
|
114
198
|
projectStructuralChanges(workspaceChangeStore.getSnapshot());
|
|
@@ -18,12 +18,28 @@ export function isStructuralDelete(value) {
|
|
|
18
18
|
if (!value || typeof value !== "object")
|
|
19
19
|
return false;
|
|
20
20
|
const candidate = value;
|
|
21
|
-
if (!hasOnlyKeys(candidate, ["id", "kind", "target"]))
|
|
21
|
+
if (!hasOnlyKeys(candidate, ["id", "kind", "target", "route", "state"]))
|
|
22
22
|
return false;
|
|
23
23
|
return candidate.kind === "delete"
|
|
24
24
|
&& typeof candidate.id === "string"
|
|
25
|
+
&& (candidate.route === undefined || isRoute(candidate.route))
|
|
26
|
+
&& (candidate.state === undefined || isStateKey(candidate.state))
|
|
25
27
|
&& isRenderedInstanceRef(candidate.target);
|
|
26
28
|
}
|
|
29
|
+
function isStateKey(value) {
|
|
30
|
+
return typeof value === "string" && value.length > 0 && value.length <= 4096;
|
|
31
|
+
}
|
|
32
|
+
function isRoute(value) {
|
|
33
|
+
if (typeof value !== "string" || value.length > 2048)
|
|
34
|
+
return false;
|
|
35
|
+
try {
|
|
36
|
+
const url = new URL(value);
|
|
37
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.hash === "";
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
27
43
|
export function isStructuralMove(value) {
|
|
28
44
|
if (!value || typeof value !== "object")
|
|
29
45
|
return false;
|
|
@@ -43,6 +43,8 @@ export declare function collectTextContentChanges(changes: ReadonlyArray<unknown
|
|
|
43
43
|
export declare function getTextContentChangeById(changeId: string): TextContentChangeRecord | null;
|
|
44
44
|
/** Apply one controller-owned text snapshot to a document adapter. */
|
|
45
45
|
export declare function applyTextContentProjection(doc: Document, changes: ReadonlyArray<TextContentChangeRecord>): TextProjectionReport[];
|
|
46
|
+
/** Registers controller-owned text changes without projecting them into the editor shell. */
|
|
47
|
+
export declare function setCanonicalTextContentChanges(changes: ReadonlyArray<TextContentChangeRecord>): void;
|
|
46
48
|
export declare function getTextProjectionReports(doc: Document): readonly TextProjectionReport[];
|
|
47
49
|
export declare function subscribeTextProjectionDiagnostics(listener: () => void): () => void;
|
|
48
50
|
export declare function getTextProjectionDiagnosticRevision(): number;
|
|
@@ -42,7 +42,8 @@ function markerKey(target) {
|
|
|
42
42
|
return target.textNodePath ? `path:${target.textNodePath.join(",")}` : "root";
|
|
43
43
|
}
|
|
44
44
|
function isEmptyTextAffordance(node) {
|
|
45
|
-
return node
|
|
45
|
+
return node?.nodeType === 1
|
|
46
|
+
&& node.hasAttribute(EMPTY_TEXT_PROJECTION_ATTR);
|
|
46
47
|
}
|
|
47
48
|
function hasInlineTextEditor(element) {
|
|
48
49
|
return element.querySelector('[data-inline-editor="true"]') !== null;
|
|
@@ -308,8 +309,9 @@ function emptyTextAffordanceFor(doc, state, change, element) {
|
|
|
308
309
|
if (!textNode || textNode.nodeValue !== "")
|
|
309
310
|
return null;
|
|
310
311
|
const parent = textNode.parentNode;
|
|
311
|
-
if (
|
|
312
|
+
if (parent?.nodeType !== 1)
|
|
312
313
|
return null;
|
|
314
|
+
const parentElement = parent;
|
|
313
315
|
const path = markerKey(change.target);
|
|
314
316
|
// During re-entry the temporary editor wraps the empty Text node. The
|
|
315
317
|
// existing slot remains beside that wrapper; search the whole projected
|
|
@@ -332,7 +334,7 @@ function emptyTextAffordanceFor(doc, state, change, element) {
|
|
|
332
334
|
// It has no application text and is never a source identity. The
|
|
333
335
|
// interaction stylesheet supplies its visible, borderless hit geometry.
|
|
334
336
|
affordance.hidden = !isInteractionStylesInstalled(doc);
|
|
335
|
-
|
|
337
|
+
parentElement.insertBefore(affordance, textNode.nextSibling);
|
|
336
338
|
state.emptyAffordances.set(change.id, affordance);
|
|
337
339
|
return affordance;
|
|
338
340
|
}
|
|
@@ -546,7 +548,7 @@ export function applyTextContentProjection(doc, changes) {
|
|
|
546
548
|
if (!isNudgeUiDev())
|
|
547
549
|
return [];
|
|
548
550
|
const state = getDocumentState(doc);
|
|
549
|
-
|
|
551
|
+
setCanonicalTextContentChanges(changes);
|
|
550
552
|
const key = JSON.stringify(changes);
|
|
551
553
|
if (state.snapshotKey === key) {
|
|
552
554
|
// Reconciliation can happen between controller snapshots. Validate before
|
|
@@ -624,6 +626,10 @@ export function applyTextContentProjection(doc, changes) {
|
|
|
624
626
|
storeReports(doc, reports);
|
|
625
627
|
return reports;
|
|
626
628
|
}
|
|
629
|
+
/** Registers controller-owned text changes without projecting them into the editor shell. */
|
|
630
|
+
export function setCanonicalTextContentChanges(changes) {
|
|
631
|
+
canonicalChanges = new Map(changes.map((change) => [change.id, change]));
|
|
632
|
+
}
|
|
627
633
|
export function getTextProjectionReports(doc) {
|
|
628
634
|
return reportsByDocument.get(doc) ?? [];
|
|
629
635
|
}
|
|
@@ -25,7 +25,7 @@ export declare function getLastClipboardReconciledCount(): number;
|
|
|
25
25
|
* Checks a copied prompt against source-rendered output and reconciles only
|
|
26
26
|
* exact records that the browser can positively verify.
|
|
27
27
|
*/
|
|
28
|
-
export declare function reconcileClipboardHandoff(): Promise<number>;
|
|
28
|
+
export declare function reconcileClipboardHandoff(doc?: Document): Promise<number>;
|
|
29
29
|
/**
|
|
30
30
|
* Watches source-driven document refreshes and page-return signals. The
|
|
31
31
|
* trailing debounce lets an HMR commit settle before verification lifts the
|