hyperprop-charting-library 0.1.16 → 0.1.17

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 CHANGED
@@ -60,6 +60,22 @@ const chart = createChart(root, {
60
60
  });
61
61
  ```
62
62
 
63
+ ## Crosshair "+" Button
64
+
65
+ ```ts
66
+ const chart = createChart(root, {
67
+ crosshair: {
68
+ showPriceActionButton: true,
69
+ priceActionButtonRounded: false // square button
70
+ }
71
+ });
72
+
73
+ chart.onCrosshairPriceAction((event) => {
74
+ // Frontend decides what to do
75
+ console.log(event.price);
76
+ });
77
+ ```
78
+
63
79
  ## Full Documentation
64
80
 
65
81
  - API reference: `docs/API.md`
@@ -74,7 +90,7 @@ const chart = createChart(root, {
74
90
  - `chart.setData(data)`
75
91
  - `chart.setPriceLines(lines)` / `chart.addPriceLine(line)` / `chart.removePriceLine(id)`
76
92
  - `chart.setOrderLines(lines)` / `chart.addOrderLine(line)` / `chart.updateOrderLine(id, patch)` / `chart.removeOrderLine(id)`
77
- - `chart.onOrderAction(handler)` / `chart.onChartClick(handler)` / `chart.onCrosshairMove(handler)`
93
+ - `chart.onOrderAction(handler)` / `chart.onChartClick(handler)` / `chart.onCrosshairMove(handler)` / `chart.onCrosshairPriceAction(handler)`
78
94
  - `chart.setDoubleClickEnabled(enabled)` / `chart.setDoubleClickAction(action)`
79
95
  - `chart.zoomInX()` / `chart.zoomOutX()` / `chart.panX(bars)` / `chart.resetViewport()`
80
96
  - `chart.resize(width, height)` / `chart.destroy()`
@@ -51,7 +51,17 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
51
51
  labelBorderRadius: 3,
52
52
  labelBorderColor: "#94a3b8",
53
53
  labelBorderWidth: 1,
54
- labelBorderStyle: "solid"
54
+ labelBorderStyle: "solid",
55
+ showPriceActionButton: false,
56
+ priceActionButtonText: "+",
57
+ priceActionButtonSize: 20,
58
+ priceActionButtonGap: 6,
59
+ priceActionButtonBackgroundColor: "#1f2937",
60
+ priceActionButtonTextColor: "#e2e8f0",
61
+ priceActionButtonBorderColor: "#475569",
62
+ priceActionButtonBorderWidth: 1,
63
+ priceActionButtonRounded: true,
64
+ priceActionButtonBorderRadius: 3
55
65
  };
56
66
  var DEFAULT_WATERMARK_OPTIONS = {
57
67
  visible: false,
@@ -163,10 +173,6 @@ var DEFAULT_OPTIONS = {
163
173
  },
164
174
  dashPatterns: DEFAULT_DASH_PATTERNS
165
175
  };
166
- var BRAND_LOGO_VIEWBOX_WIDTH = 190;
167
- var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
168
- var BRAND_LOGO_PATH_A = "M0 93.0171V45.2271H48.9851V75.0332C48.9851 84.9545 57.0416 93.0001 66.9763 93.0001H94.9957V186H49.0531V110.984C49.0531 101.063 40.9965 93.0171 31.0619 93.0171H0Z";
169
- var BRAND_LOGO_PATH_B = "M190 92.9915V140.782H141.015V110.975C141.015 101.054 132.958 93.0085 123.023 93.0085H95.0039V0H140.955V75.0162C140.955 84.9374 149.012 92.9831 158.946 92.9831H190V92.9915Z";
170
176
  function createChart(element, options = {}) {
171
177
  const mergedOptions = {
172
178
  ...DEFAULT_OPTIONS,
@@ -211,6 +217,8 @@ function createChart(element, options = {}) {
211
217
  let orderActionHandler = null;
212
218
  let chartClickHandler = null;
213
219
  let crosshairMoveHandler = null;
220
+ let crosshairPriceActionHandler = null;
221
+ let crosshairPriceActionRegion = null;
214
222
  let orderActionRegions = [];
215
223
  let orderDragRegions = [];
216
224
  let generatedPriceLineId = 1;
@@ -223,8 +231,6 @@ function createChart(element, options = {}) {
223
231
  let yMaxOverride = null;
224
232
  let autoYMin = null;
225
233
  let autoYMax = null;
226
- const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
227
- const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
228
234
  let watermarkImageSrc = null;
229
235
  let watermarkImage = null;
230
236
  let watermarkImageReady = false;
@@ -802,6 +808,7 @@ function createChart(element, options = {}) {
802
808
  const draw = () => {
803
809
  orderActionRegions = [];
804
810
  orderDragRegions = [];
811
+ crosshairPriceActionRegion = null;
805
812
  const pixelRatio = getPixelRatio();
806
813
  canvas.style.width = `${width}px`;
807
814
  canvas.style.height = `${height}px`;
@@ -1107,17 +1114,6 @@ function createChart(element, options = {}) {
1107
1114
  });
1108
1115
  drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
1109
1116
  }
1110
- const brandLogoWidth = 34;
1111
- const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
1112
- const brandLogoX = chartLeft + 16;
1113
- const brandLogoY = chartBottom - brandLogoHeight - 10;
1114
- ctx.save();
1115
- ctx.translate(brandLogoX, brandLogoY);
1116
- ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
1117
- ctx.fillStyle = "#ffffff";
1118
- ctx.fill(brandLogoPathA);
1119
- ctx.fill(brandLogoPathB);
1120
- ctx.restore();
1121
1117
  if (crosshair.visible && crosshairPoint) {
1122
1118
  const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
1123
1119
  const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
@@ -1154,6 +1150,39 @@ function createChart(element, options = {}) {
1154
1150
  fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
1155
1151
  strokeCrosshairLabel(priceX, priceY, priceWidth);
1156
1152
  drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1153
+ if (crosshair.showPriceActionButton) {
1154
+ const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
1155
+ const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
1156
+ const buttonX = priceX - buttonGap - buttonSize;
1157
+ const buttonY = priceY + (labelHeight - buttonSize) / 2;
1158
+ const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
1159
+ const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
1160
+ ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
1161
+ fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1162
+ if (buttonBorderWidth > 0) {
1163
+ ctx.save();
1164
+ ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
1165
+ ctx.lineWidth = buttonBorderWidth;
1166
+ ctx.setLineDash([]);
1167
+ strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1168
+ ctx.restore();
1169
+ }
1170
+ drawText(
1171
+ crosshair.priceActionButtonText,
1172
+ buttonX + buttonSize / 2,
1173
+ buttonY + buttonSize / 2,
1174
+ "center",
1175
+ "middle",
1176
+ crosshair.priceActionButtonTextColor
1177
+ );
1178
+ crosshairPriceActionRegion = {
1179
+ x: buttonX,
1180
+ y: buttonY,
1181
+ width: buttonSize,
1182
+ height: buttonSize,
1183
+ price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
1184
+ };
1185
+ }
1157
1186
  }
1158
1187
  if (crosshair.showTimeLabel) {
1159
1188
  const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
@@ -1316,6 +1345,16 @@ function createChart(element, options = {}) {
1316
1345
  (region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
1317
1346
  );
1318
1347
  };
1348
+ const getCrosshairPriceActionRegion = (x, y) => {
1349
+ if (!crosshairPriceActionRegion) {
1350
+ return null;
1351
+ }
1352
+ const region = crosshairPriceActionRegion;
1353
+ if (x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height) {
1354
+ return region;
1355
+ }
1356
+ return null;
1357
+ };
1319
1358
  const priceFromCanvasY = (y) => {
1320
1359
  if (!drawState) {
1321
1360
  return 0;
@@ -1381,6 +1420,15 @@ function createChart(element, options = {}) {
1381
1420
  let activePointerId = null;
1382
1421
  const onPointerDown = (event) => {
1383
1422
  const point = getCanvasPoint(event);
1423
+ const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
1424
+ if (crosshairButtonRegion) {
1425
+ crosshairPriceActionHandler?.({
1426
+ x: point.x,
1427
+ y: point.y,
1428
+ price: crosshairButtonRegion.price
1429
+ });
1430
+ return;
1431
+ }
1384
1432
  const orderRegion = getOrderActionRegion(point.x, point.y);
1385
1433
  if (orderRegion) {
1386
1434
  if (orderRegion.draggable) {
@@ -1493,6 +1541,11 @@ function createChart(element, options = {}) {
1493
1541
  return;
1494
1542
  }
1495
1543
  if (!isDragging || !dragMode) {
1544
+ const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
1545
+ if (crosshairButtonRegion) {
1546
+ canvas.style.cursor = "pointer";
1547
+ return;
1548
+ }
1496
1549
  const orderRegion = getOrderActionRegion(point.x, point.y);
1497
1550
  if (orderRegion) {
1498
1551
  canvas.style.cursor = orderRegion.draggable ? "ns-resize" : "pointer";
@@ -1746,6 +1799,9 @@ function createChart(element, options = {}) {
1746
1799
  const onCrosshairMove = (handler) => {
1747
1800
  crosshairMoveHandler = handler;
1748
1801
  };
1802
+ const onCrosshairPriceAction = (handler) => {
1803
+ crosshairPriceActionHandler = handler;
1804
+ };
1749
1805
  const setDoubleClickEnabled = (enabled) => {
1750
1806
  doubleClickEnabled = enabled;
1751
1807
  };
@@ -1775,6 +1831,7 @@ function createChart(element, options = {}) {
1775
1831
  onOrderAction,
1776
1832
  onChartClick,
1777
1833
  onCrosshairMove,
1834
+ onCrosshairPriceAction,
1778
1835
  zoomInX,
1779
1836
  zoomOutX,
1780
1837
  zoomInY,
@@ -71,6 +71,16 @@ interface CrosshairOptions {
71
71
  labelBorderColor?: string;
72
72
  labelBorderWidth?: number;
73
73
  labelBorderStyle?: "solid" | "dotted" | "dashed";
74
+ showPriceActionButton?: boolean;
75
+ priceActionButtonText?: string;
76
+ priceActionButtonSize?: number;
77
+ priceActionButtonGap?: number;
78
+ priceActionButtonBackgroundColor?: string;
79
+ priceActionButtonTextColor?: string;
80
+ priceActionButtonBorderColor?: string;
81
+ priceActionButtonBorderWidth?: number;
82
+ priceActionButtonRounded?: boolean;
83
+ priceActionButtonBorderRadius?: number;
74
84
  }
75
85
  interface WatermarkOptions {
76
86
  visible?: boolean;
@@ -178,6 +188,11 @@ interface CrosshairMoveEvent {
178
188
  time?: string;
179
189
  point?: OhlcDataPoint;
180
190
  }
191
+ interface CrosshairPriceActionEvent {
192
+ x: number;
193
+ y: number;
194
+ price: number;
195
+ }
181
196
  interface TickerLineOptions {
182
197
  visible?: boolean;
183
198
  style?: "solid" | "dotted" | "dashed";
@@ -199,6 +214,7 @@ interface ChartInstance {
199
214
  onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
200
215
  onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
201
216
  onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
217
+ onCrosshairPriceAction: (handler: ((event: CrosshairPriceActionEvent) => void) | null) => void;
202
218
  zoomInX: (factor?: number) => void;
203
219
  zoomOutX: (factor?: number) => void;
204
220
  zoomInY: (factor?: number) => void;
@@ -222,4 +238,4 @@ interface OhlcDataPoint {
222
238
  }
223
239
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
224
240
 
225
- export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
241
+ export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
@@ -27,7 +27,17 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
27
27
  labelBorderRadius: 3,
28
28
  labelBorderColor: "#94a3b8",
29
29
  labelBorderWidth: 1,
30
- labelBorderStyle: "solid"
30
+ labelBorderStyle: "solid",
31
+ showPriceActionButton: false,
32
+ priceActionButtonText: "+",
33
+ priceActionButtonSize: 20,
34
+ priceActionButtonGap: 6,
35
+ priceActionButtonBackgroundColor: "#1f2937",
36
+ priceActionButtonTextColor: "#e2e8f0",
37
+ priceActionButtonBorderColor: "#475569",
38
+ priceActionButtonBorderWidth: 1,
39
+ priceActionButtonRounded: true,
40
+ priceActionButtonBorderRadius: 3
31
41
  };
32
42
  var DEFAULT_WATERMARK_OPTIONS = {
33
43
  visible: false,
@@ -139,10 +149,6 @@ var DEFAULT_OPTIONS = {
139
149
  },
140
150
  dashPatterns: DEFAULT_DASH_PATTERNS
141
151
  };
142
- var BRAND_LOGO_VIEWBOX_WIDTH = 190;
143
- var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
144
- var BRAND_LOGO_PATH_A = "M0 93.0171V45.2271H48.9851V75.0332C48.9851 84.9545 57.0416 93.0001 66.9763 93.0001H94.9957V186H49.0531V110.984C49.0531 101.063 40.9965 93.0171 31.0619 93.0171H0Z";
145
- var BRAND_LOGO_PATH_B = "M190 92.9915V140.782H141.015V110.975C141.015 101.054 132.958 93.0085 123.023 93.0085H95.0039V0H140.955V75.0162C140.955 84.9374 149.012 92.9831 158.946 92.9831H190V92.9915Z";
146
152
  function createChart(element, options = {}) {
147
153
  const mergedOptions = {
148
154
  ...DEFAULT_OPTIONS,
@@ -187,6 +193,8 @@ function createChart(element, options = {}) {
187
193
  let orderActionHandler = null;
188
194
  let chartClickHandler = null;
189
195
  let crosshairMoveHandler = null;
196
+ let crosshairPriceActionHandler = null;
197
+ let crosshairPriceActionRegion = null;
190
198
  let orderActionRegions = [];
191
199
  let orderDragRegions = [];
192
200
  let generatedPriceLineId = 1;
@@ -199,8 +207,6 @@ function createChart(element, options = {}) {
199
207
  let yMaxOverride = null;
200
208
  let autoYMin = null;
201
209
  let autoYMax = null;
202
- const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
203
- const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
204
210
  let watermarkImageSrc = null;
205
211
  let watermarkImage = null;
206
212
  let watermarkImageReady = false;
@@ -778,6 +784,7 @@ function createChart(element, options = {}) {
778
784
  const draw = () => {
779
785
  orderActionRegions = [];
780
786
  orderDragRegions = [];
787
+ crosshairPriceActionRegion = null;
781
788
  const pixelRatio = getPixelRatio();
782
789
  canvas.style.width = `${width}px`;
783
790
  canvas.style.height = `${height}px`;
@@ -1083,17 +1090,6 @@ function createChart(element, options = {}) {
1083
1090
  });
1084
1091
  drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
1085
1092
  }
1086
- const brandLogoWidth = 34;
1087
- const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
1088
- const brandLogoX = chartLeft + 16;
1089
- const brandLogoY = chartBottom - brandLogoHeight - 10;
1090
- ctx.save();
1091
- ctx.translate(brandLogoX, brandLogoY);
1092
- ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
1093
- ctx.fillStyle = "#ffffff";
1094
- ctx.fill(brandLogoPathA);
1095
- ctx.fill(brandLogoPathB);
1096
- ctx.restore();
1097
1093
  if (crosshair.visible && crosshairPoint) {
1098
1094
  const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
1099
1095
  const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
@@ -1130,6 +1126,39 @@ function createChart(element, options = {}) {
1130
1126
  fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
1131
1127
  strokeCrosshairLabel(priceX, priceY, priceWidth);
1132
1128
  drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1129
+ if (crosshair.showPriceActionButton) {
1130
+ const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
1131
+ const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
1132
+ const buttonX = priceX - buttonGap - buttonSize;
1133
+ const buttonY = priceY + (labelHeight - buttonSize) / 2;
1134
+ const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
1135
+ const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
1136
+ ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
1137
+ fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1138
+ if (buttonBorderWidth > 0) {
1139
+ ctx.save();
1140
+ ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
1141
+ ctx.lineWidth = buttonBorderWidth;
1142
+ ctx.setLineDash([]);
1143
+ strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1144
+ ctx.restore();
1145
+ }
1146
+ drawText(
1147
+ crosshair.priceActionButtonText,
1148
+ buttonX + buttonSize / 2,
1149
+ buttonY + buttonSize / 2,
1150
+ "center",
1151
+ "middle",
1152
+ crosshair.priceActionButtonTextColor
1153
+ );
1154
+ crosshairPriceActionRegion = {
1155
+ x: buttonX,
1156
+ y: buttonY,
1157
+ width: buttonSize,
1158
+ height: buttonSize,
1159
+ price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
1160
+ };
1161
+ }
1133
1162
  }
1134
1163
  if (crosshair.showTimeLabel) {
1135
1164
  const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
@@ -1292,6 +1321,16 @@ function createChart(element, options = {}) {
1292
1321
  (region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
1293
1322
  );
1294
1323
  };
1324
+ const getCrosshairPriceActionRegion = (x, y) => {
1325
+ if (!crosshairPriceActionRegion) {
1326
+ return null;
1327
+ }
1328
+ const region = crosshairPriceActionRegion;
1329
+ if (x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height) {
1330
+ return region;
1331
+ }
1332
+ return null;
1333
+ };
1295
1334
  const priceFromCanvasY = (y) => {
1296
1335
  if (!drawState) {
1297
1336
  return 0;
@@ -1357,6 +1396,15 @@ function createChart(element, options = {}) {
1357
1396
  let activePointerId = null;
1358
1397
  const onPointerDown = (event) => {
1359
1398
  const point = getCanvasPoint(event);
1399
+ const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
1400
+ if (crosshairButtonRegion) {
1401
+ crosshairPriceActionHandler?.({
1402
+ x: point.x,
1403
+ y: point.y,
1404
+ price: crosshairButtonRegion.price
1405
+ });
1406
+ return;
1407
+ }
1360
1408
  const orderRegion = getOrderActionRegion(point.x, point.y);
1361
1409
  if (orderRegion) {
1362
1410
  if (orderRegion.draggable) {
@@ -1469,6 +1517,11 @@ function createChart(element, options = {}) {
1469
1517
  return;
1470
1518
  }
1471
1519
  if (!isDragging || !dragMode) {
1520
+ const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
1521
+ if (crosshairButtonRegion) {
1522
+ canvas.style.cursor = "pointer";
1523
+ return;
1524
+ }
1472
1525
  const orderRegion = getOrderActionRegion(point.x, point.y);
1473
1526
  if (orderRegion) {
1474
1527
  canvas.style.cursor = orderRegion.draggable ? "ns-resize" : "pointer";
@@ -1722,6 +1775,9 @@ function createChart(element, options = {}) {
1722
1775
  const onCrosshairMove = (handler) => {
1723
1776
  crosshairMoveHandler = handler;
1724
1777
  };
1778
+ const onCrosshairPriceAction = (handler) => {
1779
+ crosshairPriceActionHandler = handler;
1780
+ };
1725
1781
  const setDoubleClickEnabled = (enabled) => {
1726
1782
  doubleClickEnabled = enabled;
1727
1783
  };
@@ -1751,6 +1807,7 @@ function createChart(element, options = {}) {
1751
1807
  onOrderAction,
1752
1808
  onChartClick,
1753
1809
  onCrosshairMove,
1810
+ onCrosshairPriceAction,
1754
1811
  zoomInX,
1755
1812
  zoomOutX,
1756
1813
  zoomInY,
package/dist/index.cjs CHANGED
@@ -51,7 +51,17 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
51
51
  labelBorderRadius: 3,
52
52
  labelBorderColor: "#94a3b8",
53
53
  labelBorderWidth: 1,
54
- labelBorderStyle: "solid"
54
+ labelBorderStyle: "solid",
55
+ showPriceActionButton: false,
56
+ priceActionButtonText: "+",
57
+ priceActionButtonSize: 20,
58
+ priceActionButtonGap: 6,
59
+ priceActionButtonBackgroundColor: "#1f2937",
60
+ priceActionButtonTextColor: "#e2e8f0",
61
+ priceActionButtonBorderColor: "#475569",
62
+ priceActionButtonBorderWidth: 1,
63
+ priceActionButtonRounded: true,
64
+ priceActionButtonBorderRadius: 3
55
65
  };
56
66
  var DEFAULT_WATERMARK_OPTIONS = {
57
67
  visible: false,
@@ -163,10 +173,6 @@ var DEFAULT_OPTIONS = {
163
173
  },
164
174
  dashPatterns: DEFAULT_DASH_PATTERNS
165
175
  };
166
- var BRAND_LOGO_VIEWBOX_WIDTH = 190;
167
- var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
168
- var BRAND_LOGO_PATH_A = "M0 93.0171V45.2271H48.9851V75.0332C48.9851 84.9545 57.0416 93.0001 66.9763 93.0001H94.9957V186H49.0531V110.984C49.0531 101.063 40.9965 93.0171 31.0619 93.0171H0Z";
169
- var BRAND_LOGO_PATH_B = "M190 92.9915V140.782H141.015V110.975C141.015 101.054 132.958 93.0085 123.023 93.0085H95.0039V0H140.955V75.0162C140.955 84.9374 149.012 92.9831 158.946 92.9831H190V92.9915Z";
170
176
  function createChart(element, options = {}) {
171
177
  const mergedOptions = {
172
178
  ...DEFAULT_OPTIONS,
@@ -211,6 +217,8 @@ function createChart(element, options = {}) {
211
217
  let orderActionHandler = null;
212
218
  let chartClickHandler = null;
213
219
  let crosshairMoveHandler = null;
220
+ let crosshairPriceActionHandler = null;
221
+ let crosshairPriceActionRegion = null;
214
222
  let orderActionRegions = [];
215
223
  let orderDragRegions = [];
216
224
  let generatedPriceLineId = 1;
@@ -223,8 +231,6 @@ function createChart(element, options = {}) {
223
231
  let yMaxOverride = null;
224
232
  let autoYMin = null;
225
233
  let autoYMax = null;
226
- const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
227
- const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
228
234
  let watermarkImageSrc = null;
229
235
  let watermarkImage = null;
230
236
  let watermarkImageReady = false;
@@ -802,6 +808,7 @@ function createChart(element, options = {}) {
802
808
  const draw = () => {
803
809
  orderActionRegions = [];
804
810
  orderDragRegions = [];
811
+ crosshairPriceActionRegion = null;
805
812
  const pixelRatio = getPixelRatio();
806
813
  canvas.style.width = `${width}px`;
807
814
  canvas.style.height = `${height}px`;
@@ -1107,17 +1114,6 @@ function createChart(element, options = {}) {
1107
1114
  });
1108
1115
  drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
1109
1116
  }
1110
- const brandLogoWidth = 34;
1111
- const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
1112
- const brandLogoX = chartLeft + 16;
1113
- const brandLogoY = chartBottom - brandLogoHeight - 10;
1114
- ctx.save();
1115
- ctx.translate(brandLogoX, brandLogoY);
1116
- ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
1117
- ctx.fillStyle = "#ffffff";
1118
- ctx.fill(brandLogoPathA);
1119
- ctx.fill(brandLogoPathB);
1120
- ctx.restore();
1121
1117
  if (crosshair.visible && crosshairPoint) {
1122
1118
  const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
1123
1119
  const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
@@ -1154,6 +1150,39 @@ function createChart(element, options = {}) {
1154
1150
  fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
1155
1151
  strokeCrosshairLabel(priceX, priceY, priceWidth);
1156
1152
  drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1153
+ if (crosshair.showPriceActionButton) {
1154
+ const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
1155
+ const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
1156
+ const buttonX = priceX - buttonGap - buttonSize;
1157
+ const buttonY = priceY + (labelHeight - buttonSize) / 2;
1158
+ const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
1159
+ const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
1160
+ ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
1161
+ fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1162
+ if (buttonBorderWidth > 0) {
1163
+ ctx.save();
1164
+ ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
1165
+ ctx.lineWidth = buttonBorderWidth;
1166
+ ctx.setLineDash([]);
1167
+ strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1168
+ ctx.restore();
1169
+ }
1170
+ drawText(
1171
+ crosshair.priceActionButtonText,
1172
+ buttonX + buttonSize / 2,
1173
+ buttonY + buttonSize / 2,
1174
+ "center",
1175
+ "middle",
1176
+ crosshair.priceActionButtonTextColor
1177
+ );
1178
+ crosshairPriceActionRegion = {
1179
+ x: buttonX,
1180
+ y: buttonY,
1181
+ width: buttonSize,
1182
+ height: buttonSize,
1183
+ price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
1184
+ };
1185
+ }
1157
1186
  }
1158
1187
  if (crosshair.showTimeLabel) {
1159
1188
  const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
@@ -1316,6 +1345,16 @@ function createChart(element, options = {}) {
1316
1345
  (region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
1317
1346
  );
1318
1347
  };
1348
+ const getCrosshairPriceActionRegion = (x, y) => {
1349
+ if (!crosshairPriceActionRegion) {
1350
+ return null;
1351
+ }
1352
+ const region = crosshairPriceActionRegion;
1353
+ if (x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height) {
1354
+ return region;
1355
+ }
1356
+ return null;
1357
+ };
1319
1358
  const priceFromCanvasY = (y) => {
1320
1359
  if (!drawState) {
1321
1360
  return 0;
@@ -1381,6 +1420,15 @@ function createChart(element, options = {}) {
1381
1420
  let activePointerId = null;
1382
1421
  const onPointerDown = (event) => {
1383
1422
  const point = getCanvasPoint(event);
1423
+ const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
1424
+ if (crosshairButtonRegion) {
1425
+ crosshairPriceActionHandler?.({
1426
+ x: point.x,
1427
+ y: point.y,
1428
+ price: crosshairButtonRegion.price
1429
+ });
1430
+ return;
1431
+ }
1384
1432
  const orderRegion = getOrderActionRegion(point.x, point.y);
1385
1433
  if (orderRegion) {
1386
1434
  if (orderRegion.draggable) {
@@ -1493,6 +1541,11 @@ function createChart(element, options = {}) {
1493
1541
  return;
1494
1542
  }
1495
1543
  if (!isDragging || !dragMode) {
1544
+ const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
1545
+ if (crosshairButtonRegion) {
1546
+ canvas.style.cursor = "pointer";
1547
+ return;
1548
+ }
1496
1549
  const orderRegion = getOrderActionRegion(point.x, point.y);
1497
1550
  if (orderRegion) {
1498
1551
  canvas.style.cursor = orderRegion.draggable ? "ns-resize" : "pointer";
@@ -1746,6 +1799,9 @@ function createChart(element, options = {}) {
1746
1799
  const onCrosshairMove = (handler) => {
1747
1800
  crosshairMoveHandler = handler;
1748
1801
  };
1802
+ const onCrosshairPriceAction = (handler) => {
1803
+ crosshairPriceActionHandler = handler;
1804
+ };
1749
1805
  const setDoubleClickEnabled = (enabled) => {
1750
1806
  doubleClickEnabled = enabled;
1751
1807
  };
@@ -1775,6 +1831,7 @@ function createChart(element, options = {}) {
1775
1831
  onOrderAction,
1776
1832
  onChartClick,
1777
1833
  onCrosshairMove,
1834
+ onCrosshairPriceAction,
1778
1835
  zoomInX,
1779
1836
  zoomOutX,
1780
1837
  zoomInY,
package/dist/index.d.cts CHANGED
@@ -71,6 +71,16 @@ interface CrosshairOptions {
71
71
  labelBorderColor?: string;
72
72
  labelBorderWidth?: number;
73
73
  labelBorderStyle?: "solid" | "dotted" | "dashed";
74
+ showPriceActionButton?: boolean;
75
+ priceActionButtonText?: string;
76
+ priceActionButtonSize?: number;
77
+ priceActionButtonGap?: number;
78
+ priceActionButtonBackgroundColor?: string;
79
+ priceActionButtonTextColor?: string;
80
+ priceActionButtonBorderColor?: string;
81
+ priceActionButtonBorderWidth?: number;
82
+ priceActionButtonRounded?: boolean;
83
+ priceActionButtonBorderRadius?: number;
74
84
  }
75
85
  interface WatermarkOptions {
76
86
  visible?: boolean;
@@ -178,6 +188,11 @@ interface CrosshairMoveEvent {
178
188
  time?: string;
179
189
  point?: OhlcDataPoint;
180
190
  }
191
+ interface CrosshairPriceActionEvent {
192
+ x: number;
193
+ y: number;
194
+ price: number;
195
+ }
181
196
  interface TickerLineOptions {
182
197
  visible?: boolean;
183
198
  style?: "solid" | "dotted" | "dashed";
@@ -199,6 +214,7 @@ interface ChartInstance {
199
214
  onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
200
215
  onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
201
216
  onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
217
+ onCrosshairPriceAction: (handler: ((event: CrosshairPriceActionEvent) => void) | null) => void;
202
218
  zoomInX: (factor?: number) => void;
203
219
  zoomOutX: (factor?: number) => void;
204
220
  zoomInY: (factor?: number) => void;
@@ -222,4 +238,4 @@ interface OhlcDataPoint {
222
238
  }
223
239
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
224
240
 
225
- export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
241
+ export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
package/dist/index.d.ts CHANGED
@@ -71,6 +71,16 @@ interface CrosshairOptions {
71
71
  labelBorderColor?: string;
72
72
  labelBorderWidth?: number;
73
73
  labelBorderStyle?: "solid" | "dotted" | "dashed";
74
+ showPriceActionButton?: boolean;
75
+ priceActionButtonText?: string;
76
+ priceActionButtonSize?: number;
77
+ priceActionButtonGap?: number;
78
+ priceActionButtonBackgroundColor?: string;
79
+ priceActionButtonTextColor?: string;
80
+ priceActionButtonBorderColor?: string;
81
+ priceActionButtonBorderWidth?: number;
82
+ priceActionButtonRounded?: boolean;
83
+ priceActionButtonBorderRadius?: number;
74
84
  }
75
85
  interface WatermarkOptions {
76
86
  visible?: boolean;
@@ -178,6 +188,11 @@ interface CrosshairMoveEvent {
178
188
  time?: string;
179
189
  point?: OhlcDataPoint;
180
190
  }
191
+ interface CrosshairPriceActionEvent {
192
+ x: number;
193
+ y: number;
194
+ price: number;
195
+ }
181
196
  interface TickerLineOptions {
182
197
  visible?: boolean;
183
198
  style?: "solid" | "dotted" | "dashed";
@@ -199,6 +214,7 @@ interface ChartInstance {
199
214
  onOrderAction: (handler: ((event: OrderActionEvent) => void) | null) => void;
200
215
  onChartClick: (handler: ((event: ChartClickEvent) => void) | null) => void;
201
216
  onCrosshairMove: (handler: ((event: CrosshairMoveEvent) => void) | null) => void;
217
+ onCrosshairPriceAction: (handler: ((event: CrosshairPriceActionEvent) => void) | null) => void;
202
218
  zoomInX: (factor?: number) => void;
203
219
  zoomOutX: (factor?: number) => void;
204
220
  zoomInY: (factor?: number) => void;
@@ -222,4 +238,4 @@ interface OhlcDataPoint {
222
238
  }
223
239
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
224
240
 
225
- export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
241
+ export { type AxisOptions, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
package/dist/index.js CHANGED
@@ -27,7 +27,17 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
27
27
  labelBorderRadius: 3,
28
28
  labelBorderColor: "#94a3b8",
29
29
  labelBorderWidth: 1,
30
- labelBorderStyle: "solid"
30
+ labelBorderStyle: "solid",
31
+ showPriceActionButton: false,
32
+ priceActionButtonText: "+",
33
+ priceActionButtonSize: 20,
34
+ priceActionButtonGap: 6,
35
+ priceActionButtonBackgroundColor: "#1f2937",
36
+ priceActionButtonTextColor: "#e2e8f0",
37
+ priceActionButtonBorderColor: "#475569",
38
+ priceActionButtonBorderWidth: 1,
39
+ priceActionButtonRounded: true,
40
+ priceActionButtonBorderRadius: 3
31
41
  };
32
42
  var DEFAULT_WATERMARK_OPTIONS = {
33
43
  visible: false,
@@ -139,10 +149,6 @@ var DEFAULT_OPTIONS = {
139
149
  },
140
150
  dashPatterns: DEFAULT_DASH_PATTERNS
141
151
  };
142
- var BRAND_LOGO_VIEWBOX_WIDTH = 190;
143
- var BRAND_LOGO_VIEWBOX_HEIGHT = 186;
144
- var BRAND_LOGO_PATH_A = "M0 93.0171V45.2271H48.9851V75.0332C48.9851 84.9545 57.0416 93.0001 66.9763 93.0001H94.9957V186H49.0531V110.984C49.0531 101.063 40.9965 93.0171 31.0619 93.0171H0Z";
145
- var BRAND_LOGO_PATH_B = "M190 92.9915V140.782H141.015V110.975C141.015 101.054 132.958 93.0085 123.023 93.0085H95.0039V0H140.955V75.0162C140.955 84.9374 149.012 92.9831 158.946 92.9831H190V92.9915Z";
146
152
  function createChart(element, options = {}) {
147
153
  const mergedOptions = {
148
154
  ...DEFAULT_OPTIONS,
@@ -187,6 +193,8 @@ function createChart(element, options = {}) {
187
193
  let orderActionHandler = null;
188
194
  let chartClickHandler = null;
189
195
  let crosshairMoveHandler = null;
196
+ let crosshairPriceActionHandler = null;
197
+ let crosshairPriceActionRegion = null;
190
198
  let orderActionRegions = [];
191
199
  let orderDragRegions = [];
192
200
  let generatedPriceLineId = 1;
@@ -199,8 +207,6 @@ function createChart(element, options = {}) {
199
207
  let yMaxOverride = null;
200
208
  let autoYMin = null;
201
209
  let autoYMax = null;
202
- const brandLogoPathA = new Path2D(BRAND_LOGO_PATH_A);
203
- const brandLogoPathB = new Path2D(BRAND_LOGO_PATH_B);
204
210
  let watermarkImageSrc = null;
205
211
  let watermarkImage = null;
206
212
  let watermarkImageReady = false;
@@ -778,6 +784,7 @@ function createChart(element, options = {}) {
778
784
  const draw = () => {
779
785
  orderActionRegions = [];
780
786
  orderDragRegions = [];
787
+ crosshairPriceActionRegion = null;
781
788
  const pixelRatio = getPixelRatio();
782
789
  canvas.style.width = `${width}px`;
783
790
  canvas.style.height = `${height}px`;
@@ -1083,17 +1090,6 @@ function createChart(element, options = {}) {
1083
1090
  });
1084
1091
  drawText(timeLabel, x, chartBottom + 8, "center", "top", axis.textColor);
1085
1092
  }
1086
- const brandLogoWidth = 34;
1087
- const brandLogoHeight = BRAND_LOGO_VIEWBOX_HEIGHT / BRAND_LOGO_VIEWBOX_WIDTH * brandLogoWidth;
1088
- const brandLogoX = chartLeft + 16;
1089
- const brandLogoY = chartBottom - brandLogoHeight - 10;
1090
- ctx.save();
1091
- ctx.translate(brandLogoX, brandLogoY);
1092
- ctx.scale(brandLogoWidth / BRAND_LOGO_VIEWBOX_WIDTH, brandLogoHeight / BRAND_LOGO_VIEWBOX_HEIGHT);
1093
- ctx.fillStyle = "#ffffff";
1094
- ctx.fill(brandLogoPathA);
1095
- ctx.fill(brandLogoPathB);
1096
- ctx.restore();
1097
1093
  if (crosshair.visible && crosshairPoint) {
1098
1094
  const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
1099
1095
  const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
@@ -1130,6 +1126,39 @@ function createChart(element, options = {}) {
1130
1126
  fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
1131
1127
  strokeCrosshairLabel(priceX, priceY, priceWidth);
1132
1128
  drawText(priceText, priceX + labelPaddingX, priceY + labelHeight / 2, "left", "middle", labelTextColor);
1129
+ if (crosshair.showPriceActionButton) {
1130
+ const buttonSize = clamp(Math.round(crosshair.priceActionButtonSize), 12, 30);
1131
+ const buttonGap = Math.max(0, Math.round(crosshair.priceActionButtonGap));
1132
+ const buttonX = priceX - buttonGap - buttonSize;
1133
+ const buttonY = priceY + (labelHeight - buttonSize) / 2;
1134
+ const buttonRadius = crosshair.priceActionButtonRounded ? clamp(Math.round(crosshair.priceActionButtonBorderRadius), 0, buttonSize / 2) : 0;
1135
+ const buttonBorderWidth = Math.max(0, Math.round(crosshair.priceActionButtonBorderWidth));
1136
+ ctx.fillStyle = crosshair.priceActionButtonBackgroundColor;
1137
+ fillRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1138
+ if (buttonBorderWidth > 0) {
1139
+ ctx.save();
1140
+ ctx.strokeStyle = crosshair.priceActionButtonBorderColor;
1141
+ ctx.lineWidth = buttonBorderWidth;
1142
+ ctx.setLineDash([]);
1143
+ strokeRoundedRect(Math.round(buttonX), Math.round(buttonY), buttonSize, buttonSize, buttonRadius);
1144
+ ctx.restore();
1145
+ }
1146
+ drawText(
1147
+ crosshair.priceActionButtonText,
1148
+ buttonX + buttonSize / 2,
1149
+ buttonY + buttonSize / 2,
1150
+ "center",
1151
+ "middle",
1152
+ crosshair.priceActionButtonTextColor
1153
+ );
1154
+ crosshairPriceActionRegion = {
1155
+ x: buttonX,
1156
+ y: buttonY,
1157
+ width: buttonSize,
1158
+ height: buttonSize,
1159
+ price: Number(hoverPrice.toFixed(mergedOptions.priceDecimals))
1160
+ };
1161
+ }
1133
1162
  }
1134
1163
  if (crosshair.showTimeLabel) {
1135
1164
  const ratio = clamp((cx - chartLeft) / chartWidth, 0, 1);
@@ -1292,6 +1321,16 @@ function createChart(element, options = {}) {
1292
1321
  (region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
1293
1322
  );
1294
1323
  };
1324
+ const getCrosshairPriceActionRegion = (x, y) => {
1325
+ if (!crosshairPriceActionRegion) {
1326
+ return null;
1327
+ }
1328
+ const region = crosshairPriceActionRegion;
1329
+ if (x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height) {
1330
+ return region;
1331
+ }
1332
+ return null;
1333
+ };
1295
1334
  const priceFromCanvasY = (y) => {
1296
1335
  if (!drawState) {
1297
1336
  return 0;
@@ -1357,6 +1396,15 @@ function createChart(element, options = {}) {
1357
1396
  let activePointerId = null;
1358
1397
  const onPointerDown = (event) => {
1359
1398
  const point = getCanvasPoint(event);
1399
+ const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
1400
+ if (crosshairButtonRegion) {
1401
+ crosshairPriceActionHandler?.({
1402
+ x: point.x,
1403
+ y: point.y,
1404
+ price: crosshairButtonRegion.price
1405
+ });
1406
+ return;
1407
+ }
1360
1408
  const orderRegion = getOrderActionRegion(point.x, point.y);
1361
1409
  if (orderRegion) {
1362
1410
  if (orderRegion.draggable) {
@@ -1469,6 +1517,11 @@ function createChart(element, options = {}) {
1469
1517
  return;
1470
1518
  }
1471
1519
  if (!isDragging || !dragMode) {
1520
+ const crosshairButtonRegion = getCrosshairPriceActionRegion(point.x, point.y);
1521
+ if (crosshairButtonRegion) {
1522
+ canvas.style.cursor = "pointer";
1523
+ return;
1524
+ }
1472
1525
  const orderRegion = getOrderActionRegion(point.x, point.y);
1473
1526
  if (orderRegion) {
1474
1527
  canvas.style.cursor = orderRegion.draggable ? "ns-resize" : "pointer";
@@ -1722,6 +1775,9 @@ function createChart(element, options = {}) {
1722
1775
  const onCrosshairMove = (handler) => {
1723
1776
  crosshairMoveHandler = handler;
1724
1777
  };
1778
+ const onCrosshairPriceAction = (handler) => {
1779
+ crosshairPriceActionHandler = handler;
1780
+ };
1725
1781
  const setDoubleClickEnabled = (enabled) => {
1726
1782
  doubleClickEnabled = enabled;
1727
1783
  };
@@ -1751,6 +1807,7 @@ function createChart(element, options = {}) {
1751
1807
  onOrderAction,
1752
1808
  onChartClick,
1753
1809
  onCrosshairMove,
1810
+ onCrosshairPriceAction,
1754
1811
  zoomInX,
1755
1812
  zoomOutX,
1756
1813
  zoomInY,
package/docs/API.md CHANGED
@@ -94,6 +94,16 @@ Top-level options:
94
94
  - `labelBorderColor` (default `#94a3b8`)
