hyperprop-charting-library 0.1.137 → 0.1.138

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.
@@ -2819,6 +2819,8 @@ var DEFAULT_OPTIONS = {
2819
2819
  pinOutOfRangeLines: false,
2820
2820
  doubleClickEnabled: true,
2821
2821
  doubleClickAction: "reset",
2822
+ keyboard: { enabled: true, panBars: 1, deleteSelectedDrawing: true },
2823
+ accessibility: { label: "Price chart", description: "", focusable: true },
2822
2824
  crosshair: DEFAULT_CROSSHAIR_OPTIONS,
2823
2825
  grid: DEFAULT_GRID_OPTIONS,
2824
2826
  watermark: DEFAULT_WATERMARK_OPTIONS,
@@ -2861,6 +2863,14 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
2861
2863
  ...options.axisColor ? { lineColor: options.axisColor, textColor: options.axisColor } : {},
2862
2864
  ...options.yAxis ?? {}
2863
2865
  },
2866
+ keyboard: {
2867
+ ...baseOptions.keyboard,
2868
+ ...options.keyboard ?? {}
2869
+ },
2870
+ accessibility: {
2871
+ ...baseOptions.accessibility,
2872
+ ...options.accessibility ?? {}
2873
+ },
2864
2874
  crosshair: {
2865
2875
  ...baseOptions.crosshair,
2866
2876
  ...options.crosshair ?? {}
@@ -3034,6 +3044,10 @@ function createChart(element, options = {}) {
3034
3044
  let drawingSelectHandler = null;
3035
3045
  let drawingDoubleClickHandler = null;
3036
3046
  let drawingEditTextHandler = null;
3047
+ let selectionChangeHandler = null;
3048
+ let contextMenuHandler = null;
3049
+ let keyboardShortcutHandler = null;
3050
+ let lastSelectionSignature = null;
3037
3051
  let magnetMode = "none";
3038
3052
  let priceScaleMode = "linear";
3039
3053
  let priceScaleInverted = false;
@@ -3196,6 +3210,39 @@ function createChart(element, options = {}) {
3196
3210
  canvas.style.setProperty("-webkit-user-select", "none");
3197
3211
  canvas.style.setProperty("-webkit-touch-callout", "none");
3198
3212
  canvas.setAttribute("draggable", "false");
3213
+ const applyAccessibilityAttributes = () => {
3214
+ const a11y = mergedOptions.accessibility ?? {};
3215
+ const focusable = a11y.focusable !== false;
3216
+ if (focusable) {
3217
+ canvas.tabIndex = 0;
3218
+ } else {
3219
+ canvas.removeAttribute("tabindex");
3220
+ }
3221
+ canvas.setAttribute("role", "application");
3222
+ canvas.setAttribute("aria-label", a11y.label ?? "Price chart");
3223
+ if (a11y.description) {
3224
+ canvas.setAttribute("aria-description", a11y.description);
3225
+ } else {
3226
+ canvas.removeAttribute("aria-description");
3227
+ }
3228
+ if (focusable && (mergedOptions.keyboard?.enabled ?? true)) {
3229
+ canvas.setAttribute(
3230
+ "aria-keyshortcuts",
3231
+ "ArrowLeft ArrowRight ArrowUp ArrowDown Plus Minus Home End Delete Escape"
3232
+ );
3233
+ } else {
3234
+ canvas.removeAttribute("aria-keyshortcuts");
3235
+ }
3236
+ };
3237
+ applyAccessibilityAttributes();
3238
+ let focusFromPointer = false;
3239
+ canvas.addEventListener("focus", () => {
3240
+ canvas.style.outline = focusFromPointer ? "none" : "";
3241
+ focusFromPointer = false;
3242
+ });
3243
+ canvas.addEventListener("blur", () => {
3244
+ canvas.style.outline = "none";
3245
+ });
3199
3246
  element.innerHTML = "";
3200
3247
  element.appendChild(canvas);
3201
3248
  const baseCanvas = document.createElement("canvas");
@@ -6423,6 +6470,7 @@ function createChart(element, options = {}) {
6423
6470
  xSpan
6424
6471
  };
6425
6472
  paintCrosshairOverlay();
6473
+ emitSelectionChangeIfMoved();
6426
6474
  };
6427
6475
  const paintCrosshairOverlay = () => {
6428
6476
  crosshairPriceActionRegion = null;
@@ -7023,6 +7071,98 @@ function createChart(element, options = {}) {
7023
7071
  const ratio = (priceScaleTransform(price) - scaledMin) / scaledRange;
7024
7072
  return priceScaleInverted ? drawState.chartTop + ratio * drawState.chartHeight : drawState.chartBottom - ratio * drawState.chartHeight;
7025
7073
  };
7074
+ const getDrawingBounds = (drawing) => {
7075
+ if (!drawState || drawing.points.length === 0) {
7076
+ return null;
7077
+ }
7078
+ const { chartLeft, chartRight, chartTop, chartBottom } = drawState;
7079
+ let left;
7080
+ let right;
7081
+ let top;
7082
+ let bottom;
7083
+ if (drawing.type === "horizontal-line") {
7084
+ const y = canvasYFromDrawingPrice(drawing.points[0].price);
7085
+ left = chartLeft;
7086
+ right = chartRight;
7087
+ top = y;
7088
+ bottom = y;
7089
+ } else if (drawing.type === "vertical-line") {
7090
+ const x = canvasXFromDrawingPoint(drawing.points[0]);
7091
+ left = x;
7092
+ right = x;
7093
+ top = chartTop;
7094
+ bottom = chartBottom;
7095
+ } else {
7096
+ const xs = drawing.points.map((point) => canvasXFromDrawingPoint(point));
7097
+ const ys = drawing.points.map((point) => canvasYFromDrawingPrice(point.price));
7098
+ left = Math.min(...xs);
7099
+ right = Math.max(...xs);
7100
+ top = Math.min(...ys);
7101
+ bottom = Math.max(...ys);
7102
+ if (drawing.type === "ray") {
7103
+ right = chartRight;
7104
+ }
7105
+ }
7106
+ const clampX = (value) => clamp(value, chartLeft, chartRight);
7107
+ const clampY = (value) => clamp(value, chartTop, chartBottom);
7108
+ left = clampX(left);
7109
+ right = clampX(right);
7110
+ top = clampY(top);
7111
+ bottom = clampY(bottom);
7112
+ return {
7113
+ left,
7114
+ top,
7115
+ right,
7116
+ bottom,
7117
+ centerX: (left + right) / 2,
7118
+ centerY: (top + bottom) / 2
7119
+ };
7120
+ };
7121
+ const getSelectedDrawingWithBounds = () => {
7122
+ if (!selectedDrawingId) {
7123
+ return null;
7124
+ }
7125
+ const drawing = drawings.find((entry) => entry.id === selectedDrawingId);
7126
+ if (!drawing || !drawing.visible) {
7127
+ return null;
7128
+ }
7129
+ const bounds = getDrawingBounds(drawing);
7130
+ return bounds ? { drawing: serializeDrawing(drawing), bounds } : null;
7131
+ };
7132
+ const emitSelectionChangeIfMoved = () => {
7133
+ if (!selectionChangeHandler) {
7134
+ lastSelectionSignature = null;
7135
+ return;
7136
+ }
7137
+ const selection = getSelectedDrawingWithBounds();
7138
+ if (!selection || !drawState) {
7139
+ if (lastSelectionSignature !== null) {
7140
+ lastSelectionSignature = null;
7141
+ selectionChangeHandler(null);
7142
+ }
7143
+ return;
7144
+ }
7145
+ const dragging = drawingDragState !== null;
7146
+ const { bounds } = selection;
7147
+ const signature = `${selection.drawing.id}:${Math.round(bounds.left)}:${Math.round(bounds.top)}:${Math.round(
7148
+ bounds.right
7149
+ )}:${Math.round(bounds.bottom)}:${dragging ? 1 : 0}`;
7150
+ if (signature === lastSelectionSignature) {
7151
+ return;
7152
+ }
7153
+ lastSelectionSignature = signature;
7154
+ selectionChangeHandler({
7155
+ drawing: selection.drawing,
7156
+ bounds,
7157
+ plot: {
7158
+ left: drawState.chartLeft,
7159
+ top: drawState.chartTop,
7160
+ right: drawState.chartRight,
7161
+ bottom: drawState.chartBottom
7162
+ },
7163
+ dragging
7164
+ });
7165
+ };
7026
7166
  const distanceToSegment = (x, y, x1, y1, x2, y2) => {
7027
7167
  const dx = x2 - x1;
7028
7168
  const dy = y2 - y1;
@@ -7989,6 +8129,10 @@ function createChart(element, options = {}) {
7989
8129
  if (event.pointerType === "touch" || event.pointerType === "pen") {
7990
8130
  event.preventDefault();
7991
8131
  }
8132
+ if (mergedOptions.accessibility?.focusable !== false && document.activeElement !== canvas) {
8133
+ focusFromPointer = true;
8134
+ canvas.focus({ preventScroll: true });
8135
+ }
7992
8136
  cancelKineticPan();
7993
8137
  panVelocitySamples.length = 0;
7994
8138
  magnetModifierActive = event.metaKey || event.ctrlKey;
@@ -8106,12 +8250,14 @@ function createChart(element, options = {}) {
8106
8250
  const drawingHit = getDrawingHit(point.x, point.y);
8107
8251
  if (drawingHit) {
8108
8252
  selectedDrawingId = drawingHit.drawing.id;
8253
+ const selectBounds = getDrawingBounds(drawingHit.drawing);
8109
8254
  drawingSelectHandler?.({
8110
8255
  drawing: serializeDrawing(drawingHit.drawing),
8111
8256
  target: drawingHit.target,
8112
8257
  ...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
8113
8258
  x: point.x,
8114
- y: point.y
8259
+ y: point.y,
8260
+ ...selectBounds ? { bounds: selectBounds } : {}
8115
8261
  });
8116
8262
  if (!drawingHit.drawing.locked) {
8117
8263
  const startCanvasPoint = drawingPointFromCanvas(point.x, point.y, false);
@@ -8672,7 +8818,152 @@ function createChart(element, options = {}) {
8672
8818
  };
8673
8819
  const onContextMenu = (event) => {
8674
8820
  event.preventDefault();
8821
+ if (!contextMenuHandler || !drawState) {
8822
+ return;
8823
+ }
8824
+ const point = getCanvasPoint(event);
8825
+ const hitRegion = getHitRegion(point.x, point.y);
8826
+ if (hitRegion === "outside") {
8827
+ return;
8828
+ }
8829
+ const pane = hitRegion === "plot" ? getPaneAt(point.x, point.y) : null;
8830
+ const drawingHit = hitRegion === "plot" && !pane ? getDrawingHit(point.x, point.y) : null;
8831
+ if (drawingHit) {
8832
+ if (selectedDrawingId !== drawingHit.drawing.id) {
8833
+ selectedDrawingId = drawingHit.drawing.id;
8834
+ scheduleDraw();
8835
+ }
8836
+ }
8837
+ const region = drawingHit ? "drawing" : pane ? "indicator-pane" : hitRegion;
8838
+ const index = indexFromCanvasX(point.x);
8839
+ const barTime = index === null ? null : getTimeForIndex(index);
8840
+ const bar = index === null ? void 0 : data[index];
8841
+ const inMainPane = !pane && (hitRegion === "plot" || hitRegion === "y-axis");
8842
+ contextMenuHandler({
8843
+ region,
8844
+ x: point.x,
8845
+ y: point.y,
8846
+ clientX: event.clientX,
8847
+ clientY: event.clientY,
8848
+ ...inMainPane ? { price: roundToPricePrecision(priceFromCanvasY(point.y)) } : {},
8849
+ ...index === null ? {} : { index },
8850
+ ...barTime ? { time: barTime.toISOString() } : {},
8851
+ ...bar ? {
8852
+ point: {
8853
+ t: bar.time.toISOString(),
8854
+ o: bar.o,
8855
+ h: bar.h,
8856
+ l: bar.l,
8857
+ c: bar.c,
8858
+ ...bar.v === void 0 ? {} : { v: bar.v }
8859
+ }
8860
+ } : {},
8861
+ ...drawingHit ? {
8862
+ drawing: serializeDrawing(drawingHit.drawing),
8863
+ drawingTarget: drawingHit.target,
8864
+ ...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex }
8865
+ } : {},
8866
+ ...pane ? { indicator: { id: pane.id, type: pane.type } } : {}
8867
+ });
8868
+ };
8869
+ const onCanvasKeyDown = (event) => {
8870
+ const keyboard = mergedOptions.keyboard ?? {};
8871
+ if (keyboard.enabled === false) {
8872
+ return;
8873
+ }
8874
+ if (event.altKey || event.ctrlKey || event.metaKey) {
8875
+ return;
8876
+ }
8877
+ const panStep = Math.max(1, keyboard.panBars ?? 1) * (event.shiftKey ? 10 : 1);
8878
+ const emit = (action, drawing) => {
8879
+ keyboardShortcutHandler?.({
8880
+ action,
8881
+ key: event.key,
8882
+ shiftKey: event.shiftKey,
8883
+ ...drawing ? { drawing } : {}
8884
+ });
8885
+ };
8886
+ switch (event.key) {
8887
+ case "Delete":
8888
+ case "Backspace": {
8889
+ if (keyboard.deleteSelectedDrawing === false || !selectedDrawingId) {
8890
+ return;
8891
+ }
8892
+ const target = drawings.find((entry) => entry.id === selectedDrawingId);
8893
+ if (!target) {
8894
+ return;
8895
+ }
8896
+ const removed = serializeDrawing(target);
8897
+ removeDrawing(selectedDrawingId);
8898
+ selectedDrawingId = null;
8899
+ scheduleDraw();
8900
+ emit("delete-drawing", removed);
8901
+ break;
8902
+ }
8903
+ case "Escape": {
8904
+ if (cancelDrawing()) {
8905
+ emit("cancel");
8906
+ break;
8907
+ }
8908
+ if (activeDrawingTool) {
8909
+ setActiveDrawingTool(null);
8910
+ emit("cancel");
8911
+ break;
8912
+ }
8913
+ if (selectedDrawingId) {
8914
+ setSelectedDrawing(null);
8915
+ emit("cancel");
8916
+ break;
8917
+ }
8918
+ return;
8919
+ }
8920
+ case "ArrowLeft":
8921
+ panX(-panStep);
8922
+ emit("pan-left");
8923
+ break;
8924
+ case "ArrowRight":
8925
+ panX(panStep);
8926
+ emit("pan-right");
8927
+ break;
8928
+ case "ArrowUp":
8929
+ case "ArrowDown": {
8930
+ if (!drawState) {
8931
+ return;
8932
+ }
8933
+ const step = (drawState.yMax - drawState.yMin) / 20 * (event.shiftKey ? 5 : 1);
8934
+ panY(event.key === "ArrowUp" ? step : -step);
8935
+ emit(event.key === "ArrowUp" ? "pan-up" : "pan-down");
8936
+ break;
8937
+ }
8938
+ case "+":
8939
+ case "=":
8940
+ zoomInX();
8941
+ emit("zoom-in");
8942
+ break;
8943
+ case "-":
8944
+ case "_":
8945
+ zoomOutX();
8946
+ emit("zoom-out");
8947
+ break;
8948
+ case "Home":
8949
+ fitContent();
8950
+ emit("reset-viewport");
8951
+ break;
8952
+ case "End":
8953
+ setFollowingLatest(true);
8954
+ emit("scroll-to-realtime");
8955
+ break;
8956
+ case "r":
8957
+ case "R":
8958
+ resetViewport();
8959
+ emit("reset-viewport");
8960
+ break;
8961
+ default:
8962
+ return;
8963
+ }
8964
+ event.preventDefault();
8675
8965
  };
8966
+ canvas.addEventListener("keydown", onCanvasKeyDown);
8676
8967
  canvas.addEventListener("pointerdown", onPointerDown);
8677
8968
  canvas.addEventListener("pointermove", onPointerMove);
8678
8969
  canvas.addEventListener("pointerup", endPointerDrag);
@@ -8719,6 +9010,7 @@ function createChart(element, options = {}) {
8719
9010
  resetRightMarginCache();
8720
9011
  doubleClickEnabled = mergedOptions.doubleClickEnabled;
8721
9012
  doubleClickAction = mergedOptions.doubleClickAction;
9013
+ applyAccessibilityAttributes();
8722
9014
  const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
8723
9015
  if (!isTickerSmoothingEnabled) {
8724
9016
  smoothedTickerPrice = null;
@@ -9102,6 +9394,23 @@ function createChart(element, options = {}) {
9102
9394
  const onDrawingHover = (handler) => {
9103
9395
  drawingHoverHandler = handler;
9104
9396
  };
9397
+ const onSelectionChange = (handler) => {
9398
+ selectionChangeHandler = handler;
9399
+ lastSelectionSignature = null;
9400
+ if (handler) {
9401
+ emitSelectionChangeIfMoved();
9402
+ }
9403
+ };
9404
+ const getSelectedDrawing = () => getSelectedDrawingWithBounds();
9405
+ const onContextMenuHandler = (handler) => {
9406
+ contextMenuHandler = handler;
9407
+ };
9408
+ const onKeyboardShortcut = (handler) => {
9409
+ keyboardShortcutHandler = handler;
9410
+ };
9411
+ const focus = () => {
9412
+ canvas.focus({ preventScroll: true });
9413
+ };
9105
9414
  const saveState = () => {
9106
9415
  const drawingDefaults = {};
9107
9416
  for (const [tool, defaults] of drawingToolDefaults) {
@@ -9166,6 +9475,7 @@ function createChart(element, options = {}) {
9166
9475
  drawRafId = null;
9167
9476
  pendingDrawUpdateAutoScale = false;
9168
9477
  }
9478
+ canvas.removeEventListener("keydown", onCanvasKeyDown);
9169
9479
  canvas.removeEventListener("pointerdown", onPointerDown);
9170
9480
  canvas.removeEventListener("pointermove", onPointerMove);
9171
9481
  canvas.removeEventListener("pointerup", endPointerDrag);
@@ -9229,6 +9539,11 @@ function createChart(element, options = {}) {
9229
9539
  onDrawingSelect,
9230
9540
  onDrawingDoubleClick,
9231
9541
  onDrawingEditText,
9542
+ onSelectionChange,
9543
+ getSelectedDrawing,
9544
+ onContextMenu: onContextMenuHandler,
9545
+ onKeyboardShortcut,
9546
+ focus,
9232
9547
  setMagnetMode,
9233
9548
  getMagnetMode,
9234
9549
  setPriceScale,