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.
- package/dist/hyperprop-charting-library.cjs +316 -1
- package/dist/hyperprop-charting-library.d.ts +117 -1
- package/dist/hyperprop-charting-library.js +316 -1
- package/dist/index.cjs +316 -1
- package/dist/index.d.cts +117 -1
- package/dist/index.d.ts +117 -1
- package/dist/index.js +316 -1
- package/docs/API.md +100 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -78,6 +78,8 @@ interface ChartOptions {
|
|
|
78
78
|
pinOutOfRangeLines?: boolean;
|
|
79
79
|
doubleClickEnabled?: boolean;
|
|
80
80
|
doubleClickAction?: "reset" | "placeLimitOrder";
|
|
81
|
+
keyboard?: KeyboardOptions;
|
|
82
|
+
accessibility?: AccessibilityOptions;
|
|
81
83
|
crosshair?: CrosshairOptions;
|
|
82
84
|
grid?: GridOptions;
|
|
83
85
|
watermark?: WatermarkOptions;
|
|
@@ -164,6 +166,36 @@ interface DrawingSelectEvent {
|
|
|
164
166
|
pointIndex?: number;
|
|
165
167
|
x: number;
|
|
166
168
|
y: number;
|
|
169
|
+
/** On-screen box of the drawing, for anchoring a host toolbar to it. */
|
|
170
|
+
bounds?: DrawingBounds;
|
|
171
|
+
}
|
|
172
|
+
/** Canvas-pixel bounding box of a drawing, relative to the chart canvas. */
|
|
173
|
+
interface DrawingBounds {
|
|
174
|
+
left: number;
|
|
175
|
+
top: number;
|
|
176
|
+
right: number;
|
|
177
|
+
bottom: number;
|
|
178
|
+
centerX: number;
|
|
179
|
+
centerY: number;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Emitted whenever the selected drawing's on-screen box moves — selection,
|
|
183
|
+
* deselection (payload `null`), drag, edit, pan, zoom or resize. Hosts render
|
|
184
|
+
* their own floating toolbar and position it from `bounds`, keeping it inside
|
|
185
|
+
* `plot`; the chart never has to draw toolbar chrome itself.
|
|
186
|
+
*/
|
|
187
|
+
interface DrawingSelectionChangeEvent {
|
|
188
|
+
drawing: DrawingObjectOptions;
|
|
189
|
+
bounds: DrawingBounds;
|
|
190
|
+
/** Plot rectangle, so a toolbar can be clamped inside the chart area. */
|
|
191
|
+
plot: {
|
|
192
|
+
left: number;
|
|
193
|
+
top: number;
|
|
194
|
+
right: number;
|
|
195
|
+
bottom: number;
|
|
196
|
+
};
|
|
197
|
+
/** True while the drawing is actively being dragged or resized. */
|
|
198
|
+
dragging: boolean;
|
|
167
199
|
}
|
|
168
200
|
interface DrawingHoverEvent {
|
|
169
201
|
drawing: DrawingObjectOptions | null;
|
|
@@ -470,6 +502,67 @@ interface CrosshairPriceActionEvent {
|
|
|
470
502
|
y: number;
|
|
471
503
|
price: number;
|
|
472
504
|
}
|
|
505
|
+
/**
|
|
506
|
+
* What the pointer was over when the context menu was requested. Hosts switch
|
|
507
|
+
* on this to show the right menu instead of re-deriving it from coordinates.
|
|
508
|
+
*/
|
|
509
|
+
type ChartContextMenuRegion = "plot" | "drawing" | "indicator-pane" | "x-axis" | "y-axis";
|
|
510
|
+
interface ChartContextMenuEvent {
|
|
511
|
+
region: ChartContextMenuRegion;
|
|
512
|
+
/** Canvas-relative position. */
|
|
513
|
+
x: number;
|
|
514
|
+
y: number;
|
|
515
|
+
/** Viewport position, ready to place a DOM menu without re-measuring. */
|
|
516
|
+
clientX: number;
|
|
517
|
+
clientY: number;
|
|
518
|
+
/** Price under the pointer (main pane regions only). */
|
|
519
|
+
price?: number;
|
|
520
|
+
index?: number;
|
|
521
|
+
time?: string;
|
|
522
|
+
point?: OhlcDataPoint;
|
|
523
|
+
/** Set for region "drawing"; the drawing is also selected by the chart. */
|
|
524
|
+
drawing?: DrawingObjectOptions;
|
|
525
|
+
drawingTarget?: "line" | "handle";
|
|
526
|
+
pointIndex?: number;
|
|
527
|
+
/** Set for region "indicator-pane". */
|
|
528
|
+
indicator?: {
|
|
529
|
+
id: string;
|
|
530
|
+
type: string;
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
/** Built-in keyboard actions, reported after the chart applies them. */
|
|
534
|
+
type ChartKeyboardAction = "delete-drawing" | "cancel" | "pan-left" | "pan-right" | "pan-up" | "pan-down" | "zoom-in" | "zoom-out" | "reset-viewport" | "scroll-to-realtime";
|
|
535
|
+
interface ChartKeyboardShortcutEvent {
|
|
536
|
+
action: ChartKeyboardAction;
|
|
537
|
+
/** The originating `KeyboardEvent.key`. */
|
|
538
|
+
key: string;
|
|
539
|
+
shiftKey: boolean;
|
|
540
|
+
/** The drawing that was removed, for "delete-drawing" (so hosts can undo). */
|
|
541
|
+
drawing?: DrawingObjectOptions;
|
|
542
|
+
}
|
|
543
|
+
interface KeyboardOptions {
|
|
544
|
+
/**
|
|
545
|
+
* Master switch for the built-in shortcuts. Shortcuts only fire while the
|
|
546
|
+
* chart canvas has focus, so they never swallow typing in host inputs.
|
|
547
|
+
* Default true.
|
|
548
|
+
*/
|
|
549
|
+
enabled?: boolean;
|
|
550
|
+
/** Bars panned per arrow-key press (Shift multiplies by 10). Default 1. */
|
|
551
|
+
panBars?: number;
|
|
552
|
+
/** Delete/Backspace removes the selected drawing. Default true. */
|
|
553
|
+
deleteSelectedDrawing?: boolean;
|
|
554
|
+
}
|
|
555
|
+
interface AccessibilityOptions {
|
|
556
|
+
/** aria-label on the canvas. Default "Price chart". */
|
|
557
|
+
label?: string;
|
|
558
|
+
/**
|
|
559
|
+
* Longer description exposed via aria-description, e.g. the symbol and
|
|
560
|
+
* interval currently shown.
|
|
561
|
+
*/
|
|
562
|
+
description?: string;
|
|
563
|
+
/** Make the canvas focusable so keyboard users can reach it. Default true. */
|
|
564
|
+
focusable?: boolean;
|
|
565
|
+
}
|
|
473
566
|
interface TickerLineOptions {
|
|
474
567
|
visible?: boolean;
|
|
475
568
|
style?: "solid" | "dotted" | "dashed";
|
|
@@ -581,6 +674,29 @@ interface ChartInstance {
|
|
|
581
674
|
onDrawingSelect: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
582
675
|
onDrawingDoubleClick: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
583
676
|
onDrawingEditText: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
677
|
+
/**
|
|
678
|
+
* Selection geometry stream for a host-rendered floating toolbar. Fires on
|
|
679
|
+
* select/deselect and on every frame where the selected drawing's box moves
|
|
680
|
+
* (drag, pan, zoom, resize), so the toolbar can track the shape instead of
|
|
681
|
+
* sitting in a fixed corner. Payload is `null` when nothing is selected.
|
|
682
|
+
*/
|
|
683
|
+
onSelectionChange: (handler: ((event: DrawingSelectionChangeEvent | null) => void) | null) => void;
|
|
684
|
+
/** Current selection and its on-screen box, for imperative reads. */
|
|
685
|
+
getSelectedDrawing: () => {
|
|
686
|
+
drawing: DrawingObjectOptions;
|
|
687
|
+
bounds: DrawingBounds;
|
|
688
|
+
} | null;
|
|
689
|
+
/**
|
|
690
|
+
* Right-click (or long-press equivalent) with everything the host needs to
|
|
691
|
+
* open the correct menu: which region was hit, the price/bar under the
|
|
692
|
+
* pointer, and the drawing or indicator pane it landed on. The chart always
|
|
693
|
+
* suppresses the browser's native menu.
|
|
694
|
+
*/
|
|
695
|
+
onContextMenu: (handler: ((event: ChartContextMenuEvent) => void) | null) => void;
|
|
696
|
+
/** Fires after a built-in keyboard shortcut is applied. */
|
|
697
|
+
onKeyboardShortcut: (handler: ((event: ChartKeyboardShortcutEvent) => void) | null) => void;
|
|
698
|
+
/** Focus the chart canvas so keyboard shortcuts apply to it. */
|
|
699
|
+
focus: () => void;
|
|
584
700
|
setMagnetMode: (mode: "none" | "weak" | "strong") => void;
|
|
585
701
|
getMagnetMode: () => "none" | "weak" | "strong";
|
|
586
702
|
/**
|
|
@@ -781,4 +897,4 @@ declare const compileScriptIndicator: (definition: ScriptIndicatorDefinition) =>
|
|
|
781
897
|
|
|
782
898
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
783
899
|
|
|
784
|
-
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type ChartSavedState, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type PriceScaleMode, type PriceScaleOptions, SCRIPT_SOURCE_INPUT_VALUES, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptInputHelper, type ScriptInputOptions, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, extractScriptInputs, validateScriptSource };
|
|
900
|
+
export { type AccessibilityOptions, type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartContextMenuEvent, type ChartContextMenuRegion, type ChartInstance, type ChartKeyboardAction, type ChartKeyboardShortcutEvent, type ChartOptions, type ChartSavedState, type ChartType, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingBounds, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingSelectionChangeEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneActionEvent, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneHeightChangeEvent, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type KeyboardOptions, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type PriceScaleMode, type PriceScaleOptions, SCRIPT_SOURCE_INPUT_VALUES, type ScriptComputeResult, type ScriptIndicatorDefinition, type ScriptIndicatorInputDef, type ScriptInputHelper, type ScriptInputOptions, type ScriptPlotSpec, type TickerLineOptions, type TradeMarkerOptions, type ViewportState, type WatermarkOptions, compileScriptIndicator, createChart, extractScriptInputs, validateScriptSource };
|
package/dist/index.js
CHANGED
|
@@ -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,
|
package/docs/API.md
CHANGED
|
@@ -557,6 +557,11 @@ an up measurement, red for down.
|
|
|
557
557
|
- `fitContent(): void` (x-only fit, keeps y zoom)
|
|
558
558
|
- `resetViewport(): void` (fit x + reset y auto-scale)
|
|
559
559
|
- `setSelectedDrawing(id: string | null): void` (marks a drawing selected; only the selected/drafted drawing renders its handles, and position tools render their lines/labels only while selected)
|
|
560
|
+
- `onSelectionChange(handler: ((event: DrawingSelectionChangeEvent | null) => void) | null): void` — selection geometry stream for a floating toolbar; see "Context menus, selection toolbars and keyboard" below
|
|
561
|
+
- `getSelectedDrawing(): { drawing: DrawingObjectOptions; bounds: DrawingBounds } | null`
|
|
562
|
+
- `onContextMenu(handler: ((event: ChartContextMenuEvent) => void) | null): void` — typed right-click with region + hit info
|
|
563
|
+
- `onKeyboardShortcut(handler: ((event: ChartKeyboardShortcutEvent) => void) | null): void`
|
|
564
|
+
- `focus(): void` — focus the canvas so keyboard shortcuts apply
|
|
560
565
|
- `onDrawingDoubleClick(handler): void` (fires when a drawing is double-clicked; use it to open a settings dialog, e.g. for position tools)
|
|
561
566
|
- `onDrawingEditText(handler): void` (fires when a `text`/`note`/`callout` tool is placed or double-clicked; use it to show an inline text editor at `{x, y}` and write the result back via `updateDrawing(id, { label })`). These drawings store their content in `label` and size in `fontSize`.
|
|
562
567
|
- `setTradeMarkers(markers: TradeMarkerOptions[]): void` — draw trade execution markers (arrow + `qty @ price` label) on the candle of each fill's `time`. Buy = arrow below the low pointing up; sell = arrow above the high pointing down. Each marker: `{ time (ms|ISO), price, side: "buy"|"sell", qty?, text?, color?, textColor? }` (defaults: buy `#2962ff`, sell `#f23645`, text `#d1d4dc`). Markers resolve to bars by time, so they persist across timeframe switches and scrolling. Pass `[]` to clear.
|
|
@@ -605,6 +610,101 @@ link.click();
|
|
|
605
610
|
|
|
606
611
|
---
|
|
607
612
|
|
|
613
|
+
## Context menus, selection toolbars and keyboard
|
|
614
|
+
|
|
615
|
+
Three pieces of chrome that every host previously rebuilt by hand. The chart
|
|
616
|
+
does the hit-testing and geometry; the host still renders its own menus and
|
|
617
|
+
toolbar, so it keeps full control of styling and framework.
|
|
618
|
+
|
|
619
|
+
### `onContextMenu(handler)`
|
|
620
|
+
|
|
621
|
+
Right-click anywhere on the chart. The browser's native menu is always
|
|
622
|
+
suppressed. The event says *what* was under the pointer, so a host switches on
|
|
623
|
+
`region` instead of re-deriving it from coordinates:
|
|
624
|
+
|
|
625
|
+
```ts
|
|
626
|
+
chart.onContextMenu((event) => {
|
|
627
|
+
switch (event.region) {
|
|
628
|
+
case "drawing":
|
|
629
|
+
// event.drawing / drawingTarget / pointIndex — the chart has already
|
|
630
|
+
// selected this drawing, exactly like TradingView.
|
|
631
|
+
showDrawingMenu(event.clientX, event.clientY, event.drawing!);
|
|
632
|
+
break;
|
|
633
|
+
case "indicator-pane":
|
|
634
|
+
showIndicatorMenu(event.clientX, event.clientY, event.indicator!); // { id, type }
|
|
635
|
+
break;
|
|
636
|
+
case "y-axis":
|
|
637
|
+
showPriceScaleMenu(event.clientX, event.clientY); // log / percent / invert
|
|
638
|
+
break;
|
|
639
|
+
case "x-axis":
|
|
640
|
+
showTimeScaleMenu(event.clientX, event.clientY);
|
|
641
|
+
break;
|
|
642
|
+
case "plot":
|
|
643
|
+
showChartMenu(event.clientX, event.clientY, event.price); // + index/time/point
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
`clientX`/`clientY` are viewport coordinates, ready to position a DOM menu;
|
|
650
|
+
`x`/`y` are canvas-relative. `price`, `index`, `time` and `point` describe the
|
|
651
|
+
bar under the pointer (`price` only in the main pane).
|
|
652
|
+
|
|
653
|
+
### `onSelectionChange(handler)` — floating drawing toolbar
|
|
654
|
+
|
|
655
|
+
Fires when a drawing is selected or deselected (payload `null`) **and on every
|
|
656
|
+
frame where the selected drawing's box moves** — drag, resize, pan, zoom. That
|
|
657
|
+
is what lets a host toolbar sit above the shape and track it, instead of
|
|
658
|
+
floating at a fixed corner:
|
|
659
|
+
|
|
660
|
+
```ts
|
|
661
|
+
chart.onSelectionChange((event) => {
|
|
662
|
+
if (!event) return hideToolbar();
|
|
663
|
+
const rect = container.getBoundingClientRect();
|
|
664
|
+
// Above the shape, flipping below when there's no room.
|
|
665
|
+
const above = rect.top + event.bounds.top - toolbarHeight - 10;
|
|
666
|
+
positionToolbar({
|
|
667
|
+
left: rect.left + event.bounds.centerX - toolbarWidth / 2,
|
|
668
|
+
top: above > rect.top + 8 ? above : rect.top + event.bounds.bottom + 10,
|
|
669
|
+
});
|
|
670
|
+
showToolbarFor(event.drawing);
|
|
671
|
+
});
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
`bounds` is `{ left, top, right, bottom, centerX, centerY }` in canvas pixels,
|
|
675
|
+
clamped to the plot; `plot` gives the plot rectangle for keeping the toolbar
|
|
676
|
+
inside the chart; `dragging` is true mid-drag (dim or lock the toolbar if you
|
|
677
|
+
prefer it out of the way). `getSelectedDrawing()` returns the same
|
|
678
|
+
`{ drawing, bounds }` on demand, and `DrawingSelectEvent` now carries `bounds`
|
|
679
|
+
too.
|
|
680
|
+
|
|
681
|
+
### Keyboard shortcuts and accessibility
|
|
682
|
+
|
|
683
|
+
The canvas is focusable (`tabIndex 0`, `role="application"`, `aria-label`,
|
|
684
|
+
`aria-keyshortcuts`) and takes focus on pointer-down, so shortcuts apply to the
|
|
685
|
+
chart the user is actually using and **never swallow typing in host inputs**.
|
|
686
|
+
The focus ring only shows for keyboard focus.
|
|
687
|
+
|
|
688
|
+
| Key | Action |
|
|
689
|
+
| --- | --- |
|
|
690
|
+
| `←` / `→` | Pan one bar (`Shift` = 10) |
|
|
691
|
+
| `↑` / `↓` | Pan price by 5% of the visible range (`Shift` = 5×) |
|
|
692
|
+
| `+` / `-` | Zoom in / out |
|
|
693
|
+
| `Delete` / `Backspace` | Delete the selected drawing |
|
|
694
|
+
| `Escape` | Cancel a half-drawn shape, then the armed tool, then the selection |
|
|
695
|
+
| `Home` | Fit all bars |
|
|
696
|
+
| `End` | Scroll to realtime |
|
|
697
|
+
| `R` | Reset the viewport |
|
|
698
|
+
|
|
699
|
+
Chords with Cmd/Ctrl/Alt are ignored so host and browser shortcuts still work.
|
|
700
|
+
Configure with `keyboard: { enabled, panBars, deleteSelectedDrawing }` and
|
|
701
|
+
`accessibility: { label, description, focusable }` in `ChartOptions`, and
|
|
702
|
+
subscribe to `onKeyboardShortcut(handler)` to mirror actions in your own UI
|
|
703
|
+
(the event includes the removed `drawing` for `"delete-drawing"`, so hosts can
|
|
704
|
+
offer undo). `focus()` puts keyboard focus on the chart programmatically.
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
608
708
|
## Script Indicators (user-authored)
|
|
609
709
|
|
|
610
710
|
`compileScriptIndicator(definition: ScriptIndicatorDefinition): IndicatorPlugin` — compiles a
|