@pond-ts/charts 0.59.0 → 0.60.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.
- package/API.md +20 -16
- package/CHANGELOG.md +196 -1
- package/dist/AreaChart.d.ts +53 -1
- package/dist/AreaChart.js +16 -3
- package/dist/BarChart.js +6 -61
- package/dist/BarList.d.ts +22 -0
- package/dist/BarList.js +42 -9
- package/dist/ChartContainer.d.ts +175 -3
- package/dist/ChartContainer.js +190 -11
- package/dist/ChartRow.js +2 -0
- package/dist/Layers.js +14 -4
- package/dist/XAxis.js +19 -14
- package/dist/YAxis.js +2 -2
- package/dist/area.d.ts +43 -1
- package/dist/area.js +122 -5
- package/dist/context.d.ts +35 -2
- package/dist/format.d.ts +1 -1
- package/dist/format.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/theme.d.ts +22 -0
- package/dist/theme.js +3 -0
- package/dist/use-band-ladder.d.ts +30 -0
- package/dist/use-band-ladder.js +81 -0
- package/dist/useChartFrame.d.ts +122 -0
- package/dist/useChartFrame.js +155 -0
- package/dist/useChartLegend.d.ts +8 -0
- package/dist/viewport.d.ts +35 -2
- package/dist/viewport.js +53 -6
- package/dist/yticks.d.ts +5 -1
- package/dist/yticks.js +5 -1
- package/package.json +3 -3
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useChartFrame()` — **the resolved plot geometry, published**.
|
|
3
|
+
*
|
|
4
|
+
* A consumer whose chrome has to line up with the plot (a per-slot header
|
|
5
|
+
* table above it, a column summary strip below it, a card pinned over one
|
|
6
|
+
* band, a colour ramp keyed to the plot's own scale) needs the numbers the
|
|
7
|
+
* container already resolved: where the plot starts, how wide it is, and the
|
|
8
|
+
* scales that map data to pixels inside it. Before this hook none of that was
|
|
9
|
+
* reachable, so consumers re-derived it — pin every axis gutter to a fixed
|
|
10
|
+
* width so it stops depending on label content, measure the outer box,
|
|
11
|
+
* subtract, and re-implement the band packing.
|
|
12
|
+
*
|
|
13
|
+
* **That duplicate is not merely verbose, it is wrong over time.** It holds
|
|
14
|
+
* only until the library changes how a gutter is sized or how bands are
|
|
15
|
+
* packed, at which point the consumer's chrome slides out of alignment with
|
|
16
|
+
* the plot it labels — with no type error and no failing test. Reading the
|
|
17
|
+
* frame converts a silent drift hazard into a version-checked API.
|
|
18
|
+
*
|
|
19
|
+
* ## The x / y split is the library's own
|
|
20
|
+
*
|
|
21
|
+
* The shape mirrors the architecture rather than flattening it: **the
|
|
22
|
+
* container owns x** (one shared scale, so every row's plot left-aligns under
|
|
23
|
+
* one time axis) and **rows own y** (row-local data, one scale per axis id).
|
|
24
|
+
* So {@link ChartFrame.plot} carries x only, and y lives on
|
|
25
|
+
* {@link ChartFrame.row} — which is `null` when the hook is called outside a
|
|
26
|
+
* `<ChartRow>`.
|
|
27
|
+
*
|
|
28
|
+
* That `null` is the point. The common case (a header strip above the plot,
|
|
29
|
+
* a sibling of the rows) genuinely has no y geometry, and the alternative —
|
|
30
|
+
* reporting `height: 0` — is the same silent-misalignment failure this hook
|
|
31
|
+
* exists to remove. A consumer that needs y must be inside a row, and the
|
|
32
|
+
* type says so.
|
|
33
|
+
*
|
|
34
|
+
* ## Scope follows placement
|
|
35
|
+
*
|
|
36
|
+
* Exactly as {@link useChartLegend} does: at the container level you get the
|
|
37
|
+
* shared x frame and `row: null`; inside a `<ChartRow>` you additionally get
|
|
38
|
+
* that row's y scales. No prop selects the scope — placement does.
|
|
39
|
+
*
|
|
40
|
+
* ## Pixel origins
|
|
41
|
+
*
|
|
42
|
+
* Two different boxes, because the DOM has two:
|
|
43
|
+
*
|
|
44
|
+
* - `plot.x` / `plot.width` are relative to the **container's** box, so a
|
|
45
|
+
* `<div>` sibling of the rows pads by `plot.x` to align.
|
|
46
|
+
* - `xScale(v)` and `bands.at(i)` are relative to the **plot**, i.e. `0 …
|
|
47
|
+
* plot.width` — the coordinate system the canvas draws in. Add `plot.x` to
|
|
48
|
+
* place DOM chrome in container space.
|
|
49
|
+
* - `row.topInset` / `row.height` are relative to the **row's** box.
|
|
50
|
+
*
|
|
51
|
+
* @example Align a per-slot header strip above a categorical plot
|
|
52
|
+
* ```tsx
|
|
53
|
+
* function SlotHeader() {
|
|
54
|
+
* const { plot, bands } = useChartFrame();
|
|
55
|
+
* if (bands === null) return null;
|
|
56
|
+
* return (
|
|
57
|
+
* <div style={{ position: 'relative', height: 22, marginLeft: plot.x, width: plot.width }}>
|
|
58
|
+
* {bands.labels.map((label, i) => {
|
|
59
|
+
* const b = bands.at(i)!;
|
|
60
|
+
* return (
|
|
61
|
+
* <div key={label} style={{ position: 'absolute', left: b.x0, width: b.x1 - b.x0 }}>
|
|
62
|
+
* {label}
|
|
63
|
+
* </div>
|
|
64
|
+
* );
|
|
65
|
+
* })}
|
|
66
|
+
* </div>
|
|
67
|
+
* );
|
|
68
|
+
* }
|
|
69
|
+
*
|
|
70
|
+
* <ChartContainer categories={tickers} width="auto">
|
|
71
|
+
* <SlotHeader />
|
|
72
|
+
* <ChartRow height={200}>…</ChartRow>
|
|
73
|
+
* </ChartContainer>
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @packageDocumentation
|
|
77
|
+
*/
|
|
78
|
+
import { useContext, useMemo } from 'react';
|
|
79
|
+
import { ContainerContext, RowContext, } from './context.js';
|
|
80
|
+
/** Whether the container resolved an ordinal scale (which alone carries `label`). */
|
|
81
|
+
function asBandScale(scale, kind) {
|
|
82
|
+
return kind === 'category' ? scale : null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Read the container's resolved plot geometry — the plot rect, the axis
|
|
86
|
+
* gutters, the shared x scale, the row's y scales, and (on a category axis)
|
|
87
|
+
* the ordinal slot edges. See the module docblock for the x/y split, the
|
|
88
|
+
* placement-scoped `row` half, and which box each pixel value is relative to.
|
|
89
|
+
*
|
|
90
|
+
* Must be called under a `<ChartContainer>`; throws otherwise. To render the
|
|
91
|
+
* markup *outside* the chart's box, portal it out (`createPortal`) — context
|
|
92
|
+
* flows through portals.
|
|
93
|
+
*/
|
|
94
|
+
export function useChartFrame() {
|
|
95
|
+
const container = useContext(ContainerContext);
|
|
96
|
+
if (container === null) {
|
|
97
|
+
throw new Error('useChartFrame() must be used inside a <ChartContainer>');
|
|
98
|
+
}
|
|
99
|
+
const row = useContext(RowContext);
|
|
100
|
+
const { leftGutter, rightGutter, plotWidth, xScale, xKind } = container;
|
|
101
|
+
const bands = useMemo(() => {
|
|
102
|
+
const band = asBandScale(xScale, xKind);
|
|
103
|
+
if (band === null)
|
|
104
|
+
return null;
|
|
105
|
+
// The slot count is the scale's own domain width, not the label count:
|
|
106
|
+
// `scaleBand` is built with `domain([0, n])` from the container's resolved
|
|
107
|
+
// category list, so the domain is the authority on geometry (the labels
|
|
108
|
+
// ride alongside for naming). They agree today; reading the domain means
|
|
109
|
+
// they cannot disagree here if that ever stops being true.
|
|
110
|
+
const [d0, d1] = band.domain();
|
|
111
|
+
const count = Math.max(0, Math.round(d1 - d0));
|
|
112
|
+
const pitch = band.step();
|
|
113
|
+
// Slot `i` is the domain value `d0 + i`; the container always sets
|
|
114
|
+
// `domain([0, n])` so `d0` is 0 today, but every read goes through it so
|
|
115
|
+
// an offset domain could never silently shift the labels off the slots.
|
|
116
|
+
const labels = [];
|
|
117
|
+
for (let i = 0; i < count; i++)
|
|
118
|
+
labels.push(band.label(d0 + i + 0.5));
|
|
119
|
+
return {
|
|
120
|
+
count,
|
|
121
|
+
pitch,
|
|
122
|
+
labels,
|
|
123
|
+
at(index) {
|
|
124
|
+
if (!Number.isInteger(index) || index < 0 || index >= count) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
x0: band(d0 + index),
|
|
129
|
+
x1: band(d0 + index + 1),
|
|
130
|
+
center: band(d0 + index + 0.5),
|
|
131
|
+
label: labels[index] ?? '',
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}, [xScale, xKind]);
|
|
136
|
+
const rowHalf = useMemo(() => {
|
|
137
|
+
if (row === null)
|
|
138
|
+
return null;
|
|
139
|
+
return {
|
|
140
|
+
topInset: row.topInset,
|
|
141
|
+
height: Math.max(0, row.height - row.topInset),
|
|
142
|
+
yScales: row.yScales,
|
|
143
|
+
axisSides: row.axisSides,
|
|
144
|
+
};
|
|
145
|
+
}, [row]);
|
|
146
|
+
return useMemo(() => ({
|
|
147
|
+
plot: { x: leftGutter, width: plotWidth },
|
|
148
|
+
gutters: { left: leftGutter, right: rightGutter },
|
|
149
|
+
xScale,
|
|
150
|
+
xKind,
|
|
151
|
+
bands,
|
|
152
|
+
row: rowHalf,
|
|
153
|
+
}), [leftGutter, rightGutter, plotWidth, xScale, xKind, bands, rowHalf]);
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=useChartFrame.js.map
|
package/dist/useChartLegend.d.ts
CHANGED
|
@@ -32,6 +32,14 @@ export interface ChartLegend {
|
|
|
32
32
|
* inset from the chart box on each side. A custom legend laid out above /
|
|
33
33
|
* below the chart pads by `gutters.left` (and `gutters.right`) to align
|
|
34
34
|
* with the plot instead of the y-axis column.
|
|
35
|
+
*
|
|
36
|
+
* The same two numbers {@link useChartFrame} publishes as
|
|
37
|
+
* `ChartFrame.gutters`, kept here so a legend needs one hook rather than
|
|
38
|
+
* two. **Reach for `useChartFrame()` instead** when the chrome is not a
|
|
39
|
+
* legend, or when aligning needs more than the gutters — the plot width,
|
|
40
|
+
* the x scale, the per-slot band edges, or a row's y scales. This field
|
|
41
|
+
* predates that hook and is the reason it exists: the geometry was
|
|
42
|
+
* published for exactly one consumer, on a hook named for something else.
|
|
35
43
|
*/
|
|
36
44
|
readonly gutters: {
|
|
37
45
|
readonly left: number;
|
package/dist/viewport.d.ts
CHANGED
|
@@ -24,13 +24,46 @@ export type TimeRange = readonly [number, number];
|
|
|
24
24
|
* range untouched) so a mis-specified extent can't collapse the view.
|
|
25
25
|
*/
|
|
26
26
|
export declare function clampToBounds(range: TimeRange, bounds: TimeRange): [number, number];
|
|
27
|
+
/**
|
|
28
|
+
* How a viewport gesture should treat the domain it is moving.
|
|
29
|
+
*
|
|
30
|
+
* Both flags exist because {@link roundRange} was written for a **millisecond**
|
|
31
|
+
* axis and silently assumed every axis was one.
|
|
32
|
+
*/
|
|
33
|
+
export interface ViewportOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Snap the result to whole integers. **Default `true`** — right for a time
|
|
36
|
+
* axis, where a fractional domain is meaningless and the 1 ms floor is the
|
|
37
|
+
* finest real view.
|
|
38
|
+
*
|
|
39
|
+
* Pass `false` on a **value** axis, where the units are not milliseconds and
|
|
40
|
+
* the fractions are the data: a power–duration curve over `[0.5, 10800]`
|
|
41
|
+
* seconds would otherwise snap its floor to `0`, and a `[0.001, 1]` domain
|
|
42
|
+
* would collapse to `[0, 1]`.
|
|
43
|
+
*
|
|
44
|
+
* The axis kind is a caller's fact, not something to infer from magnitude — a
|
|
45
|
+
* 0.2 ms span and a 0.2-unit value span are indistinguishable by size, and
|
|
46
|
+
* guessing breaks whichever one you guessed against.
|
|
47
|
+
*/
|
|
48
|
+
readonly snap?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Do the arithmetic in **log space**, where a log axis is linear. Implies
|
|
51
|
+
* `snap: false`.
|
|
52
|
+
*
|
|
53
|
+
* Without it a log axis zooms additively, which drags the value under the
|
|
54
|
+
* cursor sideways — the one thing zoom must never do — and pans by an offset,
|
|
55
|
+
* so a drag near the low end walks off the plot while the same drag near the
|
|
56
|
+
* high end barely moves.
|
|
57
|
+
*/
|
|
58
|
+
readonly log?: boolean;
|
|
59
|
+
}
|
|
27
60
|
/**
|
|
28
61
|
* Shift a range by `dt` ms (drag-pan). The caller signs `dt` from the gesture —
|
|
29
62
|
* dragging the plot right reveals earlier data, i.e. a negative `dt`. The result
|
|
30
63
|
* is snapped to whole milliseconds ({@link roundRange}) — `dt` comes from a pixel
|
|
31
64
|
* delta through `xScale.invert()`, so it is fractional by construction.
|
|
32
65
|
*/
|
|
33
|
-
export declare function panRange(range: TimeRange, dt: number): [number, number];
|
|
66
|
+
export declare function panRange(range: TimeRange, dt: number, options?: ViewportOptions): [number, number];
|
|
34
67
|
/**
|
|
35
68
|
* Zoom `range` around `pivot` (ms) by `factor` — `< 1` zooms in, `> 1` out, with
|
|
36
69
|
* the pivot held fixed (the time under the cursor stays put). Clamped so the
|
|
@@ -43,7 +76,7 @@ export declare function panRange(range: TimeRange, dt: number): [number, number]
|
|
|
43
76
|
* lands on the 1 ms floor the snap guarantees, which is the finest view this
|
|
44
77
|
* model has.
|
|
45
78
|
*/
|
|
46
|
-
export declare function zoomRange(range: TimeRange, pivot: number, factor: number, minDuration?: number): [number, number];
|
|
79
|
+
export declare function zoomRange(range: TimeRange, pivot: number, factor: number, minDuration?: number, options?: ViewportOptions): [number, number];
|
|
47
80
|
/**
|
|
48
81
|
* The slice of a discontinuity provider the trading-time viewport math needs —
|
|
49
82
|
* a structural subset of the charts `DiscontinuityProvider` (so `viewport.ts`
|
package/dist/viewport.js
CHANGED
|
@@ -70,8 +70,26 @@ function roundRange(lo, hi) {
|
|
|
70
70
|
* is snapped to whole milliseconds ({@link roundRange}) — `dt` comes from a pixel
|
|
71
71
|
* delta through `xScale.invert()`, so it is fractional by construction.
|
|
72
72
|
*/
|
|
73
|
-
export function panRange(range, dt) {
|
|
74
|
-
|
|
73
|
+
export function panRange(range, dt, options = {}) {
|
|
74
|
+
const { log = false, snap = !log } = options;
|
|
75
|
+
if (log) {
|
|
76
|
+
// On a log axis a pixel drag is a RATIO, not an offset. `dt` arrives as a
|
|
77
|
+
// domain delta from `xScale.invert`, which on a log scale is meaningless as
|
|
78
|
+
// an addend: adding 100s near the 1s end walks off the plot, and near the
|
|
79
|
+
// 3h end barely moves. Convert it to the fraction of the visible decades it
|
|
80
|
+
// represents and shift by that instead, so a drag of N pixels moves the
|
|
81
|
+
// same visual distance wherever it starts.
|
|
82
|
+
const [lo, hi] = range;
|
|
83
|
+
if (!(lo > 0) || !(hi > lo))
|
|
84
|
+
return [lo, hi];
|
|
85
|
+
const span = hi - lo;
|
|
86
|
+
const f = span > 0 ? dt / span : 0; // fraction of the window dragged
|
|
87
|
+
const k = Math.exp(Math.log(hi / lo) * f); // …as a ratio over the decades
|
|
88
|
+
return [lo * k, hi * k];
|
|
89
|
+
}
|
|
90
|
+
const lo = range[0] + dt;
|
|
91
|
+
const hi = range[1] + dt;
|
|
92
|
+
return snap ? roundRange(lo, hi) : [lo, hi];
|
|
75
93
|
}
|
|
76
94
|
/**
|
|
77
95
|
* Zoom `range` around `pivot` (ms) by `factor` — `< 1` zooms in, `> 1` out, with
|
|
@@ -85,15 +103,44 @@ export function panRange(range, dt) {
|
|
|
85
103
|
* lands on the 1 ms floor the snap guarantees, which is the finest view this
|
|
86
104
|
* model has.
|
|
87
105
|
*/
|
|
88
|
-
export function zoomRange(range, pivot, factor, minDuration = 1) {
|
|
106
|
+
export function zoomRange(range, pivot, factor, minDuration = 1, options = {}) {
|
|
107
|
+
const { log = false, snap = !log } = options;
|
|
108
|
+
if (log) {
|
|
109
|
+
// The same arithmetic, done in log space — which is where a log axis is
|
|
110
|
+
// linear. Zooming a log domain multiplicatively is what keeps the pivot
|
|
111
|
+
// under the cursor; doing it additively (as the linear branch does) drags
|
|
112
|
+
// the value under the pointer sideways, which is the one thing zoom must
|
|
113
|
+
// never do.
|
|
114
|
+
//
|
|
115
|
+
// `minDuration` is read as a minimum RATIO between the ends rather than a
|
|
116
|
+
// minimum difference, because a span on a log axis is a number of decades.
|
|
117
|
+
const [lo0, hi0] = range;
|
|
118
|
+
if (!(lo0 > 0) || !(hi0 > lo0) || !(pivot > 0))
|
|
119
|
+
return [lo0, hi0];
|
|
120
|
+
const L = Math.log(lo0);
|
|
121
|
+
const H = Math.log(hi0);
|
|
122
|
+
const P = Math.min(H, Math.max(L, Math.log(pivot)));
|
|
123
|
+
let l = P - (P - L) * factor;
|
|
124
|
+
let h = P + (H - P) * factor;
|
|
125
|
+
const floor = Math.log(Math.max(minDuration, 1 + 1e-9)); // ratio → decades
|
|
126
|
+
if (h - l < floor) {
|
|
127
|
+
const frac = H - L > 0 ? (P - L) / (H - L) : 0.5;
|
|
128
|
+
l = P - floor * frac;
|
|
129
|
+
h = P + floor * (1 - frac);
|
|
130
|
+
}
|
|
131
|
+
return [Math.exp(l), Math.exp(h)];
|
|
132
|
+
}
|
|
89
133
|
const lo = pivot - (pivot - range[0]) * factor;
|
|
90
134
|
const hi = pivot + (range[1] - pivot) * factor;
|
|
91
|
-
if (hi - lo >= minDuration)
|
|
92
|
-
return roundRange(lo, hi);
|
|
135
|
+
if (hi - lo >= minDuration) {
|
|
136
|
+
return snap ? roundRange(lo, hi) : [lo, hi];
|
|
137
|
+
}
|
|
93
138
|
// Floor reached: hold the pivot's fractional position, set span = minDuration.
|
|
94
139
|
const span = range[1] - range[0];
|
|
95
140
|
const frac = span > 0 ? (pivot - range[0]) / span : 0.5;
|
|
96
|
-
|
|
141
|
+
const flo = pivot - minDuration * frac;
|
|
142
|
+
const fhi = pivot + minDuration * (1 - frac);
|
|
143
|
+
return snap ? roundRange(flo, fhi) : [flo, fhi];
|
|
97
144
|
}
|
|
98
145
|
/**
|
|
99
146
|
* Pan a range on a **trading-time** axis: shift both endpoints by the same
|
package/dist/yticks.d.ts
CHANGED
|
@@ -57,11 +57,15 @@ interface TickableScale {
|
|
|
57
57
|
* d3's within-decade selection (2,3,…9 × 10ⁿ) is the right answer and is well
|
|
58
58
|
* behaved — so that case defers to the scale.
|
|
59
59
|
*
|
|
60
|
+
* Used by **both** axes. Nothing in here was ever y-specific — it detects log
|
|
61
|
+
* and symlog structurally and defers to the scale for everything else — and
|
|
62
|
+
* `<ChartContainer xScale="log">` made that concrete ([PND-XLOG]).
|
|
63
|
+
*
|
|
60
64
|
* Log detection is structural: `base()` exists on `scaleLog` and on no other
|
|
61
65
|
* continuous scale. The alternative is threading the axis kind down to the
|
|
62
66
|
* gridline site, which has no `AxisSpec` in scope — the same localized-shape
|
|
63
67
|
* approach `resolveBarBaseline` takes to read `.domain()`.
|
|
64
68
|
*/
|
|
65
|
-
export declare function
|
|
69
|
+
export declare function tickValues(scale: TickableScale, count: number): number[];
|
|
66
70
|
export {};
|
|
67
71
|
//# sourceMappingURL=yticks.d.ts.map
|
package/dist/yticks.js
CHANGED
|
@@ -55,12 +55,16 @@ export function resolveYTickCount(height, explicit) {
|
|
|
55
55
|
* d3's within-decade selection (2,3,…9 × 10ⁿ) is the right answer and is well
|
|
56
56
|
* behaved — so that case defers to the scale.
|
|
57
57
|
*
|
|
58
|
+
* Used by **both** axes. Nothing in here was ever y-specific — it detects log
|
|
59
|
+
* and symlog structurally and defers to the scale for everything else — and
|
|
60
|
+
* `<ChartContainer xScale="log">` made that concrete ([PND-XLOG]).
|
|
61
|
+
*
|
|
58
62
|
* Log detection is structural: `base()` exists on `scaleLog` and on no other
|
|
59
63
|
* continuous scale. The alternative is threading the axis kind down to the
|
|
60
64
|
* gridline site, which has no `AxisSpec` in scope — the same localized-shape
|
|
61
65
|
* approach `resolveBarBaseline` takes to read `.domain()`.
|
|
62
66
|
*/
|
|
63
|
-
export function
|
|
67
|
+
export function tickValues(scale, count) {
|
|
64
68
|
if (typeof scale.constant === 'function')
|
|
65
69
|
return symlogTickValues(scale, count);
|
|
66
70
|
if (typeof scale.base !== 'function')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pond-ts/charts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Canvas-rendered, streaming-first time-series charts for pond-ts",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"perf": "PERF_BENCH=1 playwright test perf.spec.ts --workers=1"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@pond-ts/react": "^0.
|
|
43
|
-
"pond-ts": "^0.
|
|
42
|
+
"@pond-ts/react": "^0.60.0",
|
|
43
|
+
"pond-ts": "^0.60.0",
|
|
44
44
|
"react": "^18.0.0 || ^19.0.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|