react-native-vroom-chart 0.11.0 → 0.11.1

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.
@@ -187,6 +187,13 @@ typedef struct VroomDrawing {
187
187
  int32_t kind; // 0 = line, 1 = box, 2 = pencil, 3 = path
188
188
  const VroomDrawPoint* points; // pencil/path points (kind 2/3), else null
189
189
  int32_t point_count; // number of `points`, else 0
190
+ // Box (kind 1) interior fill, 0xAARRGGBB. Alpha 0 means unset, in which case
191
+ // the interior falls back to `color` at 10% alpha. Ignored by other kinds.
192
+ uint32_t fill;
193
+ // Protects the drawing from editing: hit_test reports its body (so a host
194
+ // can still select it and offer to unlock) but never its handles, and no
195
+ // handles are drawn. Rendering is otherwise unaffected.
196
+ bool locked;
190
197
  } VroomDrawing;
191
198
 
192
199
  // A resting-liquidity band: a price interval carrying a total order size on one
@@ -257,6 +264,14 @@ typedef struct VroomCoord {
257
264
  double price;
258
265
  } VroomCoord;
259
266
 
267
+ // An axis-aligned rectangle in CSS pixels, relative to the chart's top-left.
268
+ typedef struct VroomRect {
269
+ float x;
270
+ float y;
271
+ float width;
272
+ float height;
273
+ } VroomRect;
274
+
260
275
  // ---- Styling keys ---------------------------------------------------------
261
276
 