95
95
  - `labelBorderWidth` (default `1`)
96
96
  - `labelBorderStyle` (`"solid" | "dotted" | "dashed"`, default `"solid"`)
97
+ - `showPriceActionButton` (default `false`)
98
+ - `priceActionButtonText` (default `"+"`)
99
+ - `priceActionButtonSize` (default `20`)
100
+ - `priceActionButtonGap` (default `6`)
101
+ - `priceActionButtonBackgroundColor` (default `#1f2937`)
102
+ - `priceActionButtonTextColor` (default `#e2e8f0`)
103
+ - `priceActionButtonBorderColor` (default `#475569`)
104
+ - `priceActionButtonBorderWidth` (default `1`)
105
+ - `priceActionButtonRounded` (default `true`; set `false` for square corners)
106
+ - `priceActionButtonBorderRadius` (default `3`)
97
107
 
98
108
  ### `WatermarkOptions`
99
109
 
@@ -243,6 +253,7 @@ Connector/fill visuals:
243
253
  - `onOrderAction(handler: ((event: OrderActionEvent) => void) | null): void`
244
254
  - `onChartClick(handler: ((event: ChartClickEvent) => void) | null): void`
245
255
  - `onCrosshairMove(handler: ((event: CrosshairMoveEvent) => void) | null): void`
