panelui-native 0.49.0 → 0.53.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 (60) hide show
  1. package/README.md +9 -1
  2. package/lib/module/components/candlestick-chart/index.js +1161 -0
  3. package/lib/module/components/candlestick-chart/index.js.map +1 -0
  4. package/lib/module/components/combobox/index.js +73 -7
  5. package/lib/module/components/combobox/index.js.map +1 -1
  6. package/lib/module/components/context-menu/index.js +529 -0
  7. package/lib/module/components/context-menu/index.js.map +1 -0
  8. package/lib/module/components/menu/index.js +18 -10
  9. package/lib/module/components/menu/index.js.map +1 -1
  10. package/lib/module/components/popover/index.js +71 -7
  11. package/lib/module/components/popover/index.js.map +1 -1
  12. package/lib/module/components/sortable/index.js +942 -0
  13. package/lib/module/components/sortable/index.js.map +1 -0
  14. package/lib/module/components/swipe/index.js +140 -5
  15. package/lib/module/components/swipe/index.js.map +1 -1
  16. package/lib/module/components/tabs/index.js +56 -15
  17. package/lib/module/components/tabs/index.js.map +1 -1
  18. package/lib/module/components/time-picker/index.js +295 -31
  19. package/lib/module/components/time-picker/index.js.map +1 -1
  20. package/lib/module/icons/index.js +29 -0
  21. package/lib/module/icons/index.js.map +1 -1
  22. package/lib/module/index.js +5 -2
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/utils/haptics.js +19 -0
  25. package/lib/module/utils/haptics.js.map +1 -1
  26. package/lib/typescript/src/components/candlestick-chart/index.d.ts +278 -0
  27. package/lib/typescript/src/components/candlestick-chart/index.d.ts.map +1 -0
  28. package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
  29. package/lib/typescript/src/components/context-menu/index.d.ts +270 -0
  30. package/lib/typescript/src/components/context-menu/index.d.ts.map +1 -0
  31. package/lib/typescript/src/components/menu/index.d.ts +20 -20
  32. package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
  33. package/lib/typescript/src/components/popover/index.d.ts +51 -1
  34. package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
  35. package/lib/typescript/src/components/sortable/index.d.ts +248 -0
  36. package/lib/typescript/src/components/sortable/index.d.ts.map +1 -0
  37. package/lib/typescript/src/components/swipe/index.d.ts +35 -0
  38. package/lib/typescript/src/components/swipe/index.d.ts.map +1 -1
  39. package/lib/typescript/src/components/tabs/index.d.ts +34 -2
  40. package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
  41. package/lib/typescript/src/components/time-picker/index.d.ts.map +1 -1
  42. package/lib/typescript/src/icons/index.d.ts +8 -0
  43. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  44. package/lib/typescript/src/index.d.ts +5 -2
  45. package/lib/typescript/src/index.d.ts.map +1 -1
  46. package/lib/typescript/src/utils/haptics.d.ts +14 -0
  47. package/lib/typescript/src/utils/haptics.d.ts.map +1 -1
  48. package/package.json +1 -1
  49. package/src/components/candlestick-chart/index.tsx +1360 -0
  50. package/src/components/combobox/index.tsx +84 -6
  51. package/src/components/context-menu/index.tsx +658 -0
  52. package/src/components/menu/index.tsx +17 -10
  53. package/src/components/popover/index.tsx +94 -6
  54. package/src/components/sortable/index.tsx +1266 -0
  55. package/src/components/swipe/index.tsx +165 -3
  56. package/src/components/tabs/index.tsx +82 -16
  57. package/src/components/time-picker/index.tsx +330 -35
  58. package/src/icons/index.tsx +22 -0
  59. package/src/index.ts +39 -0
  60. package/src/utils/haptics.ts +21 -0
