nudge-ui 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -46
- package/dist/client.mjs +252 -54
- package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
- package/dist/css/value-semantics/colorSemantics.js +17 -0
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
- package/dist/hosts/astro/clientTransport.js +41 -32
- package/dist/hosts/astro/integration.d.ts +2 -2
- package/dist/hosts/astro/integration.js +9 -4
- package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
- package/dist/hosts/astro/markdownIdentity.js +81 -0
- package/dist/hosts/environment.d.ts +15 -0
- package/dist/hosts/environment.js +16 -0
- package/dist/hosts/next/manifest.js +1 -1
- package/dist/hosts/next/sidecar.js +12 -2
- package/dist/hosts/next/wrapper.d.ts +2 -0
- package/dist/hosts/next/wrapper.js +2 -1
- package/dist/hosts/projectBridge.d.ts +1 -0
- package/dist/hosts/projectBridge.js +1 -0
- package/dist/hosts/static/manifest.js +3 -1
- package/dist/hosts/static/server.js +4 -0
- package/dist/hosts/vite/vite.d.ts +4 -0
- package/dist/hosts/vite/vite.js +28 -5
- package/dist/inspector/agent/McpConnectionDialog.d.ts +3 -2
- package/dist/inspector/agent/McpConnectionDialog.js +23 -22
- package/dist/inspector/agent/activity.d.ts +18 -0
- package/dist/inspector/agent/activity.js +70 -0
- package/dist/inspector/agent/client.d.ts +8 -0
- package/dist/inspector/agent/client.js +65 -5
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +9 -2
- package/dist/inspector/agent/httpTransport.d.ts +6 -0
- package/dist/inspector/agent/httpTransport.js +50 -3
- package/dist/inspector/agent/protocol.d.ts +9 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +11 -1
- package/dist/inspector/agent/verification.js +53 -25
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
- package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
- package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
- package/dist/inspector/canvas/CanvasCard.js +133 -38
- package/dist/inspector/canvas/CanvasElementOverlay.js +200 -22
- package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
- package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
- package/dist/inspector/canvas/CanvasToolbar.js +8 -4
- package/dist/inspector/canvas/CanvasWorkspace.js +243 -56
- package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
- package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
- package/dist/inspector/canvas/agentPresentation.js +64 -52
- package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
- package/dist/inspector/canvas/canvasGestures.js +1 -1
- package/dist/inspector/canvas/canvasStore.d.ts +46 -82
- package/dist/inspector/canvas/canvasStore.js +251 -284
- package/dist/inspector/canvas/frameContent.d.ts +21 -0
- package/dist/inspector/canvas/frameContent.js +44 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +34 -4
- package/dist/inspector/canvas/frameProtocol.js +9 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +5 -3
- package/dist/inspector/canvas/layout.d.ts +52 -0
- package/dist/inspector/canvas/layout.js +106 -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 +15 -5
- package/dist/inspector/canvas/projection.js +69 -13
- package/dist/inspector/canvas/rendererBootstrap.js +45 -4
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +102 -45
- package/dist/inspector/canvas/rendererStylesheet.js +2 -2
- package/dist/inspector/canvas/resizeHandleScale.js +1 -1
- package/dist/inspector/canvas/sessionStore.d.ts +11 -26
- package/dist/inspector/canvas/sessionStore.js +43 -183
- package/dist/inspector/canvas/staleChangeDetector.js +9 -3
- package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
- package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
- package/dist/inspector/canvas/viewStore.d.ts +38 -0
- package/dist/inspector/canvas/viewStore.js +104 -0
- package/dist/inspector/canvas/wheelPan.d.ts +8 -0
- package/dist/inspector/canvas/wheelPan.js +8 -0
- package/dist/inspector/changes/changesLog.d.ts +6 -5
- package/dist/inspector/changes/changesLog.js +40 -32
- package/dist/inspector/changes/draftChanges.d.ts +56 -0
- package/dist/inspector/changes/draftChanges.js +300 -0
- package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
- package/dist/inspector/changes/previewDiagnostics.js +11 -25
- package/dist/inspector/clientManifest.d.ts +3 -1
- package/dist/inspector/clientManifest.js +1 -0
- package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
- package/dist/inspector/comments/CommentsOverlay.js +250 -0
- package/dist/inspector/comments/element.d.ts +6 -0
- package/dist/inspector/comments/element.js +47 -0
- package/dist/inspector/comments/store.d.ts +29 -0
- package/dist/inspector/comments/store.js +121 -0
- package/dist/inspector/comments/verification.d.ts +5 -0
- package/dist/inspector/comments/verification.js +52 -0
- package/dist/inspector/drafts/model.d.ts +35 -0
- package/dist/inspector/drafts/model.js +45 -0
- package/dist/inspector/drafts/store.d.ts +28 -0
- package/dist/inspector/drafts/store.js +231 -0
- package/dist/inspector/index.js +26 -35
- package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
- package/dist/inspector/iterations/capture.d.ts +2 -0
- package/dist/inspector/iterations/capture.js +173 -0
- package/dist/inspector/iterations/client.d.ts +7 -0
- package/dist/inspector/iterations/client.js +52 -0
- package/dist/inspector/iterations/coordinator.d.ts +15 -0
- package/dist/inspector/iterations/coordinator.js +130 -0
- package/dist/inspector/iterations/prompt.d.ts +3 -0
- package/dist/inspector/iterations/prompt.js +26 -0
- package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
- package/dist/inspector/overlay/DomNavigator.js +46 -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/interactionStyles.js +3 -1
- package/dist/inspector/overlay/spacingGestures.d.ts +15 -3
- package/dist/inspector/overlay/spacingGestures.js +88 -13
- package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
- package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
- package/dist/inspector/projection/renderedInstance.d.ts +2 -1
- package/dist/inspector/projection/renderedInstance.js +9 -5
- package/dist/inspector/projection/structuralProjection.d.ts +2 -1
- package/dist/inspector/projection/structuralProjection.js +19 -15
- package/dist/inspector/projection/textProjection.d.ts +2 -1
- package/dist/inspector/projection/textProjection.js +8 -4
- package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
- package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
- package/dist/inspector/prompt/clipboardHandoff.js +92 -143
- package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
- package/dist/inspector/prompt/generatePrompt.js +17 -16
- package/dist/inspector/runtime/runtimeConfig.d.ts +10 -1
- package/dist/inspector/runtime/runtimeConfig.js +24 -0
- package/dist/inspector/selection/hierarchy.d.ts +6 -0
- package/dist/inspector/selection/hierarchy.js +8 -0
- package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
- package/dist/inspector/settings/SettingsDialog.js +22 -9
- package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
- package/dist/inspector/shell/AgentStatusButton.js +36 -0
- package/dist/inspector/shell/AppShell.js +4 -2
- package/dist/inspector/shell/ChangesLog.js +47 -27
- package/dist/inspector/shell/CopyPromptButton.js +146 -90
- package/dist/inspector/shell/EmptyState.js +9 -8
- package/dist/inspector/shell/InspectorShell.js +14 -25
- package/dist/inspector/shell/PeekOriginalButton.js +24 -23
- package/dist/inspector/shell/originalPreview.js +5 -5
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +11 -0
- package/dist/inspector/sketch/SketchEditor.js +14 -14
- package/dist/inspector/sketch/SketchEntryButton.js +1 -1
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
- package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
- package/dist/inspector/sketch/SketchOverlay.js +7 -7
- package/dist/inspector/sketch/SketchWorkspace.js +18 -12
- package/dist/inspector/sketch/interaction.d.ts +1 -0
- package/dist/inspector/sketch/interaction.js +8 -4
- package/dist/inspector/sketch/model.d.ts +5 -0
- package/dist/inspector/sketch/model.js +6 -2
- package/dist/inspector/sketch/raster.d.ts +1 -2
- package/dist/inspector/sketch/raster.js +68 -4
- package/dist/inspector/sketch/store.js +48 -23
- package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
- package/dist/inspector/styleEditors/BorderEditor.js +49 -78
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
- package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
- package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
- package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
- package/dist/inspector/styleEditors/ColorPicker.js +10 -4
- package/dist/inspector/styleEditors/GapField.js +3 -5
- package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
- package/dist/inspector/styleEditors/GridChildSection.js +18 -9
- package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
- package/dist/inspector/styleEditors/GridPicker.js +8 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/GridValueField.js +19 -14
- package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
- package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
- package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
- package/dist/inspector/styleEditors/LayoutSection.js +32 -11
- package/dist/inspector/styleEditors/SpacingBox.js +16 -9
- package/dist/inspector/styleEditors/Typography.js +11 -3
- package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
- package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
- package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
- package/dist/inspector/styleEditors/gradientValue.js +114 -0
- package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
- package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
- package/dist/inspector/styleEditors/layoutValue.js +0 -42
- package/dist/inspector/styleEditors/nudgeValue.d.ts +4 -4
- package/dist/inspector/styleEditors/nudgeValue.js +41 -11
- package/dist/inspector/tokens/TokenField.d.ts +7 -0
- package/dist/inspector/tokens/TokenField.js +49 -22
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- 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 +1 -1
- package/dist/inspector/tokens/editActions.js +1 -1
- package/dist/inspector/ui/AtRuleContext.js +4 -6
- package/dist/inspector/ui/Button.d.ts +6 -2
- package/dist/inspector/ui/Button.js +4 -3
- package/dist/inspector/ui/ControlSurface.d.ts +2 -2
- package/dist/inspector/ui/ControlSurface.js +4 -3
- package/dist/inspector/ui/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
- package/dist/inspector/ui/InlineStyleWarning.js +13 -0
- package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
- package/dist/inspector/ui/InspectorPopover.js +4 -5
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +8 -7
- package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
- package/dist/inspector/ui/SegmentedControl.js +2 -2
- package/dist/inspector/ui/Select.d.ts +6 -1
- package/dist/inspector/ui/Select.js +7 -11
- package/dist/inspector/ui/SideValuesField.js +1 -1
- package/dist/inspector/ui/Tooltip.d.ts +26 -0
- package/dist/inspector/ui/Tooltip.js +32 -0
- package/dist/inspector/ui/classNames.d.ts +2 -0
- package/dist/inspector/ui/classNames.js +4 -0
- package/dist/inspector/ui/customCursors.d.ts +23 -0
- package/dist/inspector/ui/customCursors.js +122 -0
- package/dist/inspector/ui/dragCursor.d.ts +6 -0
- package/dist/inspector/ui/dragCursor.js +13 -0
- package/dist/inspector/ui/portalContainer.d.ts +2 -0
- package/dist/inspector/ui/portalContainer.js +6 -0
- package/dist/inspector/ui/styles.js +34 -24
- package/dist/inspector/workspace/commands.d.ts +9 -0
- package/dist/inspector/workspace/commands.js +82 -0
- package/dist/inspector/workspace/controller.d.ts +4 -0
- package/dist/inspector/workspace/controller.js +66 -0
- package/dist/inspector/workspace/handoff.d.ts +42 -0
- package/dist/inspector/workspace/handoff.js +115 -0
- package/dist/inspector/workspace/timeline.d.ts +30 -0
- package/dist/inspector/workspace/timeline.js +108 -0
- package/dist/internal/agent-protocol.d.ts +19 -2
- package/dist/internal/agent-protocol.js +13 -0
- package/dist/nudge-ui.mjs +560 -299
- package/dist/project/artifacts.d.ts +13 -0
- package/dist/project/artifacts.js +253 -0
- package/dist/transport/artifacts.d.ts +8 -0
- package/dist/transport/artifacts.js +9 -0
- package/dist/transport/editor.d.ts +20 -1
- package/dist/transport/editor.js +44 -11
- package/dist/transport/routes.d.ts +6 -1
- package/dist/transport/routes.js +6 -1
- package/package.json +6 -2
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -43
- package/dist/inspector/changes/workspaceChanges.js +0 -239
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { HTML_ARTIFACT_REVISION_ATTRIBUTE } from "../../transport/artifacts.js";
|
|
2
|
+
/** Captures the visible DOM of a same-origin frame as a script-free HTML iteration. */
|
|
3
|
+
export async function captureHtmlIteration(frame) {
|
|
4
|
+
const source = frame.contentDocument;
|
|
5
|
+
const view = frame.contentWindow;
|
|
6
|
+
if (!source || !view?.document.documentElement)
|
|
7
|
+
throw new Error("The frame is not ready to capture.");
|
|
8
|
+
await source.fonts?.ready;
|
|
9
|
+
await Promise.all([...source.images].map((image) => image.decode?.().catch(() => undefined)));
|
|
10
|
+
const original = source.documentElement;
|
|
11
|
+
const copy = original.cloneNode(true);
|
|
12
|
+
copy.removeAttribute(HTML_ARTIFACT_REVISION_ATTRIBUTE);
|
|
13
|
+
const sourceElements = [original, ...original.querySelectorAll("*")];
|
|
14
|
+
const copiedElements = [copy, ...copy.querySelectorAll("*")];
|
|
15
|
+
const assets = new Map();
|
|
16
|
+
for (let index = 0; index < sourceElements.length; index += 1) {
|
|
17
|
+
const sourceElement = sourceElements[index];
|
|
18
|
+
const copied = copiedElements[index];
|
|
19
|
+
if (sourceElement.nodeType !== 1 || copied.nodeType !== 1)
|
|
20
|
+
continue;
|
|
21
|
+
// Renderer IDs belong to the source iframe's node registry. The iteration's
|
|
22
|
+
// fresh registry can reuse those values for different nodes.
|
|
23
|
+
copied.removeAttribute("data-renderer-id");
|
|
24
|
+
// Preserve authored inline values (percentages, variables, and calc), rather
|
|
25
|
+
// than replacing them with computed dimensions at the capture viewport.
|
|
26
|
+
const inline = copied.getAttribute("style");
|
|
27
|
+
if (inline)
|
|
28
|
+
copied.setAttribute("style", await resolveCssUrls(inline, source.baseURI, assets));
|
|
29
|
+
if (sourceElement.tagName === "INPUT" && copied.tagName === "INPUT") {
|
|
30
|
+
const input = sourceElement;
|
|
31
|
+
copied.setAttribute("value", input.value);
|
|
32
|
+
if (input.checked)
|
|
33
|
+
copied.setAttribute("checked", "");
|
|
34
|
+
else
|
|
35
|
+
copied.removeAttribute("checked");
|
|
36
|
+
}
|
|
37
|
+
if (sourceElement.tagName === "TEXTAREA" && copied.tagName === "TEXTAREA")
|
|
38
|
+
copied.textContent = sourceElement.value;
|
|
39
|
+
if (sourceElement.tagName === "CANVAS" && copied.tagName === "CANVAS") {
|
|
40
|
+
try {
|
|
41
|
+
const image = source.createElement("img");
|
|
42
|
+
image.src = sourceElement.toDataURL();
|
|
43
|
+
for (const attribute of [...copied.attributes])
|
|
44
|
+
image.setAttribute(attribute.name, attribute.value);
|
|
45
|
+
copied.replaceWith(image);
|
|
46
|
+
}
|
|
47
|
+
catch { /* Tainted canvases remain empty. */ }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const base = source.createElement("base");
|
|
51
|
+
base.href = source.querySelector("base")?.href ?? source.location.href;
|
|
52
|
+
copy.querySelectorAll("base").forEach((element) => element.remove());
|
|
53
|
+
(copy.querySelector("head") ?? copy).prepend(base);
|
|
54
|
+
copy.querySelectorAll('script, iframe, object, embed, meta[http-equiv="refresh"], meta[http-equiv="Content-Security-Policy"], link[rel=modulepreload], link[rel=preload]').forEach((element) => element.remove());
|
|
55
|
+
copy.querySelectorAll("[data-nudge-ui-mount], #nudge-ui-root").forEach((element) => element.remove());
|
|
56
|
+
copy.querySelectorAll("a, form").forEach((element) => {
|
|
57
|
+
element.removeAttribute("href");
|
|
58
|
+
element.removeAttribute("action");
|
|
59
|
+
});
|
|
60
|
+
copy.querySelectorAll("*").forEach((element) => {
|
|
61
|
+
for (const attribute of [...element.attributes]) {
|
|
62
|
+
if (/^on/i.test(attribute.name))
|
|
63
|
+
element.removeAttribute(attribute.name);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
for (const image of copy.querySelectorAll("img")) {
|
|
67
|
+
const url = image.getAttribute("src");
|
|
68
|
+
if (url)
|
|
69
|
+
image.src = await inlineAsset(url, base.href, assets);
|
|
70
|
+
}
|
|
71
|
+
await captureStylesheets(source, copy, sourceElements, copiedElements, assets);
|
|
72
|
+
return `<!doctype html>\n${copy.outerHTML}`;
|
|
73
|
+
}
|
|
74
|
+
/** Copies live CSSOM rules in place so stylesheet order and media stay intact. */
|
|
75
|
+
async function captureStylesheets(document, copy, sourceElements, copiedElements, assets) {
|
|
76
|
+
for (const sheet of [...document.styleSheets]) {
|
|
77
|
+
const owner = sheet.ownerNode;
|
|
78
|
+
// Some DOM implementations do not expose ownerNode.
|
|
79
|
+
const sourceNode = owner ?? sourceElements.find((element) => "sheet" in element && element.sheet === sheet);
|
|
80
|
+
const index = sourceNode ? sourceElements.indexOf(sourceNode) : -1;
|
|
81
|
+
const copiedNode = index >= 0 ? copiedElements[index] : undefined;
|
|
82
|
+
if (!copiedNode?.parentNode)
|
|
83
|
+
continue;
|
|
84
|
+
if (sheet.disabled) {
|
|
85
|
+
copiedNode.remove();
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
let css;
|
|
89
|
+
try {
|
|
90
|
+
css = [...sheet.cssRules].map((rule) => rule.cssText).join("\n");
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
if (!sheet.href)
|
|
94
|
+
continue;
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch(sheet.href);
|
|
97
|
+
if (!response.ok)
|
|
98
|
+
continue;
|
|
99
|
+
css = await response.text();
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Keep the stylesheet link when CORS prevents embedding it. Its URL
|
|
103
|
+
// still resolves against the original base and retains responsiveness.
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const style = document.createElement("style");
|
|
108
|
+
for (const attribute of [...copiedNode.attributes]) {
|
|
109
|
+
if (!["href", "rel", "integrity", "crossorigin"].includes(attribute.name)) {
|
|
110
|
+
style.setAttribute(attribute.name, attribute.value);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
style.textContent = await resolveCssUrls(css, sheet.href ?? document.baseURI, assets);
|
|
114
|
+
// Captured edits become the iteration's base. The renderer must create its own
|
|
115
|
+
// managed stylesheet for new edits instead of clearing these captured rules.
|
|
116
|
+
if (style.id === "nudge-ui-styles") {
|
|
117
|
+
style.removeAttribute("id");
|
|
118
|
+
style.removeAttribute("data-nudge-ui");
|
|
119
|
+
style.setAttribute("data-nudge-capture-overrides", "");
|
|
120
|
+
}
|
|
121
|
+
copiedNode.replaceWith(style);
|
|
122
|
+
}
|
|
123
|
+
for (const sheet of document.adoptedStyleSheets ?? []) {
|
|
124
|
+
if (sheet.disabled)
|
|
125
|
+
continue;
|
|
126
|
+
const style = document.createElement("style");
|
|
127
|
+
style.textContent = await resolveCssUrls([...sheet.cssRules].map((rule) => rule.cssText).join("\n"), document.baseURI, assets);
|
|
128
|
+
(copy.querySelector("head") ?? copy).append(style);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function resolveCssUrls(css, base, assets) {
|
|
132
|
+
const matches = [...css.matchAll(/url\(\s*(["']?)([^)"']+)\1\s*\)/g)];
|
|
133
|
+
const values = await Promise.all(matches.map((match) => inlineAsset(match[2], base, assets)));
|
|
134
|
+
let index = 0;
|
|
135
|
+
return css.replace(/url\(\s*(["']?)([^)"']+)\1\s*\)/g, () => `url("${values[index++]}")`);
|
|
136
|
+
}
|
|
137
|
+
async function inlineAsset(path, base, assets) {
|
|
138
|
+
if (path.trim().startsWith("#"))
|
|
139
|
+
return path;
|
|
140
|
+
let url;
|
|
141
|
+
try {
|
|
142
|
+
url = new URL(path, base).href;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return path;
|
|
146
|
+
}
|
|
147
|
+
if (!/^https?:/.test(url))
|
|
148
|
+
return url;
|
|
149
|
+
const existing = assets.get(url);
|
|
150
|
+
if (existing)
|
|
151
|
+
return existing;
|
|
152
|
+
const value = (async () => {
|
|
153
|
+
try {
|
|
154
|
+
const response = await fetch(url);
|
|
155
|
+
if (!response.ok || Number(response.headers.get("content-length")) > 4_000_000)
|
|
156
|
+
return url;
|
|
157
|
+
const blob = await response.blob();
|
|
158
|
+
if (blob.size > 4_000_000)
|
|
159
|
+
return url;
|
|
160
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
161
|
+
let binary = "";
|
|
162
|
+
for (let offset = 0; offset < bytes.length; offset += 8192) {
|
|
163
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 8192));
|
|
164
|
+
}
|
|
165
|
+
return `data:${blob.type || "application/octet-stream"};base64,${btoa(binary)}`;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return url;
|
|
169
|
+
}
|
|
170
|
+
})();
|
|
171
|
+
assets.set(url, value);
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type HtmlArtifactRevision } from "../../transport/artifacts.js";
|
|
2
|
+
export declare function artifactDocumentUrl(id: string): string;
|
|
3
|
+
export declare function readHtmlArtifactRevision(id: string): Promise<HtmlArtifactRevision>;
|
|
4
|
+
/** Checks the editing base before saving and promoting a captured document. */
|
|
5
|
+
export declare function commitHtmlArtifact(id: string, expected: HtmlArtifactRevision, html?: string): Promise<HtmlArtifactRevision>;
|
|
6
|
+
export declare function createHtmlArtifact(frame: HTMLIFrameElement, sourceUrl: string, title: string, capturedHtml?: string): Promise<string>;
|
|
7
|
+
export declare function removeHtmlArtifact(id: string): Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isHtmlArtifactRevision, HTML_ITERATION_CONFLICT } from "../../transport/artifacts.js";
|
|
2
|
+
import { NUDGE_UI_ARTIFACTS_PATH } from "../../transport/routes.js";
|
|
3
|
+
import { captureHtmlIteration } from "./capture.js";
|
|
4
|
+
export function artifactDocumentUrl(id) {
|
|
5
|
+
return `${NUDGE_UI_ARTIFACTS_PATH}/${id}/preview`;
|
|
6
|
+
}
|
|
7
|
+
export async function readHtmlArtifactRevision(id) {
|
|
8
|
+
const response = await fetch(`${NUDGE_UI_ARTIFACTS_PATH}/${id}/revision`, { cache: "no-store", signal: AbortSignal.timeout(10000) });
|
|
9
|
+
if (!response.ok)
|
|
10
|
+
throw new Error("The HTML iteration could not be refreshed.");
|
|
11
|
+
const revision = await response.json();
|
|
12
|
+
if (!isHtmlArtifactRevision(revision))
|
|
13
|
+
throw new Error("The HTML iteration returned an invalid revision.");
|
|
14
|
+
return revision;
|
|
15
|
+
}
|
|
16
|
+
/** Checks the editing base before saving and promoting a captured document. */
|
|
17
|
+
export async function commitHtmlArtifact(id, expected, html) {
|
|
18
|
+
const response = await fetch(`${NUDGE_UI_ARTIFACTS_PATH}/${id}/commit`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
signal: AbortSignal.timeout(10000),
|
|
21
|
+
headers: { "content-type": "application/json" },
|
|
22
|
+
body: JSON.stringify({ expected, ...(html === undefined ? {} : { html }) }),
|
|
23
|
+
});
|
|
24
|
+
if (response.status === 409)
|
|
25
|
+
throw new Error(HTML_ITERATION_CONFLICT);
|
|
26
|
+
if (!response.ok)
|
|
27
|
+
throw new Error((await response.text()) || "The HTML iteration could not be saved.");
|
|
28
|
+
const revision = await response.json();
|
|
29
|
+
if (!isHtmlArtifactRevision(revision))
|
|
30
|
+
throw new Error("The HTML iteration returned an invalid revision.");
|
|
31
|
+
return revision;
|
|
32
|
+
}
|
|
33
|
+
export async function createHtmlArtifact(frame, sourceUrl, title, capturedHtml) {
|
|
34
|
+
const id = crypto.randomUUID();
|
|
35
|
+
const html = capturedHtml ?? await captureHtmlIteration(frame);
|
|
36
|
+
const response = await fetch(NUDGE_UI_ARTIFACTS_PATH, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "content-type": "application/json" },
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
id, html, sourceUrl, title,
|
|
41
|
+
viewport: { width: frame.clientWidth, height: frame.clientHeight },
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok)
|
|
45
|
+
throw new Error((await response.text()) || "The HTML iteration could not be saved.");
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
export async function removeHtmlArtifact(id) {
|
|
49
|
+
const response = await fetch(`${NUDGE_UI_ARTIFACTS_PATH}/${id}`, { method: "DELETE", keepalive: true });
|
|
50
|
+
if (!response.ok)
|
|
51
|
+
throw new Error((await response.text()) || "The HTML iteration could not be removed.");
|
|
52
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HtmlArtifactRevision } from "../../transport/artifacts.js";
|
|
2
|
+
import { type DraftContents } from "../changes/draftChanges.js";
|
|
3
|
+
/** Owned transport; tests use a working in-memory artifact store. */
|
|
4
|
+
export interface HtmlIterationStorage {
|
|
5
|
+
readRevision(id: string): Promise<HtmlArtifactRevision>;
|
|
6
|
+
commit(id: string, expected: HtmlArtifactRevision, html?: string): Promise<HtmlArtifactRevision>;
|
|
7
|
+
}
|
|
8
|
+
/** Owns save ordering, draft settlement, and external document promotion. */
|
|
9
|
+
export declare function createHtmlIterationCoordinator(storage: HtmlIterationStorage): {
|
|
10
|
+
prepare(cardId: string, artifactId: string, baseRevision: string, capture: () => Promise<string>): Promise<DraftContents>;
|
|
11
|
+
refresh(cardId: string, artifactId: string, baseRevision: string, available: () => boolean): Promise<boolean>;
|
|
12
|
+
};
|
|
13
|
+
/** Materializes pending intent before the agent can start editing document.html. */
|
|
14
|
+
export declare function prepareHtmlIterationHandoff(cardId: string, artifactId: string, frame: HTMLIFrameElement): Promise<DraftContents>;
|
|
15
|
+
export declare function refreshHtmlIteration(cardId: string, artifactId: string, frame: HTMLIFrameElement): Promise<boolean>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { HTML_ARTIFACT_REVISION_ATTRIBUTE } from "../../transport/artifacts.js";
|
|
2
|
+
import { getDraftForCard, getDraftContentsForCard, clearSavedIterationDraft } from "../drafts/store.js";
|
|
3
|
+
import { lockDraft } from "../changes/draftChanges.js";
|
|
4
|
+
import { canWriteWorkspace } from "../canvas/workspaceLease.js";
|
|
5
|
+
import { projectDraftToDocument, isCanvasProjectionRevisionCurrent, projectToAllReadyCards } from "../canvas/projection.js";
|
|
6
|
+
import { isInlineTextEditingActive } from "../inline-text/inlineTextEditor.js";
|
|
7
|
+
import { captureHtmlIteration } from "./capture.js";
|
|
8
|
+
import { artifactDocumentUrl, commitHtmlArtifact, readHtmlArtifactRevision } from "./client.js";
|
|
9
|
+
/** Owns save ordering, draft settlement, and external document promotion. */
|
|
10
|
+
export function createHtmlIterationCoordinator(storage) {
|
|
11
|
+
const operations = new Map();
|
|
12
|
+
async function serialize(id, operation) {
|
|
13
|
+
const previous = operations.get(id) ?? Promise.resolve();
|
|
14
|
+
const pending = previous.catch(() => undefined).then(operation);
|
|
15
|
+
operations.set(id, pending);
|
|
16
|
+
try {
|
|
17
|
+
return await pending;
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
if (operations.get(id) === pending)
|
|
21
|
+
operations.delete(id);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
prepare(cardId, artifactId, baseRevision, capture) {
|
|
26
|
+
return serialize(artifactId, async () => {
|
|
27
|
+
if (!canWriteWorkspace())
|
|
28
|
+
throw new Error("This workspace is open in another tab.");
|
|
29
|
+
const draft = getDraftForCard(cardId);
|
|
30
|
+
const workspace = getDraftContentsForCard(cardId);
|
|
31
|
+
if (!draft || draft.target.kind !== "html" || draft.target.artifactId !== artifactId || !workspace)
|
|
32
|
+
throw new Error("The iteration draft is no longer available.");
|
|
33
|
+
const html = await capture();
|
|
34
|
+
const current = getDraftForCard(cardId);
|
|
35
|
+
if (current?.id !== draft.id || current.revision !== draft.revision) {
|
|
36
|
+
throw new Error("The iteration changed while saving. Your edits are still pending. Try again.");
|
|
37
|
+
}
|
|
38
|
+
const release = lockDraft(draft.id);
|
|
39
|
+
try {
|
|
40
|
+
if (!canWriteWorkspace())
|
|
41
|
+
throw new Error("This workspace is open in another tab.");
|
|
42
|
+
await storage.commit(artifactId, { document: baseRevision, preview: baseRevision }, html);
|
|
43
|
+
if (!canWriteWorkspace())
|
|
44
|
+
throw new Error("The iteration was saved. Reopen the active workspace to continue.");
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
release();
|
|
48
|
+
}
|
|
49
|
+
// No await between releasing the lock and settling this exact revision.
|
|
50
|
+
if (!clearSavedIterationDraft(cardId, draft.revision)) {
|
|
51
|
+
throw new Error("The iteration was saved, but its newer edits remain pending.");
|
|
52
|
+
}
|
|
53
|
+
return workspace;
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
refresh(cardId, artifactId, baseRevision, available) {
|
|
57
|
+
return serialize(artifactId, async () => {
|
|
58
|
+
const clean = () => {
|
|
59
|
+
const workspace = getDraftContentsForCard(cardId);
|
|
60
|
+
return canWriteWorkspace() && available() && workspace !== null
|
|
61
|
+
&& workspace.changes.length === 0 && workspace.structuralChanges.length === 0;
|
|
62
|
+
};
|
|
63
|
+
if (!clean())
|
|
64
|
+
return false;
|
|
65
|
+
const revision = await storage.readRevision(artifactId);
|
|
66
|
+
if (!clean())
|
|
67
|
+
return false;
|
|
68
|
+
// A previous promotion may have completed while an inline edit deferred reload.
|
|
69
|
+
if (revision.document === revision.preview)
|
|
70
|
+
return revision.preview !== baseRevision;
|
|
71
|
+
const draft = getDraftForCard(cardId);
|
|
72
|
+
if (!draft)
|
|
73
|
+
return false;
|
|
74
|
+
const release = lockDraft(draft.id);
|
|
75
|
+
try {
|
|
76
|
+
await storage.commit(artifactId, revision);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
release();
|
|
80
|
+
}
|
|
81
|
+
return clean();
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const coordinator = createHtmlIterationCoordinator({ readRevision: readHtmlArtifactRevision, commit: commitHtmlArtifact });
|
|
87
|
+
function loadedRevision(frame) {
|
|
88
|
+
const revision = frame.contentDocument?.documentElement.getAttribute(HTML_ARTIFACT_REVISION_ATTRIBUTE);
|
|
89
|
+
if (!revision || !/^[a-f0-9]{64}$/.test(revision))
|
|
90
|
+
throw new Error("The HTML iteration is still loading. Try again when the frame is ready.");
|
|
91
|
+
return revision;
|
|
92
|
+
}
|
|
93
|
+
/** Materializes pending intent before the agent can start editing document.html. */
|
|
94
|
+
export async function prepareHtmlIterationHandoff(cardId, artifactId, frame) {
|
|
95
|
+
if (isInlineTextEditingActive())
|
|
96
|
+
throw new Error("Finish editing the text before saving this iteration.");
|
|
97
|
+
const contents = getDraftContentsForCard(cardId);
|
|
98
|
+
if (!contents)
|
|
99
|
+
throw new Error("The iteration draft is no longer available.");
|
|
100
|
+
if (contents.changes.length + contents.structuralChanges.length === 0)
|
|
101
|
+
return contents;
|
|
102
|
+
const document = frame.contentDocument;
|
|
103
|
+
const baseRevision = loadedRevision(frame);
|
|
104
|
+
try {
|
|
105
|
+
const workspace = await coordinator.prepare(cardId, artifactId, baseRevision, async () => {
|
|
106
|
+
if (!document || isInlineTextEditingActive())
|
|
107
|
+
throw new Error("Finish editing the text before saving this iteration.");
|
|
108
|
+
const workspace = getDraftContentsForCard(cardId);
|
|
109
|
+
if (!workspace)
|
|
110
|
+
throw new Error("The iteration draft is no longer available.");
|
|
111
|
+
const revision = await projectDraftToDocument(document, workspace);
|
|
112
|
+
if (revision === null)
|
|
113
|
+
throw new Error("The iteration preview is still updating. Try again.");
|
|
114
|
+
const html = await captureHtmlIteration(frame);
|
|
115
|
+
if (frame.contentDocument !== document || !isCanvasProjectionRevisionCurrent(document, revision)) {
|
|
116
|
+
throw new Error("The iteration changed while saving. Your edits are still pending. Try again.");
|
|
117
|
+
}
|
|
118
|
+
return html;
|
|
119
|
+
});
|
|
120
|
+
frame.src = `${artifactDocumentUrl(artifactId)}?revision=${Date.now()}`;
|
|
121
|
+
return workspace;
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
projectToAllReadyCards();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export async function refreshHtmlIteration(cardId, artifactId, frame) {
|
|
128
|
+
const document = frame.contentDocument;
|
|
129
|
+
return coordinator.refresh(cardId, artifactId, loadedRevision(frame), () => frame.contentDocument === document && !isInlineTextEditingActive());
|
|
130
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Defines the edit destination for every HTML-iteration handoff, including fallback copies. */
|
|
2
|
+
export declare function htmlIterationPrompt(id: string, changes: string, noInspectorChanges: boolean, hasSketches: boolean, customInstructions?: string): string;
|
|
3
|
+
export declare function htmlIterationImplementationPrompt(id: string, label: string, page: string, changes: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Defines the edit destination for every HTML-iteration handoff, including fallback copies. */
|
|
2
|
+
export function htmlIterationPrompt(id, changes, noInspectorChanges, hasSketches, customInstructions = "") {
|
|
3
|
+
const directory = `.nudge/artifacts/${id}`;
|
|
4
|
+
return [
|
|
5
|
+
"# Edit this HTML iteration",
|
|
6
|
+
"",
|
|
7
|
+
`Edit only \`${directory}/document.html\`. This is the standalone HTML and CSS shown in the selected Nudge iteration frame.`,
|
|
8
|
+
"Preserve its responsive CSS and existing design edits. Keep application source files unchanged. Any original source locations below identify captured elements; they are not files to edit.",
|
|
9
|
+
`Keep \`${directory}/baseline.html\`, \`${directory}/preview.html\`, and metadata unchanged. Nudge will reload the iteration from your updated document.`,
|
|
10
|
+
...(hasSketches ? ["Implement the attached sketch notes in this iteration's HTML and CSS. Match numbered notes to their marks in the image."] : []),
|
|
11
|
+
"",
|
|
12
|
+
...(noInspectorChanges ? ["Use the current iteration as the editing base. Existing visual edits are already saved in its markup and styles.", ...(customInstructions.trim() ? ["", "## Custom instructions", "", customInstructions.trim()] : [])] : [changes]),
|
|
13
|
+
].join("\n");
|
|
14
|
+
}
|
|
15
|
+
export function htmlIterationImplementationPrompt(id, label, page, changes) {
|
|
16
|
+
const directory = `.nudge/artifacts/${id}`;
|
|
17
|
+
return [
|
|
18
|
+
`# Implement ${label} in the live app`,
|
|
19
|
+
"",
|
|
20
|
+
`Page: ${page}`,
|
|
21
|
+
`Use \`${directory}/document.html\` as the design reference, including all saved visual edits.`,
|
|
22
|
+
"Update application source for this page to match it. Reuse existing components and styles, and preserve app behavior and data.",
|
|
23
|
+
"Keep iteration files unchanged; captured source locations below are hints for finding app code.",
|
|
24
|
+
...(changes.trim() ? ["", changes] : []),
|
|
25
|
+
].join("\n");
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
|
+
import type { SelectedElement } from "../selection/selectionStore.js";
|
|
3
|
+
import type { Rect } from "./overlayGeometry.js";
|
|
4
|
+
export interface DomNavigatorProps {
|
|
5
|
+
selected: SelectedElement;
|
|
6
|
+
hierarchy: HTMLElement[];
|
|
7
|
+
anchor: Rect;
|
|
8
|
+
project: (element: HTMLElement) => Rect | null;
|
|
9
|
+
}
|
|
10
|
+
export declare function DomNavigator({ selected, hierarchy, anchor, project }: DomNavigatorProps): ReactElement | null;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { IconMenu2 } from "@tabler/icons-react";
|
|
4
|
+
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
5
|
+
import { setSelectedElement } from "../selection/selectionStore.js";
|
|
6
|
+
import { computeNavigationNodes } from "../selection/hierarchy.js";
|
|
7
|
+
import { resolveSelectionFromElement } from "../selection/resolveSelection.js";
|
|
8
|
+
import { portalContainer } from "../ui/portalContainer.js";
|
|
9
|
+
import { cx } from "../ui/classNames.js";
|
|
10
|
+
function nodeDescription(node) {
|
|
11
|
+
const id = node.id ? `#${node.id}` : "";
|
|
12
|
+
const className = typeof node.className === "string"
|
|
13
|
+
? node.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((name) => `.${name}`).join("")
|
|
14
|
+
: "";
|
|
15
|
+
return `${node.localName}${id}${className}`;
|
|
16
|
+
}
|
|
17
|
+
function selectNode(node) {
|
|
18
|
+
const resolved = resolveSelectionFromElement(node);
|
|
19
|
+
if (resolved)
|
|
20
|
+
setSelectedElement(resolved);
|
|
21
|
+
}
|
|
22
|
+
export function DomNavigator({ selected, hierarchy, anchor, project }) {
|
|
23
|
+
const nodes = computeNavigationNodes(selected.domElement, hierarchy);
|
|
24
|
+
const parents = nodes.filter((node) => node.direction === "up");
|
|
25
|
+
const children = nodes.filter((node) => node.direction === "down");
|
|
26
|
+
const [expanded, setExpanded] = useState(false);
|
|
27
|
+
const [hovered, setHovered] = useState(null);
|
|
28
|
+
if (nodes.length === 0)
|
|
29
|
+
return null;
|
|
30
|
+
const preview = hovered ? project(hovered) : null;
|
|
31
|
+
const setOpen = (next) => {
|
|
32
|
+
setExpanded(next);
|
|
33
|
+
if (!next)
|
|
34
|
+
setHovered(null);
|
|
35
|
+
};
|
|
36
|
+
const renderItem = (node, depth) => {
|
|
37
|
+
const current = node === null;
|
|
38
|
+
const element = current ? selected.domElement : node.element;
|
|
39
|
+
return (_jsx("button", { type: "button", className: cx("dom-navigator__item", current && "dom-navigator__item--selected"), "data-test": "dom-navigator-item", "data-current": current ? "true" : undefined, "data-direction": node?.direction, "data-depth": depth, "data-cid": element.getAttribute("data-cid") ?? undefined, style: { paddingLeft: `calc(var(--space-8) + ${depth * 16}px)` }, role: "menuitem", "aria-current": current ? "true" : undefined, onMouseEnter: () => setHovered(current ? null : element), onClick: () => {
|
|
40
|
+
if (!current)
|
|
41
|
+
selectNode(element);
|
|
42
|
+
setOpen(false);
|
|
43
|
+
}, children: _jsx("span", { children: nodeDescription(element) }) }, current ? "current" : `${node.direction}-${node.depth}-${element.getAttribute("data-cid") ?? element.localName}`));
|
|
44
|
+
};
|
|
45
|
+
return (_jsxs(_Fragment, { children: [preview ? _jsx("div", { className: "dom-navigator-preview", "data-test": "dom-navigator-preview", style: preview, "aria-hidden": "true" }) : null, _jsx("div", { className: "dom-navigator", "data-test": "dom-navigator", "aria-label": "DOM navigator", style: { left: Math.max(4, anchor.left), top: Math.max(4, anchor.top - 28) }, children: _jsxs(BasePopover.Root, { open: expanded, onOpenChange: setOpen, children: [_jsx(BasePopover.Trigger, { render: _jsx("button", { type: "button", className: "dom-navigator__trigger", "data-test": "dom-navigator-trigger", "aria-label": "Open DOM navigator", "aria-haspopup": "menu", "aria-expanded": expanded, onMouseEnter: () => setHovered(null) }), children: _jsx(IconMenu2, { size: 14, "aria-hidden": "true" }) }), _jsx("span", { className: "dom-navigator__label", children: nodeDescription(selected.domElement) }), _jsx(BasePopover.Portal, { container: portalContainer(), children: _jsx(BasePopover.Positioner, { className: "dom-navigator__positioner", side: "top", align: "start", sideOffset: 4, children: _jsxs(BasePopover.Popup, { className: "dom-navigator__menu", role: "menu", "aria-label": "DOM navigator", onMouseLeave: () => setHovered(null), children: [parents.slice().reverse().map((node) => renderItem(node, parents.length - node.depth)), renderItem(null, parents.length), children.map((node) => renderItem(node, parents.length + node.depth))] }) }) })] }) })] }));
|
|
46
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { useInspectorOpen } from "../shell/openStore.js";
|
|
4
|
-
import { getSelectedElements, useSelectedElement, useSelectedElements } from "../selection/selectionStore.js";
|
|
4
|
+
import { getSelectedElements, useHierarchy, useSelectedElement, useSelectedElements } from "../selection/selectionStore.js";
|
|
5
5
|
import { installElementSelector } from "./elementSelector.js";
|
|
6
6
|
import { getMarginFills, getMarginGuides, readBorderWidths, readMargins, toRect, } from "./overlayGeometry.js";
|
|
7
7
|
import { observeSelectedGeometry } from "./selectedGeometry.js";
|
|
@@ -17,6 +17,8 @@ import { MeasurementGuideOverlay } from "./MeasurementGuideOverlay.js";
|
|
|
17
17
|
import { resolveSelectionTarget, selectionTargetMode } from "../selection/selectionTarget.js";
|
|
18
18
|
import { isInlineTextEditingActive, useInlineTextSession } from "../inline-text/inlineTextEditor.js";
|
|
19
19
|
import { EMPTY_TEXT_PROJECTION_ATTR } from "../projection/textProjection.js";
|
|
20
|
+
import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
|
|
21
|
+
import { DomNavigator } from "./DomNavigator.js";
|
|
20
22
|
export { getMarginFills, getMarginGuides, } from "./overlayGeometry.js";
|
|
21
23
|
function overlayStyle(rect) {
|
|
22
24
|
return {
|
|
@@ -32,6 +34,8 @@ export function InspectorOverlay({ host }) {
|
|
|
32
34
|
const open = useInspectorOpen();
|
|
33
35
|
const selected = useSelectedElement();
|
|
34
36
|
const selectedElements = useSelectedElements();
|
|
37
|
+
const hierarchy = useHierarchy();
|
|
38
|
+
const domNavigationEnabled = useNudgeUiRuntimeConfig().capabilities.domNavigation === true;
|
|
35
39
|
const inlineTextSession = useInlineTextSession();
|
|
36
40
|
const [hoverRect, setHoverRect] = useState(null);
|
|
37
41
|
const [hoverMargins, setHoverMargins] = useState(null);
|
|
@@ -322,7 +326,7 @@ export function InspectorOverlay({ host }) {
|
|
|
322
326
|
width: guide.width,
|
|
323
327
|
height: guide.height,
|
|
324
328
|
pointerEvents: "none",
|
|
325
|
-
}, "aria-hidden": "true" }, guide.side)))] })) : null, open && !inlineTextSession ? selectedGeometry.map(({ element, rect }, index) => (_jsx("div", { className: "selected-outline", "data-test": "selected-outline", "data-selected-cid": element.cid, "data-selected-index": index, "data-selected-src": element.src, style: overlayStyle(rect), "aria-hidden": "true" }, `${element.domElement.getAttribute("data-cid") ?? "element"}-${index}`))) : null, open && selectedElements.length === 1 ? _jsx(DropGuideOverlay, { guide: dropGuide?.document === document
|
|
329
|
+
}, "aria-hidden": "true" }, guide.side)))] })) : null, open && !inlineTextSession ? selectedGeometry.map(({ element, rect }, index) => (_jsx("div", { className: "selected-outline", "data-test": "selected-outline", "data-selected-cid": element.cid, "data-selected-index": index, "data-selected-src": element.src, style: overlayStyle(rect), "aria-hidden": "true" }, `${element.domElement.getAttribute("data-cid") ?? "element"}-${index}`))) : null, open && domNavigationEnabled && !inlineTextSession && selectedElements.length === 1 && selected && selectedRect ? (_jsx(DomNavigator, { selected: selected, hierarchy: hierarchy, anchor: selectedRect, project: (element) => toRect(element.getBoundingClientRect()) })) : null, open && selectedElements.length === 1 ? _jsx(DropGuideOverlay, { guide: dropGuide?.document === document
|
|
326
330
|
? { orientation: dropGuide.orientation, line: dropGuide.line, target: dropGuide.target }
|
|
327
331
|
: null, lineClassName: "dom-drop-line", lineTestId: "dom-drop-line", targetClassName: "dom-drop-target", targetTestId: "dom-drop-target" }) : null] }));
|
|
328
332
|
}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
export type ClickModifiers = Pick<MouseEvent, "ctrlKey" | "metaKey" | "shiftKey">;
|
|
2
|
+
/**
|
|
3
|
+
* Press and release gestures that application widgets use to open menus,
|
|
4
|
+
* dialogs, and popovers before any `click` fires. The Design tool keeps them
|
|
5
|
+
* from reaching the application.
|
|
6
|
+
*/
|
|
7
|
+
export declare const APPLICATION_GESTURE_EVENTS: readonly ["pointerdown", "pointerup", "mousedown", "mouseup", "touchstart", "touchend", "auxclick", "contextmenu"];
|
|
2
8
|
/** Command/Ctrl-click selects the deepest tracked element unless Shift is held. */
|
|
3
9
|
export declare function isDeepSelectionClick(event: ClickModifiers): boolean;
|
|
4
|
-
/**
|
|
5
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Only input from a person becomes inspector selection. Script events such
|
|
12
|
+
* as `element.click()` keep reaching the application.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isUserClick(event: Pick<Event, "isTrusted">): boolean;
|
|
15
|
+
/** The active inline text editor needs native press events for caret placement. */
|
|
16
|
+
export declare function isInlineEditorTarget(target: EventTarget | null): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Keeps an application press gesture from reaching application listeners.
|
|
19
|
+
* Listeners on the document itself, including the inspector's own, still run.
|
|
20
|
+
*/
|
|
21
|
+
export declare function blockApplicationGesture(event: Event): void;
|
|
6
22
|
/** Prevent an inspected application click from reaching the host application. */
|
|
7
23
|
export declare function blockApplicationClick(event: MouseEvent): void;
|
|
@@ -1,10 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Press and release gestures that application widgets use to open menus,
|
|
3
|
+
* dialogs, and popovers before any `click` fires. The Design tool keeps them
|
|
4
|
+
* from reaching the application.
|
|
5
|
+
*/
|
|
6
|
+
export const APPLICATION_GESTURE_EVENTS = [
|
|
7
|
+
"pointerdown",
|
|
8
|
+
"pointerup",
|
|
9
|
+
"mousedown",
|
|
10
|
+
"mouseup",
|
|
11
|
+
"touchstart",
|
|
12
|
+
"touchend",
|
|
13
|
+
"auxclick",
|
|
14
|
+
"contextmenu",
|
|
15
|
+
];
|
|
1
16
|
/** Command/Ctrl-click selects the deepest tracked element unless Shift is held. */
|
|
2
17
|
export function isDeepSelectionClick(event) {
|
|
3
18
|
return (event.ctrlKey || event.metaKey) && !event.shiftKey;
|
|
4
19
|
}
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Only input from a person becomes inspector selection. Script events such
|
|
22
|
+
* as `element.click()` keep reaching the application.
|
|
23
|
+
*/
|
|
24
|
+
export function isUserClick(event) {
|
|
25
|
+
return event.isTrusted;
|
|
26
|
+
}
|
|
27
|
+
/** The active inline text editor needs native press events for caret placement. */
|
|
28
|
+
export function isInlineEditorTarget(target) {
|
|
29
|
+
return typeof Element !== "undefined"
|
|
30
|
+
&& target instanceof Element
|
|
31
|
+
&& target.closest('[data-inline-editor="true"]') !== null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Keeps an application press gesture from reaching application listeners.
|
|
35
|
+
* Listeners on the document itself, including the inspector's own, still run.
|
|
36
|
+
*/
|
|
37
|
+
export function blockApplicationGesture(event) {
|
|
38
|
+
event.stopPropagation();
|
|
8
39
|
}
|
|
9
40
|
/** Prevent an inspected application click from reaching the host application. */
|
|
10
41
|
export function blockApplicationClick(event) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function installElementSelector(inspectorHost: HTMLElement): () => void;
|
|
1
|
+
export declare function installElementSelector(inspectorHost: HTMLElement, acceptsClick?: (event: Event) => boolean): () => void;
|