nudge-ui 0.2.1 → 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/dist/client.mjs +16 -9
- package/dist/hosts/astro/clientTransport.js +3 -0
- 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.js +7 -1
- 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 +2 -0
- package/dist/hosts/static/server.js +1 -0
- package/dist/hosts/vite/vite.d.ts +1 -0
- package/dist/hosts/vite/vite.js +4 -1
- package/dist/inspector/agent/McpConnectionDialog.d.ts +3 -2
- package/dist/inspector/agent/McpConnectionDialog.js +11 -4
- package/dist/inspector/agent/client.d.ts +6 -0
- package/dist/inspector/agent/client.js +36 -4
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +8 -1
- package/dist/inspector/agent/httpTransport.d.ts +6 -0
- package/dist/inspector/agent/httpTransport.js +41 -1
- package/dist/inspector/agent/protocol.d.ts +3 -0
- package/dist/inspector/canvas/CanvasElementOverlay.js +49 -7
- package/dist/inspector/canvas/CanvasToolbar.js +4 -4
- package/dist/inspector/canvas/CanvasWorkspace.js +30 -11
- package/dist/inspector/canvas/frameProtocol.d.ts +16 -3
- package/dist/inspector/canvas/frameProtocol.js +9 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +2 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +3 -1
- package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
- package/dist/inspector/canvas/linkEligibility.js +0 -29
- package/dist/inspector/canvas/rendererBootstrap.js +13 -0
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +55 -39
- package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.js +29 -3
- package/dist/inspector/clientManifest.d.ts +2 -0
- package/dist/inspector/clientManifest.js +1 -0
- package/dist/inspector/index.js +6 -8
- 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/spacingGestures.d.ts +3 -3
- package/dist/inspector/overlay/spacingGestures.js +15 -17
- package/dist/inspector/runtime/runtimeConfig.d.ts +1 -1
- 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 +2 -2
- package/dist/inspector/shell/ChangesLog.js +20 -20
- package/dist/inspector/shell/CopyPromptButton.js +9 -2
- package/dist/inspector/shell/InspectorShell.js +2 -5
- package/dist/inspector/sketch/store.js +48 -23
- package/dist/inspector/styleEditors/SpacingBox.js +9 -4
- package/dist/inspector/styleEditors/nudgeValue.d.ts +4 -4
- package/dist/inspector/styleEditors/nudgeValue.js +9 -7
- package/dist/inspector/tokens/TokenField.js +21 -2
- 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/ui/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- package/dist/inspector/ui/dragCursor.d.ts +8 -0
- package/dist/inspector/ui/dragCursor.js +21 -0
- package/dist/inspector/ui/styles.js +11 -9
- package/dist/nudge-ui.mjs +15 -1
- package/dist/transport/editor.d.ts +20 -1
- package/dist/transport/editor.js +42 -11
- package/dist/transport/routes.d.ts +4 -1
- package/dist/transport/routes.js +4 -1
- package/package.json +1 -1
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { PROTOCOL_VERSION, getRendererIdentity, sendToParent, isKeyboardShortcutCode, } from "./frameProtocol.js";
|
|
2
|
-
import { findClosestAnchor, isEligibleNavigation, hasDifferentRoute, shouldPreserveNativeLinkActivation, } from "./linkEligibility.js";
|
|
3
2
|
import { readBorderWidths, readMargins } from "../overlay/overlayGeometry.js";
|
|
4
3
|
import { installInteractionStyles } from "../overlay/interactionStyles.js";
|
|
5
4
|
import { createFrameThrottle } from "../overlay/frameThrottle.js";
|
|
@@ -8,7 +7,7 @@ import { isNudgeUiDev } from "../runtime/devFlag.js";
|
|
|
8
7
|
import { isEditableEvent, isInspectorToggleShortcut, isSendPromptShortcut } from "../shell/shortcuts.js";
|
|
9
8
|
import { resolveSelectionTarget, selectionTargetMode } from "../selection/selectionTarget.js";
|
|
10
9
|
import { escapeCssString } from "../projection/cssEscapes.js";
|
|
11
|
-
import { blockApplicationClick,
|
|
10
|
+
import { APPLICATION_GESTURE_EVENTS, blockApplicationClick, blockApplicationGesture, isInlineEditorTarget, isUserClick, } from "../overlay/clickPolicy.js";
|
|
12
11
|
import { EMPTY_TEXT_PROJECTION_ATTR } from "../projection/textProjection.js";
|
|
13
12
|
import { getSpacingAffordanceAtPoint, toSpacingDescriptor, } from "../overlay/spacingGestures.js";
|
|
14
13
|
const REACT_FIBER_KEY = /^__reactFiber\$/;
|
|
@@ -78,19 +77,20 @@ export function buildSelector(el) {
|
|
|
78
77
|
}
|
|
79
78
|
let installed = false;
|
|
80
79
|
const noopDisposal = () => undefined;
|
|
81
|
-
export function installRendererElementSelector() {
|
|
80
|
+
export function installRendererElementSelector(acceptsClick = isUserClick) {
|
|
82
81
|
if (!isNudgeUiDev())
|
|
83
82
|
return noopDisposal;
|
|
84
83
|
if (installed)
|
|
85
84
|
return noopDisposal;
|
|
86
85
|
installed = true;
|
|
87
|
-
|
|
86
|
+
let removeInteractionStyles = installInteractionStyles();
|
|
88
87
|
const listenerCleanup = [];
|
|
89
88
|
let disposed = false;
|
|
90
89
|
let interactionsSuspended = false;
|
|
91
90
|
let scrolling = false;
|
|
92
91
|
let scrollSettleTimer = null;
|
|
93
92
|
let activeHoverElement = null;
|
|
93
|
+
let suppressHoverDuringSpacingDrag = false;
|
|
94
94
|
let lastHoverPoint = null;
|
|
95
95
|
function trackListener(target, type, listener, options) {
|
|
96
96
|
const eventListener = (event) => listener(event);
|
|
@@ -187,7 +187,7 @@ export function installRendererElementSelector() {
|
|
|
187
187
|
sendToParent(msg);
|
|
188
188
|
}
|
|
189
189
|
const hoverUpdate = createFrameThrottle((pending) => {
|
|
190
|
-
if (disposed || scrolling)
|
|
190
|
+
if (disposed || scrolling || suppressHoverDuringSpacingDrag)
|
|
191
191
|
return;
|
|
192
192
|
reportHover(pending);
|
|
193
193
|
});
|
|
@@ -202,7 +202,7 @@ export function installRendererElementSelector() {
|
|
|
202
202
|
function resumeHoverAfterScroll() {
|
|
203
203
|
scrollSettleTimer = null;
|
|
204
204
|
scrolling = false;
|
|
205
|
-
if (disposed || interactionsSuspended || !lastHoverPoint)
|
|
205
|
+
if (disposed || interactionsSuspended || suppressHoverDuringSpacingDrag || !lastHoverPoint)
|
|
206
206
|
return;
|
|
207
207
|
const target = document.elementFromPoint?.(lastHoverPoint.x, lastHoverPoint.y);
|
|
208
208
|
const element = resolveSelectionTarget(target, lastHoverPoint.mode);
|
|
@@ -248,6 +248,8 @@ export function installRendererElementSelector() {
|
|
|
248
248
|
if (interactionsSuspended)
|
|
249
249
|
return;
|
|
250
250
|
updateMeasureState(event.altKey, true);
|
|
251
|
+
if (suppressHoverDuringSpacingDrag)
|
|
252
|
+
return;
|
|
251
253
|
const target = event.target;
|
|
252
254
|
if (!(target instanceof Element))
|
|
253
255
|
return;
|
|
@@ -304,26 +306,35 @@ export function installRendererElementSelector() {
|
|
|
304
306
|
}
|
|
305
307
|
if (interactionsSuspended) {
|
|
306
308
|
cancelActiveDrag();
|
|
307
|
-
|
|
308
|
-
clearSpacingCursor();
|
|
309
|
+
clearActiveHover();
|
|
309
310
|
if (scrollSettleTimer !== null)
|
|
310
311
|
window.clearTimeout(scrollSettleTimer);
|
|
311
312
|
scrollSettleTimer = null;
|
|
312
313
|
scrolling = false;
|
|
313
|
-
activeHoverElement = null;
|
|
314
314
|
updateMeasureState(false, false);
|
|
315
|
+
removeInteractionStyles?.();
|
|
316
|
+
removeInteractionStyles = null;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
removeInteractionStyles ??= installInteractionStyles();
|
|
315
320
|
}
|
|
316
321
|
});
|
|
317
|
-
const dragMoveUpdate = createFrameThrottle((
|
|
322
|
+
const dragMoveUpdate = createFrameThrottle((drag) => {
|
|
318
323
|
if (disposed)
|
|
319
324
|
return;
|
|
320
325
|
const identity = getRendererIdentity();
|
|
321
|
-
if (!
|
|
326
|
+
if (!drag || !identity)
|
|
322
327
|
return;
|
|
323
|
-
const msg = {
|
|
328
|
+
const msg = {
|
|
329
|
+
type: "element-drag-move",
|
|
330
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
331
|
+
point: drag.point,
|
|
332
|
+
shiftKey: drag.shiftKey,
|
|
333
|
+
...identity,
|
|
334
|
+
};
|
|
324
335
|
sendToParent(msg);
|
|
325
336
|
});
|
|
326
|
-
function sendDragEnd(point, cancelled) {
|
|
337
|
+
function sendDragEnd(point, cancelled, shiftKey = false) {
|
|
327
338
|
if (!dragging || !point)
|
|
328
339
|
return;
|
|
329
340
|
const identity = getRendererIdentity();
|
|
@@ -333,6 +344,7 @@ export function installRendererElementSelector() {
|
|
|
333
344
|
type: "element-drag-end",
|
|
334
345
|
protocolVersion: PROTOCOL_VERSION,
|
|
335
346
|
point,
|
|
347
|
+
shiftKey,
|
|
336
348
|
...(cancelled ? { cancelled: true } : {}),
|
|
337
349
|
...identity,
|
|
338
350
|
};
|
|
@@ -343,6 +355,7 @@ export function installRendererElementSelector() {
|
|
|
343
355
|
sendDragEnd(lastDragPoint, true);
|
|
344
356
|
pendingDrag = null;
|
|
345
357
|
dragging = false;
|
|
358
|
+
suppressHoverDuringSpacingDrag = false;
|
|
346
359
|
}
|
|
347
360
|
trackListener(document, "mousedown", (event) => {
|
|
348
361
|
if (interactionsSuspended)
|
|
@@ -356,6 +369,11 @@ export function installRendererElementSelector() {
|
|
|
356
369
|
const spacing = spacingAffordance?.element === element
|
|
357
370
|
? toSpacingDescriptor(spacingAffordance)
|
|
358
371
|
: null;
|
|
372
|
+
if (spacing) {
|
|
373
|
+
suppressHoverDuringSpacingDrag = true;
|
|
374
|
+
hoverUpdate.cancel();
|
|
375
|
+
lastHoverPoint = null;
|
|
376
|
+
}
|
|
359
377
|
const identity = getRendererIdentity();
|
|
360
378
|
// A spacing drag owns the pointer gesture. Do not start an inline-text
|
|
361
379
|
// attempt for the same down event; padding and gaps are intentionally not
|
|
@@ -386,6 +404,13 @@ export function installRendererElementSelector() {
|
|
|
386
404
|
};
|
|
387
405
|
lastDragPoint = { x: event.clientX, y: event.clientY };
|
|
388
406
|
}, true);
|
|
407
|
+
for (const type of APPLICATION_GESTURE_EVENTS) {
|
|
408
|
+
trackListener(document, type, (event) => {
|
|
409
|
+
if (interactionsSuspended || !acceptsClick(event) || isInlineEditorTarget(event.target))
|
|
410
|
+
return;
|
|
411
|
+
blockApplicationGesture(event);
|
|
412
|
+
}, true);
|
|
413
|
+
}
|
|
389
414
|
trackListener(document, "mousemove", (event) => {
|
|
390
415
|
if (!pendingDrag) {
|
|
391
416
|
if (interactionsSuspended)
|
|
@@ -419,12 +444,13 @@ export function installRendererElementSelector() {
|
|
|
419
444
|
elementId: cidIndex.elementId(pendingDrag.element), point,
|
|
420
445
|
startPoint: pendingDrag.point,
|
|
421
446
|
spacing: pendingDrag.spacing,
|
|
447
|
+
shiftKey: event.shiftKey,
|
|
422
448
|
...identity,
|
|
423
449
|
};
|
|
424
450
|
sendToParent(msg);
|
|
425
451
|
}
|
|
426
452
|
else {
|
|
427
|
-
dragMoveUpdate.schedule(point);
|
|
453
|
+
dragMoveUpdate.schedule({ point, shiftKey: event.shiftKey });
|
|
428
454
|
}
|
|
429
455
|
}, true);
|
|
430
456
|
function finishDrag(event) {
|
|
@@ -433,10 +459,11 @@ export function installRendererElementSelector() {
|
|
|
433
459
|
event.preventDefault();
|
|
434
460
|
const point = { x: event.clientX, y: event.clientY };
|
|
435
461
|
lastDragPoint = point;
|
|
436
|
-
sendDragEnd(point, false);
|
|
462
|
+
sendDragEnd(point, false, event.shiftKey);
|
|
437
463
|
}
|
|
438
464
|
pendingDrag = null;
|
|
439
465
|
dragging = false;
|
|
466
|
+
suppressHoverDuringSpacingDrag = false;
|
|
440
467
|
}
|
|
441
468
|
trackListener(document, "mouseup", finishDrag, true);
|
|
442
469
|
function forwardKeyboardShortcut(event, phase) {
|
|
@@ -470,7 +497,10 @@ export function installRendererElementSelector() {
|
|
|
470
497
|
sendToParent(message);
|
|
471
498
|
return phase === "keydown";
|
|
472
499
|
}
|
|
473
|
-
|
|
500
|
+
// Global tool shortcuts need to run before application capture handlers on
|
|
501
|
+
// the iframe document. A page-level window handler can otherwise stop the
|
|
502
|
+
// event before it reaches the document listener.
|
|
503
|
+
trackListener(window, "keydown", (event) => {
|
|
474
504
|
if (isEditableEvent(event))
|
|
475
505
|
return;
|
|
476
506
|
if (forwardKeyboardShortcut(event, "keydown"))
|
|
@@ -554,7 +584,7 @@ export function installRendererElementSelector() {
|
|
|
554
584
|
};
|
|
555
585
|
sendToParent(msg);
|
|
556
586
|
}, true);
|
|
557
|
-
trackListener(
|
|
587
|
+
trackListener(window, "keyup", (event) => {
|
|
558
588
|
forwardKeyboardShortcut(event, "keyup");
|
|
559
589
|
if (event.key === "Alt")
|
|
560
590
|
updateMeasureState(false, measurePointerOverPage);
|
|
@@ -612,36 +642,20 @@ export function installRendererElementSelector() {
|
|
|
612
642
|
sendToParent(message);
|
|
613
643
|
}, true);
|
|
614
644
|
trackListener(document, "click", (event) => {
|
|
615
|
-
if (interactionsSuspended)
|
|
645
|
+
if (interactionsSuspended || !acceptsClick(event))
|
|
616
646
|
return;
|
|
617
647
|
const target = event.target;
|
|
618
648
|
if (!(target instanceof Element))
|
|
619
649
|
return;
|
|
620
|
-
//
|
|
621
|
-
//
|
|
622
|
-
// intent to the controller while leaving native or framework navigation in
|
|
623
|
-
// charge of the live application document.
|
|
624
|
-
const anchor = findClosestAnchor(target);
|
|
625
|
-
if (anchor && isEligibleNavigation(anchor, event) && hasDifferentRoute(anchor)) {
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
// Command/Ctrl+Shift-click is the deliberate escape hatch for running
|
|
629
|
-
// the application action. All other selected canvas clicks are editing
|
|
630
|
-
// gestures and must not reach the rendered application.
|
|
631
|
-
if (isApplicationActivationClick(event))
|
|
632
|
-
return;
|
|
633
|
-
const preserveNativeLink = anchor
|
|
634
|
-
? shouldPreserveNativeLinkActivation(anchor, event)
|
|
635
|
-
: false;
|
|
650
|
+
// Design-tool clicks are editing gestures and must not reach the
|
|
651
|
+
// rendered application; the Select tool is how the app receives them.
|
|
636
652
|
const el = resolveSelectionTarget(target, selectionTargetMode(event));
|
|
637
653
|
if (!el) {
|
|
638
|
-
|
|
639
|
-
blockApplicationClick(event);
|
|
654
|
+
blockApplicationClick(event);
|
|
640
655
|
return;
|
|
641
656
|
}
|
|
642
657
|
lastSelected = el;
|
|
643
|
-
|
|
644
|
-
blockApplicationClick(event);
|
|
658
|
+
blockApplicationClick(event);
|
|
645
659
|
const cid = el.getAttribute("data-cid");
|
|
646
660
|
const selector = buildSelector(el);
|
|
647
661
|
const { file, line, component, src } = getFiberInfo(el);
|
|
@@ -676,11 +690,13 @@ export function installRendererElementSelector() {
|
|
|
676
690
|
for (const cleanup of listenerCleanup)
|
|
677
691
|
cleanup();
|
|
678
692
|
listenerCleanup.length = 0;
|
|
679
|
-
removeInteractionStyles();
|
|
693
|
+
removeInteractionStyles?.();
|
|
694
|
+
removeInteractionStyles = null;
|
|
680
695
|
lastSelected = null;
|
|
681
696
|
activeHoverElement = null;
|
|
682
697
|
lastHoverPoint = null;
|
|
683
698
|
scrolling = false;
|
|
699
|
+
suppressHoverDuringSpacingDrag = false;
|
|
684
700
|
measurePointerOverPage = false;
|
|
685
701
|
measureAltKey = false;
|
|
686
702
|
document.documentElement.removeAttribute("data-nudge-ui-panel");
|
|
@@ -31,6 +31,10 @@ export interface WorkspaceChangeStore {
|
|
|
31
31
|
restoreWorkspaceChanges(next: WorkspaceContents): void;
|
|
32
32
|
clearWorkspaceChanges(): void;
|
|
33
33
|
}
|
|
34
|
+
/** Groups consecutive synchronous updates from one gesture into one undo step.
|
|
35
|
+
* Updates outside the callback keep their own history entries.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createChangeHistoryGroup(): (update: () => void) => void;
|
|
34
38
|
export declare const workspaceChangeStore: WorkspaceChangeStore;
|
|
35
39
|
/** Compatibility accessors retained for callers that have not adopted the store seam. */
|
|
36
40
|
export declare function getWorkspaceChanges(): WorkspaceChangesSnapshot;
|
|
@@ -5,6 +5,7 @@ let contents = { changes: [], structuralChanges: [] };
|
|
|
5
5
|
let revision = 0;
|
|
6
6
|
let undoStack = [];
|
|
7
7
|
let redoStack = [];
|
|
8
|
+
let activeHistoryGroup;
|
|
8
9
|
let snapshot = createSnapshot();
|
|
9
10
|
const listeners = new Set();
|
|
10
11
|
function cloneContents(value) {
|
|
@@ -60,11 +61,36 @@ function commit(next) {
|
|
|
60
61
|
return false;
|
|
61
62
|
const before = cloneContents(contents);
|
|
62
63
|
contents = cloneContents(next);
|
|
63
|
-
|
|
64
|
+
const previous = undoStack.at(-1);
|
|
65
|
+
const entry = {
|
|
66
|
+
before: activeHistoryGroup && previous?.group === activeHistoryGroup ? previous.before : before,
|
|
67
|
+
after: cloneContents(contents),
|
|
68
|
+
group: activeHistoryGroup,
|
|
69
|
+
};
|
|
70
|
+
if (activeHistoryGroup && previous?.group === activeHistoryGroup)
|
|
71
|
+
undoStack.pop();
|
|
72
|
+
if (!sameWorkspaceContents(entry.before, entry.after))
|
|
73
|
+
undoStack.push(entry);
|
|
64
74
|
redoStack = [];
|
|
65
75
|
publish();
|
|
66
76
|
return true;
|
|
67
77
|
}
|
|
78
|
+
/** Groups consecutive synchronous updates from one gesture into one undo step.
|
|
79
|
+
* Updates outside the callback keep their own history entries.
|
|
80
|
+
*/
|
|
81
|
+
export function createChangeHistoryGroup() {
|
|
82
|
+
const group = Symbol("change-history-group");
|
|
83
|
+
return (update) => {
|
|
84
|
+
const previous = activeHistoryGroup;
|
|
85
|
+
activeHistoryGroup = group;
|
|
86
|
+
try {
|
|
87
|
+
update();
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
activeHistoryGroup = previous;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
68
94
|
function getSnapshot() {
|
|
69
95
|
return snapshot;
|
|
70
96
|
}
|
|
@@ -123,7 +149,7 @@ function reconcileWorkspaceChangesImpl(verifiedKeys, verifiedStructuralIds) {
|
|
|
123
149
|
return 0;
|
|
124
150
|
contents = next;
|
|
125
151
|
const prune = (entries) => entries
|
|
126
|
-
.map((entry) => ({ before: retain(entry.before), after: retain(entry.after) }))
|
|
152
|
+
.map((entry) => ({ group: entry.group, before: retain(entry.before), after: retain(entry.after) }))
|
|
127
153
|
.filter((entry) => !sameWorkspaceContents(entry.before, entry.after));
|
|
128
154
|
undoStack = prune(undoStack);
|
|
129
155
|
redoStack = prune(redoStack);
|
|
@@ -159,7 +185,7 @@ function redoWorkspaceChangeImpl() {
|
|
|
159
185
|
return false;
|
|
160
186
|
redoStack.pop();
|
|
161
187
|
contents = cloneContents(entry.after);
|
|
162
|
-
undoStack.push(entry);
|
|
188
|
+
undoStack.push({ before: entry.before, after: entry.after });
|
|
163
189
|
publish();
|
|
164
190
|
return true;
|
|
165
191
|
}
|
|
@@ -24,6 +24,8 @@ export interface NudgeUiClientManifest {
|
|
|
24
24
|
readonly strategy: "refresh-manifest" | "reload-document";
|
|
25
25
|
readonly events?: readonly string[];
|
|
26
26
|
};
|
|
27
|
+
/** Set by `NUDGE_UI=1`: automated browsers open the editor instead of the plain app. */
|
|
28
|
+
readonly inspectAutomatedBrowsers?: true;
|
|
27
29
|
}
|
|
28
30
|
/** Parses and normalizes an external manifest before it replaces inspector state. */
|
|
29
31
|
export declare function parseNudgeUiClientManifest(value: unknown): NudgeUiClientManifest | null;
|
|
@@ -23,6 +23,7 @@ value) {
|
|
|
23
23
|
...(isAgentBridge(value.agentBridge) ? { agentBridge: value.agentBridge } : {}),
|
|
24
24
|
...(value.document === undefined ? {} : { document: value.document }),
|
|
25
25
|
...(value.reload === undefined ? {} : { reload: value.reload }),
|
|
26
|
+
...(value.inspectAutomatedBrowsers === true ? { inspectAutomatedBrowsers: true } : {}),
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
catch {
|
package/dist/inspector/index.js
CHANGED
|
@@ -208,14 +208,12 @@ export function mountInspector(host) {
|
|
|
208
208
|
const demo = isDemoRuntime();
|
|
209
209
|
if (!demo && !hasWriteLease())
|
|
210
210
|
return;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
});
|
|
218
|
-
}
|
|
211
|
+
const runtimeConfig = getNudgeUiRuntimeConfig();
|
|
212
|
+
if (runtimeConfig.capabilities.domNavigation !== true) {
|
|
213
|
+
configureNudgeUiRuntime({
|
|
214
|
+
...runtimeConfig,
|
|
215
|
+
capabilities: { ...runtimeConfig.capabilities, domNavigation: true },
|
|
216
|
+
});
|
|
219
217
|
}
|
|
220
218
|
if (lockedRoot) {
|
|
221
219
|
lockedRoot.unmount();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
|
+
import type { SelectedElement } from "../selection/selectionStore.js";
|
|
3
|
+
import type { Rect } from "./overlayGeometry.js";
|
|
4
|
+
export interface DomNavigatorProps {
|
|
5
|
+
selected: SelectedElement;
|
|
6
|
+
hierarchy: HTMLElement[];
|
|
7
|
+
anchor: Rect;
|
|
8
|
+
project: (element: HTMLElement) => Rect | null;
|
|
9
|
+
}
|
|
10
|
+
export declare function DomNavigator({ selected, hierarchy, anchor, project }: DomNavigatorProps): ReactElement | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { setSelectedElement } from "../selection/selectionStore.js";
|
|
4
|
+
import { computeNavigationNodes } from "../selection/hierarchy.js";
|
|
5
|
+
import { resolveSelectionFromElement } from "../selection/resolveSelection.js";
|
|
6
|
+
function nodeLabel(node) {
|
|
7
|
+
return node.getAttribute("data-cid") ?? node.localName;
|
|
8
|
+
}
|
|
9
|
+
function nodeDescription(node) {
|
|
10
|
+
const id = node.id ? `#${node.id}` : "";
|
|
11
|
+
const className = typeof node.className === "string"
|
|
12
|
+
? node.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((name) => `.${name}`).join("")
|
|
13
|
+
: "";
|
|
14
|
+
const source = node.getAttribute("data-src");
|
|
15
|
+
return `${node.localName}${id}${className}${source ? ` — ${source}` : ""}`;
|
|
16
|
+
}
|
|
17
|
+
function selectNode(node) {
|
|
18
|
+
const resolved = resolveSelectionFromElement(node);
|
|
19
|
+
if (resolved)
|
|
20
|
+
setSelectedElement(resolved);
|
|
21
|
+
}
|
|
22
|
+
export function DomNavigator({ selected, hierarchy, anchor, project }) {
|
|
23
|
+
const nodes = computeNavigationNodes(selected.domElement, hierarchy);
|
|
24
|
+
const [expanded, setExpanded] = useState(false);
|
|
25
|
+
const [hovered, setHovered] = useState(null);
|
|
26
|
+
if (nodes.length === 0)
|
|
27
|
+
return null;
|
|
28
|
+
const preview = hovered ? project(hovered) : null;
|
|
29
|
+
const close = () => {
|
|
30
|
+
setExpanded(false);
|
|
31
|
+
setHovered(null);
|
|
32
|
+
};
|
|
33
|
+
return (_jsxs(_Fragment, { children: [preview ? _jsx("div", { className: "dom-navigator-preview", "data-test": "dom-navigator-preview", style: preview, "aria-hidden": "true" }) : null, _jsxs("div", { className: "dom-navigator", "data-test": "dom-navigator", "aria-label": "DOM navigator", style: { left: Math.max(4, anchor.left), top: Math.max(4, anchor.top) }, onMouseEnter: () => setExpanded(true), onMouseLeave: close, onFocus: () => setExpanded(true), onBlur: (event) => {
|
|
34
|
+
if (!(event.relatedTarget instanceof Node) || !event.currentTarget.contains(event.relatedTarget))
|
|
35
|
+
close();
|
|
36
|
+
}, children: [_jsxs("button", { type: "button", className: "dom-navigator__trigger", "data-test": "dom-navigator-trigger", "aria-expanded": expanded, "aria-haspopup": "menu", title: nodeDescription(selected.domElement), onMouseEnter: () => setHovered(null), children: [_jsx("span", { children: nodeLabel(selected.domElement) }), _jsx("span", { "aria-hidden": "true", children: "\u2304" })] }), expanded ? (_jsx("div", { className: "dom-navigator__menu", role: "menu", children: nodes.map((node) => (_jsxs("button", { type: "button", className: "dom-navigator__item", "data-test": "dom-navigator-item", "data-direction": node.direction, "data-depth": node.depth, "data-cid": node.element.getAttribute("data-cid") ?? undefined, title: nodeDescription(node.element), role: "menuitem", onMouseEnter: () => setHovered(node.element), onClick: () => {
|
|
37
|
+
selectNode(node.element);
|
|
38
|
+
close();
|
|
39
|
+
}, children: [_jsx("span", { "aria-hidden": "true", children: node.direction === "up" ? "↑" : "↓" }), _jsx("span", { children: nodeLabel(node.element) })] }, `${node.direction}-${node.depth}-${node.element.getAttribute("data-cid") ?? node.element.localName}`))) })) : null] })] }));
|
|
40
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { useInspectorOpen } from "../shell/openStore.js";
|
|
4
|
-
import { getSelectedElements, useSelectedElement, useSelectedElements } from "../selection/selectionStore.js";
|
|
4
|
+
import { getSelectedElements, useHierarchy, useSelectedElement, useSelectedElements } from "../selection/selectionStore.js";
|
|
5
5
|
import { installElementSelector } from "./elementSelector.js";
|
|
6
6
|
import { getMarginFills, getMarginGuides, readBorderWidths, readMargins, toRect, } from "./overlayGeometry.js";
|
|
7
7
|
import { observeSelectedGeometry } from "./selectedGeometry.js";
|
|
@@ -17,6 +17,8 @@ import { MeasurementGuideOverlay } from "./MeasurementGuideOverlay.js";
|
|
|
17
17
|
import { resolveSelectionTarget, selectionTargetMode } from "../selection/selectionTarget.js";
|
|
18
18
|
import { isInlineTextEditingActive, useInlineTextSession } from "../inline-text/inlineTextEditor.js";
|
|
19
19
|
import { EMPTY_TEXT_PROJECTION_ATTR } from "../projection/textProjection.js";
|
|
20
|
+
import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
|
|
21
|
+
import { DomNavigator } from "./DomNavigator.js";
|
|
20
22
|
export { getMarginFills, getMarginGuides, } from "./overlayGeometry.js";
|
|
21
23
|
function overlayStyle(rect) {
|
|
22
24
|
return {
|
|
@@ -32,6 +34,8 @@ export function InspectorOverlay({ host }) {
|
|
|
32
34
|
const open = useInspectorOpen();
|
|
33
35
|
const selected = useSelectedElement();
|
|
34
36
|
const selectedElements = useSelectedElements();
|
|
37
|
+
const hierarchy = useHierarchy();
|
|
38
|
+
const domNavigationEnabled = useNudgeUiRuntimeConfig().capabilities.domNavigation === true;
|
|
35
39
|
const inlineTextSession = useInlineTextSession();
|
|
36
40
|
const [hoverRect, setHoverRect] = useState(null);
|
|
37
41
|
const [hoverMargins, setHoverMargins] = useState(null);
|
|
@@ -322,7 +326,7 @@ export function InspectorOverlay({ host }) {
|
|
|
322
326
|
width: guide.width,
|
|
323
327
|
height: guide.height,
|
|
324
328
|
pointerEvents: "none",
|
|
325
|
-
}, "aria-hidden": "true" }, guide.side)))] })) : null, open && !inlineTextSession ? selectedGeometry.map(({ element, rect }, index) => (_jsx("div", { className: "selected-outline", "data-test": "selected-outline", "data-selected-cid": element.cid, "data-selected-index": index, "data-selected-src": element.src, style: overlayStyle(rect), "aria-hidden": "true" }, `${element.domElement.getAttribute("data-cid") ?? "element"}-${index}`))) : null, open && selectedElements.length === 1 ? _jsx(DropGuideOverlay, { guide: dropGuide?.document === document
|
|
329
|
+
}, "aria-hidden": "true" }, guide.side)))] })) : null, open && !inlineTextSession ? selectedGeometry.map(({ element, rect }, index) => (_jsx("div", { className: "selected-outline", "data-test": "selected-outline", "data-selected-cid": element.cid, "data-selected-index": index, "data-selected-src": element.src, style: overlayStyle(rect), "aria-hidden": "true" }, `${element.domElement.getAttribute("data-cid") ?? "element"}-${index}`))) : null, open && domNavigationEnabled && !inlineTextSession && selectedElements.length === 1 && selected && selectedRect ? (_jsx(DomNavigator, { selected: selected, hierarchy: hierarchy, anchor: selectedRect, project: (element) => toRect(element.getBoundingClientRect()) })) : null, open && selectedElements.length === 1 ? _jsx(DropGuideOverlay, { guide: dropGuide?.document === document
|
|
326
330
|
? { orientation: dropGuide.orientation, line: dropGuide.line, target: dropGuide.target }
|
|
327
331
|
: null, lineClassName: "dom-drop-line", lineTestId: "dom-drop-line", targetClassName: "dom-drop-target", targetTestId: "dom-drop-target" }) : null] }));
|
|
328
332
|
}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
export type ClickModifiers = Pick<MouseEvent, "ctrlKey" | "metaKey" | "shiftKey">;
|
|
2
|
+
/**
|
|
3
|
+
* Press and release gestures that application widgets use to open menus,
|
|
4
|
+
* dialogs, and popovers before any `click` fires. The Design tool keeps them
|
|
5
|
+
* from reaching the application.
|
|
6
|
+
*/
|
|
7
|
+
export declare const APPLICATION_GESTURE_EVENTS: readonly ["pointerdown", "pointerup", "mousedown", "mouseup", "touchstart", "touchend", "auxclick", "contextmenu"];
|
|
2
8
|
/** Command/Ctrl-click selects the deepest tracked element unless Shift is held. */
|
|
3
9
|
export declare function isDeepSelectionClick(event: ClickModifiers): boolean;
|
|
4
|
-
/**
|
|
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;
|
|
@@ -3,8 +3,8 @@ import { getOpen } from "../shell/openStore.js";
|
|
|
3
3
|
import { resolveSelectionFromEvent } from "../selection/resolveSelection.js";
|
|
4
4
|
import { handleInlineTextEditIntent, isInlineTextEditingActive, } from "../inline-text/inlineTextEditor.js";
|
|
5
5
|
import { EMPTY_TEXT_PROJECTION_ATTR } from "../projection/textProjection.js";
|
|
6
|
-
import { blockApplicationClick,
|
|
7
|
-
export function installElementSelector(inspectorHost) {
|
|
6
|
+
import { APPLICATION_GESTURE_EVENTS, blockApplicationClick, blockApplicationGesture, isInlineEditorTarget, isUserClick, } from "./clickPolicy.js";
|
|
7
|
+
export function installElementSelector(inspectorHost, acceptsClick = isUserClick) {
|
|
8
8
|
function applyInlineTextDisposition(event, disposition) {
|
|
9
9
|
if (disposition === "pass-through")
|
|
10
10
|
return;
|
|
@@ -43,7 +43,7 @@ export function installElementSelector(inspectorHost) {
|
|
|
43
43
|
return root instanceof ShadowRoot && root.host === inspectorHost;
|
|
44
44
|
}
|
|
45
45
|
function onClick(e) {
|
|
46
|
-
if (!getOpen())
|
|
46
|
+
if (!getOpen() || !acceptsClick(e))
|
|
47
47
|
return;
|
|
48
48
|
if (isInsideInspectorUi(e)) {
|
|
49
49
|
// The selector guards the inspected application only. Clicks on the
|
|
@@ -61,13 +61,10 @@ export function installElementSelector(inspectorHost) {
|
|
|
61
61
|
blockApplicationClick(e);
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
-
if (isApplicationActivationClick(e))
|
|
65
|
-
return;
|
|
66
64
|
// The inspector is an editing surface while open. Capture every ordinary
|
|
67
65
|
// application click so buttons, links, and untracked controls cannot run
|
|
68
66
|
// alongside selection. Shift-click toggles the primary target in the
|
|
69
|
-
// ordered group; Command/Ctrl-click
|
|
70
|
-
// element, and Command/Ctrl+Shift-click remains the activation escape hatch.
|
|
67
|
+
// ordered group; Command/Ctrl-click selects the deepest tracked element.
|
|
71
68
|
const sel = resolveSelectionFromEvent(e, inspectorHost);
|
|
72
69
|
blockApplicationClick(e);
|
|
73
70
|
if (sel) {
|
|
@@ -89,12 +86,26 @@ export function installElementSelector(inspectorHost) {
|
|
|
89
86
|
clickCount: e.detail,
|
|
90
87
|
}));
|
|
91
88
|
}
|
|
89
|
+
function onApplicationGesture(e) {
|
|
90
|
+
if (!getOpen() || !acceptsClick(e) || isInlineEditorTarget(e.target))
|
|
91
|
+
return;
|
|
92
|
+
if (e.target instanceof Element
|
|
93
|
+
&& (e.target === inspectorHost || inspectorHost.contains(e.target)))
|
|
94
|
+
return;
|
|
95
|
+
blockApplicationGesture(e);
|
|
96
|
+
}
|
|
92
97
|
document.addEventListener("mousedown", onMouseDown, true);
|
|
93
98
|
document.addEventListener("dblclick", onDoubleClick, true);
|
|
94
99
|
document.addEventListener("click", onClick, true);
|
|
100
|
+
for (const type of APPLICATION_GESTURE_EVENTS) {
|
|
101
|
+
document.addEventListener(type, onApplicationGesture, true);
|
|
102
|
+
}
|
|
95
103
|
return () => {
|
|
96
104
|
document.removeEventListener("mousedown", onMouseDown, true);
|
|
97
105
|
document.removeEventListener("dblclick", onDoubleClick, true);
|
|
98
106
|
document.removeEventListener("click", onClick, true);
|
|
107
|
+
for (const type of APPLICATION_GESTURE_EVENTS) {
|
|
108
|
+
document.removeEventListener(type, onApplicationGesture, true);
|
|
109
|
+
}
|
|
99
110
|
};
|
|
100
111
|
}
|
|
@@ -16,7 +16,7 @@ export interface SpacingGuideData {
|
|
|
16
16
|
guide: Rect;
|
|
17
17
|
affectedGuides: Rect[];
|
|
18
18
|
affectedAreas: Rect[];
|
|
19
|
-
/** A
|
|
19
|
+
/** A 16px-thick pointer target centred on the visible, up-to-40px guide handle. */
|
|
20
20
|
hit: Rect;
|
|
21
21
|
}
|
|
22
22
|
export interface SpacingAffordance extends SpacingGuideData {
|
|
@@ -32,14 +32,14 @@ export declare const SPACING_GUIDE_HANDLE_LENGTH = 40;
|
|
|
32
32
|
export declare function getSpacingAffordanceAtPoint(doc: Document, x: number, y: number): SpacingAffordance | null;
|
|
33
33
|
/** Re-resolves a renderer-reported spacing descriptor in its owner document. */
|
|
34
34
|
export declare function getSpacingAffordanceForDescriptor(element: HTMLElement, descriptor: SpacingDescriptor): SpacingAffordance | null;
|
|
35
|
-
/** Calculates
|
|
35
|
+
/** Calculates a direct-manipulation value, optionally stepping changes by 8px. */
|
|
36
36
|
export declare function spacingValueForDrag(affordance: Pick<SpacingAffordance, "dragAxis" | "direction" | "value">, start: {
|
|
37
37
|
x: number;
|
|
38
38
|
y: number;
|
|
39
39
|
}, point: {
|
|
40
40
|
x: number;
|
|
41
41
|
y: number;
|
|
42
|
-
}): number;
|
|
42
|
+
}, shiftKey?: boolean): number;
|
|
43
43
|
export declare function spacingValueCss(value: number): string;
|
|
44
44
|
/** Serializes the stable part of an affordance for renderer-to-controller messages. */
|
|
45
45
|
export declare function toSpacingDescriptor(affordance: SpacingAffordance): SpacingDescriptor;
|