@@ -0,0 +1,1161 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * CandlestickChart — four numbers a period, drawn as one mark.
5
+ *
6
+ * Composed the same way every chart here is: the grid, the candles, the axes
7
+ * and the readout are separate children, so a chart that wants no grid simply
8
+ * does not have one.
9
+ *
10
+ * ```tsx
11
+ * <CandlestickChart data={session} xDataKey="day">
12
+ * <CandlestickChart.Grid />
13
+ * <CandlestickChart.Candles />
14
+ * <CandlestickChart.XAxis />
15
+ * <CandlestickChart.Tooltip />
16
+ * </CandlestickChart>
17
+ * ```
18
+ *
19
+ * ## One mark, four numbers
20
+ *
21
+ * A row is `{ open, high, low, close }`. The body spans open to close and is
22
+ * filled by direction — rising when the close is at or above the open, falling
23
+ * when it is below — and the wick spans low to high behind it. Colour is
24
+ * therefore the *sign* of the period rather than a series identity, which is
25
+ * why this chart has no series list and no legend: there is only ever one
26
+ * thing plotted, and the two colours are its two states.
27
+ *
28
+ * ## The baseline is not zero, and must not be
29
+ *
30
+ * A bar compares lengths, so its axis has to reach zero or twice as tall stops
31
+ * meaning twice as much. A candle compares nothing to zero. What is being read
32
+ * is the distance between four numbers that sit close together and far from the
33
+ * origin — a share at 180 that moved between 178 and 183 is a chart of that
34
+ * five-point span, and forcing zero onto the axis turns every candle in it into
35
+ * a dash. So the domain is derived from the lowest low and the highest high,
36
+ * with a margin so the extremes are not drawn on the frame.
37
+ *
38
+ * ## Two paths, not two per candle
39
+ *
40
+ * Every rising body is a subpath of one path and every falling body of another,
41
+ * and the wicks likewise — four animated props a frame whether the chart holds
42
+ * twenty candles or two hundred. A candle also cannot be drawn as a rectangle
43
+ * element and animated: the grow-in scales each body about its own middle, and
44
+ * that is a path rebuilt per frame on the UI thread rather than a transform
45
+ * that would take the wick and the neighbours with it.
46
+ */
47
+ import { Children, createContext, forwardRef, isValidElement, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
48
+ import { StyleSheet, View } from 'react-native';
49
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
50
+ import Animated, { Easing, runOnJS, useAnimatedProps, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming } from 'react-native-reanimated';
51
+ import Svg, { G, Line as SvgLine, Path } from 'react-native-svg';
52
+ import { useCSSVariable } from 'uniwind';
53
+ import { Text } from "../../primitives/text.js";
54
+ import { bandOf, compactNumber, yOf } from "../../utils/chart.js";
55
+ import { cn } from "../../utils/cn.js";
56
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
57
+ const AnimatedPath = Animated.createAnimatedComponent(Path);
58
+
59
+ /** Room left around the plot for the axis labels. */
60
+ const PADDING = {
61
+ top: 12,
62
+ right: 10,
63
+ bottom: 22,
64
+ left: 10
65
+ };
66
+
67
+ /** Width the readout is laid out at, so it can be clamped inside the plot. */
68
+ const LABEL_WIDTH = 148;
69
+
70
+ /** Left gutter reserved when a `YAxis` is present, for its labels to sit in. */
71
+ const Y_AXIS_WIDTH = 48;
72
+
73
+ /** Line height of an `xs` label, for centring one on the grid line it names. */
74
+ const AXIS_LABEL_HEIGHT = 16;
75
+
76
+ /**
77
+ * Closest two date labels may be placed, in points.
78
+ *
79
+ * Wider than the text needs, and that is the whole job of it. A candlestick
80
+ * chart holds far more periods than a bar chart holds categories — thirty
81
+ * sessions across a phone is eleven points each — so the axis is always
82
+ * thinning, and how much it thins is set here. Spacing them at the width of the
83
+ * text alone leaves every label touching its neighbour, which reads as one long
84
+ * smear rather than as a row of dates.
85
+ */
86
+ const MIN_BAND_LABEL = 68;
87
+
88
+ /**
89
+ * Width one date label is drawn in.
90
+ *
91
+ * Deliberately narrower than the spacing above, so there is always a gap
92
+ * between one label and the next. It is also what each is centred in, so a
93
+ * label sits over its own candle rather than over the slice it shares.
94
+ */
95
+ const LABEL_BOX = 52;
96
+
97
+ /** Thinnest a wick is drawn, in points. Below about this it stops being visible. */
98
+ const WICK_WIDTH = 1.5;
99
+
100
+ /**
101
+ * Shortest a body is drawn, in points.
102
+ *
103
+ * A period that opened and closed at the same number is the one case where the
104
+ * body has no height of its own, and it is not the same event as no period at
105
+ * all — a flat body says the price came back to where it started, which is
106
+ * worth seeing. It is also what a doji is.
107
+ */
108
+ const MIN_BODY_HEIGHT = 1.5;
109
+ /** Green and red before the stylesheet has been read. */
110
+ const FALLBACK_RISING = '#10b981';
111
+ const FALLBACK_FALLING = '#ef4444';
112
+
113
+ /**
114
+ * A candle's colour, which is its *direction* rather than a series identity.
115
+ *
116
+ * The chart-series tokens are wrong here on purpose. They are a palette meant
117
+ * to tell several things apart, and are picked to be distinguishable rather
118
+ * than to mean anything; up and down are not two series, they are two states of
119
+ * one, and the convention for them is older than any palette. So this reaches
120
+ * for the success and destructive tokens, which are the theme's green and red
121
+ * and already carry that sense everywhere else in the library.
122
+ */
123
+ function useDirectionColor(explicit, direction) {
124
+ const token = useCSSVariable(direction === 'rising' ? '--color-success' : '--color-destructive');
125
+ const fallback = direction === 'rising' ? FALLBACK_RISING : FALLBACK_FALLING;
126
+ return explicit ?? (typeof token === 'string' ? token : fallback);
127
+ }
128
+
129
+ /**
130
+ * One period. `open`, `high`, `low` and `close` are required to be numbers —
131
+ * unlike a line, a candle cannot be drawn from some of them.
132
+ */
133
+
134
+ const CandlestickChartContext = /*#__PURE__*/createContext(null);
135
+ function useChart(component) {
136
+ const context = useContext(CandlestickChartContext);
137
+ if (!context) {
138
+ throw new Error(`${component} must be used within a <CandlestickChart>`);
139
+ }
140
+ return context;
141
+ }
142
+
143
+ /**
144
+ * The candle under the finger, for something rendered *inside* the chart. A
145
+ * readout in the card's header is outside this provider — use
146
+ * `onActiveIndexChange` for that.
147
+ */
148
+ export function useCandlestickChart() {
149
+ const {
150
+ data,
151
+ activeIndexJS,
152
+ xDataKey
153
+ } = useChart('useCandlestickChart');
154
+ return {
155
+ activeIndex: activeIndexJS,
156
+ activePoint: activeIndexJS >= 0 ? data[activeIndexJS] ?? null : null,
157
+ xDataKey
158
+ };
159
+ }
160
+
161
+ /** Reads one row's four numbers, or `null` if any of them is missing. */
162
+ function ohlcOf(row, openKey, highKey, lowKey, closeKey) {
163
+ 'worklet';
164
+
165
+ if (!row) return null;
166
+ const open = row[openKey];
167
+ const high = row[highKey];
168
+ const low = row[lowKey];
169
+ const close = row[closeKey];
170
+ if (typeof open !== 'number' || typeof high !== 'number' || typeof low !== 'number' || typeof close !== 'number' || Number.isNaN(open) || Number.isNaN(high) || Number.isNaN(low) || Number.isNaN(close)) {
171
+ return null;
172
+ }
173
+ return {
174
+ open,
175
+ high,
176
+ low,
177
+ close
178
+ };
179
+ }
180
+
181
+ /** Imperative handle: re-run the grow-in, for a "replay" control. */
182
+
183
+ function partition(children) {
184
+ const svg = [];
185
+ const overlay = [];
186
+ const header = [];
187
+ Children.forEach(children, (child, index) => {
188
+ if (! /*#__PURE__*/isValidElement(child)) return;
189
+ const layer = child.type.layer ?? 'svg';
190
+ const slot = /*#__PURE__*/_jsx(ChildSlot, {
191
+ children: child
192
+ }, index);
193
+ (layer === 'header' ? header : layer === 'overlay' ? overlay : svg).push(slot);
194
+ });
195
+ return {
196
+ svg,
197
+ overlay,
198
+ header
199
+ };
200
+ }
201
+
202
+ /** Identity wrapper, purely so the partitioned arrays can carry keys. */
203
+ function ChildSlot({
204
+ children
205
+ }) {
206
+ return /*#__PURE__*/_jsx(_Fragment, {
207
+ children: children
208
+ });
209
+ }
210
+ const CandlestickChartRoot = /*#__PURE__*/forwardRef(function CandlestickChartRoot({
211
+ className,
212
+ data,
213
+ xDataKey = 'date',
214
+ openDataKey = 'open',
215
+ highDataKey = 'high',
216
+ lowDataKey = 'low',
217
+ closeDataKey = 'close',
218
+ status = 'ready',
219
+ aspectRatio = 1.6,
220
+ animationDuration = 1100,
221
+ domainDuration = 500,
222
+ yDomain,
223
+ candleGap = 0.3,
224
+ candleWidth,
225
+ fadedOpacity = 0.3,
226
+ onActiveIndexChange,
227
+ compact = false,
228
+ children,
229
+ ...props
230
+ }, ref) {
231
+ const [size, setSize] = useState({
232
+ width: 0,
233
+ height: 0
234
+ });
235
+ const [activeIndexJS, setActiveIndexJS] = useState(-1);
236
+ const reveal = useSharedValue(0);
237
+ const domainMin = useSharedValue(0);
238
+ const domainMax = useSharedValue(0);
239
+ const activeIndex = useSharedValue(-1);
240
+ const reducedMotion = useReducedMotion();
241
+
242
+ /*
243
+ * Whether an axis is asking for room. It has to be known before the plot is
244
+ * laid out, and only the root sees the children early enough to ask — the
245
+ * axis itself renders into a box that has already been decided.
246
+ */
247
+ const hasYAxis = useMemo(() => {
248
+ let found = false;
249
+ Children.forEach(children, child => {
250
+ if (/*#__PURE__*/isValidElement(child) && child.type.axis === 'y') {
251
+ found = true;
252
+ }
253
+ });
254
+ return found;
255
+ }, [children]);
256
+ const pad = compact ? {
257
+ top: 2,
258
+ right: 1,
259
+ bottom: 2,
260
+ left: 1
261
+ } : {
262
+ ...PADDING,
263
+ left: hasYAxis ? Y_AXIS_WIDTH : PADDING.left
264
+ };
265
+ const plot = {
266
+ left: pad.left,
267
+ top: pad.top,
268
+ width: Math.max(size.width - pad.left - pad.right, 0),
269
+ height: Math.max(size.height - pad.top - pad.bottom, 0)
270
+ };
271
+
272
+ /*
273
+ * The lowest low to the highest high, with a tenth of the span as margin at
274
+ * each end so the extremes are not drawn on the frame.
275
+ *
276
+ * Nothing here reaches for zero, and that is the point. A candle's four
277
+ * numbers sit close together and usually far from the origin; including
278
+ * zero would compress the whole chart into a band at the top and every
279
+ * candle in it into a dash.
280
+ */
281
+ const extent = useMemo(() => {
282
+ if (yDomain) return yDomain;
283
+ let min = Number.POSITIVE_INFINITY;
284
+ let max = Number.NEGATIVE_INFINITY;
285
+ for (const row of data) {
286
+ const ohlc = ohlcOf(row, openDataKey, highDataKey, lowDataKey, closeDataKey);
287
+ if (!ohlc) continue;
288
+ if (ohlc.low < min) min = ohlc.low;
289
+ if (ohlc.high > max) max = ohlc.high;
290
+ }
291
+ if (!Number.isFinite(min) || !Number.isFinite(max)) return [0, 1];
292
+ // A span of nothing — one period, or a price that never moved — still
293
+ // needs a domain to divide by, so it is given a little room either side.
294
+ if (min === max) {
295
+ const nudge = Math.abs(min) * 0.05 || 1;
296
+ return [min - nudge, max + nudge];
297
+ }
298
+ const margin = (max - min) * 0.1;
299
+ return [min - margin, max + margin];
300
+ }, [data, yDomain, openDataKey, highDataKey, lowDataKey, closeDataKey]);
301
+ const loading = status === 'loading';
302
+ useEffect(() => {
303
+ if (loading) return;
304
+ const [min, max] = extent;
305
+ const first = domainMin.value === 0 && domainMax.value === 0;
306
+ if (first || reducedMotion) {
307
+ domainMin.value = min;
308
+ domainMax.value = max;
309
+ return;
310
+ }
311
+ domainMin.value = withTiming(min, {
312
+ duration: domainDuration
313
+ });
314
+ domainMax.value = withTiming(max, {
315
+ duration: domainDuration
316
+ });
317
+ }, [extent, loading, reducedMotion, domainDuration, domainMin, domainMax]);
318
+ const revealed = useRef(false);
319
+ const playReveal = useMemo(() => () => {
320
+ if (reducedMotion) {
321
+ reveal.value = 1;
322
+ return;
323
+ }
324
+ reveal.value = 0;
325
+ reveal.value = withTiming(1, {
326
+ duration: animationDuration,
327
+ easing: Easing.bezier(0.85, 0, 0.15, 1)
328
+ });
329
+ }, [reducedMotion, animationDuration, reveal]);
330
+ useEffect(() => {
331
+ if (revealed.current || loading || plot.width <= 0 || !data.length) return;
332
+ revealed.current = true;
333
+ playReveal();
334
+ }, [loading, plot.width, data.length, playReveal]);
335
+ useImperativeHandle(ref, () => ({
336
+ replay: playReveal
337
+ }), [playReveal]);
338
+ const handleActiveIndex = useMemo(() => index => {
339
+ setActiveIndexJS(index);
340
+ onActiveIndexChange?.(index, index >= 0 ? data[index] ?? null : null);
341
+ }, [onActiveIndexChange, data]);
342
+ const onLayout = event => {
343
+ const {
344
+ width,
345
+ height
346
+ } = event.nativeEvent.layout;
347
+ setSize(current => Math.abs(current.width - width) < 1 && Math.abs(current.height - height) < 1 ? current : {
348
+ width,
349
+ height
350
+ });
351
+ props.onLayout?.(event);
352
+ };
353
+ const context = useMemo(() => ({
354
+ data,
355
+ xDataKey,
356
+ openKey: openDataKey,
357
+ highKey: highDataKey,
358
+ lowKey: lowDataKey,
359
+ closeKey: closeDataKey,
360
+ plot,
361
+ status,
362
+ candleGap,
363
+ candleWidth,
364
+ fadedOpacity,
365
+ domainMin,
366
+ domainMax,
367
+ extent,
368
+ reveal,
369
+ activeIndex,
370
+ activeIndexJS,
371
+ setActiveIndexJS: handleActiveIndex
372
+ }),
373
+ // `plot` is rebuilt every render from `size`, so it is compared by value.
374
+ // eslint-disable-next-line react-hooks/exhaustive-deps
375
+ [data, xDataKey, openDataKey, highDataKey, lowDataKey, closeDataKey, plot.width, plot.height, plot.left, plot.top, status, candleGap, candleWidth, fadedOpacity, domainMin, domainMax, extent, reveal, activeIndex, activeIndexJS, handleActiveIndex]);
376
+ const {
377
+ svg,
378
+ overlay,
379
+ header
380
+ } = partition(children);
381
+
382
+ /*
383
+ * Two views, because the header is not part of the plot. `aspectRatio` and
384
+ * the layout measurement belong to the drawing area alone — measured on the
385
+ * outer view they would take in the header too, and the plot would lose as
386
+ * much height as the readout took while still claiming the shape asked for.
387
+ */
388
+ return /*#__PURE__*/_jsx(CandlestickChartContext.Provider, {
389
+ value: context,
390
+ children: /*#__PURE__*/_jsxs(View, {
391
+ ...props,
392
+ style: props.style,
393
+ className: cn('w-full', className),
394
+ children: [header, /*#__PURE__*/_jsx(View, {
395
+ onLayout: onLayout,
396
+ style: {
397
+ aspectRatio
398
+ },
399
+ className: "w-full",
400
+ children: plot.width > 0 ? /*#__PURE__*/_jsxs(_Fragment, {
401
+ children: [/*#__PURE__*/_jsx(Svg, {
402
+ width: "100%",
403
+ height: "100%",
404
+ style: StyleSheet.absoluteFill,
405
+ children: svg
406
+ }), overlay]
407
+ }) : null
408
+ })]
409
+ })
410
+ });
411
+ });
412
+ CandlestickChartRoot.displayName = 'CandlestickChart';
413
+
414
+ /* -------------------------------------------------------------------------- */
415
+ /* SVG layer */
416
+ /* -------------------------------------------------------------------------- */
417
+
418
+ /**
419
+ * Lines across the price axis, so a candle can be read against a number rather
420
+ * than only against the candle beside it.
421
+ */
422
+ function CandlestickChartGrid({
423
+ rows = 4,
424
+ color,
425
+ dashArray = '4,6',
426
+ opacity = 1
427
+ }) {
428
+ const {
429
+ plot
430
+ } = useChart('CandlestickChart.Grid');
431
+ const token = useCSSVariable('--color-border');
432
+ const stroke = color ?? (typeof token === 'string' ? token : 'rgba(128,128,128,0.2)');
433
+ return /*#__PURE__*/_jsx(G, {
434
+ opacity: opacity,
435
+ children: Array.from({
436
+ length: rows + 1
437
+ }, (_unused, index) => {
438
+ const y = plot.top + plot.height / rows * index;
439
+ return /*#__PURE__*/_jsx(SvgLine, {
440
+ x1: plot.left,
441
+ x2: plot.left + plot.width,
442
+ y1: y,
443
+ y2: y,
444
+ stroke: stroke,
445
+ strokeWidth: 1,
446
+ strokeDasharray: dashArray
447
+ }, index);
448
+ })
449
+ });
450
+ }
451
+ CandlestickChartGrid.displayName = 'CandlestickChart.Grid';
452
+ CandlestickChartGrid.layer = 'svg';
453
+ /**
454
+ * The candles.
455
+ *
456
+ * Four paths, not four per candle: rising bodies, falling bodies, and the wicks
457
+ * behind each. A chart of two hundred periods is the same four animated props
458
+ * a frame as a chart of twenty, and the split by direction is what lets each
459
+ * half carry its own fill without a fill per mark.
460
+ *
461
+ * The wicks are drawn first so the bodies sit over them, which is what makes a
462
+ * body with a wick behind it read as one mark rather than as a line crossing a
463
+ * rectangle.
464
+ */
465
+ function CandlestickChartCandles({
466
+ risingColor,
467
+ fallingColor,
468
+ cornerRadius = 1.5
469
+ }) {
470
+ const {
471
+ data,
472
+ openKey,
473
+ highKey,
474
+ lowKey,
475
+ closeKey,
476
+ plot,
477
+ candleGap,
478
+ candleWidth,
479
+ fadedOpacity,
480
+ domainMin,
481
+ domainMax,
482
+ reveal,
483
+ activeIndex,
484
+ status
485
+ } = useChart('CandlestickChart.Candles');
486
+ const rising = useDirectionColor(risingColor, 'rising');
487
+ const falling = useDirectionColor(fallingColor, 'falling');
488
+ const total = data.length;
489
+ const slice = total > 0 ? plot.width / total : 0;
490
+ const width = Math.max(1, Math.min(candleWidth ?? slice * (1 - candleGap), Math.max(slice - 1, 1)));
491
+
492
+ /*
493
+ * Pulled out of the worklet as plain arrays. A worklet may close over numbers
494
+ * and arrays of them freely, but reading `data`'s rows — objects of mixed
495
+ * types, keyed by strings the caller chose — inside one every frame is the
496
+ * kind of work that belongs on the JS side once rather than on the UI thread
497
+ * sixty times a second.
498
+ */
499
+ const columns = useMemo(() => {
500
+ const opens = [];
501
+ const highs = [];
502
+ const lows = [];
503
+ const closes = [];
504
+ const usable = [];
505
+ for (const row of data) {
506
+ const ohlc = ohlcOf(row, openKey, highKey, lowKey, closeKey);
507
+ opens.push(ohlc?.open ?? 0);
508
+ highs.push(ohlc?.high ?? 0);
509
+ lows.push(ohlc?.low ?? 0);
510
+ closes.push(ohlc?.close ?? 0);
511
+ usable.push(ohlc !== null);
512
+ }
513
+ return {
514
+ opens,
515
+ highs,
516
+ lows,
517
+ closes,
518
+ usable
519
+ };
520
+ }, [data, openKey, highKey, lowKey, closeKey]);
521
+
522
+ /*
523
+ * The four builders share this. Declared as a worklet at this scope so each
524
+ * `useAnimatedProps` below can call it — a worklet may only call another
525
+ * worklet, and the rule is enforced by crashing rather than by warning.
526
+ */
527
+ const build = (wantRising, wicks) => () => {
528
+ 'worklet';
529
+
530
+ const min = domainMin.value;
531
+ const max = domainMax.value;
532
+ if (max === min || total === 0 || plot.width <= 0) return {
533
+ d: ''
534
+ };
535
+ const grow = reveal.value;
536
+ let path = '';
537
+ for (let index = 0; index < total; index += 1) {
538
+ if (!columns.usable[index]) continue;
539
+ const open = columns.opens[index] ?? 0;
540
+ const close = columns.closes[index] ?? 0;
541
+ const up = close >= open;
542
+ if (up !== wantRising) continue;
543
+
544
+ /*
545
+ * Staggered, so the chart fills in left to right rather than every
546
+ * candle inflating at once. Each one gets the back half of the run,
547
+ * which leaves the last candle finishing with the animation.
548
+ */
549
+ const start = total > 1 ? index / total * 0.45 : 0;
550
+ const eased = Math.max(0, Math.min(1, (grow - start) / 0.55));
551
+ if (eased <= 0) continue;
552
+ const centre = bandOf(index, total, plot);
553
+ const half = width / 2;
554
+ if (wicks) {
555
+ const high = columns.highs[index] ?? 0;
556
+ const low = columns.lows[index] ?? 0;
557
+ const yHigh = yOf(high, plot, min, max);
558
+ const yLow = yOf(low, plot, min, max);
559
+ // Grown about the body's middle rather than from the low, so the wick
560
+ // opens out in both directions the way the body does.
561
+ const mid = (yHigh + yLow) / 2;
562
+ const top = mid + (yHigh - mid) * eased;
563
+ const bottom = mid + (yLow - mid) * eased;
564
+ const x = centre - WICK_WIDTH / 2;
565
+ path += `M${x} ${top}h${WICK_WIDTH}V${bottom}h${-WICK_WIDTH}Z`;
566
+ continue;
567
+ }
568
+ const yOpen = yOf(open, plot, min, max);
569
+ const yClose = yOf(close, plot, min, max);
570
+ const bodyTop = Math.min(yOpen, yClose);
571
+ const bodyBottom = Math.max(yOpen, yClose);
572
+ const height = Math.max(bodyBottom - bodyTop, MIN_BODY_HEIGHT);
573
+ const mid = bodyTop + height / 2;
574
+ const grown = height * eased;
575
+ const top = mid - grown / 2;
576
+
577
+ // The radius can never exceed half the shortest side, or the two corners
578
+ // on one edge overlap and the arc turns itself inside out.
579
+ const r = Math.max(0, Math.min(cornerRadius, width / 2, grown / 2));
580
+ const x = centre - half;
581
+ if (r <= 0) {
582
+ path += `M${x} ${top}h${width}v${grown}h${-width}Z`;
583
+ } else {
584
+ path += `M${x + r} ${top}` + `h${width - r * 2}a${r} ${r} 0 0 1 ${r} ${r}` + `v${grown - r * 2}a${r} ${r} 0 0 1 ${-r} ${r}` + `h${-(width - r * 2)}a${r} ${r} 0 0 1 ${-r} ${-r}` + `v${-(grown - r * 2)}a${r} ${r} 0 0 1 ${r} ${-r}Z`;
585
+ }
586
+ }
587
+ return {
588
+ d: path
589
+ };
590
+ };
591
+
592
+ /*
593
+ * The builder is curried and its result handed straight to
594
+ * `useAnimatedProps`, rather than being called from inside another worklet.
595
+ * That is not a style choice: the hook works out what to re-run on by reading
596
+ * the closure of the worklet it is given, and a shared value read one level
597
+ * down is not in that closure. Wrapped, the paths were built once and then
598
+ * never again — the chart drew nothing until some unrelated re-render
599
+ * happened to rebuild them, which looked like a chart that only appeared once
600
+ * it was touched.
601
+ */
602
+ const risingBodies = useAnimatedProps(build(true, false));
603
+ const fallingBodies = useAnimatedProps(build(false, false));
604
+ const risingWicks = useAnimatedProps(build(true, true));
605
+ const fallingWicks = useAnimatedProps(build(false, true));
606
+
607
+ /*
608
+ * Dimming is a group opacity driven from the active index, rather than a
609
+ * second pair of paths for the candle under the finger. A candle is one mark
610
+ * of one colour, so there is nothing to keep at full ink separately — the
611
+ * whole group fades and the active candle is redrawn over it at full
612
+ * strength by `Highlight` below.
613
+ */
614
+ const dim = useAnimatedProps(() => ({
615
+ opacity: activeIndex.value >= 0 ? fadedOpacity : 1
616
+ }));
617
+ if (status === 'loading') return null;
618
+ return /*#__PURE__*/_jsxs(_Fragment, {
619
+ children: [/*#__PURE__*/_jsxs(AnimatedGroup, {
620
+ animatedProps: dim,
621
+ children: [/*#__PURE__*/_jsx(AnimatedPath, {
622
+ animatedProps: risingWicks,
623
+ fill: rising
624
+ }), /*#__PURE__*/_jsx(AnimatedPath, {
625
+ animatedProps: fallingWicks,
626
+ fill: falling
627
+ }), /*#__PURE__*/_jsx(AnimatedPath, {
628
+ animatedProps: risingBodies,
629
+ fill: rising
630
+ }), /*#__PURE__*/_jsx(AnimatedPath, {
631
+ animatedProps: fallingBodies,
632
+ fill: falling
633
+ })]
634
+ }), /*#__PURE__*/_jsx(CandlestickHighlight, {
635
+ rising: rising,
636
+ falling: falling,
637
+ width: width,
638
+ cornerRadius: cornerRadius,
639
+ columns: columns
640
+ })]
641
+ });
642
+ }
643
+ CandlestickChartCandles.displayName = 'CandlestickChart.Candles';
644
+ CandlestickChartCandles.layer = 'svg';
645
+ const AnimatedGroup = Animated.createAnimatedComponent(G);
646
+
647
+ /**
648
+ * The candle under the finger, drawn again at full strength over the dimmed
649
+ * rest.
650
+ *
651
+ * Redrawing one mark is cheaper than splitting every path in two and rebuilding
652
+ * both whenever the finger moves to the next candle, and it keeps the grow-in
653
+ * builder above to a single shape.
654
+ */
655
+ function CandlestickHighlight({
656
+ rising,
657
+ falling,
658
+ width,
659
+ cornerRadius,
660
+ columns
661
+ }) {
662
+ const {
663
+ data,
664
+ plot,
665
+ domainMin,
666
+ domainMax,
667
+ activeIndex
668
+ } = useChart('CandlestickChart.Candles');
669
+ const total = data.length;
670
+
671
+ /*
672
+ * The fill cannot be crossfaded between two colours on the UI thread, so both
673
+ * paths are drawn and the one that does not apply is emptied. An empty path
674
+ * costs nothing to render, and the swap never touches JS.
675
+ */
676
+ const build = wantRising => () => {
677
+ 'worklet';
678
+
679
+ const index = activeIndex.value;
680
+ const min = domainMin.value;
681
+ const max = domainMax.value;
682
+ if (index < 0 || index >= total || max === min || !columns.usable[index]) {
683
+ return {
684
+ d: ''
685
+ };
686
+ }
687
+ const open = columns.opens[index] ?? 0;
688
+ const close = columns.closes[index] ?? 0;
689
+ if (close >= open !== wantRising) return {
690
+ d: ''
691
+ };
692
+ const high = columns.highs[index] ?? 0;
693
+ const low = columns.lows[index] ?? 0;
694
+ const centre = bandOf(index, total, plot);
695
+ const yOpen = yOf(open, plot, min, max);
696
+ const yClose = yOf(close, plot, min, max);
697
+ const bodyTop = Math.min(yOpen, yClose);
698
+ const height = Math.max(Math.abs(yClose - yOpen), MIN_BODY_HEIGHT);
699
+ const x = centre - width / 2;
700
+ const r = Math.max(0, Math.min(cornerRadius, width / 2, height / 2));
701
+ const wickX = centre - WICK_WIDTH / 2;
702
+ const wick = `M${wickX} ${yOf(high, plot, min, max)}h${WICK_WIDTH}` + `V${yOf(low, plot, min, max)}h${-WICK_WIDTH}Z`;
703
+ const body = r <= 0 ? `M${x} ${bodyTop}h${width}v${height}h${-width}Z` : `M${x + r} ${bodyTop}` + `h${width - r * 2}a${r} ${r} 0 0 1 ${r} ${r}` + `v${height - r * 2}a${r} ${r} 0 0 1 ${-r} ${r}` + `h${-(width - r * 2)}a${r} ${r} 0 0 1 ${-r} ${-r}` + `v${-(height - r * 2)}a${r} ${r} 0 0 1 ${r} ${-r}Z`;
704
+ return {
705
+ d: `${wick}${body}`
706
+ };
707
+ };
708
+ const risingShape = useAnimatedProps(build(true));
709
+ const fallingShape = useAnimatedProps(build(false));
710
+ return /*#__PURE__*/_jsxs(_Fragment, {
711
+ children: [/*#__PURE__*/_jsx(AnimatedPath, {
712
+ animatedProps: risingShape,
713
+ fill: rising
714
+ }), /*#__PURE__*/_jsx(AnimatedPath, {
715
+ animatedProps: fallingShape,
716
+ fill: falling
717
+ })]
718
+ });
719
+ }
720
+
721
+ /* -------------------------------------------------------------------------- */
722
+ /* Overlay layer */
723
+ /* -------------------------------------------------------------------------- */
724
+
725
+ /**
726
+ * The period labels, one under each candle it has room for. Real text rather
727
+ * than SVG text, so they follow the theme's font and the platform's text
728
+ * scaling — SVG text does neither.
729
+ */
730
+ function CandlestickChartXAxis({
731
+ ticks,
732
+ format,
733
+ className
734
+ }) {
735
+ const {
736
+ data,
737
+ xDataKey,
738
+ plot
739
+ } = useChart('CandlestickChart.XAxis');
740
+ const labels = useMemo(() => {
741
+ if (!data.length) return [];
742
+
743
+ // The axis asks the plot how much room there is and only thins when the
744
+ // answer is not enough, rather than dropping labels that would have fitted.
745
+ const room = Math.max(1, Math.floor(plot.width / MIN_BAND_LABEL));
746
+ const count = Math.max(1, Math.min(ticks ?? room, data.length));
747
+
748
+ // Every nth period, rather than a fractional step rounded to the nearest
749
+ // index — rounding lands on the same one twice and skips its neighbour.
750
+ const stride = Math.ceil(data.length / count);
751
+ const picked = [];
752
+ for (let index = 0; index < data.length; index += stride) {
753
+ const datum = data[index];
754
+ if (!datum) continue;
755
+ picked.push({
756
+ key: index,
757
+ text: format ? format(datum, index) : String(datum[xDataKey] ?? '')
758
+ });
759
+ }
760
+ return picked;
761
+ }, [data, ticks, format, xDataKey, plot.width]);
762
+
763
+ /*
764
+ * Each label is boxed at a readable width and centred on its own candle, not
765
+ * at the width of the slice that candle occupies.
766
+ *
767
+ * A bar chart can tile its labels one band each, because a chart of eight
768
+ * months has bands wide enough to write a month in. Thirty sessions across a
769
+ * phone gives each one about eleven points, and a date boxed at eleven points
770
+ * is a date clipped to nothing — which is what was happening. The axis has
771
+ * already thinned the labels so the ones it kept are at least this far apart,
772
+ * so a box this wide cannot collide with its neighbour; it is clamped inside
773
+ * the plot so the first and last do not hang off the ends.
774
+ */
775
+ const boxes = useMemo(() => {
776
+ const half = LABEL_BOX / 2;
777
+ return labels.map(label => {
778
+ const centre = bandOf(label.key, data.length, plot);
779
+ const clamped = Math.min(plot.left + plot.width - half, Math.max(plot.left + half, centre));
780
+ return {
781
+ ...label,
782
+ left: clamped - half
783
+ };
784
+ });
785
+ }, [labels, data.length, plot]);
786
+ return /*#__PURE__*/_jsx(View, {
787
+ style: {
788
+ position: 'absolute',
789
+ inset: 0,
790
+ pointerEvents: 'none'
791
+ },
792
+ className: cn(className),
793
+ children: boxes.map(label => /*#__PURE__*/_jsx(Text, {
794
+ size: "xs",
795
+ muted: true,
796
+ numberOfLines: 1,
797
+ style: {
798
+ position: 'absolute',
799
+ bottom: 0,
800
+ left: label.left,
801
+ width: LABEL_BOX,
802
+ textAlign: 'center'
803
+ },
804
+ children: label.text
805
+ }, label.key))
806
+ });
807
+ }
808
+ CandlestickChartXAxis.displayName = 'CandlestickChart.XAxis';
809
+ CandlestickChartXAxis.layer = 'overlay';
810
+ /** Price labels down the side, aligned to the grid lines. */
811
+ function CandlestickChartYAxis({
812
+ ticks = 4,
813
+ format,
814
+ className
815
+ }) {
816
+ const {
817
+ plot,
818
+ extent
819
+ } = useChart('CandlestickChart.YAxis');
820
+
821
+ /*
822
+ * Read off the settled domain rather than the shared values the paths use.
823
+ * A label is text, and text is JS — following the tween would re-render on
824
+ * every frame of it to redraw a number nobody can read while it moves.
825
+ */
826
+ const labels = useMemo(() => {
827
+ const [min, max] = extent;
828
+ if (min === max) return [];
829
+ return Array.from({
830
+ length: ticks + 1
831
+ }, (_unused, index) => {
832
+ const value = max - (max - min) * index / ticks;
833
+ return {
834
+ key: index,
835
+ text: format ? format(value) : compactNumber(value)
836
+ };
837
+ });
838
+ }, [extent, ticks, format]);
839
+ return /*#__PURE__*/_jsx(View, {
840
+ pointerEvents: "none",
841
+ style: {
842
+ position: 'absolute',
843
+ left: 0,
844
+ // Each label is centred on the grid line it names: the strip is lifted
845
+ // half a label and grown by a whole one, so `justify-between` lands the
846
+ // text's middle on the line rather than its top edge on the first and
847
+ // its bottom edge on the last.
848
+ top: plot.top - AXIS_LABEL_HEIGHT / 2,
849
+ height: plot.height + AXIS_LABEL_HEIGHT,
850
+ width: Math.max(Y_AXIS_WIDTH - 6, 0),
851
+ justifyContent: 'space-between'
852
+ },
853
+ className: cn(className),
854
+ children: labels.map(label => /*#__PURE__*/_jsx(Text, {
855
+ size: "xs",
856
+ muted: true,
857
+ numberOfLines: 1,
858
+ className: "text-right",
859
+ children: label.text
860
+ }, label.key))
861
+ });
862
+ }
863
+ CandlestickChartYAxis.displayName = 'CandlestickChart.YAxis';
864
+ CandlestickChartYAxis.layer = 'overlay';
865
+ // Read by the root, which has to leave room for the labels before it lays the
866
+ // plot out — an axis drawn over the plot is unreadable, and makes what it is
867
+ // drawn over unreadable too.
868
+ CandlestickChartYAxis.axis = 'y';
869
+ /**
870
+ * The readout, and the gesture that drives it.
871
+ *
872
+ * There is no crosshair. A candle is already the thing being pointed at, so
873
+ * highlighting it and dimming the rest says the same thing without drawing a
874
+ * line through the chart.
875
+ *
876
+ * The hit area is the whole plot. A readout you have to land on a candle to
877
+ * summon is a readout nobody finds — and candles are thin.
878
+ */
879
+ function CandlestickChartTooltip({
880
+ formatValue,
881
+ formatX,
882
+ showChange = true,
883
+ className
884
+ }) {
885
+ const {
886
+ data,
887
+ xDataKey,
888
+ openKey,
889
+ highKey,
890
+ lowKey,
891
+ closeKey,
892
+ plot,
893
+ activeIndex,
894
+ activeIndexJS,
895
+ setActiveIndexJS,
896
+ status
897
+ } = useChart('CandlestickChart.Tooltip');
898
+ const total = data.length;
899
+ const left = plot.left;
900
+ const width = plot.width;
901
+
902
+ /*
903
+ * Declared inside the memo, next to its callers: a worklet may only call
904
+ * another worklet, and the rule is enforced by crashing rather than warning.
905
+ */
906
+ const pan = useMemo(() => {
907
+ const resolve = x => {
908
+ 'worklet';
909
+
910
+ if (!total) return;
911
+ const offset = (x - left) / (width || 1);
912
+ // Slices, not points: the finger is inside whichever one it lands on,
913
+ // which is a floor rather than a round to the nearest centre.
914
+ const next = Math.max(0, Math.min(total - 1, Math.floor(offset * total)));
915
+ if (next === activeIndex.value) return;
916
+ activeIndex.value = next;
917
+ runOnJS(setActiveIndexJS)(next);
918
+ };
919
+ return Gesture.Pan().minDistance(0).onBegin(event => {
920
+ 'worklet';
921
+
922
+ resolve(event.x);
923
+ }).onUpdate(event => {
924
+ 'worklet';
925
+
926
+ resolve(event.x);
927
+ }).onFinalize(() => {
928
+ 'worklet';
929
+
930
+ activeIndex.value = -1;
931
+ runOnJS(setActiveIndexJS)(-1);
932
+ });
933
+ }, [total, left, width, activeIndex, setActiveIndexJS]);
934
+
935
+ // The readout centres over its candle and is clamped inside the plot, so it
936
+ // never runs off the edge at the first or last one.
937
+ const labelStyle = useAnimatedStyle(() => {
938
+ const index = activeIndex.value;
939
+ if (index < 0 || !total) return {
940
+ opacity: 0
941
+ };
942
+ const slice = plot.width / total;
943
+ const centre = plot.left + slice * (index + 0.5);
944
+ const half = LABEL_WIDTH / 2;
945
+ const clamped = Math.min(plot.left + plot.width - half, Math.max(plot.left + half, centre));
946
+ return {
947
+ opacity: 1,
948
+ transform: [{
949
+ translateX: clamped - half
950
+ }]
951
+ };
952
+ });
953
+ const active = activeIndexJS >= 0 ? data[activeIndexJS] : null;
954
+ const ohlc = ohlcOf(active ?? undefined, openKey, highKey, lowKey, closeKey);
955
+ const fmt = formatValue ?? (value => compactNumber(value));
956
+ const fmtX = formatX ?? (datum => String(datum[xDataKey] ?? ''));
957
+ const rising = useDirectionColor(undefined, 'rising');
958
+ const falling = useDirectionColor(undefined, 'falling');
959
+ if (status === 'loading') return null;
960
+ const change = ohlc ? ohlc.close - ohlc.open : 0;
961
+ const percent = ohlc && ohlc.open !== 0 ? change / ohlc.open * 100 : 0;
962
+ return /*#__PURE__*/_jsx(GestureDetector, {
963
+ gesture: pan,
964
+ children: /*#__PURE__*/_jsx(View, {
965
+ style: StyleSheet.absoluteFill,
966
+ children: /*#__PURE__*/_jsx(Animated.View, {
967
+ pointerEvents: "none",
968
+ style: [{
969
+ position: 'absolute',
970
+ left: 0,
971
+ top: 0,
972
+ width: LABEL_WIDTH
973
+ }, labelStyle],
974
+ children: active && ohlc ? /*#__PURE__*/_jsxs(View, {
975
+ className: cn('gap-0.5 rounded-xl border border-border bg-popover px-2.5 py-1.5 shadow-lg', className),
976
+ children: [/*#__PURE__*/_jsx(Text, {
977
+ size: "xs",
978
+ muted: true,
979
+ numberOfLines: 1,
980
+ children: fmtX(active)
981
+ }), /*#__PURE__*/_jsxs(View, {
982
+ className: "flex-row gap-3",
983
+ children: [/*#__PURE__*/_jsxs(View, {
984
+ className: "flex-1 gap-0.5",
985
+ children: [/*#__PURE__*/_jsx(OhlcRow, {
986
+ label: "O",
987
+ value: fmt(ohlc.open, 'open')
988
+ }), /*#__PURE__*/_jsx(OhlcRow, {
989
+ label: "H",
990
+ value: fmt(ohlc.high, 'high')
991
+ })]
992
+ }), /*#__PURE__*/_jsxs(View, {
993
+ className: "flex-1 gap-0.5",
994
+ children: [/*#__PURE__*/_jsx(OhlcRow, {
995
+ label: "L",
996
+ value: fmt(ohlc.low, 'low')
997
+ }), /*#__PURE__*/_jsx(OhlcRow, {
998
+ label: "C",
999
+ value: fmt(ohlc.close, 'close')
1000
+ })]
1001
+ })]
1002
+ }), showChange ? /*#__PURE__*/_jsxs(Text, {
1003
+ size: "xs",
1004
+ weight: "medium",
1005
+ numberOfLines: 1,
1006
+ style: {
1007
+ color: change >= 0 ? rising : falling
1008
+ },
1009
+ children: [change >= 0 ? '+' : '', compactNumber(change), " (", percent >= 0 ? '+' : '', percent.toFixed(2), "%)"]
1010
+ }) : null]
1011
+ }) : null
1012
+ })
1013
+ })
1014
+ });
1015
+ }
1016
+ CandlestickChartTooltip.displayName = 'CandlestickChart.Tooltip';
1017
+ CandlestickChartTooltip.layer = 'overlay';
1018
+
1019
+ /** One of the four prices in the readout, named by its initial. */
1020
+ function OhlcRow({
1021
+ label,
1022
+ value
1023
+ }) {
1024
+ return /*#__PURE__*/_jsxs(View, {
1025
+ className: "flex-row items-baseline gap-1",
1026
+ children: [/*#__PURE__*/_jsx(Text, {
1027
+ size: "xs",
1028
+ muted: true,
1029
+ children: label
1030
+ }), /*#__PURE__*/_jsx(Text, {
1031
+ size: "xs",
1032
+ weight: "medium",
1033
+ numberOfLines: 1,
1034
+ children: value
1035
+ })]
1036
+ });
1037
+ }
1038
+
1039
+ /* -------------------------------------------------------------------------- */
1040
+ /* Header layer */
1041
+ /* -------------------------------------------------------------------------- */
1042
+
1043
+ /**
1044
+ * The strip above the plot: what the chart is of, what it currently reads, and
1045
+ * what the two colours mean.
1046
+ *
1047
+ * It belongs to the chart rather than to the card around it because it is about
1048
+ * the *plot* — the number changes as a finger moves along the candles. The
1049
+ * card's header is a caption on the tray the chart sits in; this is the chart
1050
+ * introducing itself.
1051
+ *
1052
+ * The value is not derived here. A readout that follows the finger belongs to
1053
+ * whoever owns the data — take it from `onActiveIndexChange` and pass the
1054
+ * formatted string down, so one header can show the latest close when nothing
1055
+ * is pressed and a period's close when something is.
1056
+ */
1057
+ function CandlestickChartHeader({
1058
+ className,
1059
+ title,
1060
+ value,
1061
+ caption,
1062
+ legend = false,
1063
+ risingLabel = 'Up',
1064
+ fallingLabel = 'Down',
1065
+ children,
1066
+ ...props
1067
+ }) {
1068
+ const rising = useDirectionColor(undefined, 'rising');
1069
+ const falling = useDirectionColor(undefined, 'falling');
1070
+ const trailing = children ?? (legend ? /*#__PURE__*/_jsxs(View, {
1071
+ className: "flex-row flex-wrap items-center justify-end gap-x-3 gap-y-1",
1072
+ children: [/*#__PURE__*/_jsx(SeriesSwatch, {
1073
+ color: rising,
1074
+ label: risingLabel
1075
+ }), /*#__PURE__*/_jsx(SeriesSwatch, {
1076
+ color: falling,
1077
+ label: fallingLabel
1078
+ })]
1079
+ }) : null);
1080
+ return /*#__PURE__*/_jsxs(View, {
1081
+ ...props,
1082
+ className: cn('flex-row items-start justify-between gap-3 pb-3', className),
1083
+ children: [/*#__PURE__*/_jsxs(View, {
1084
+ className: "flex-1 gap-0.5",
1085
+ children: [title ? /*#__PURE__*/_jsx(Text, {
1086
+ size: "xs",
1087
+ muted: true,
1088
+ children: title
1089
+ }) : null, value ? /*#__PURE__*/_jsx(Text, {
1090
+ size: "xl",
1091
+ weight: "bold",
1092
+ children: value
1093
+ }) : null, caption ? /*#__PURE__*/_jsx(Text, {
1094
+ size: "xs",
1095
+ muted: true,
1096
+ children: caption
1097
+ }) : null]
1098
+ }), trailing ? /*#__PURE__*/_jsx(View, {
1099
+ className: "shrink pt-1",
1100
+ children: trailing
1101
+ }) : null]
1102
+ });
1103
+ }
1104
+ CandlestickChartHeader.displayName = 'CandlestickChart.Header';
1105
+ CandlestickChartHeader.layer = 'header';
1106
+
1107
+ /** One colour and what it means. Shared by the legend and the header. */
1108
+ function SeriesSwatch({
1109
+ color,
1110
+ label
1111
+ }) {
1112
+ return /*#__PURE__*/_jsxs(View, {
1113
+ className: "flex-row items-center gap-1.5",
1114
+ children: [/*#__PURE__*/_jsx(View, {
1115
+ style: {
1116
+ backgroundColor: color
1117
+ },
1118
+ className: "h-2 w-2 rounded-full"
1119
+ }), /*#__PURE__*/_jsx(Text, {
1120
+ size: "xs",
1121
+ muted: true,
1122
+ children: label
1123
+ })]
1124
+ });
1125
+ }
1126
+ /** The two colours and what they mean, floated over the plot. */
1127
+ function CandlestickChartLegend({
1128
+ className,
1129
+ risingLabel = 'Up',
1130
+ fallingLabel = 'Down',
1131
+ ...props
1132
+ }) {
1133
+ const rising = useDirectionColor(undefined, 'rising');
1134
+ const falling = useDirectionColor(undefined, 'falling');
1135
+ return /*#__PURE__*/_jsxs(View, {
1136
+ ...props,
1137
+ style: [{
1138
+ pointerEvents: 'none'
1139
+ }, props.style],
1140
+ className: cn('absolute right-2 top-1 flex-row gap-3', className),
1141
+ children: [/*#__PURE__*/_jsx(SeriesSwatch, {
1142
+ color: rising,
1143
+ label: risingLabel
1144
+ }), /*#__PURE__*/_jsx(SeriesSwatch, {
1145
+ color: falling,
1146
+ label: fallingLabel
1147
+ })]
1148
+ });
1149
+ }
1150
+ CandlestickChartLegend.displayName = 'CandlestickChart.Legend';
1151
+ CandlestickChartLegend.layer = 'overlay';
1152
+ export const CandlestickChart = Object.assign(CandlestickChartRoot, {
1153
+ Header: CandlestickChartHeader,
1154
+ Grid: CandlestickChartGrid,
1155
+ Candles: CandlestickChartCandles,
1156
+ XAxis: CandlestickChartXAxis,
1157
+ YAxis: CandlestickChartYAxis,
1158
+ Tooltip: CandlestickChartTooltip,
1159
+ Legend: CandlestickChartLegend
1160
+ });
1161
+ //# sourceMappingURL=index.js.map