@scalewing/react 1.37.0 → 1.39.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 (117) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +91 -1
  3. package/dist/accessible-name.d.ts +13 -0
  4. package/dist/accessible-name.js +1 -0
  5. package/dist/chart-axis.d.ts +61 -0
  6. package/dist/chart-axis.js +83 -0
  7. package/dist/chart-dates.d.ts +6 -0
  8. package/dist/chart-dates.js +15 -0
  9. package/dist/chart-metrics.d.ts +40 -0
  10. package/dist/chart-metrics.js +48 -0
  11. package/dist/chart-scale.d.ts +48 -0
  12. package/dist/chart-scale.js +116 -0
  13. package/dist/chart-table-rows.d.ts +8 -0
  14. package/dist/chart-table-rows.js +1 -0
  15. package/dist/column-chart-bars.d.ts +38 -0
  16. package/dist/column-chart-bars.js +82 -0
  17. package/dist/column-chart-callouts.d.ts +48 -0
  18. package/dist/column-chart-callouts.js +54 -0
  19. package/dist/column-chart-input.d.ts +40 -0
  20. package/dist/column-chart-input.js +60 -0
  21. package/dist/column-chart-layout.d.ts +63 -0
  22. package/dist/column-chart-layout.js +97 -0
  23. package/dist/components/BarChart.d.ts +1 -1
  24. package/dist/components/BarChart.js +1 -1
  25. package/dist/components/ChartLegend.d.ts +38 -0
  26. package/dist/components/ChartLegend.js +32 -0
  27. package/dist/components/ColumnChart.d.ts +72 -0
  28. package/dist/components/ColumnChart.js +71 -0
  29. package/dist/components/DataBar.d.ts +48 -0
  30. package/dist/components/DataBar.js +28 -0
  31. package/dist/components/LineChart.d.ts +83 -0
  32. package/dist/components/LineChart.js +77 -0
  33. package/dist/components/Pagination.d.ts +34 -0
  34. package/dist/components/Pagination.js +27 -0
  35. package/dist/components/ScrollRegion.d.ts +9 -5
  36. package/dist/components/ScrollRegion.js +8 -6
  37. package/dist/components/SectionNav.d.ts +2 -8
  38. package/dist/components/SegmentedControl.d.ts +2 -8
  39. package/dist/components/Table.d.ts +48 -4
  40. package/dist/components/Table.js +9 -3
  41. package/dist/components/Tabs.d.ts +2 -9
  42. package/dist/components/chart/ChartTable.d.ts +16 -0
  43. package/dist/components/chart/ChartTable.js +12 -0
  44. package/dist/components/chart/ChartValueAxis.d.ts +14 -0
  45. package/dist/components/chart/ChartValueAxis.js +11 -0
  46. package/dist/components/chart/use-chart-width.d.ts +8 -0
  47. package/dist/components/chart/use-chart-width.js +34 -0
  48. package/dist/components/column-chart/ChartCalloutList.d.ts +16 -0
  49. package/dist/components/column-chart/ChartCalloutList.js +13 -0
  50. package/dist/components/column-chart/ColumnChartDrawing.d.ts +17 -0
  51. package/dist/components/column-chart/ColumnChartDrawing.js +18 -0
  52. package/dist/components/line-chart/LineChartDrawing.d.ts +17 -0
  53. package/dist/components/line-chart/LineChartDrawing.js +16 -0
  54. package/dist/components/table/TableRow.d.ts +8 -2
  55. package/dist/components/table/TableRow.js +1 -1
  56. package/dist/components/table/table-max-height.d.ts +20 -0
  57. package/dist/components/table/table-max-height.js +23 -0
  58. package/dist/components/use-scroll-overflow.d.ts +10 -8
  59. package/dist/components/use-scroll-overflow.js +23 -10
  60. package/dist/css/chart-marks.d.ts +10 -0
  61. package/dist/css/chart-marks.js +15 -0
  62. package/dist/css/chart-tones.d.ts +68 -0
  63. package/dist/css/chart-tones.js +130 -0
  64. package/dist/css/chevron.d.ts +6 -3
  65. package/dist/css/chevron.js +9 -4
  66. package/dist/css/{css-chart.d.ts → css-bar-chart.d.ts} +2 -2
  67. package/dist/css/{css-chart.js → css-bar-chart.js} +2 -2
  68. package/dist/css/css-chart-legend.d.ts +5 -0
  69. package/dist/css/css-chart-legend.js +102 -0
  70. package/dist/css/css-chart-plot.d.ts +2 -0
  71. package/dist/css/css-chart-plot.js +170 -0
  72. package/dist/css/css-components.js +20 -2
  73. package/dist/css/css-data-bar.d.ts +11 -0
  74. package/dist/css/css-data-bar.js +91 -0
  75. package/dist/css/css-line-chart.d.ts +2 -0
  76. package/dist/css/css-line-chart.js +31 -0
  77. package/dist/css/css-pagination.d.ts +2 -0
  78. package/dist/css/css-pagination.js +63 -0
  79. package/dist/css/css-scroll-region.d.ts +1 -0
  80. package/dist/css/css-scroll-region.js +10 -5
  81. package/dist/css/css-table-bounded-edges.d.ts +1 -0
  82. package/dist/css/css-table-bounded-edges.js +132 -0
  83. package/dist/css/css-table-bounded.d.ts +2 -0
  84. package/dist/css/css-table-bounded.js +73 -0
  85. package/dist/css/css-table-ledger.d.ts +5 -2
  86. package/dist/css/css-table-ledger.js +5 -2
  87. package/dist/css/css-table-metrics.d.ts +14 -0
  88. package/dist/css/css-table-metrics.js +13 -0
  89. package/dist/css/css-table-sort.js +2 -1
  90. package/dist/css/css-table-total-row.d.ts +2 -0
  91. package/dist/css/css-table-total-row.js +29 -0
  92. package/dist/css/css-table.js +14 -3
  93. package/dist/css/stylesheet.js +14 -2
  94. package/dist/data-bar-geometry.d.ts +24 -0
  95. package/dist/data-bar-geometry.js +41 -0
  96. package/dist/index.d.ts +6 -1
  97. package/dist/index.js +5 -0
  98. package/dist/is-one-of.d.ts +5 -0
  99. package/dist/is-one-of.js +7 -0
  100. package/dist/line-chart-input.d.ts +39 -0
  101. package/dist/line-chart-input.js +70 -0
  102. package/dist/line-chart-layout.d.ts +68 -0
  103. package/dist/line-chart-layout.js +99 -0
  104. package/dist/line-chart-markers.d.ts +29 -0
  105. package/dist/line-chart-markers.js +57 -0
  106. package/dist/line-chart-path.d.ts +18 -0
  107. package/dist/line-chart-path.js +33 -0
  108. package/dist/line-chart-points.d.ts +6 -0
  109. package/dist/line-chart-points.js +8 -0
  110. package/dist/line-chart-table.d.ts +15 -0
  111. package/dist/line-chart-table.js +29 -0
  112. package/dist/pagination-pages.d.ts +13 -0
  113. package/dist/pagination-pages.js +22 -0
  114. package/dist/scroll-overflow.d.ts +12 -0
  115. package/dist/scroll-overflow.js +31 -7
  116. package/dist/styles.css +442 -0
  117. package/package.json +1 -1
