Handsontable 18.1.0 — pinned columns obscure data
HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/handsontable@18.1.0/styles/handsontable.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/handsontable@18.1.0/styles/ht-theme-main.min.css">
<script src="https://cdn.jsdelivr.net/npm/handsontable@18.1.0/dist/handsontable.full.min.js"></script>
<h2>Pinned columns obscure data — reproduction starter</h2>
<p>Handsontable 18.1.0 · 2 frozen columns · 12 columns total</p>
<p>Scroll horizontally, widen the pinned columns, or shrink the table.
Columns C–L should remain reachable. Compare with no pinned columns.</p>
<div class="controls">
<label>Table width <input id="tableWidth" type="number" min="180" max="1400" value="560" step="20"></label>
<label>Pinned column width <input id="pinnedWidth" type="number" min="60" max="600" value="260" step="20"></label>
<button id="apply">Apply</button>
<button id="wide">Widen pinned columns</button>
<button id="toggle">Unpin columns</button>
<button id="reset">Reset</button>
</div>
<p id="status" role="status"></p>
<div id="grid"></div>
<p><strong>Test:</strong> Reset → Widen pinned columns → try scrolling to C–L → Unpin columns.</p>
<p>This tests the case where frozen columns consume the viewport. It is a starter for isolating the reported overlap.</p>
CSS
body { font: 14px/1.5 system-ui, sans-serif; padding: 16px; margin: 0; color: #1c2736; }
h2 { font-size: 18px; margin: 0 0 8px; }
p { max-width: 760px; }
.controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
input { width: 65px; padding: 5px; }
button { padding: 6px 10px; cursor: pointer; }
#grid { width: 560px; overflow: hidden; }
#status { font-family: monospace; }
.handsontable td.pinned { background: #fff0bf; color: #624600; }
JavaScript
// Exact JS and CSS version: 18.1.0. Synthetic data only.
const data = Array.from({ length: 30 }, (_, row) =>
Array.from({ length: 12 }, (_, col) =>
String.fromCharCode(65 + col) + (row + 1) + (col < 2 ? ' (pinned)' : ' data')
)
);
const grid = document.getElementById('grid');
let pinned = true;
const hot = new Handsontable(grid, {
data,
themeName: 'ht-theme-main',
width: 560,
height: 320,
rowHeaders: true,
colHeaders: true,
fixedColumnsStart: 2,
colWidths: 260,
autoColumnSize: false,
manualColumnResize: true,
stretchH: 'none',
cells(row, col) { return { className: col < 2 ? 'pinned' : '' }; },
licenseKey: 'non-commercial-and-evaluation',
afterColumnResize() { requestAnimationFrame(report); }
});
window.hot = hot;
function report() {
const frozenWidth = pinned ? hot.getColWidth(0) + hot.getColWidth(1) : 0;
const viewport = grid.clientWidth;
document.getElementById('status').textContent =
'v' + Handsontable.version + ' | table: ' + viewport +
'px | pinned: ' + frozenWidth + 'px (+ row header)' +
(frozenWidth >= viewport ? ' | Pinned columns occupy the entire viewport.' : '');
}
function apply() {
const width = Math.max(180, Number(document.getElementById('tableWidth').value) || 560);
const frozenWidth = Math.max(60, Number(document.getElementById('pinnedWidth').value) || 260);
grid.style.width = width + 'px';
hot.updateSettings({
width,
fixedColumnsStart: pinned ? 2 : 0,
colWidths: Array.from({ length: 12 }, (_, col) => col < 2 ? frozenWidth : 260),
manualColumnResize: true
});
document.getElementById('toggle').textContent = pinned ? 'Unpin columns' : 'Pin 2 columns';
report();
}
document.getElementById('apply').onclick = apply;
document.getElementById('wide').onclick = () => {
document.getElementById('pinnedWidth').value = 300;
apply();
};
document.getElementById('toggle').onclick = () => { pinned = !pinned; apply();...