neba 1.3.0 → 1.5.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 (135) hide show
  1. package/README.md +7 -7
  2. package/dist/components/alert/Alert.d.ts +9 -1
  3. package/dist/components/alert/Alert.js +1 -1
  4. package/dist/components/area-chart/AreaChart.d.ts +54 -0
  5. package/dist/components/area-chart/AreaChart.js +1 -0
  6. package/dist/components/area-chart/index.d.ts +2 -0
  7. package/dist/components/area-chart/index.js +1 -0
  8. package/dist/components/bar-chart/BarChart.d.ts +63 -0
  9. package/dist/components/bar-chart/BarChart.js +1 -0
  10. package/dist/components/bar-chart/index.d.ts +2 -0
  11. package/dist/components/bar-chart/index.js +1 -0
  12. package/dist/components/bottom-navigation/BottomNavigation.d.ts +111 -0
  13. package/dist/components/bottom-navigation/BottomNavigation.js +1 -0
  14. package/dist/components/bottom-navigation/index.d.ts +1 -0
  15. package/dist/components/bottom-navigation/index.js +1 -0
  16. package/dist/components/breadcrumb/Breadcrumb.d.ts +35 -2
  17. package/dist/components/breadcrumb/Breadcrumb.js +1 -1
  18. package/dist/components/button/Button.d.ts +16 -0
  19. package/dist/components/button/Button.js +1 -1
  20. package/dist/components/carousel/Carousel.d.ts +10 -4
  21. package/dist/components/carousel/Carousel.js +1 -1
  22. package/dist/components/chip/Chip.d.ts +9 -1
  23. package/dist/components/chip/Chip.js +1 -1
  24. package/dist/components/collapsible/Collapsible.d.ts +88 -0
  25. package/dist/components/collapsible/Collapsible.js +1 -0
  26. package/dist/components/collapsible/index.d.ts +1 -0
  27. package/dist/components/collapsible/index.js +1 -0
  28. package/dist/components/combobox/Combobox.d.ts +19 -7
  29. package/dist/components/combobox/Combobox.js +1 -1
  30. package/dist/components/data-table/DataTable.d.ts +323 -0
  31. package/dist/components/data-table/DataTable.js +1 -0
  32. package/dist/components/data-table/index.d.ts +2 -0
  33. package/dist/components/data-table/index.js +1 -0
  34. package/dist/components/dialog/Dialog.d.ts +11 -5
  35. package/dist/components/dialog/Dialog.js +1 -1
  36. package/dist/components/drawer/Drawer.d.ts +11 -5
  37. package/dist/components/drawer/Drawer.js +1 -1
  38. package/dist/components/empty/Empty.d.ts +69 -0
  39. package/dist/components/empty/Empty.js +1 -0
  40. package/dist/components/empty/index.d.ts +2 -0
  41. package/dist/components/empty/index.js +1 -0
  42. package/dist/components/file-picker/FilePicker.d.ts +1 -3
  43. package/dist/components/file-picker/FilePicker.js +1 -1
  44. package/dist/components/floating-action-button/FloatingActionButton.d.ts +146 -0
  45. package/dist/components/floating-action-button/FloatingActionButton.js +1 -0
  46. package/dist/components/floating-action-button/index.d.ts +1 -0
  47. package/dist/components/floating-action-button/index.js +1 -0
  48. package/dist/components/heatmap-chart/HeatmapChart.d.ts +69 -0
  49. package/dist/components/heatmap-chart/HeatmapChart.js +1 -0
  50. package/dist/components/heatmap-chart/index.d.ts +2 -0
  51. package/dist/components/heatmap-chart/index.js +1 -0
  52. package/dist/components/highlight/Highlight.js +1 -1
  53. package/dist/components/line-chart/LineChart.d.ts +74 -0
  54. package/dist/components/line-chart/LineChart.js +1 -0
  55. package/dist/components/line-chart/index.d.ts +2 -0
  56. package/dist/components/line-chart/index.js +1 -0
  57. package/dist/components/mockup/Mockup.d.ts +151 -0
  58. package/dist/components/mockup/Mockup.js +1 -0
  59. package/dist/components/mockup/index.d.ts +2 -0
  60. package/dist/components/mockup/index.js +1 -0
  61. package/dist/components/number-field/NumberField.d.ts +10 -6
  62. package/dist/components/number-field/NumberField.js +1 -1
  63. package/dist/components/overlay/Overlay.d.ts +11 -6
  64. package/dist/components/overlay/Overlay.js +1 -1
  65. package/dist/components/pagination/Pagination.d.ts +28 -6
  66. package/dist/components/pagination/Pagination.js +1 -1
  67. package/dist/components/panes/Panes.js +1 -1
  68. package/dist/components/pie-chart/PieChart.d.ts +58 -0
  69. package/dist/components/pie-chart/PieChart.js +1 -0
  70. package/dist/components/pie-chart/index.d.ts +2 -0
  71. package/dist/components/pie-chart/index.js +1 -0
  72. package/dist/components/popover/Popover.d.ts +11 -5
  73. package/dist/components/popover/Popover.js +1 -1
  74. package/dist/components/progress-box/ProgressBox.js +1 -1
  75. package/dist/components/progress-circular/ProgressCircular.js +1 -1
  76. package/dist/components/progress-linear/ProgressLinear.js +1 -1
  77. package/dist/components/rating/Rating.d.ts +100 -0
  78. package/dist/components/rating/Rating.js +1 -0
  79. package/dist/components/rating/index.d.ts +1 -0
  80. package/dist/components/rating/index.js +1 -0
  81. package/dist/components/scatter-chart/ScatterChart.d.ts +45 -0
  82. package/dist/components/scatter-chart/ScatterChart.js +1 -0
  83. package/dist/components/scatter-chart/index.d.ts +2 -0
  84. package/dist/components/scatter-chart/index.js +1 -0
  85. package/dist/components/select/Select.d.ts +1 -3
  86. package/dist/components/select/Select.js +1 -1
  87. package/dist/components/sparkline/Sparkline.d.ts +67 -0
  88. package/dist/components/sparkline/Sparkline.js +1 -0
  89. package/dist/components/sparkline/index.d.ts +2 -0
  90. package/dist/components/sparkline/index.js +1 -0
  91. package/dist/components/statistic/Statistic.d.ts +7 -0
  92. package/dist/components/statistic/Statistic.js +1 -1
  93. package/dist/components/table/Table.d.ts +13 -2
  94. package/dist/components/table/Table.js +1 -1
  95. package/dist/components/text-link/TextLink.js +1 -1
  96. package/dist/components/time-picker/TimePicker.js +1 -1
  97. package/dist/components/timeline-chart/TimelineChart.d.ts +44 -0
  98. package/dist/components/timeline-chart/TimelineChart.js +1 -0
  99. package/dist/components/timeline-chart/index.d.ts +2 -0
  100. package/dist/components/timeline-chart/index.js +1 -0
  101. package/dist/components/toast/Toast.d.ts +10 -2
  102. package/dist/components/toast/Toast.js +1 -1
  103. package/dist/components/tooltip/Tooltip.d.ts +2 -4
  104. package/dist/components/tooltip/Tooltip.js +1 -1
  105. package/dist/components/tree-view/TreeView.js +1 -1
  106. package/dist/index.d.ts +15 -0
  107. package/dist/index.js +1 -1
  108. package/dist/internal/animate.d.ts +0 -9
  109. package/dist/internal/animate.js +1 -1
  110. package/dist/internal/calendar.js +1 -1
  111. package/dist/internal/chart-frame.d.ts +409 -0
  112. package/dist/internal/chart-frame.js +1 -0
  113. package/dist/internal/chart-line.d.ts +44 -0
  114. package/dist/internal/chart-line.js +1 -0
  115. package/dist/internal/chart.d.ts +456 -0
  116. package/dist/internal/chart.js +1 -0
  117. package/dist/internal/data-table.d.ts +170 -0
  118. package/dist/internal/data-table.js +1 -0
  119. package/dist/internal/date.d.ts +0 -2
  120. package/dist/internal/date.js +1 -1
  121. package/dist/internal/format.d.ts +29 -0
  122. package/dist/internal/format.js +1 -0
  123. package/dist/internal/i18n.d.ts +166 -0
  124. package/dist/internal/i18n.js +1 -1
  125. package/dist/internal/icons.d.ts +2 -0
  126. package/dist/internal/icons.js +1 -1
  127. package/dist/internal/mockup.d.ts +193 -0
  128. package/dist/internal/mockup.js +1 -0
  129. package/dist/internal/picker.d.ts +2 -4
  130. package/dist/internal/picker.js +1 -1
  131. package/dist/internal/progress.d.ts +1 -3
  132. package/dist/styles.css +1 -1
  133. package/dist/tailwind.css +231 -11
  134. package/dist/types.d.ts +214 -0
  135. package/package.json +19 -15
