← Commit history

0.30.3: one wheel handler scrolls the whole gantt body, not just task rows

Drew Owens ·9bfdcedc4c · ·parent 4a39f34
4 files changed +66−3
docs/BUILD-NOTES.md+36
@@ -3,6 +3,42 @@ Decisions taken while building, and the places where the plan and the code disagreed. Newest first. +## Build notes, 2026-08-17 (0.30.3, the whole gantt body scrolls)++The plan scrolled only when the cursor sat over the timeline side of a task row.+Over a phase header, anywhere in the label column, or down in the empty tail, the+wheel did nothing.++**Cause: only one element was ever a scroller.** `.timeline-col` is `overflow:+auto`; `.labels-col` is `overflow: hidden` and merely mirrors the timeline's+`scrollTop` from its scroll event. The label column, the headers, and the tail all+live in `.labels-col` or over it, so a wheel there had no scrollable ancestor+(`.chart-wrap` and `body` are both `overflow: hidden`) and the gesture was+swallowed.++**Fix: one wheel owner for the whole body.** A single handler on `.chart-wrap`,+which wraps both columns and the resizer, drives the timeline's scroll from+wherever the pointer is: `deltaY` to `scrollTop`, `deltaX` (or Shift with a+vertical wheel) to `scrollLeft`, with line and page deltas normalised to pixels so+a mouse and a trackpad move the plan by a comparable amount. It sets exactly the+`scrollTop`/`scrollLeft` the native scroller would, so the axis rows, which live+inside `.timeline-col`, behave on scroll exactly as before.++**The label column is synced in the same tick, not on the timeline's scroll+event.** A programmatic `scrollTop` fires that event late, or not at all in some+engines, so the labels are pulled along right in the wheel handler; leaning on the+event left them a frame behind the bars. The existing scroll-event sync stays for+the other things that scroll the timeline (drag-autoscroll, keyboard row-into-view).++Verified headless: a wheel dispatched over a phase header, a task label, a bar+row, the label tail and the bar tail each scroll the plan by the same 120px and+the label column follows every time; at a fine grain where the axis overflows its+pane, Shift+wheel and a horizontal wheel both scroll sideways by the same amount+over bars AND over headers, while the label column never moves horizontally; the+axis rows stay present and track the content; the page behind never rubber-bands.+Twelve suites green, zero JS errors. The table view is untouched: it scrolls its+own container and is outside `.chart-wrap`.+ ## Build notes, 2026-08-17 (0.30.2, a bar cannot disagree with its lane)  The Drone Project lane showed mixed bar colours, some teal, some blue. Not a
gantt.html+27
@@ -2519,6 +2519,32 @@ function mountLabelTailroom(){ labelTail.className='tailroom';labelsEl.appendChild(labelTail); timelineEl.addEventListener('scroll',()=>{labelsEl.scrollTop=timelineEl.scrollTop;}); }+function wireChartWheel(){+  const wrap=timelineEl.closest('.chart-wrap')||timelineEl.parentElement;+  if(!wrap)return;+  wrap.addEventListener('wheel',e=>{+    // A dropdown or menu that opens over the chart and scrolls itself keeps its own+    // wheel; nothing like that lives inside .chart-wrap today, but the guard is cheap.+    if(e.target.closest('.cell-list, .dropdown, .sec-list'))return;+    // Line and page deltas become pixels, so a mouse wheel and a trackpad move the+    // plan by a comparable amount and the maths is uniform.+    const unitY=e.deltaMode===1?16:(e.deltaMode===2?timelineEl.clientHeight:1);+    const unitX=e.deltaMode===1?16:(e.deltaMode===2?timelineEl.clientWidth:1);+    let dx=e.deltaX*unitX, dy=e.deltaY*unitY;+    // Shift with a vertical wheel is the standard "scroll sideways" gesture.+    if(e.shiftKey&&dx===0){ dx=dy; dy=0; }+    if(dy)timelineEl.scrollTop+=dy;+    if(dx)timelineEl.scrollLeft+=dx;+    // Sync the label column HERE rather than lean on the timeline's own scroll+    // event: a programmatic scrollTop fires that event late (or not at all in some+    // engines), so the labels have to be pulled along in the same tick the wheel is+    // handled or they lag a frame behind the bars.+    labelsEl.scrollTop=timelineEl.scrollTop;+    // Own the gesture: the label column has no scroll of its own, and the page+    // behind must never rubber-band because the pointer was over a header.+    e.preventDefault();+  },{passive:false});+}  // ── Responsive relayout ── // Everything on the timeline is positioned in px derived from COL, so a resize@@ -5662,6 +5688,7 @@ async function init(){   mountTodayLine();   mountBarTailroom();   mountLabelTailroom();+  wireChartWheel();   wireRelayoutObservers();   wireDepRedraw();   wireLabelClicks();
package.json+1−1
@@ -2,7 +2,7 @@   "slug": "adom-gantt",   "title": "Adom Gantt",   "type": "app",-  "version": "0.30.2",+  "version": "0.30.3",   "description": "Interactive Gantt chart viewer for project timelines, with a table view over the same data. Data-driven, dark by default with a print-friendly light mode, and fully editable in the browser: drag bars to move or resize, multi-select and batch-shift dates, manage dependency arrows inline, reorder rows, filter by phase, and undo. Toggle to the table for every task and milestone as a row with owner, deadline and status, filtered per owner and edited in place. Every edit is recorded in an append-only changelog so an AI can read what a human changed and build on it. Imports JSON, CSV, MS Project XML and Jira CSV; exports JSON, CSV, SVG and a self-contained HTML snapshot.",   "main": "server.js",   "bin": {
page.json+2−2
@@ -2,7 +2,7 @@   "slug": "adom-gantt",   "title": "Adom Gantt",   "type": "app",-  "version": "0.30.2",+  "version": "0.30.3",   "description": "Interactive Gantt chart viewer for project timelines, with a table view over the same data. Data-driven, dark by default with a print-friendly light mode, and fully editable in the browser: drag bars to move or resize, multi-select and batch-shift dates, manage dependency arrows inline, reorder rows, filter by phase, and undo. Toggle to the table for every task and milestone as a row with owner, deadline and status, filtered per owner and edited in place. Every edit is recorded in an append-only changelog so an AI can read what a human changed and build on it. Imports JSON, CSV, MS Project XML and Jira CSV; exports JSON, CSV, SVG and a self-contained HTML snapshot.",   "main": "server.js",   "bin": {@@ -63,4 +63,4 @@     "name": "Drew Owens",     "email": "[email protected]"   }-}\ No newline at end of file+}