@pond-ts/process 0.59.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/API.md +21 -16
  2. package/CHANGELOG.md +238 -1
  3. package/package.json +2 -2
package/API.md CHANGED
@@ -249,17 +249,17 @@ Types: `UseSnapshotOptions`, `SnapshotSource` (structural — covers
249
249
 
250
250
  ### Components — layout & axes
251
251
 
252
- | Component | Key props | Purpose | Source |
253
- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
254
- | `ChartContainer` | `width`, `range?`, `theme?`, `cursor?`, `panZoom?`, `bounds?`, `showAxis?`, `calendar?`, `origin?`, `maxBandWidth?`/`bandAlign?`, `onTrackerChanged?`, `onDrawStats?` | Root: shared x-scale, interactions, annotations | `packages/charts/src/ChartContainer.tsx` |
255
- | `ChartRow` | `height`, `cursor?` (deprecated — mount a cursor in the row) | One stacked plot band; owns its y-axes | `packages/charts/src/ChartRow.tsx` |
256
- | `Layers` | children | Mandatory z-stack inside a row (back-to-front) | `packages/charts/src/Layers.tsx` |
257
- | `YAxis` | `id` (req), `side?`, `scale?` (`'linear'` \| `'log'` \| `'symlog'`), `linearWindow?`, `min?`/`max?`, `format?`, `width?`, `hide?` | Y-axis gutter; layers bind via their `axis` prop | `packages/charts/src/YAxis.tsx` |
258
- | `XAxis` | `side?`, `label?`, `format?`, `ticks?`, `transform?`, `dateStyle?` | Placeable x-axis strip; kind inferred from data | `packages/charts/src/XAxis.tsx` |
259
- | `TimeAxis` / `CategoryAxis` | (XAxis props) | Thin `XAxis` presets | `packages/charts/src/TimeAxis.tsx`, `CategoryAxis.tsx` |
260
- | `Canvas` | `width`, `height`, `draw` | Low-level DPR-aware canvas primitive | `packages/charts/src/Canvas.tsx` |
261
- | `Selector` | `enabled?` (default `true`), `selected?` (mark \| set), `hovered?`, `onSelect?`, `onHover?`, `children?` | Wraps its scope; mounting enables click-select and owns the state it drives (RFC A10) | `packages/charts/src/selectors.tsx` |
262
- | `MultiSelector` | `enabled?`, `selected?`, `hovered?`, `sequence?`, `onSelect?`, `onHover?`, `children?` | Sweep-select superset of `Selector`: drag sweeps marks, release reports `(hits, modifiers, spans)` — plural, one per swept layer (RFC A5.2) | `packages/charts/src/selectors.tsx` |
252
+ | Component | Key props | Purpose | Source |
253
+ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
254
+ | `ChartContainer` | `width`, `range?`, `theme?`, `cursor?`, `panZoom?`, `xScale?`, `bounds?`, `showAxis?`, `calendar?`, `origin?`, `maxBandWidth?`/`bandAlign?`, `onTrackerChanged?`, `onDrawStats?` | Root: shared x-scale, interactions, annotations | `packages/charts/src/ChartContainer.tsx` |
255
+ | `ChartRow` | `height`, `cursor?` (deprecated — mount a cursor in the row) | One stacked plot band; owns its y-axes | `packages/charts/src/ChartRow.tsx` |
256
+ | `Layers` | children | Mandatory z-stack inside a row (back-to-front) | `packages/charts/src/Layers.tsx` |
257
+ | `YAxis` | `id` (req), `side?`, `scale?` (`'linear'` \| `'log'` \| `'symlog'`), `linearWindow?`, `min?`/`max?`, `format?`, `width?`, `hide?` | Y-axis gutter; layers bind via their `axis` prop | `packages/charts/src/YAxis.tsx` |
258
+ | `XAxis` | `side?`, `label?`, `format?`, `ticks?`, `transform?`, `dateStyle?` | Placeable x-axis strip; kind inferred from data | `packages/charts/src/XAxis.tsx` |
259
+ | `TimeAxis` / `CategoryAxis` | (XAxis props) | Thin `XAxis` presets | `packages/charts/src/TimeAxis.tsx`, `CategoryAxis.tsx` |
260
+ | `Canvas` | `width`, `height`, `draw` | Low-level DPR-aware canvas primitive | `packages/charts/src/Canvas.tsx` |
261
+ | `Selector` | `enabled?` (default `true`), `selected?` (mark \| set), `hovered?`, `onSelect?`, `onHover?`, `children?` | Wraps its scope; mounting enables click-select and owns the state it drives (RFC A10) | `packages/charts/src/selectors.tsx` |
262
+ | `MultiSelector` | `enabled?`, `selected?`, `hovered?`, `sequence?`, `onSelect?`, `onHover?`, `children?` | Sweep-select superset of `Selector`: drag sweeps marks, release reports `(hits, modifiers, spans)` — plural, one per swept layer (RFC A5.2) | `packages/charts/src/selectors.tsx` |
263
263
 
264
264
  ### Components — draw layers
265
265
 
@@ -280,7 +280,7 @@ schema columns). Because the union splits per series _kind_, a value typed as
280
280
  | Component | Data props | Purpose | Source |
