Fix docked dialog drag snap threshold and add queue splitter resize feedback
This commit is contained in:
@@ -172,6 +172,8 @@ test('floating dialog drag stays on the compositor without forced style reads',
|
||||
assert.match(resolverDialogSource, /mr-undock-drop-preview/);
|
||||
assert.match(startDockedDrag, /getRememberedFloatingSize\(\)/);
|
||||
assert.match(onDockedDrag, /Math\.hypot\(dx,\s*dy\)\s*<\s*5/);
|
||||
assert.match(onDockedDrag, /e\.clientX\s*>\s*this\.getDockSnapThreshold\(\)/);
|
||||
assert.match(onDockedDrag, /setUndockDropPreviewActive\(shouldUndock\)/);
|
||||
assert.match(onDockedDrag, /requestAnimationFrame/);
|
||||
assert.match(endDockedDrag, /this\.undockToFloating\(\{\s*persist:\s*false\s*\}\)/);
|
||||
assert.match(endDockedDrag, /this\._floatingRectBeforeDock\s*=\s*\{\s*\.\.\.finalRect\s*\}/);
|
||||
@@ -241,6 +243,70 @@ test('floating dialog drag stays on the compositor without forced style reads',
|
||||
}
|
||||
});
|
||||
|
||||
test('docked dialog drag stays docked after returning to the dock edge', () => {
|
||||
const onDockedDrag = eval(`(${extractMethod(dialogShellMethodsSource, 'onDockedDrag')})`);
|
||||
const endDockedDrag = eval(`(${extractMethod(dialogShellMethodsSource, 'endDockedDrag')})`);
|
||||
const previousDocument = globalThis.document;
|
||||
const previousRequestAnimationFrame = globalThis.requestAnimationFrame;
|
||||
const previousCancelAnimationFrame = globalThis.cancelAnimationFrame;
|
||||
let undockCalls = 0;
|
||||
let cancelledFrame = null;
|
||||
|
||||
globalThis.document = {
|
||||
removeEventListener() {},
|
||||
};
|
||||
globalThis.requestAnimationFrame = () => 23;
|
||||
globalThis.cancelAnimationFrame = frame => {
|
||||
cancelledFrame = frame;
|
||||
};
|
||||
|
||||
try {
|
||||
const dialog = {
|
||||
docked: true,
|
||||
fullscreen: false,
|
||||
element: { style: {} },
|
||||
undockDropPreview: { style: {} },
|
||||
_dockedDragStart: { x: 20, y: 20 },
|
||||
_dockedDragPendingRect: null,
|
||||
_dockedDragAnimationFrame: null,
|
||||
_dragUndockCandidate: false,
|
||||
getDockSnapThreshold: () => 64,
|
||||
getDockedDragPreviewRect: (clientX, clientY) => ({
|
||||
top: clientY,
|
||||
left: clientX,
|
||||
width: 800,
|
||||
height: 600,
|
||||
}),
|
||||
setUndockDropPreviewActive(active) {
|
||||
this._dragUndockCandidate = Boolean(active && this.docked && !this.fullscreen);
|
||||
},
|
||||
undockToFloating() {
|
||||
undockCalls += 1;
|
||||
},
|
||||
saveModalPosition() {},
|
||||
};
|
||||
|
||||
onDockedDrag.call(dialog, { clientX: 120, clientY: 40 });
|
||||
assert.equal(dialog._dragUndockCandidate, true);
|
||||
assert.equal(dialog._dockedDragAnimationFrame, 23);
|
||||
|
||||
onDockedDrag.call(dialog, { clientX: 20, clientY: 40 });
|
||||
assert.equal(dialog._dragUndockCandidate, false);
|
||||
assert.equal(dialog._dockedDragPendingRect, null);
|
||||
|
||||
endDockedDrag.call(dialog);
|
||||
|
||||
assert.equal(undockCalls, 0);
|
||||
assert.equal(dialog.docked, true);
|
||||
assert.equal(cancelledFrame, 23);
|
||||
assert.equal(dialog._dockedDragStart, null);
|
||||
} finally {
|
||||
globalThis.document = previousDocument;
|
||||
globalThis.requestAnimationFrame = previousRequestAnimationFrame;
|
||||
globalThis.cancelAnimationFrame = previousCancelAnimationFrame;
|
||||
}
|
||||
});
|
||||
|
||||
test('missing browser splitter keeps a compositor preview between live layout frames', () => {
|
||||
const previousDocument = globalThis.document;
|
||||
const previousRequestAnimationFrame = globalThis.requestAnimationFrame;
|
||||
@@ -419,6 +485,61 @@ test('docked ComfyUI splitter coalesces live layout while its gutter stays respo
|
||||
assert.doesNotMatch(dialogShellMethodsSource, /mr-resolver-external-resizing/);
|
||||
});
|
||||
|
||||
test('queue splitter suppresses tooltips while resizing', () => {
|
||||
const activateQueueSplitUi = eval(
|
||||
`(${extractMethod(queueMethodsSource, 'activateQueueSplitUi')})`
|
||||
);
|
||||
const deactivateQueueSplitUi = eval(
|
||||
`(${extractMethod(queueMethodsSource, 'deactivateQueueSplitUi')})`
|
||||
);
|
||||
const classes = new Set();
|
||||
const splitterClasses = new Set();
|
||||
let hideTooltipCalls = 0;
|
||||
const body = {
|
||||
classList: {
|
||||
add: value => classes.add(value),
|
||||
remove: value => classes.delete(value),
|
||||
},
|
||||
};
|
||||
const dialog = {
|
||||
_queueSplitUiActive: false,
|
||||
splitterElement: {
|
||||
classList: {
|
||||
add: value => splitterClasses.add(value),
|
||||
remove: value => splitterClasses.delete(value),
|
||||
},
|
||||
},
|
||||
hideTooltip() {
|
||||
hideTooltipCalls += 1;
|
||||
},
|
||||
};
|
||||
|
||||
activateQueueSplitUi.call(dialog, body);
|
||||
activateQueueSplitUi.call(dialog, body);
|
||||
|
||||
assert.equal(hideTooltipCalls, 1);
|
||||
assert.equal(classes.has('is-resizing'), true);
|
||||
assert.equal(splitterClasses.has('is-resizing'), true);
|
||||
|
||||
deactivateQueueSplitUi.call(dialog, body);
|
||||
|
||||
assert.equal(dialog._queueSplitUiActive, false);
|
||||
assert.equal(classes.has('is-resizing'), false);
|
||||
assert.equal(splitterClasses.has('is-resizing'), false);
|
||||
assert.match(
|
||||
queueMethodsSource,
|
||||
/onBeforeDrag:\s*\(newW\)\s*=>\s*\{\s*this\.activateQueueSplitUi\(body\)/
|
||||
);
|
||||
assert.match(
|
||||
queueMethodsSource,
|
||||
/onEnd:\s*\(finalWidth\)\s*=>\s*\{[^]*this\.deactivateQueueSplitUi\(body\)/
|
||||
);
|
||||
assert.match(
|
||||
resolverMainCssSource,
|
||||
/#model-resolver-body\.is-resizing::after\s*\{[^}]*pointer-events:\s*auto/s
|
||||
);
|
||||
});
|
||||
|
||||
test('missing browser detail remains available at narrow widths', () => {
|
||||
assert.doesNotMatch(missingBrowserMethodsSource, /updateMissingBrowserDetailCollapse/);
|
||||
assert.doesNotMatch(missingBrowserMethodsSource, /is-detail-collapsed/);
|
||||
|
||||
@@ -742,6 +742,15 @@
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#model-resolver-body.is-resizing::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
inset: 0;
|
||||
cursor: col-resize;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#model-resolver-modal .mr-missing-list-row {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 70px;
|
||||
|
||||
@@ -1955,6 +1955,23 @@ export const queueMethods = {
|
||||
});
|
||||
},
|
||||
|
||||
activateQueueSplitUi(body) {
|
||||
if (this._queueSplitUiActive) return;
|
||||
|
||||
const targetBody = body || this.getQueueSplitBody();
|
||||
this._queueSplitUiActive = true;
|
||||
this.hideTooltip?.();
|
||||
targetBody?.classList?.add('is-resizing');
|
||||
this.splitterElement?.classList?.add('is-resizing');
|
||||
},
|
||||
|
||||
deactivateQueueSplitUi(body) {
|
||||
const targetBody = body || this.getQueueSplitBody();
|
||||
this._queueSplitUiActive = false;
|
||||
targetBody?.classList?.remove('is-resizing');
|
||||
this.splitterElement?.classList?.remove('is-resizing');
|
||||
},
|
||||
|
||||
// Begin split drag for resizable panels
|
||||
startSplitDrag(e, { startWidth = null, edgeOpen = false, startCollapsedPreview = false } = {}) {
|
||||
try {
|
||||
@@ -1987,12 +2004,14 @@ export const queueMethods = {
|
||||
this._splitPreviewCollapsed = !!startCollapsedPreview;
|
||||
this._splitEdgeOpen = !!edgeOpen;
|
||||
this._splitEdgeOpenedPastThreshold = false;
|
||||
const body = this.getQueueSplitBody();
|
||||
|
||||
this._splitInteraction = startSplitterDrag(e, {
|
||||
anchor: 'right',
|
||||
startWidth: initialWidth,
|
||||
bounds,
|
||||
onBeforeDrag: (newW) => {
|
||||
this.activateQueueSplitUi(body);
|
||||
if (newW < bounds.min) newW = bounds.min;
|
||||
if (newW > bounds.max) newW = bounds.max;
|
||||
return newW;
|
||||
@@ -2041,6 +2060,7 @@ export const queueMethods = {
|
||||
},
|
||||
onEnd: (finalWidth) => {
|
||||
this._splitDragging = false;
|
||||
this.deactivateQueueSplitUi(body);
|
||||
if (this.queueElement && finalWidth && !this._splitPreviewCollapsed) {
|
||||
this.applyQueueSplitWidth(finalWidth, {
|
||||
bounds: this._splitStart?.bounds,
|
||||
@@ -2049,8 +2069,6 @@ export const queueMethods = {
|
||||
});
|
||||
}
|
||||
|
||||
const body = this.getQueueSplitBody();
|
||||
|
||||
if (this._splitEdgeOpen && !this._pendingSplitWidth) {
|
||||
try {
|
||||
const restoreWidth = Math.max(240, Math.round(this._splitStart?.startWidth || 320));
|
||||
|
||||
@@ -906,8 +906,14 @@ export const dialogShellMethods = {
|
||||
const dy = e.clientY - start.y;
|
||||
if (!this._dragUndockCandidate && Math.hypot(dx, dy) < 5) return;
|
||||
|
||||
const shouldUndock = e.clientX > this.getDockSnapThreshold();
|
||||
this.setUndockDropPreviewActive(shouldUndock);
|
||||
if (!shouldUndock) {
|
||||
this._dockedDragPendingRect = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this._dockedDragPendingRect = this.getDockedDragPreviewRect(e.clientX, e.clientY);
|
||||
this.setUndockDropPreviewActive(true);
|
||||
if (this._dockedDragAnimationFrame || !this._dockedDragPendingRect) return;
|
||||
|
||||
this._dockedDragAnimationFrame = requestAnimationFrame(() => {
|
||||
|
||||
Reference in New Issue
Block a user