Skip to content

Timeline plugin: timeline view - #240

Open
sandesh-sp wants to merge 4 commits into
feat/timeline-plugin-basefrom
feat/timeline-view
Open

Timeline plugin: timeline view#240
sandesh-sp wants to merge 4 commits into
feat/timeline-plugin-basefrom
feat/timeline-view

Conversation

@sandesh-sp

@sandesh-sp sandesh-sp commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Replaces the timeline view and layer row placeholders with the d3 implementation: top and bottom time axes, a dashed row per visible layer with its time-range bars, and a draggable scrubber that previews while dragging and commits on release. Scroll zooms and pans; the view hands a reset-zoom callback back to the adapter.

Stack — merge in order, starting from the base:

  1. Timeline plugin: shell and component placeholders #239 shell and component placeholders → development
  2. Timeline plugin: timeline view #240 timeline view
  3. Timeline plugin: date selector #241 date selector
  4. Timeline plugin: playback controls #242 playback controls
  5. Timeline plugin: time mode control #243 time mode control

Replaces the timeline view and layer row placeholders with the d3-driven
implementation: a time scale with top and bottom axes, one dashed row per
visible layer with its time-range bars, and a draggable scrubber that
previews while dragging and commits on release. Scroll zooms and pans the
scale, and the view hands a reset-zoom callback back to the adapter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgqjkZhQvfoBgdJtt9SSKS
- Drive the scrubber with Pointer Events and pointer capture, so mouse, touch
  and pen share one path and a drag keeps tracking after the pointer leaves
  the SVG. Adds touch-action: none so the gesture isn't taken for a scroll.
- Expose the scrubber as a slider: tabIndex, role, aria-value*, arrow-key
  stepping by the current granularity, and Home/End for the range ends.
- Ignore clicks that land on the scrubber handle, so grabbing it no longer
  commits a seek, and clear the drag flag on pointer up so a drag released
  outside the SVG doesn't swallow the next click.
- Restore pointer events on the layer range rects so their tooltips display.
@sandesh-sp

Copy link
Copy Markdown
Collaborator Author

Review pass over the stack (#239#243). Five issues in this PR, pushed as 9d2d328. Rebased on the fixes in #239.

  • The scrubber couldn't be dragged on touch. Both hit targets bound only onMouseDown and the drag loop used window mouse events; the d3-zoom filter additionally rejects touchstart on .timeline-scrubber-handle, so the ±5px band around the current time was inert to touch as well. That matters because TimelineTool explicitly awaits app:isMobile and switches to a width: 'full', height: 300 layout — touch is a supported mode. Moved to Pointer Events with setPointerCapture, which covers mouse, touch and pen in one path and keeps tracking when the pointer leaves the SVG, plus touch-action: none so the browser doesn't take the gesture for a scroll.

  • A drag released outside the main SVG swallowed the next click. didDragRef was only cleared by a trailing click on that SVG, and the diamond sits flush against its top edge with a separate <svg> immediately above — drift a few px up while releasing and the flag stayed set, silently dropping the next click-to-seek. Now cleared on pointer up.

  • Clicking the handle committed a seek. stopPropagation on mousedown doesn't suppress the later click, and d3's own click suppressor never installs because the zoom filter rejects the handle first. So grabbing the 18px diamond off-centre emitted a time:changeRequested for the time under the cursor — roughly 8h of drift on an 800px/30-day view, from a gesture meant as a no-op. handleTimelineClick now bails when the target is inside .timeline-scrubber-handle.

  • Scrubber is keyboard operable. It had no tabIndex, role, aria-value* or key handler. Now role="slider" with min/max/now plus a UTC-formatted aria-valuetext, arrow keys stepping by the current granularity, and Home/End for the range ends.

  • Layer tooltips were dead code. .layer-timelines { pointer-events: none } is inherited by every descendant rect, so the <title> elements LayerTimeline renders never surfaced. Re-enabled on .layer-time-range only.

Worth noting what held up under scrutiny: the d3 machinery is solid. Degenerate domains and a zero-width container don't produce NaN (d3-scale's normalize returns constant(0.5)), zoom state survives behavior re-mounting, the wheel listener is correctly non-passive, and svg.on('.zoom', null) removes exactly d3's namespace. No listener or observer leaks.

tsc --noEmit clean, 898 unit tests pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant