@pradeepjadhav/trading-reload-chart 1.0.1 → 1.0.3
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.
- package/README.md +20 -20
- package/dist/canvas/layers/TradeLayer/TradeLayer.d.ts +9 -9
- package/dist/canvas/layers/TradeLayer/TradeLayer.helpers.d.ts +2 -2
- package/dist/canvas/layers/TradeLayer/TradeLayer.types.d.ts +10 -45
- package/dist/chart/ChartController.types.d.ts +10 -5
- package/dist/config/chartConfig.types.d.ts +8 -8
- package/dist/index.d.ts +2 -2
- package/dist/models/Trade.types.d.ts +2 -2
- package/dist/react/TradingReload.d.ts +1 -1
- package/dist/react/TradingReload.types.d.ts +1 -1
- package/dist/trading-reload-chart.js +251 -251
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Trading Chart Library by Pradeep
|
|
2
2
|
|
|
3
|
-
A **React chart library** (HTML Canvas) for trading UIs: candlesticks, volume, axes, crosshair, drawing tools, and open/
|
|
3
|
+
A **React chart library** (HTML Canvas) for trading UIs: candlesticks, volume, axes, crosshair, drawing tools, and open/closed trade overlays. Data and state are **controlled by the parent** via props and callbacks.
|
|
4
4
|
|
|
5
5
|
The repo includes a **demo app** under `demo/` (Vite dev server) that loads live/historical data from an API. That harness is not published in the npm package.
|
|
6
6
|
|
|
@@ -81,22 +81,22 @@ The chart is **client-only** (canvas + DOM). It does **not** support SSR.
|
|
|
81
81
|
|
|
82
82
|
### 1. Install the package
|
|
83
83
|
|
|
84
|
-
**From npm** (after publish):
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
npm install trading-reload-chart react react-dom
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
**Directly from GitHub**:
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
npm install git+https://github.com/Pradeep17Jadhav/trading-reload-chart.git#main
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
**From this repo** (local path or `npm pack`):
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
# In trading-reload-chart repo
|
|
84
|
+
**From npm** (after publish):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install trading-reload-chart react react-dom
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Directly from GitHub**:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install git+https://github.com/Pradeep17Jadhav/trading-reload-chart.git#main
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**From this repo** (local path or `npm pack`):
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# In trading-reload-chart repo
|
|
100
100
|
npm run build
|
|
101
101
|
npm pack
|
|
102
102
|
# In your app
|
|
@@ -186,9 +186,9 @@ Import from the package root:
|
|
|
186
186
|
| `Candle`, `OpenTrade`, `ClosedTrade`, … | Domain types |
|
|
187
187
|
| `Shape`, `ShapeToolType`, shape payloads | Drawing types |
|
|
188
188
|
| `ChartConfig`, `CHART_CONFIG`, `DeepPartial` | Configuration |
|
|
189
|
-
| `
|
|
189
|
+
| `ClosedTradeIndicator`, trade handle types | Trade overlay types |
|
|
190
190
|
|
|
191
|
-
Controlled props (parent owns state): `candles`, `liveCandle`, `openTrades`, `
|
|
191
|
+
Controlled props (parent owns state): `candles`, `liveCandle`, `openTrades`, `closedTrades`, `shapes`, `activeShapeTool`, `config`, `activeSymbol`.
|
|
192
192
|
|
|
193
193
|
Callbacks: `onShapeAdded`, `onShapeModified`, `onActiveShapeToolChange`, `onTradeModify`.
|
|
194
194
|
|
|
@@ -226,7 +226,7 @@ Type declarations live in **`*.types.ts`** files next to implementation code (se
|
|
|
226
226
|
- Pan, horizontal wheel zoom, Ctrl+wheel vertical zoom
|
|
227
227
|
- Drawing tools (trendline, rectangle, path, fib, long/short position)
|
|
228
228
|
- Open trade SL/TP drag with `onTradeModify` on release
|
|
229
|
-
-
|
|
229
|
+
- Closed trade markers
|
|
230
230
|
- Deep-merge chart `config` over defaults
|
|
231
231
|
|
|
232
232
|
---
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { TemporaryTradeProtectionHandleRectConfig, TradeHandleRectConfig, TradeHandleStyleConfig } from '../../../config/chartConfig.types';
|
|
2
2
|
import { Candle } from '../../../models/Candle.types';
|
|
3
3
|
import { ChartViewport } from '../../../models/ChartViewport.types';
|
|
4
|
-
import { OpenTrade } from '../../../models/Trade.types';
|
|
5
|
-
import { CreateHitBoxOptions, CreateMissingProtectionHitBoxOptions, DrawConnectorLineOptions, DrawHandleLineOptions, DrawHandleRectOptions, DrawHandleSectionsOptions, DrawTradeHandleOptions, LineBlockedRange,
|
|
4
|
+
import { ClosedTrade, OpenTrade } from '../../../models/Trade.types';
|
|
5
|
+
import { CreateHitBoxOptions, CreateMissingProtectionHitBoxOptions, DrawConnectorLineOptions, DrawHandleLineOptions, DrawHandleRectOptions, DrawHandleSectionsOptions, DrawTradeHandleOptions, LineBlockedRange, TemporaryTradeProtectionDrag, TradeHandleHitbox, TradeHandleRenderState, TradeHandleType, TradeLayerOptions, TradeProtectionHandleType } from './TradeLayer.types';
|
|
6
6
|
export declare class TradeLayer {
|
|
7
7
|
#private;
|
|
8
8
|
isDragging: boolean;
|
|
9
9
|
trades: OpenTrade[];
|
|
10
|
-
|
|
10
|
+
closedTrades: ClosedTrade[];
|
|
11
11
|
candles: Candle[];
|
|
12
12
|
viewport: ChartViewport | null;
|
|
13
13
|
liveCandle: Candle | null;
|
|
@@ -15,7 +15,7 @@ export declare class TradeLayer {
|
|
|
15
15
|
temporaryProtectionDrag: TemporaryTradeProtectionDrag | null;
|
|
16
16
|
constructor(options: TradeLayerOptions);
|
|
17
17
|
setTrades(trades: OpenTrade[]): void;
|
|
18
|
-
|
|
18
|
+
setClosedTrades(closedTrades: ClosedTrade[]): void;
|
|
19
19
|
setCandles(candles: Candle[]): void;
|
|
20
20
|
setViewport(viewport: ChartViewport): void;
|
|
21
21
|
setLiveCandle(candle: Candle): void;
|
|
@@ -107,11 +107,11 @@ export declare class TradeLayer {
|
|
|
107
107
|
}): number;
|
|
108
108
|
private clearCanvas;
|
|
109
109
|
private resetHitboxes;
|
|
110
|
-
private
|
|
111
|
-
private
|
|
112
|
-
private
|
|
113
|
-
private
|
|
114
|
-
private
|
|
110
|
+
private drawClosedTradeIndicators;
|
|
111
|
+
private getClosedTradeIndicatorRenderState;
|
|
112
|
+
private drawClosedTradeConnector;
|
|
113
|
+
private drawClosedTradeArrows;
|
|
114
|
+
private drawClosedTradeArrow;
|
|
115
115
|
private getTimeX;
|
|
116
116
|
private normalizeTimestamp;
|
|
117
117
|
private getFirstCandleIndexAtOrAfterTime;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TradeDirection } from '../../../models/Trade.types';
|
|
2
2
|
export declare const getPipSize: (symbol: string) => number;
|
|
3
|
-
export declare const calculatePotentialPnlUsd: (type:
|
|
3
|
+
export declare const calculatePotentialPnlUsd: (type: TradeDirection, entryPrice: number, targetPrice: number, volume: number, symbol: string) => number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { MissingTradeProtectionHandleRectConfig,
|
|
1
|
+
import { MissingTradeProtectionHandleRectConfig, ClosedTradeIndicatorArrowConfig as ClosedTradeIndicatorArrowConfig, TemporaryTradeProtectionHandleRectConfig, TradeHandleLineConfig, TradeHandleRectConfig, TradeHandleStyleConfig } from '../../../config/chartConfig.types';
|
|
2
2
|
import { ChartViewport } from '../../../models/ChartViewport.types';
|
|
3
|
-
import { OpenTrade,
|
|
3
|
+
import { ClosedTrade, OpenTrade, TradeDirection } from '../../../models/Trade.types';
|
|
4
4
|
/**
|
|
5
5
|
* Type of existing trade handle shown on open positions.
|
|
6
6
|
*
|
|
@@ -50,41 +50,6 @@ export type TemporaryTradeProtectionDrag = {
|
|
|
50
50
|
price: number;
|
|
51
51
|
viewport: ChartViewport;
|
|
52
52
|
};
|
|
53
|
-
/**
|
|
54
|
-
* Historical trade marker rendered on the chart after a position is closed.
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```ts
|
|
58
|
-
* const trade: PastTrade = {
|
|
59
|
-
* symbol: "EURUSD",
|
|
60
|
-
* type: "sell",
|
|
61
|
-
* startTime: 1716796800000,
|
|
62
|
-
* closeTime: 1716800400000,
|
|
63
|
-
* openPrice: 1.089,
|
|
64
|
-
* closePrice: 1.086,
|
|
65
|
-
* volume: 1,
|
|
66
|
-
* sl: 1.091,
|
|
67
|
-
* tp: 1.086,
|
|
68
|
-
* commission: 0,
|
|
69
|
-
* swap: 0,
|
|
70
|
-
* pnl: 30,
|
|
71
|
-
* };
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
export type PastTrade = {
|
|
75
|
-
symbol: string;
|
|
76
|
-
type: TradeType;
|
|
77
|
-
startTime: number;
|
|
78
|
-
closeTime: number;
|
|
79
|
-
openPrice: number;
|
|
80
|
-
closePrice: number;
|
|
81
|
-
volume: number;
|
|
82
|
-
sl: number | null;
|
|
83
|
-
tp: number | null;
|
|
84
|
-
commission: number;
|
|
85
|
-
swap: number;
|
|
86
|
-
pnl: number;
|
|
87
|
-
};
|
|
88
53
|
export type TradeLayerEventsOptions = {
|
|
89
54
|
canvas: HTMLCanvasElement;
|
|
90
55
|
getHandleHitboxes: () => TradeHandleHitbox[];
|
|
@@ -161,16 +126,16 @@ export type TemporaryProtectionRenderState = {
|
|
|
161
126
|
handleWidth: number;
|
|
162
127
|
handleHeight: number;
|
|
163
128
|
};
|
|
164
|
-
export type
|
|
129
|
+
export type ClosedTradeIndicatorPoint = {
|
|
165
130
|
x: number;
|
|
166
131
|
y: number;
|
|
167
132
|
price: number;
|
|
168
133
|
time: number;
|
|
169
134
|
};
|
|
170
|
-
export type
|
|
171
|
-
trade:
|
|
172
|
-
start:
|
|
173
|
-
close:
|
|
135
|
+
export type ClosedTradeIndicatorRenderState = {
|
|
136
|
+
trade: ClosedTrade;
|
|
137
|
+
start: ClosedTradeIndicatorPoint;
|
|
138
|
+
close: ClosedTradeIndicatorPoint;
|
|
174
139
|
arrowWidth: number;
|
|
175
140
|
arrowHeight: number;
|
|
176
141
|
};
|
|
@@ -240,13 +205,13 @@ export type DrawHandleSectionsOptions = {
|
|
|
240
205
|
trade: OpenTrade;
|
|
241
206
|
y: number;
|
|
242
207
|
};
|
|
243
|
-
export type
|
|
208
|
+
export type DrawClosedTradeArrowOptions = {
|
|
244
209
|
x: number;
|
|
245
210
|
y: number;
|
|
246
|
-
type:
|
|
211
|
+
type: TradeDirection;
|
|
247
212
|
width: number;
|
|
248
213
|
height: number;
|
|
249
|
-
config:
|
|
214
|
+
config: ClosedTradeIndicatorArrowConfig;
|
|
250
215
|
};
|
|
251
216
|
export type HandleSection = {
|
|
252
217
|
label: string;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Shape, ShapeAddedPayload, ShapeModifiedPayload, ShapeSelectedPayload, ShapeToolType } from '../canvas/layers/ShapesLayer/ShapesLayer.types';
|
|
2
|
-
import { PastTrade } from '../canvas/layers/TradeLayer/TradeLayer.types';
|
|
3
2
|
import { ChartConfig } from '../config/chartConfig.types';
|
|
4
3
|
import { Candle } from '../models/Candle.types';
|
|
5
|
-
import { OpenTrade } from '../models/Trade.types';
|
|
4
|
+
import { ClosedTrade, OpenTrade } from '../models/Trade.types';
|
|
6
5
|
import { DeepPartial } from './utils/deepPartial.types';
|
|
7
6
|
/**
|
|
8
7
|
* Payload emitted when the user commits a stop-loss or take-profit change.
|
|
@@ -45,6 +44,12 @@ export type TradeModifyPayload = {
|
|
|
45
44
|
* ```
|
|
46
45
|
*/
|
|
47
46
|
export type PreviousDay = {
|
|
47
|
+
/**
|
|
48
|
+
* Trading symbol for the previous day data.
|
|
49
|
+
*
|
|
50
|
+
* @example `"EURUSD"`
|
|
51
|
+
*/
|
|
52
|
+
symbol: string;
|
|
48
53
|
/** Previous day high price.
|
|
49
54
|
*
|
|
50
55
|
* @example `1.0924`
|
|
@@ -67,7 +72,7 @@ export type PreviousDay = {
|
|
|
67
72
|
* candles,
|
|
68
73
|
* liveCandle,
|
|
69
74
|
* openTrades,
|
|
70
|
-
*
|
|
75
|
+
* closedTrades,
|
|
71
76
|
* shapes,
|
|
72
77
|
* activeShapeTool,
|
|
73
78
|
* onShapeAdded: ({ shape }) => setShapes((prev) => [...prev, shape]),
|
|
@@ -117,10 +122,10 @@ export type ChartControllerProps = {
|
|
|
117
122
|
*
|
|
118
123
|
* @example
|
|
119
124
|
* ```ts
|
|
120
|
-
*
|
|
125
|
+
* closedTrades: [{ ticket: 101245, symbol: "EURUSD", type: "sell", openTime: 1716796800000, openPrice: 1.089, closeTime: 1716800400000, closePrice: 1.086, volume: 1, sl: 1.091, tp: 1.086, commission: 0, swap: 0, pnl: 30, status: "closed" }]
|
|
121
126
|
* ```
|
|
122
127
|
*/
|
|
123
|
-
|
|
128
|
+
closedTrades: ClosedTrade[];
|
|
124
129
|
/**
|
|
125
130
|
* Parent-owned shapes rendered and edited by the chart.
|
|
126
131
|
*
|
|
@@ -375,11 +375,11 @@ export type TradeHandlesConfig = {
|
|
|
375
375
|
missingProtectionHandles: MissingTradeProtectionConfig;
|
|
376
376
|
};
|
|
377
377
|
/**
|
|
378
|
-
* Arrow appearance for
|
|
378
|
+
* Arrow appearance for closed trade markers.
|
|
379
379
|
*
|
|
380
380
|
* @example
|
|
381
381
|
* ```ts
|
|
382
|
-
* const arrow:
|
|
382
|
+
* const arrow: ClosedTradeIndicatorArrowConfig = {
|
|
383
383
|
* visible: true,
|
|
384
384
|
* width: 60,
|
|
385
385
|
* height: 30,
|
|
@@ -394,7 +394,7 @@ export type TradeHandlesConfig = {
|
|
|
394
394
|
* };
|
|
395
395
|
* ```
|
|
396
396
|
*/
|
|
397
|
-
export type
|
|
397
|
+
export type ClosedTradeIndicatorArrowConfig = {
|
|
398
398
|
visible: boolean;
|
|
399
399
|
width: number;
|
|
400
400
|
height: number;
|
|
@@ -412,16 +412,16 @@ export type PastTradeIndicatorArrowConfig = {
|
|
|
412
412
|
*
|
|
413
413
|
* @example
|
|
414
414
|
* ```ts
|
|
415
|
-
* const
|
|
415
|
+
* const closedTrades: ClosedTradeIndicatorsConfig = {
|
|
416
416
|
* visible: true,
|
|
417
417
|
* arrow: { visible: true, width: 60, height: 30, headHeightRatio: 0.45, shaftWidthRatio: 6, buyColor: "#22c55e", sellColor: "#ef4444", buyBorderColor: "#ffffff", sellBorderColor: "#ffffff", borderWidth: 1, opacity: 1 },
|
|
418
418
|
* connectorLine: { width: 1, color: "#ffffff", opacity: 0.65, style: "dotted", dash: [2, 4] },
|
|
419
419
|
* };
|
|
420
420
|
* ```
|
|
421
421
|
*/
|
|
422
|
-
export type
|
|
422
|
+
export type ClosedTradeIndicatorsConfig = {
|
|
423
423
|
visible: boolean;
|
|
424
|
-
arrow:
|
|
424
|
+
arrow: ClosedTradeIndicatorArrowConfig;
|
|
425
425
|
connectorLine: TradeHandleLineConfig;
|
|
426
426
|
};
|
|
427
427
|
/**
|
|
@@ -622,7 +622,7 @@ export type PreviousDayHighLowConfig = {
|
|
|
622
622
|
* volume: { visible: true, bullishColor: "#22c55e", bearishColor: "#ef4444", opacity: 0.35, height: 200, bottomOffset: 0, minBarHeight: 1 },
|
|
623
623
|
* crosshair: { color: "rgba(255,255,255,0.35)", thickness: 1, style: "dashed" },
|
|
624
624
|
* tradeHandles: {} as TradeHandlesConfig,
|
|
625
|
-
*
|
|
625
|
+
* closedTradeIndicators: { visible: true, arrow: { visible: true, width: 60, height: 30, headHeightRatio: 0.45, shaftWidthRatio: 6, buyColor: "#22c55e", sellColor: "#ef4444", buyBorderColor: "#ffffff", sellBorderColor: "#ffffff", borderWidth: 1, opacity: 1 }, connectorLine: { width: 1, color: "#ffffff", opacity: 0.65, style: "dotted", dash: [2, 4] } },
|
|
626
626
|
* colors: { bullish: "#22c55e", bearish: "#ef4444", background: "#0b1220" },
|
|
627
627
|
* axis: { axisX: { height: 30, backgroundColor: "#0b1220", borderColor: "#1f2937", borderWidth: 1, textColor: "#cbd5e1", font: "12px Arial", textAlign: "center", tickColor: "#334155", tickWidth: 1, tickLength: 4, labelOffsetY: 16, minLabelGap: 16, crosshairLabel: { backgroundColor: "#111827", borderColor: "#111827", borderWidth: 1, textColor: "#ffffff", font: "12px Arial", paddingX: 8, height: 26 } }, axisY: { width: 80, backgroundColor: "#0b1220", borderColor: "#1f2937", borderWidth: 1, textColor: "#cbd5e1", font: "12px Arial", textAlign: "left", tickCount: 12, tickColor: "#334155", tickWidth: 1, tickLength: 4, tickGap: 64, labelOffsetX: 8, crosshairLabel: { backgroundColor: "#111827", borderColor: "#111827", borderWidth: 1, textColor: "#ffffff", font: "12px Arial", paddingX: 8, height: 20 } } },
|
|
628
628
|
* shapes: { trendline: { lineColor: "#22c55e" } },
|
|
@@ -635,7 +635,7 @@ export type ChartConfig = {
|
|
|
635
635
|
volume: VolumeConfig;
|
|
636
636
|
crosshair: CrosshairConfig;
|
|
637
637
|
tradeHandles: TradeHandlesConfig;
|
|
638
|
-
|
|
638
|
+
closedTradeIndicators: ClosedTradeIndicatorsConfig;
|
|
639
639
|
colors: ColorsConfig;
|
|
640
640
|
axis: AxisConfig;
|
|
641
641
|
shapes: PartialShapeConfig;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { PartialShapeConfig, Shape, ShapeAddedPayload, ShapeId, ShapeModifiedPayload, ShapeSelectedPayload, ShapeToolType, ShapeType, ShapeVertex, } from './canvas/layers/ShapesLayer/ShapesLayer.types';
|
|
2
|
-
export type {
|
|
2
|
+
export type { TradeHandleType, TradeProtectionHandleType, } from './canvas/layers/TradeLayer/TradeLayer.types';
|
|
3
3
|
export type { ChartControllerProps, PreviousDay, TradeModifyPayload, } from './chart/ChartController.types';
|
|
4
4
|
export type { DeepPartial } from './chart/utils/deepPartial.types';
|
|
5
5
|
export type { CanvasPoint } from './chart/utils/getCanvasPoint.types';
|
|
@@ -8,7 +8,7 @@ export type { ChartConfig, PreviousDayHighLowConfig } from './config/chartConfig
|
|
|
8
8
|
export type { Candle } from './models/Candle.types';
|
|
9
9
|
export type { ChartViewport } from './models/ChartViewport.types';
|
|
10
10
|
export type { Point2D } from './models/Point.types';
|
|
11
|
-
export type { BaseTrade, ClosedTrade, OpenTrade,
|
|
11
|
+
export type { BaseTrade, ClosedTrade, OpenTrade, TradeDirection } from './models/Trade.types';
|
|
12
12
|
export type { VisibleRange } from './models/VisibleRange.types';
|
|
13
13
|
export { TradingReload } from './react/TradingReload';
|
|
14
14
|
export type { TradingReloadHandle, TradingReloadProps } from './react/TradingReload.types';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @example `"buy"`
|
|
5
5
|
*/
|
|
6
|
-
export type
|
|
6
|
+
export type TradeDirection = "buy" | "sell";
|
|
7
7
|
/**
|
|
8
8
|
* Shared trade fields used by open and closed trade records.
|
|
9
9
|
*
|
|
@@ -54,7 +54,7 @@ export type BaseTrade = {
|
|
|
54
54
|
*
|
|
55
55
|
* @example `"buy"`
|
|
56
56
|
*/
|
|
57
|
-
type:
|
|
57
|
+
type: TradeDirection;
|
|
58
58
|
/** Trade open time in milliseconds UTC.
|
|
59
59
|
*
|
|
60
60
|
* @example `1716883200000`
|
|
@@ -13,7 +13,7 @@ import { TradingReloadHandle } from './TradingReload.types';
|
|
|
13
13
|
* candles={candles}
|
|
14
14
|
* liveCandle={liveCandle}
|
|
15
15
|
* openTrades={openTrades}
|
|
16
|
-
*
|
|
16
|
+
* closedTrades={closedTrades}
|
|
17
17
|
* shapes={shapes}
|
|
18
18
|
* activeShapeTool={activeShapeTool}
|
|
19
19
|
* onShapeAdded={({ shape }) => setShapes((prev) => [...prev, shape])}
|
|
@@ -38,7 +38,7 @@ export type TradingReloadHandle = {
|
|
|
38
38
|
* candles={candles}
|
|
39
39
|
* liveCandle={liveCandle}
|
|
40
40
|
* openTrades={openTrades}
|
|
41
|
-
*
|
|
41
|
+
* closedTrades={closedTrades}
|
|
42
42
|
* shapes={shapes}
|
|
43
43
|
* activeShapeTool={activeShapeTool}
|
|
44
44
|
* onShapeAdded={({ shape }) => setShapes((prev) => [...prev, shape])}
|