panelui-native 0.22.6 → 0.25.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 +1 -0
  2. package/lib/module/components/area-chart/index.js +826 -0
  3. package/lib/module/components/area-chart/index.js.map +1 -0
  4. package/lib/module/components/avatar/index.js +1 -1
  5. package/lib/module/components/avatar/index.js.map +1 -1
  6. package/lib/module/components/bar-chart/index.js +853 -0
  7. package/lib/module/components/bar-chart/index.js.map +1 -0
  8. package/lib/module/components/bottom-sheet/index.js +207 -23
  9. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  10. package/lib/module/components/breadcrumb/index.js +275 -0
  11. package/lib/module/components/breadcrumb/index.js.map +1 -0
  12. package/lib/module/components/chip/index.js +2 -2
  13. package/lib/module/components/direction/index.js +13 -19
  14. package/lib/module/components/direction/index.js.map +1 -1
  15. package/lib/module/components/frame/index.js +17 -0
  16. package/lib/module/components/frame/index.js.map +1 -1
  17. package/lib/module/components/heatmap-chart/index.js +28 -8
  18. package/lib/module/components/heatmap-chart/index.js.map +1 -1
  19. package/lib/module/components/input-group/index.js +2 -2
  20. package/lib/module/components/input-group/index.js.map +1 -1
  21. package/lib/module/components/line-chart/index.js +4 -146
  22. package/lib/module/components/line-chart/index.js.map +1 -1
  23. package/lib/module/components/loader/index.js +758 -0
  24. package/lib/module/components/loader/index.js.map +1 -0
  25. package/lib/module/components/map/basemap.js +265 -0
  26. package/lib/module/components/map/basemap.js.map +1 -0
  27. package/lib/module/components/map/index.js +850 -0
  28. package/lib/module/components/map/index.js.map +1 -0
  29. package/lib/module/components/map/maplibre.js +87 -0
  30. package/lib/module/components/map/maplibre.js.map +1 -0
  31. package/lib/module/components/message/index.js +8 -7
  32. package/lib/module/components/message/index.js.map +1 -1
  33. package/lib/module/components/otp-input/index.js +2 -2
  34. package/lib/module/components/progress/index.js +8 -2
  35. package/lib/module/components/progress/index.js.map +1 -1
  36. package/lib/module/components/ring-chart/index.js +423 -0
  37. package/lib/module/components/ring-chart/index.js.map +1 -0
  38. package/lib/module/components/section-rail/index.js +2 -2
  39. package/lib/module/components/separator/index.js +76 -22
  40. package/lib/module/components/separator/index.js.map +1 -1
  41. package/lib/module/components/shimmer/index.js +10 -1
  42. package/lib/module/components/shimmer/index.js.map +1 -1
  43. package/lib/module/components/slider/index.js +19 -5
  44. package/lib/module/components/slider/index.js.map +1 -1
  45. package/lib/module/components/steps/index.js +1 -1
  46. package/lib/module/components/switch/index.js +6 -1
  47. package/lib/module/components/switch/index.js.map +1 -1
  48. package/lib/module/components/typography/index.js +5 -0
  49. package/lib/module/components/typography/index.js.map +1 -1
  50. package/lib/module/hooks/use-direction.js +43 -0
  51. package/lib/module/hooks/use-direction.js.map +1 -0
  52. package/lib/module/icons/index.js +117 -2
  53. package/lib/module/icons/index.js.map +1 -1
  54. package/lib/module/index.js +8 -2
  55. package/lib/module/index.js.map +1 -1
  56. package/lib/module/primitives/text.js +30 -10
  57. package/lib/module/primitives/text.js.map +1 -1
  58. package/lib/module/utils/chart.js +279 -0
  59. package/lib/module/utils/chart.js.map +1 -0
  60. package/lib/typescript/src/components/area-chart/index.d.ts +197 -0
  61. package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -0
  62. package/lib/typescript/src/components/bar-chart/index.d.ts +228 -0
  63. package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -0
  64. package/lib/typescript/src/components/bottom-sheet/index.d.ts +75 -4
  65. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  66. package/lib/typescript/src/components/breadcrumb/index.d.ts +100 -0
  67. package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -0
  68. package/lib/typescript/src/components/direction/index.d.ts +11 -10
  69. package/lib/typescript/src/components/direction/index.d.ts.map +1 -1
  70. package/lib/typescript/src/components/frame/index.d.ts +17 -0
  71. package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
  72. package/lib/typescript/src/components/heatmap-chart/index.d.ts +22 -3
  73. package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
  74. package/lib/typescript/src/components/line-chart/index.d.ts +1 -0
  75. package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
  76. package/lib/typescript/src/components/loader/index.d.ts +38 -0
  77. package/lib/typescript/src/components/loader/index.d.ts.map +1 -0
  78. package/lib/typescript/src/components/map/basemap.d.ts +76 -0
  79. package/lib/typescript/src/components/map/basemap.d.ts.map +1 -0
  80. package/lib/typescript/src/components/map/index.d.ts +367 -0
  81. package/lib/typescript/src/components/map/index.d.ts.map +1 -0
  82. package/lib/typescript/src/components/map/maplibre.d.ts +193 -0
  83. package/lib/typescript/src/components/map/maplibre.d.ts.map +1 -0
  84. package/lib/typescript/src/components/message/index.d.ts.map +1 -1
  85. package/lib/typescript/src/components/progress/index.d.ts.map +1 -1
  86. package/lib/typescript/src/components/ring-chart/index.d.ts +156 -0
  87. package/lib/typescript/src/components/ring-chart/index.d.ts.map +1 -0
  88. package/lib/typescript/src/components/separator/index.d.ts +36 -1
  89. package/lib/typescript/src/components/separator/index.d.ts.map +1 -1
  90. package/lib/typescript/src/components/shimmer/index.d.ts.map +1 -1
  91. package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
  92. package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
  93. package/lib/typescript/src/components/typography/index.d.ts +6 -0
  94. package/lib/typescript/src/components/typography/index.d.ts.map +1 -1
  95. package/lib/typescript/src/hooks/use-direction.d.ts +20 -0
  96. package/lib/typescript/src/hooks/use-direction.d.ts.map +1 -0
  97. package/lib/typescript/src/icons/index.d.ts +5 -0
  98. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  99. package/lib/typescript/src/index.d.ts +9 -3
  100. package/lib/typescript/src/index.d.ts.map +1 -1
  101. package/lib/typescript/src/primitives/text.d.ts.map +1 -1
  102. package/lib/typescript/src/utils/chart.d.ts +96 -0
  103. package/lib/typescript/src/utils/chart.d.ts.map +1 -0
  104. package/package.json +9 -1
  105. package/src/components/area-chart/index.tsx +994 -0
  106. package/src/components/avatar/index.tsx +1 -1
  107. package/src/components/bar-chart/index.tsx +1048 -0
  108. package/src/components/bottom-sheet/index.tsx +280 -20
  109. package/src/components/breadcrumb/index.tsx +365 -0
  110. package/src/components/chip/index.tsx +2 -2
  111. package/src/components/direction/index.tsx +20 -21
  112. package/src/components/frame/index.tsx +17 -0
  113. package/src/components/heatmap-chart/index.tsx +58 -11
  114. package/src/components/input-group/index.tsx +2 -2
  115. package/src/components/line-chart/index.tsx +13 -181
  116. package/src/components/loader/index.tsx +812 -0
  117. package/src/components/map/basemap.ts +304 -0
  118. package/src/components/map/index.tsx +1118 -0
  119. package/src/components/map/maplibre.ts +201 -0
  120. package/src/components/message/index.tsx +8 -7
  121. package/src/components/otp-input/index.tsx +2 -2
  122. package/src/components/progress/index.tsx +10 -2
  123. package/src/components/ring-chart/index.tsx +543 -0
  124. package/src/components/section-rail/index.tsx +2 -2
  125. package/src/components/separator/index.tsx +77 -19
  126. package/src/components/shimmer/index.tsx +10 -1
  127. package/src/components/slider/index.tsx +20 -5
  128. package/src/components/steps/index.tsx +1 -1
  129. package/src/components/switch/index.tsx +6 -1
  130. package/src/components/typography/index.tsx +5 -0
  131. package/src/hooks/use-direction.ts +45 -0
  132. package/src/icons/index.tsx +66 -0
  133. package/src/index.ts +84 -0
  134. package/src/primitives/text.tsx +25 -7
  135. package/src/utils/chart.ts +355 -0