262
277
  typedef enum {
@@ -578,6 +593,18 @@ bool vroom_chart_hit_test_drawing(VroomChart* chart, float x_px, float y_px,
578
593
  int32_t* out_index, int32_t* out_part,
579
594
  float* out_t);
580
595
 
596
+ // Fills *out with the pixel bounding box of committed drawing `index` and
597
+ // returns true; false (out untouched) for an out-of-range index, an empty
598
+ // series, or a degenerate viewport.
599
+ //
600
+ // The rectangle spans the shape's painted extent — every anchor of a pencil or
601
+ // path, both corners of a line or box — grown by half the stroke width, so it
602
+ // covers the pixels the stroke actually paints. It is the anchor a host uses to
603
+ // position UI against the selection, and because it is computed from live core
604
+ // state it stays correct mid-reshape, before the edit reaches the host's array.
605
+ bool vroom_chart_drawing_bounds(VroomChart* chart, int32_t index,
606
+ VroomRect* out);
607
+
581
608
  // Selects a committed drawing (renders its handles). `index` -1 clears the
582
609
  // selection. `grabbed_endpoint` renders that handle 50% larger while it's being
583
610
  // dragged (0/1 for a line, 0 for the box corner the host normalized to, the
@@ -206,6 +206,8 @@ struct VroomChart {
206
206
  float width = 2.f;
207
207
  int32_t kind = 0;
208
208
  std::vector<VroomDrawPoint> points; // pencil path (kind 2)
209
+ uint32_t fill = 0; // 0 alpha = derive from color
210
+ bool locked = false;
209
211
  };
210
212
  std::vector<StoredDrawing> drawings;
211
213
 
@@ -926,6 +926,8 @@ extern "C" void vroom_chart_set_drawings(VroomChart* chart,
926
926
  d.color = src.color;
927
927
  d.width = src.width;
928
928
  d.kind = src.kind;
929
+ d.fill = src.fill;
930
+ d.locked = src.locked;
929
931
  if ((src.kind == 2 || src.kind == 3) && src.points && src.point_count > 0) {
930
932
  d.points.assign(src.points, src.points + src.point_count);
931
933
  // Keep a/b mirroring the path ends so bounds/handle code is uniform.
@@ -1152,6 +1154,31 @@ extern "C" bool vroom_chart_hit_test_drawing(VroomChart* chart, float x_px,
1152
1154
  return true;
1153
1155
  }
1154
1156
 
1157
+ extern "C" bool vroom_chart_drawing_bounds(VroomChart* chart, int32_t index,
1158
+ VroomRect* out) {
1159
+ if (!chart || !out || chart->candles.empty()) return false;
1160
+ const int64_t window_ms = chart->visible_end_ms - chart->visible_start_ms;
1161
+ if (window_ms <= 0) return false;
1162
+ const auto lay = chart->layout();
1163
+ // Same bounds as coord_at / draw_chart so the rectangle matches the render.
1164
+ const auto range = vroom::visible_indices(
1165
+ chart->candles.data(), chart->candles.size(),
1166
+ chart->visible_start_ms, chart->visible_end_ms);
1167
+ const size_t n = range.end - range.start;
1168
+ const auto bounds =
1169
+ chart->price_bounds_manual
1170
+ ? chart->price_bounds
1171
+ : vroom::auto_price_bounds(chart->candles.data() + range.start, n);
1172
+ vroom::drawing_bounds::RectPx r{};
1173
+ if (!vroom::drawings::bounds_of(*chart, lay, bounds, window_ms, index, &r))
1174
+ return false;
1175
+ out->x = r.x;
1176
+ out->y = r.y;
1177
+ out->width = r.width;
1178
+ out->height = r.height;
1179
+ return true;
1180
+ }
1181
+
1155
1182
  extern "C" bool vroom_chart_hit_test_price_line(VroomChart* chart, float x_px,
1156
1183
  float y_px, int32_t* out_index,
1157
1184
  int32_t* out_part) {
@@ -0,0 +1,70 @@
1
+ // The pixel bounding box of a drawing (see bounds_of in drawings.cpp).
2
+ //
3
+ // A host anchoring UI to a selected drawing needs one rectangle, whatever the
4
+ // shape: a box and a freehand stroke both have to yield something positionable.
5
+ // Reducing the projected anchors to their extent gives that uniformly, where the
6
+ // anchors themselves would not — a pencil stroke's `a`/`b` are just its first
7
+ // and last samples, which say nothing about how far the stroke wandered between
8
+ // them.
9
+ //
10
+ // The extent is grown by half the stroke width on every side, because a stroke
11
+ // straddles its path: a 2px line through y=100 paints 99..101. Without that the
12
+ // rectangle would clip the very pixels it is meant to describe.
13
+ //
14
+ // Skia-free and header-only so the unit tests can cover it; see
15
+ // tests/test_drawing_bounds.cpp.
16
+
17
+ #pragma once
18
+
19
+ #include <algorithm>
20
+ #include <cmath>
21
+
22
+ namespace vroom::drawing_bounds {
23
+
24
+ // An axis-aligned rectangle in CSS pixels, relative to the chart's top-left.
25
+ struct RectPx {
26
+ float x = 0.f;
27
+ float y = 0.f;
28
+ float width = 0.f;
29
+ float height = 0.f;
30
+ };
31
+
32
+ // Accumulates projected points into their extent. Points are fed one at a time
33
+ // so callers can project straight from their own storage without materializing
34
+ // an intermediate array.
35
+ struct Accumulator {
36
+ float min_x = 0.f;
37
+ float min_y = 0.f;
38
+ float max_x = 0.f;
39
+ float max_y = 0.f;
40
+ bool has = false;
41
+
42
+ // Non-finite coordinates are dropped rather than propagated: a single NaN
43
+ // would otherwise poison every comparison and hand the host an unusable
44
+ // rectangle.
45
+ void add(float x, float y) {
46
+ if (!std::isfinite(x) || !std::isfinite(y)) return;
47
+ if (!has) {
48
+ min_x = max_x = x;
49
+ min_y = max_y = y;
50
+ has = true;
51
+ return;
52
+ }
53
+ min_x = std::min(min_x, x);
54
+ max_x = std::max(max_x, x);
55
+ min_y = std::min(min_y, y);
56
+ max_y = std::max(max_y, y);
57
+ }
58
+
59
+ // The extent grown by half of `stroke_width` on each side. A single point
60
+ // yields a zero-extent rectangle inflated to the stroke's own footprint,
61
+ // which is exactly what a one-sample pencil stroke paints.
62
+ RectPx to_rect(float stroke_width) const {
63
+ if (!has) return RectPx{};
64
+ const float pad = std::max(stroke_width, 0.f) * 0.5f;
65
+ return RectPx{min_x - pad, min_y - pad, (max_x - min_x) + pad * 2.f,
66
+ (max_y - min_y) + pad * 2.f};
67
+ }
68
+ };
69
+
70
+ } // namespace vroom::drawing_bounds
@@ -51,17 +51,23 @@ SkRect box_rect(SkPoint a, SkPoint b) {
51
51
  std::max(a.fX, b.fX), std::max(a.fY, b.fY));
52
52
  }
53
53
 
54
- // Strokes the box outline and paints a faint fill (~10% of the border alpha) of
55
- // the same color, spanning opposite corners `a` and `b`.
56
- void draw_box(SkCanvas* canvas, SkPoint a, SkPoint b, SkColor color, float width) {
54
+ // Strokes the box outline and paints its interior, spanning opposite corners
55
+ // `a` and `b`. `fill_color` is used as given; alpha 0 means the caller didn't
56
+ // set one, and the interior falls back to a faint tint of the border (~10% of
57
+ // its alpha) — a solid default would hide the candles the box was drawn around.
58
+ void draw_box(SkCanvas* canvas, SkPoint a, SkPoint b, SkColor color, float width,
59
+ SkColor fill_color) {
57
60
  const SkRect r = box_rect(a, b);
58
61
 
59
62
  SkPaint fill;
60
63
  fill.setAntiAlias(true);
61
64
  fill.setStyle(SkPaint::kFill_Style);
62
- // Faint fill: 10% of the border's alpha, same RGB.
63
- const U8CPU fill_alpha = static_cast<U8CPU>(SkColorGetA(color) * 0.1f);
64
- fill.setColor(SkColorSetA(color, fill_alpha));
65
+ if (SkColorGetA(fill_color) != 0) {
66
+ fill.setColor(fill_color);
67
+ } else {
68
+ const U8CPU fill_alpha = static_cast<U8CPU>(SkColorGetA(color) * 0.1f);
69
+ fill.setColor(SkColorSetA(color, fill_alpha));
70
+ }
65
71
  canvas->drawRect(r, fill);
66
72
 
67
73
  SkPaint border;
@@ -334,7 +340,8 @@ void draw(SkCanvas* canvas,
334
340
  const SkPoint a = to_px(chart, lay, bounds, window_ms, d.a);
335
341
  const SkPoint b = to_px(chart, lay, bounds, window_ms, d.b);
336
342
  if (d.kind == 1) {
337
- draw_box(canvas, a, b, static_cast<SkColor>(d.color), d.width);
343
+ draw_box(canvas, a, b, static_cast<SkColor>(d.color), d.width,
344
+ static_cast<SkColor>(d.fill));
338
345
  continue;
339
346
  }
340
347
  SkPaint line;
@@ -348,9 +355,12 @@ void draw(SkCanvas* canvas,
348
355
  }
349
356
 
350
357
  // 1b. Handles on the selected committed drawing (unclipped, like the draft's
351
- // dots). The grabbed endpoint renders 50% larger.
358
+ // dots). The grabbed endpoint renders 50% larger. A locked drawing shows
359
+ // none: nothing about it can be dragged, so an affordance saying
360
+ // otherwise would be a lie.
352
361
  if (chart.selected_drawing >= 0 &&
353
- static_cast<size_t>(chart.selected_drawing) < chart.drawings.size()) {
362
+ static_cast<size_t>(chart.selected_drawing) < chart.drawings.size() &&
363
+ !chart.drawings[chart.selected_drawing].locked) {
354
364
  const auto& d = chart.drawings[chart.selected_drawing];
355
365
  const SkPoint sa = to_px(chart, lay, bounds, window_ms, d.a);
356
366
  const SkPoint sb = to_px(chart, lay, bounds, window_ms, d.b);
@@ -432,8 +442,10 @@ void draw(SkCanvas* canvas,
432
442
  canvas->save();
433
443
  canvas->clipRect(clip);
434
444
  if (chart.draft_kind == 1) {
445
+ // The draft has no fill of its own — the preview shows the default
446
+ // tint until the box is committed with one.
435
447
  draw_box(canvas, a, b, static_cast<SkColor>(chart.draft_color),
436
- chart.draft_width);
448
+ chart.draft_width, SK_ColorTRANSPARENT);
437
449
  } else {
438
450
  SkPaint guide;
439
451
  guide.setAntiAlias(true);
@@ -463,9 +475,13 @@ HitResult hit_test(const VroomChart& chart,
463
475
  if (window_ms <= 0 || chart.drawings.empty()) return miss;
464
476
 
465
477
  // Grab-priority: if a drawing is selected, its (visible) handles win first.
478
+ // A locked drawing draws no handles, so there is nothing here to grab — it
479
+ // falls through to the body pass and stays selectable, which is how a host
480
+ // toolbar can still reach it to unlock it.
466
481
  constexpr float kHandleHit = kNodeRingRadius + 6.f;
467
482
  if (chart.selected_drawing >= 0 &&
468
- static_cast<size_t>(chart.selected_drawing) < chart.drawings.size()) {
483
+ static_cast<size_t>(chart.selected_drawing) < chart.drawings.size() &&
484
+ !chart.drawings[chart.selected_drawing].locked) {
469
485
  const auto& d = chart.drawings[chart.selected_drawing];
470
486
  const SkPoint a = to_px(chart, lay, bounds, window_ms, d.a);
471
487
  const SkPoint b = to_px(chart, lay, bounds, window_ms, d.b);
@@ -546,4 +562,37 @@ HitResult hit_test(const VroomChart& chart,
546
562
  return best_i >= 0 ? HitResult{best_i, best_part, best_t} : miss;
547
563
  }
548
564
 
565
+ bool bounds_of(const VroomChart& chart,
566
+ const Layout& lay,
567
+ const PriceBounds& bounds,
568
+ int64_t window_ms,
569
+ int32_t index,
570
+ vroom::drawing_bounds::RectPx* out) {
571
+ if (!out || window_ms <= 0) return false;
572
+ if (index < 0 || static_cast<size_t>(index) >= chart.drawings.size())
573
+ return false;
574
+ const auto& d = chart.drawings[static_cast<size_t>(index)];
575
+
576
+ vroom::drawing_bounds::Accumulator acc;
577
+ if ((d.kind == 2 || d.kind == 3) && !d.points.empty()) {
578
+ // Pencil/path: every sample counts. `a`/`b` only mirror the first and
579
+ // last, so they would miss however far the stroke wandered between them.
580
+ for (const auto& p : d.points) {
581
+ const SkPoint q = to_px(chart, lay, bounds, window_ms, p);
582
+ acc.add(q.fX, q.fY);
583
+ }
584
+ } else {
585
+ // Line/box: the two stored corners already span the shape — a box's
586
+ // derived corners are exactly their extent.
587
+ const SkPoint a = to_px(chart, lay, bounds, window_ms, d.a);
588
+ const SkPoint b = to_px(chart, lay, bounds, window_ms, d.b);
589
+ acc.add(a.fX, a.fY);
590
+ acc.add(b.fX, b.fY);
591
+ }
592
+ if (!acc.has) return false;
593
+
594
+ *out = acc.to_rect(d.width > 0.f ? d.width : 2.f);
595
+ return true;
596
+ }
597
+
549
598
  } // namespace vroom::drawings
@@ -12,6 +12,7 @@
12
12
 
13
13
  #pragma once
14
14
 
15
+ #include "drawing_bounds.h"
15
16
  #include "viewport.h"
16
17
  #include "vroom/vroom_chart.h"
17
18
 
@@ -59,4 +60,18 @@ HitResult hit_test(const VroomChart& chart,
59
60
  float x,
60
61
  float y);
61
62
 
63
+ // The pixel bounding box of committed drawing `index`, written to `out`. False
64
+ // (leaving `out` untouched) for an out-of-range index or a degenerate viewport.
65
+ //
66
+ // The rectangle covers the shape's painted extent — every anchor of a pencil or
67
+ // path, both corners of a line or box — grown by half the stroke width. It is
68
+ // what the host positions a selection toolbar against, so it tracks the drawing
69
+ // live through a pan, a zoom, and a reshape in progress.
70
+ bool bounds_of(const VroomChart& chart,
71
+ const vroom::Layout& lay,
72
+ const vroom::PriceBounds& bounds,
73
+ int64_t window_ms,
74
+ int32_t index,
75
+ vroom::drawing_bounds::RectPx* out);
76
+
62
77
  } // namespace vroom::drawings
package/lib/index.d.mts CHANGED
@@ -177,6 +177,12 @@ type DrawingBase = {
177
177
  color?: VroomColor;
178
178
  /** Stroke width in px. Default 2. */
179
179
  width?: number;
180
+ /**
181
+ * Protect the drawing from editing. A locked drawing still renders and can
182
+ * still be selected — so a toolbar can offer to unlock it — but it can't be
183
+ * dragged, reshaped or deleted, and its grab handles aren't drawn. Web only.
184
+ */
185
+ locked?: boolean;
180
186
  };
181
187
  /** A two-point trendline from `points[0]` to `points[1]`. */
182
188
  type LineDrawing = DrawingBase & {
@@ -192,6 +198,15 @@ type BoxDrawing = DrawingBase & {
192
198
  type: 'box';
193
199
  /** Two opposite corners, in data space. */
194
200
  points: [DrawPoint, DrawPoint];
201
+ /**
202
+ * Interior fill, painted beneath the stroke. Omitted, the interior keeps its
203
+ * default tint of the stroke color at 10% alpha.
204
+ *
205
+ * A solid fill hides the candles the box was drawn around, so this usually
206
+ * wants an alpha — as the high byte of an 8-digit hex (`'#5400ce2c'` is a
207
+ * green at 33%), since vroom reads hex as `#aarrggbb`, not CSS `#rrggbbaa`.
208
+ */
209
+ fill?: VroomColor;
195
210
  };
196
211
  /**
197
212
  * A freehand pencil stroke: an open path through `points`, in order. Unlike the
@@ -224,6 +239,35 @@ type PathDrawing = DrawingBase & {
224
239
  * `'line'` and `'box'` are always exactly two points.
225
240
  */
226
241
  type Drawing = LineDrawing | BoxDrawing | PencilDrawing | PathDrawing;
242
+ /** An axis-aligned rectangle in CSS px, relative to the chart container. */
243
+ type DrawingRect = {
244
+ x: number;
245
+ y: number;
246
+ width: number;
247
+ height: number;
248
+ };
249
+ /** The currently selected drawing and where it sits on screen. */
250
+ type DrawingSelection = {
251
+ /**
252
+ * The selected drawing. While a drag or reshape is in flight this holds the
253
+ * last committed geometry — the edit reaches you through `onDrawingChange`
254
+ * on release — whereas `rect` tracks the shape live.
255
+ */
256
+ drawing: Drawing;
257
+ /** The drawing's bounds in CSS px, relative to the chart container. */
258
+ rect: DrawingRect;
259
+ };
260
+ /**
261
+ * The subset of a drawing's appearance a host can change after the fact,
262
+ * through `restyle`. Fields left out are kept as they were.
263
+ */
264
+ type DrawingStyle = {
265
+ color?: VroomColor;
266
+ width?: number;
267
+ /** Box interior fill; ignored by the other drawing types. */
268
+ fill?: VroomColor;
269
+ locked?: boolean;
270
+ };
227
271
  /**
228
272
  * Storage adapter for **managed** drawing persistence. Provide it via the
229
273
  * `drawingStore` prop and the chart owns the drawings array itself — loading and
@@ -264,11 +308,11 @@ type UndoRedoState = {
264
308
  canRedo: boolean;
265
309
  };
266
310
  /**
267
- * Programmatic undo/redo controls, published through the `historyRef` prop in
268
- * managed mode — for toolbar buttons and other UI outside the chart. The
269
- * keyboard shortcuts (⌘Z / ⇧⌘Z / Ctrl+Y) work without this.
311
+ * Programmatic controls over the chart's drawings, published through the
312
+ * `historyRef` prop in managed mode — for toolbar buttons and other UI outside
313
+ * the chart. The keyboard shortcuts (⌘Z / ⇧⌘Z / Ctrl+Y) work without this.
270
314
  */
271
- type UndoRedoControls = {
315
+ type DrawingControls = {
272
316
  /** Roll back the most recent committed drawing action. No-op when empty. */
273
317
  undo: () => void;
274
318
  /** Re-apply the most recently undone action. No-op when empty. */
@@ -279,6 +323,16 @@ type UndoRedoControls = {
279
323
  * chart for what the user perceives as a brand-new context.
280
324
  */
281
325
  clearHistory: () => void;
326
+ /**
327
+ * Restyle the drawing with this `id`, merging `patch` over its current
328
+ * appearance. No-op for an unknown id.
329
+ *
330
+ * In managed mode the chart owns the drawings array, so this is the only way
331
+ * to say "this drawing's color is now X" — the array is otherwise mutated by
332
+ * user gestures alone. The change records an undo step and persists like any
333
+ * other edit.
334
+ */
335
+ restyle: (id: string, patch: DrawingStyle) => void;
282
336
  };
283
337
  /** Price source for a moving average. */
284
338
  type MASource = 'close' | 'open' | 'high' | 'low' | 'hl2' | 'hlc3' | 'ohlc4';
@@ -749,6 +803,17 @@ type VroomChartCoreProps = {
749
803
  * drawing with this `id` from your controlled `drawings` state. Web only.
750
804
  */
751
805
  onDrawingDelete?: (id: string) => void;
806
+ /**
807
+ * Fired when the selected drawing changes, and again whenever its position on
808
+ * screen moves — panning, zooming, resizing, or dragging the shape itself.
809
+ * `null` on deselect.
810
+ *
811
+ * Anchor a floating toolbar to `selection.rect` and it will stay attached to
812
+ * the drawing: the rect is recomputed from live chart state once per painted
813
+ * frame, so it tracks 1:1 rather than settling after the gesture. Works in
814
+ * both controlled and managed mode. Web only.
815
+ */
816
+ onSelectionChange?: (selection: DrawingSelection | null) => void;
752
817
  /**
753
818
  * Fired when the chart wants the mode changed — e.g. it requests `'pan'` after
754
819
  * the user clicks away from a just-drawn line. Since `mode` is controlled, the
@@ -769,13 +834,13 @@ type VroomChartCoreProps = {
769
834
  */
770
835
  onHistoryChange?: (state: UndoRedoState) => void;
771
836
  /**
772
- * Receives programmatic `undo`/`redo`/`clearHistory` controls in managed mode
773
- * (e.g. `useRef<UndoRedoControls | null>(null)` passed here, then
837
+ * Receives programmatic `undo`/`redo`/`clearHistory`/`restyle` controls in
838
+ * managed mode (e.g. `useRef<DrawingControls | null>(null)` passed here, then
774
839
  * `historyRef.current?.undo()` from a toolbar button). Set to `null` while
775
840
  * unmounted or when no `drawingStore` is present. Web only.
776
841
  */
777
842
  historyRef?: {
778
- current: UndoRedoControls | null;
843
+ current: DrawingControls | null;
779
844
  };
780
845
  /**
781
846
  * Fired when the user presses the undo shortcut (⌘Z / Ctrl+Z) in controlled
package/lib/index.d.ts CHANGED
@@ -177,6 +177,12 @@ type DrawingBase = {
177
177
  color?: VroomColor;
178
178
  /** Stroke width in px. Default 2. */
179
179
  width?: number;
180
+ /**
181
+ * Protect the drawing from editing. A locked drawing still renders and can
182
+ * still be selected — so a toolbar can offer to unlock it — but it can't be
183
+ * dragged, reshaped or deleted, and its grab handles aren't drawn. Web only.
184
+ */
185
+ locked?: boolean;
180
186
  };
181
187
  /** A two-point trendline from `points[0]` to `points[1]`. */
182
188
  type LineDrawing = DrawingBase & {
@@ -192,6 +198,15 @@ type BoxDrawing = DrawingBase & {
192
198
  type: 'box';
193
199
  /** Two opposite corners, in data space. */
194
200
  points: [DrawPoint, DrawPoint];
201
+ /**
202
+ * Interior fill, painted beneath the stroke. Omitted, the interior keeps its
203
+ * default tint of the stroke color at 10% alpha.
204
+ *
205
+ * A solid fill hides the candles the box was drawn around, so this usually
206
+ * wants an alpha — as the high byte of an 8-digit hex (`'#5400ce2c'` is a
207
+ * green at 33%), since vroom reads hex as `#aarrggbb`, not CSS `#rrggbbaa`.
208
+ */
209
+ fill?: VroomColor;
195
210
  };
196
211
  /**
197
212
  * A freehand pencil stroke: an open path through `points`, in order. Unlike the
@@ -224,6 +239,35 @@ type PathDrawing = DrawingBase & {
224
239
  * `'line'` and `'box'` are always exactly two points.
225
240
  */
226
241
  type Drawing = LineDrawing | BoxDrawing | PencilDrawing | PathDrawing;
242
+ /** An axis-aligned rectangle in CSS px, relative to the chart container. */
243
+ type DrawingRect = {
244
+ x: number;
245
+ y: number;
246
+ width: number;
247
+ height: number;
248
+ };
249
+ /** The currently selected drawing and where it sits on screen. */
250
+ type DrawingSelection = {
251
+ /**
252
+ * The selected drawing. While a drag or reshape is in flight this holds the
253
+ * last committed geometry — the edit reaches you through `onDrawingChange`
254
+ * on release — whereas `rect` tracks the shape live.
255
+ */
256
+ drawing: Drawing;
257
+ /** The drawing's bounds in CSS px, relative to the chart container. */
258
+ rect: DrawingRect;
259
+ };
260
+ /**
261
+ * The subset of a drawing's appearance a host can change after the fact,
262
+ * through `restyle`. Fields left out are kept as they were.
263
+ */
264
+ type DrawingStyle = {
265
+ color?: VroomColor;
266
+ width?: number;
267
+ /** Box interior fill; ignored by the other drawing types. */
268
+ fill?: VroomColor;
269
+ locked?: boolean;
270
+ };
227
271
  /**
228
272
  * Storage adapter for **managed** drawing persistence. Provide it via the
229
273
  * `drawingStore` prop and the chart owns the drawings array itself — loading and
@@ -264,11 +308,11 @@ type UndoRedoState = {
264
308
  canRedo: boolean;
265
309
  };
266
310
  /**
267
- * Programmatic undo/redo controls, published through the `historyRef` prop in
268
- * managed mode — for toolbar buttons and other UI outside the chart. The
269
- * keyboard shortcuts (⌘Z / ⇧⌘Z / Ctrl+Y) work without this.
311
+ * Programmatic controls over the chart's drawings, published through the
312
+ * `historyRef` prop in managed mode — for toolbar buttons and other UI outside
313
+ * the chart. The keyboard shortcuts (⌘Z / ⇧⌘Z / Ctrl+Y) work without this.
270
314
  */
271
- type UndoRedoControls = {
315
+ type DrawingControls = {
272
316
  /** Roll back the most recent committed drawing action. No-op when empty. */
273
317
  undo: () => void;
274
318
  /** Re-apply the most recently undone action. No-op when empty. */
@@ -279,6 +323,16 @@ type UndoRedoControls = {
279
323
  * chart for what the user perceives as a brand-new context.
280
324
  */
281
325
  clearHistory: () => void;
326
+ /**
327
+ * Restyle the drawing with this `id`, merging `patch` over its current
328
+ * appearance. No-op for an unknown id.
329
+ *
330
+ * In managed mode the chart owns the drawings array, so this is the only way
331
+ * to say "this drawing's color is now X" — the array is otherwise mutated by
332
+ * user gestures alone. The change records an undo step and persists like any
333
+ * other edit.
334
+ */
335
+ restyle: (id: string, patch: DrawingStyle) => void;
282
336
  };
283
337
  /** Price source for a moving average. */
284
338
  type MASource = 'close' | 'open' | 'high' | 'low' | 'hl2' | 'hlc3' | 'ohlc4';
@@ -749,6 +803,17 @@ type VroomChartCoreProps = {
749
803
  * drawing with this `id` from your controlled `drawings` state. Web only.
750
804
  */
751
805
  onDrawingDelete?: (id: string) => void;
806
+ /**
807
+ * Fired when the selected drawing changes, and again whenever its position on
808
+ * screen moves — panning, zooming, resizing, or dragging the shape itself.
809
+ * `null` on deselect.
810
+ *
811
+ * Anchor a floating toolbar to `selection.rect` and it will stay attached to
812
+ * the drawing: the rect is recomputed from live chart state once per painted
813
+ * frame, so it tracks 1:1 rather than settling after the gesture. Works in
814
+ * both controlled and managed mode. Web only.
815
+ */
816
+ onSelectionChange?: (selection: DrawingSelection | null) => void;
752
817
  /**
753
818
  * Fired when the chart wants the mode changed — e.g. it requests `'pan'` after
754
819
  * the user clicks away from a just-drawn line. Since `mode` is controlled, the
@@ -769,13 +834,13 @@ type VroomChartCoreProps = {
769
834
  */
770
835
  onHistoryChange?: (state: UndoRedoState) => void;
771
836
  /**
772
- * Receives programmatic `undo`/`redo`/`clearHistory` controls in managed mode
773
- * (e.g. `useRef<UndoRedoControls | null>(null)` passed here, then
837
+ * Receives programmatic `undo`/`redo`/`clearHistory`/`restyle` controls in
838
+ * managed mode (e.g. `useRef<DrawingControls | null>(null)` passed here, then
774
839
  * `historyRef.current?.undo()` from a toolbar button). Set to `null` while
775
840
  * unmounted or when no `drawingStore` is present. Web only.
776
841
  */
777
842
  historyRef?: {
778
- current: UndoRedoControls | null;
843
+ current: DrawingControls | null;
779
844
  };
780
845
  /**
781
846
  * Fired when the user presses the undo shortcut (⌘Z / Ctrl+Z) in controlled
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-vroom-chart",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Mobile-first Skia candlestick chart for React Native",
5
5
  "license": "MIT",
6
6
  "author": "Darion Welch",