Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/warm-charts-wheel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/charts': minor
---

Add configurable focus, modifier, and always wheel activation policies to `zoomX`.
27 changes: 20 additions & 7 deletions API-FRICTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ observed difficulty from examples, production migrations, tests, and agent
evaluations so later API, documentation, and TanStack Intent skill work is
based on evidence.

Last updated: 2026-08-26
Last updated: 2026-09-03

## Triage rule

Expand Down Expand Up @@ -2285,9 +2285,10 @@ Each entry records:
touch previews, controlled pinning, leave/cancel cleanup, toggle/Escape
clearing, and host teardown without importing brush or D3 policy.
Exact-subpath `zoomX` now binds a controlled semantic window to the final x
scale and owns focus-gated wheel capture, pointer-anchored zoom, drag and
horizontal-wheel pan, touch and keyboard input, clamping, cancellation, and
teardown. D3 Zoom remains private to that optional DOM control. Exact-subpath
scale and owns configurable focus, modifier-gated, or unconditional wheel
capture, pointer-anchored zoom, drag and horizontal-wheel pan, touch and
keyboard input, clamping, cancellation, and teardown. D3 Zoom remains private
to that optional DOM control. Exact-subpath
`handleX` binds one controlled semantic x value to ordered candidates and
owns final-scale track, rule and handle paint, nearest snapping, pointer and
touch capture, cancellation, keyboard slider semantics, and teardown without
Expand Down Expand Up @@ -2357,8 +2358,9 @@ Each entry records:
update, and strict type gate at 98.7% diagnostic geometry.
- Zoom follow-up: case 90 now uses exact-subpath `zoomX` in one ordinary
definition. The behavior owns final-scale inversion, wheel normalization and
focus gating, pointer-anchored zoom, drag and horizontal-wheel pan, touch and
keyboard input, clamping, cancellation, controlled updates, and teardown.
activation policy, pointer-anchored zoom, drag and horizontal-wheel pan,
touch and keyboard input, clamping, cancellation, controlled updates, and
teardown.
The case retains the accepted semantic window, visible-row and y-domain
policy, live status, Reset control, persistence, and conformance observation.
The React effect, second SVG, D3 imports, copied scale, transform conversion,
Expand All @@ -2367,12 +2369,23 @@ Each entry records:
limits, responsive replacement, teardown, and source ownership. The exact
optional bundle fixture isolates D3 Zoom and Selection from root, universal,
ordinary DOM, brush, cursor, legend, and selection consumers. It adds 20,429
bytes, or 19.95 KiB gzip, over the ordinary DOM host under its 20 KiB cap.
bytes, or 19.95 KiB gzip, over the ordinary DOM host under its original 20
KiB cap.
The quick paired browser matrix passes unfocused page scrolling, focus-gated
pixel/line/page wheel zoom, horizontal-wheel pan, pointer drag, touch
activation and pan, keyboard, Reset, revisions, visual, and strict type
scenarios at 98.6% diagnostic geometry. TanStack uses 512 authored lines and
51.59 kB gzip versus ECharts' 727 lines and 172.82 kB.
- Wheel-policy follow-up: `zoomX` now accepts `wheelActivation: 'modifier'` for
immediate Control+wheel or Command+wheel input without focus while plain
wheel events remain page-owned. The existing focus policy remains the
default, and an explicit `always` policy supports chart-owned wheel surfaces.
Focused tests cover both modifier keys, unmodified passthrough while focused,
horizontal-wheel pan, unconditional capture, bounds and zero deltas, grouped
commits, cancellation, controlled updates, and mode-specific instructions.
The added policy and instructions bring the exact optional bundle to 20,957
bytes, or 20.47 KiB gzip, under a narrow 20.55 KiB cap without changing root
or universal consumers.
- Scale-handle follow-up: cases 91 and 92 now use exact-subpath `handleX` in
their ordinary definitions. The behavior maps ordered semantic candidates
through the final x scale, paints the track, optional rule and handle, and
Expand Down
6 changes: 3 additions & 3 deletions benchmarks/comparison/bundle-baseline.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"schemaVersion": 4,
"generatedAt": "2026-08-26T21:51:12.832Z",
"generatedAt": "2026-09-03T17:27:31.349Z",
"packageVersions": {
"tanstack": "0.15.0",
"tanstack": "0.16.0",
"chartjs": "4.5.1",
"echarts": "6.1.0",
"recharts": "3.10.1",
Expand All @@ -12,7 +12,7 @@
"tanstack": {
"kind": "workspace",
"revision": "3df87d71f0305e5a450c10b66940f76f3e14259a",
"inputDigest": "sha256:b1016e599e63d05e1647b9d27adabaaed7a38bb44dac8d6ba0fef06b8b8bd38e"
"inputDigest": "sha256:bb3b5cc84f6040f3c8c03d71510c1afafff88ec4827a403d799fa4afcb5c7ccb"
},
"chartjs": {
"kind": "package",
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/conformance/previews/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schemaVersion": 1,
"width": 288,
"height": 192,
"sourceHash": "d3177faf65fca2414b431c195a003cf9afdceb8ae2f1b37e4f8cb81578dc8293",
"sourceHash": "d5e5bbdcb0c10fb17d0975dda7f9241720094d214bc73ee330babc358f353096",
"assets": [
{
"id": "01-line-gaps",
Expand Down
2 changes: 1 addition & 1 deletion docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ output model.

## Bundle snapshot

Baseline date: `2026-08-26`.
Baseline date: `2026-09-03`.

Controlled ranges cover 12 independently built, minified browser consumers:
line, bar, area, and scatter at basic, interactive, and advanced tiers. Only
Expand Down
6 changes: 4 additions & 2 deletions docs/examples/interactive-charts.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,11 @@ start and end values.

Import `zoomX` from `@tanstack/charts/interaction/zoom`, bind its `window` to a
controlled signal, and provide the full `extent` and allowed `scaleExtent`.
The behavior owns final-scale inversion, focus-gated wheel capture,
The behavior owns final-scale inversion, configurable wheel capture,
pointer-anchored zoom, pan, touch and keyboard input, cancellation, clamping,
and teardown.
and teardown. Wheel capture defaults to focused plots. Use
`wheelActivation: 'modifier'` when Control+wheel or Command+wheel should act
without prior focus while unmodified wheels continue scrolling the page.

Keep the accepted window, visible-row or clipping policy, y-domain policy,
status, reset control, persistence, and follow-latest behavior in application
Expand Down
11 changes: 7 additions & 4 deletions docs/guides/accessibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,13 @@ retain the accepted handle paint but need an application-owned semantic input.

`zoomX` exposes one named plot surface with visible focus. Plus and minus zoom,
the arrow keys pan, Home resets, and Escape cancels an active gesture. Wheel
input is captured only while that surface is focused; an unfocused chart must
not trap page scrolling. Keep a visible Reset button and current-window text
bound to the same controlled window. Static SVG and React Native render the
accepted window but need application-owned semantic zoom controls.
input is captured only while that surface is focused by default. For scrolling
dashboards, `wheelActivation: 'modifier'` lets Control+wheel or Command+wheel
act immediately under the pointer while every unmodified wheel keeps scrolling
the page. `wheelActivation: 'always'` deliberately captures every wheel over
the plot. Keep a visible Reset button and current-window text bound to the same
controlled window. Static SVG and React Native render the accepted window but
need application-owned semantic zoom controls.

## Never rely on color alone

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/bundle-size-and-performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ Horizontal zoom is exact-subpath-only through
behavior, final-scale interaction axis, DOM host control, `d3-zoom`, and
`d3-selection` only for a consumer that imports it. Root, universal, ordinary
DOM, brush, cursor, legend, and selection consumers retain none of those
modules. Its incremental DOM-host fixture adds 20.28 KiB gzip under a 20.35 KiB
modules. Its incremental DOM-host fixture adds 20.47 KiB gzip under a 20.55 KiB
cap.

Your bundler must honor ESM exports and tree shaking. Avoid namespace imports
Expand Down
8 changes: 5 additions & 3 deletions docs/guides/interactions-and-selections.md
Original file line number Diff line number Diff line change
Expand Up @@ -667,9 +667,11 @@ const definition = defineChart({
`extent` is the complete allowed x domain. `scaleExtent` is `[1, maximum]` and
defaults to `[1, Infinity]`. The behavior owns final-scale inversion,
pointer-anchored wheel zoom, drag and horizontal-wheel pan, touch input,
keyboard zoom and pan, clamping, cancellation, and teardown. It captures the
wheel only after its plot surface receives focus, so normal page scrolling
remains available beforehand.
keyboard zoom and pan, clamping, cancellation, and teardown. By default, it
captures the wheel only after its plot surface receives focus. Set
`wheelActivation: 'modifier'` to capture Control+wheel or Command+wheel without
prior focus while leaving every unmodified wheel to the page. Set it to
`always` only when every wheel over the plot should be chart-owned.

Every proposal is a complete number or Date window. `ZoomXChange` distinguishes
gesture `preview`, `commit`, and `cancel` events, includes the gesture origin,
Expand Down
27 changes: 16 additions & 11 deletions docs/reference/focus-and-interaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,9 +650,13 @@ with that same window.
The behavior resolves against the final x scale and plot bounds. Its contained
SVG or Canvas host control owns pointer-anchored vertical-wheel zoom, drag and
horizontal-wheel pan, pointer and touch input, delta-mode normalization,
clamping, cancellation, and teardown. It captures wheel input only while the
plot control is focused. Plus and minus zoom around the center, arrow keys pan,
Home proposes the full extent, and Escape cancels an active gesture.
clamping, cancellation, and teardown. `wheelActivation` defaults to `focus`, so
wheel input is captured only while the plot control is focused. Use `modifier`
to capture Control+wheel or Command+wheel under the pointer without prior focus
while every unmodified wheel continues to scroll the page. Use `always` only
when the chart should capture every wheel over its plot. Plus and minus zoom
around the center, arrow keys pan, Home proposes the full extent, and Escape
cancels an active gesture.

`ZoomXChange` reports `preview`, `commit`, or `cancel`; the proposed `value`,
the gesture `origin`, a `zoom`, `pan`, or `reset` action, and a wheel, pointer,
Expand All @@ -672,14 +676,15 @@ render the accepted configured window but provide no zoom host control.

The public zoom types are:

| Type | Contract |
| ---------------------- | ---------------------------------------------------------------------- |
| `ZoomXValue` | A finite `number` or valid `Date` |
| `ZoomXWindow<TValue>` | Semantic `start` and `end` values |
| `ZoomXSource` | Wheel, pointer, touch, or keyboard origin |
| `ZoomXAction` | Zoom, pan, or reset action |
| `ZoomXChange<TValue>` | Preview, commit, and cancel reason union |
| `ZoomXOptions<TValue>` | Controlled window, extent, limits, accessibility, and keyboard options |
| Type | Contract |
| ---------------------- | ------------------------------------------------------------------------ |
| `ZoomXValue` | A finite `number` or valid `Date` |
| `ZoomXWindow<TValue>` | Semantic `start` and `end` values |
| `ZoomXSource` | Wheel, pointer, touch, or keyboard origin |
| `ZoomXAction` | Zoom, pan, or reset action |
| `ZoomXWheelActivation` | Focus, modifier-gated, or unconditional wheel capture |
| `ZoomXChange<TValue>` | Preview, commit, and cancel reason union |
| `ZoomXOptions<TValue>` | Controlled window, extent, limits, wheel, accessibility, and key options |

## Disabling chart-owned focus

Expand Down
2 changes: 1 addition & 1 deletion docs/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ aggregate `/scales` export.
| `@tanstack/charts/interaction/brush` | `brushX` and horizontal-brush range, change, source, target, and option types |
| `@tanstack/charts/interaction/cursor` | `continuousCursor` and continuous-cursor position, change, guide, label, and option types |
| `@tanstack/charts/interaction/signal` | `controlledSignal`, `ControlledSignal`, `ControlledSignalChangeContext` |
| `@tanstack/charts/interaction/zoom` | `zoomX`, `ZoomXValue`, `ZoomXWindow`, `ZoomXSource`, `ZoomXAction`, `ZoomXChange`, and `ZoomXOptions` |
| `@tanstack/charts/interaction/zoom` | `zoomX`, `ZoomXValue`, `ZoomXWindow`, `ZoomXSource`, `ZoomXAction`, `ZoomXWheelActivation`, `ZoomXChange`, and `ZoomXOptions` |
| `@tanstack/charts/legend` | `colorLegend`, `colorGradientLegend`, `interactiveColorLegend`, `InteractiveColorLegendItemContext`, and legend option/change types |
| `@tanstack/charts/line` | `lineY`, `lineX`, `LineYOptions`, and `LineXOptions` |
| `@tanstack/charts/regression` | `linearRegressionRowsY`, `linearRegressionRowsX`, `linearRegressionY`, `linearRegressionX`, derived datum types, and option types |
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,8 @@ their behavior:
`ContinuousCursorLabelOptions`, and `ContinuousCursorOptions`. See
[Continuous cursor](./focus-and-interaction.md#continuous-cursor).
- `@tanstack/charts/interaction/zoom`: `ZoomXValue`, `ZoomXWindow`,
`ZoomXSource`, `ZoomXAction`, `ZoomXChange`, and `ZoomXOptions`. See
`ZoomXSource`, `ZoomXAction`, `ZoomXWheelActivation`, `ZoomXChange`, and
`ZoomXOptions`. See
[Horizontal zoom](./focus-and-interaction.md#horizontal-zoom).
- `@tanstack/charts/polar`: `PolarOptions`, `PolarScales`, `PolarMark`,
`PolarGuide`, `PolarGuideScene`, `PolarAngleOptions`,
Expand Down
2 changes: 1 addition & 1 deletion packages/charts-core/docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ output model.

## Bundle snapshot

Baseline date: `2026-08-26`.
Baseline date: `2026-09-03`.

Controlled ranges cover 12 independently built, minified browser consumers:
line, bar, area, and scatter at basic, interactive, and advanced tiers. Only
Expand Down
6 changes: 4 additions & 2 deletions packages/charts-core/docs/examples/interactive-charts.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,11 @@ start and end values.

Import `zoomX` from `@tanstack/charts/interaction/zoom`, bind its `window` to a
controlled signal, and provide the full `extent` and allowed `scaleExtent`.
The behavior owns final-scale inversion, focus-gated wheel capture,
The behavior owns final-scale inversion, configurable wheel capture,
pointer-anchored zoom, pan, touch and keyboard input, cancellation, clamping,
and teardown.
and teardown. Wheel capture defaults to focused plots. Use
`wheelActivation: 'modifier'` when Control+wheel or Command+wheel should act
without prior focus while unmodified wheels continue scrolling the page.

Keep the accepted window, visible-row or clipping policy, y-domain policy,
status, reset control, persistence, and follow-latest behavior in application
Expand Down
11 changes: 7 additions & 4 deletions packages/charts-core/docs/guides/accessibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,13 @@ retain the accepted handle paint but need an application-owned semantic input.

`zoomX` exposes one named plot surface with visible focus. Plus and minus zoom,
the arrow keys pan, Home resets, and Escape cancels an active gesture. Wheel
input is captured only while that surface is focused; an unfocused chart must
not trap page scrolling. Keep a visible Reset button and current-window text
bound to the same controlled window. Static SVG and React Native render the
accepted window but need application-owned semantic zoom controls.
input is captured only while that surface is focused by default. For scrolling
dashboards, `wheelActivation: 'modifier'` lets Control+wheel or Command+wheel
act immediately under the pointer while every unmodified wheel keeps scrolling
the page. `wheelActivation: 'always'` deliberately captures every wheel over
the plot. Keep a visible Reset button and current-window text bound to the same
controlled window. Static SVG and React Native render the accepted window but
need application-owned semantic zoom controls.

## Never rely on color alone

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ Horizontal zoom is exact-subpath-only through
behavior, final-scale interaction axis, DOM host control, `d3-zoom`, and
`d3-selection` only for a consumer that imports it. Root, universal, ordinary
DOM, brush, cursor, legend, and selection consumers retain none of those
modules. Its incremental DOM-host fixture adds 20.28 KiB gzip under a 20.35 KiB
modules. Its incremental DOM-host fixture adds 20.47 KiB gzip under a 20.55 KiB
cap.

Your bundler must honor ESM exports and tree shaking. Avoid namespace imports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -667,9 +667,11 @@ const definition = defineChart({
`extent` is the complete allowed x domain. `scaleExtent` is `[1, maximum]` and
defaults to `[1, Infinity]`. The behavior owns final-scale inversion,
pointer-anchored wheel zoom, drag and horizontal-wheel pan, touch input,
keyboard zoom and pan, clamping, cancellation, and teardown. It captures the
wheel only after its plot surface receives focus, so normal page scrolling
remains available beforehand.
keyboard zoom and pan, clamping, cancellation, and teardown. By default, it
captures the wheel only after its plot surface receives focus. Set
`wheelActivation: 'modifier'` to capture Control+wheel or Command+wheel without
prior focus while leaving every unmodified wheel to the page. Set it to
`always` only when every wheel over the plot should be chart-owned.

Every proposal is a complete number or Date window. `ZoomXChange` distinguishes
gesture `preview`, `commit`, and `cancel` events, includes the gesture origin,
Expand Down
Loading