256
+ - `onCrosshairPriceAction(handler: ((event: CrosshairPriceActionEvent) => void) | null): void`
246
257
  - `zoomInX(factor?: number): void` (default factor `1.25`)
247
258
  - `zoomOutX(factor?: number): void` (default factor `1.25`)
248
259
  - `zoomInY(factor?: number): void` (default factor `1.25`)
package/docs/EVENTS.md CHANGED
@@ -109,6 +109,33 @@ type CrosshairMoveEvent = {
109
109
 
110
110
  ---
111
111
 
112
+ ## `onCrosshairPriceAction`
113
+
114
+ Register:
115
+
116
+ ```ts
117
+ chart.onCrosshairPriceAction((event) => {
118
+ // frontend action (open order ticket, quick action, etc)
119
+ });
120
+ ```
121
+
122
+ Payload type:
123
+
124
+ ```ts
125
+ type CrosshairPriceActionEvent = {
126
+ x: number;
127
+ y: number;
128
+ price: number;
129
+ };
130
+ ```
131
+
132
+ ### Notes
133
+
134
+ - Fires when the optional crosshair price action button (default text `"+"`) is clicked.
135
+ - Enable button rendering via `crosshair.showPriceActionButton: true`.
136
+
137
+ ---
138
+
112
139
  ## Double-click integration
113
140
 
114
141
  Set behavior:
package/docs/RECIPES.md CHANGED
@@ -63,6 +63,35 @@ const chart = createChart(root, {
63
63
  });
64
64
  ```
65
65
 
66
+ ## Add crosshair "+" action button
67
+
68
+ ```ts
69
+ const chart = createChart(root, {
70
+ crosshair: {
71
+ showPriceActionButton: true,
72
+ priceActionButtonText: "+",
73
+ priceActionButtonGap: 6
74
+ }
75
+ });
76
+
77
+ chart.onCrosshairPriceAction((event) => {
78
+ // Handle in app/frontend (place order modal, quick ticket, etc.)
79
+ console.log("crosshair + clicked at price", event.price);
80
+ });
81
+ ```
82
+
83
+ Square style example:
84
+
85
+ ```ts
86
+ const chart = createChart(root, {
87
+ crosshair: {
88
+ showPriceActionButton: true,
89
+ priceActionButtonRounded: false,
90
+ priceActionButtonBorderRadius: 0
91
+ }
92
+ });
93
+ ```
94
+
66
95
  ## Add a draggable pending limit line
67
96
 
68
97
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",