basalt-ui 1.30.1 → 1.30.2
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/CHANGELOG.md +7 -0
- package/MIGRATING.md +58 -14
- package/dist/charts/primitives/ChartFrame.d.ts +11 -3
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +18 -0
- package/dist/charts/primitives/ChartLegend.d.ts +5 -3
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/chart-frame-layout.d.ts +4 -3
- package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
- package/dist/charts/primitives/chart-frame-layout.js +7 -2
- package/dist/common/errors.d.ts +11 -0
- package/dist/common/errors.d.ts.map +1 -1
- package/dist/common/errors.js +4 -0
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/charts/primitives/ChartFrame.tsx +39 -3
- package/src/charts/primitives/ChartLegend.tsx +5 -3
- package/src/charts/primitives/chart-frame-layout.ts +39 -5
- package/src/common/errors.ts +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.30.2](https://github.com/jkrumm/basalt-ui/compare/v1.30.1...v1.30.2) (2026-09-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* stop legendWins collapsing a self-measured fill frame to no plot ([07f0fcd](https://github.com/jkrumm/basalt-ui/commit/07f0fcd65df5047caec1ce09aaaf7f05958e58ad))
|
|
7
|
+
|
|
1
8
|
## [1.30.1](https://github.com/jkrumm/basalt-ui/compare/v1.30.0...v1.30.1) (2026-09-09)
|
|
2
9
|
|
|
3
10
|
|
package/MIGRATING.md
CHANGED
|
@@ -66,18 +66,18 @@ There is one row per SURFACE, not per export, and a surface earns a row once any
|
|
|
66
66
|
shipped lefthook preset both changed in 1.30.0 with no row that owned them — which is how a
|
|
67
67
|
consumer read this index, found nothing, and diffed a KPI row that had silently gone 4-up → 2-up.
|
|
68
68
|
|
|
69
|
-
| Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | Unreleased |
|
|
70
|
-
| ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ---------- |
|
|
71
|
-
| **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — |
|
|
72
|
-
| **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — |
|
|
73
|
-
| **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — |
|
|
74
|
-
| **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — |
|
|
75
|
-
| **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — |
|
|
76
|
-
| **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — |
|
|
77
|
-
| **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — |
|
|
78
|
-
| **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — |
|
|
79
|
-
| **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — |
|
|
80
|
-
| **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — |
|
|
69
|
+
| Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | Unreleased |
|
|
70
|
+
| ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ | ---------- |
|
|
71
|
+
| **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | — |
|
|
72
|
+
| **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | — |
|
|
73
|
+
| **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | — |
|
|
74
|
+
| **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — |
|
|
75
|
+
| **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — |
|
|
76
|
+
| **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — |
|
|
77
|
+
| **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | — |
|
|
78
|
+
| **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | — |
|
|
79
|
+
| **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | — |
|
|
80
|
+
| **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | — |
|
|
81
81
|
|
|
82
82
|
---
|
|
83
83
|
|
|
@@ -86,6 +86,50 @@ consumer read this index, found nothing, and diffed a KPI row that had silently
|
|
|
86
86
|
_Nothing yet. Author the next section under this heading as `## Unreleased — <title>`; the
|
|
87
87
|
release renames it and opens a new one here._
|
|
88
88
|
|
|
89
|
+
## 1.30.2 — the `legendWins` collapse, and two things `ChartFrame` never said out loud
|
|
90
|
+
|
|
91
|
+
**A patch. Nothing is added, renamed or removed.** One rendering regression 1.30.1 shipped is
|
|
92
|
+
fixed; the other two entries correct documentation, not behaviour.
|
|
93
|
+
|
|
94
|
+
### Charts — a stated `legend.maxRows` no longer costs a `fill` chart its whole plot
|
|
95
|
+
|
|
96
|
+
1.30.1's `legendWins` drops `resolvePlotRect`'s `VX.minPlotHeight` floor so the caller's cap is
|
|
97
|
+
paid for out of the frame's own cell. `ChartFrame` measures its OWN node, and under `fill` its
|
|
98
|
+
`height: 100%` resolves to `auto` in a parent that states no height — so with the floor gone the
|
|
99
|
+
sequence has a fixpoint at zero: plot 0 → the legend is the only content → the box IS the legend
|
|
100
|
+
band → 0 again, forever. MEASURED in headless Chrome: a 7-entry `fill` chart at `maxRows: 3` in an
|
|
101
|
+
unsized parent rendered a 29.375px frame and **no plot svg at all**; with nothing stated the same
|
|
102
|
+
chart rendered a 120px plot in a 149.375px frame.
|
|
103
|
+
|
|
104
|
+
The floor now stands while the frame measures exactly its own legend band and nothing else, which
|
|
105
|
+
is the signature of a box nothing else sized. From there the frame is 120px + the band, the box has
|
|
106
|
+
room, and `legendWins` applies normally.
|
|
107
|
+
|
|
108
|
+
- **In the documented `fill` shape — a parent that states a height — nothing changes.** The plot
|
|
109
|
+
still yields the cell to a stated cap, below `VX.minPlotHeight` and down to 0 when the legend
|
|
110
|
+
band is taller than the cell. That is still the caller's arithmetic, made visible.
|
|
111
|
+
- **`fill` in a parent that states no height is not a supported shape** and never was: the frame
|
|
112
|
+
has no external height to fill, so it settles on the plot floor. State a height on the parent
|
|
113
|
+
cell, or use `height` / `aspectRatio` instead of `fill`.
|
|
114
|
+
- Pinned by `tests/layout/charts.layout.test.ts` INVARIANT 7 (both halves) — happy-dom cannot
|
|
115
|
+
observe a `ResizeObserver` fixpoint at all.
|
|
116
|
+
|
|
117
|
+
### `ChartFrame` warns in dev when `height` or `aspectRatio` is passed with `fill`
|
|
118
|
+
|
|
119
|
+
`fill` always won and the other two were never read; the JSDoc said so and nothing else did. Both
|
|
120
|
+
now `console.error` once in dev through `common/errors.ts`' new `ignoredProp` builder. **No
|
|
121
|
+
rendering moves** — the props were already ignored. A consumer carrying `height` alongside `fill`
|
|
122
|
+
will see one message per prop and can delete the dead constants behind it. A mutually-exclusive
|
|
123
|
+
union prop is the stronger fix and is deliberately not in a patch.
|
|
124
|
+
|
|
125
|
+
### `legend.maxRows` counts ENTRIES, not rows — a documentation correction
|
|
126
|
+
|
|
127
|
+
`ChartLegend` slices `entries.slice(0, cap)`. `maxRows: 3` on a 7-entry legend renders three
|
|
128
|
+
entries and a `+4 more` chip, on ONE row at any width they fit. This is unchanged behaviour and
|
|
129
|
+
matches `ChartLegend`'s own JSDoc; § 1.30.1's "takes the rows it asked for" and the name itself
|
|
130
|
+
(plus `ChartTierMetrics.legendMaxRows`) said otherwise. **Nothing is renamed** — a public prop
|
|
131
|
+
rename is not a patch, and the value you pass keeps meaning exactly what it always meant.
|
|
132
|
+
|
|
89
133
|
## 1.30.1 — finishing 1.30.0
|
|
90
134
|
|
|
91
135
|
**A patch. Nothing is added, renamed or removed — three things 1.30.0 shipped are made to work, and
|
|
@@ -118,8 +162,8 @@ exactly the case it was restored for. Measured: a 7-entry legend in a 92–150px
|
|
|
118
162
|
entries at `maxRows` 3, 6 **and** 99, with five series drawn in colours nothing named.
|
|
119
163
|
|
|
120
164
|
The fit now resolves an ABSENT cap only. When a caller states a number on a `fill` band, the legend
|
|
121
|
-
takes the
|
|
122
|
-
height instead. Both things that used to trim the number are framework DEFAULTS (the tier's cap,
|
|
165
|
+
takes the ENTRIES it asked for (`maxRows` counts entries, not rows — see § Unreleased) and
|
|
166
|
+
**`VX.minPlotHeight` stops being a floor** — the plot yields the height instead. Both things that used to trim the number are framework DEFAULTS (the tier's cap,
|
|
123
167
|
and a floor about the plot); the stated number is the only intent in the room.
|
|
124
168
|
|
|
125
169
|
- **Expect a taller legend and a shorter plot** anywhere you set `maxRows` on a docked/`fill` chart.
|
|
@@ -13,7 +13,13 @@ export type ChartFrameLegend = {
|
|
|
13
13
|
/** Default 'bottom'. */
|
|
14
14
|
placement?: LegendPlacement;
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Rollup cap at high cardinality: the legend renders this many ENTRIES and folds the remainder
|
|
17
|
+
* into a `+N more` disclosure chip.
|
|
18
|
+
*
|
|
19
|
+
* **It counts entries, not rows** — the name is inherited and now load-bearing across
|
|
20
|
+
* `ChartTierMetrics.legendMaxRows`, so it is not renamed in a patch. `maxRows: 3` on a 7-entry
|
|
21
|
+
* legend renders three entries, which is ONE row at any width they fit on. `ChartLegend` slices
|
|
22
|
+
* (`entries.slice(0, cap)`); nothing here measures rows.
|
|
17
23
|
*
|
|
18
24
|
* An explicit value WINS OUTRIGHT — over the phone tier's two-row default (`chartTierMetrics`)
|
|
19
25
|
* AND over a `fill` frame's measured fit (`legendEntryCap`). 1.30.0 fixed only the first half:
|
|
@@ -43,7 +49,8 @@ export type ChartFrameProps = BasaltProps & {
|
|
|
43
49
|
series: readonly SeriesStyle[];
|
|
44
50
|
/**
|
|
45
51
|
* Height in pixels, or one per size step. Used when neither `aspectRatio` nor `fill` is set.
|
|
46
|
-
* Default 240.
|
|
52
|
+
* Default 240. Passing it WITH `fill` is a wiring mistake, not a fallback — `fill` wins and
|
|
53
|
+
* this is never read, which now warns once in dev (`common/errors.ts`' `ignoredProp`).
|
|
47
54
|
*
|
|
48
55
|
* `{ base: 180, md: 260 }` is the declarative escape from a page of fixed-height charts: the
|
|
49
56
|
* steps are compared against the frame's own MEASURED width, not the viewport, so a chart squeezed
|
|
@@ -51,7 +58,8 @@ export type ChartFrameProps = BasaltProps & {
|
|
|
51
58
|
* supported ({@link resolveFrameHeight}).
|
|
52
59
|
*/
|
|
53
60
|
height?: number | ResponsiveChartHeight;
|
|
54
|
-
/** height = Math.round(containerWidth / aspectRatio). Ignored when `fill` is set
|
|
61
|
+
/** height = Math.round(containerWidth / aspectRatio). Ignored when `fill` is set — which warns
|
|
62
|
+
* once in dev, for the same reason `height` does. */
|
|
55
63
|
aspectRatio?: number;
|
|
56
64
|
/** Fill the parent flex/grid cell's measured height instead of a fixed/derived one. */
|
|
57
65
|
fill?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useState } from "react";
|
|
3
|
+
import { ignoredProp } from "../../common/errors.js";
|
|
4
|
+
import { useValidateProps } from "../../common/validate.js";
|
|
3
5
|
import { VX } from "../../tokens/index.js";
|
|
4
6
|
import { useChartSize } from "../hooks/useChartSize.js";
|
|
5
7
|
import { deriveLegend } from "../series.js";
|
|
@@ -67,6 +69,22 @@ function ChartFrame({
|
|
|
67
69
|
return next;
|
|
68
70
|
});
|
|
69
71
|
}, []);
|
|
72
|
+
useValidateProps(
|
|
73
|
+
"ChartFrame",
|
|
74
|
+
() => [
|
|
75
|
+
fill && height !== void 0 ? ignoredProp(
|
|
76
|
+
"ChartFrame",
|
|
77
|
+
"height",
|
|
78
|
+
"`fill` is set \u2014 a fill frame takes its parent cell's measured height. Pass one or the other."
|
|
79
|
+
) : null,
|
|
80
|
+
fill && aspectRatio !== void 0 ? ignoredProp(
|
|
81
|
+
"ChartFrame",
|
|
82
|
+
"aspectRatio",
|
|
83
|
+
"`fill` is set \u2014 a fill frame takes its parent cell's measured height. Pass one or the other."
|
|
84
|
+
) : null
|
|
85
|
+
],
|
|
86
|
+
[fill, height, aspectRatio]
|
|
87
|
+
);
|
|
70
88
|
const placement = legend === false ? "bottom" : legend.placement ?? "bottom";
|
|
71
89
|
const vertical = placement === "left" || placement === "right";
|
|
72
90
|
const resolvedState = resolveChartState({ ...state !== void 0 && { state }, isPending });
|
|
@@ -27,9 +27,11 @@ export type LegendEntry = {
|
|
|
27
27
|
* Shared legend for all non-sparkline charts. `highlighted`/`onHighlight` are optional — omit for
|
|
28
28
|
* static legends without hover interactivity. `chartId` namespaces `split` clipPath ids so two
|
|
29
29
|
* legends on the same page never collide. `groups` renders series → hairline divider → overlay →
|
|
30
|
-
* reference bands instead of one flat smear. `maxRows` caps the number of rendered
|
|
31
|
-
* rolls the remainder into a `+N more` chip
|
|
32
|
-
*
|
|
30
|
+
* reference bands instead of one flat smear. `maxRows` caps the number of rendered ENTRIES and
|
|
31
|
+
* rolls the remainder into a `+N more` chip — it is an entry count, not a row count, in spite of
|
|
32
|
+
* the name (`entries.slice(0, cap)` below is the whole of it, and `ChartFrame` measures rows for a
|
|
33
|
+
* different purpose in `legendEntryCap`). The name is inherited and reaches the public
|
|
34
|
+
* `ChartTierMetrics.legendMaxRows`, so it stays until a minor can rename both.
|
|
33
35
|
*
|
|
34
36
|
* `onToggle` makes an entry a real toggle: clicking it hides the series in the plot, the tooltip,
|
|
35
37
|
* and the auto y-domain together (`ChartFrame` owns the `hidden` set — see `docs/CHARTS-SPEC.md`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAG5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;0EACsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAgOD
|
|
1
|
+
{"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAG5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;0EACsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAgOD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAkB,EAClB,WAAW,EACX,MAAkB,EAClB,QAAQ,EACR,OAAY,EACZ,SAAoB,EACpB,MAAc,EACd,OAAO,EACP,SAAS,EACT,KAAK,GACN,EAAE,WAAW,GAAG;IACf,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C,kCAAkC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,+BAkFA"}
|
|
@@ -152,9 +152,10 @@ export declare function resolveLegendRollup(input: {
|
|
|
152
152
|
* column with `height: auto`, so that growth is automatic. Under `fill` the box CANNOT grow, so
|
|
153
153
|
* the legend is capped instead ({@link legendEntryCap}) — UNLESS the caller stated
|
|
154
154
|
* `legend.maxRows`, which is `legendWins`: there the floor itself yields, because a floor is a
|
|
155
|
-
* default and the caller's number is not
|
|
156
|
-
*
|
|
157
|
-
*
|
|
155
|
+
* default and the caller's number is not, but only as far as the box HAS room
|
|
156
|
+
* ({@link SELF_MEASURED_SLACK}). An unmeasured box (`resolvedHeight <= 0`, i.e. a `fill` frame
|
|
157
|
+
* before its first measurement) stays at 0 and renders nothing, exactly as before — the floor
|
|
158
|
+
* must never invent a height for a box nobody has measured.
|
|
158
159
|
*/
|
|
159
160
|
export declare function resolvePlotRect(input: {
|
|
160
161
|
containerW: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart-frame-layout.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/chart-frame-layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAUhD;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS,CAAA;AAE3C;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAE9D;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,uGAAuG;IACvG,IAAI,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,qBAAqB;IACrB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,sBAAsB;IACtB,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ,CAAA;AAiBD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,GAAG,qBAAqB,EACtC,UAAU,EAAE,MAAM,GACjB,MAAM,CAQR;AAqBD;6FAC6F;AAC7F,MAAM,MAAM,gBAAgB,GAAG;IAC7B,2FAA2F;IAC3F,IAAI,EAAE,SAAS,CAAA;IACf;uFACmF;IACnF,QAAQ,EAAE,MAAM,CAAA;IAChB,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAA;IACtB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,uCAAuC;IACvC,eAAe,EAAE,MAAM,CAAA;IACvB;+EAC2E;IAC3E,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,6EAA6E;IAC7E,MAAM,EAAE,WAAW,CAAA;CACpB,CAAA;AA6BD,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,gBAAgB,CAElE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,IAAI,EAAE,SAAS,CAAA;CAChB,GAAG,MAAM,GAAG,SAAS,CAErB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,yCAAyC;IACzC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,IAAI,EAAE,SAAS,CAAA;IACf,2FAA2F;IAC3F,QAAQ,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,SAAS,WAAW,EAAE,CAAA;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAA;CAClB,GAAG;IAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAcvD;AAED
|
|
1
|
+
{"version":3,"file":"chart-frame-layout.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/chart-frame-layout.ts"],"names":[],"mappings":"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"}
|
|
@@ -76,12 +76,17 @@ function resolvePlotRect(input) {
|
|
|
76
76
|
topBottomLegendHeight,
|
|
77
77
|
legendWins = false
|
|
78
78
|
} = input;
|
|
79
|
-
const
|
|
79
|
+
const room = resolvedHeight - topBottomLegendHeight;
|
|
80
|
+
const plotFloor = legendWins && !isSelfMeasured(room) ? 0 : VX.minPlotHeight;
|
|
80
81
|
return {
|
|
81
82
|
width: containerW === 0 ? minWidth : Math.max(containerW - sideLegendWidth, 1),
|
|
82
|
-
height: resolvedHeight <= 0 ? 0 : Math.max(
|
|
83
|
+
height: resolvedHeight <= 0 ? 0 : Math.max(room, plotFloor)
|
|
83
84
|
};
|
|
84
85
|
}
|
|
86
|
+
const SELF_MEASURED_SLACK = 0.5;
|
|
87
|
+
function isSelfMeasured(room) {
|
|
88
|
+
return room >= 0 && room < SELF_MEASURED_SLACK;
|
|
89
|
+
}
|
|
85
90
|
const legendBandHeight = (rows) => LEGEND_PAD_Y + rows * LEGEND_LINE_H + Math.max(rows - 1, 0) * VX.legendGap;
|
|
86
91
|
const legendEntryWidth = (item) => LEGEND_SWATCH_W + measureText(
|
|
87
92
|
item.note === void 0 ? item.label : `${item.label} ${item.note}`,
|
package/dist/common/errors.d.ts
CHANGED
|
@@ -23,6 +23,17 @@ export declare function oneOf(component: string, prop: string, allowed: readonly
|
|
|
23
23
|
* Majors are banned here, so the deprecation notice IS the migration channel.
|
|
24
24
|
*/
|
|
25
25
|
export declare function deprecatedProp(component: string, oldProp: string, newProp: string, removeIn: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* A prop the component ACCEPTS, reads nothing from, and cannot report as wrong — because a SECOND
|
|
28
|
+
* prop it was passed alongside decides the same thing and won.
|
|
29
|
+
*
|
|
30
|
+
* Distinct from {@link deprecatedProp} (still read, going away) and {@link oneOf} (read, and the
|
|
31
|
+
* value is out of range): this one renders something the caller did not ask for and nothing in the
|
|
32
|
+
* type system, the linter or the console says so. `ChartFrame`'s `height` under `fill` is the
|
|
33
|
+
* reference case — meteo passed both for the life of the file and never learnt that eight height
|
|
34
|
+
* constants and a whole `compact` branch changed no pixel.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ignoredProp(component: string, prop: string, because: string): string;
|
|
26
37
|
/**
|
|
27
38
|
* An imperative call needs a layer that is not there — the optional peer is not installed, or the
|
|
28
39
|
* mount that subscribes to its event bus is disabled. Both halves are named because the remedy
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/common/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,yDAAyD;AACzD,eAAO,MAAM,aAAa,aAAa,CAAA;AAMvC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,oEAAoE;AACpE,wBAAgB,KAAK,CACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,GAAG,EAAE,OAAO,GACX,MAAM,CAMR;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,MAAM,CAKR;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAErF;AAED,kGAAkG;AAClG,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxD;AAOD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI5D;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,SAAwB,GAAG,MAAM,CAKrF"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/common/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,yDAAyD;AACzD,eAAO,MAAM,aAAa,aAAa,CAAA;AAMvC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,oEAAoE;AACpE,wBAAgB,KAAK,CACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,GAAG,EAAE,OAAO,GACX,MAAM,CAMR;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,MAAM,CAKR;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAErF;AAED,kGAAkG;AAClG,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxD;AAOD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI5D;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,SAAwB,GAAG,MAAM,CAKrF"}
|
package/dist/common/errors.js
CHANGED
|
@@ -17,6 +17,9 @@ function deprecatedProp(component, oldProp, newProp, removeIn) {
|
|
|
17
17
|
`prop "${oldProp}" is deprecated \u2014 use "${newProp}" instead. "${oldProp}" is removed in ${removeIn}.`
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
|
+
function ignoredProp(component, prop, because) {
|
|
21
|
+
return message(component, `prop "${prop}" is ignored because ${because}`);
|
|
22
|
+
}
|
|
20
23
|
function missingLayer(component, layer, remedy) {
|
|
21
24
|
return message(component, `needs ${layer}, which is not available \u2014 ${remedy}.`);
|
|
22
25
|
}
|
|
@@ -57,6 +60,7 @@ export {
|
|
|
57
60
|
deprecatedProp,
|
|
58
61
|
duplicateMount,
|
|
59
62
|
errorStatus,
|
|
63
|
+
ignoredProp,
|
|
60
64
|
missingLayer,
|
|
61
65
|
oneOf,
|
|
62
66
|
requiredProp,
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v1.30.
|
|
1
|
+
# basalt-ui v1.30.2
|
|
2
2
|
# Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
|
|
3
3
|
#
|
|
4
4
|
# basalt-ui is an opinionated framework for Mantine-based React apps.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
|
|
20
20
|
|
|
21
21
|
Package: basalt-ui
|
|
22
|
-
Version: 1.30.
|
|
22
|
+
Version: 1.30.2
|
|
23
23
|
|
|
24
24
|
## basalt-ui
|
|
25
25
|
Import: import { ... } from 'basalt-ui'
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode } from 'react'
|
|
2
2
|
import { useCallback, useState } from 'react'
|
|
3
3
|
import type { BasaltProps } from '../../common/props'
|
|
4
|
+
import { ignoredProp } from '../../common/errors'
|
|
5
|
+
import { useValidateProps } from '../../common/validate'
|
|
4
6
|
import { VX } from '../../tokens'
|
|
5
7
|
import { useChartSize } from '../hooks/useChartSize'
|
|
6
8
|
import { deriveLegend } from '../series'
|
|
@@ -36,7 +38,13 @@ export type ChartFrameLegend = {
|
|
|
36
38
|
/** Default 'bottom'. */
|
|
37
39
|
placement?: LegendPlacement
|
|
38
40
|
/**
|
|
39
|
-
*
|
|
41
|
+
* Rollup cap at high cardinality: the legend renders this many ENTRIES and folds the remainder
|
|
42
|
+
* into a `+N more` disclosure chip.
|
|
43
|
+
*
|
|
44
|
+
* **It counts entries, not rows** — the name is inherited and now load-bearing across
|
|
45
|
+
* `ChartTierMetrics.legendMaxRows`, so it is not renamed in a patch. `maxRows: 3` on a 7-entry
|
|
46
|
+
* legend renders three entries, which is ONE row at any width they fit on. `ChartLegend` slices
|
|
47
|
+
* (`entries.slice(0, cap)`); nothing here measures rows.
|
|
40
48
|
*
|
|
41
49
|
* An explicit value WINS OUTRIGHT — over the phone tier's two-row default (`chartTierMetrics`)
|
|
42
50
|
* AND over a `fill` frame's measured fit (`legendEntryCap`). 1.30.0 fixed only the first half:
|
|
@@ -67,7 +75,8 @@ export type ChartFrameProps = BasaltProps & {
|
|
|
67
75
|
series: readonly SeriesStyle[]
|
|
68
76
|
/**
|
|
69
77
|
* Height in pixels, or one per size step. Used when neither `aspectRatio` nor `fill` is set.
|
|
70
|
-
* Default 240.
|
|
78
|
+
* Default 240. Passing it WITH `fill` is a wiring mistake, not a fallback — `fill` wins and
|
|
79
|
+
* this is never read, which now warns once in dev (`common/errors.ts`' `ignoredProp`).
|
|
71
80
|
*
|
|
72
81
|
* `{ base: 180, md: 260 }` is the declarative escape from a page of fixed-height charts: the
|
|
73
82
|
* steps are compared against the frame's own MEASURED width, not the viewport, so a chart squeezed
|
|
@@ -75,7 +84,8 @@ export type ChartFrameProps = BasaltProps & {
|
|
|
75
84
|
* supported ({@link resolveFrameHeight}).
|
|
76
85
|
*/
|
|
77
86
|
height?: number | ResponsiveChartHeight
|
|
78
|
-
/** height = Math.round(containerWidth / aspectRatio). Ignored when `fill` is set
|
|
87
|
+
/** height = Math.round(containerWidth / aspectRatio). Ignored when `fill` is set — which warns
|
|
88
|
+
* once in dev, for the same reason `height` does. */
|
|
79
89
|
aspectRatio?: number
|
|
80
90
|
/** Fill the parent flex/grid cell's measured height instead of a fixed/derived one. */
|
|
81
91
|
fill?: boolean
|
|
@@ -220,6 +230,32 @@ export function ChartFrame({
|
|
|
220
230
|
})
|
|
221
231
|
}, [])
|
|
222
232
|
|
|
233
|
+
// The three sizing props are mutually exclusive and `fill` wins, silently — the type cannot say
|
|
234
|
+
// so without a union prop, which is a bigger change than a patch carries. Warn instead: a
|
|
235
|
+
// consumer passed `height` alongside `fill` for the life of a file and kept eight height
|
|
236
|
+
// constants (and a whole `compact` branch) that never moved a pixel, with nothing from tsc,
|
|
237
|
+
// oxlint or check-theme saying a word.
|
|
238
|
+
useValidateProps(
|
|
239
|
+
'ChartFrame',
|
|
240
|
+
() => [
|
|
241
|
+
fill && height !== undefined
|
|
242
|
+
? ignoredProp(
|
|
243
|
+
'ChartFrame',
|
|
244
|
+
'height',
|
|
245
|
+
"`fill` is set — a fill frame takes its parent cell's measured height. Pass one or the other.",
|
|
246
|
+
)
|
|
247
|
+
: null,
|
|
248
|
+
fill && aspectRatio !== undefined
|
|
249
|
+
? ignoredProp(
|
|
250
|
+
'ChartFrame',
|
|
251
|
+
'aspectRatio',
|
|
252
|
+
"`fill` is set — a fill frame takes its parent cell's measured height. Pass one or the other.",
|
|
253
|
+
)
|
|
254
|
+
: null,
|
|
255
|
+
],
|
|
256
|
+
[fill, height, aspectRatio],
|
|
257
|
+
)
|
|
258
|
+
|
|
223
259
|
const placement = legend === false ? 'bottom' : (legend.placement ?? 'bottom')
|
|
224
260
|
const vertical = placement === 'left' || placement === 'right'
|
|
225
261
|
// Every non-null state replaces the plot, and all three suppress the legend for one reason: a
|
|
@@ -255,9 +255,11 @@ function LegendChild({ item }: { item: LegendEntry }) {
|
|
|
255
255
|
* Shared legend for all non-sparkline charts. `highlighted`/`onHighlight` are optional — omit for
|
|
256
256
|
* static legends without hover interactivity. `chartId` namespaces `split` clipPath ids so two
|
|
257
257
|
* legends on the same page never collide. `groups` renders series → hairline divider → overlay →
|
|
258
|
-
* reference bands instead of one flat smear. `maxRows` caps the number of rendered
|
|
259
|
-
* rolls the remainder into a `+N more` chip
|
|
260
|
-
*
|
|
258
|
+
* reference bands instead of one flat smear. `maxRows` caps the number of rendered ENTRIES and
|
|
259
|
+
* rolls the remainder into a `+N more` chip — it is an entry count, not a row count, in spite of
|
|
260
|
+
* the name (`entries.slice(0, cap)` below is the whole of it, and `ChartFrame` measures rows for a
|
|
261
|
+
* different purpose in `legendEntryCap`). The name is inherited and reaches the public
|
|
262
|
+
* `ChartTierMetrics.legendMaxRows`, so it stays until a minor can rename both.
|
|
261
263
|
*
|
|
262
264
|
* `onToggle` makes an entry a real toggle: clicking it hides the series in the plot, the tooltip,
|
|
263
265
|
* and the auto y-domain together (`ChartFrame` owns the `hidden` set — see `docs/CHARTS-SPEC.md`
|
|
@@ -260,9 +260,10 @@ export function resolveLegendRollup(input: {
|
|
|
260
260
|
* column with `height: auto`, so that growth is automatic. Under `fill` the box CANNOT grow, so
|
|
261
261
|
* the legend is capped instead ({@link legendEntryCap}) — UNLESS the caller stated
|
|
262
262
|
* `legend.maxRows`, which is `legendWins`: there the floor itself yields, because a floor is a
|
|
263
|
-
* default and the caller's number is not
|
|
264
|
-
*
|
|
265
|
-
*
|
|
263
|
+
* default and the caller's number is not, but only as far as the box HAS room
|
|
264
|
+
* ({@link SELF_MEASURED_SLACK}). An unmeasured box (`resolvedHeight <= 0`, i.e. a `fill` frame
|
|
265
|
+
* before its first measurement) stays at 0 and renders nothing, exactly as before — the floor
|
|
266
|
+
* must never invent a height for a box nobody has measured.
|
|
266
267
|
*/
|
|
267
268
|
export function resolvePlotRect(input: {
|
|
268
269
|
containerW: number
|
|
@@ -288,13 +289,46 @@ export function resolvePlotRect(input: {
|
|
|
288
289
|
topBottomLegendHeight,
|
|
289
290
|
legendWins = false,
|
|
290
291
|
} = input
|
|
291
|
-
const
|
|
292
|
+
const room = resolvedHeight - topBottomLegendHeight
|
|
293
|
+
const plotFloor = legendWins && !isSelfMeasured(room) ? 0 : VX.minPlotHeight
|
|
292
294
|
return {
|
|
293
295
|
width: containerW === 0 ? minWidth : Math.max(containerW - sideLegendWidth, 1),
|
|
294
|
-
height: resolvedHeight <= 0 ? 0 : Math.max(
|
|
296
|
+
height: resolvedHeight <= 0 ? 0 : Math.max(room, plotFloor),
|
|
295
297
|
}
|
|
296
298
|
}
|
|
297
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Sub-pixel slack on "the frame measures its own legend band and nothing else".
|
|
302
|
+
*
|
|
303
|
+
* MEASURED exact in the collapse this guards (both `ResizeObserver` contentRects read 29.375 in
|
|
304
|
+
* headless Chrome, `tests/layout/charts.layout.test.ts` INVARIANT 7) — the slack exists only so a
|
|
305
|
+
* future rounding or border difference between the two observers cannot re-open a fixpoint that
|
|
306
|
+
* has no other way out. A plot this thin has nothing to draw either way.
|
|
307
|
+
*/
|
|
308
|
+
const SELF_MEASURED_SLACK = 0.5
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Is the frame's height its OWN content rather than a box something else stated?
|
|
312
|
+
*
|
|
313
|
+
* `ChartFrame` measures its own node (`useChartSize` → visx `useParentSize` observes the element
|
|
314
|
+
* the ref is attached to, its name notwithstanding), and under `fill` its `height: 100%` resolves
|
|
315
|
+
* to `auto` in any parent that states no height. Its only children are the plot and the legend, so
|
|
316
|
+
* `room === 0` means the box IS the legend band — which happens exactly when no plot was drawn.
|
|
317
|
+
*
|
|
318
|
+
* That is why `legendWins` cannot yield the floor there: with the floor gone the sequence has a
|
|
319
|
+
* FIXPOINT AT ZERO — plot 0 → content is the legend → box = band → room 0 → plot 0, forever, and
|
|
320
|
+
* the chart never appears. Keeping the floor while `room` is 0 converges on the same box a `fill`
|
|
321
|
+
* frame in an unsized parent has always had (plot `VX.minPlotHeight`, box = plot + band), from
|
|
322
|
+
* which `room` is positive and `legendWins` applies normally.
|
|
323
|
+
*
|
|
324
|
+
* A NEGATIVE `room` is not this case and must keep yielding: a self-measured box can never be
|
|
325
|
+
* shorter than its own content, so a band taller than the box means the box was stated by
|
|
326
|
+
* something else, and spending all of it on legend rows is the caller's own arithmetic.
|
|
327
|
+
*/
|
|
328
|
+
function isSelfMeasured(room: number): boolean {
|
|
329
|
+
return room >= 0 && room < SELF_MEASURED_SLACK
|
|
330
|
+
}
|
|
331
|
+
|
|
298
332
|
/** Height of a legend band `rows` rows tall, wrapper padding included. */
|
|
299
333
|
const legendBandHeight = (rows: number): number =>
|
|
300
334
|
LEGEND_PAD_Y + rows * LEGEND_LINE_H + Math.max(rows - 1, 0) * VX.legendGap
|
package/src/common/errors.ts
CHANGED
|
@@ -55,6 +55,20 @@ export function deprecatedProp(
|
|
|
55
55
|
)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* A prop the component ACCEPTS, reads nothing from, and cannot report as wrong — because a SECOND
|
|
60
|
+
* prop it was passed alongside decides the same thing and won.
|
|
61
|
+
*
|
|
62
|
+
* Distinct from {@link deprecatedProp} (still read, going away) and {@link oneOf} (read, and the
|
|
63
|
+
* value is out of range): this one renders something the caller did not ask for and nothing in the
|
|
64
|
+
* type system, the linter or the console says so. `ChartFrame`'s `height` under `fill` is the
|
|
65
|
+
* reference case — meteo passed both for the life of the file and never learnt that eight height
|
|
66
|
+
* constants and a whole `compact` branch changed no pixel.
|
|
67
|
+
*/
|
|
68
|
+
export function ignoredProp(component: string, prop: string, because: string): string {
|
|
69
|
+
return message(component, `prop "${prop}" is ignored because ${because}`)
|
|
70
|
+
}
|
|
71
|
+
|
|
58
72
|
/**
|
|
59
73
|
* An imperative call needs a layer that is not there — the optional peer is not installed, or the
|
|
60
74
|
* mount that subscribes to its event bus is disabled. Both halves are named because the remedy
|