@@ -0,0 +1,456 @@
1
+ /**
2
+ * The arithmetic every chart is made of.
3
+ *
4
+ * Here rather than in a component for the reason `progress.ts` is: five
5
+ * components draw five different marks and ask exactly the same four questions
6
+ * first — what is the range, where does a value land in the plot, what are the
7
+ * clean numbers to tick at, and what colour is series four. A chart file that
8
+ * also has to answer those is a file where the drawing cannot be read.
9
+ *
10
+ * There is no React in here and nothing in it knows what an SVG is. What it
11
+ * knows is data and pixels; `chart-frame.tsx` is where those become elements.
12
+ *
13
+ * The scales are deliberately not a `d3-scale`. A linear scale is six lines, a
14
+ * band scale is four, and `nice numbers` is twelve — and the package still has
15
+ * one runtime dependency, which is the same trade `color.ts` makes.
16
+ */
17
+ import type * as React from 'react';
18
+ import type { NebaChartCategory, NebaChartDatum, NebaChartSeries, NebaDensity, NebaSize } from '../types';
19
+ /**
20
+ * How tall a plot is when nobody said, in pixels.
21
+ *
22
+ * A chart is one of the few things in the library with no intrinsic height — it
23
+ * is as tall as it is given — so this ladder is what stops every chart on a
24
+ * dashboard being a different shape. The steps climb faster than the control
25
+ * ladder because the thing being scaled is a *picture*: at `xs` this is a strip
26
+ * beside a number, at `xl` it is what the screen is about.
27
+ *
28
+ * The axis band is drawn *inside* this, not added to it. A card sized to the
29
+ * plot and then handed axis labels is the card that grows a two-line scrollbar.
30
+ */
31
+ export declare const plotHeights: Record<NebaSize, number>;
32
+ /**
33
+ * A Sparkline's own ladder, which is a different object: it has no axes, no
34
+ * legend and nothing to read off it but the shape, so it is sized against the
35
+ * line of text it sits next to rather than against the page.
36
+ */
37
+ export declare const sparklineHeights: Record<NebaSize, number>;
38
+ /**
39
+ * The weight of a line, in pixels. `md` is 2, which is the width a data line
40
+ * wants everywhere — thin enough to stay a line where two of them cross, heavy
41
+ * enough to hold a hue at 3:1.
42
+ */
43
+ export declare const lineWidths: Record<NebaSize, number>;
44
+ /**
45
+ * The radius of a marker. `md` is 4, so the dot is 8px across before its ring —
46
+ * the floor below which a marker stops being something a pointer can find.
47
+ */
48
+ export declare const markerRadii: Record<NebaSize, number>;
49
+ /**
50
+ * Tick and label type, in pixels rather than as a class.
51
+ *
52
+ * SVG text does not inherit a Tailwind utility usefully — the `<text>` has to
53
+ * carry a `font-size` the layout arithmetic can also read, because the room the
54
+ * axis reserves is measured from it. These are `metaTextClasses` as numbers;
55
+ * keep the two in step.
56
+ */
57
+ export declare const chartFontSizes: Record<NebaSize, number>;
58
+ /**
59
+ * How thick a bar is allowed to get, in pixels.
60
+ *
61
+ * A cap and not a width: the band a bar sits in is whatever the plot divided by
62
+ * the category count gives, and a bar that fills its band leaves the chart with
63
+ * no air in it at all. Past this the leftover stays as space.
64
+ */
65
+ export declare const barMaxThickness: Record<NebaSize, number>;
66
+ /**
67
+ * How much of a band the bars in it take, before the cap above applies.
68
+ * `density` is the only thing that moves it — the same rule as everywhere else,
69
+ * spacing and nothing but spacing.
70
+ */
71
+ export declare const barBandRatio: Record<NebaDensity, number>;
72
+ /** The gap the surface shows through between two touching marks, in pixels. */
73
+ export declare const markGap = 2;
74
+ /** The corner cut off the data end of a bar. Square at the baseline. */
75
+ export declare const barRadius = 4;
76
+ /** The eight slots, as the `var()`s that resolve them per theme. */
77
+ export declare const chartPalette: readonly string[];
78
+ /** A `NebaColor` resolves to its readable-on-surface accent; anything else is CSS. */
79
+ export declare function resolveColor(value: string): string;
80
+ /**
81
+ * What colour a mark is, in the fixed order the palette is handed out in.
82
+ *
83
+ * `index` is the series' place in the array it was passed in, not its place
84
+ * among the ones currently visible. That is the whole point: filtering a legend
85
+ * must not repaint the survivors, because a reader who learned that Europe is
86
+ * blue has learned something that a re-render is not allowed to take back.
87
+ *
88
+ * Past the eighth slot it wraps, and a chart that gets there should not have —
89
+ * a ninth hue is indistinguishable from one of the first eight under colour
90
+ * vision deficiency no matter which one is chosen. Fold the tail into an
91
+ * "Other" series, or draw a second chart.
92
+ */
93
+ export declare function seriesColor(series: Pick<NebaChartSeries, 'color'> | undefined, index: number, palette?: readonly string[]): string;
94
+ /** A datum unpacked into the shape the drawing code reads. */
95
+ export interface ChartValue {
96
+ value: number | null;
97
+ x?: NebaChartCategory;
98
+ z?: number;
99
+ color?: string;
100
+ label?: React.ReactNode;
101
+ }
102
+ /**
103
+ * One datum, whichever of the three ways it was written.
104
+ *
105
+ * `NaN` is folded into `null` here rather than at every call site: it arrives
106
+ * from a division somewhere upstream, it means the same thing a gap means, and
107
+ * a scale that is handed one produces a path with the letters `NaN` in it —
108
+ * which fails silently as a blank chart rather than loudly as an error.
109
+ */
110
+ export declare function toValue(datum: NebaChartDatum): ChartValue;
111
+ /** Every series unpacked, in the order it was given. */
112
+ export declare function toValues(series: readonly NebaChartSeries[]): ChartValue[][];
113
+ /**
114
+ * A category as a number, for a category axis that is really a value axis.
115
+ *
116
+ * A `Date` is its epoch milliseconds, which is what makes a scatter of
117
+ * timestamps work at all. A string is not a position on a number line, so it
118
+ * comes back `null` rather than `NaN` — the same rule `toValue` follows, and
119
+ * for the same reason: a `NaN` reaches the scale and leaves the letters in the
120
+ * path.
121
+ */
122
+ export declare function toNumber(value: NebaChartCategory | undefined): number | null;
123
+ /**
124
+ * Where one point sits along a category axis that runs on numbers.
125
+ *
126
+ * The same three sources `categoryAt` reads, in the same order — but per
127
+ * *point* rather than per column, because on a scatter each series has its own
128
+ * x at every index and there is no column for them to share.
129
+ */
130
+ export declare function pointX(value: ChartValue, index: number, categories: readonly NebaChartCategory[] | undefined): number | null;
131
+ /**
132
+ * The extent of the category values, for a chart whose x is a number.
133
+ *
134
+ * Only points that have a `y` count. A point with no value is not on the plot,
135
+ * so letting its `x` stretch the axis would leave a margin of empty plot
136
+ * standing in for data that was never drawn.
137
+ */
138
+ export declare function categoryExtent(values: readonly ChartValue[][], categories: readonly NebaChartCategory[] | undefined): {
139
+ min: number;
140
+ max: number;
141
+ } | null;
142
+ /**
143
+ * The radius a bubble gets for its `z`, in pixels.
144
+ *
145
+ * `z` is an **area** and not a radius, which is the single most common way a
146
+ * bubble chart lies: encode it as a radius and a value twice as large draws a
147
+ * mark four times the size. The square root is what makes the ink on the page
148
+ * proportional to the number behind it.
149
+ *
150
+ * `min` is a floor rather than a scale — a bubble for a small-but-real value
151
+ * has to stay something a pointer can find, and a zero is the only thing
152
+ * allowed to disappear.
153
+ */
154
+ export declare function bubbleRadius(z: number, maxZ: number, max: number, min: number): number;
155
+ /** How many categories the widest series has. */
156
+ export declare function categoryCount(series: readonly NebaChartSeries[]): number;
157
+ /**
158
+ * What the category axis says at position `index`.
159
+ *
160
+ * `categories` wins, then whatever the first series that has one calls its own
161
+ * point, then the index. Three sources rather than one because a chart is
162
+ * written both ways in the wild — a column of labels beside a column of
163
+ * numbers, or points that carry their own `x` — and neither is wrong.
164
+ */
165
+ export declare function categoryAt(index: number, categories: readonly NebaChartCategory[] | undefined, values: readonly ChartValue[][]): NebaChartCategory;
166
+ /**
167
+ * The extent of the values, with the stacking rule applied.
168
+ *
169
+ * Stacked charts measure the *totals* and not the parts, and the two arms are
170
+ * accumulated separately so a series that goes negative does not shorten the
171
+ * bar above it. An all-`null` chart has no extent at all, which is what the
172
+ * `null` return says — the caller draws its empty state rather than an axis
173
+ * from `Infinity` to `-Infinity`.
174
+ */
175
+ export declare function extentOf(values: readonly ChartValue[][], stacked: boolean): {
176
+ min: number;
177
+ max: number;
178
+ } | null;
179
+ /** A value scale: where it starts, where it ends, and what it ticks at. */
180
+ export interface ValueScale {
181
+ min: number;
182
+ max: number;
183
+ ticks: number[];
184
+ /** A value → a fraction of the plot, `0` at `min` and `1` at `max`. */
185
+ fraction: (value: number) => number;
186
+ }
187
+ /**
188
+ * The scale a value axis runs on, rounded out to clean numbers.
189
+ *
190
+ * Rounding *outward* is the part that matters: a maximum of 4,830 becomes 5,000
191
+ * and not 4,830, so the top tick is a number and the tallest bar stops short of
192
+ * the ceiling. A scale whose last bar touches the frame reads as clipped even
193
+ * when it is exactly right.
194
+ *
195
+ * Zero is included unless the caller says otherwise, because bar length is only
196
+ * proportional to value when the baseline is zero. A line chart of a quantity
197
+ * that never approaches zero is the case for passing `min` — and it is a case
198
+ * the caller has to make, not one the chart makes for them.
199
+ */
200
+ export declare function valueScale(extent: {
201
+ min: number;
202
+ max: number;
203
+ } | null, options?: {
204
+ min?: number;
205
+ max?: number;
206
+ tickCount?: number;
207
+ /** Keeps zero in range. Off for a line chart told an explicit `min`. */
208
+ includeZero?: boolean;
209
+ }): ValueScale;
210
+ /** The units a time axis is allowed to step in. */
211
+ export type TimeUnit = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
212
+ /** A value scale whose numbers are instants, and the unit its ticks step in. */
213
+ export interface TimeScale extends ValueScale {
214
+ unit: TimeUnit;
215
+ /** How many of that unit each step covers — 1, 5, 15 minutes and so on. */
216
+ step: number;
217
+ }
218
+ /**
219
+ * The scale a time axis runs on, ticking where a calendar ticks.
220
+ *
221
+ * The ends round *outward* to the step for the reason `valueScale`'s do: a span
222
+ * that starts exactly on the left edge reads as clipped rather than as
223
+ * starting there. Past a year the 1-2-5 family comes back, because above the
224
+ * year time really is decimal — decades and centuries are the only units left.
225
+ */
226
+ export declare function timeScale(extent: {
227
+ min: number;
228
+ max: number;
229
+ } | null, options?: {
230
+ min?: number;
231
+ max?: number;
232
+ tickCount?: number;
233
+ }): TimeScale;
234
+ /** One instant on a time axis, written unambiguously — for a tooltip or a table. */
235
+ export declare function formatTimeValue(value: number, unit: TimeUnit, locale?: string): string;
236
+ /**
237
+ * A whole axis of ticks, written the way an axis is read.
238
+ *
239
+ * The year is decided for the axis rather than for each tick, and that is the
240
+ * part worth explaining. Writing it only where it *changes* is what a reader
241
+ * wants and is not safe here: the labels are thinned again downstream, by a
242
+ * stride measured against the plot's real width, and the tick the year was
243
+ * riding on is exactly the one that gets dropped — leaving `Oct 2025 · Dec ·
244
+ * Feb` with nothing to say which year February is in.
245
+ *
246
+ * So: an axis inside one year names it once, on the first tick, which is the
247
+ * one tick a stride never removes. An axis that crosses a year names it on
248
+ * every tick, so whichever ones survive are each unambiguous. Wider labels mean
249
+ * a heavier stride, and a heavier stride is the better failure.
250
+ */
251
+ export declare function formatTimeTicks(ticks: readonly number[], unit: TimeUnit, locale?: string): string[];
252
+ /**
253
+ * How many ticks a category axis can show before the labels collide, and which
254
+ * ones they are.
255
+ *
256
+ * Every nth label rather than rotating them: a rotated axis is unreadable at a
257
+ * glance and it steals a band of the plot to be unreadable in. `n` is chosen so
258
+ * the labels clear each other at the measured width, and it always keeps the
259
+ * first — a reader who cannot see where the axis starts cannot read any of it.
260
+ */
261
+ export declare function tickStride(count: number, available: number, labelWidth: number): number;
262
+ /**
263
+ * Whether the label at `index` survives the stride.
264
+ *
265
+ * Every nth, and — when it fits — the last one, which is the part a plain
266
+ * modulo gets wrong: a fourteen-day axis at a stride of two ends at day
267
+ * thirteen, and a percentage axis ends at 80%. The end of a scale is the number
268
+ * a reader looks for first, and dropping it to keep the arithmetic tidy is the
269
+ * wrong trade.
270
+ *
271
+ * `roomForLast` is the other half of it and is measured rather than guessed —
272
+ * see `fitsLast`. Forcing a label that does not fit turns a missing "Jun" into
273
+ * an overlapping "MayJun", which is worse than what it fixed.
274
+ */
275
+ export declare function showsTick(index: number, count: number, stride: number, roomForLast: boolean): boolean;
276
+ /**
277
+ * Whether the last label clears the last one the stride kept.
278
+ *
279
+ * The two are `(count - 1) % stride` steps apart, and they need half of each
280
+ * label plus a little air between them — labels are centred on their tick, so
281
+ * only the inner halves can collide.
282
+ */
283
+ export declare function fitsLast(count: number, stride: number, step: number, labelWidth: number): boolean;
284
+ /**
285
+ * Roughly how wide a string renders at a given font size.
286
+ *
287
+ * An estimate on purpose. The alternative is a canvas measurement per label per
288
+ * render, which is a layout read on a path that runs on every resize — and what
289
+ * this number is used for is deciding how much room to reserve, where being a
290
+ * few pixels generous costs nothing and being exact costs a reflow.
291
+ *
292
+ * 0.6em is the average advance of a digit in the sans-serifs a UI runs in;
293
+ * anything CJK is close to a full em, so the widest character decides.
294
+ */
295
+ export declare function textWidth(text: string, fontSize: number): number;
296
+ /**
297
+ * A label cut to the room it has, with an ellipsis.
298
+ *
299
+ * The alternative when a category name is wider than its slot is to drop
300
+ * labels until the survivors fit, and on five categories called things like
301
+ * "Onboarding flow" that leaves exactly one of them on the axis — an axis with
302
+ * one label is not a shorter axis, it is an unlabelled one. Cut instead: the
303
+ * first few characters distinguish five words, and the tooltip and the table
304
+ * both still have the whole thing.
305
+ */
306
+ export declare function truncate(text: string, maxWidth: number, fontSize: number): string;
307
+ /** The plot's box inside the chart, once the axes have taken their bands. */
308
+ export interface PlotBox {
309
+ left: number;
310
+ top: number;
311
+ width: number;
312
+ height: number;
313
+ }
314
+ /** A band scale: one slot per category, with the marks centred in it. */
315
+ export interface BandScale {
316
+ /** The centre of category `index`, in pixels along the axis. */
317
+ centre: (index: number) => number;
318
+ /** How wide one slot is. */
319
+ step: number;
320
+ /** How wide the marks in a slot are allowed to be, together. */
321
+ band: number;
322
+ }
323
+ export declare function bandScale(count: number, length: number, ratio: number): BandScale;
324
+ /**
325
+ * A path through the points, in whichever of the three shapes was asked for.
326
+ *
327
+ * `null` breaks the path rather than interpolating across it — the `M` that
328
+ * starts a new subpath is the gap. A line that bridges a missing month is a
329
+ * line that invents a number.
330
+ *
331
+ * `smooth` is a monotone cubic and not a Catmull-Rom, which is not a detail: a
332
+ * plain spline overshoots between two close points, so a series that never goes
333
+ * below zero draws a curve that does. A chart is allowed to be curved and it is
334
+ * not allowed to show a value that is not in the data.
335
+ */
336
+ export declare function linePath(points: readonly ({
337
+ x: number;
338
+ y: number;
339
+ } | null)[], curve: 'linear' | 'smooth' | 'step'): string;
340
+ /**
341
+ * The same path closed down to a baseline, for an area.
342
+ *
343
+ * Built from the runs rather than from the whole line so a gap is a gap in the
344
+ * fill too — an area that closes across a missing month fills in a value that
345
+ * was never measured, which is the same lie the bridged line tells, painted
346
+ * over a larger part of the chart.
347
+ */
348
+ export declare function areaPath(points: readonly ({
349
+ x: number;
350
+ y: number;
351
+ } | null)[], baseline: readonly ({
352
+ x: number;
353
+ y: number;
354
+ } | null)[] | number, curve: 'linear' | 'smooth' | 'step'): string;
355
+ /**
356
+ * A rectangle with the two corners at its *data end* cut off.
357
+ *
358
+ * Rounded at the end and square at the baseline, which is not a stylistic
359
+ * split: a bar that is rounded where it meets the axis has lost the exact
360
+ * moment it starts, and a row of them turns the baseline into a scalloped edge.
361
+ * The end is where the value is, and that is the end worth softening.
362
+ *
363
+ * The radius shrinks to fit rather than clipping, so a bar two pixels tall is a
364
+ * bar and not a circle. `end` is which way the value grows.
365
+ */
366
+ export declare function barPath(x: number, y: number, width: number, height: number, radius: number, end: 'up' | 'down' | 'left' | 'right'): string;
367
+ /**
368
+ * The shapes a point mark can take.
369
+ *
370
+ * The dependable second identity channel, and the only chart form with one
371
+ * going spare: a line cannot be a triangle and a bar cannot be a cross, but a
372
+ * dot can be anything. Five is enough for every palette a chart is allowed.
373
+ */
374
+ export type MarkShape = 'circle' | 'square' | 'triangle' | 'diamond' | 'cross';
375
+ /** The order shapes are handed out in, matching the palette: fixed, never cycled. */
376
+ export declare const markShapes: readonly MarkShape[];
377
+ /**
378
+ * One point mark, as a path, centred on `cx`/`cy` and covering the same area a
379
+ * circle of radius `r` would.
380
+ *
381
+ * A circle comes back as a path too rather than as a `<circle>`, so whoever
382
+ * draws the marks writes one element and not a branch — the ring, the fill and
383
+ * the hover treatment are then unarguably the same on all five.
384
+ */
385
+ export declare function markPath(shape: MarkShape, cx: number, cy: number, r: number): string;
386
+ /**
387
+ * A slice of a ring, as a path.
388
+ *
389
+ * `inner` of 0 is a pie and anything above it is a donut. Angles are degrees
390
+ * clockwise from twelve o'clock, which is where a reader starts reading a
391
+ * circle — SVG's own zero is at three o'clock, and the offset is applied here
392
+ * once rather than at four call sites.
393
+ */
394
+ export declare function arcPath(cx: number, cy: number, outer: number, inner: number, from: number, to: number): string;
395
+ /** Which way a magnitude is coloured. */
396
+ export type ChartScaleKind = 'sequential' | 'diverging';
397
+ /** How many steps each ramp has. Five, and the reason is in `styles.css`. */
398
+ export declare const rampSteps = 5;
399
+ /**
400
+ * The step a value lands on, and the ink a label on it wears.
401
+ *
402
+ * A magnitude is not an identity, so it does not come off the eight-slot
403
+ * categorical ramp — see the note above `--neba-chart-seq-1`. It comes off a
404
+ * one-hue ladder, and which rung is arithmetic on the value.
405
+ *
406
+ * A diverging scale is read from its *middle* rather than from its bottom, so
407
+ * it is the distance either side of the neutral that is scaled — and by the
408
+ * larger of the two arms, so a set running from −2 to +40 does not paint every
409
+ * negative the deepest blue there is.
410
+ */
411
+ export declare function rampStep(value: number, min: number, max: number, kind: ChartScaleKind, midpoint?: number): number;
412
+ /** The `var()` for a step of the ramp, and for the ink that reads on it. */
413
+ export declare function rampFill(step: number, kind: ChartScaleKind): string;
414
+ export declare function rampInk(step: number, kind: ChartScaleKind): string;
415
+ /** One tile, in pixels, and which value it came from. */
416
+ export interface TreemapTile {
417
+ index: number;
418
+ x: number;
419
+ y: number;
420
+ width: number;
421
+ height: number;
422
+ }
423
+ /**
424
+ * A squarified treemap: the values as boxes whose areas are proportional, laid
425
+ * out as close to square as they can be got.
426
+ *
427
+ * Squarified rather than sliced, and the difference is the whole reason the
428
+ * forty lines are worth it. A slice-and-dice treemap of twenty values ends in
429
+ * slivers a pixel wide, and a sliver's *area* is unreadable however exact it is
430
+ * — the reader compares its length instead, which is not the encoded quantity.
431
+ * Bruls, Huizing and van Wijk's answer is greedy and simple: fill a row along
432
+ * the box's shorter side, keep adding to it while the worst aspect ratio in it
433
+ * improves, and start a new row the moment it stops.
434
+ *
435
+ * The order is the caller's; the layout sorts descending internally because the
436
+ * algorithm needs it and hands the original index back on every tile, so a
437
+ * tile's colour and its name are still its own.
438
+ */
439
+ export declare function squarify(values: readonly number[], width: number, height: number): TreemapTile[];
440
+ /**
441
+ * How a category is written when nobody said.
442
+ *
443
+ * A `Date` gets the reader's own short form, because the alternative is an ISO
444
+ * string across the bottom of every time series. Everything else is `String`,
445
+ * which is what the caller wrote it as.
446
+ */
447
+ export declare function formatCategory(value: NebaChartCategory, locale?: string): string;
448
+ /**
449
+ * A number, compactly enough that a y-axis of thousands is not four labels of
450
+ * seven characters.
451
+ *
452
+ * Only when the caller passed no `format` of their own — the moment they do,
453
+ * they have said what the number means and the library's opinion about
454
+ * thousands separators stops being welcome.
455
+ */
456
+ export declare function compactNumber(value: number, locale?: string): string;
@@ -0,0 +1 @@
1
+ import{dateFormatter as t,numberFormatter as e}from"./format";export const plotHeights={xs:120,sm:160,md:220,lg:280,xl:360};export const sparklineHeights={xs:16,sm:20,md:28,lg:40,xl:56};export const lineWidths={xs:1.5,sm:1.75,md:2,lg:2.25,xl:2.5};export const markerRadii={xs:3,sm:3.5,md:4,lg:4.5,xl:5};export const chartFontSizes={xs:10,sm:11,md:12,lg:13,xl:14};export const barMaxThickness={xs:14,sm:18,md:24,lg:30,xl:36};export const barBandRatio={default:.62,compact:.82};export const markGap=2;export const barRadius=4;export const chartPalette=["var(--neba-chart-1)","var(--neba-chart-2)","var(--neba-chart-3)","var(--neba-chart-4)","var(--neba-chart-5)","var(--neba-chart-6)","var(--neba-chart-7)","var(--neba-chart-8)"];const n=new Set(["primary","secondary","success","warning","danger","info"]);export function resolveColor(t){return n.has(t)?`var(--neba-${t}-accent)`:t}export function seriesColor(t,e,n=chartPalette){return resolveColor(t?.color?t.color:n[e%n.length]??chartPalette[0])}export function toValue(t){return null==t?{value:null}:"number"==typeof t?{value:Number.isFinite(t)?t:null}:(t=>"object"==typeof t&&null!==t)(t)?{value:null!==t.y&&Number.isFinite(t.y)?t.y:null,x:t.x,z:t.z,color:t.color?resolveColor(t.color):void 0,label:t.label}:{value:null}}export function toValues(t){return t.map(t=>t.data.map(toValue))}export function toNumber(t){return t instanceof Date?Number.isFinite(t.getTime())?t.getTime():null:"number"==typeof t&&Number.isFinite(t)?t:null}export function pointX(t,e,n){return toNumber(t.x??n?.[e]??e)}export function categoryExtent(t,e){let n=1/0,r=-1/0,o=!1;return t.forEach(t=>{t.forEach((t,a)=>{if(null===t.value)return;const i=pointX(t,a,e);null!==i&&(o=!0,n=Math.min(n,i),r=Math.max(r,i))})}),o?{min:n,max:r}:null}export function bubbleRadius(t,e,n,r){return e>0&&Number.isFinite(t)&&!(t<=0)?Math.max(r,Math.sqrt(Math.min(t,e)/e)*n):0===t?0:r}export function categoryCount(t){return t.reduce((t,e)=>Math.max(t,e.data.length),0)}export function categoryAt(t,e,n){if(e&&t<e.length)return e[t];for(const e of n){const n=e[t]?.x;if(void 0!==n)return n}return t}export function extentOf(t,e){let n=1/0,r=-1/0,o=!1;if(e){const e=t.reduce((t,e)=>Math.max(t,e.length),0);for(let a=0;a<e;a++){let e=0,i=0;for(const n of t){const t=n[a]?.value;null!=t&&(o=!0,t>=0?e+=t:i+=t)}n=Math.min(n,i),r=Math.max(r,e)}}else for(const e of t)for(const{value:t}of e)null!==t&&(o=!0,n=Math.min(n,t),r=Math.max(r,t));return o?{min:n,max:r}:null}function r(t){const e=10**Math.floor(Math.log10(t)),n=t/e;return n<=1?e:n<=2?2*e:n<=5?5*e:10*e}export function valueScale(t,e={}){const{tickCount:n=5,includeZero:o=!0}=e;let a=e.min??(t?t.min:0),i=e.max??(t?t.max:1);if(o&&void 0===e.min&&(a=Math.min(a,0)),o&&void 0===e.max&&(i=Math.max(i,0)),i===a){const t=Math.abs(i)>0?.5*Math.abs(i):1;a-=t,i+=t}const u=void 0!==e.min&&void 0!==e.max?function(t,e){const n=10**Math.floor(Math.log10(t/Math.max(1,e)));let o=r(t/Math.max(1,e)),a=1/0;for(const r of[.1,1,10])for(const i of[1,2,2.5,5]){const u=i*r*n,s=t/u,c=Math.round(s);if(c<1||Math.abs(s-c)>1e-9)continue;const l=Math.abs(c-e);l<a&&(a=l,o=u)}return o}(i-a,n):r((i-a)/Math.max(1,n)),s=void 0!==e.min?a:Math.floor(a/u)*u,c=void 0!==e.max?i:Math.ceil(i/u)*u,l=c-s||1,h=[];for(let t=s;t<=c+1e-9*u;t+=u)h.push(Number(t.toFixed(12)));return{min:s,max:c,ticks:h,fraction:t=>(t-s)/l}}const o=1e3,a=6e4,i=36e5,u=24*i,s=[{unit:"second",count:1,size:o},{unit:"second",count:5,size:5e3},{unit:"second",count:15,size:15e3},{unit:"second",count:30,size:3e4},{unit:"minute",count:1,size:a},{unit:"minute",count:5,size:3e5},{unit:"minute",count:15,size:9e5},{unit:"minute",count:30,size:18e5},{unit:"hour",count:1,size:i},{unit:"hour",count:3,size:3*i},{unit:"hour",count:6,size:6*i},{unit:"hour",count:12,size:12*i},{unit:"day",count:1,size:u},{unit:"day",count:2,size:2*u},{unit:"week",count:1,size:7*u},{unit:"week",count:2,size:14*u},{unit:"month",count:1,size:30*u},{unit:"quarter",count:1,size:91*u},{unit:"month",count:6,size:182*u},{unit:"year",count:1,size:365*u}];function c(t,e,n){const r=new Date(t);if("year"===e)r.setFullYear(r.getFullYear()+n);else if("quarter"===e)r.setMonth(r.getMonth()+3*n);else if("month"===e)r.setMonth(r.getMonth()+n);else if("week"===e)r.setDate(r.getDate()+7*n);else if("day"===e)r.setDate(r.getDate()+n);else{if("hour"!==e)return t+n*("minute"===e?a:o);r.setHours(r.getHours()+n)}return r.getTime()}const l={second:"minute",minute:"hour",hour:"day",day:"month",week:"week",month:"year",quarter:"year",year:"year"};function h(t,e,n){let r=function(t,e){const n=new Date(t);if("year"===e)return new Date(n.getFullYear(),0,1).getTime();if("quarter"===e)return new Date(n.getFullYear(),3*Math.floor(n.getMonth()/3),1).getTime();if("month"===e)return new Date(n.getFullYear(),n.getMonth(),1).getTime();if("week"===e){const t=new Date(n.getFullYear(),n.getMonth(),n.getDate());return t.setDate(t.getDate()-t.getDay()),t.getTime()}return"day"===e?new Date(n.getFullYear(),n.getMonth(),n.getDate()).getTime():"hour"===e?new Date(n.getFullYear(),n.getMonth(),n.getDate(),n.getHours()).getTime():"minute"===e?Math.floor(t/a)*a:Math.floor(t/o)*o}(t,l[e]);if("year"===e&&n>1){const t=new Date(r).getFullYear();r=new Date(t-(t%n+n)%n,0,1).getTime()}for(let o=0;o<500;o++){const o=c(r,e,n);if(o>t)return r;r=o}return r}export function timeScale(t,e={}){const{tickCount:n=6}=e;let o=e.min??t?.min??Date.parse("2000-01-01T00:00:00"),a=e.max??t?.max??o+u;a<=o&&(o-=u/2,a+=u/2);const i=a-o;let l=s[0],m=1/0;for(const t of s){const e=Math.abs(i/t.size-n);e<m&&(m=e,l=t)}const f=l.unit,$="year"===f?Math.max(1,Math.round(r(i/n/(365*u)))):l.count,x=e.min??h(o,f,$),p=[];let M=x;for(let t=0;t<500&&(p.push(M),!(M>a));t++)M=c(M,f,$);const g=e.max??p[p.length-1],d=g-x||1;return{min:x,max:g,ticks:p.filter(t=>t>=x&&t<=g),unit:f,step:$,fraction:t=>(t-x)/d}}function m(t,e){if("second"===t)return{hour:"2-digit",minute:"2-digit",second:"2-digit",hourCycle:"h23"};if("minute"===t||"hour"===t)return{hour:"2-digit",minute:"2-digit",hourCycle:"h23"};if("year"===t)return{year:"numeric"};const n="month"===t||"quarter"===t?{month:"short"}:{month:"short",day:"numeric"};return e?{...n,year:"numeric"}:n}export function formatTimeValue(e,n,r){return t(r,m(n,!0)).format(new Date(e))}export function formatTimeTicks(e,n,r){const o=new Set(e.map(t=>new Date(t).getFullYear())).size>1;return e.map((e,a)=>t(r,m(n,o||0===a)).format(new Date(e)))}export function tickStride(t,e,n){if(t<=1||e<=0)return 1;const r=Math.max(1,Math.floor(e/Math.max(1,n)));return Math.max(1,Math.ceil(t/r))}export function showsTick(t,e,n,r){return t%n===0||r&&t===e-1}export function fitsLast(t,e,n,r){const o=(t-1)%e;return o>0&&o*n>=r+8}export function textWidth(t,e){let n=0;for(const e of t)n+=/[ᄀ-ᇿ⺀-꓏가-퟿豈-﫿︰-﹏]/.test(e)?1:.6;return n*e}export function truncate(t,e,n){if(e<=0||textWidth(t,n)<=e)return t;const r=e-textWidth("…",n);let o="",a=0;for(const e of t){const t=a+textWidth(e,n);if(t>r)break;a=t,o+=e}return o.length>0?`${o.trimEnd()}…`:"…"}export function bandScale(t,e,n){const r=t>0?e/t:e;return{step:r,band:r*n,centre:t=>r*(t+.5)}}export function linePath(t,e){const n=[];let r=[];const o=()=>{if(0!==r.length){if(1===r.length)n.push(`M${r[0].x} ${r[0].y}h0`);else if("step"===e){n.push(`M${r[0].x} ${r[0].y}`);for(let t=1;t<r.length;t++){const e=(r[t-1].x+r[t].x)/2;n.push(`H${e}V${r[t].y}H${r[t].x}`)}}else if("smooth"===e)n.push(`M${r[0].x} ${r[0].y}`),n.push(function(t){const e=t.length,n=[];for(let r=0;r<e-1;r++){const e=t[r+1].x-t[r].x;n.push(0===e?0:(t[r+1].y-t[r].y)/e)}const r=[n[0]??0];for(let t=1;t<e-1;t++){const e=n[t-1],o=n[t];r.push(e*o<=0?0:2*e*o/(e+o))}r.push(n[e-2]??0);const o=[];for(let n=0;n<e-1;n++){const e=(t[n+1].x-t[n].x)/3;o.push(`C${t[n].x+e} ${t[n].y+r[n]*e} ${t[n+1].x-e} ${t[n+1].y-r[n+1]*e} ${t[n+1].x} ${t[n+1].y}`)}return o.join("")}(r));else{n.push(`M${r[0].x} ${r[0].y}`);for(let t=1;t<r.length;t++)n.push(`L${r[t].x} ${r[t].y}`)}r=[]}};for(const e of t)null===e?o():r.push(e);return o(),n.join("")}export function areaPath(t,e,n){const r=[];let o=0;const a=a=>{const i=t.slice(o,a).filter(Boolean);if(0===i.length)return;const u="number"==typeof e?i.map(t=>({x:t.x,y:e})).reverse():e.slice(o,a).filter(Boolean).reverse();if(0===u.length)return;const s=linePath(i,n),c=linePath(u,n).replace(/^M/,"L");r.push(`${s}${c}Z`)};for(let e=0;e<t.length;e++)null===t[e]&&(a(e),o=e+1);return a(t.length),r.join("")}export function barPath(t,e,n,r,o,a){const i=Math.max(0,Math.min(o,n/2,r/2));if(0===i||n<=0||r<=0)return`M${t} ${e}h${n}v${r}h${-n}Z`;const u=(t,e)=>`a${i} ${i} 0 0 1 ${t} ${e}`;return"up"===a?`M${t} ${e+r}V${e+i}${u(i,-i)}H${t+n-i}${u(i,i)}V${e+r}Z`:"down"===a?`M${t} ${e}H${t+n}V${e+r-i}${u(-i,i)}H${t+i}${u(-i,-i)}Z`:"right"===a?`M${t} ${e}H${t+n-i}${u(i,i)}V${e+r-i}${u(-i,i)}H${t}Z`:`M${t+n} ${e+r}H${t+i}${u(-i,-i)}V${e+i}${u(i,-i)}H${t+n}Z`}export const markShapes=["circle","square","triangle","diamond","cross"];const f={circle:1,square:.8862,triangle:1.5551,diamond:1.2533,cross:1.189};export function markPath(t,e,n,r){const o=Math.max(0,r)*f[t];if(0===o)return"";if("circle"===t)return`M${e-o} ${n}a${o} ${o} 0 1 0 ${2*o} 0a${o} ${o} 0 1 0 ${2*-o} 0Z`;if("square"===t)return`M${e-o} ${n-o}h${2*o}v${2*o}h${2*-o}Z`;if("diamond"===t)return`M${e} ${n-o}L${e+o} ${n}L${e} ${n+o}L${e-o} ${n}Z`;if("triangle"===t){return`M${[0,120,240].map(t=>{const r=(t-90)*Math.PI/180;return`${e+o*Math.cos(r)} ${n+o*Math.sin(r)}`}).join("L")}Z`}const a=o/3;return`M${e-a} ${n-o}h${2*a}v${o-a}h${o-a}v${2*a}h${-(o-a)}v${o-a}h${2*-a}v${-(o-a)}h${-(o-a)}v${2*-a}h${o-a}Z`}export function arcPath(t,e,n,r,o,a){const i=(n,r)=>{const o=(r-90)*Math.PI/180;return`${t+n*Math.cos(o)} ${e+n*Math.sin(o)}`};if(Math.abs(a-o)>=360){const t=o+180;return r>0?`M${i(n,o)}A${n} ${n} 0 1 1 ${i(n,t)}A${n} ${n} 0 1 1 ${i(n,o)}ZM${i(r,o)}A${r} ${r} 0 1 0 ${i(r,t)}A${r} ${r} 0 1 0 ${i(r,o)}Z`:`M${i(n,o)}A${n} ${n} 0 1 1 ${i(n,t)}A${n} ${n} 0 1 1 ${i(n,o)}Z`}const u=Math.abs(a-o)>180?1:0;return r<=0?`M${t} ${e}L${i(n,o)}A${n} ${n} 0 ${u} 1 ${i(n,a)}Z`:`M${i(n,o)}A${n} ${n} 0 ${u} 1 ${i(n,a)}L${i(r,a)}A${r} ${r} 0 ${u} 0 ${i(r,o)}Z`}export const rampSteps=5;export function rampStep(t,e,n,r,o=0){if("diverging"===r){const r=Math.max(Math.abs(n-o),Math.abs(o-e));if(!(r>0))return 2;const a=(t-o)/r;return Math.min(4,Math.max(0,2+Math.round(2*a)))}const a=n-e;return a>0?Math.min(4,Math.max(0,Math.floor((t-e)/a*5))):4}export function rampFill(t,e){return`var(--neba-chart-${"diverging"===e?"div":"seq"}-${t+1})`}export function rampInk(t,e){return`var(--neba-chart-${"diverging"===e?"div":"seq"}-on-${t+1})`}export function squarify(t,e,n){const r=t.reduce((t,e)=>t+Math.max(0,e),0);if(!(r>0)||e<=0||n<=0)return[];const o=e*n/r,a=t.map((t,e)=>({index:e,area:Math.max(0,t)*o})).filter(t=>t.area>0).sort((t,e)=>e.area-t.area),i=[];let u=0,s=0,c=e,l=n,h=[];const m=t=>{const e=Math.min(c,l),n=t.reduce((t,e)=>t+e.area,0);if(!(n>0&&e>0))return 1/0;const r=t[0].area,o=t[t.length-1].area;return Math.max(e*e*r/(n*n),n*n/(e*e*o))},f=()=>{const t=h.reduce((t,e)=>t+e.area,0),e=Math.min(c,l),n=e>0?t/e:0,r=c>=l;let o=0;for(const t of h){const e=n>0?t.area/n:0;i.push(r?{index:t.index,x:u,y:s+o,width:n,height:e}:{index:t.index,x:u+o,y:s,width:e,height:n}),o+=e}r?(u+=n,c-=n):(s+=n,l-=n),h=[]};for(const t of a)0===h.length||m([...h,t])<=m(h)||f(),h.push(t);return h.length>0&&f(),i}const $={month:"short",day:"numeric"},x={notation:"compact",maximumFractionDigits:1},p={maximumFractionDigits:2};export function formatCategory(e,n){return e instanceof Date?t(n,$).format(e):String(e)}export function compactNumber(t,n){return Math.abs(t)>=1e4?e(n,x).format(t):e(n,p).format(t)}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The arithmetic a DataTable is made of.
3
+ *
4
+ * Here rather than in the component for the reason `progress.ts` and `color.ts`
5
+ * are here: none of it is layout and none of it is a class name. Sorting rows,
6
+ * matching a query against them, cutting a page out of the result and working
7
+ * out which twenty of forty thousand rows are actually on screen are four
8
+ * questions with numeric answers, and a component file that also has to draw a
9
+ * resize handle is not where they can be read.
10
+ *
11
+ * `color.ts` is the precedent for a single-consumer file: the test is whether
12
+ * the thing is separable, not whether two components happen to want it. Every
13
+ * function below takes plain values and returns plain values — there is no
14
+ * React in this file, and nothing in it knows what a column is.
15
+ */
16
+ import type { NebaDensity, NebaSize } from '../types';
17
+ /**
18
+ * Row height, in **pixels**, and the one number in the library that has to be a
19
+ * number rather than a class.
20
+ *
21
+ * Virtual scrolling is arithmetic on this value: how far down the list a scroll
22
+ * offset lands, how tall the spacer above the first rendered row is, how many
23
+ * rows fit in the viewport for PageDown. A Tailwind class cannot be added up.
24
+ *
25
+ * The ladder is deliberately below `controlHeightClasses` at every step — 28px
26
+ * at `md` against a Button's 32 — because a DataTable is a hundred rows on one
27
+ * screen and a Table is eight. A row here is the height of the text plus enough
28
+ * air to click, and nothing more; `density` is what takes the last of the air
29
+ * out. A caller who puts a Chip or an Avatar in a cell raises `rowHeight`.
30
+ */
31
+ export declare const dataRowHeights: Record<NebaDensity, Record<NebaSize, number>>;
32
+ /**
33
+ * The header, one step taller than the rows under it.
34
+ *
35
+ * A fixed number rather than "whatever the content measures", because a sticky
36
+ * header over a group row needs an offset to stick the second row at, and a
37
+ * measured one would be a layout read on every scroll frame.
38
+ */
39
+ export declare const dataHeaderHeights: Record<NebaDensity, Record<NebaSize, number>>;
40
+ /**
41
+ * The tick column, and the resize handle's grab area.
42
+ *
43
+ * The tick column is `tickSizeClasses` plus the two padding tracks, rounded to
44
+ * a whole pixel: a column narrower than the box in it clips the focus ring.
45
+ */
46
+ export declare const dataTickWidths: Record<NebaSize, number>;
47
+ /** How wide a column is when neither the caller nor a drag has said. */
48
+ export declare const defaultColumnWidth = 160;
49
+ /** How narrow a drag may make one. Below this the heading is a single letter. */
50
+ export declare const minColumnWidth = 48;
51
+ export type SortDirection = 'asc' | 'desc';
52
+ /** One key of a sort, and which way it runs. */
53
+ export interface SortEntry {
54
+ key: string;
55
+ direction: SortDirection;
56
+ }
57
+ /**
58
+ * The default comparison, for the columns that do not bring one.
59
+ *
60
+ * Empty sorts last in both directions, which is the one asymmetry here and the
61
+ * one every spreadsheet has: a blank is not the smallest value, it is the
62
+ * absence of one, and a descending sort whose first screen is forty blanks has
63
+ * answered the wrong question.
64
+ *
65
+ * Numbers compare as numbers, dates as instants, booleans with `false` first,
66
+ * and everything else through the collator — which is `numeric`, so `item2`
67
+ * comes before `item10` rather than after it.
68
+ */
69
+ export declare function compareValues(a: unknown, b: unknown, collator: Intl.Collator): number;
70
+ /**
71
+ * Sorts by every key at once, first key outermost.
72
+ *
73
+ * The sort is **stable** — `Array.prototype.sort` has been since ES2019 — which
74
+ * is what makes a multi-key sort composable: sorting by name and then adding
75
+ * date to the end of the list leaves rows with the same date in name order,
76
+ * because that is the order they were already in.
77
+ *
78
+ * `comparatorFor` returns `null` for a key no column claims, so a sort left
79
+ * over from a column that has since been removed is skipped rather than
80
+ * throwing.
81
+ */
82
+ export declare function sortRows<T>(rows: readonly T[], sort: readonly SortEntry[], comparatorFor: (key: string) => ((a: T, b: T) => number) | null): T[];
83
+ /**
84
+ * What pressing a heading does, given what the sort already says.
85
+ *
86
+ * Three states rather than two — ascending, descending, unsorted — because the
87
+ * order the rows arrived in is a state a caller cannot get back to by pressing
88
+ * anything if the cycle only has two. This is the opposite of the choice
89
+ * TreeView's single select makes, and for the same reason: there, "nothing
90
+ * chosen" is reachable by pressing something else; here it is not.
91
+ *
92
+ * `additive` is what a Shift-click sets. It keeps the column where it already
93
+ * is in the list rather than moving it to the end, so flipping the second key
94
+ * of a two-key sort does not silently make it the first.
95
+ */
96
+ export declare function nextSort(current: readonly SortEntry[], key: string, additive: boolean): SortEntry[];
97
+ /**
98
+ * A cell value as something a query can be matched against.
99
+ *
100
+ * Case-folded and stripped of combining marks, so `jose` finds `José` and
101
+ * `SEOUL` finds `Seoul`. `NFD` splits an accented letter into the letter and
102
+ * its accent and the range then deletes the accent; a search field is the one
103
+ * place where losing that distinction is the point.
104
+ *
105
+ * A `Date` is deliberately *not* formatted here. What a reader sees in the cell
106
+ * came out of the caller's `render`, and guessing a format the search would
107
+ * agree with is how a table ends up not finding a date that is on the screen —
108
+ * a column that wants its dates searchable gives them a `value`.
109
+ */
110
+ export declare function searchText(value: unknown): string;
111
+ /** Where one page starts and stops, and how many there are. */
112
+ export interface PageBounds {
113
+ /** How many pages the rows come to. Never below `1`. */
114
+ pages: number;
115
+ /** The page actually shown, clamped into range. */
116
+ page: number;
117
+ /** Index of the first row on it. */
118
+ start: number;
119
+ /** One past the last. */
120
+ end: number;
121
+ }
122
+ /**
123
+ * The page arithmetic, with the clamp in it.
124
+ *
125
+ * The clamp is the whole reason this is a function: a filter that cuts a
126
+ * twenty-page table to three leaves the caller's `page` at 14, and a table that
127
+ * answers with an empty screen has told the reader their search found nothing.
128
+ * The page moves to the last one that exists instead.
129
+ */
130
+ export declare function pageBounds(total: number, page: number, pageSize: number): PageBounds;
131
+ /** Which rows are rendered, and how much empty space stands in for the rest. */
132
+ export interface VirtualWindow {
133
+ /** Index of the first row rendered. */
134
+ start: number;
135
+ /** One past the last. */
136
+ end: number;
137
+ /** The height of the spacer row above, in pixels. */
138
+ before: number;
139
+ /** And of the one below. */
140
+ after: number;
141
+ }
142
+ /**
143
+ * The rows a scroll offset puts on screen, plus a margin either side.
144
+ *
145
+ * Two spacer rows rather than a translated `<tbody>`: a transform on the body
146
+ * of a table breaks `position: sticky` on the header above it, and the library
147
+ * has a rule about transforms anyway. Padding is also what keeps the scrollbar
148
+ * honest — the scroll height is the real one, so dragging the thumb to the
149
+ * middle lands in the middle of the data.
150
+ *
151
+ * `viewport` of `0` is the first render, before anything has been measured. It
152
+ * cannot mean "no rows" — the layout effect that measures runs after a render
153
+ * and there would be nothing to measure — so it renders a screen's worth on
154
+ * spec and corrects before the frame is painted.
155
+ */
156
+ export declare function virtualWindow(scrollTop: number, viewport: number, rowHeight: number, count: number, overscan: number): VirtualWindow;
157
+ /**
158
+ * Every key between two, inclusive, in the order the rows are currently in.
159
+ *
160
+ * "Currently" is the point: a Shift-click selects what the reader can see
161
+ * between the two rows they clicked, so the range is taken from the sorted,
162
+ * filtered order rather than from the order `items` arrived in. Which of the
163
+ * two keys is the anchor does not matter — dragging up selects the same rows as
164
+ * dragging down.
165
+ *
166
+ * A key that is no longer in the list (a row the current filter hides) yields
167
+ * an empty range rather than a range from one end, which is what an index of
168
+ * `-1` would silently produce.
169
+ */
170
+ export declare function keysBetween(order: readonly string[], from: string, to: string): string[];
@@ -0,0 +1 @@
1
+ export const dataRowHeights={default:{xs:24,sm:28,md:32,lg:38,xl:44},compact:{xs:20,sm:24,md:28,lg:32,xl:38}};export const dataHeaderHeights={default:{xs:28,sm:32,md:36,lg:42,xl:48},compact:{xs:24,sm:28,md:32,lg:36,xl:42}};export const dataTickWidths={xs:30,sm:34,md:38,lg:42,xl:50};export const defaultColumnWidth=160;export const minColumnWidth=48;export function compareValues(t,e,n){const o=null==t||""===t,r=null==e||""===e;return o||r?o&&r?0:o?1:-1:"number"==typeof t&&"number"==typeof e?Number.isNaN(t)||Number.isNaN(e)?Number.isNaN(t)&&Number.isNaN(e)?0:Number.isNaN(t)?1:-1:t-e:"boolean"==typeof t&&"boolean"==typeof e?Number(t)-Number(e):t instanceof Date&&e instanceof Date?t.getTime()-e.getTime():n.compare(String(t),String(e))}export function sortRows(t,e,n){if(0===e.length)return t;const o=e.map(t=>{const e=n(t.key);return e?{compare:e,sign:"desc"===t.direction?-1:1}:null}).filter(t=>null!==t);return 0===o.length?t:[...t].sort((t,e)=>{for(const n of o){const o=n.compare(t,e);if(0!==o)return o*n.sign}return 0})}export function nextSort(t,e,n){const o=t.find(t=>t.key===e),r=void 0===o?"asc":"asc"===o.direction?"desc":null;return n?null===r?t.filter(t=>t.key!==e):void 0===o?[...t,{key:e,direction:r}]:t.map(t=>t.key===e?{key:e,direction:r}:t):null===r?[]:[{key:e,direction:r}]}export function searchText(t){return null==t||"object"==typeof t?"":String(t).normalize("NFD").replace(/[\u0300-\u036f]/g,"").toLowerCase()}export function pageBounds(t,e,n){const o=Math.max(1,Math.floor(n)),r=Math.max(1,Math.ceil(t/o)),a=Math.min(Math.max(Math.floor(e),1),r),i=(a-1)*o;return{pages:r,page:a,start:i,end:Math.min(i+o,t)}}export function virtualWindow(t,e,n,o,r){const a=e>0?Math.ceil(e/n):3*r,i=Math.max(0,Math.floor(t/n)-r),s=Math.min(o,i+a+2*r);return{start:i,end:s,before:i*n,after:Math.max(0,(o-s)*n)}}export function keysBetween(t,e,n){const o=t.indexOf(e),r=t.indexOf(n);return-1===o||-1===r?[]:t.slice(Math.min(o,r),Math.max(o,r)+1)}
@@ -109,8 +109,6 @@ export declare function mergeDateAndTime(date: Date, time: Date): Date;
109
109
  export declare function calendarWeeks(month: Date, weekStartsOn: NebaWeekday): Date[][];
110
110
  /** The first year on the page a given year falls on. 2026 → 2016 at 12 a page. */
111
111
  export declare function yearPageStart(year: number): number;
112
- /** A memoised `Intl.DateTimeFormat`. `undefined` locale means the runtime's own. */
113
- export declare function dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat;
114
112
  /** Formats a date, tolerating the `null` a cleared picker holds. */
115
113
  export declare function formatDate(date: Date | null | undefined, locale: string | undefined, options: Intl.DateTimeFormatOptions): string;
116
114
  /**