@luxalgo/vela 0.6.8 → 0.6.9
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/{DataProvider-BsQM2WNH.d.cts → DataProvider-8Z95Q-RJ.d.cts} +1 -1
- package/dist/{DataProvider-BSLlBpB9.d.ts → DataProvider-DExJrfut.d.ts} +1 -1
- package/dist/{chunk-2R7BANEM.js → chunk-62SVGONC.js} +2 -2
- package/dist/{chunk-SQETIBFU.js → chunk-6WDDVMBJ.js} +502 -139
- package/dist/{chunk-L3I2CCYO.js → chunk-EQCHJZOT.js} +49 -2
- package/dist/{chunk-OICPLNU7.js → chunk-FCVIG7JG.js} +188 -2
- package/dist/{chunk-N7LGMKCE.js → chunk-STHSKXOR.js} +107 -84
- package/dist/{chunk-PN5KWFZ4.js → chunk-WFSZBX3R.js} +2 -2
- package/dist/{contributions-Vw-Hm58R.d.cts → contributions-C1U2Krwg.d.cts} +99 -7
- package/dist/{contributions-DLLdV9jD.d.ts → contributions-D7PVZO2i.d.ts} +99 -7
- package/dist/index.cjs +709 -144
- package/dist/index.d.cts +12 -7
- package/dist/index.d.ts +12 -7
- package/dist/index.js +4 -4
- package/dist/{options-BaVTMXaO.d.ts → options-FM0peknS.d.cts} +84 -7
- package/dist/{options-BaVTMXaO.d.cts → options-FM0peknS.d.ts} +84 -7
- package/dist/{plugin-CC7rBrOY.d.ts → plugin-7bkF32Rk.d.ts} +3 -3
- package/dist/{plugin-BnJgjLAy.d.cts → plugin-DfVqBz9p.d.cts} +3 -3
- package/dist/plugin.cjs +18 -2
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{statusline-DqzBqy42.d.cts → statusline-DOPiT6I6.d.cts} +5 -3
- package/dist/{statusline-CGkB2EOo.d.ts → statusline-zdF4eZLr.d.ts} +5 -3
- package/dist/ui.cjs +201 -4
- package/dist/ui.d.cts +89 -2
- package/dist/ui.d.ts +89 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +709 -144
- package/dist/vela.global.min.js +93 -44
- package/dist/widget.cjs +798 -188
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +798 -188
- package/dist/workspace.d.cts +16 -6
- package/dist/workspace.d.ts +16 -6
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-
|
|
2
|
-
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-
|
|
3
|
-
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-
|
|
1
|
+
import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-EQCHJZOT.js';
|
|
2
|
+
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-FCVIG7JG.js';
|
|
3
|
+
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-WFSZBX3R.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
6
6
|
var TypedEventBus = class {
|
|
@@ -428,6 +428,15 @@ var RendererControl = class {
|
|
|
428
428
|
setLegendActions(provider) {
|
|
429
429
|
this.renderer.setLegendActions?.(provider);
|
|
430
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Wire the legend rows' HOST-CONTRIBUTED callout bubbles (the shells route the
|
|
433
|
+
* plugin registry through this; see `registerLegendCallout`). Silent on a renderer
|
|
434
|
+
* without the seam — contributed callouts simply never show there, same graceful
|
|
435
|
+
* degradation as {@link setLegendActions}.
|
|
436
|
+
*/
|
|
437
|
+
setLegendCallouts(provider) {
|
|
438
|
+
this.renderer.setLegendCallouts?.(provider);
|
|
439
|
+
}
|
|
431
440
|
/**
|
|
432
441
|
* Replace the indicator legend's fold toggle with a host action (or restore it with
|
|
433
442
|
* `null`) — multi-chart shells point the chip at their indicator overview instead of
|
|
@@ -2898,6 +2907,10 @@ function isLineLikeSeries(spec) {
|
|
|
2898
2907
|
}
|
|
2899
2908
|
|
|
2900
2909
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
2910
|
+
var PROPS_TAB = "Properties";
|
|
2911
|
+
function bagOf(row, decl) {
|
|
2912
|
+
return decl.prop ? row.propValues : row.values;
|
|
2913
|
+
}
|
|
2901
2914
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
2902
2915
|
var IndicatorInputsDialog = class {
|
|
2903
2916
|
constructor(host) {
|
|
@@ -2908,6 +2921,7 @@ var IndicatorInputsDialog = class {
|
|
|
2908
2921
|
this.openId = null;
|
|
2909
2922
|
this.row = null;
|
|
2910
2923
|
this.snapshot = null;
|
|
2924
|
+
this.propSnapshot = null;
|
|
2911
2925
|
this.dialogTips = [];
|
|
2912
2926
|
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
2913
2927
|
this.refreshVisibility = null;
|
|
@@ -2930,10 +2944,11 @@ var IndicatorInputsDialog = class {
|
|
|
2930
2944
|
}
|
|
2931
2945
|
open(row) {
|
|
2932
2946
|
this.close();
|
|
2933
|
-
if (row.inputs.length === 0) return;
|
|
2947
|
+
if (row.inputs.length === 0 && row.props.length === 0) return;
|
|
2934
2948
|
this.row = row;
|
|
2935
2949
|
this.openId = row.id;
|
|
2936
2950
|
this.snapshot = { ...row.values };
|
|
2951
|
+
this.propSnapshot = { ...row.propValues };
|
|
2937
2952
|
ensureDialogStyles();
|
|
2938
2953
|
const t = this.host.theme();
|
|
2939
2954
|
const border = "var(--vela-border)";
|
|
@@ -2941,7 +2956,7 @@ var IndicatorInputsDialog = class {
|
|
|
2941
2956
|
const host = this.host.dialogHost() ?? this.host.container;
|
|
2942
2957
|
const ui = new Dialog({
|
|
2943
2958
|
host,
|
|
2944
|
-
title: row.
|
|
2959
|
+
title: row.title,
|
|
2945
2960
|
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
2946
2961
|
// whole body, killing the chart and the body-portaled popovers.
|
|
2947
2962
|
modal: false,
|
|
@@ -2973,6 +2988,16 @@ var IndicatorInputsDialog = class {
|
|
|
2973
2988
|
text: () => "Close"
|
|
2974
2989
|
}));
|
|
2975
2990
|
const tabDefs = tabInputs(row.inputs);
|
|
2991
|
+
if (row.props.length > 0) {
|
|
2992
|
+
const propDecls = row.props.map((p) => {
|
|
2993
|
+
const d = { ...p, prop: true };
|
|
2994
|
+
delete d.tab;
|
|
2995
|
+
return d;
|
|
2996
|
+
});
|
|
2997
|
+
const existing = tabDefs.find((t2) => t2.name === PROPS_TAB);
|
|
2998
|
+
if (existing) existing.inputs.push(...propDecls);
|
|
2999
|
+
else tabDefs.push({ name: PROPS_TAB, inputs: propDecls });
|
|
3000
|
+
}
|
|
2976
3001
|
const tabs = document.createElement("div");
|
|
2977
3002
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
2978
3003
|
const tabEls = [];
|
|
@@ -3075,9 +3100,10 @@ var IndicatorInputsDialog = class {
|
|
|
3075
3100
|
this.openId = null;
|
|
3076
3101
|
this.row = null;
|
|
3077
3102
|
this.snapshot = null;
|
|
3103
|
+
this.propSnapshot = null;
|
|
3078
3104
|
ui?.destroy();
|
|
3079
3105
|
}
|
|
3080
|
-
/** Restore every input to its open-time value (re-running the indicator), then close. */
|
|
3106
|
+
/** Restore every input and prop to its open-time value (re-running the indicator), then close. */
|
|
3081
3107
|
revertAndClose() {
|
|
3082
3108
|
const row = this.row;
|
|
3083
3109
|
const snap = this.snapshot;
|
|
@@ -3091,6 +3117,17 @@ var IndicatorInputsDialog = class {
|
|
|
3091
3117
|
}
|
|
3092
3118
|
}
|
|
3093
3119
|
}
|
|
3120
|
+
const propSnap = this.propSnapshot;
|
|
3121
|
+
if (row && propSnap) {
|
|
3122
|
+
for (const p of row.props) {
|
|
3123
|
+
const snapped = propSnap[p.key];
|
|
3124
|
+
const before = snapped !== void 0 ? snapped : p.defval;
|
|
3125
|
+
if (row.propValues[p.key] !== before) {
|
|
3126
|
+
row.propValues[p.key] = before;
|
|
3127
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: before, kind: "prop" });
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3094
3131
|
this.close();
|
|
3095
3132
|
}
|
|
3096
3133
|
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
@@ -3108,19 +3145,27 @@ var IndicatorInputsDialog = class {
|
|
|
3108
3145
|
const row = this.row;
|
|
3109
3146
|
if (!row) return;
|
|
3110
3147
|
const snap = this.snapshot;
|
|
3148
|
+
const propSnap = this.propSnapshot;
|
|
3111
3149
|
for (const inp of row.inputs) {
|
|
3112
3150
|
if (row.values[inp.key] !== inp.defval) {
|
|
3113
3151
|
row.values[inp.key] = inp.defval;
|
|
3114
3152
|
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
3115
3153
|
}
|
|
3116
3154
|
}
|
|
3155
|
+
for (const p of row.props) {
|
|
3156
|
+
if (row.propValues[p.key] !== p.defval) {
|
|
3157
|
+
row.propValues[p.key] = p.defval;
|
|
3158
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: p.defval, kind: "prop" });
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3117
3161
|
this.open(row);
|
|
3118
3162
|
if (snap) this.snapshot = snap;
|
|
3163
|
+
if (propSnap) this.propSnapshot = propSnap;
|
|
3119
3164
|
}
|
|
3120
3165
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
3121
|
-
commit(row,
|
|
3122
|
-
row.
|
|
3123
|
-
this.host.onChange?.({ indicatorId: row.id, key, value });
|
|
3166
|
+
commit(row, decl, value) {
|
|
3167
|
+
bagOf(row, decl)[decl.key] = value;
|
|
3168
|
+
this.host.onChange?.({ indicatorId: row.id, key: decl.key, value, ...decl.prop ? { kind: "prop" } : {} });
|
|
3124
3169
|
this.refreshVisibility?.();
|
|
3125
3170
|
}
|
|
3126
3171
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
@@ -3147,7 +3192,7 @@ var IndicatorInputsDialog = class {
|
|
|
3147
3192
|
const id = idOf(lead);
|
|
3148
3193
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
3149
3194
|
if (lead.type === "bool") {
|
|
3150
|
-
const current = Boolean(row
|
|
3195
|
+
const current = Boolean(bagOf(row, lead)[lead.key] ?? lead.defval);
|
|
3151
3196
|
return append(fieldRow({
|
|
3152
3197
|
label: nameOf(lead),
|
|
3153
3198
|
id,
|
|
@@ -3156,7 +3201,7 @@ var IndicatorInputsDialog = class {
|
|
|
3156
3201
|
toggle: {
|
|
3157
3202
|
id,
|
|
3158
3203
|
checked: current,
|
|
3159
|
-
onChange: (v) => this.commit(row, lead
|
|
3204
|
+
onChange: (v) => this.commit(row, lead, v)
|
|
3160
3205
|
}
|
|
3161
3206
|
}));
|
|
3162
3207
|
}
|
|
@@ -3181,8 +3226,8 @@ var IndicatorInputsDialog = class {
|
|
|
3181
3226
|
}
|
|
3182
3227
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
3183
3228
|
buildControl(row, inp, id) {
|
|
3184
|
-
const current = row
|
|
3185
|
-
const emit = (value) => this.commit(row, inp
|
|
3229
|
+
const current = bagOf(row, inp)[inp.key] ?? inp.defval;
|
|
3230
|
+
const emit = (value) => this.commit(row, inp, value);
|
|
3186
3231
|
if (inp.type === "bool") {
|
|
3187
3232
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
3188
3233
|
}
|
|
@@ -3193,7 +3238,7 @@ var IndicatorInputsDialog = class {
|
|
|
3193
3238
|
kind: "color",
|
|
3194
3239
|
id,
|
|
3195
3240
|
theme: this.host.theme(),
|
|
3196
|
-
get: () => String(row
|
|
3241
|
+
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
3197
3242
|
onChange: (v) => emit(v)
|
|
3198
3243
|
}).el;
|
|
3199
3244
|
}
|
|
@@ -3756,6 +3801,7 @@ function timeParts(ts) {
|
|
|
3756
3801
|
}
|
|
3757
3802
|
|
|
3758
3803
|
// src/renderers/shared/InputsUI.ts
|
|
3804
|
+
var LEGEND_AT_TOP_ATTR = "data-vela-pane-at-top";
|
|
3759
3805
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
3760
3806
|
var LEGEND_ICON_PX2 = 16;
|
|
3761
3807
|
var LEGEND_CTL_PX = 18;
|
|
@@ -3794,10 +3840,14 @@ var InputsUI = class {
|
|
|
3794
3840
|
this.moveApi = null;
|
|
3795
3841
|
/** Host-contributed legend actions, resolved PER ROW at render time (see setLegendActions). */
|
|
3796
3842
|
this.legendActions = null;
|
|
3843
|
+
/** Host-contributed callout bubbles, resolved PER ROW at render time (see setLegendCallouts). */
|
|
3844
|
+
this.legendCallouts = null;
|
|
3797
3845
|
/** Chrome-tooltip disposers, per row id — a tip open at removal must not outlive its row. */
|
|
3798
3846
|
this.rowTips = /* @__PURE__ */ new Map();
|
|
3799
3847
|
/** Same, for the contributed extras only (rebuilt independently by setLegendActions). */
|
|
3800
3848
|
this.extrasTips = /* @__PURE__ */ new Map();
|
|
3849
|
+
/** Same, for the contributed callouts only (rebuilt independently by setLegendCallouts). */
|
|
3850
|
+
this.calloutTips = /* @__PURE__ */ new Map();
|
|
3801
3851
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
3802
3852
|
this.moveMenu = null;
|
|
3803
3853
|
this.moveTargets = [];
|
|
@@ -3955,6 +4005,42 @@ var InputsUI = class {
|
|
|
3955
4005
|
extrasEl.appendChild(btn2);
|
|
3956
4006
|
}
|
|
3957
4007
|
}
|
|
4008
|
+
/**
|
|
4009
|
+
* Wire the host-contributed callout bubbles. Same contract as
|
|
4010
|
+
* {@link setLegendActions}: re-calling replaces the provider and re-projects the
|
|
4011
|
+
* rows already on screen.
|
|
4012
|
+
*/
|
|
4013
|
+
setLegendCallouts(provider) {
|
|
4014
|
+
this.legendCallouts = provider;
|
|
4015
|
+
for (const row of this.rows.values()) this.renderCallouts(row);
|
|
4016
|
+
}
|
|
4017
|
+
/** (Re)build one row's callout bubbles — tinted icon circles beside the title whose
|
|
4018
|
+
* click (when the view carries content) deploys a panel of text and actions. */
|
|
4019
|
+
renderCallouts(row) {
|
|
4020
|
+
this.disposeTips(this.calloutTips, row.id);
|
|
4021
|
+
for (const bubble of row.callouts) bubble.destroy();
|
|
4022
|
+
row.callouts = [];
|
|
4023
|
+
row.calloutsEl.replaceChildren();
|
|
4024
|
+
const views = this.legendCallouts?.(row.id) ?? [];
|
|
4025
|
+
row.calloutsEl.style.display = views.length > 0 ? "inline-flex" : "none";
|
|
4026
|
+
for (const view of views) {
|
|
4027
|
+
const bubble = new CalloutBubble({
|
|
4028
|
+
icon: view.icon,
|
|
4029
|
+
background: view.background,
|
|
4030
|
+
...view.color !== void 0 ? { color: view.color } : {},
|
|
4031
|
+
label: view.tooltip,
|
|
4032
|
+
...view.content !== void 0 ? { panel: view.content } : {},
|
|
4033
|
+
// The panel portals into the plot host and self-tokens: it must work on
|
|
4034
|
+
// a bare chart, where no `.vela-ui` token ancestor exists.
|
|
4035
|
+
host: this.container,
|
|
4036
|
+
theme: () => this.theme
|
|
4037
|
+
});
|
|
4038
|
+
bubble.el.dataset.legendCallout = view.id;
|
|
4039
|
+
this.tip(this.calloutTips, row.id, bubble.el, view.tooltip);
|
|
4040
|
+
row.callouts.push(bubble);
|
|
4041
|
+
row.calloutsEl.appendChild(bubble.el);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
3958
4044
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
3959
4045
|
reposition() {
|
|
3960
4046
|
for (const [paneId, lg] of this.legends) this.positionLegend(lg, paneId);
|
|
@@ -4122,6 +4208,7 @@ var InputsUI = class {
|
|
|
4122
4208
|
}
|
|
4123
4209
|
positionLegend(lg, paneId) {
|
|
4124
4210
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
4211
|
+
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
4125
4212
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
4126
4213
|
const collapsed = this.paneCollapse.has(paneId);
|
|
4127
4214
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
@@ -4197,13 +4284,13 @@ var InputsUI = class {
|
|
|
4197
4284
|
}
|
|
4198
4285
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
4199
4286
|
upsert(id, title, inputs, values, paneId = "price", opts = {}) {
|
|
4200
|
-
const settingsTitle = opts.settingsTitle ?? title;
|
|
4201
4287
|
const existing = this.rows.get(id);
|
|
4202
4288
|
if (existing) {
|
|
4203
4289
|
existing.title = title;
|
|
4204
|
-
existing.settingsTitle = settingsTitle;
|
|
4205
4290
|
existing.inputs = inputs;
|
|
4206
4291
|
existing.values = { ...values };
|
|
4292
|
+
existing.props = opts.props ?? [];
|
|
4293
|
+
existing.propValues = { ...opts.propValues ?? {} };
|
|
4207
4294
|
existing.titleEl.textContent = title;
|
|
4208
4295
|
if (existing.paneId !== paneId) {
|
|
4209
4296
|
existing.paneId = paneId;
|
|
@@ -4255,6 +4342,9 @@ var InputsUI = class {
|
|
|
4255
4342
|
titleWrap.appendChild(beta);
|
|
4256
4343
|
}
|
|
4257
4344
|
el.appendChild(titleWrap);
|
|
4345
|
+
const calloutsEl = document.createElement("span");
|
|
4346
|
+
calloutsEl.style.cssText = `display:none;align-items:center;gap:4px;flex:none;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;`;
|
|
4347
|
+
el.appendChild(calloutsEl);
|
|
4258
4348
|
el.appendChild(statusEl);
|
|
4259
4349
|
const valuesEl = document.createElement("span");
|
|
4260
4350
|
valuesEl.style.cssText = `display:none;align-items:center;gap:5px;margin-left:${LEGEND_TITLE_VALUES_GAP_PX}px;white-space:nowrap;font-variant-numeric:tabular-nums;`;
|
|
@@ -4271,13 +4361,13 @@ var InputsUI = class {
|
|
|
4271
4361
|
eye.className = "vela-ind-ctl";
|
|
4272
4362
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
4273
4363
|
eye.addEventListener("click", () => {
|
|
4274
|
-
const
|
|
4275
|
-
this.onToggleVisible?.(id, Boolean(
|
|
4364
|
+
const row2 = this.rows.get(id);
|
|
4365
|
+
this.onToggleVisible?.(id, Boolean(row2?.hidden));
|
|
4276
4366
|
});
|
|
4277
4367
|
controlsEl.appendChild(eye);
|
|
4278
4368
|
eyeEl = eye;
|
|
4279
4369
|
}
|
|
4280
|
-
if (inputs.length > 0) {
|
|
4370
|
+
if (inputs.length > 0 || (opts.props ?? []).length > 0) {
|
|
4281
4371
|
const gear = document.createElement("button");
|
|
4282
4372
|
gear.type = "button";
|
|
4283
4373
|
gear.setAttribute("aria-label", "Settings");
|
|
@@ -4317,7 +4407,9 @@ var InputsUI = class {
|
|
|
4317
4407
|
controlsEl.appendChild(close);
|
|
4318
4408
|
el.appendChild(controlsEl);
|
|
4319
4409
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
4320
|
-
|
|
4410
|
+
const row = { id, title, inputs, values: { ...values }, props: opts.props ?? [], propValues: { ...opts.propValues ?? {} }, el, titleEl, statusEl, valuesEl, plotValues: [], plotValuesKey: "", showValues: null, highlighted: false, paneId, hidden: false, eyeEl, controlsEl, extrasEl, calloutsEl, callouts: [], native: !!opts.native };
|
|
4411
|
+
this.rows.set(id, row);
|
|
4412
|
+
this.renderCallouts(row);
|
|
4321
4413
|
this.syncFoldToggle();
|
|
4322
4414
|
}
|
|
4323
4415
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -4326,9 +4418,11 @@ var InputsUI = class {
|
|
|
4326
4418
|
else container.appendChild(el);
|
|
4327
4419
|
}
|
|
4328
4420
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
4329
|
-
setValues(id, values) {
|
|
4421
|
+
setValues(id, values, props) {
|
|
4330
4422
|
const row = this.rows.get(id);
|
|
4331
|
-
if (row)
|
|
4423
|
+
if (!row) return;
|
|
4424
|
+
row.values = { ...row.values, ...values };
|
|
4425
|
+
if (props) row.propValues = { ...row.propValues, ...props };
|
|
4332
4426
|
}
|
|
4333
4427
|
/**
|
|
4334
4428
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -4410,8 +4504,13 @@ var InputsUI = class {
|
|
|
4410
4504
|
syncRowActions(row) {
|
|
4411
4505
|
const open = row.highlighted;
|
|
4412
4506
|
row.controlsEl.style.display = open || row.hidden ? "inline-flex" : "none";
|
|
4413
|
-
if (open)
|
|
4414
|
-
|
|
4507
|
+
if (open) {
|
|
4508
|
+
row.el.appendChild(row.statusEl);
|
|
4509
|
+
row.el.appendChild(row.calloutsEl);
|
|
4510
|
+
} else {
|
|
4511
|
+
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
4512
|
+
row.el.insertBefore(row.calloutsEl, row.statusEl);
|
|
4513
|
+
}
|
|
4415
4514
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
4416
4515
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
4417
4516
|
if (child === row.extrasEl) {
|
|
@@ -4429,10 +4528,12 @@ var InputsUI = class {
|
|
|
4429
4528
|
}
|
|
4430
4529
|
remove(id) {
|
|
4431
4530
|
const row = this.rows.get(id);
|
|
4531
|
+
for (const bubble of row?.callouts ?? []) bubble.destroy();
|
|
4432
4532
|
row?.el.remove();
|
|
4433
4533
|
this.rows.delete(id);
|
|
4434
4534
|
this.disposeTips(this.rowTips, id);
|
|
4435
4535
|
this.disposeTips(this.extrasTips, id);
|
|
4536
|
+
this.disposeTips(this.calloutTips, id);
|
|
4436
4537
|
if (this.selectedId === id) this.selectedId = null;
|
|
4437
4538
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
4438
4539
|
this.syncFoldToggle();
|
|
@@ -4452,6 +4553,8 @@ var InputsUI = class {
|
|
|
4452
4553
|
this.rowMenu = null;
|
|
4453
4554
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
4454
4555
|
for (const id of [...this.extrasTips.keys()]) this.disposeTips(this.extrasTips, id);
|
|
4556
|
+
for (const id of [...this.calloutTips.keys()]) this.disposeTips(this.calloutTips, id);
|
|
4557
|
+
for (const row of this.rows.values()) for (const bubble of row.callouts) bubble.destroy();
|
|
4455
4558
|
for (const lg of this.legends.values()) lg.remove();
|
|
4456
4559
|
this.legends.clear();
|
|
4457
4560
|
this.rows.clear();
|
|
@@ -4806,6 +4909,32 @@ var SIZE_PX = {
|
|
|
4806
4909
|
large: 16,
|
|
4807
4910
|
huge: 20
|
|
4808
4911
|
};
|
|
4912
|
+
function fontPxOf(size) {
|
|
4913
|
+
if (typeof size === "number") return size > 0 ? size : SIZE_PX.auto;
|
|
4914
|
+
return SIZE_PX[size] ?? SIZE_PX.auto;
|
|
4915
|
+
}
|
|
4916
|
+
function tableHasContent(t) {
|
|
4917
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
4918
|
+
}
|
|
4919
|
+
function mergeRenderPlan(t) {
|
|
4920
|
+
const span = /* @__PURE__ */ new Map();
|
|
4921
|
+
const omit = /* @__PURE__ */ new Set();
|
|
4922
|
+
for (const m of t.merges) {
|
|
4923
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
4924
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
4925
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
4926
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
4931
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
4932
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
for (const key of span.keys()) omit.delete(key);
|
|
4936
|
+
return { span, omit };
|
|
4937
|
+
}
|
|
4809
4938
|
var TableOverlay = class {
|
|
4810
4939
|
constructor(container, theme, paneBounds) {
|
|
4811
4940
|
this.container = container;
|
|
@@ -4826,7 +4955,9 @@ var TableOverlay = class {
|
|
|
4826
4955
|
update(tables) {
|
|
4827
4956
|
this.lastTables = tables;
|
|
4828
4957
|
this.root.replaceChildren();
|
|
4829
|
-
for (const t of tables)
|
|
4958
|
+
for (const t of tables) {
|
|
4959
|
+
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
4960
|
+
}
|
|
4830
4961
|
}
|
|
4831
4962
|
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
4832
4963
|
reposition() {
|
|
@@ -4841,39 +4972,36 @@ var TableOverlay = class {
|
|
|
4841
4972
|
this.root.remove();
|
|
4842
4973
|
}
|
|
4843
4974
|
renderTable(t) {
|
|
4975
|
+
const b = this.paneBounds(t.paneId);
|
|
4844
4976
|
const wrap = document.createElement("div");
|
|
4845
4977
|
wrap.style.position = "absolute";
|
|
4846
|
-
|
|
4978
|
+
if (t.frameColor && t.frameWidth > 0) wrap.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
4979
|
+
this.anchor(wrap, t.position, b);
|
|
4847
4980
|
const table = document.createElement("table");
|
|
4848
4981
|
Object.assign(table.style, {
|
|
4849
4982
|
borderCollapse: "collapse",
|
|
4850
4983
|
background: t.bgColor ?? "transparent",
|
|
4851
4984
|
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
4852
|
-
// Frame as an inset shadow (not a `border`) so it stays independent of the
|
|
4853
|
-
// collapsed cell borders even when frame_width != border_width.
|
|
4854
|
-
boxShadow: t.frameColor && t.frameWidth > 0 ? `inset 0 0 0 ${t.frameWidth}px ${t.frameColor}` : "none",
|
|
4855
4985
|
border: "none",
|
|
4856
4986
|
tableLayout: "auto",
|
|
4857
4987
|
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
4858
4988
|
pointerEvents: "auto"
|
|
4859
4989
|
});
|
|
4860
|
-
const span
|
|
4861
|
-
const
|
|
4862
|
-
|
|
4863
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
4864
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
4865
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
4866
|
-
if (r !== m.startRow || c !== m.startCol) skip.add(`${r}:${c}`);
|
|
4867
|
-
}
|
|
4868
|
-
}
|
|
4869
|
-
}
|
|
4990
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
4991
|
+
const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
|
|
4992
|
+
const paneH = b.height;
|
|
4870
4993
|
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
4871
4994
|
for (let r = 0; r < t.rows; r += 1) {
|
|
4872
4995
|
const tr = document.createElement("tr");
|
|
4873
4996
|
for (let c = 0; c < t.columns; c += 1) {
|
|
4997
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
4874
4998
|
const cell = t.cells[r]?.[c] ?? null;
|
|
4875
|
-
if (skip.has(`${r}:${c}`) || cell?.merged) continue;
|
|
4876
4999
|
const td = document.createElement("td");
|
|
5000
|
+
if (cell === null) {
|
|
5001
|
+
Object.assign(td.style, { padding: "0", border: "none" });
|
|
5002
|
+
tr.appendChild(td);
|
|
5003
|
+
continue;
|
|
5004
|
+
}
|
|
4877
5005
|
const sp = span.get(`${r}:${c}`);
|
|
4878
5006
|
if (sp) {
|
|
4879
5007
|
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
@@ -4882,18 +5010,22 @@ var TableOverlay = class {
|
|
|
4882
5010
|
Object.assign(td.style, {
|
|
4883
5011
|
border: cellBorder,
|
|
4884
5012
|
padding: "2px 6px",
|
|
4885
|
-
background: cell
|
|
4886
|
-
color: cell
|
|
4887
|
-
textAlign: cell
|
|
4888
|
-
verticalAlign: cell
|
|
4889
|
-
fontSize: `${
|
|
4890
|
-
fontFamily: cell
|
|
4891
|
-
fontWeight: cell
|
|
4892
|
-
fontStyle: cell
|
|
4893
|
-
|
|
5013
|
+
background: cell.bgColor ?? "transparent",
|
|
5014
|
+
color: cell.textColor ?? this.theme.textColor,
|
|
5015
|
+
textAlign: cell.hAlign,
|
|
5016
|
+
verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
|
|
5017
|
+
fontSize: `${fontPxOf(cell.textSize)}px`,
|
|
5018
|
+
fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
5019
|
+
fontWeight: cell.bold ? "bold" : "normal",
|
|
5020
|
+
fontStyle: cell.italic ? "italic" : "normal",
|
|
5021
|
+
// Pine cell text never wraps; `\n` still breaks lines. Wrapping
|
|
5022
|
+
// used to collapse unicode sparklines and ━━━ dividers.
|
|
5023
|
+
whiteSpace: "pre"
|
|
4894
5024
|
});
|
|
4895
|
-
if (cell
|
|
4896
|
-
td.
|
|
5025
|
+
if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
|
|
5026
|
+
if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
|
|
5027
|
+
if (cell.tooltip) td.title = cell.tooltip;
|
|
5028
|
+
td.textContent = cell.text ?? "";
|
|
4897
5029
|
tr.appendChild(td);
|
|
4898
5030
|
}
|
|
4899
5031
|
table.appendChild(tr);
|
|
@@ -5778,8 +5910,14 @@ var WebGL2Backend = class {
|
|
|
5778
5910
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
5779
5911
|
};
|
|
5780
5912
|
b.alpha = this.modelAlpha;
|
|
5781
|
-
for (const m of models) for (const bgSpan of m.backgrounds) this.emitBackground(b, bgSpan, pane, coords);
|
|
5782
|
-
for (const m of models) for (const f of m.fills) this.emitFill(b, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
5913
|
+
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
5914
|
+
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
5915
|
+
if (isPrice) {
|
|
5916
|
+
for (const m of scene.indicators.values()) {
|
|
5917
|
+
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
5918
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5783
5921
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
5784
5922
|
let candleDrawn = false;
|
|
5785
5923
|
for (const m of models) {
|
|
@@ -5793,7 +5931,7 @@ var WebGL2Backend = class {
|
|
|
5793
5931
|
b.alpha = this.modelAlpha;
|
|
5794
5932
|
const off = scene.offsetOf(m.id);
|
|
5795
5933
|
const mp = effPane(m);
|
|
5796
|
-
for (const s of m.series) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
5934
|
+
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
5797
5935
|
}
|
|
5798
5936
|
if (drawCandles && !candleDrawn) {
|
|
5799
5937
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -5802,6 +5940,12 @@ var WebGL2Backend = class {
|
|
|
5802
5940
|
}
|
|
5803
5941
|
drawSlicesUpTo(Infinity);
|
|
5804
5942
|
b.alpha = this.modelAlpha;
|
|
5943
|
+
if (isPrice) {
|
|
5944
|
+
for (const m of scene.indicators.values()) {
|
|
5945
|
+
const off = scene.offsetOf(m.id);
|
|
5946
|
+
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5805
5949
|
for (const m of models) {
|
|
5806
5950
|
const mp = effPane(m);
|
|
5807
5951
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -5848,7 +5992,7 @@ var WebGL2Backend = class {
|
|
|
5848
5992
|
for (const pane of scene.orderedPanes()) {
|
|
5849
5993
|
const b = this.glowBatch;
|
|
5850
5994
|
b.reset();
|
|
5851
|
-
this.emitGlowSources(b, scene
|
|
5995
|
+
this.emitGlowSources(b, scene, pane, coords, i0, i1);
|
|
5852
5996
|
if (b.vertexCount === 0) continue;
|
|
5853
5997
|
const topH = Math.round(pane.bounds.top * dpr * 0.5);
|
|
5854
5998
|
const botH = Math.round((pane.bounds.top + pane.bounds.height) * dpr * 0.5);
|
|
@@ -5917,15 +6061,23 @@ var WebGL2Backend = class {
|
|
|
5917
6061
|
gl.bindVertexArray(null);
|
|
5918
6062
|
return true;
|
|
5919
6063
|
}
|
|
5920
|
-
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
6064
|
+
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
6065
|
+
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
6066
|
+
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
6067
|
+
const emitOne = (s, off) => {
|
|
6068
|
+
if (!isLineLikeSeries(s) || s.visible === false) return;
|
|
6069
|
+
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
6070
|
+
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
6071
|
+
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
6072
|
+
};
|
|
6073
|
+
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
6074
|
+
const off = scene.offsetOf(m.id);
|
|
6075
|
+
for (const s of m.series) if (s.overlay !== true) emitOne(s, off);
|
|
6076
|
+
}
|
|
6077
|
+
if (pane.kind === "price") {
|
|
6078
|
+
for (const m of scene.indicators.values()) {
|
|
6079
|
+
const off = scene.offsetOf(m.id);
|
|
6080
|
+
for (const s of m.series) if (s.overlay === true) emitOne(s, off);
|
|
5929
6081
|
}
|
|
5930
6082
|
}
|
|
5931
6083
|
}
|
|
@@ -6208,9 +6360,9 @@ var WebGL2Backend = class {
|
|
|
6208
6360
|
b.alpha = this.candleBodyAlpha;
|
|
6209
6361
|
b.rect(g.bodyX, bodyTop, g.bodyW, bodyH, c);
|
|
6210
6362
|
}
|
|
6211
|
-
if (cs.borderVisible ||
|
|
6363
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
6212
6364
|
b.alpha = this.candleStructureAlpha;
|
|
6213
|
-
const bord = cs.borderVisible
|
|
6365
|
+
const bord = cs.borderVisible ? parseColor((isUp ? cs.borderUpColor : cs.borderDownColor) ?? bodyColorStr) : c;
|
|
6214
6366
|
const bw = Math.max(0, g.bodyW - 1);
|
|
6215
6367
|
const bh = Math.max(0, bodyH - 1);
|
|
6216
6368
|
b.rectStroke(g.bodyX + 0.5, bodyTop + 0.5, bw, bh, 1, bord);
|
|
@@ -7762,8 +7914,14 @@ var Canvas2dBackend = class {
|
|
|
7762
7914
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
7763
7915
|
};
|
|
7764
7916
|
ctx.globalAlpha = this.modelAlpha;
|
|
7765
|
-
for (const m of models) for (const bg of m.backgrounds) this.drawBackground(ctx, bg, pane, coords);
|
|
7766
|
-
for (const m of models) for (const f of m.fills) this.drawFill(ctx, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
7917
|
+
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
7918
|
+
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
7919
|
+
if (isPrice) {
|
|
7920
|
+
for (const m of scene.indicators.values()) {
|
|
7921
|
+
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
7922
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
7923
|
+
}
|
|
7924
|
+
}
|
|
7767
7925
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
7768
7926
|
let si = 0;
|
|
7769
7927
|
const drawSlicesUpTo = (z) => {
|
|
@@ -7785,7 +7943,7 @@ var Canvas2dBackend = class {
|
|
|
7785
7943
|
ctx.globalAlpha = this.modelAlpha;
|
|
7786
7944
|
const off = scene.offsetOf(m.id);
|
|
7787
7945
|
const mp = effPane(m);
|
|
7788
|
-
for (const s of m.series) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
7946
|
+
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
7789
7947
|
}
|
|
7790
7948
|
if (drawCandles && !candleDrawn) {
|
|
7791
7949
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -7793,6 +7951,13 @@ var Canvas2dBackend = class {
|
|
|
7793
7951
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
7794
7952
|
}
|
|
7795
7953
|
drawSlicesUpTo(Infinity);
|
|
7954
|
+
if (isPrice) {
|
|
7955
|
+
ctx.globalAlpha = this.modelAlpha;
|
|
7956
|
+
for (const m of scene.indicators.values()) {
|
|
7957
|
+
const off = scene.offsetOf(m.id);
|
|
7958
|
+
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
7959
|
+
}
|
|
7960
|
+
}
|
|
7796
7961
|
ctx.globalAlpha = this.modelAlpha;
|
|
7797
7962
|
for (const m of models) {
|
|
7798
7963
|
const mp = effPane(m);
|
|
@@ -8064,9 +8229,9 @@ var Canvas2dBackend = class {
|
|
|
8064
8229
|
ctx.fillStyle = color;
|
|
8065
8230
|
ctx.fillRect(g.bodyX, top, g.bodyW, bodyH);
|
|
8066
8231
|
}
|
|
8067
|
-
if (cs.borderVisible ||
|
|
8232
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
8068
8233
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
8069
|
-
ctx.strokeStyle = cs.borderVisible
|
|
8234
|
+
ctx.strokeStyle = cs.borderVisible ? (up ? cs.borderUpColor : cs.borderDownColor) ?? color : color;
|
|
8070
8235
|
ctx.lineWidth = 1;
|
|
8071
8236
|
const bw = Math.max(0, g.bodyW - 1);
|
|
8072
8237
|
const bh = Math.max(0, bodyH - 1);
|
|
@@ -8498,6 +8663,8 @@ var DrawingSceneRenderer = class {
|
|
|
8498
8663
|
this.set = set;
|
|
8499
8664
|
/** measureText width cache, keyed by `${font} ${text}`, persists across frames. */
|
|
8500
8665
|
this.widthCache = /* @__PURE__ */ new Map();
|
|
8666
|
+
/** Tooltip hit-rects captured while drawing the CURRENT set (rebuilt per render). */
|
|
8667
|
+
this.tipRegions = [];
|
|
8501
8668
|
/**
|
|
8502
8669
|
* Index offset of the CURRENT set's model: its `xloc:'bar_index'` coordinates count
|
|
8503
8670
|
* from the model's anchor bar, so they shift by this to land on chart logical indices.
|
|
@@ -8518,8 +8685,13 @@ var DrawingSceneRenderer = class {
|
|
|
8518
8685
|
const s = this.set;
|
|
8519
8686
|
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
8520
8687
|
}
|
|
8688
|
+
/** Tooltip hit-rects of the labels drawn by the LAST `render` call (same coords as `ctx`). */
|
|
8689
|
+
labelTipRegions() {
|
|
8690
|
+
return this.tipRegions;
|
|
8691
|
+
}
|
|
8521
8692
|
/** Draw the whole set into `ctx` using the supplied coordinate closures. */
|
|
8522
8693
|
render(ctx, W, H, xOf, yOf) {
|
|
8694
|
+
this.tipRegions = [];
|
|
8523
8695
|
if (this.isEmpty()) return;
|
|
8524
8696
|
ctx.save();
|
|
8525
8697
|
this.drawLinefills(ctx, W, H, xOf, yOf);
|
|
@@ -8867,13 +9039,35 @@ var DrawingSceneRenderer = class {
|
|
|
8867
9039
|
if (this.isPointShape(lb.style)) {
|
|
8868
9040
|
if (!lb.noFill) this.drawLabelShape(ctx, lb.style, px, py, fontPx, color);
|
|
8869
9041
|
if (lb.text) this.drawLabelText(ctx, lb, px, py + fontPx, fontPx);
|
|
8870
|
-
|
|
8871
|
-
|
|
9042
|
+
if (lb.tooltip) {
|
|
9043
|
+
const r = Math.max(4, fontPx * 0.6) + 3;
|
|
9044
|
+
this.tipRegions.push({ left: px - r, top: py - r, right: px + r, bottom: py + r, text: lb.tooltip });
|
|
9045
|
+
}
|
|
9046
|
+
} else if (lb.style === "none" || lb.style === "text_outline") {
|
|
9047
|
+
if (lb.text) {
|
|
9048
|
+
this.drawLabelText(ctx, lb, px, py, fontPx, lb.style === "text_outline");
|
|
9049
|
+
if (lb.tooltip) this.tipRegions.push(this.textRegion(ctx, lb, px, py, fontPx, lb.tooltip));
|
|
9050
|
+
}
|
|
8872
9051
|
} else {
|
|
8873
|
-
this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
9052
|
+
const r = this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
9053
|
+
if (lb.tooltip) this.tipRegions.push({ left: r.x, top: r.y, right: r.x + r.w, bottom: r.y + r.h, text: lb.tooltip });
|
|
8874
9054
|
}
|
|
8875
9055
|
}
|
|
8876
9056
|
}
|
|
9057
|
+
/** Canvas font for a label's text — family + Pine `text_formatting` (bold/italic). */
|
|
9058
|
+
labelFont(lb, fontPx) {
|
|
9059
|
+
const family = lb.fontFamily === "monospace" ? "monospace" : this.deps.theme.fontFamily || "sans-serif";
|
|
9060
|
+
return `${lb.italic ? "italic " : ""}${lb.bold ? "bold " : ""}${fontPx}px ${family}`;
|
|
9061
|
+
}
|
|
9062
|
+
/** Hover rect of a text-only label (style none/text_outline/noFill), centered like drawLabelText. */
|
|
9063
|
+
textRegion(ctx, lb, cx, cy, fontPx, text) {
|
|
9064
|
+
const font = this.labelFont(lb, fontPx);
|
|
9065
|
+
const lines = (lb.text ?? "").split("\n");
|
|
9066
|
+
const w = Math.max(1, ...lines.map((l) => this.measure(ctx, font, l)));
|
|
9067
|
+
const h = fontPx * 1.25 * lines.length;
|
|
9068
|
+
const left = lb.textAlign === "left" ? cx : lb.textAlign === "right" ? cx - w : cx - w / 2;
|
|
9069
|
+
return { left, top: cy - h / 2, right: left + w, bottom: cy + h / 2, text };
|
|
9070
|
+
}
|
|
8877
9071
|
isPointShape(style) {
|
|
8878
9072
|
switch (style) {
|
|
8879
9073
|
case "circle":
|
|
@@ -8892,8 +9086,7 @@ var DrawingSceneRenderer = class {
|
|
|
8892
9086
|
}
|
|
8893
9087
|
}
|
|
8894
9088
|
drawLabelText(ctx, lb, cx, cy, fontPx, outline = false) {
|
|
8895
|
-
|
|
8896
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
9089
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
8897
9090
|
ctx.textAlign = lb.textAlign === "left" ? "left" : lb.textAlign === "right" ? "right" : "center";
|
|
8898
9091
|
ctx.textBaseline = "middle";
|
|
8899
9092
|
const lines = lb.text.split("\n");
|
|
@@ -8973,9 +9166,9 @@ var DrawingSceneRenderer = class {
|
|
|
8973
9166
|
ctx.fill();
|
|
8974
9167
|
}
|
|
8975
9168
|
}
|
|
9169
|
+
/** Draw a bubble label; returns the bubble body rect (for tooltip hit-testing). */
|
|
8976
9170
|
drawBubble(ctx, lb, px, py, fontPx, color) {
|
|
8977
|
-
|
|
8978
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
9171
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
8979
9172
|
const lines = (lb.text ?? "").split("\n");
|
|
8980
9173
|
const padX = 6;
|
|
8981
9174
|
const padY = 4;
|
|
@@ -9033,38 +9226,51 @@ var DrawingSceneRenderer = class {
|
|
|
9033
9226
|
by = py - h - ptr;
|
|
9034
9227
|
pointer = "down";
|
|
9035
9228
|
}
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
if (pointer !== "none") {
|
|
9040
|
-
ctx.beginPath();
|
|
9041
|
-
if (pointer === "down") {
|
|
9042
|
-
ctx.moveTo(px - ptr, by + h);
|
|
9043
|
-
ctx.lineTo(px + ptr, by + h);
|
|
9044
|
-
ctx.lineTo(px, py);
|
|
9045
|
-
} else if (pointer === "up") {
|
|
9046
|
-
ctx.moveTo(px - ptr, by);
|
|
9047
|
-
ctx.lineTo(px + ptr, by);
|
|
9048
|
-
ctx.lineTo(px, py);
|
|
9049
|
-
} else if (pointer === "left") {
|
|
9050
|
-
ctx.moveTo(bx, py - ptr);
|
|
9051
|
-
ctx.lineTo(bx, py + ptr);
|
|
9052
|
-
ctx.lineTo(px, py);
|
|
9053
|
-
} else {
|
|
9054
|
-
ctx.moveTo(bx + w, py - ptr);
|
|
9055
|
-
ctx.lineTo(bx + w, py + ptr);
|
|
9056
|
-
ctx.lineTo(px, py);
|
|
9057
|
-
}
|
|
9058
|
-
ctx.closePath();
|
|
9229
|
+
if (!lb.noFill) {
|
|
9230
|
+
ctx.fillStyle = color;
|
|
9231
|
+
this.roundRect(ctx, bx, by, w, h, 4);
|
|
9059
9232
|
ctx.fill();
|
|
9233
|
+
if (pointer !== "none") {
|
|
9234
|
+
ctx.beginPath();
|
|
9235
|
+
if (pointer === "down") {
|
|
9236
|
+
ctx.moveTo(px - ptr, by + h);
|
|
9237
|
+
ctx.lineTo(px + ptr, by + h);
|
|
9238
|
+
ctx.lineTo(px, py);
|
|
9239
|
+
} else if (pointer === "up") {
|
|
9240
|
+
ctx.moveTo(px - ptr, by);
|
|
9241
|
+
ctx.lineTo(px + ptr, by);
|
|
9242
|
+
ctx.lineTo(px, py);
|
|
9243
|
+
} else if (pointer === "left") {
|
|
9244
|
+
ctx.moveTo(bx, py - ptr);
|
|
9245
|
+
ctx.lineTo(bx, py + ptr);
|
|
9246
|
+
ctx.lineTo(px, py);
|
|
9247
|
+
} else {
|
|
9248
|
+
ctx.moveTo(bx + w, py - ptr);
|
|
9249
|
+
ctx.lineTo(bx + w, py + ptr);
|
|
9250
|
+
ctx.lineTo(px, py);
|
|
9251
|
+
}
|
|
9252
|
+
ctx.closePath();
|
|
9253
|
+
ctx.fill();
|
|
9254
|
+
}
|
|
9060
9255
|
}
|
|
9061
9256
|
if (lb.text) {
|
|
9062
|
-
ctx.fillStyle = lb.textColor ?? contrastColor(color);
|
|
9063
|
-
ctx.textAlign = "center";
|
|
9257
|
+
ctx.fillStyle = lb.textColor ?? (lb.noFill ? this.deps.theme.textColor : contrastColor(color));
|
|
9064
9258
|
ctx.textBaseline = "middle";
|
|
9259
|
+
let tx;
|
|
9260
|
+
if (lb.textAlign === "left") {
|
|
9261
|
+
ctx.textAlign = "left";
|
|
9262
|
+
tx = bx + padX;
|
|
9263
|
+
} else if (lb.textAlign === "right") {
|
|
9264
|
+
ctx.textAlign = "right";
|
|
9265
|
+
tx = bx + w - padX;
|
|
9266
|
+
} else {
|
|
9267
|
+
ctx.textAlign = "center";
|
|
9268
|
+
tx = bx + w / 2;
|
|
9269
|
+
}
|
|
9065
9270
|
const startY = by + padY + lineH / 2;
|
|
9066
|
-
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i],
|
|
9271
|
+
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i], tx, startY + i * lineH);
|
|
9067
9272
|
}
|
|
9273
|
+
return { x: bx, y: by, w, h };
|
|
9068
9274
|
}
|
|
9069
9275
|
roundRect(ctx, x, y, w, h, r) {
|
|
9070
9276
|
const rr = Math.min(r, w / 2, h / 2);
|
|
@@ -9302,6 +9508,8 @@ var ChromeRenderer = class {
|
|
|
9302
9508
|
this.axisTextColor = DARK_THEME.textColor;
|
|
9303
9509
|
// Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
|
|
9304
9510
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
9511
|
+
// Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
|
|
9512
|
+
this.labelTips = [];
|
|
9305
9513
|
}
|
|
9306
9514
|
mount(canvas) {
|
|
9307
9515
|
this.canvas = canvas;
|
|
@@ -9343,6 +9551,7 @@ var ChromeRenderer = class {
|
|
|
9343
9551
|
const dataW = coords.width;
|
|
9344
9552
|
const dataH = coords.height;
|
|
9345
9553
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
9554
|
+
this.labelTips = [];
|
|
9346
9555
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
9347
9556
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
9348
9557
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -9452,6 +9661,17 @@ var ChromeRenderer = class {
|
|
|
9452
9661
|
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
9453
9662
|
);
|
|
9454
9663
|
ctx.restore();
|
|
9664
|
+
for (const r of this.drawScene.labelTipRegions()) {
|
|
9665
|
+
this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
9666
|
+
}
|
|
9667
|
+
}
|
|
9668
|
+
/** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
|
|
9669
|
+
labelTooltipAt(x, y) {
|
|
9670
|
+
for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
|
|
9671
|
+
const r = this.labelTips[i];
|
|
9672
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
9673
|
+
}
|
|
9674
|
+
return null;
|
|
9455
9675
|
}
|
|
9456
9676
|
// ── axes ──
|
|
9457
9677
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
@@ -9668,6 +9888,68 @@ function formatCountdown(ms) {
|
|
|
9668
9888
|
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
9669
9889
|
}
|
|
9670
9890
|
|
|
9891
|
+
// src/renderers/native/chrome/LabelTooltip.ts
|
|
9892
|
+
var HOVER_DELAY_MS = 350;
|
|
9893
|
+
var LabelTooltip = class {
|
|
9894
|
+
constructor(plot, deps) {
|
|
9895
|
+
this.plot = plot;
|
|
9896
|
+
this.deps = deps;
|
|
9897
|
+
this.tip = null;
|
|
9898
|
+
this.timer = null;
|
|
9899
|
+
/** Text of the currently open OR armed tip — dedupes moves inside one label. */
|
|
9900
|
+
this.current = null;
|
|
9901
|
+
this.onMove = (e) => {
|
|
9902
|
+
if (e.pointerType !== "mouse") return;
|
|
9903
|
+
const rect = this.plot.getBoundingClientRect();
|
|
9904
|
+
const x = e.clientX - rect.left;
|
|
9905
|
+
const y = e.clientY - rect.top;
|
|
9906
|
+
const text = this.deps.lookup(x, y);
|
|
9907
|
+
if (!text) {
|
|
9908
|
+
this.clear();
|
|
9909
|
+
return;
|
|
9910
|
+
}
|
|
9911
|
+
if (text === this.current) return;
|
|
9912
|
+
this.clear();
|
|
9913
|
+
this.current = text;
|
|
9914
|
+
this.timer = window.setTimeout(() => this.show(text, x, y), HOVER_DELAY_MS);
|
|
9915
|
+
};
|
|
9916
|
+
this.onLeave = () => {
|
|
9917
|
+
this.clear();
|
|
9918
|
+
};
|
|
9919
|
+
plot.addEventListener("pointermove", this.onMove);
|
|
9920
|
+
plot.addEventListener("pointerleave", this.onLeave);
|
|
9921
|
+
plot.addEventListener("pointerdown", this.onLeave);
|
|
9922
|
+
}
|
|
9923
|
+
destroy() {
|
|
9924
|
+
this.plot.removeEventListener("pointermove", this.onMove);
|
|
9925
|
+
this.plot.removeEventListener("pointerleave", this.onLeave);
|
|
9926
|
+
this.plot.removeEventListener("pointerdown", this.onLeave);
|
|
9927
|
+
this.clear();
|
|
9928
|
+
}
|
|
9929
|
+
show(text, x, y) {
|
|
9930
|
+
const doc = this.plot.ownerDocument;
|
|
9931
|
+
const tip = doc.createElement("div");
|
|
9932
|
+
tip.textContent = text;
|
|
9933
|
+
tip.style.cssText = "position:absolute;z-index:var(--vela-z-tooltip);pointer-events:none;background:var(--vela-bg);border:1px solid var(--vela-border);color:var(--vela-fg);border-radius:var(--vela-radius-md);padding:4px 9px;box-shadow:var(--vela-shadow);font:var(--vela-font-size-md) var(--vela-font);max-width:260px;";
|
|
9934
|
+
applyChromeTokens(tip, this.deps.theme());
|
|
9935
|
+
this.plot.appendChild(tip);
|
|
9936
|
+
const pw = this.plot.clientWidth;
|
|
9937
|
+
const ph = this.plot.clientHeight;
|
|
9938
|
+
tip.style.left = `${Math.max(0, Math.min(x + 12, pw - tip.offsetWidth - 4))}px`;
|
|
9939
|
+
tip.style.top = `${Math.max(0, Math.min(y + 16, ph - tip.offsetHeight - 4))}px`;
|
|
9940
|
+
this.tip = tip;
|
|
9941
|
+
}
|
|
9942
|
+
clear() {
|
|
9943
|
+
if (this.timer !== null) {
|
|
9944
|
+
clearTimeout(this.timer);
|
|
9945
|
+
this.timer = null;
|
|
9946
|
+
}
|
|
9947
|
+
this.tip?.remove();
|
|
9948
|
+
this.tip = null;
|
|
9949
|
+
this.current = null;
|
|
9950
|
+
}
|
|
9951
|
+
};
|
|
9952
|
+
|
|
9671
9953
|
// src/renderers/native/chrome/CrosshairRenderer.ts
|
|
9672
9954
|
var CrosshairRenderer = class {
|
|
9673
9955
|
constructor() {
|
|
@@ -14745,19 +15027,8 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
14745
15027
|
for (const model of models) {
|
|
14746
15028
|
const off = offsetOf(model.id);
|
|
14747
15029
|
for (const s of model.series) {
|
|
14748
|
-
if (s.
|
|
14749
|
-
|
|
14750
|
-
const b = s.bars[i - off];
|
|
14751
|
-
if (b) {
|
|
14752
|
-
consider(b.high);
|
|
14753
|
-
consider(b.low);
|
|
14754
|
-
}
|
|
14755
|
-
}
|
|
14756
|
-
} else if (isLineLikeSeries(s)) {
|
|
14757
|
-
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
14758
|
-
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
14759
|
-
else if (s.style?.base != null) consider(s.style.base);
|
|
14760
|
-
}
|
|
15030
|
+
if (s.overlay === true) continue;
|
|
15031
|
+
considerSeries(s, i0, i1, off, consider);
|
|
14761
15032
|
}
|
|
14762
15033
|
for (const pl of model.priceLines) consider(pl.price);
|
|
14763
15034
|
}
|
|
@@ -14779,6 +15050,36 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
14779
15050
|
const span = max - min;
|
|
14780
15051
|
return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
|
|
14781
15052
|
}
|
|
15053
|
+
function considerSeries(s, i0, i1, off, consider) {
|
|
15054
|
+
if (s.kind === "candle" || s.kind === "bar") {
|
|
15055
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
15056
|
+
const b = s.bars[i - off];
|
|
15057
|
+
if (b) {
|
|
15058
|
+
consider(b.high);
|
|
15059
|
+
consider(b.low);
|
|
15060
|
+
}
|
|
15061
|
+
}
|
|
15062
|
+
} else if (isLineLikeSeries(s)) {
|
|
15063
|
+
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
15064
|
+
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
15065
|
+
else if (s.style?.base != null) consider(s.style.base);
|
|
15066
|
+
}
|
|
15067
|
+
}
|
|
15068
|
+
function overlaySeriesRange(models, i0, i1, offsetOf = () => 0) {
|
|
15069
|
+
let min = Infinity;
|
|
15070
|
+
let max = -Infinity;
|
|
15071
|
+
const consider = (v) => {
|
|
15072
|
+
if (v != null && Number.isFinite(v)) {
|
|
15073
|
+
if (v < min) min = v;
|
|
15074
|
+
if (v > max) max = v;
|
|
15075
|
+
}
|
|
15076
|
+
};
|
|
15077
|
+
for (const model of models) {
|
|
15078
|
+
const off = offsetOf(model.id);
|
|
15079
|
+
for (const s of model.series) if (s.overlay === true) considerSeries(s, i0, i1, off, consider);
|
|
15080
|
+
}
|
|
15081
|
+
return min === Infinity ? null : { min, max };
|
|
15082
|
+
}
|
|
14782
15083
|
function expandScaleByPixels(scale, heightPx, abovePx, belowPx) {
|
|
14783
15084
|
if (abovePx <= 0 && belowPx <= 0) return scale;
|
|
14784
15085
|
const content = heightPx - abovePx - belowPx;
|
|
@@ -15535,6 +15836,8 @@ var NativeRenderer = class {
|
|
|
15535
15836
|
this.glowAmount = 0;
|
|
15536
15837
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
15537
15838
|
this.chrome = new ChromeRenderer();
|
|
15839
|
+
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
15840
|
+
this.labelTooltip = null;
|
|
15538
15841
|
this.crosshairLayer = new CrosshairRenderer();
|
|
15539
15842
|
/** 1 Hz repaint pump so the price-axis countdown-to-bar-close ticks; null when off. */
|
|
15540
15843
|
this.countdownTimer = null;
|
|
@@ -15545,6 +15848,8 @@ var NativeRenderer = class {
|
|
|
15545
15848
|
this.indicatorValuesOn = true;
|
|
15546
15849
|
/** Host-contributed legend actions — held here so a rebuild of the legend re-wires them. */
|
|
15547
15850
|
this.legendActionsProvider = null;
|
|
15851
|
+
/** Host-contributed legend callouts — held here so a rebuild of the legend re-wires them. */
|
|
15852
|
+
this.legendCalloutsProvider = null;
|
|
15548
15853
|
/** Host override of the legend's fold toggle — held here so a remount re-applies it. */
|
|
15549
15854
|
this.legendOverviewAction = null;
|
|
15550
15855
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -16653,6 +16958,10 @@ var NativeRenderer = class {
|
|
|
16653
16958
|
this.plot.appendChild(this.scrollButton);
|
|
16654
16959
|
this.plot.addEventListener("pointermove", this.onScrollProximityMove);
|
|
16655
16960
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
16961
|
+
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
16962
|
+
theme: () => this.chromeTheme(),
|
|
16963
|
+
lookup: (x, y) => this.chrome.labelTooltipAt(x, y)
|
|
16964
|
+
});
|
|
16656
16965
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
16657
16966
|
projector: () => this.drawingProjector(),
|
|
16658
16967
|
dpr: () => this.coords.dpr,
|
|
@@ -16692,9 +17001,10 @@ var NativeRenderer = class {
|
|
|
16692
17001
|
this.inputsUI.setDialogHost(this.dialogHost);
|
|
16693
17002
|
this.inputsUI.setSymbolPicker(this.symbolPicker);
|
|
16694
17003
|
this.inputsUI.setLegendActions(this.legendActionsProvider);
|
|
17004
|
+
this.inputsUI.setLegendCallouts(this.legendCalloutsProvider);
|
|
16695
17005
|
this.inputsUI.setLegendOverviewAction(this.legendOverviewAction);
|
|
16696
17006
|
this.inputsUI.setOnChange((c) => {
|
|
16697
|
-
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value });
|
|
17007
|
+
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value, ...c.kind ? { kind: c.kind } : {} });
|
|
16698
17008
|
});
|
|
16699
17009
|
this.inputsUI.setOnRemove((id) => {
|
|
16700
17010
|
for (const cb of this.removeIndicatorCbs) cb(id);
|
|
@@ -16884,6 +17194,8 @@ var NativeRenderer = class {
|
|
|
16884
17194
|
this.settingsButton = null;
|
|
16885
17195
|
this.plot?.removeEventListener("pointermove", this.onScrollProximityMove);
|
|
16886
17196
|
this.plot?.removeEventListener("pointerleave", this.onScrollProximityLeave);
|
|
17197
|
+
this.labelTooltip?.destroy();
|
|
17198
|
+
this.labelTooltip = null;
|
|
16887
17199
|
this.scrollButton?.remove();
|
|
16888
17200
|
this.scrollButton = null;
|
|
16889
17201
|
for (const l of this.extLayers) l.instance.destroy?.();
|
|
@@ -17124,7 +17436,7 @@ var NativeRenderer = class {
|
|
|
17124
17436
|
else this.scene.assignIndicatorZ(model.id);
|
|
17125
17437
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
17126
17438
|
native: !!model.native,
|
|
17127
|
-
...model.
|
|
17439
|
+
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
17128
17440
|
});
|
|
17129
17441
|
this.syncTables(model);
|
|
17130
17442
|
if (model.native?.type === "volume") {
|
|
@@ -17175,8 +17487,8 @@ var NativeRenderer = class {
|
|
|
17175
17487
|
this.paneControls?.refresh();
|
|
17176
17488
|
this.scheduler.invalidate(4 /* Full */);
|
|
17177
17489
|
}
|
|
17178
|
-
setIndicatorInputs(handle, values) {
|
|
17179
|
-
this.inputsUI.setValues(handle.id, values);
|
|
17490
|
+
setIndicatorInputs(handle, values, props) {
|
|
17491
|
+
this.inputsUI.setValues(handle.id, values, props);
|
|
17180
17492
|
}
|
|
17181
17493
|
setSymbolPicker(picker) {
|
|
17182
17494
|
this.symbolPicker = picker;
|
|
@@ -17186,6 +17498,10 @@ var NativeRenderer = class {
|
|
|
17186
17498
|
this.legendActionsProvider = provider;
|
|
17187
17499
|
this.inputsUI?.setLegendActions(provider);
|
|
17188
17500
|
}
|
|
17501
|
+
setLegendCallouts(provider) {
|
|
17502
|
+
this.legendCalloutsProvider = provider;
|
|
17503
|
+
this.inputsUI?.setLegendCallouts(provider);
|
|
17504
|
+
}
|
|
17189
17505
|
setLegendOverviewAction(action) {
|
|
17190
17506
|
this.legendOverviewAction = action;
|
|
17191
17507
|
this.inputsUI?.setLegendOverviewAction(action);
|
|
@@ -18166,7 +18482,11 @@ var NativeRenderer = class {
|
|
|
18166
18482
|
}
|
|
18167
18483
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
18168
18484
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
18169
|
-
|
|
18485
|
+
let dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
18486
|
+
if (pane === pricePane) {
|
|
18487
|
+
const or = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
|
|
18488
|
+
if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
|
|
18489
|
+
}
|
|
18170
18490
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
18171
18491
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
18172
18492
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -18440,6 +18760,7 @@ var NativeRenderer = class {
|
|
|
18440
18760
|
for (const m of models) {
|
|
18441
18761
|
const off = this.scene.offsetOf(m.id);
|
|
18442
18762
|
for (const s of m.series) {
|
|
18763
|
+
if (s.overlay === true) continue;
|
|
18443
18764
|
if (isLineLikeSeries(s)) {
|
|
18444
18765
|
const v = s.points[i0 - off]?.value;
|
|
18445
18766
|
if (v != null && Number.isFinite(v)) return v;
|
|
@@ -18820,6 +19141,7 @@ var IndicatorHandleImpl = class {
|
|
|
18820
19141
|
constructor(id, title, controller, source) {
|
|
18821
19142
|
this.controller = controller;
|
|
18822
19143
|
this.schema = [];
|
|
19144
|
+
this.propsSchema = [];
|
|
18823
19145
|
this.visibleState = true;
|
|
18824
19146
|
this.bus = new TypedEventBus();
|
|
18825
19147
|
this.id = id;
|
|
@@ -18829,6 +19151,9 @@ var IndicatorHandleImpl = class {
|
|
|
18829
19151
|
get inputs() {
|
|
18830
19152
|
return this.schema;
|
|
18831
19153
|
}
|
|
19154
|
+
get props() {
|
|
19155
|
+
return this.propsSchema;
|
|
19156
|
+
}
|
|
18832
19157
|
get visible() {
|
|
18833
19158
|
return this.visibleState;
|
|
18834
19159
|
}
|
|
@@ -18838,6 +19163,12 @@ var IndicatorHandleImpl = class {
|
|
|
18838
19163
|
setInputs(values) {
|
|
18839
19164
|
this.controller.applyInputs(this.id, values);
|
|
18840
19165
|
}
|
|
19166
|
+
setProp(key, value) {
|
|
19167
|
+
this.controller.applyProps(this.id, { [key]: value });
|
|
19168
|
+
}
|
|
19169
|
+
setProps(values) {
|
|
19170
|
+
this.controller.applyProps(this.id, values);
|
|
19171
|
+
}
|
|
18841
19172
|
setVisible(visible) {
|
|
18842
19173
|
this.controller.setVisible(this.id, visible);
|
|
18843
19174
|
}
|
|
@@ -18857,6 +19188,9 @@ var IndicatorHandleImpl = class {
|
|
|
18857
19188
|
setSchema(schema) {
|
|
18858
19189
|
this.schema = schema;
|
|
18859
19190
|
}
|
|
19191
|
+
setPropsSchema(schema) {
|
|
19192
|
+
this.propsSchema = schema;
|
|
19193
|
+
}
|
|
18860
19194
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
18861
19195
|
setVisibleState(visible) {
|
|
18862
19196
|
this.visibleState = visible;
|
|
@@ -18870,6 +19204,8 @@ var IndicatorHandleImpl = class {
|
|
|
18870
19204
|
function summarizeModel(model) {
|
|
18871
19205
|
const series = {};
|
|
18872
19206
|
for (const s of model.series) series[s.kind] = (series[s.kind] ?? 0) + 1;
|
|
19207
|
+
const countOverlay = (items) => items?.filter((i) => i.overlay === true).length ?? 0;
|
|
19208
|
+
const forcedOverlay = countOverlay(model.series) + countOverlay(model.fills) + countOverlay(model.backgrounds) + countOverlay(model.lines) + countOverlay(model.boxes) + countOverlay(model.labels) + countOverlay(model.polylines) + countOverlay(model.linefills) + countOverlay(model.tables);
|
|
18873
19209
|
return {
|
|
18874
19210
|
id: model.id,
|
|
18875
19211
|
title: model.title,
|
|
@@ -18889,7 +19225,8 @@ function summarizeModel(model) {
|
|
|
18889
19225
|
tables: model.tables?.length ?? 0,
|
|
18890
19226
|
barColors: model.barColors?.length ?? 0,
|
|
18891
19227
|
trades: model.trades?.length ?? 0,
|
|
18892
|
-
inputs: model.inputs.length
|
|
19228
|
+
inputs: model.inputs.length,
|
|
19229
|
+
forcedOverlay
|
|
18893
19230
|
};
|
|
18894
19231
|
}
|
|
18895
19232
|
function inspectModels(models) {
|
|
@@ -18910,7 +19247,8 @@ function inspectModels(models) {
|
|
|
18910
19247
|
linefills: sum((s) => s.linefills),
|
|
18911
19248
|
tables: sum((s) => s.tables),
|
|
18912
19249
|
barColors: sum((s) => s.barColors),
|
|
18913
|
-
trades: sum((s) => s.trades)
|
|
19250
|
+
trades: sum((s) => s.trades),
|
|
19251
|
+
forcedOverlay: sum((s) => s.forcedOverlay)
|
|
18914
19252
|
}
|
|
18915
19253
|
};
|
|
18916
19254
|
}
|
|
@@ -19054,7 +19392,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19054
19392
|
for (const engine of engines) this.registerEngine(engine.language, engine);
|
|
19055
19393
|
this.defaultLanguage = config.defaultLanguage;
|
|
19056
19394
|
this.renderer.mount(container, config.theme);
|
|
19057
|
-
this.renderer.onInputChange(
|
|
19395
|
+
this.renderer.onInputChange(
|
|
19396
|
+
(e) => e.kind === "prop" ? this.applyProps(e.indicatorId, { [e.key]: e.value }) : this.applyInputs(e.indicatorId, { [e.key]: e.value })
|
|
19397
|
+
);
|
|
19058
19398
|
this.renderer.onRemoveIndicator((id) => this.removeIndicator(id));
|
|
19059
19399
|
this.renderer.onToggleIndicatorVisible?.((id, visible) => this.setVisible(id, visible));
|
|
19060
19400
|
this.paneActionUnsub = this.renderer.onPaneAction?.((a) => this.handlePaneAction(a)) ?? null;
|
|
@@ -19734,7 +20074,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19734
20074
|
const title = options.title ?? "Indicator";
|
|
19735
20075
|
const handle = new IndicatorHandleImpl(id, title, this, source);
|
|
19736
20076
|
this.handles.set(id, handle);
|
|
19737
|
-
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} } });
|
|
20077
|
+
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} }, propValues: { ...options.props ?? {} } });
|
|
19738
20078
|
void this.startIndicator(id, source, options, handle);
|
|
19739
20079
|
return handle;
|
|
19740
20080
|
}
|
|
@@ -19757,7 +20097,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19757
20097
|
return handle;
|
|
19758
20098
|
}
|
|
19759
20099
|
const inputValues = { ...descriptor.defaultInputs(), ...options.inputs ?? {} };
|
|
19760
|
-
this.registry.add({ id, title, source: type, inputValues, native: { type, instance: descriptor.create(), descriptor } });
|
|
20100
|
+
this.registry.add({ id, title, source: type, inputValues, propValues: {}, native: { type, instance: descriptor.create(), descriptor } });
|
|
19761
20101
|
handle.setSchema(descriptor.inputsSchema());
|
|
19762
20102
|
void this.startNativeIndicator(id, handle);
|
|
19763
20103
|
return handle;
|
|
@@ -19810,6 +20150,19 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19810
20150
|
if (record.session) record.session.update(record.inputValues);
|
|
19811
20151
|
else if (record.native && !record.hidden) record.native.instance.setInputs(record.inputValues);
|
|
19812
20152
|
}
|
|
20153
|
+
/** IndicatorController: re-run an indicator with merged declaration-prop overrides.
|
|
20154
|
+
* Same lifecycle as {@link applyInputs} — a prop change replays the whole script.
|
|
20155
|
+
* Script indicators only: natives have no declaration props. */
|
|
20156
|
+
applyProps(id, values) {
|
|
20157
|
+
const record = this.registry.get(id);
|
|
20158
|
+
if (!record?.session) return;
|
|
20159
|
+
record.propValues = { ...record.propValues, ...values };
|
|
20160
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20161
|
+
record.pendingStructural = true;
|
|
20162
|
+
if (!record.hidden) this.setLoading(record, true);
|
|
20163
|
+
record.pendingCause = "inputs";
|
|
20164
|
+
record.session.update(record.inputValues, record.propValues);
|
|
20165
|
+
}
|
|
19813
20166
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
19814
20167
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
19815
20168
|
listIndicators() {
|
|
@@ -19958,6 +20311,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19958
20311
|
for (const input of prepared.inputs) defaults[input.key] = input.defval;
|
|
19959
20312
|
record.inputValues = { ...defaults, ...record.inputValues };
|
|
19960
20313
|
handle.setSchema(prepared.inputs);
|
|
20314
|
+
const propDefaults = {};
|
|
20315
|
+
for (const prop of prepared.props ?? []) propDefaults[prop.key] = prop.defval;
|
|
20316
|
+
record.propValues = { ...propDefaults, ...record.propValues };
|
|
20317
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
19961
20318
|
this.mountLoadingPlaceholder(id, record);
|
|
19962
20319
|
this.executeIndicator(id, handle);
|
|
19963
20320
|
} catch (err) {
|
|
@@ -19981,6 +20338,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19981
20338
|
getBars: () => this.bars,
|
|
19982
20339
|
fetchSeries: (sym, tf, range) => this.fetchSeries(sym, tf, range),
|
|
19983
20340
|
inputs: record.inputValues,
|
|
20341
|
+
props: record.propValues,
|
|
19984
20342
|
visibleRange: this.currentVisibleRange(),
|
|
19985
20343
|
mode,
|
|
19986
20344
|
// Mid-backfill session starts (add / re-show / price-style re-execute) let the
|
|
@@ -20074,6 +20432,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20074
20432
|
const meta = record.prepared.meta;
|
|
20075
20433
|
const model = {
|
|
20076
20434
|
id,
|
|
20435
|
+
// Deliberately the FULL title, never a shorttitle: while the script loads the
|
|
20436
|
+
// legend identifies it by its full name; the compact shorttitle arrives with
|
|
20437
|
+
// the first computed model and takes over from there.
|
|
20077
20438
|
title: record.options?.title ?? meta.title,
|
|
20078
20439
|
overlay: meta.overlay,
|
|
20079
20440
|
paneHint: meta.overlay ? "price" : "new",
|
|
@@ -20082,7 +20443,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20082
20443
|
backgrounds: [],
|
|
20083
20444
|
priceLines: [],
|
|
20084
20445
|
inputs: record.prepared.inputs,
|
|
20085
|
-
inputValues: record.inputValues
|
|
20446
|
+
inputValues: record.inputValues,
|
|
20447
|
+
...record.prepared.props ? { props: record.prepared.props, propValues: record.propValues } : {}
|
|
20086
20448
|
};
|
|
20087
20449
|
const paneId = this.routePane(id, model, record.options ?? {});
|
|
20088
20450
|
this.placeModel(model, id, paneId);
|
|
@@ -20315,7 +20677,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20315
20677
|
record.model = model;
|
|
20316
20678
|
if (record.pendingStructural) {
|
|
20317
20679
|
record.renderHandle = this.renderer.mountIndicator(model);
|
|
20318
|
-
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues);
|
|
20680
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20319
20681
|
record.pendingStructural = false;
|
|
20320
20682
|
} else {
|
|
20321
20683
|
this.renderer.updateIndicator(record.renderHandle, modelToValuePatch(model));
|
|
@@ -20342,16 +20704,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20342
20704
|
placeModel(model, id, paneId) {
|
|
20343
20705
|
model.id = id;
|
|
20344
20706
|
model.paneId = paneId;
|
|
20345
|
-
|
|
20346
|
-
for (const
|
|
20347
|
-
for (const
|
|
20707
|
+
const paneOf = (item) => item.overlay === true ? "price" : paneId;
|
|
20708
|
+
for (const series of model.series) series.paneId = paneOf(series);
|
|
20709
|
+
for (const fill of model.fills) fill.paneId = paneOf(fill);
|
|
20710
|
+
for (const bg of model.backgrounds) bg.paneId = paneOf(bg);
|
|
20348
20711
|
for (const line of model.priceLines) line.paneId = paneId;
|
|
20349
|
-
if (model.lines) for (const ln of model.lines) ln.paneId =
|
|
20350
|
-
if (model.boxes) for (const bx of model.boxes) bx.paneId =
|
|
20351
|
-
if (model.labels) for (const lb of model.labels) lb.paneId =
|
|
20352
|
-
if (model.polylines) for (const pl of model.polylines) pl.paneId =
|
|
20353
|
-
if (model.linefills) for (const lf of model.linefills) lf.paneId =
|
|
20354
|
-
if (model.tables) for (const tb of model.tables) tb.paneId =
|
|
20712
|
+
if (model.lines) for (const ln of model.lines) ln.paneId = paneOf(ln);
|
|
20713
|
+
if (model.boxes) for (const bx of model.boxes) bx.paneId = paneOf(bx);
|
|
20714
|
+
if (model.labels) for (const lb of model.labels) lb.paneId = paneOf(lb);
|
|
20715
|
+
if (model.polylines) for (const pl of model.polylines) pl.paneId = paneOf(pl);
|
|
20716
|
+
if (model.linefills) for (const lf of model.linefills) lf.paneId = paneOf(lf);
|
|
20717
|
+
if (model.tables) for (const tb of model.tables) tb.paneId = paneOf(tb);
|
|
20355
20718
|
}
|
|
20356
20719
|
emitContextChanged(id) {
|
|
20357
20720
|
if (!this.registry.get(id)?.session?.getContext) return;
|
|
@@ -21011,4 +21374,4 @@ function resolveElement(container) {
|
|
|
21011
21374
|
return element;
|
|
21012
21375
|
}
|
|
21013
21376
|
|
|
21014
|
-
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LIGHT_THEME, MultiProviderFeed, NativeRenderer, RendererControl, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultToolbar, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs, tzButtonLabel, tzMenuLabel, tzOffset };
|
|
21377
|
+
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MultiProviderFeed, NativeRenderer, RendererControl, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultToolbar, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs, tzButtonLabel, tzMenuLabel, tzOffset };
|