281
281
  | -------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -------------------------------------- |
282
282
  | `LineChart` | `column`, `gaps?`, `sessionBreaks?` | Gap-aware line | `packages/charts/src/LineChart.tsx` |
283
- | `AreaChart` | `column`, `baseline?`, `gaps?` | Filled area | `packages/charts/src/AreaChart.tsx` |
283
+ | `AreaChart` | `column`, `baseline?`, `gaps?`, `thresholds?`/`bandColors?` | Filled area | `packages/charts/src/AreaChart.tsx` |
284
284
  | `BandChart` | `lower`, `upper` | Variance-band envelope | `packages/charts/src/BandChart.tsx` |
285
285
  | `ScatterChart` | `column`, `id?` (selection), radius/color encodings | Points; data-driven size/colour | `packages/charts/src/ScatterChart.tsx` |
286
286
  | `BarChart` | `column` \| `columns` \| `bins` \| `categories`, `orientation?`, `thresholds?`/`bandColors?` | Bars, stacked bars, histograms, categorical | `packages/charts/src/BarChart.tsx` |
@@ -328,10 +328,10 @@ label + data cells, `sortBy`/`sort`, optional per-row expander. The in-plot
328
328
  histogram stays `<BarChart orientation="horizontal">` — these are the table
329
329
  shape (react-timeseries-charts' `HorizontalBarChart`).
330
330
 
331
- | Component | Data props | Purpose | Source |
332
- | --------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | --------------------------------- |
333
- | `BarList` | `rows`, `columns` (`values` names), `sortBy?`, `before?`/`after?`, `renderExpanded?` | Ranked bar list — one proportional bar line per column per row | `packages/charts/src/BarList.tsx` |
334
- | `BoxList` | `rows`, `columns` (five-number names + `value?` tick), same table props | Distribution list — range band / q1→q3 body / median / current tick | `packages/charts/src/BoxList.tsx` |
331
+ | Component | Data props | Purpose | Source |
332
+ | --------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------- |
333
+ | `BarList` | `rows`, `columns` (`values` names), `barColors?`, `sortBy?`, `before?`/`after?`, `renderExpanded?` | Ranked bar list — one proportional bar line per column per row | `packages/charts/src/BarList.tsx` |
334
+ | `BoxList` | `rows`, `columns` (five-number names + `value?` tick), same table props | Distribution list — range band / q1→q3 body / median / current tick | `packages/charts/src/BoxList.tsx` |
335
335
 
336
336
  Row/option types + readers (`packages/charts/src/list.ts`): `ListRow`,
337
337
  `ListValue`, `ListCellSpec`, `ListMarker` (reference rule through every row,
@@ -411,12 +411,17 @@ Series shapes (same file): `ChartSeries`, `BandSeries`, `BoxSeries`,
411
411
  | `SwatchSpec` / `LegendItemInput` | Legend swatch vocabulary + explicit-rows input (`<Legend items>`) | `packages/charts/src/swatch.ts` |
412
412
  | `useChartLegend` | Headless legend hook: rows (items grouped by chart row) + `hover`/`select` verbs | `packages/charts/src/useChartLegend.ts` |
413
413
  | `ChartLegend` / `LegendRow` / `LegendItem` | The hook's return shape (`rows` group `items`; items carry `selected`/`hovered`) | `packages/charts/src/useChartLegend.ts` |
414
+ | `useChartFrame` | Resolved plot geometry: plot rect, gutters, x scale, a row's y scales, band slot edges | `packages/charts/src/useChartFrame.ts` |
415
+ | `ChartFrame` / `ChartFrameRow` | The hook's return shape — container x half, plus a row y half that is `null` outside a `<ChartRow>` | `packages/charts/src/useChartFrame.ts` |
416
+ | `ChartBands` / `ChartBand` | Ordinal slot geometry on a category axis (`count`/`pitch`/`labels`/`at(i)`); `null` on time/value | `packages/charts/src/useChartFrame.ts` |
417
+ | `ChartXScale` | The union the container's shared x scale resolves to (time / linear / trading / band / elapsed) | `packages/charts/src/context.ts` |
414
418
  | `LegendPlacement` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | `packages/charts/src/Legend.tsx` |
415
419
  | `Curve` | Path interpolation: `'linear' \| 'monotone' \| 'natural' \| 'basis' \| 'step'` | `packages/charts/src/curve.ts` |
416
420
  | `RadiusEncoding` / `ColorEncoding` | Data-driven scatter size/colour | `packages/charts/src/encoding.ts` |
417
421
  | `CandleVariant` / `ColorBy` | OHLC mark shape / colouring strategy | `packages/charts/src/ohlc.ts` |
418
422
  | `AxisFormat` / `CursorFormat` | Tick and cursor-readout formatting (d3 specifier or fn) | `packages/charts/src/format.ts` |
419
423
  | `AxisTransform` | Monotonic `to`/`from` pair for derived-unit x-axis relabeling | `packages/charts/src/derivedTicks.ts` |
424
+ | `AxisMouseEvent` / `AxisMouseHandler` | Axis `onMouseEvent` payload — the mouse event, the axis's `id`, and the value/label under the pointer | `packages/charts/src/axis-events.ts` |
420
425
  | `Orientation` | Bar growth direction | `packages/charts/src/bars.ts` |
421
426
 
422
427
  ---
package/CHANGELOG.md CHANGED
@@ -8,7 +8,9 @@ The `@pond-ts` packages — `pond-ts`, `@pond-ts/react`, `@pond-ts/charts`,
8
8
  under a single `v*` tag, so this file covers them all. Pre-1.0: minor bumps may
9
9
  include new features and type-level changes; patch bumps are strictly additive.
10
10
 
11
- [Unreleased]: https://github.com/pond-ts/pond/compare/v0.59.0...HEAD
11
+ [Unreleased]: https://github.com/pond-ts/pond/compare/v0.61.0...HEAD
12
+ [0.61.0]: https://github.com/pond-ts/pond/compare/v0.60.0...v0.61.0
13
+ [0.60.0]: https://github.com/pond-ts/pond/compare/v0.59.0...v0.60.0
12
14
  [0.59.0]: https://github.com/pond-ts/pond/compare/v0.58.0...v0.59.0
13
15
  [0.58.0]: https://github.com/pond-ts/pond/compare/v0.57.0...v0.58.0
14
16
  [0.57.0]: https://github.com/pond-ts/pond/compare/v0.56.2...v0.57.0
@@ -62,6 +64,241 @@ include new features and type-level changes; patch bumps are strictly additive.
62
64
 
63
65
  ## [Unreleased]
64
66
 
67
+ ## [0.61.0] — 2026-08-16
68
+
69
+ ### Added
70
+
71
+ - `@pond-ts/charts`: **`onMouseEvent` on `<XAxis>` and `<YAxis>`** — mouse
72
+ events on an axis strip, carrying the **axis value under the pointer** (the
73
+ part a consumer can't compute, since the scale lives inside the container).
74
+ One handler takes every mouse event on the strip (`click`, `dblclick`,
75
+ `contextmenu`, `mousedown`/`mouseup`, `mousemove`, `mouseenter`/`mouseleave`)
76
+ — switch on `event.type`. The payload (`AxisMouseEvent`, exported) carries
77
+ the raw React event, `axis: 'x' | 'y'`, the axis's `id` (a `<YAxis>` has one;
78
+ an `<XAxis>` does not), the inverted `value`, and the `label` that axis would
79
+ print there — the **category name** on a category axis, whose scale inverts
80
+ to the nearest band centre. Nothing is attached when the prop is omitted, so
81
+ an axis that doesn't opt in pays nothing for the move events. Axis strips now
82
+ also carry `data-axis="x"` / `data-axis="y"` + `data-axis-id` hooks, so a
83
+ consumer can style one (`[data-axis='x'] { cursor: pointer }`) despite the
84
+ axes taking no `className`.
85
+
86
+ ### Fixed
87
+
88
+ - `@pond-ts/charts`: the category x-axis label fit now **measures** rendered
89
+ label widths (offscreen-canvas `measureText` in the axis font, with a
90
+ per-glyph estimate fallback for SSR/test DOMs) instead of estimating by
91
+ character count — labels wider than their band (e.g. `SYMBOL-VENUE-TYPE`
92
+ keys) no longer overprint into a smear. The fit measures against the scale's
93
+ real band pitch (so a `maxBandWidth`-packed axis thins correctly), keeps a
94
+ minimum clear gap between drawn labels, truncates from the **middle**
95
+ (`EDGE01…EQT`) so a shared prefix or a shared tail stays distinguishable,
96
+ and draws **no** labels at a degenerate (collapsed / pre-layout) width
97
+ rather than overprinting every label at x ≈ 0. Filed from the SPARC
98
+ migration ([PND-CATFIT]).
99
+
100
+ **Heads-up on a deliberate visual change:** an axis whose labels previously
101
+ packed edge-to-edge at a borderline pitch (label within a couple of px of
102
+ its band) now **thins to every 2nd label** instead — clear separation
103
+ outranks per-band labelling at the margin. On a dense dashboard axis this
104
+ can read as "half the labels disappeared"; it is the fit working as
105
+ intended, not data loss — every band still draws its mark, and the cursor
106
+ readout still names every category.
107
+
108
+ ## [0.60.0] — 2026-08-13
109
+
110
+ ### Added
111
+
112
+ - `@pond-ts/charts`: `<AreaChart thresholds>` + `bandColors` — threshold
113
+ banding along the area's height ([PND-BANDAREA]), the area counterpart of
114
+ `<BarChart thresholds>`. `n` breakpoints (absolute data values, magnitude-
115
+ mirrored below zero) make `n + 1` bands; fills resolve `bandColors` → the
116
+ new `AreaStyle.bands` theme token (the default theme ships the same
117
+ teal/amber/red ladder as the bar role). One hard-stop pixel-space gradient
118
+ carries the ladder for the fill **and** the outline, so the value line
119
+ switches hue exactly at each crossing and the banded area keeps one hit
120
+ region, one legend row and one readout identity. Composes with `curve`,
121
+ `gaps` and M4 decimation unchanged; a swept window keeps the band colours
122
+ (no `spanColor` swap). Ladder resolution + dev warnings shared with
123
+ `<BarChart>` via one internal hook.
124
+ - **charts: `<ChartContainer xScale="log" | "symlog">` — a logarithmic value x
125
+ axis** ([#649]). The x counterpart of `<YAxis scale>`, for a quantity spanning
126
+ orders of magnitude — a power–duration curve is watts against 1s · 5s · 1m ·
127
+ 20m · 3h, which is unreadable on a linear x.
128
+
129
+ ```tsx
130
+ <ChartContainer range={[1, 10800]} xScale="log">
131
+ ```
132
+
133
+ **Why the container and not `<XAxis scale>`**, where the `<YAxis>` mirror would
134
+ put it: there is one x scale shared by every row and the container builds it,
135
+ while every `<XAxis>` prop is presentational (`format`, `label`, `side`,
136
+ `ticks`, `align`, …). `<YAxis>` is the opposite — one scale per axis per row,
137
+ declared by the axis, which is why `min`/`max`/`pad`/`scale` live there. It
138
+ sits beside `origin`, `spacing` and `calendar`, which shape the same scale.
139
+
140
+ Ignored on a time or category axis. A `'log'` domain reaching zero **falls back
141
+ to linear and warns** rather than silently clamping — `log(0)` is undefined;
142
+ use `'symlog'` for data that crosses zero.
143
+
144
+ Nothing downstream branches: d3's log scales share the continuous-scale
145
+ surface, so draw layers are untouched. The tick ladder is the one the y axis
146
+ already built (`tickValues`, renamed from `yTickValues` now that both axes use
147
+ it) — d3's raw `scaleLog.ticks()` is nearly a step function.
148
+
149
+ [#649]: https://github.com/pond-ts/pond/issues/649
150
+
151
+ - **charts: `<BarList barColors>` — per-row bar colour** ([#650]). The list's
152
+ counterpart to `<BarChart binColors>`: `barColors[i]` aligned to the rows you
153
+ passed, an `undefined` or short entry falling back to the column's `as` /
154
+ theme fill.
155
+
156
+ ```tsx
157
+ <BarList rows={zones} columns={[{ column: 'frac' }]} barColors={ZONE_RAMP} />
158
+ ```
159
+
160
+ For the case `binColors` was built for and a list could not do — a zone table
161
+ where each row's bar carries its own step of a ramp. A `BarListColumn`'s
162
+ single `as` paints every row the same colour, so the ramp had to move onto the
163
+ row label, putting it on the wrong element: the bar is the natural carrier of
164
+ a magnitude.
165
+
166
+ **Colours key on the row, not its render position**, so they follow the data
167
+ through a `sortBy` rather than repainting the ramp onto whichever rows now sit
168
+ in those slots.
169
+
170
+ **A per-row colour makes the fill load-bearing, so its state treatment stands
171
+ down** — a coloured bar keeps its own colour while selected, and shows the
172
+ state in opacity instead. That is the rule a multi-metric row already
173
+ followed, and the one `binColors` follows on the canvas: recolouring a bar
174
+ that means something trades a distinction the reader needs for one the band
175
+ and rail already give them.
176
+
177
+ [#650]: https://github.com/pond-ts/pond/issues/650
178
+
179
+ - **charts: `<ChartContainer categories>` — the ordinal axis as a
180
+ container-level choice** ([PND-IGNITECAT]). Declare the slot names on the
181
+ container and **any value-keyed layer can live on them** — a target line, a
182
+ point mark or a filled envelope over categorical bars, which was previously
183
+ not expressible at all.
184
+
185
+ The band scale used to be reachable only _through a layer_: `<BarChart
186
+ categories>` and a **horizontal** heat map reported `xKind: 'category'`, every other layer
187
+ reported `'time'` or `'value'`, and a container throws on a mixed kind. The
188
+ workaround — key every layer to a synthetic integer index and hand-supply the
189
+ tick labels — forfeits two features the ordinal axis already implements, and
190
+ both came back as their own friction entries: `<XAxis>` label thinning (gated
191
+ on a category axis with no custom ticks) and the `maxBandWidth` / `bandAlign`
192
+ slot packing. Declaring the categories on the container keeps both.
193
+
194
+ The change is small because the scale was already built for it: `scaleBand`'s
195
+ domain is **numeric** (`[0, n]`, slot `i` at `[i, i+1]`) with a linear pixel
196
+ mapping, so a `ValueSeries` keyed on slot coordinates already lands where the
197
+ bars do. **Slot `i`'s centre is `i + 0.5`** — the same number
198
+ `ScaleBand.ticks()` returns and where `<XAxis>` puts the tick.
199
+
200
+ Two things error, deliberately: a **time-keyed layer** (a timestamp has no
201
+ slot), and a **category layer that disagrees** with the prop in content or
202
+ order — the prop is authoritative, and a silent mismatch would draw bars
203
+ under the wrong labels. The pre-existing mixed-kind error now names the prop
204
+ as the fix.
205
+
206
+ Declaring it has two costs, both already true of an inferred category axis
207
+ and now reachable from a previously-continuous container: **x pan and zoom
208
+ stop** (`panZoom` keeps working on y), and **`range` stops applying to x**
209
+ (the domain is `[0, n]` from the slot count, so an x range is a no-op).
210
+
211
+ One hazard is documented rather than enforced: **a value-keyed layer is taken
212
+ at its word**, so a layer whose x means something other than a slot
213
+ coordinate — a horizontal categorical `<BarChart>`, whose x is bar _length_ —
214
+ will draw in the wrong place. A guard for that case was written and removed
215
+ after review: it tested `binCategories`, which is the generic "my _y_ is
216
+ ordinal" channel that a **vertical heat map** sets for its rows, so it
217
+ rejected a slot-keyed grid with named columns on x — a wanted layout, since
218
+ ordinal rows plus ordinal columns is just a 2-D grid. Nothing distinguishes
219
+ "my x is a coordinate" from "my x is a magnitude", so there is no
220
+ contradiction to detect.
221
+
222
+ `categories={[]}` is an ordinal axis with **no slots yet**, not a fallback to
223
+ time — so the kind doesn't flip and rebuild every scale when data arrives.
224
+
225
+ Omitting `categories` leaves the inferred behaviour exactly as it was.
226
+
227
+ - **charts: `useChartFrame()` — the resolved plot geometry, published**
228
+ ([PND-IGNITEFRAME]). A hook returning what the container already worked out:
229
+ the plot rect (`plot.x` / `plot.width`), the reserved axis `gutters`, the
230
+ shared `xScale` and its `xKind`, a row's `yScales` and top inset, and — on a
231
+ category axis — the ordinal slot edges (`bands.at(i)`, `pitch`, `labels`).
232
+
233
+ Consumers aligning DOM chrome to the plot (per-slot header tables, column
234
+ summary strips, cards pinned over a band, a colour ramp keyed to the plot's
235
+ own scale) previously had to re-derive all of it: pin every axis gutter to a
236
+ fixed width so it stops depending on label content, measure the outer box,
237
+ subtract, and re-implement the band packing. **That duplicate is not merely
238
+ verbose — it is wrong over time.** It holds only until the library changes
239
+ how a gutter is sized or how bands are packed, at which point the chrome
240
+ slides out of alignment with the plot it labels, with no type error and no
241
+ failing test.
242
+
243
+ Two shape notes. **The x/y split is the library's own** — the container owns
244
+ one shared x scale, rows own their y scales — so `plot` carries x and `row`
245
+ carries y, and `row` is `null` when the hook is called outside a
246
+ `<ChartRow>`. That `null` is deliberate: the common case (a header strip
247
+ beside the rows) genuinely has no y geometry, and reporting `height: 0`
248
+ instead would be the same silent misalignment the hook exists to remove.
249
+ **Scope follows placement**, exactly as `useChartLegend` already does.
250
+
251
+ `useChartLegend`'s `gutters` is unchanged and still the right call for a
252
+ legend; it now documents `useChartFrame()` as the fuller surface. It was the
253
+ only geometry the library published, for one consumer, on a hook named for
254
+ something else — which is why this exists.
255
+
256
+ - **charts: `<ChartContainer width="auto">` — fill the available width**
257
+ ([PND-WIDTH]). `width` now accepts `'auto'`, and an omitted `width` means the
258
+ same; a number still skips the measure pass and paints on the first render.
259
+ The container renders a plain full-width box, measures it with a
260
+ `ResizeObserver`, and mounts the chart at that pixel width — the canvas
261
+ renderer needs real pixels to lay out ticks and slots, so this is measurement
262
+ moved inside the library rather than a percentage handed to a canvas. Nothing
263
+ paints until a real width exists.
264
+
265
+ This is the shipped
266
+ [responsive-width recipe](https://pond-ts.github.io/pond/docs/recipes/responsive-width)
267
+ become the implementation, and it closes that recipe's sharpest edge by
268
+ construction: the measured box is one the library owns, so it can never be
269
+ the caller's padded or bordered box (whose border-box width overflowed the
270
+ chart by exactly the padding, silently clipped when the box also hid
271
+ overflow). Style your own wrapper freely.
272
+
273
+ Two behaviours worth knowing. A container **hidden** by an ancestor's
274
+ `display: none` keeps the last width it measured and stays mounted, so a tab
275
+ switch does not discard pan/zoom position, selection or hover — writing the
276
+ zero measurement through would unmount and rebuild all of it. And `'auto'`
277
+ needs a parent with a **definite** width: a parent sized by its own content
278
+ (a float, an `inline-block`, a grid `auto` track, a flex child without
279
+ `min-width: 0`) measures 0, and the chart is the content that would have
280
+ given it a width, so the chart stays blank with no error.
281
+
282
+ Three independent consumers reported the explicit-pixel requirement; the
283
+ third was multiplying the same ~25-line measure-and-gate hook across seven
284
+ panes.
285
+
286
+ ### Changed
287
+
288
+ - **charts: `ChartContainerProps.width` is now `number | 'auto'` and optional**
289
+ (was a required `number`). Strictly additive for callers passing a number.
290
+
291
+ ### Fixed
292
+
293
+ - **charts: value-axis pan and zoom no longer snap to whole integers**
294
+ (shipped inside [#653]). `panRange`/`zoomRange` were written for a
295
+ millisecond axis and silently assumed every axis was one, so a value domain
296
+ of `[0.5, 10800]` snapped its floor to `0` and `[0.001, 1]` collapsed to
297
+ `[0, 1]` — fatal under a log axis, where `log(0)` is undefined. Gestures now
298
+ snap only on a **time** axis (`ViewportOptions.snap`), and a log x axis pans
299
+ by ratio and zooms in log space, keeping the value under the cursor fixed.
300
+ Time-axis behaviour is unchanged.
301
+
65
302
  ## [0.59.0] — 2026-08-11
66
303
 
67
304
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/process",
3
- "version": "0.59.0",
3
+ "version": "0.61.0",
4
4
  "description": "Computations as data over pond-ts: processing graphs authored fluently or composed as JSON, resolved against a declared op vocabulary with content-addressed caching, provenance, and per-node timings. Experimental, pre-1.0.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "verify": "npm run format:check && npm run build && npm test"
43
43
  },
44
44
  "peerDependencies": {
45
- "pond-ts": "^0.59.0"
45
+ "pond-ts": "^0.61.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "typescript": "^5.6.3",