@@ -0,0 +1,355 @@
1
+ /**
2
+ * The maths every chart shares.
3
+ *
4
+ * Scales, monotone tangents and path building, in one place because four
5
+ * charts need them and a copy per chart is four chances for two charts to
6
+ * disagree about where a point goes. A line and an area drawn from different
7
+ * code do not lie on top of each other, and that is exactly the pair most
8
+ * often drawn together.
9
+ *
10
+ * Everything here is a worklet. The paths are rebuilt on the UI thread on
11
+ * every frame a domain is tweening, so none of it can be allowed to need the
12
+ * JS thread — which also rules out reaching for a charting dependency, since
13
+ * that is the one thing none of them are written to survive.
14
+ */
15
+
16
+ import { useCSSVariable } from 'uniwind';
17
+
18
+ /** Only reached if the theme CSS was never imported. */
19
+ const FALLBACK_SERIES = ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899'];
20
+
21
+ /** Which of the five chart tokens a series takes. */
22
+ export type SeriesColorIndex = 1 | 2 | 3 | 4 | 5;
23
+
24
+ /**
25
+ * A series' colour: an explicit one, else the `--color-chart-*` token.
26
+ *
27
+ * Shared so every chart draws its first series in the same colour. Two charts
28
+ * on one screen disagreeing about what "series one" looks like is the kind of
29
+ * thing nobody reports and everybody notices.
30
+ */
31
+ export function useSeriesColor(
32
+ explicit: string | undefined,
33
+ index: SeriesColorIndex
34
+ ): string {
35
+ const token = useCSSVariable(`--color-chart-${index}`);
36
+ return explicit ?? (typeof token === 'string' ? token : FALLBACK_SERIES[index - 1]!);
37
+ }
38
+
39
+ /** `12.4k` rather than `12400` — a readout has one line to say it in. */
40
+ export function compactNumber(value: number): string {
41
+ const abs = Math.abs(value);
42
+ if (abs >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
43
+ if (abs >= 1_000) return `${(value / 1_000).toFixed(1)}k`;
44
+ return Number.isInteger(value) ? String(value) : value.toFixed(1);
45
+ }
46
+
47
+ /** The drawable box inside a chart, after its padding is taken off. */
48
+ export interface Plot {
49
+ width: number;
50
+ height: number;
51
+ left: number;
52
+ top: number;
53
+ }
54
+
55
+ /** A point's x, spreading `total` of them evenly across the plot. */
56
+ export function xOf(index: number, total: number, plot: Plot): number {
57
+ 'worklet';
58
+ if (total <= 1) return plot.left + plot.width / 2;
59
+ return plot.left + (plot.width * index) / (total - 1);
60
+ }
61
+
62
+ /** A value's y, with `min` at the bottom of the plot and `max` at the top. */
63
+ export function yOf(value: number, plot: Plot, min: number, max: number): number {
64
+ 'worklet';
65
+ const span = max - min || 1;
66
+ return plot.top + plot.height - ((value - min) / span) * plot.height;
67
+ }
68
+
69
+ /** The centre of the `index`th of `total` bands, as used for bars and columns. */
70
+ export function bandOf(index: number, total: number, plot: Plot): number {
71
+ 'worklet';
72
+ if (total <= 0) return plot.left + plot.width / 2;
73
+ const width = plot.width / total;
74
+ return plot.left + width * (index + 0.5);
75
+ }
76
+
77
+ /**
78
+ * Monotone cubic tangents.
79
+ *
80
+ * Written out rather than taken from a charting dependency, because it is forty
81
+ * lines and it has to run inside a worklet.
82
+ *
83
+ * Monotone is the right default for a time series. A plain cubic spline
84
+ * overshoots between points, so a series that never goes below zero draws a dip
85
+ * under the axis between two low values — a shape that is not in the data. This
86
+ * one cannot, because the tangent at each point is clamped against the slopes
87
+ * either side of it, and a local peak or trough is given a flat one.
88
+ */
89
+ export function tangents(xs: number[], ys: number[]): number[] {
90
+ 'worklet';
91
+ const n = xs.length;
92
+ const slopes: number[] = [];
93
+ for (let i = 0; i < n - 1; i += 1) {
94
+ slopes.push((ys[i + 1]! - ys[i]!) / (xs[i + 1]! - xs[i]! || 1));
95
+ }
96
+
97
+ const result: number[] = new Array(n).fill(0);
98
+ result[0] = slopes[0] ?? 0;
99
+ result[n - 1] = slopes[n - 2] ?? 0;
100
+
101
+ for (let i = 1; i < n - 1; i += 1) {
102
+ const previous = slopes[i - 1]!;
103
+ const next = slopes[i]!;
104
+ result[i] = previous * next <= 0 ? 0 : (previous + next) / 2;
105
+ }
106
+
107
+ // Fritsch–Carlson: pull any tangent back inside the circle of radius 3, which
108
+ // is the condition for the segment to stay monotone.
109
+ for (let i = 0; i < n - 1; i += 1) {
110
+ const slope = slopes[i]!;
111
+ if (slope === 0) {
112
+ result[i] = 0;
113
+ result[i + 1] = 0;
114
+ continue;
115
+ }
116
+ const a = result[i]! / slope;
117
+ const b = result[i + 1]! / slope;
118
+ const magnitude = Math.sqrt(a * a + b * b);
119
+ if (magnitude > 3) {
120
+ result[i] = (3 / magnitude) * a * slope;
121
+ result[i + 1] = (3 / magnitude) * b * slope;
122
+ }
123
+ }
124
+
125
+ return result;
126
+ }
127
+
128
+ /** How a series is joined between its points. */
129
+ export type ChartCurve = 'monotone' | 'linear';
130
+
131
+ export interface ChartPoint {
132
+ x: number;
133
+ y: number;
134
+ }
135
+
136
+ /** Points, split at the gaps — a null breaks the series rather than crossing it. */
137
+ export function runsOf(
138
+ values: (number | null)[],
139
+ plot: Plot,
140
+ min: number,
141
+ max: number,
142
+ /** Baselines to stack each point on top of, for a stacked area. */
143
+ baselines?: number[]
144
+ ): ChartPoint[][] {
145
+ 'worklet';
146
+ const runs: ChartPoint[][] = [];
147
+ let run: ChartPoint[] = [];
148
+
149
+ for (let i = 0; i < values.length; i += 1) {
150
+ const value = values[i];
151
+ if (value === null || value === undefined) {
152
+ if (run.length) runs.push(run);
153
+ run = [];
154
+ continue;
155
+ }
156
+ const stacked = value + (baselines?.[i] ?? 0);
157
+ run.push({ x: xOf(i, values.length, plot), y: yOf(stacked, plot, min, max) });
158
+ }
159
+ if (run.length) runs.push(run);
160
+ return runs;
161
+ }
162
+
163
+ /** One unbroken run of points as a path, curved or straight. */
164
+ export function segment(points: ChartPoint[], curve: ChartCurve): string {
165
+ 'worklet';
166
+ if (!points.length) return '';
167
+ if (points.length < 3 || curve === 'linear') {
168
+ return points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`).join(' ');
169
+ }
170
+
171
+ const xs = points.map((p) => p.x);
172
+ const ys = points.map((p) => p.y);
173
+ const ms = tangents(xs, ys);
174
+
175
+ let d = `M${xs[0]},${ys[0]}`;
176
+ for (let i = 0; i < points.length - 1; i += 1) {
177
+ const dx = (xs[i + 1]! - xs[i]!) / 3;
178
+ d += ` C${xs[i]! + dx},${ys[i]! + ms[i]! * dx} ${xs[i + 1]! - dx},${ys[i + 1]! - ms[i + 1]! * dx} ${xs[i + 1]},${ys[i + 1]}`;
179
+ }
180
+ return d;
181
+ }
182
+
183
+ /** A series as a stroked line. Flat down the middle while it is still loading. */
184
+ export function linePath(
185
+ values: (number | null)[],
186
+ plot: Plot,
187
+ min: number,
188
+ max: number,
189
+ curve: ChartCurve,
190
+ loading: boolean,
191
+ baselines?: number[]
192
+ ): string {
193
+ 'worklet';
194
+ if (loading || plot.width <= 0) {
195
+ // Flat down the middle: the shape the skeleton holds, and the shape the
196
+ // real series grows out of once the data arrives.
197
+ const y = plot.top + plot.height / 2;
198
+ return `M${plot.left},${y} L${plot.left + plot.width},${y}`;
199
+ }
200
+ return runsOf(values, plot, min, max, baselines)
201
+ .map((run) => segment(run, curve))
202
+ .join(' ');
203
+ }
204
+
205
+ /**
206
+ * A series as a filled area.
207
+ *
208
+ * `floors` is what makes a stack: without it every run is closed against the
209
+ * bottom of the plot, and with it each run is closed against the series below,
210
+ * so the bands sit on each other rather than overlapping. The floor is walked
211
+ * backwards because the fill has to trace its underside right to left to close.
212
+ */
213
+ export function areaPath(
214
+ values: (number | null)[],
215
+ plot: Plot,
216
+ min: number,
217
+ max: number,
218
+ curve: ChartCurve,
219
+ loading: boolean,
220
+ baselines?: number[]
221
+ ): string {
222
+ 'worklet';
223
+ if (loading || plot.width <= 0) return '';
224
+
225
+ const bottom = plot.top + plot.height;
226
+ const runs = runsOf(values, plot, min, max, baselines);
227
+
228
+ if (!baselines) {
229
+ return runs
230
+ .map((run) => {
231
+ const top = segment(run, curve);
232
+ if (!top) return '';
233
+ const first = run[0]!;
234
+ const last = run[run.length - 1]!;
235
+ return `${top} L${last.x},${bottom} L${first.x},${bottom} Z`;
236
+ })
237
+ .join(' ');
238
+ }
239
+
240
+ // Stacked: the underside is the baseline itself, drawn as its own curve so
241
+ // the two edges of a band meet cleanly at both ends.
242
+ const floors = runsOf(
243
+ values.map((value) => (value === null || value === undefined ? null : 0)),
244
+ plot,
245
+ min,
246
+ max,
247
+ baselines
248
+ );
249
+
250
+ return runs
251
+ .map((run, index) => {
252
+ const top = segment(run, curve);
253
+ const floor = floors[index];
254
+ if (!top || !floor?.length) return '';
255
+ const under = segment([...floor].reverse(), curve).replace('M', 'L');
256
+ return `${top} ${under} Z`;
257
+ })
258
+ .join(' ');
259
+ }
260
+
261
+ /**
262
+ * A rounded rectangle, built as a path rather than drawn as a `Rect`.
263
+ *
264
+ * A bar is rounded on the end it grows towards and square on the end it grows
265
+ * from, so it reads as sitting on the axis rather than floating above it —
266
+ * which `rx` cannot express, since it rounds all four corners or none.
267
+ */
268
+ export function barPath(
269
+ x: number,
270
+ y: number,
271
+ width: number,
272
+ height: number,
273
+ radius: number,
274
+ /** Which end is the growing one. */
275
+ towards: 'up' | 'down' | 'left' | 'right'
276
+ ): string {
277
+ 'worklet';
278
+ if (width <= 0 || height <= 0) return '';
279
+ const r = Math.max(0, Math.min(radius, width / 2, height / 2));
280
+ const right = x + width;
281
+ const bottom = y + height;
282
+
283
+ if (towards === 'up') {
284
+ return (
285
+ `M${x},${bottom}L${x},${y + r}Q${x},${y} ${x + r},${y}` +
286
+ `L${right - r},${y}Q${right},${y} ${right},${y + r}L${right},${bottom}Z`
287
+ );
288
+ }
289
+ if (towards === 'down') {
290
+ return (
291
+ `M${x},${y}L${x},${bottom - r}Q${x},${bottom} ${x + r},${bottom}` +
292
+ `L${right - r},${bottom}Q${right},${bottom} ${right},${bottom - r}L${right},${y}Z`
293
+ );
294
+ }
295
+ if (towards === 'right') {
296
+ return (
297
+ `M${x},${y}L${right - r},${y}Q${right},${y} ${right},${y + r}` +
298
+ `L${right},${bottom - r}Q${right},${bottom} ${right - r},${bottom}L${x},${bottom}Z`
299
+ );
300
+ }
301
+ return (
302
+ `M${right},${y}L${x + r},${y}Q${x},${y} ${x},${y + r}` +
303
+ `L${x},${bottom - r}Q${x},${bottom} ${x + r},${bottom}L${right},${bottom}Z`
304
+ );
305
+ }
306
+
307
+ /**
308
+ * An arc of a circle, as a path.
309
+ *
310
+ * Angles are in turns from twelve o'clock, clockwise, because that is how a
311
+ * ring chart is described — "sixty percent of the way round" — and converting
312
+ * at every call site is where the sign errors live.
313
+ */
314
+ export function arcPath(
315
+ cx: number,
316
+ cy: number,
317
+ radius: number,
318
+ from: number,
319
+ to: number
320
+ ): string {
321
+ 'worklet';
322
+ const sweep = to - from;
323
+ if (radius <= 0 || sweep <= 0) return '';
324
+
325
+ // A full turn cannot be drawn as one arc — start and end are the same point,
326
+ // and the renderer draws nothing rather than everything. Two halves can.
327
+ if (sweep >= 1) {
328
+ const top = `${cx},${cy - radius}`;
329
+ const bottomPoint = `${cx},${cy + radius}`;
330
+ return (
331
+ `M${top}A${radius},${radius} 0 0 1 ${bottomPoint}` +
332
+ `A${radius},${radius} 0 0 1 ${top}`
333
+ );
334
+ }
335
+
336
+ const angle = (turn: number) => (turn - 0.25) * Math.PI * 2;
337
+ const x1 = cx + radius * Math.cos(angle(from));
338
+ const y1 = cy + radius * Math.sin(angle(from));
339
+ const x2 = cx + radius * Math.cos(angle(to));
340
+ const y2 = cy + radius * Math.sin(angle(to));
341
+
342
+ return `M${x1},${y1}A${radius},${radius} 0 ${sweep > 0.5 ? 1 : 0} 1 ${x2},${y2}`;
343
+ }
344
+
345
+ /** The numbers of one column, with anything unusable left as a gap. */
346
+ export function columnValues(
347
+ data: Record<string, unknown>[],
348
+ key: string
349
+ ): (number | null)[] {
350
+ 'worklet';
351
+ return data.map((row) => {
352
+ const value = row[key];
353
+ return typeof value === 'number' && !Number.isNaN(value) ? value : null;
354
+ });
355
+ }