fix: improve missing browser split pane resize behavior

This commit is contained in:
Dariusz L
2026-07-01 01:23:24 +02:00
parent ef8ff03743
commit c2b074ce12
2 changed files with 383 additions and 87 deletions
+6 -17
View File
@@ -509,6 +509,7 @@
overflow: hidden;
border: 1px solid var(--mr-missing-line);
background: var(--mr-missing-bg);
contain: layout paint style;
}
.mr-missing-list-pane,
@@ -521,7 +522,7 @@
}
.mr-missing-list-pane {
flex: 1 1 auto;
flex: 1 1 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
border-right: 0;
@@ -557,26 +558,11 @@
.mr-missing-browser-splitter:hover::before,
.mr-missing-browser-splitter:focus-visible::before,
.mr-missing-browser.is-resizing .mr-missing-browser-splitter::before {
.mr-missing-browser-splitter.is-resizing::before {
background: rgba(114, 211, 138, 0.78);
box-shadow: 0 0 0 3px rgba(114, 211, 138, 0.12);
}
.mr-missing-browser.is-resizing .mr-missing-list-pane,
.mr-missing-browser.is-resizing .mr-missing-detail-pane {
pointer-events: none;
user-select: none;
}
.mr-missing-browser.is-resizing .mr-missing-list-row,
.mr-missing-browser.is-resizing .mr-match-row,
.mr-missing-browser.is-resizing .mr-search-results-table tr,
.mr-missing-browser.is-resizing .mr-btn,
.mr-missing-browser.is-resizing .mr-node-chip,
.mr-missing-browser.is-resizing .mr-category-chip {
transition: none !important;
}
.mr-missing-list-toolbar {
display: flex;
align-items: center;
@@ -735,6 +721,7 @@
min-height: 0;
overflow-y: auto;
scrollbar-width: auto;
scrollbar-gutter: stable;
background: var(--mr-missing-bg);
}
@@ -751,6 +738,7 @@
font-family: inherit;
font-size: 11px;
text-align: left;
contain: layout paint style;
transition: background 0.14s ease, box-shadow 0.14s ease, border-color 0.14s ease;
}
@@ -1113,6 +1101,7 @@
.mr-missing-detail-pane {
flex: 0 0 var(--mr-missing-detail-track, 42%);
overflow-y: auto;
scrollbar-gutter: stable;
background: var(--mr-missing-bg);
}
+377 -70
View File
@@ -526,10 +526,7 @@ export const missingBrowserMethods = {
? `${hiddenAutoDownloadCount} auto-download hidden`
: (hasAny100Match ? 'Auto-link ready for exact matches' : 'Review matches or search online');
const listLayout = this.getMissingModelsListLayout(missingModels);
let savedDetailWidth = null;
try {
savedDetailWidth = parseInt(localStorage.getItem(this.missingBrowserSplitStorageKey) || '', 10);
} catch (e) {}
const savedDetailWidth = this.getStoredMissingBrowserSplitWidth();
const splitStyle = Number.isFinite(savedDetailWidth) && savedDetailWidth > 0
? `--mr-missing-detail-track:${savedDetailWidth}px;`
: '';
@@ -766,13 +763,25 @@ export const missingBrowserMethods = {
this.restoreMissingBrowserSplitWidth(browser);
splitter.addEventListener('mousedown', (event) => {
if (event.button !== 0) return;
this.startMissingBrowserSplitDrag(event, browser);
});
const startSplitDrag = (event) => {
if (event.button !== undefined && event.button !== 0) return;
this.startMissingBrowserSplitDrag(event, browser, splitter);
};
if (typeof PointerEvent === 'function') {
splitter.addEventListener('pointerdown', startSplitDrag);
} else {
splitter.addEventListener('mousedown', startSplitDrag);
}
splitter.addEventListener('dblclick', () => {
browser.querySelector('.mr-missing-detail-pane')?.style.removeProperty('--mr-missing-detail-track');
this.clearMissingBrowserSplitStyles(browser);
this._pendingMissingBrowserRestoreBrowser = null;
this._pendingMissingBrowserRestoreWidth = null;
this._pendingMissingBrowserRestoreUseDefault = false;
this._missingBrowserStoredSplitWidth = null;
this._missingBrowserStoredSplitWidthLoaded = true;
this._missingBrowserLastDetailWidth = null;
this.cancelMissingBrowserSplitWidthPersist();
try {
localStorage.removeItem(this.missingBrowserSplitStorageKey);
} catch (e) {}
@@ -786,138 +795,436 @@ export const missingBrowserMethods = {
this._missingBrowserResizeObserver?.disconnect?.();
if (typeof ResizeObserver === 'function') {
this._missingBrowserResizeObserver = new ResizeObserver(() => {
if (!this._missingBrowserSplitDragging) {
this.restoreMissingBrowserSplitWidth(browser);
this._missingBrowserResizeObserver = new ResizeObserver((entries) => {
const observedWidth = Number(entries?.[0]?.contentRect?.width);
if (Number.isFinite(observedWidth) && observedWidth > 0) {
this._missingBrowserObservedWidth = observedWidth;
this._missingBrowserLastBrowserWidth = observedWidth;
if (!this._missingBrowserLastDetailWidth) {
this._missingBrowserLastDetailWidth = this.getDefaultMissingBrowserDetailWidth(observedWidth);
}
}
const settlingUntil = Number(this._missingBrowserSplitSettlingUntil || 0);
const isSettling = typeof performance === 'object'
&& typeof performance.now === 'function'
&& performance.now() < settlingUntil;
if (!this._missingBrowserSplitDragging && !isSettling) {
this.scheduleMissingBrowserSplitRestore(browser, observedWidth, { useDefault: true });
}
});
this._missingBrowserResizeObserver.observe(browser);
}
},
restoreMissingBrowserSplitWidth(browser) {
getStoredMissingBrowserSplitWidth() {
if (this._missingBrowserStoredSplitWidthLoaded) {
return this._missingBrowserStoredSplitWidth || null;
}
let savedWidth = null;
try {
const savedWidth = parseInt(localStorage.getItem(this.missingBrowserSplitStorageKey) || '', 10);
if (Number.isFinite(savedWidth) && savedWidth > 0) {
this.setMissingBrowserDetailWidth(browser, savedWidth, { persist: false });
}
const storedWidth = parseInt(localStorage.getItem(this.missingBrowserSplitStorageKey) || '', 10);
savedWidth = Number.isFinite(storedWidth) && storedWidth > 0 ? storedWidth : null;
} catch (e) {}
this._missingBrowserStoredSplitWidth = savedWidth;
this._missingBrowserStoredSplitWidthLoaded = true;
return savedWidth;
},
startMissingBrowserSplitDrag(event, browser) {
getFastMissingBrowserWidthEstimate(browser = null) {
const observedWidth = Number(this._missingBrowserObservedWidth);
if (Number.isFinite(observedWidth) && observedWidth > 0) return observedWidth;
const lastWidth = Number(this._missingBrowserLastBrowserWidth);
if (Number.isFinite(lastWidth) && lastWidth > 0) return lastWidth;
if (browser instanceof HTMLElement) {
const clientWidth = Number(browser.clientWidth || browser.offsetWidth);
if (Number.isFinite(clientWidth) && clientWidth > 0) {
this._missingBrowserObservedWidth = clientWidth;
this._missingBrowserLastBrowserWidth = clientWidth;
return clientWidth;
}
}
return 720;
},
getMissingBrowserSplitBoundsForWidth(width) {
const safeWidth = Math.max(1, Number(width) || 1);
const available = Math.max(1, safeWidth - 10);
const minListWidth = Math.min(360, Math.max(240, Math.floor(available * 0.28)));
const max = Math.max(1, available - minListWidth);
const min = Math.min(max, Math.min(420, Math.max(300, Math.floor(available * 0.3))));
return { min, max, available };
},
getDefaultMissingBrowserDetailWidth(browserWidth = null) {
const width = Number(browserWidth) > 0
? Number(browserWidth)
: this.getFastMissingBrowserWidthEstimate();
const bounds = this.getMissingBrowserSplitBoundsForWidth(width);
const preferred = Math.round(width * 0.42);
return Math.round(Math.max(bounds.min, Math.min(bounds.max, preferred)));
},
scheduleMissingBrowserSplitRestore(browser, browserWidth = null, { useDefault = false } = {}) {
if (!(browser instanceof HTMLElement)) return;
const detailPane = browser.querySelector('.mr-missing-detail-pane');
if (!(detailPane instanceof HTMLElement)) return;
this._pendingMissingBrowserRestoreBrowser = browser;
this._pendingMissingBrowserRestoreWidth = Number.isFinite(Number(browserWidth)) && Number(browserWidth) > 0
? Number(browserWidth)
: null;
this._pendingMissingBrowserRestoreUseDefault = Boolean(useDefault);
if (this._missingBrowserRestoreFrame) return;
this._missingBrowserRestoreFrame = requestAnimationFrame(() => {
this._missingBrowserRestoreFrame = null;
const targetBrowser = this._pendingMissingBrowserRestoreBrowser;
const targetWidth = this._pendingMissingBrowserRestoreWidth;
const targetUseDefault = this._pendingMissingBrowserRestoreUseDefault;
this._pendingMissingBrowserRestoreBrowser = null;
this._pendingMissingBrowserRestoreWidth = null;
this._pendingMissingBrowserRestoreUseDefault = false;
if (!this._missingBrowserSplitDragging && targetBrowser?.isConnected) {
this.restoreMissingBrowserSplitWidth(targetBrowser, {
browserWidth: targetWidth,
useDefault: targetUseDefault
});
}
});
},
restoreMissingBrowserSplitWidth(browser, { browserWidth = null, useDefault = false } = {}) {
const savedWidth = this.getStoredMissingBrowserSplitWidth();
const targetWidth = savedWidth || (useDefault ? this.getDefaultMissingBrowserDetailWidth(browserWidth) : null);
if (!targetWidth) return;
this.setMissingBrowserDetailWidth(browser, targetWidth, {
persist: false,
browserWidth,
skipIfUnchanged: true
});
},
startMissingBrowserSplitDrag(event, browser, splitter = null) {
if (!(browser instanceof HTMLElement)) return;
event.preventDefault();
event.stopPropagation?.();
if (this._missingBrowserReleaseCleanupFrame) {
cancelAnimationFrame(this._missingBrowserReleaseCleanupFrame);
this._missingBrowserReleaseCleanupFrame = null;
}
const detailPane = browser.querySelector('.mr-missing-detail-pane');
const listPane = browser.querySelector('.mr-missing-list-pane');
if (!(detailPane instanceof HTMLElement) || !(listPane instanceof HTMLElement)) return;
const browserWidth = this.getFastMissingBrowserWidthEstimate(browser);
const bounds = this.getMissingBrowserSplitBoundsForWidth(browserWidth);
const savedWidth = this.getStoredMissingBrowserSplitWidth();
const detailWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, this.getMissingBrowserDetailTrackWidth(detailPane)
|| savedWidth
|| this.getDefaultMissingBrowserDetailWidth(browserWidth))));
if (this._missingBrowserRestoreFrame) {
cancelAnimationFrame(this._missingBrowserRestoreFrame);
this._missingBrowserRestoreFrame = null;
}
this._pendingMissingBrowserRestoreBrowser = null;
this._pendingMissingBrowserRestoreWidth = null;
this._pendingMissingBrowserRestoreUseDefault = false;
this.cancelMissingBrowserSplitWidthPersist();
this.applyMissingBrowserDetailWidth(detailPane, detailWidth, {
browserWidth,
availableWidth: bounds.available,
splitBounds: bounds,
skipIfUnchanged: true
});
this._missingBrowserSplitDragging = true;
this._missingBrowserSplitStart = {
x: event.clientX,
width: detailPane.getBoundingClientRect().width,
bounds: this.getMissingBrowserSplitBounds(browser)
width: detailWidth,
bounds,
browserWidth,
availableWidth: bounds.available
};
this._appliedMissingBrowserSplitWidth = Math.round(this._missingBrowserSplitStart.width);
this._lastMissingBrowserSplitApply = 0;
this._missingBrowserSplitBrowser = browser;
this._missingBrowserSplitListPane = listPane;
this._missingBrowserSplitDetailPane = detailPane;
this._missingBrowserSplitSplitter = splitter instanceof HTMLElement
? splitter
: browser.querySelector('.mr-missing-browser-splitter');
this._missingBrowserPrevUserSelect = document.body.style.userSelect;
this._missingBrowserPrevCursor = document.body.style.cursor;
document.body.style.userSelect = 'none';
document.body.style.cursor = 'col-resize';
browser.classList.add('is-resizing');
this._missingBrowserSplitSplitter?.classList?.add('is-resizing');
if (event.pointerId !== undefined && this._missingBrowserSplitSplitter?.setPointerCapture) {
try {
this._missingBrowserSplitSplitter.setPointerCapture(event.pointerId);
} catch (e) {}
}
this._onMissingBrowserSplitMove = (moveEvent) => this.onMissingBrowserSplitDrag(moveEvent);
this._onMissingBrowserSplitUp = () => this.endMissingBrowserSplitDrag();
document.addEventListener('mousemove', this._onMissingBrowserSplitMove);
document.addEventListener('mouseup', this._onMissingBrowserSplitUp, { once: true });
this._onMissingBrowserSplitUp = (upEvent) => this.endMissingBrowserSplitDrag(upEvent);
this._missingBrowserSplitMoveEvent = event.type === 'pointerdown' ? 'pointermove' : 'mousemove';
this._missingBrowserSplitUpEvent = event.type === 'pointerdown' ? 'pointerup' : 'mouseup';
this._missingBrowserSplitCancelEvent = event.type === 'pointerdown' ? 'pointercancel' : null;
document.addEventListener(this._missingBrowserSplitMoveEvent, this._onMissingBrowserSplitMove, true);
document.addEventListener(this._missingBrowserSplitUpEvent, this._onMissingBrowserSplitUp, { once: true, capture: true });
if (this._missingBrowserSplitCancelEvent) {
document.addEventListener(this._missingBrowserSplitCancelEvent, this._onMissingBrowserSplitUp, { once: true, capture: true });
}
},
onMissingBrowserSplitDrag(event) {
if (!this._missingBrowserSplitDragging || !this._missingBrowserSplitStart || !this._missingBrowserSplitBrowser) return;
event.preventDefault?.();
event.stopPropagation?.();
const nextWidth = this._missingBrowserSplitStart.width - (event.clientX - this._missingBrowserSplitStart.x);
const bounds = this._missingBrowserSplitStart.bounds;
this._pendingMissingBrowserSplitWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, nextWidth)));
if (this._pendingMissingBrowserSplitWidth === this._appliedMissingBrowserSplitWidth) return;
const now = performance.now();
if (this._lastMissingBrowserSplitApply && now - this._lastMissingBrowserSplitApply < 33) return;
this._lastMissingBrowserSplitApply = now;
if (this._missingBrowserSplitFrame) return;
this._missingBrowserSplitFrame = requestAnimationFrame(() => {
this._missingBrowserSplitFrame = null;
const nextWidth = this._pendingMissingBrowserSplitWidth;
if (!this._missingBrowserSplitDragging || !this._missingBrowserSplitDetailPane || !nextWidth) return;
if (nextWidth === this._appliedMissingBrowserSplitWidth) return;
this._missingBrowserSplitDetailPane.style.setProperty('--mr-missing-detail-track', `${nextWidth}px`);
this._appliedMissingBrowserSplitWidth = nextWidth;
if (!this._missingBrowserSplitDetailPane) return;
this.applyMissingBrowserDetailWidth(this._missingBrowserSplitDetailPane, this._pendingMissingBrowserSplitWidth, {
browserWidth: this._missingBrowserSplitStart.browserWidth,
availableWidth: this._missingBrowserSplitStart.availableWidth,
splitBounds: this._missingBrowserSplitStart.bounds,
skipIfUnchanged: true
});
this._appliedMissingBrowserSplitWidth = this._pendingMissingBrowserSplitWidth;
},
endMissingBrowserSplitDrag() {
endMissingBrowserSplitDrag(event = null) {
if (!this._missingBrowserSplitDragging) return;
event?.preventDefault?.();
event?.stopPropagation?.();
const settlingNow = typeof performance === 'object' && typeof performance.now === 'function'
? performance.now()
: Date.now();
this._missingBrowserSplitSettlingUntil = settlingNow + 350;
const releaseSplitter = this._missingBrowserSplitSplitter;
const releasePointerId = event?.pointerId;
const previousUserSelect = this._missingBrowserPrevUserSelect;
const previousCursor = this._missingBrowserPrevCursor;
const finalWidth = this._pendingMissingBrowserSplitWidth || this._appliedMissingBrowserSplitWidth;
this._missingBrowserSplitDragging = false;
document.removeEventListener('mousemove', this._onMissingBrowserSplitMove);
if (this._missingBrowserSplitMoveEvent) {
document.removeEventListener(this._missingBrowserSplitMoveEvent, this._onMissingBrowserSplitMove, true);
}
if (this._missingBrowserSplitCancelEvent) {
document.removeEventListener(this._missingBrowserSplitCancelEvent, this._onMissingBrowserSplitUp, true);
}
if (this._missingBrowserSplitUpEvent) {
document.removeEventListener(this._missingBrowserSplitUpEvent, this._onMissingBrowserSplitUp, true);
}
if (this._missingBrowserSplitFrame) {
cancelAnimationFrame(this._missingBrowserSplitFrame);
this._missingBrowserSplitFrame = null;
}
if (this._missingBrowserSplitDetailPane && this._pendingMissingBrowserSplitWidth) {
if (this._pendingMissingBrowserSplitWidth !== this._appliedMissingBrowserSplitWidth) {
this._missingBrowserSplitDetailPane.style.setProperty('--mr-missing-detail-track', `${this._pendingMissingBrowserSplitWidth}px`);
this.applyMissingBrowserDetailWidth(this._missingBrowserSplitDetailPane, this._pendingMissingBrowserSplitWidth, {
browserWidth: this._missingBrowserSplitStart?.browserWidth,
availableWidth: this._missingBrowserSplitStart?.availableWidth,
splitBounds: this._missingBrowserSplitStart?.bounds
});
this._appliedMissingBrowserSplitWidth = this._pendingMissingBrowserSplitWidth;
}
try {
localStorage.setItem(this.missingBrowserSplitStorageKey, String(this._pendingMissingBrowserSplitWidth));
} catch (e) {}
}
this._missingBrowserSplitBrowser?.classList.remove('is-resizing');
if (finalWidth) {
this.persistMissingBrowserSplitWidth(finalWidth, { delay: 600 });
}
this.deferMissingBrowserSplitReleaseUi(releaseSplitter, releasePointerId, previousUserSelect, previousCursor);
this._missingBrowserSplitBrowser = null;
this._missingBrowserSplitListPane = null;
this._missingBrowserSplitDetailPane = null;
this._missingBrowserSplitSplitter = null;
this._missingBrowserSplitStart = null;
this._pendingMissingBrowserSplitWidth = null;
this._appliedMissingBrowserSplitWidth = null;
this._lastMissingBrowserSplitApply = 0;
try {
document.body.style.userSelect = this._missingBrowserPrevUserSelect || '';
document.body.style.cursor = this._missingBrowserPrevCursor || '';
} catch (e) {}
this._missingBrowserSplitMoveEvent = null;
this._missingBrowserSplitUpEvent = null;
this._missingBrowserSplitCancelEvent = null;
},
deferMissingBrowserSplitReleaseUi(splitter, pointerId, previousUserSelect, previousCursor) {
const release = () => {
this._missingBrowserReleaseCleanupFrame = null;
if (pointerId !== undefined && splitter?.releasePointerCapture) {
try {
splitter.releasePointerCapture(pointerId);
} catch (e) {}
}
splitter?.classList?.remove('is-resizing');
try {
document.body.style.userSelect = previousUserSelect || '';
document.body.style.cursor = previousCursor || '';
} catch (e) {}
};
if (typeof requestAnimationFrame === 'function') {
this._missingBrowserReleaseCleanupFrame = requestAnimationFrame(release);
} else {
release();
}
},
resizeMissingBrowserDetailBy(browser, delta) {
const detailPane = browser.querySelector('.mr-missing-detail-pane');
if (!(detailPane instanceof HTMLElement)) return;
this.setMissingBrowserDetailWidth(browser, detailPane.getBoundingClientRect().width + delta);
const currentWidth = this.getMissingBrowserDetailTrackWidth(detailPane)
|| detailPane.getBoundingClientRect().width;
this.setMissingBrowserDetailWidth(browser, currentWidth + delta);
},
setMissingBrowserDetailWidth(browser, width, { persist = true } = {}) {
const bounds = this.getMissingBrowserSplitBounds(browser);
const nextWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, width)));
const detailPane = browser.querySelector('.mr-missing-detail-pane');
const target = detailPane instanceof HTMLElement ? detailPane : browser;
target.style.setProperty('--mr-missing-detail-track', `${nextWidth}px`);
applyMissingBrowserDetailWidth(target, width, { skipIfUnchanged = false, browserWidth = null, availableWidth = null, splitBounds = null } = {}) {
const detailPane = target instanceof HTMLElement && target.classList.contains('mr-missing-detail-pane')
? target
: target?.querySelector?.('.mr-missing-detail-pane');
const browser = detailPane instanceof HTMLElement
? detailPane.closest('.mr-missing-browser')
: (target instanceof HTMLElement && target.classList.contains('mr-missing-browser') ? target : null);
if (!(browser instanceof HTMLElement) || !(detailPane instanceof HTMLElement)) return false;
if (persist) {
const listPane = this._missingBrowserSplitListPane instanceof HTMLElement && this._missingBrowserSplitListPane.isConnected
? this._missingBrowserSplitListPane
: browser.querySelector('.mr-missing-list-pane');
const measuredWidth = Number(browserWidth);
const bounds = splitBounds && Number.isFinite(Number(splitBounds.min)) && Number.isFinite(Number(splitBounds.max))
? splitBounds
: Number.isFinite(measuredWidth) && measuredWidth > 0
? this.getMissingBrowserSplitBoundsForWidth(measuredWidth)
: this.getMissingBrowserSplitBounds(browser);
const available = Number.isFinite(Number(availableWidth)) && Number(availableWidth) > 0
? Number(availableWidth)
: bounds.available;
const nextWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, width)));
const listWidth = Math.max(1, Math.round(available - nextWidth));
const detailValue = `${nextWidth}px`;
const listValue = `${listWidth}px`;
if (
skipIfUnchanged
&& detailPane.style.flexBasis === detailValue
&& (!(listPane instanceof HTMLElement) || listPane.style.flexBasis === listValue)
) {
return false;
}
if (listPane instanceof HTMLElement) {
listPane.style.flexBasis = listValue;
listPane.style.flexGrow = '0';
listPane.style.flexShrink = '0';
}
detailPane.style.setProperty('--mr-missing-detail-track', detailValue);
detailPane.style.flexBasis = detailValue;
detailPane.style.flexGrow = '0';
detailPane.style.flexShrink = '0';
this._missingBrowserLastDetailWidth = nextWidth;
return true;
},
clearMissingBrowserSplitStyles(browser) {
if (!(browser instanceof HTMLElement)) return;
const listPane = browser.querySelector('.mr-missing-list-pane');
const detailPane = browser.querySelector('.mr-missing-detail-pane');
[listPane, detailPane].forEach((pane) => {
if (!(pane instanceof HTMLElement)) return;
pane.style.removeProperty('flex-basis');
pane.style.removeProperty('flex-grow');
pane.style.removeProperty('flex-shrink');
});
detailPane?.style?.removeProperty('--mr-missing-detail-track');
},
getMissingBrowserDetailTrackWidth(detailPane) {
if (!(detailPane instanceof HTMLElement)) return null;
const inlineBasis = String(detailPane.style.flexBasis || '').trim();
if (inlineBasis.endsWith('px')) {
const basisWidth = parseFloat(inlineBasis);
if (Number.isFinite(basisWidth) && basisWidth > 0) return basisWidth;
}
const raw = String(detailPane.style.getPropertyValue('--mr-missing-detail-track') || '').trim();
if (!raw.endsWith('px')) return null;
const width = parseFloat(raw);
return Number.isFinite(width) && width > 0 ? width : null;
},
cancelMissingBrowserSplitWidthPersist() {
if (this._missingBrowserSplitPersistIdle && typeof cancelIdleCallback === 'function') {
try {
cancelIdleCallback(this._missingBrowserSplitPersistIdle);
} catch (e) {}
}
if (this._missingBrowserSplitPersistTimer) {
clearTimeout(this._missingBrowserSplitPersistTimer);
}
this._missingBrowserSplitPersistIdle = null;
this._missingBrowserSplitPersistTimer = null;
},
persistMissingBrowserSplitWidth(width, { delay = 180 } = {}) {
const nextWidth = Math.round(Number(width));
if (!Number.isFinite(nextWidth) || nextWidth <= 0) return;
this._missingBrowserStoredSplitWidth = nextWidth;
this._missingBrowserStoredSplitWidthLoaded = true;
this._missingBrowserLastDetailWidth = nextWidth;
this.cancelMissingBrowserSplitWidthPersist();
const writeWidth = () => {
this._missingBrowserSplitPersistIdle = null;
this._missingBrowserSplitPersistTimer = null;
try {
localStorage.setItem(this.missingBrowserSplitStorageKey, String(nextWidth));
} catch (e) {}
};
const writeDelay = Math.max(0, Number(delay) || 0);
this._missingBrowserSplitPersistTimer = setTimeout(writeWidth, writeDelay);
},
setMissingBrowserDetailWidth(browser, width, { persist = true, browserWidth = null, skipIfUnchanged = false } = {}) {
const bounds = this.getMissingBrowserSplitBounds(browser, browserWidth);
const nextWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, width)));
const detailPane = browser.querySelector('.mr-missing-detail-pane');
const target = detailPane instanceof HTMLElement ? detailPane : browser;
this.applyMissingBrowserDetailWidth(target, nextWidth, {
browserWidth: Number.isFinite(Number(browserWidth)) && Number(browserWidth) > 0
? Number(browserWidth)
: null,
availableWidth: bounds.available,
splitBounds: bounds,
skipIfUnchanged
});
if (persist) {
this.persistMissingBrowserSplitWidth(nextWidth);
}
},
getMissingBrowserSplitBounds(browser) {
const browserRect = browser.getBoundingClientRect();
const splitter = browser.querySelector('.mr-missing-browser-splitter');
const splitterWidth = splitter instanceof HTMLElement
? splitter.getBoundingClientRect().width
: 10;
const available = Math.max(1, browserRect.width - splitterWidth);
const minListWidth = Math.min(360, Math.max(240, Math.floor(available * 0.28)));
const max = Math.max(1, available - minListWidth);
const min = Math.min(max, Math.min(420, Math.max(300, Math.floor(available * 0.3))));
return { min, max };
getMissingBrowserSplitBounds(browser, browserWidth = null) {
const measuredWidth = Number(browserWidth);
const width = Number.isFinite(measuredWidth) && measuredWidth > 0
? measuredWidth
: browser.getBoundingClientRect().width;
if (Number.isFinite(width) && width > 0) {
this._missingBrowserObservedWidth = width;
this._missingBrowserLastBrowserWidth = width;
}
return this.getMissingBrowserSplitBoundsForWidth(width);
},
wireMissingModelDetail(container, missing, missingIndex) {