Custom View: native sticky header, one-line Excel-style header; fixes
- Custom View header now uses the browser's sticky positioning like the other tables, so it no longer lags or bounces while scrolling. Because a sideways-scrolling box prevents sticky headers, the table is clipped and shifted sideways by a scrollbar pinned to the bottom of the window (shown only when the columns are wider than the page; trackpad, shift+wheel and touch swipes drive it too) - Custom View header on one line like the other tables: drag grip, name, the combined sort/filter dropdown and copy icon (remove on hover) - Workbench stack text boxes size to their contents on load and when the page is shown, not only while typing - Demo sandbox waits for the worker's first sweep after a container restart instead of treating every device as unknown
This commit is contained in:
1 parent
b216ce43f2
commit
318c59e610
4 files changed
+114
-31
No files matched your search
+67
-13
@@ -76,27 +76,77 @@
|
||||
setVar(table, '--sticky-top', top);
|
||||
if (table.parentElement) setVar(table.parentElement, '--sticky-top', top); // for the filter chip bar
|
||||
});
|
||||
pinCustomHeader();
|
||||
setupCustomHScroll();
|
||||
}
|
||||
|
||||
// Custom View can be wider than the page, so it scrolls sideways in its own box, and a
|
||||
// sideways-scrolling box can't contain a position:sticky header. Instead its header is
|
||||
// shifted down by exactly the amount needed to stay pinned under the group header.
|
||||
function pinCustomHeader() {
|
||||
// Custom View can be wider than the page. A sideways-scrolling box would stop its header
|
||||
// from sticking, so the box clips instead (overflow-x: clip) and the table is shifted
|
||||
// sideways by a scrollbar pinned to the bottom of the window. Wheel, shift+wheel and
|
||||
// touch swipes over the table drive the same scrollbar.
|
||||
function setupCustomHScroll() {
|
||||
const box = document.getElementById('scroll-custom');
|
||||
const host = document.getElementById('custom-table-host');
|
||||
const table = document.getElementById('table-custom');
|
||||
if (!table) return;
|
||||
if (cardsQuery.matches || !table.offsetParent || !table.tHead) { setVar(table, '--pin-y', '0px'); return; }
|
||||
const top = parseFloat(table.style.getPropertyValue('--sticky-top')) || 0;
|
||||
const r = table.getBoundingClientRect();
|
||||
const headH = table.tHead.getBoundingClientRect().height;
|
||||
const y = Math.max(0, Math.min(top - r.top, r.height - headH));
|
||||
setVar(table, '--pin-y', y + 'px');
|
||||
if (!box || !host || !table) return;
|
||||
let bar = document.getElementById('custom-hscroll');
|
||||
if (!bar) {
|
||||
bar = document.createElement('div');
|
||||
bar.id = 'custom-hscroll';
|
||||
bar.className = 'custom-hscroll no-copy';
|
||||
bar.appendChild(document.createElement('div'));
|
||||
box.insertAdjacentElement('afterend', bar);
|
||||
const applyX = () => { host.style.transform = bar.scrollLeft ? `translateX(${-bar.scrollLeft}px)` : ''; };
|
||||
bar.addEventListener('scroll', applyX, { passive: true });
|
||||
box.addEventListener('wheel', e => {
|
||||
if (bar.hidden) return;
|
||||
const dx = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : (e.shiftKey ? e.deltaY : 0);
|
||||
if (!dx) return;
|
||||
e.preventDefault();
|
||||
bar.scrollLeft += dx;
|
||||
}, { passive: false });
|
||||
let start = null;
|
||||
box.addEventListener('touchstart', e => {
|
||||
if (bar.hidden || e.touches.length !== 1) { start = null; return; }
|
||||
start = { x: e.touches[0].clientX, y: e.touches[0].clientY, left: bar.scrollLeft, lock: null };
|
||||
}, { passive: true });
|
||||
box.addEventListener('touchmove', e => {
|
||||
if (!start) return;
|
||||
const dx = e.touches[0].clientX - start.x, dy = e.touches[0].clientY - start.y;
|
||||
if (!start.lock && Math.hypot(dx, dy) > 8) start.lock = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y';
|
||||
if (start.lock === 'x') { e.preventDefault(); bar.scrollLeft = start.left - dx; }
|
||||
}, { passive: false });
|
||||
box.addEventListener('touchend', () => { start = null; }, { passive: true });
|
||||
}
|
||||
if (cardsQuery.matches || !table.offsetParent) {
|
||||
bar.hidden = true;
|
||||
if (host.style.transform) host.style.transform = '';
|
||||
return;
|
||||
}
|
||||
const full = Math.ceil(table.getBoundingClientRect().width);
|
||||
const view = box.clientWidth;
|
||||
const inner = bar.firstChild;
|
||||
if (inner.style.width !== full + 'px') inner.style.width = full + 'px';
|
||||
const hide = full <= view + 1;
|
||||
if (bar.hidden !== hide) bar.hidden = hide;
|
||||
if (hide && bar.scrollLeft) { bar.scrollLeft = 0; host.style.transform = ''; }
|
||||
}
|
||||
|
||||
// Auto-sizing text boxes (Workbench stacks) only grow while typing in the original;
|
||||
// size them to their contents whenever they're shown or re-rendered too.
|
||||
function autosizeTextareas() {
|
||||
document.querySelectorAll('textarea.auto-resize').forEach(t => {
|
||||
if (!t.offsetParent || document.activeElement === t) return;
|
||||
if (Math.abs(t.scrollHeight - t.clientHeight) <= 2) return;
|
||||
t.style.height = 'auto';
|
||||
t.style.height = t.scrollHeight + 'px';
|
||||
});
|
||||
}
|
||||
|
||||
let queued = false;
|
||||
function run() {
|
||||
queued = false;
|
||||
updateSticky();
|
||||
autosizeTextareas();
|
||||
if (!cardsQuery.matches) return;
|
||||
document.querySelectorAll('table.master-table').forEach(labelTable);
|
||||
}
|
||||
@@ -114,7 +164,11 @@
|
||||
// Groups and pages are shown by toggling display (not a DOM change), so also
|
||||
// re-measure as the user scrolls; writes only happen when a value changes.
|
||||
window.addEventListener('scroll', schedule, { passive: true });
|
||||
window.addEventListener('scroll', pinCustomHeader, { passive: true }); // same frame: no lag
|
||||
// Page switches show and hide pages without changing the DOM
|
||||
if (typeof window.switchPage === 'function') {
|
||||
const orig = window.switchPage;
|
||||
window.switchPage = function () { const r = orig.apply(this, arguments); schedule(); return r; };
|
||||
}
|
||||
// The terminal dock opens, closes and resizes without a page resize
|
||||
if (window.ResizeObserver) {
|
||||
const watchDock = () => {
|
||||
|
||||
Reference in new issue
Block a user