@@ -0,0 +1,68 @@
1
+ import { type ChartPlot, type ChartTick } from './chart-axis.js';
2
+ import { type PlacedMarker } from './line-chart-markers.js';
3
+ import { type ChartPoint, type LineChartCurve } from './line-chart-path.js';
4
+ export type LineChartLayoutInput = {
5
+ width: number;
6
+ /** The points as day counts, in date order. */
7
+ points: readonly {
8
+ day: number;
9
+ y: number;
10
+ }[];
11
+ curve: LineChartCurve;
12
+ area: boolean;
13
+ /** Each marker's day (one of the points') and words. */
14
+ markers: readonly {
15
+ day: number;
16
+ label: string;
17
+ }[];
18
+ /** The days to label on the time axis, and their words. */
19
+ ticks: readonly {
20
+ day: number;
21
+ text: string;
22
+ }[];
23
+ /** The boxed period's first and last days. */
24
+ highlight: {
25
+ from: number;
26
+ to: number;
27
+ } | null;
28
+ /** The words at the line's last value, or none. */
29
+ endLabel: string | null;
30
+ formatTick: (value: number) => string;
31
+ };
32
+ export type LineChartLayout = {
33
+ width: number;
34
+ height: number;
35
+ compact: boolean;
36
+ plot: ChartPlot;
37
+ ticks: ChartTick[];
38
+ path: ChartPoint[];
39
+ area: ChartPoint[];
40
+ last: ChartPoint | null;
41
+ end: {
42
+ x: number;
43
+ y: number;
44
+ text: string;
45
+ } | null;
46
+ dateLabels: {
47
+ day: number;
48
+ x: number;
49
+ y: number;
50
+ text: string;
51
+ }[];
52
+ highlight: {
53
+ x: number;
54
+ y: number;
55
+ width: number;
56
+ height: number;
57
+ } | null;
58
+ markers: PlacedMarker[];
59
+ };
60
+ /**
61
+ * Lays a line chart out at a width: its value axis on the left (through
62
+ * zero), the line through its points by date (straight or in steps), the
63
+ * area under it, its last value beside the plot's end, the time axis's
64
+ * labels (fewer when they would meet), the boxed period, and each marker's
65
+ * triangle and words. A compact chart (under `chartCompactWidth`) has a
66
+ * shorter plot.
67
+ */
68
+ export declare function lineChartLayout(input: LineChartLayoutInput): LineChartLayout;
@@ -0,0 +1,99 @@
1
+ import { endGutter, labelGutter, labelInside, periodBox, valueTicks, valueY, } from './chart-axis.js';
2
+ import { chartCompactWidth, chartGap, chartPlotHeight, chartTextWidth, chartType, } from './chart-metrics.js';
3
+ import { chartScale, extentThroughZero } from './chart-scale.js';
4
+ import { placeMarkers } from './line-chart-markers.js';
5
+ import { closingIndexes } from './line-chart-points.js';
6
+ import { areaPath, linePath, } from './line-chart-path.js';
7
+ /**
8
+ * The time axis's labels: each asked-for day at its place, kept inside the
9
+ * chart, and thinned from the last back so no two meet.
10
+ */
11
+ function dateLabels(ticks, x, width, y) {
12
+ const kept = [];
13
+ const sorted = [...ticks].sort((a, b) => b.day - a.day);
14
+ for (const tick of sorted) {
15
+ const centre = labelInside(x(tick.day), tick.text, width);
16
+ const half = chartTextWidth(tick.text) / 2;
17
+ const next = kept[0];
18
+ if (next &&
19
+ centre + half + chartGap * 2 > next.x - chartTextWidth(next.text) / 2)
20
+ continue;
21
+ kept.unshift({ day: tick.day, x: centre, y, text: tick.text });
22
+ }
23
+ return kept;
24
+ }
25
+ /**
26
+ * Lays a line chart out at a width: its value axis on the left (through
27
+ * zero), the line through its points by date (straight or in steps), the
28
+ * area under it, its last value beside the plot's end, the time axis's
29
+ * labels (fewer when they would meet), the boxed period, and each marker's
30
+ * triangle and words. A compact chart (under `chartCompactWidth`) has a
31
+ * shorter plot.
32
+ */
33
+ export function lineChartLayout(input) {
34
+ const { width, points } = input;
35
+ const compact = width < chartCompactWidth;
36
+ const scale = chartScale(extentThroughZero(points.map((p) => p.y)));
37
+ const left = labelGutter(scale.ticks.map(input.formatTick));
38
+ const right = Math.max(left + 1, width -
39
+ endGutter({ width, left, axisWords: [], endLabel: input.endLabel })
40
+ .gutter);
41
+ const top = chartGap + chartType.lineHeight / 2;
42
+ const plot = {
43
+ left,
44
+ right,
45
+ top,
46
+ bottom: top + chartPlotHeight(compact),
47
+ };
48
+ const first = points[0]?.day ?? 0;
49
+ const span = (points.at(-1)?.day ?? first) - first;
50
+ const dayWidth = span > 0 ? (right - left) / span : right - left;
51
+ const x = (day) => span > 0 ? left + (day - first) * dayWidth : (left + right) / 2;
52
+ const placed = points.map((p) => ({
53
+ x: x(p.day),
54
+ y: valueY(p.y, scale, plot),
55
+ }));
56
+ const path = linePath(placed, input.curve);
57
+ const last = placed.at(-1) ?? null;
58
+ // A marker sits on its day's last point, the day's closing value.
59
+ const closing = closingIndexes(points.map((p) => p.day));
60
+ const markerPoints = input.markers.map((marker) => ({
61
+ x: x(marker.day),
62
+ y: placed[closing.get(marker.day) ?? -1]?.y ?? plot.bottom,
63
+ label: marker.label,
64
+ }));
65
+ return {
66
+ width,
67
+ height: plot.bottom + chartGap + chartType.lineHeight,
68
+ compact,
69
+ plot,
70
+ ticks: valueTicks(scale, plot, input.formatTick),
71
+ path,
72
+ area: input.area ? areaPath(path, valueY(0, scale, plot)) : [],
73
+ last,
74
+ end: last && input.endLabel
75
+ ? { x: right + chartGap, y: last.y, text: input.endLabel }
76
+ : null,
77
+ dateLabels: dateLabels(input.ticks, x, width, plot.bottom + chartGap + chartType.fontSize),
78
+ highlight: points.length > 0
79
+ ? highlightBox(input.highlight, first, span, x, dayWidth, plot)
80
+ : null,
81
+ markers: placeMarkers(markerPoints, plot),
82
+ };
83
+ }
84
+ /**
85
+ * The boxed period: its days, half a day past each end, clipped to the
86
+ * plot. On a chart of one date it is the whole plot when the period holds
87
+ * that date and nothing otherwise.
88
+ */
89
+ function highlightBox(period, first, span, x, dayWidth, plot) {
90
+ if (!period)
91
+ return null;
92
+ if (span === 0 && (period.from > first || period.to < first))
93
+ return null;
94
+ const from = Math.max(x(period.from) - dayWidth / 2, plot.left);
95
+ const to = Math.min(x(period.to) + dayWidth / 2, plot.right);
96
+ if (to < from)
97
+ return null;
98
+ return periodBox(from, to - from, plot, 0);
99
+ }
@@ -0,0 +1,29 @@
1
+ import { type ChartPlot } from './chart-axis.js';
2
+ /**
3
+ * A marker on a line: a downward triangle at its date with its words,
4
+ * below the line where they fit inside the plot, above it otherwise. Its
5
+ * words are left out where they would meet an earlier marker's.
6
+ */
7
+ export type PlacedMarker = {
8
+ /** Its place in the chart's `markers`. */
9
+ order: number;
10
+ x: number;
11
+ /** The triangle's corners, as SVG points. */
12
+ triangle: string;
13
+ /** Its words' centre, moved in so they stay over the plot. */
14
+ labelX: number;
15
+ labelY: number;
16
+ label: string;
17
+ labelShown: boolean;
18
+ };
19
+ /**
20
+ * Places each marker at the line's height on its date: the triangle a
21
+ * small inset under the line, its words under it (moved in to stay over
22
+ * the plot), or both above the line when the words would pass the plot's
23
+ * bottom.
24
+ */
25
+ export declare function placeMarkers(markers: readonly {
26
+ x: number;
27
+ y: number;
28
+ label: string;
29
+ }[], plot: ChartPlot): PlacedMarker[];
@@ -0,0 +1,57 @@
1
+ import { chartInset, chartMarkerSize, chartTextWidth, chartType, } from './chart-metrics.js';
2
+ function meets(a, b) {
3
+ return (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom);
4
+ }
5
+ /**
6
+ * A centre for words `width` wide that keeps them over the plot, clear of
7
+ * the value axis's words and the last value beside it; centred on the plot
8
+ * when they are wider than it.
9
+ */
10
+ function overPlot(x, width, plot) {
11
+ const low = plot.left + width / 2;
12
+ const high = plot.right - width / 2;
13
+ if (low > high)
14
+ return (plot.left + plot.right) / 2;
15
+ return Math.min(Math.max(x, low), high);
16
+ }
17
+ /**
18
+ * Places each marker at the line's height on its date: the triangle a
19
+ * small inset under the line, its words under it (moved in to stay over
20
+ * the plot), or both above the line when the words would pass the plot's
21
+ * bottom.
22
+ */
23
+ export function placeMarkers(markers, plot) {
24
+ const half = chartMarkerSize / 2;
25
+ const shown = [];
26
+ return markers.map((marker, order) => {
27
+ const below = marker.y + chartInset + chartMarkerSize + chartType.lineHeight <=
28
+ plot.bottom;
29
+ const top = below
30
+ ? marker.y + chartInset
31
+ : marker.y - chartInset - chartMarkerSize;
32
+ const bottom = top + chartMarkerSize;
33
+ const labelY = below
34
+ ? bottom + chartType.fontSize
35
+ : top - (chartType.lineHeight - chartType.fontSize);
36
+ const width = chartTextWidth(marker.label);
37
+ const labelX = overPlot(marker.x, width, plot);
38
+ const box = {
39
+ left: labelX - width / 2,
40
+ right: labelX + width / 2,
41
+ top: labelY - chartType.fontSize,
42
+ bottom: labelY + (chartType.lineHeight - chartType.fontSize),
43
+ };
44
+ const labelShown = !shown.some((taken) => meets(taken, box));
45
+ if (labelShown)
46
+ shown.push(box);
47
+ return {
48
+ order,
49
+ x: marker.x,
50
+ labelX,
51
+ triangle: `${marker.x - half},${top} ${marker.x + half},${top} ${marker.x},${bottom}`,
52
+ labelY,
53
+ label: marker.label,
54
+ labelShown,
55
+ };
56
+ });
57
+ }
@@ -0,0 +1,18 @@
1
+ /** How a line runs between its points. */
2
+ export declare const lineChartCurves: readonly ["linear", "step"];
3
+ export type LineChartCurve = (typeof lineChartCurves)[number];
4
+ /** A point in the chart's pixels. */
5
+ export type ChartPoint = {
6
+ x: number;
7
+ y: number;
8
+ };
9
+ /**
10
+ * The vertices a line is drawn through. `linear` joins each point to the
11
+ * next; `step` holds each value until the next point's date, then moves
12
+ * straight up or down to it, as a balance that changes on a day does.
13
+ */
14
+ export declare function linePath(points: readonly ChartPoint[], curve: LineChartCurve): ChartPoint[];
15
+ /** The area under a line's path, closed along the zero line. */
16
+ export declare function areaPath(path: readonly ChartPoint[], zeroY: number): ChartPoint[];
17
+ /** Vertices as an SVG `points` list. */
18
+ export declare function svgPoints(path: readonly ChartPoint[]): string;
@@ -0,0 +1,33 @@
1
+ /** How a line runs between its points. */
2
+ export const lineChartCurves = ['linear', 'step'];
3
+ /**
4
+ * The vertices a line is drawn through. `linear` joins each point to the
5
+ * next; `step` holds each value until the next point's date, then moves
6
+ * straight up or down to it, as a balance that changes on a day does.
7
+ */
8
+ export function linePath(points, curve) {
9
+ if (curve === 'linear')
10
+ return [...points];
11
+ const path = [];
12
+ points.forEach((point, index) => {
13
+ const previous = points[index - 1];
14
+ if (previous)
15
+ path.push({ x: point.x, y: previous.y });
16
+ path.push(point);
17
+ });
18
+ return path;
19
+ }
20
+ /** The area under a line's path, closed along the zero line. */
21
+ export function areaPath(path, zeroY) {
22
+ if (path.length === 0)
23
+ return [];
24
+ return [
25
+ { x: path[0].x, y: zeroY },
26
+ ...path,
27
+ { x: path.at(-1).x, y: zeroY },
28
+ ];
29
+ }
30
+ /** Vertices as an SVG `points` list. */
31
+ export function svgPoints(path) {
32
+ return path.map((point) => `${point.x},${point.y}`).join(' ');
33
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Each date's closing point: the index of the last point on it. A balance
3
+ * can change twice on one day; the day's markers and figures belong to its
4
+ * closing value.
5
+ */
6
+ export declare function closingIndexes<Day>(days: readonly Day[]): Map<Day, number>;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Each date's closing point: the index of the last point on it. A balance
3
+ * can change twice on one day; the day's markers and figures belong to its
4
+ * closing value.
5
+ */
6
+ export function closingIndexes(days) {
7
+ return new Map(days.map((day, index) => [day, index]));
8
+ }
@@ -0,0 +1,15 @@
1
+ import { type ChartTableRow } from './chart-table-rows.js';
2
+ import { type LineChartMarker, type LineChartPeriod, type LineChartPoint } from './line-chart-input.js';
3
+ /**
4
+ * A line chart's hidden table rows: one per point, headed by its date in
5
+ * the consumer's words, holding its value and (when the chart has markers)
6
+ * the words of the markers on its date, in the date's closing row only;
7
+ * the rows inside the boxed period are current.
8
+ */
9
+ export declare function lineChartRows({ formatDate, formatValue, highlight, markers, points, }: {
10
+ points: readonly LineChartPoint[];
11
+ markers: readonly LineChartMarker[];
12
+ highlight: LineChartPeriod | undefined;
13
+ formatDate: (date: string) => string;
14
+ formatValue: (value: number) => string;
15
+ }): ChartTableRow[];
@@ -0,0 +1,29 @@
1
+ import { closingIndexes } from './line-chart-points.js';
2
+ /**
3
+ * A line chart's hidden table rows: one per point, headed by its date in
4
+ * the consumer's words, holding its value and (when the chart has markers)
5
+ * the words of the markers on its date, in the date's closing row only;
6
+ * the rows inside the boxed period are current.
7
+ */
8
+ export function lineChartRows({ formatDate, formatValue, highlight, markers, points, }) {
9
+ const closing = closingIndexes(points.map((point) => point.x));
10
+ return points.map((point, index) => {
11
+ const cells = [formatValue(point.y)];
12
+ if (markers.length > 0)
13
+ cells.push(closing.get(point.x) === index
14
+ ? markers
15
+ .filter((marker) => marker.x === point.x)
16
+ .map((marker) => marker.label)
17
+ .join(', ')
18
+ : '');
19
+ return {
20
+ key: `${point.x}-${index}`,
21
+ header: formatDate(point.x),
22
+ cells,
23
+ // YYYY-MM-DD dates compare in date order as text.
24
+ current: highlight !== undefined &&
25
+ point.x >= highlight.from &&
26
+ point.x <= highlight.to,
27
+ };
28
+ });
29
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Checks a pager's place: `pageCount` a whole number from 1 and `page` a
3
+ * whole number from 1 to `pageCount`. Pages count from 1, as people read
4
+ * them. Anything else throws a `RangeError`.
5
+ */
6
+ export declare function assertPaginationPages(page: number, pageCount: number): void;
7
+ /** The page a step goes to, or `null` at the end it cannot pass. */
8
+ export declare function paginationStep(page: number, pageCount: number, direction: 'previous' | 'next'): number | null;
9
+ /** The steps' names, one per step, each a word or two (not blank). */
10
+ export declare function assertPaginationLabels(labels: {
11
+ previous: string;
12
+ next: string;
13
+ }): void;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Checks a pager's place: `pageCount` a whole number from 1 and `page` a
3
+ * whole number from 1 to `pageCount`. Pages count from 1, as people read
4
+ * them. Anything else throws a `RangeError`.
5
+ */
6
+ export function assertPaginationPages(page, pageCount) {
7
+ if (!Number.isInteger(pageCount) || pageCount < 1)
8
+ throw new RangeError(`Pagination pageCount must be a whole number from 1: ${String(pageCount)}`);
9
+ if (!Number.isInteger(page) || page < 1 || page > pageCount)
10
+ throw new RangeError(`Pagination page must be a whole number from 1 to ${pageCount}: ${String(page)}`);
11
+ }
12
+ /** The page a step goes to, or `null` at the end it cannot pass. */
13
+ export function paginationStep(page, pageCount, direction) {
14
+ const target = direction === 'previous' ? page - 1 : page + 1;
15
+ return target < 1 || target > pageCount ? null : target;
16
+ }
17
+ /** The steps' names, one per step, each a word or two (not blank). */
18
+ export function assertPaginationLabels(labels) {
19
+ for (const key of ['previous', 'next'])
20
+ if (typeof labels?.[key] !== 'string' || labels[key].trim() === '')
21
+ throw new RangeError(`Pagination labels.${key} must be a word or two`);
22
+ }
@@ -3,7 +3,13 @@ export type ScrollOverflow = {
3
3
  start: boolean;
4
4
  end: boolean;
5
5
  };
6
+ /** Which block edges of a scroll box have content scrolled out past them. */
7
+ export type BlockScrollOverflow = {
8
+ top: boolean;
9
+ bottom: boolean;
10
+ };
6
11
  export declare const noScrollOverflow: ScrollOverflow;
12
+ export declare const noBlockScrollOverflow: BlockScrollOverflow;
7
13
  /**
8
14
  * Reads a box's horizontal overflow from its scroll metrics. `scrollLeft` is
9
15
  * 0 at the inline start and grows toward the end in a left-to-right box; in
@@ -11,4 +17,10 @@ export declare const noScrollOverflow: ScrollOverflow;
11
17
  * the distance travelled is its magnitude in both.
12
18
  */
13
19
  export declare function scrollOverflow(scrollLeft: number, scrollWidth: number, clientWidth: number): ScrollOverflow;
20
+ /**
21
+ * Reads a box's vertical overflow from its scroll metrics: `scrollTop` is 0
22
+ * at the top and grows toward the bottom.
23
+ */
24
+ export declare function blockScrollOverflow(scrollTop: number, scrollHeight: number, clientHeight: number): BlockScrollOverflow;
14
25
  export declare function sameScrollOverflow(a: ScrollOverflow, b: ScrollOverflow): boolean;
26
+ export declare function sameBlockScrollOverflow(a: BlockScrollOverflow, b: BlockScrollOverflow): boolean;
@@ -1,10 +1,25 @@
1
1
  export const noScrollOverflow = { start: false, end: false };
2
+ export const noBlockScrollOverflow = {
3
+ top: false,
4
+ bottom: false,
5
+ };
2
6
  /**
3
7
  * Sub-pixel layout can leave a box a fraction of a pixel wider than its
4
8
  * content, or a scroll position a fraction short of its end; within this
5
9
  * many pixels an edge counts as reached.
6
10
  */
7
11
  const edgeTolerance = 1;
12
+ /**
13
+ * Whether one axis has content past its leading and its trailing edge, or
14
+ * `null` when the content fits that axis. `travelled` is how far the box
15
+ * has scrolled from its leading edge.
16
+ */
17
+ function axisOverflow(travelled, scrollSize, clientSize) {
18
+ const hidden = scrollSize - clientSize;
19
+ if (hidden <= edgeTolerance)
20
+ return null;
21
+ return [travelled > edgeTolerance, travelled < hidden - edgeTolerance];
22
+ }
8
23
  /**
9
24
  * Reads a box's horizontal overflow from its scroll metrics. `scrollLeft` is
10
25
  * 0 at the inline start and grows toward the end in a left-to-right box; in
@@ -12,15 +27,24 @@ const edgeTolerance = 1;
12
27
  * the distance travelled is its magnitude in both.
13
28
  */
14
29
  export function scrollOverflow(scrollLeft, scrollWidth, clientWidth) {
15
- const hidden = scrollWidth - clientWidth;
16
- if (hidden <= edgeTolerance)
30
+ const edges = axisOverflow(Math.abs(scrollLeft), scrollWidth, clientWidth);
31
+ if (edges === null)
17
32
  return noScrollOverflow;
18
- const travelled = Math.abs(scrollLeft);
19
- return {
20
- start: travelled > edgeTolerance,
21
- end: travelled < hidden - edgeTolerance,
22
- };
33
+ return { start: edges[0], end: edges[1] };
34
+ }
35
+ /**
36
+ * Reads a box's vertical overflow from its scroll metrics: `scrollTop` is 0
37
+ * at the top and grows toward the bottom.
38
+ */
39
+ export function blockScrollOverflow(scrollTop, scrollHeight, clientHeight) {
40
+ const edges = axisOverflow(scrollTop, scrollHeight, clientHeight);
41
+ if (edges === null)
42
+ return noBlockScrollOverflow;
43
+ return { top: edges[0], bottom: edges[1] };
23
44
  }
24
45
  export function sameScrollOverflow(a, b) {
25
46
  return a.start === b.start && a.end === b.end;
26
47
  }
48
+ export function sameBlockScrollOverflow(a, b) {
49
+ return a.top === b.top && a.bottom === b.bottom;
50
+ }