← back to Nationalrealestate
Fix column resize on markets/brokers grids (adjust column not working)
3dfd37e1c113a60adf37e62c7aef414a1c008950 · 2026-07-31 11:38:10 -0700 · Steve Abrams
Propagate the fixed col-resize.js from CRCP (was the old pre-fix drop-in with
none of the width-application / hidden-table / handle-position / draggable-guard
fixes) AND add the missing per-engine wiring in crcp-grid.js:
- col-resize.js: full parity with commercialrealestate's fixed copy
(syncTableWidth pins table width so fixed-layout honors <col> widths, hidden-
table bail, reattach, handle right:0, draggable guard, startW-from-style).
- crcp-grid.js render(): deterministic ColResize.refresh() after the innerHTML
table rebuild instead of racing the async MutationObserver.
- crcp-grid.js sort handler: ignore clicks on .cr-handle / during cr-dragging,
so grabbing the resize handle no longer fires a sort + full re-render mid-drag
(that was collapsing resize to a single 14px step / 0).
Verified real-mouse (Playwright) on the running :9913 app: markets 59->179,
brokers 112->232 (was 59->59 / 112->112 broken); sort still works; 0 errors.
Local only — Kamatera deploy is gated. yoloforever cycle 2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M public/col-resize.jsM public/crcp-grid.js
Diff
commit 3dfd37e1c113a60adf37e62c7aef414a1c008950
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jul 31 11:38:10 2026 -0700
Fix column resize on markets/brokers grids (adjust column not working)
Propagate the fixed col-resize.js from CRCP (was the old pre-fix drop-in with
none of the width-application / hidden-table / handle-position / draggable-guard
fixes) AND add the missing per-engine wiring in crcp-grid.js:
- col-resize.js: full parity with commercialrealestate's fixed copy
(syncTableWidth pins table width so fixed-layout honors <col> widths, hidden-
table bail, reattach, handle right:0, draggable guard, startW-from-style).
- crcp-grid.js render(): deterministic ColResize.refresh() after the innerHTML
table rebuild instead of racing the async MutationObserver.
- crcp-grid.js sort handler: ignore clicks on .cr-handle / during cr-dragging,
so grabbing the resize handle no longer fires a sort + full re-render mid-drag
(that was collapsing resize to a single 14px step / 0).
Verified real-mouse (Playwright) on the running :9913 app: markets 59->179,
brokers 112->232 (was 59->59 / 112->112 broken); sort still works; 0 errors.
Local only — Kamatera deploy is gated. yoloforever cycle 2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
public/col-resize.js | 98 +++++++++++++++++++++++++++++++++++++++++++++++++---
public/crcp-grid.js | 8 +++++
2 files changed, 101 insertions(+), 5 deletions(-)
diff --git a/public/col-resize.js b/public/col-resize.js
index 66ba0c1..a322990 100644
--- a/public/col-resize.js
+++ b/public/col-resize.js
@@ -59,6 +59,19 @@
return cg;
}
+ // ---- keep the table's own width == sum of column widths -------------------
+ // CRITICAL for table-layout:fixed: with width:auto the browser shrink-to-fits
+ // the table to its container and REDISTRIBUTES column widths, silently ignoring
+ // any <col> width we set (so resizing appears to do nothing). Pinning the table
+ // to the exact sum of the column widths makes the <col> widths authoritative and
+ // lets the table grow past its container into the overflow-x:auto scroll wrapper.
+ function syncTableWidth(table, cols) {
+ if (table.getBoundingClientRect().width === 0) return; // hidden — never pin garbage widths
+ var sum = 0;
+ for (var i = 0; i < cols.length; i++) sum += parseFloat(cols[i].style.width) || 0;
+ if (sum > 0) table.style.width = Math.round(sum) + 'px';
+ }
+
// ---- wrap table in a horizontal-scroll container --------------------------
function ensureScroll(table) {
var p = table.parentElement;
@@ -75,6 +88,11 @@
if (table.dataset.crDone === '1' || table.hasAttribute('data-no-resize')) return;
var headers = headerCells(table);
if (headers.length < 2) return; // nothing meaningful to resize
+ // Don't enhance a HIDDEN table: its cells measure 0 → clamp to MIN, and
+ // syncTableWidth would then pin the table to MIN×ncols (e.g. 240px), so it
+ // renders as a broken sliver when later shown. Leave it untouched (renders
+ // fine at its natural CSS width); it enhances once a scan sees it visible.
+ if (table.getBoundingClientRect().width === 0) return;
table.dataset.crDone = '1';
var sig = tableSig(headers);
@@ -85,14 +103,25 @@
// browser stops redistributing. Saved widths override the snapshot.
var measured = headers.map(function (h) { return Math.max(MIN, Math.round(h.getBoundingClientRect().width)); });
table.style.tableLayout = 'fixed';
- table.style.width = 'auto';
for (var i = 0; i < headers.length; i++) {
var saved = load(sig, i);
cols[i].style.width = (saved != null ? Math.max(MIN, saved) : measured[i]) + 'px';
}
+ syncTableWidth(table, cols); // definite width so fixed-layout honors the <col> widths
// Add a drag handle to each header cell.
+ attachHandles(table, headers, cols, sig);
+
+ addSorting(table, headers);
+ }
+
+ // ---- (re)attach a drag handle to any header cell missing one ---------------
+ // Idempotent: an existing .cr-handle short-circuits, so this is safe to call
+ // repeatedly. Used both by enhance() and by reattach() after a host-app
+ // <thead> rebuild wipes the handle spans.
+ function attachHandles(table, headers, cols, sig) {
headers.forEach(function (th, idx) {
+ if (th.querySelector('.cr-handle')) return;
var cs = getComputedStyle(th);
if (cs.position === 'static') th.style.position = 'relative';
var handle = document.createElement('span');
@@ -101,10 +130,50 @@
handle.setAttribute('aria-orientation', 'vertical');
handle.title = 'Drag to resize • double-click to auto-fit';
th.appendChild(handle);
+ guardDraggableAncestor(handle, th);
wireHandle(handle, table, cols, headers, sig, idx);
});
+ }
- addSorting(table, headers);
+ // ---- coexist with native column-reorder (draggable <th>) ------------------
+ // If a header cell (or an ancestor) is draggable="true" for HTML5 drag-reorder,
+ // a real mousedown on the resize handle starts a NATIVE drag, which suppresses
+ // the pointermove stream the resizer needs — so the edge won't resize. The drag
+ // decision is made at mousedown, so we must clear draggable BEFORE the press:
+ // disable it whenever the pointer is over the handle, restore it on leave.
+ function guardDraggableAncestor(handle, th) {
+ var anc = (th.closest && th.closest('[draggable="true"]')) || null;
+ if (!anc) return;
+ var restore = function () { anc.draggable = true; };
+ handle.addEventListener('mouseenter', function () { anc.draggable = false; });
+ handle.addEventListener('mouseleave', restore);
+ // Belt-and-suspenders for pointer paths that skip mouseenter (pen/touch):
+ handle.addEventListener('pointerdown', function () { anc.draggable = false; });
+ window.addEventListener('pointerup', restore);
+ }
+
+ // ---- reattach after the host app rebuilds <thead> innerHTML ----------------
+ // A single-page app that re-renders its own <thead> (sort / search / filter)
+ // destroys the handle spans but leaves the <colgroup> — so widths survive but
+ // resizing dies. This re-wires the handles onto the fresh <th>s WITHOUT
+ // touching the colgroup, preserving custom widths. Falls back to a full
+ // enhance() when the table was never enhanced or its column COUNT changed.
+ function reattach(table) {
+ if (!table) return;
+ var cg = table.querySelector('colgroup[data-cr]');
+ if (!cg) { enhance(table); return; } // never enhanced → full path
+ var headers = headerCells(table);
+ if (!headers.length || headers.length !== cg.children.length) {
+ cg.remove(); // column set changed → rebuild
+ delete table.dataset.crDone;
+ table.style.tableLayout = '';
+ table.style.width = '';
+ enhance(table);
+ return;
+ }
+ table.dataset.crDone = '1';
+ attachHandles(table, headers, cg.children, tableSig(headers));
+ syncTableWidth(table, cg.children); // re-assert width in case a re-render cleared it
}
// ---- click-a-header-to-sort (asc → desc → original) -----------------------
@@ -196,7 +265,11 @@
function down(e) {
dragging = true;
startX = (e.touches ? e.touches[0].clientX : e.clientX);
- startW = cols[idx].getBoundingClientRect().width;
+ // Read the width we SET, not <col>.getBoundingClientRect() — that rect is
+ // unreliable for a <col> (Chromium returns stale/tiny values, same quirk as
+ // TK-10089). A bad startW + a zero-delta move (e.g. the mousedowns inside a
+ // double-click) would otherwise collapse the column to MIN (40px).
+ startW = parseFloat(cols[idx].style.width) || cols[idx].getBoundingClientRect().width;
document.body.classList.add('cr-dragging');
window.addEventListener('pointermove', move);
window.addEventListener('pointerup', up);
@@ -210,6 +283,7 @@
var x = (e.touches ? e.touches[0].clientX : e.clientX);
var w = Math.max(MIN, startW + (x - startX));
cols[idx].style.width = w + 'px';
+ syncTableWidth(table, cols); // grow/shrink the table with the dragged column
if (e.cancelable) e.preventDefault();
}
function up() {
@@ -221,7 +295,12 @@
window.removeEventListener('touchmove', move);
window.removeEventListener('touchend', up);
lastResizeUp = Date.now();
- save(sig, idx, cols[idx].getBoundingClientRect().width);
+ // Persist the width move() actually applied. A <col>'s getBoundingClientRect()
+ // does NOT reliably reflect a just-set style.width (Chromium returns a stale value),
+ // so saving that reverted resized columns to their old width on reload. Read the
+ // style we set; fall back to the header cell's rendered width (TK-10089).
+ var savedW = parseFloat(cols[idx].style.width) || headers[idx].getBoundingClientRect().width;
+ save(sig, idx, savedW);
}
// Double-click → auto-fit this column to its content.
function autofit(e) {
@@ -230,12 +309,16 @@
// Remember every column's current width, let this one go auto, measure, restore.
var widths = [];
for (var i = 0; i < cols.length; i++) widths[i] = cols[i].style.width;
+ var savedTableW = table.style.width;
table.style.tableLayout = 'auto';
+ table.style.width = ''; // release the pinned width so the column can size to content
cols[idx].style.width = '';
var natural = Math.max(MIN, Math.round(headers[idx].getBoundingClientRect().width));
for (var j = 0; j < cols.length; j++) cols[j].style.width = widths[j];
cols[idx].style.width = natural + 'px';
table.style.tableLayout = 'fixed';
+ table.style.width = savedTableW;
+ syncTableWidth(table, cols);
save(sig, idx, natural);
}
@@ -250,7 +333,11 @@
if (document.getElementById('cr-style')) return;
var css = ''
+ '.cr-scroll{overflow-x:auto;max-width:100%;}'
- + '.cr-handle{position:absolute;top:0;right:-' + (HANDLE_W / 2) + 'px;width:' + HANDLE_W + 'px;height:100%;'
+ // Handle sits INSIDE the column's right edge (right:0), not straddling it.
+ // Straddling (right:-N) pushes half the handle over the NEXT sticky <th>,
+ // which then paints on top (sibling stacking context) and steals the click —
+ // breaking resize entirely on draggable-column tables.
+ + '.cr-handle{position:absolute;top:0;right:0;width:' + HANDLE_W + 'px;height:100%;'
+ 'cursor:col-resize;user-select:none;touch-action:none;z-index:5;}'
+ '.cr-handle:hover::after,.cr-dragging .cr-handle::after{content:"";position:absolute;top:0;left:50%;'
+ 'width:2px;height:100%;transform:translateX(-50%);background:currentColor;opacity:.55;}'
@@ -294,6 +381,7 @@
window.ColResize = {
refresh: function () { scan(document); },
+ reattach: reattach, // re-wire handles after a host-app thead rebuild
reset: function () { // wipe all saved widths for this page
try {
Object.keys(localStorage).forEach(function (k) {
diff --git a/public/crcp-grid.js b/public/crcp-grid.js
index 71afff4..206b394 100644
--- a/public/crcp-grid.js
+++ b/public/crcp-grid.js
@@ -275,6 +275,10 @@
if (sel) sel.value = SORT && SORT.k ? SORT.k + ':' + (SORT.dir < 0 ? 'desc' : 'asc') : '';
const ab = $(O.mount + ' .cg-active');
if (ab) ab.innerHTML = activeFilterHTML();
+ // render() rebuilds the whole table via innerHTML, so the previous
+ // col-resize enhancement is gone. Re-enhance deterministically instead of
+ // waiting on the async MutationObserver (which races the first interaction).
+ if (window.ColResize && window.ColResize.refresh) window.ColResize.refresh();
syncURL();
}
@@ -328,6 +332,10 @@
}
const af = e.target.closest('.cg-af, .cg-af-clear');
if (af) { clearFilter(af); return; }
+ // A click on the resize handle (or the tail of a resize drag) must NOT
+ // sort — otherwise grabbing the handle re-renders the whole table
+ // mid-gesture and the column never resizes. col-resize owns the handle.
+ if (e.target.closest('.cr-handle') || document.body.classList.contains('cr-dragging')) return;
const th = e.target.closest('.cg-sortth');
if (th && th.dataset.k) {
const k = th.dataset.k;
← 06c2d3a Add recent_commercial_deals view — closed commercial deals f
·
back to Nationalrealestate
·
listings.html: load col-resize.js for parity with markets/br 498df4a →