@luxalgo/vela 0.7.8 → 0.8.0
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/NOTICE +28 -28
- package/dist/{DataProvider-CGa4KHRa.d.cts → DataProvider-BrlxntPN.d.cts} +1 -1
- package/dist/{DataProvider-CmWUtDVL.d.ts → DataProvider-DwFIoV4r.d.ts} +1 -1
- package/dist/{chunk-WTWGEB7K.js → chunk-2IAETM2I.js} +260 -2
- package/dist/{chunk-CAFCLMPF.js → chunk-BZQM2XO7.js} +1 -0
- package/dist/{chunk-M6GP7DAX.js → chunk-E3GU6AUI.js} +459 -37
- package/dist/{chunk-BFA32GOU.js → chunk-EZ5FWVLA.js} +5 -2
- package/dist/{chunk-PVGWNF7V.js → chunk-KA77UTRF.js} +2 -2
- package/dist/{chunk-N4ZZYLMP.js → chunk-QBPUT5SU.js} +686 -251
- package/dist/{contributions-DF_Ea07m.d.cts → contributions-CtlptxR8.d.cts} +187 -5
- package/dist/{contributions-nKOSeLy8.d.ts → contributions-tjNExo1o.d.ts} +187 -5
- package/dist/index.cjs +966 -247
- package/dist/index.d.cts +24 -8
- package/dist/index.d.ts +24 -8
- package/dist/index.js +28 -4
- package/dist/{options-D1AJKsn_.d.ts → options-BnEPUuVV.d.cts} +1 -1
- package/dist/{options-D1AJKsn_.d.cts → options-BnEPUuVV.d.ts} +1 -1
- package/dist/{plugin-6F04gjQe.d.cts → plugin-BWxJRKzS.d.cts} +10 -4
- package/dist/{plugin-BLsNipkc.d.ts → plugin-e2WUEtiI.d.ts} +10 -4
- package/dist/plugin.cjs +1 -0
- 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-model-DAFyqVmR.d.ts → statusline-model-DFY7tuHj.d.cts} +5 -3
- package/dist/{statusline-model-BoWJ3cWQ.d.cts → statusline-model-zKXtGIjj.d.ts} +5 -3
- package/dist/ui.cjs +263 -0
- package/dist/ui.d.cts +75 -2
- package/dist/ui.d.ts +75 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +968 -249
- package/dist/vela.global.min.js +73 -71
- package/dist/widget.cjs +1411 -292
- package/dist/widget.d.cts +26 -7
- package/dist/widget.d.ts +26 -7
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1414 -292
- package/dist/workspace.d.cts +124 -6
- package/dist/workspace.d.ts +124 -6
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { deserializeDrawing, chartTypes, rendererLayers, isLineLikeSeries, seriesShownOn, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, nativeInstanceChannel, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, DEFAULT_DRAWING_COLOR, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, seriesInScale, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-
|
|
2
|
-
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-
|
|
3
|
-
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, INFO, BULLISH, BEARISH, isDarkColor, injectStyles, SERIES_LINE, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, iconMarkup, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-
|
|
1
|
+
import { deserializeDrawing, chartTypes, rendererLayers, isLineLikeSeries, seriesShownOn, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, nativeInstanceChannel, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, DEFAULT_DRAWING_COLOR, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, seriesInScale, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-EZ5FWVLA.js';
|
|
2
|
+
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, DatePicker, eventDismissedPopover, toggleSelectList, normalizeDateInput, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-2IAETM2I.js';
|
|
3
|
+
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, INFO, BULLISH, BEARISH, isDarkColor, injectStyles, SERIES_LINE, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, iconMarkup, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-BZQM2XO7.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
6
6
|
var TypedEventBus = class {
|
|
@@ -514,6 +514,12 @@ var RendererControl = class {
|
|
|
514
514
|
onCrosshairMove(cb) {
|
|
515
515
|
return this.renderer.onCrosshairMove(cb);
|
|
516
516
|
}
|
|
517
|
+
/** A click — or a touch tap — on the plot, never the end of a pan: the open time of
|
|
518
|
+
* the bar under it (`null` off the bars). On touch, where a tap moves no crosshair,
|
|
519
|
+
* this is how an interaction learns which bar was chosen. */
|
|
520
|
+
onClick(cb) {
|
|
521
|
+
return this.renderer.onClick(cb);
|
|
522
|
+
}
|
|
517
523
|
/** Touch long-press on a price or time axis strip — silent no-op without the seam. */
|
|
518
524
|
onAxisLongPress(cb) {
|
|
519
525
|
return this.renderer.onAxisLongPress?.(cb) ?? (() => void 0);
|
|
@@ -1780,11 +1786,76 @@ var MarksControl = class {
|
|
|
1780
1786
|
}
|
|
1781
1787
|
};
|
|
1782
1788
|
|
|
1789
|
+
// src/core/ReplayControl.ts
|
|
1790
|
+
var ReplayControl = class {
|
|
1791
|
+
constructor(ctrl) {
|
|
1792
|
+
this.ctrl = ctrl;
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Enter replay (or seek, when already replaying) at `opts.from`. Resolves once the
|
|
1796
|
+
* chart shows the rewound history — after any in-flight history load has finished.
|
|
1797
|
+
* Starts paused; call {@link play} or {@link step}.
|
|
1798
|
+
*/
|
|
1799
|
+
start(opts) {
|
|
1800
|
+
return this.ctrl.replayStart(opts);
|
|
1801
|
+
}
|
|
1802
|
+
/** Reveal the next bar — or, mid-way through a bar played tick by tick, complete it.
|
|
1803
|
+
* Returns false when replay is off or nothing is left. */
|
|
1804
|
+
step() {
|
|
1805
|
+
return this.ctrl.replayStep();
|
|
1806
|
+
}
|
|
1807
|
+
/** Reveal the next UPDATE: with {@link setTicks}, the next tick of the forming bar — or
|
|
1808
|
+
* the next bar opened at its first tick (once its ticks are in, if still loading);
|
|
1809
|
+
* otherwise the next whole bar, like {@link step}. Returns false when nothing is left. */
|
|
1810
|
+
stepUpdate() {
|
|
1811
|
+
return this.ctrl.replayStepUpdate();
|
|
1812
|
+
}
|
|
1813
|
+
/** Reveal one bar (one tick, with {@link setTicks}) every `intervalMs` (default: the last
|
|
1814
|
+
* interval used, else 1000). Calling it again while playing changes the pace. */
|
|
1815
|
+
play(intervalMs2) {
|
|
1816
|
+
this.ctrl.replayPlay(intervalMs2);
|
|
1817
|
+
return this;
|
|
1818
|
+
}
|
|
1819
|
+
pause() {
|
|
1820
|
+
this.ctrl.replayPause();
|
|
1821
|
+
return this;
|
|
1822
|
+
}
|
|
1823
|
+
/** Leave replay: the full history comes back and live updates resume. Also cancels a
|
|
1824
|
+
* `start()` still loading older history (the chart returns to its previous depth). */
|
|
1825
|
+
stop() {
|
|
1826
|
+
this.ctrl.replayStop();
|
|
1827
|
+
return this;
|
|
1828
|
+
}
|
|
1829
|
+
get state() {
|
|
1830
|
+
return this.ctrl.replayState();
|
|
1831
|
+
}
|
|
1832
|
+
/** The loaded history a replay can walk — hidden bars included while replaying. Null
|
|
1833
|
+
* before any bar loaded. A start older than `first` deepens the history first. */
|
|
1834
|
+
get bounds() {
|
|
1835
|
+
return this.ctrl.replayBounds();
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Play each revealed bar as a series of intrabar updates instead of whole: while
|
|
1839
|
+
* playing, the forming candle opens at the first tick, then every tick moves its close
|
|
1840
|
+
* and stretches its high and low, exactly like a live tick — indicators, chart types
|
|
1841
|
+
* and the `bar` event follow — and the last one settles it on the stored bar. The play
|
|
1842
|
+
* interval is then the time between two TICKS: a bar lasts as many intervals as it
|
|
1843
|
+
* has ticks (below ~16 ms, ticks are batched per frame). The next bar's ticks are
|
|
1844
|
+
* requested while the current one plays; playback waits for them. `null` goes back to
|
|
1845
|
+
* whole bars. Takes effect from the next bar; a bar forming when the source changes
|
|
1846
|
+
* completes at once.
|
|
1847
|
+
*/
|
|
1848
|
+
setTicks(source) {
|
|
1849
|
+
this.ctrl.replaySetTicks(source);
|
|
1850
|
+
return this;
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
|
|
1783
1854
|
// src/core/util/wall-clock.ts
|
|
1784
1855
|
var BOUNDARY_SLACK_MS = 5;
|
|
1785
1856
|
var SecondClock = class _SecondClock {
|
|
1786
|
-
constructor(
|
|
1787
|
-
this.now =
|
|
1857
|
+
constructor(now2 = () => Date.now()) {
|
|
1858
|
+
this.now = now2;
|
|
1788
1859
|
this.subs = /* @__PURE__ */ new Set();
|
|
1789
1860
|
this.timer = null;
|
|
1790
1861
|
}
|
|
@@ -1800,15 +1871,15 @@ var SecondClock = class _SecondClock {
|
|
|
1800
1871
|
};
|
|
1801
1872
|
}
|
|
1802
1873
|
/** Milliseconds from `now` to just past the next second boundary. */
|
|
1803
|
-
static delayToNextSecond(
|
|
1804
|
-
const intoSecond = (
|
|
1874
|
+
static delayToNextSecond(now2) {
|
|
1875
|
+
const intoSecond = (now2 % 1e3 + 1e3) % 1e3;
|
|
1805
1876
|
return 1e3 - intoSecond + BOUNDARY_SLACK_MS;
|
|
1806
1877
|
}
|
|
1807
1878
|
arm() {
|
|
1808
1879
|
this.timer = setTimeout(() => {
|
|
1809
1880
|
this.timer = null;
|
|
1810
|
-
const
|
|
1811
|
-
for (const cb of this.subs) cb(
|
|
1881
|
+
const now2 = this.now();
|
|
1882
|
+
for (const cb of this.subs) cb(now2);
|
|
1812
1883
|
if (this.subs.size > 0) this.arm();
|
|
1813
1884
|
}, _SecondClock.delayToNextSecond(this.now()));
|
|
1814
1885
|
}
|
|
@@ -3801,10 +3872,16 @@ var IndicatorInputsDialog = class {
|
|
|
3801
3872
|
this.calendarPop = null;
|
|
3802
3873
|
this.calendarAnchor = null;
|
|
3803
3874
|
},
|
|
3804
|
-
content: (el) =>
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3875
|
+
content: (el) => {
|
|
3876
|
+
const picker = new DatePicker({
|
|
3877
|
+
value: current,
|
|
3878
|
+
onPick: (iso) => {
|
|
3879
|
+
pop.hide();
|
|
3880
|
+
onPick(iso);
|
|
3881
|
+
}
|
|
3882
|
+
});
|
|
3883
|
+
el.append(picker.el);
|
|
3884
|
+
}
|
|
3808
3885
|
});
|
|
3809
3886
|
this.calendarPop = pop;
|
|
3810
3887
|
this.calendarAnchor = anchor;
|
|
@@ -3929,28 +4006,6 @@ function ensureDialogStyles() {
|
|
|
3929
4006
|
.vela-ind-combo-chevron{position:absolute;right:0;top:0;bottom:0;width:26px;border:none;background:transparent;color:inherit;opacity:0.55;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;}
|
|
3930
4007
|
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
3931
4008
|
.vela-ind-cal{background:var(--vela-bg);color:var(--vela-fg);border:none;border-radius:6px;box-shadow:var(--vela-shadow);font:14px var(--vela-font);padding:10px 12px;user-select:none;}
|
|
3932
|
-
.vela-ind-cal [hidden]{display:none !important;}
|
|
3933
|
-
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
3934
|
-
.vela-ind-cal-title{flex:1;display:flex;align-items:center;justify-content:center;gap:2px;min-width:0;}
|
|
3935
|
-
.vela-ind-cal-switch{border:none;background:transparent;color:var(--vela-fg-bright);font:inherit;font-weight:600;font-size:14px;padding:2px 6px;border-radius:4px;cursor:pointer;}
|
|
3936
|
-
.vela-ind-cal-switch:not(:disabled):hover{background:var(--vela-hover);}
|
|
3937
|
-
.vela-ind-cal-switch:disabled{cursor:default;}
|
|
3938
|
-
.vela-ind-cal-nav{width:24px;height:24px;border:none;background:transparent;color:var(--vela-fg-muted);border-radius:4px;padding:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;}
|
|
3939
|
-
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
3940
|
-
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
3941
|
-
.vela-ind-cal-week{margin-bottom:4px;color:var(--vela-fg-muted);font-size:11px;text-align:center;}
|
|
3942
|
-
.vela-ind-cal-week span{line-height:20px;}
|
|
3943
|
-
.vela-ind-cal-blank{width:28px;height:28px;}
|
|
3944
|
-
.vela-ind-cal-day{width:28px;height:28px;border:none;background:transparent;color:inherit;border-radius:4px;padding:0;cursor:pointer;font:inherit;font-size:14px;}
|
|
3945
|
-
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
3946
|
-
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
3947
|
-
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
3948
|
-
.vela-ind-cal-cells{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;width:calc(7 * 28px + 6 * 2px);}
|
|
3949
|
-
.vela-ind-cal-cell{height:36px;border:none;background:transparent;color:inherit;border-radius:4px;padding:0 4px;cursor:pointer;font:inherit;font-size:14px;}
|
|
3950
|
-
.vela-ind-cal-cell:hover{background:var(--vela-hover);}
|
|
3951
|
-
.vela-ind-cal-cell[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
3952
|
-
.vela-ind-cal-cell[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
3953
|
-
.vela-ind-cal-cell[data-outside]{opacity:0.45;}
|
|
3954
4009
|
${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
3955
4010
|
.vela-ind-tab{font-weight:600;font-size:13px;line-height:20px;transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
3956
4011
|
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
@@ -3984,174 +4039,6 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
|
|
3984
4039
|
const v = `${hh}:${mm}`;
|
|
3985
4040
|
return { value: v, label: v };
|
|
3986
4041
|
});
|
|
3987
|
-
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
3988
|
-
var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
3989
|
-
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
3990
|
-
function normalizeDateInput(raw) {
|
|
3991
|
-
const t = raw.trim();
|
|
3992
|
-
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(t);
|
|
3993
|
-
if (!m) return null;
|
|
3994
|
-
const y = Number(m[1]);
|
|
3995
|
-
const mo = Number(m[2]);
|
|
3996
|
-
const d = Number(m[3]);
|
|
3997
|
-
const dt = new Date(y, mo - 1, d);
|
|
3998
|
-
if (dt.getFullYear() !== y || dt.getMonth() !== mo - 1 || dt.getDate() !== d) return null;
|
|
3999
|
-
return isoDate(dt);
|
|
4000
|
-
}
|
|
4001
|
-
function parseIsoDate(raw) {
|
|
4002
|
-
const iso = normalizeDateInput(raw);
|
|
4003
|
-
if (!iso) return null;
|
|
4004
|
-
const [y, mo, d] = iso.split("-").map(Number);
|
|
4005
|
-
return new Date(y, mo - 1, d);
|
|
4006
|
-
}
|
|
4007
|
-
function isoDate(d) {
|
|
4008
|
-
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
4009
|
-
}
|
|
4010
|
-
function fillCalendar(el, current, onPick) {
|
|
4011
|
-
const today = /* @__PURE__ */ new Date();
|
|
4012
|
-
const selected = parseIsoDate(current);
|
|
4013
|
-
let year = selected?.getFullYear() ?? today.getFullYear();
|
|
4014
|
-
let month = selected?.getMonth() ?? today.getMonth();
|
|
4015
|
-
let mode = "date";
|
|
4016
|
-
const prev2 = document.createElement("button");
|
|
4017
|
-
prev2.type = "button";
|
|
4018
|
-
prev2.className = "vela-ind-cal-nav";
|
|
4019
|
-
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
4020
|
-
const next = document.createElement("button");
|
|
4021
|
-
next.type = "button";
|
|
4022
|
-
next.className = "vela-ind-cal-nav";
|
|
4023
|
-
next.innerHTML = iconAt("chevron-right", 14);
|
|
4024
|
-
const monthBtn = document.createElement("button");
|
|
4025
|
-
monthBtn.type = "button";
|
|
4026
|
-
monthBtn.className = "vela-ind-cal-switch";
|
|
4027
|
-
monthBtn.setAttribute("aria-label", "Choose month");
|
|
4028
|
-
const yearBtn = document.createElement("button");
|
|
4029
|
-
yearBtn.type = "button";
|
|
4030
|
-
yearBtn.className = "vela-ind-cal-switch";
|
|
4031
|
-
yearBtn.setAttribute("aria-label", "Choose year");
|
|
4032
|
-
const title = document.createElement("div");
|
|
4033
|
-
title.className = "vela-ind-cal-title";
|
|
4034
|
-
title.append(monthBtn, yearBtn);
|
|
4035
|
-
const head = document.createElement("div");
|
|
4036
|
-
head.className = "vela-ind-cal-head";
|
|
4037
|
-
head.append(prev2, title, next);
|
|
4038
|
-
const week = document.createElement("div");
|
|
4039
|
-
week.className = "vela-ind-cal-week";
|
|
4040
|
-
for (const d of WEEKDAY_LABELS) {
|
|
4041
|
-
const cell = document.createElement("span");
|
|
4042
|
-
cell.textContent = d;
|
|
4043
|
-
week.appendChild(cell);
|
|
4044
|
-
}
|
|
4045
|
-
const grid = document.createElement("div");
|
|
4046
|
-
const paint = () => {
|
|
4047
|
-
const decade = Math.floor(year / 10) * 10;
|
|
4048
|
-
monthBtn.hidden = mode !== "date";
|
|
4049
|
-
monthBtn.textContent = MONTH_LABELS[month] ?? "";
|
|
4050
|
-
yearBtn.textContent = mode === "year" ? `${decade}-${decade + 9}` : String(year);
|
|
4051
|
-
yearBtn.disabled = mode === "year";
|
|
4052
|
-
week.hidden = mode !== "date";
|
|
4053
|
-
prev2.setAttribute("aria-label", mode === "date" ? "Previous month" : mode === "month" ? "Previous year" : "Previous decade");
|
|
4054
|
-
next.setAttribute("aria-label", mode === "date" ? "Next month" : mode === "month" ? "Next year" : "Next decade");
|
|
4055
|
-
grid.className = mode === "date" ? "vela-ind-cal-grid" : "vela-ind-cal-cells";
|
|
4056
|
-
grid.replaceChildren();
|
|
4057
|
-
if (mode === "date") {
|
|
4058
|
-
const startPad = new Date(year, month, 1).getDay();
|
|
4059
|
-
const days = new Date(year, month + 1, 0).getDate();
|
|
4060
|
-
const todayIso = isoDate(today);
|
|
4061
|
-
const selectedIso = selected ? isoDate(selected) : "";
|
|
4062
|
-
for (let i = 0; i < startPad; i++) {
|
|
4063
|
-
const blank = document.createElement("span");
|
|
4064
|
-
blank.className = "vela-ind-cal-blank";
|
|
4065
|
-
grid.appendChild(blank);
|
|
4066
|
-
}
|
|
4067
|
-
for (let day = 1; day <= days; day++) {
|
|
4068
|
-
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
4069
|
-
const b = document.createElement("button");
|
|
4070
|
-
b.type = "button";
|
|
4071
|
-
b.className = "vela-ind-cal-day";
|
|
4072
|
-
b.textContent = String(day);
|
|
4073
|
-
if (iso === selectedIso) b.dataset.checked = "1";
|
|
4074
|
-
if (iso === todayIso) b.dataset.today = "1";
|
|
4075
|
-
b.addEventListener("click", (e) => {
|
|
4076
|
-
e.stopPropagation();
|
|
4077
|
-
onPick(iso);
|
|
4078
|
-
});
|
|
4079
|
-
grid.appendChild(b);
|
|
4080
|
-
}
|
|
4081
|
-
return;
|
|
4082
|
-
}
|
|
4083
|
-
if (mode === "month") {
|
|
4084
|
-
for (let m = 0; m < 12; m++) {
|
|
4085
|
-
const b = document.createElement("button");
|
|
4086
|
-
b.type = "button";
|
|
4087
|
-
b.className = "vela-ind-cal-cell";
|
|
4088
|
-
b.textContent = MONTH_SHORT[m] ?? "";
|
|
4089
|
-
if (selected?.getFullYear() === year && selected.getMonth() === m) b.dataset.checked = "1";
|
|
4090
|
-
if (today.getFullYear() === year && today.getMonth() === m) b.dataset.today = "1";
|
|
4091
|
-
b.addEventListener("click", (e) => {
|
|
4092
|
-
e.stopPropagation();
|
|
4093
|
-
month = m;
|
|
4094
|
-
mode = "date";
|
|
4095
|
-
paint();
|
|
4096
|
-
});
|
|
4097
|
-
grid.appendChild(b);
|
|
4098
|
-
}
|
|
4099
|
-
return;
|
|
4100
|
-
}
|
|
4101
|
-
for (let y = decade - 1; y <= decade + 10; y++) {
|
|
4102
|
-
const b = document.createElement("button");
|
|
4103
|
-
b.type = "button";
|
|
4104
|
-
b.className = "vela-ind-cal-cell";
|
|
4105
|
-
b.textContent = String(y);
|
|
4106
|
-
if (y < decade || y > decade + 9) b.dataset.outside = "1";
|
|
4107
|
-
if (selected?.getFullYear() === y) b.dataset.checked = "1";
|
|
4108
|
-
if (today.getFullYear() === y) b.dataset.today = "1";
|
|
4109
|
-
b.addEventListener("click", (e) => {
|
|
4110
|
-
e.stopPropagation();
|
|
4111
|
-
year = y;
|
|
4112
|
-
mode = "month";
|
|
4113
|
-
paint();
|
|
4114
|
-
});
|
|
4115
|
-
grid.appendChild(b);
|
|
4116
|
-
}
|
|
4117
|
-
};
|
|
4118
|
-
const step = (dir) => {
|
|
4119
|
-
if (mode === "date") {
|
|
4120
|
-
month += dir;
|
|
4121
|
-
if (month < 0) {
|
|
4122
|
-
month = 11;
|
|
4123
|
-
year -= 1;
|
|
4124
|
-
}
|
|
4125
|
-
if (month > 11) {
|
|
4126
|
-
month = 0;
|
|
4127
|
-
year += 1;
|
|
4128
|
-
}
|
|
4129
|
-
} else {
|
|
4130
|
-
year += mode === "month" ? dir : dir * 10;
|
|
4131
|
-
}
|
|
4132
|
-
paint();
|
|
4133
|
-
};
|
|
4134
|
-
prev2.addEventListener("click", (e) => {
|
|
4135
|
-
e.stopPropagation();
|
|
4136
|
-
step(-1);
|
|
4137
|
-
});
|
|
4138
|
-
next.addEventListener("click", (e) => {
|
|
4139
|
-
e.stopPropagation();
|
|
4140
|
-
step(1);
|
|
4141
|
-
});
|
|
4142
|
-
monthBtn.addEventListener("click", (e) => {
|
|
4143
|
-
e.stopPropagation();
|
|
4144
|
-
mode = "month";
|
|
4145
|
-
paint();
|
|
4146
|
-
});
|
|
4147
|
-
yearBtn.addEventListener("click", (e) => {
|
|
4148
|
-
e.stopPropagation();
|
|
4149
|
-
mode = "year";
|
|
4150
|
-
paint();
|
|
4151
|
-
});
|
|
4152
|
-
paint();
|
|
4153
|
-
el.append(head, week, grid);
|
|
4154
|
-
}
|
|
4155
4042
|
function normalizeTimeInput(raw) {
|
|
4156
4043
|
const t = raw.trim();
|
|
4157
4044
|
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
@@ -5529,6 +5416,22 @@ function priceStyleIds() {
|
|
|
5529
5416
|
for (const t of chartTypes()) if (!out.includes(t.id)) out.push(t.id);
|
|
5530
5417
|
return out;
|
|
5531
5418
|
}
|
|
5419
|
+
function sanitizeCrosshairOverride(value) {
|
|
5420
|
+
if (!value || typeof value !== "object") return null;
|
|
5421
|
+
const v = value;
|
|
5422
|
+
const out = {};
|
|
5423
|
+
if (isBool(v.vertical)) out.vertical = v.vertical;
|
|
5424
|
+
if (isBool(v.horizontal)) out.horizontal = v.horizontal;
|
|
5425
|
+
if (isColor(v.color)) out.color = v.color;
|
|
5426
|
+
if (isNum(v.width)) out.width = Math.max(0.5, Math.min(8, v.width));
|
|
5427
|
+
if (isLineStyle(v.style)) out.style = v.style;
|
|
5428
|
+
if (isNum(v.opacity)) out.opacity = clampOpacity(v.opacity);
|
|
5429
|
+
const shade = v.shadeRight;
|
|
5430
|
+
if (shade && typeof shade === "object" && isColor(shade.color)) {
|
|
5431
|
+
out.shadeRight = isNum(shade.opacity) ? { color: shade.color, opacity: clampOpacity(shade.opacity) } : { color: shade.color };
|
|
5432
|
+
}
|
|
5433
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
5434
|
+
}
|
|
5532
5435
|
function isColor(v) {
|
|
5533
5436
|
return typeof v === "string" && v.trim().length > 0;
|
|
5534
5437
|
}
|
|
@@ -7190,7 +7093,7 @@ var Scheduler = class {
|
|
|
7190
7093
|
|
|
7191
7094
|
// src/renderers/native/core/Animator.ts
|
|
7192
7095
|
var Animator = class {
|
|
7193
|
-
constructor(tick, raf, cancel,
|
|
7096
|
+
constructor(tick, raf, cancel, now2) {
|
|
7194
7097
|
this.tick = tick;
|
|
7195
7098
|
this.handle = null;
|
|
7196
7099
|
this.last = 0;
|
|
@@ -7204,7 +7107,7 @@ var Animator = class {
|
|
|
7204
7107
|
};
|
|
7205
7108
|
this.raf = raf ?? ((cb) => requestAnimationFrame(cb));
|
|
7206
7109
|
this.cancel = cancel ?? ((h) => cancelAnimationFrame(h));
|
|
7207
|
-
this.now =
|
|
7110
|
+
this.now = now2 ?? (() => performance.now());
|
|
7208
7111
|
}
|
|
7209
7112
|
get active() {
|
|
7210
7113
|
return this.handle !== null;
|
|
@@ -8039,6 +7942,8 @@ var SceneGraph = class {
|
|
|
8039
7942
|
/** VPVR-layer config pushed by the VPVR native indicator (null ⇒ layer off). Ephemeral. */
|
|
8040
7943
|
this.vpvrLayer = null;
|
|
8041
7944
|
this.crosshair = null;
|
|
7945
|
+
/** Runtime override of the crosshair's lines and style (the `crosshairOverride` feature). Ephemeral. */
|
|
7946
|
+
this.crosshairOverride = null;
|
|
8042
7947
|
/** How the base price series is drawn on the price pane (candles by default). */
|
|
8043
7948
|
this.priceStyle = "candles";
|
|
8044
7949
|
/** Price-series base painting for the ACTIVE style (see ChartTypeDefinition.basePainting). */
|
|
@@ -9955,9 +9860,9 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
|
|
|
9955
9860
|
}
|
|
9956
9861
|
|
|
9957
9862
|
// src/renderers/native/chrome/countdown.ts
|
|
9958
|
-
function countdownText(barOpen, barMs,
|
|
9863
|
+
function countdownText(barOpen, barMs, now2) {
|
|
9959
9864
|
if (!(barMs > 0)) return null;
|
|
9960
|
-
const remaining = barOpen + barMs -
|
|
9865
|
+
const remaining = barOpen + barMs - now2;
|
|
9961
9866
|
if (remaining <= 0) return null;
|
|
9962
9867
|
return formatCountdown(remaining);
|
|
9963
9868
|
}
|
|
@@ -10769,19 +10674,35 @@ var CrosshairRenderer = class {
|
|
|
10769
10674
|
const dataH = coords.height;
|
|
10770
10675
|
if (!ch || ch.x < 0 || ch.x > dataW || ch.y < 0 || ch.y > dataH) return;
|
|
10771
10676
|
const cs = scene.style.crosshair;
|
|
10677
|
+
const ov = scene.crosshairOverride;
|
|
10678
|
+
const vertical = ov?.vertical !== false;
|
|
10679
|
+
const horizontal = ov?.horizontal !== false;
|
|
10772
10680
|
ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
10773
10681
|
ctx.textBaseline = "middle";
|
|
10774
10682
|
const logical = Math.round(coords.xToLogical(ch.x));
|
|
10775
|
-
const x =
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10683
|
+
const x = crisp(coords.logicalToX(logical), ov?.width ?? cs.width);
|
|
10684
|
+
if (vertical && ov?.shadeRight) {
|
|
10685
|
+
const from = Math.max(0, Math.round(coords.logicalToX(logical + 0.5)));
|
|
10686
|
+
if (from < dataW) {
|
|
10687
|
+
ctx.fillStyle = ov.shadeRight.color;
|
|
10688
|
+
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
10689
|
+
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
10690
|
+
ctx.globalAlpha = 1;
|
|
10691
|
+
}
|
|
10692
|
+
}
|
|
10693
|
+
ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
|
|
10694
|
+
ctx.lineWidth = ov?.width ?? cs.width;
|
|
10695
|
+
ctx.globalAlpha = ov?.opacity ?? cs.opacity;
|
|
10696
|
+
setDash3(ctx, ov?.style ?? cs.style);
|
|
10780
10697
|
ctx.beginPath();
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10698
|
+
if (vertical) {
|
|
10699
|
+
ctx.moveTo(x, 0);
|
|
10700
|
+
ctx.lineTo(x, dataH);
|
|
10701
|
+
}
|
|
10702
|
+
if (horizontal) {
|
|
10703
|
+
ctx.moveTo(0, Math.round(ch.y) + 0.5);
|
|
10704
|
+
ctx.lineTo(dataW, Math.round(ch.y) + 0.5);
|
|
10705
|
+
}
|
|
10785
10706
|
ctx.stroke();
|
|
10786
10707
|
ctx.setLineDash([]);
|
|
10787
10708
|
ctx.lineWidth = 1;
|
|
@@ -10794,11 +10715,11 @@ var CrosshairRenderer = class {
|
|
|
10794
10715
|
}
|
|
10795
10716
|
}
|
|
10796
10717
|
const chipBg = cs.labelBackground ?? theme.borderColor;
|
|
10797
|
-
if (pane && pane.axisFormat !== "none") {
|
|
10718
|
+
if (horizontal && pane && pane.axisFormat !== "none") {
|
|
10798
10719
|
const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
|
|
10799
10720
|
this.chip(ctx, dataW + 1, ch.y, formatAxisValue(pane.scale, pane.bounds.height, price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
|
|
10800
10721
|
}
|
|
10801
|
-
this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
10722
|
+
if (vertical) this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
10802
10723
|
}
|
|
10803
10724
|
destroy() {
|
|
10804
10725
|
this.canvas = null;
|
|
@@ -10809,23 +10730,37 @@ var CrosshairRenderer = class {
|
|
|
10809
10730
|
* along), with that bar's time chip in this chart's own timezone and — when the
|
|
10810
10731
|
* level resolved — the price chip on the right axis. The snap happened upstream
|
|
10811
10732
|
* (`externalCrossPx`, floor-to-containing-bar) — this method only draws. Chips
|
|
10812
|
-
* render slightly dimmed so the ghost still reads as foreign.
|
|
10733
|
+
* render slightly dimmed so the ghost still reads as foreign. A `crosshairOverride`
|
|
10734
|
+
* applies to the ghost as well — its line style, a dropped horizontal line, and the
|
|
10735
|
+
* `shadeRight` veil from the ghost bar's right edge — so a pick that spans several
|
|
10736
|
+
* charts reads the same on every one of them. */
|
|
10813
10737
|
drawExternal(ctx, ext, scene, coords, theme) {
|
|
10814
10738
|
const cs = scene.style.crosshair;
|
|
10739
|
+
const ov = scene.crosshairOverride;
|
|
10815
10740
|
const dataW = coords.width;
|
|
10816
10741
|
const dataH = coords.height;
|
|
10817
|
-
const x =
|
|
10818
|
-
if (
|
|
10742
|
+
const x = crisp(ext.x, ov?.width ?? cs.width);
|
|
10743
|
+
if (ov?.vertical === false) return;
|
|
10744
|
+
if (ov?.shadeRight) {
|
|
10745
|
+
const from = Math.max(0, Math.round(coords.logicalToX(Math.round(coords.xToLogical(ext.x)) + 0.5)));
|
|
10746
|
+
if (from < dataW) {
|
|
10747
|
+
ctx.fillStyle = ov.shadeRight.color;
|
|
10748
|
+
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
10749
|
+
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
10750
|
+
ctx.globalAlpha = 1;
|
|
10751
|
+
}
|
|
10752
|
+
}
|
|
10753
|
+
if (ext.line === false || x < 0 || x > dataW) return;
|
|
10819
10754
|
ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
10820
10755
|
ctx.textBaseline = "middle";
|
|
10821
|
-
ctx.strokeStyle = cs.color ?? theme.textColor;
|
|
10822
|
-
ctx.lineWidth = cs.width;
|
|
10823
|
-
ctx.globalAlpha = cs.opacity * 0.55;
|
|
10824
|
-
setDash3(ctx, cs.style);
|
|
10756
|
+
ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
|
|
10757
|
+
ctx.lineWidth = ov?.width ?? cs.width;
|
|
10758
|
+
ctx.globalAlpha = (ov?.opacity ?? cs.opacity) * 0.55;
|
|
10759
|
+
setDash3(ctx, ov?.style ?? cs.style);
|
|
10825
10760
|
ctx.beginPath();
|
|
10826
10761
|
ctx.moveTo(x, 0);
|
|
10827
10762
|
ctx.lineTo(x, dataH);
|
|
10828
|
-
if (ext.y != null) {
|
|
10763
|
+
if (ext.y != null && ov?.horizontal !== false) {
|
|
10829
10764
|
ctx.moveTo(0, Math.round(ext.y) + 0.5);
|
|
10830
10765
|
ctx.lineTo(dataW, Math.round(ext.y) + 0.5);
|
|
10831
10766
|
}
|
|
@@ -10834,7 +10769,7 @@ var CrosshairRenderer = class {
|
|
|
10834
10769
|
ctx.lineWidth = 1;
|
|
10835
10770
|
ctx.globalAlpha = 0.8;
|
|
10836
10771
|
const chipBg = cs.labelBackground ?? theme.borderColor;
|
|
10837
|
-
if (ext.y != null && ext.price != null) {
|
|
10772
|
+
if (ext.y != null && ext.price != null && ov?.horizontal !== false) {
|
|
10838
10773
|
let pane;
|
|
10839
10774
|
for (const p of scene.panes.values()) {
|
|
10840
10775
|
if (ext.y >= p.bounds.top && ext.y <= p.bounds.top + p.bounds.height) {
|
|
@@ -10874,6 +10809,9 @@ var CrosshairRenderer = class {
|
|
|
10874
10809
|
ctx.textAlign = "start";
|
|
10875
10810
|
}
|
|
10876
10811
|
};
|
|
10812
|
+
function crisp(px, width) {
|
|
10813
|
+
return Math.round(px) + (Math.round(width) % 2 === 1 ? 0.5 : 0);
|
|
10814
|
+
}
|
|
10877
10815
|
function setDash3(ctx, style) {
|
|
10878
10816
|
if (style === "dashed") ctx.setLineDash([6, 4]);
|
|
10879
10817
|
else if (style === "dotted") ctx.setLineDash([2, 3]);
|
|
@@ -18434,7 +18372,7 @@ var NativeRenderer = class {
|
|
|
18434
18372
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
18435
18373
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
18436
18374
|
this.name = "native";
|
|
18437
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues"];
|
|
18375
|
+
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride"];
|
|
18438
18376
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
18439
18377
|
* so moving onto the button doesn't count as leaving). */
|
|
18440
18378
|
this.onScrollProximityMove = (e) => {
|
|
@@ -18613,6 +18551,9 @@ var NativeRenderer = class {
|
|
|
18613
18551
|
this.scene.marks = mergeMarksState(this.scene.marks, value);
|
|
18614
18552
|
this.markPopover?.close();
|
|
18615
18553
|
break;
|
|
18554
|
+
case "crosshairOverride":
|
|
18555
|
+
this.scene.crosshairOverride = sanitizeCrosshairOverride(value);
|
|
18556
|
+
break;
|
|
18616
18557
|
case "keyboard":
|
|
18617
18558
|
this.setKeyboardEnabled(Boolean(value));
|
|
18618
18559
|
return;
|
|
@@ -18726,6 +18667,8 @@ var NativeRenderer = class {
|
|
|
18726
18667
|
return { ...this.scene.tradeMarkers, colors: { ...this.scene.tradeMarkers.colors } };
|
|
18727
18668
|
case "marks":
|
|
18728
18669
|
return { visible: this.scene.marks.visible, groups: { ...this.scene.marks.groups } };
|
|
18670
|
+
case "crosshairOverride":
|
|
18671
|
+
return this.scene.crosshairOverride ? { ...this.scene.crosshairOverride } : null;
|
|
18729
18672
|
case "keyboard":
|
|
18730
18673
|
return this.keyboardEnabled;
|
|
18731
18674
|
case "historyChords":
|
|
@@ -19304,8 +19247,8 @@ var NativeRenderer = class {
|
|
|
19304
19247
|
for (const pane of this.scene.panes.values()) pane.scale = { ...pane.scaleTarget };
|
|
19305
19248
|
this.modelAlpha = 0;
|
|
19306
19249
|
const start = performance.now();
|
|
19307
|
-
const step = (
|
|
19308
|
-
const p = Math.min(1, (
|
|
19250
|
+
const step = (now2) => {
|
|
19251
|
+
const p = Math.min(1, (now2 - start) / duration);
|
|
19309
19252
|
this.scene.bars = p >= 1 ? real : real.map((b, i) => this.revealCandle(b, i, p, n, style));
|
|
19310
19253
|
this.paintData();
|
|
19311
19254
|
if (p < 1) {
|
|
@@ -19321,8 +19264,8 @@ var NativeRenderer = class {
|
|
|
19321
19264
|
fadeInModels() {
|
|
19322
19265
|
const fade = Math.min(INTRO_MODEL_FADE_MS, this.intro.duration || INTRO_MODEL_FADE_MS);
|
|
19323
19266
|
const start = performance.now();
|
|
19324
|
-
const step = (
|
|
19325
|
-
this.modelAlpha = Math.min(1, (
|
|
19267
|
+
const step = (now2) => {
|
|
19268
|
+
this.modelAlpha = Math.min(1, (now2 - start) / fade);
|
|
19326
19269
|
this.paintData();
|
|
19327
19270
|
if (this.modelAlpha < 1) {
|
|
19328
19271
|
this.introRaf = requestAnimationFrame(step);
|
|
@@ -19845,10 +19788,13 @@ var NativeRenderer = class {
|
|
|
19845
19788
|
this.animator.start();
|
|
19846
19789
|
}
|
|
19847
19790
|
} else if (!last || bar.time > last.time) {
|
|
19791
|
+
const vp = this.coords.getViewport();
|
|
19792
|
+
const holdView = n > 0 && vp.rightOffset < 0 && this.scrollTargetRO === null;
|
|
19848
19793
|
this.bars.push(bar);
|
|
19849
19794
|
this.syncLiveEase(bar);
|
|
19850
19795
|
if (this.coords.barInterval > 0) this.coords.appendBar(bar.time);
|
|
19851
19796
|
else this.coords.setBars(this.bars.map((b) => b.time));
|
|
19797
|
+
if (holdView) this.coords.setViewport({ ...vp, rightOffset: vp.rightOffset - 1 });
|
|
19852
19798
|
} else {
|
|
19853
19799
|
return;
|
|
19854
19800
|
}
|
|
@@ -20530,10 +20476,10 @@ var NativeRenderer = class {
|
|
|
20530
20476
|
if (this.markPulseRaf !== null || typeof requestAnimationFrame !== "function") return;
|
|
20531
20477
|
const tick = () => {
|
|
20532
20478
|
this.markPulseRaf = null;
|
|
20533
|
-
const
|
|
20534
|
-
if (this.scene.marksFlash && this.scene.marksFlash.until <=
|
|
20479
|
+
const now2 = frameNow();
|
|
20480
|
+
if (this.scene.marksFlash && this.scene.marksFlash.until <= now2) this.scene.marksFlash = null;
|
|
20535
20481
|
this.scheduler?.invalidate(2 /* Chrome */);
|
|
20536
|
-
const pulsing = this.scene.marksHoverKey !== null &&
|
|
20482
|
+
const pulsing = this.scene.marksHoverKey !== null && now2 - this.scene.marksHoverSince < MARK_PULSE_MS;
|
|
20537
20483
|
if (pulsing || this.scene.marksFlash !== null) this.markPulseRaf = requestAnimationFrame(tick);
|
|
20538
20484
|
};
|
|
20539
20485
|
this.markPulseRaf = requestAnimationFrame(tick);
|
|
@@ -21094,9 +21040,10 @@ var NativeRenderer = class {
|
|
|
21094
21040
|
const ext = this.externalCross;
|
|
21095
21041
|
if (!ext || this.coords.barCount === 0) return null;
|
|
21096
21042
|
const logical = Math.floor(this.coords.timeToLogical(ext.time));
|
|
21097
|
-
if (logical <
|
|
21098
|
-
const x = this.coords.logicalToX(logical);
|
|
21099
|
-
if (!Number.isFinite(x)
|
|
21043
|
+
if (!(logical < this.coords.barCount)) return null;
|
|
21044
|
+
const x = this.coords.logicalToX(Math.max(logical, -1));
|
|
21045
|
+
if (!Number.isFinite(x)) return null;
|
|
21046
|
+
const line = logical >= 0 && x >= 0 && x <= this.coords.width;
|
|
21100
21047
|
let y = null;
|
|
21101
21048
|
if (ext.price != null) {
|
|
21102
21049
|
const pricePane = this.scene.orderedPanes().find((p) => p.kind === "price");
|
|
@@ -21105,7 +21052,7 @@ var NativeRenderer = class {
|
|
|
21105
21052
|
if (!Number.isFinite(y) || y < pricePane.bounds.top || y > pricePane.bounds.top + pricePane.bounds.height) y = null;
|
|
21106
21053
|
}
|
|
21107
21054
|
}
|
|
21108
|
-
return { x, y, time: this.coords.logicalToTime(logical), price: y != null ? ext.price : null };
|
|
21055
|
+
return { x, y, time: this.coords.logicalToTime(Math.max(logical, 0)), price: y != null ? ext.price : null, line };
|
|
21109
21056
|
}
|
|
21110
21057
|
/** Set the sticky magnet mode (called by the drawings toolbar's 3-state button). */
|
|
21111
21058
|
setSnapMode(mode) {
|
|
@@ -22329,6 +22276,7 @@ var CHUNK_BARS = 1e4;
|
|
|
22329
22276
|
var FIRST_PAINT_BARS = 20;
|
|
22330
22277
|
var GAP_FACTOR = 1.5;
|
|
22331
22278
|
var HEAL_COOLDOWN_MS = 5e3;
|
|
22279
|
+
var MIN_TICK_UPDATE_MS = 16;
|
|
22332
22280
|
var EngineOrchestrator = class _EngineOrchestrator {
|
|
22333
22281
|
constructor(container, renderer, feed, engines, config, dataControl) {
|
|
22334
22282
|
this.renderer = renderer;
|
|
@@ -22381,6 +22329,32 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22381
22329
|
this.lastHealAt = 0;
|
|
22382
22330
|
/** The `visibilitychange` catch-up listener (removed on destroy). */
|
|
22383
22331
|
this.onVisible = null;
|
|
22332
|
+
// ── bar replay: `rawBars` holds the history up to the cursor, the rest waits here ──
|
|
22333
|
+
/** Bars still hidden right of the replay cursor, oldest first; null ≡ replay off. */
|
|
22334
|
+
this.replayQueue = null;
|
|
22335
|
+
this.replayTimer = null;
|
|
22336
|
+
this.replayPlaying = false;
|
|
22337
|
+
this.replayIntervalMs = 1e3;
|
|
22338
|
+
/** The depth (`market.bars`) before a replay deepened the history — restored when it ends. */
|
|
22339
|
+
this.replayDepth = null;
|
|
22340
|
+
/** A start is loading older history before it can cut; `seek` = a replay was running
|
|
22341
|
+
* (and left silently to deepen), so abandoning the load must announce its end. */
|
|
22342
|
+
this.replayLoading = null;
|
|
22343
|
+
/** A replay carried over a timeframe/session switch, between the old series' silent end
|
|
22344
|
+
* and the cut of the new one: still ACTIVE for the outside (`replayState`), resumed
|
|
22345
|
+
* at `revealedEnd` once the new bars land, unless stopped (`cancelled`) meanwhile. */
|
|
22346
|
+
this.replayCarrying = null;
|
|
22347
|
+
/** Intrabar prices for the bars replay reveals — null ≡ whole bars. */
|
|
22348
|
+
this.replayTickSource = null;
|
|
22349
|
+
/** The bar being revealed tick by tick: its stored form (what it settles on), its ticks,
|
|
22350
|
+
* the next tick to apply, the candle built so far, the volume each tick adds when the
|
|
22351
|
+
* ticks carry none, and its pacing (ticks per update, ms between updates — one play
|
|
22352
|
+
* interval per tick — and the clock time tick 0 is due at, so late timers catch up). */
|
|
22353
|
+
this.replayForming = null;
|
|
22354
|
+
/** Tick requests by bar time, shared by prefetch and playback; `ticks` once they landed. */
|
|
22355
|
+
this.replayTickFetches = /* @__PURE__ */ new Map();
|
|
22356
|
+
/** Aborts the tick requests in flight — renewed whenever the replay moves on. */
|
|
22357
|
+
this.replayTickAbort = null;
|
|
22384
22358
|
/** What the last applied bar did: refine the open candle, or open a new one (which
|
|
22385
22359
|
* settles the previous). Read by the `script:run` cause attribution. */
|
|
22386
22360
|
this.barCause = "tick";
|
|
@@ -22604,6 +22578,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22604
22578
|
const paint = (bars, final) => {
|
|
22605
22579
|
if (this.generation !== gen || !final && bars.length === 0) return;
|
|
22606
22580
|
if (!painted && !final && bars.length < Math.min(requested, FIRST_PAINT_BARS)) return;
|
|
22581
|
+
if (painted && this.replayQueue) {
|
|
22582
|
+
this.mergeHistoryIntoReplay(bars);
|
|
22583
|
+
return;
|
|
22584
|
+
}
|
|
22607
22585
|
this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
|
|
22608
22586
|
if (!painted && bars.length > 0) {
|
|
22609
22587
|
painted = true;
|
|
@@ -22746,6 +22724,36 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22746
22724
|
// exactly like a timeframe change; the cache keys the sessions apart.
|
|
22747
22725
|
next.session !== void 0 && next.session !== m.session || next.data !== void 0;
|
|
22748
22726
|
const depthChanged = next.bars !== void 0 && next.bars !== m.bars;
|
|
22727
|
+
const sameClock = identityChanged && next.data === void 0 && (next.symbol === void 0 || next.symbol === m.symbol) && (next.timeframe !== void 0 || next.session !== void 0);
|
|
22728
|
+
const pending = identityChanged ? this.replayCarrying : null;
|
|
22729
|
+
if (pending) {
|
|
22730
|
+
this.replayCarrying = null;
|
|
22731
|
+
if (!sameClock) {
|
|
22732
|
+
pending.cancelled = true;
|
|
22733
|
+
this.events.emit("replay:end", { reason: "market" });
|
|
22734
|
+
}
|
|
22735
|
+
}
|
|
22736
|
+
const replayCarry = this.replayQueue && sameClock && this.rawBars.length > 0 ? { revealedEnd: this.rawBars[this.rawBars.length - 1].time + this.barIntervalMs(), playing: this.replayPlaying, cancelled: false } : pending && sameClock ? { revealedEnd: pending.revealedEnd, playing: pending.playing, cancelled: false } : null;
|
|
22737
|
+
if (pending) pending.cancelled = true;
|
|
22738
|
+
if (replayCarry) this.replayCarrying = replayCarry;
|
|
22739
|
+
if (replayCarry && this.replayQueue) {
|
|
22740
|
+
this.endReplay(null, { restore: false, resume: false });
|
|
22741
|
+
const depth = this.replayDepth;
|
|
22742
|
+
this.replayDepth = null;
|
|
22743
|
+
if (depth && next.bars === void 0) {
|
|
22744
|
+
if (depth.bars === void 0) delete m.bars;
|
|
22745
|
+
else m.bars = depth.bars;
|
|
22746
|
+
}
|
|
22747
|
+
}
|
|
22748
|
+
if (this.replayQueue && (identityChanged || depthChanged)) this.endReplay("market", { restore: !identityChanged, resume: false });
|
|
22749
|
+
if (this.replayLoading && (identityChanged || depthChanged)) {
|
|
22750
|
+
const depth = this.replayDepth;
|
|
22751
|
+
this.abandonReplayLoad("market");
|
|
22752
|
+
if (depth && next.bars === void 0) {
|
|
22753
|
+
if (depth.bars === void 0) delete m.bars;
|
|
22754
|
+
else m.bars = depth.bars;
|
|
22755
|
+
}
|
|
22756
|
+
}
|
|
22749
22757
|
const depthOnly = depthChanged && !identityChanged && this.rawBars.length > 0 && !m.data?.length && ((next.bars ?? 0) <= this.rawBars.length || typeof this.feed.loadRange === "function");
|
|
22750
22758
|
if (!identityChanged && !depthChanged) {
|
|
22751
22759
|
if (typeof next.visibleRange === "string") this.setVisibleRangePreset(next.visibleRange);
|
|
@@ -22803,15 +22811,20 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22803
22811
|
const from = bars[Math.max(0, idx - (carriedCount - 1))].time;
|
|
22804
22812
|
this.renderer.setVisibleRange({ from, to: Math.max(carried.right, bars[idx].time + 1) });
|
|
22805
22813
|
}
|
|
22806
|
-
|
|
22814
|
+
const carry = replayCarry && !replayCarry.cancelled ? replayCarry : null;
|
|
22815
|
+
const carryFrom = carry ? carry.revealedEnd - this.barIntervalMs() : 0;
|
|
22816
|
+
const carryNow = carry != null && this.rawBars.length > 0 && carryFrom >= this.rawBars[0].time;
|
|
22817
|
+
const replayBack = carryNow && this.resumeCarriedReplay(this.replayStart({ from: carryFrom }), carry);
|
|
22818
|
+
if (!depthOnly && !replayBack) {
|
|
22807
22819
|
this.restartNativeIndicators();
|
|
22808
22820
|
this.restartChartTypeEngine();
|
|
22809
22821
|
this.reexecuteIndicators();
|
|
22810
22822
|
}
|
|
22811
|
-
this.startLive();
|
|
22823
|
+
if (!replayBack) this.startLive();
|
|
22812
22824
|
if (identityChanged) {
|
|
22813
22825
|
this.events.emit("market:changed", { symbol: m.symbol ?? "TEST", timeframe: m.timeframe ?? "60", prev: prev2 });
|
|
22814
22826
|
}
|
|
22827
|
+
if (carry && !carryNow) this.resumeCarriedReplay(this.replayStart({ from: carryFrom }), carry);
|
|
22815
22828
|
}
|
|
22816
22829
|
/**
|
|
22817
22830
|
* Restart every native indicator over the new market: a `NativeIndicatorContext`
|
|
@@ -22935,6 +22948,397 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22935
22948
|
historyComplete() {
|
|
22936
22949
|
return this.historyCompletePromise;
|
|
22937
22950
|
}
|
|
22951
|
+
// ── bar replay ──────────────────────────────────────────────
|
|
22952
|
+
/**
|
|
22953
|
+
* Enter replay — or seek, when already replaying — keeping every bar opened at or before
|
|
22954
|
+
* `opts.from` and queueing the rest. A `from` inside the loaded bars cuts at once, even
|
|
22955
|
+
* while older history is still streaming in: what lands later joins the tape's head
|
|
22956
|
+
* (see {@link mergeHistoryIntoReplay}) and leaves the cut alone. An older `from` waits
|
|
22957
|
+
* for that load, then deepens if it still has to. Consumers restart over the cut exactly
|
|
22958
|
+
* as on a market switch — their models describe bars that are now hidden — and the
|
|
22959
|
+
* active chart-type engine is rebuilt without live data.
|
|
22960
|
+
*/
|
|
22961
|
+
async replayStart(opts) {
|
|
22962
|
+
if (this.rawBars.length === 0) {
|
|
22963
|
+
console.warn("[vela] chart.replay.start() ignored \u2014 the chart has no bars yet");
|
|
22964
|
+
return;
|
|
22965
|
+
}
|
|
22966
|
+
let gen = this.generation;
|
|
22967
|
+
if (opts.from < this.rawBars[0].time) {
|
|
22968
|
+
await this.historyCompletePromise;
|
|
22969
|
+
if (this.generation !== gen || this.rawBars.length === 0) return;
|
|
22970
|
+
}
|
|
22971
|
+
const oldest = this.rawBars[0].time;
|
|
22972
|
+
if (opts.from < oldest && this.canHeal() && typeof this.feed.loadRange === "function") {
|
|
22973
|
+
const held = this.rawBars.length + (this.replayQueue?.length ?? 0);
|
|
22974
|
+
const bars = held + Math.ceil((oldest - opts.from) / this.barIntervalMs()) + 1;
|
|
22975
|
+
const seek = this.replayQueue !== null;
|
|
22976
|
+
if (seek) this.endReplay(null, { restore: true, resume: false });
|
|
22977
|
+
this.replayDepth ?? (this.replayDepth = { bars: this.config.market.bars });
|
|
22978
|
+
const deepened = this.setMarket({ bars });
|
|
22979
|
+
this.replayLoading = { seek };
|
|
22980
|
+
await deepened;
|
|
22981
|
+
gen = this.generation;
|
|
22982
|
+
await this.historyCompletePromise;
|
|
22983
|
+
if (!this.replayLoading || this.generation !== gen || this.rawBars.length === 0) return;
|
|
22984
|
+
this.replayLoading = null;
|
|
22985
|
+
}
|
|
22986
|
+
this.settleFormingBar();
|
|
22987
|
+
this.dropTickFetches();
|
|
22988
|
+
const tape = this.replayQueue ? [...this.rawBars, ...this.replayQueue] : this.rawBars;
|
|
22989
|
+
let cut = 0;
|
|
22990
|
+
while (cut < tape.length && tape[cut].time <= opts.from) cut += 1;
|
|
22991
|
+
cut = Math.max(1, cut);
|
|
22992
|
+
if (cut >= tape.length) {
|
|
22993
|
+
console.warn("[vela] chart.replay.start() ignored \u2014 no bar opens after the requested time");
|
|
22994
|
+
return;
|
|
22995
|
+
}
|
|
22996
|
+
const wasPlaying = this.replayPlaying;
|
|
22997
|
+
this.clearReplayTimer();
|
|
22998
|
+
if (!this.replayQueue) this.stopLive();
|
|
22999
|
+
this.replayQueue = tape.slice(cut);
|
|
23000
|
+
this.setBarSeries(tape.slice(0, cut));
|
|
23001
|
+
this.restartBarConsumers({ blank: true });
|
|
23002
|
+
if (wasPlaying) this.events.emit("replay:pause", void 0);
|
|
23003
|
+
this.events.emit("replay:start", { cursorTime: tape[cut - 1].time, remaining: this.replayQueue.length });
|
|
23004
|
+
this.prefetchTicks();
|
|
23005
|
+
}
|
|
23006
|
+
/** Reveal the next queued bar through the live-bar path — or complete the bar forming
|
|
23007
|
+
* tick by tick; the last one ends the replay. */
|
|
23008
|
+
replayStep() {
|
|
23009
|
+
if (this.replayForming) {
|
|
23010
|
+
this.finishFormingBar();
|
|
23011
|
+
return true;
|
|
23012
|
+
}
|
|
23013
|
+
const queue = this.replayQueue;
|
|
23014
|
+
const bar = queue?.shift();
|
|
23015
|
+
if (!queue || !bar) return false;
|
|
23016
|
+
this.replayTickFetches.delete(bar.time);
|
|
23017
|
+
this.applyBar(bar);
|
|
23018
|
+
this.completeReveal(bar);
|
|
23019
|
+
this.prefetchTicks();
|
|
23020
|
+
return true;
|
|
23021
|
+
}
|
|
23022
|
+
/**
|
|
23023
|
+
* Follow up a replay carried onto a new series of the same symbol (another timeframe or
|
|
23024
|
+
* session). The cut asked for keeps the bars that CLOSED by the end of what was revealed —
|
|
23025
|
+
* every finer bar of the last revealed one, never a coarser bar still open at that point
|
|
23026
|
+
* (no look-ahead). Once it settles: playback resumes if it was on; if the replay did not
|
|
23027
|
+
* come back (nothing left after that point, or a later switch took over), it ends as a
|
|
23028
|
+
* market switch would. Returns whether the replay is back already — a start with no
|
|
23029
|
+
* history to wait for cuts synchronously (an async function runs up to its first await).
|
|
23030
|
+
*/
|
|
23031
|
+
resumeCarriedReplay(start, carry) {
|
|
23032
|
+
if (this.replayQueue && this.replayCarrying === carry) this.replayCarrying = null;
|
|
23033
|
+
void start.then(() => {
|
|
23034
|
+
if (this.replayCarrying === carry) this.replayCarrying = null;
|
|
23035
|
+
if (carry.cancelled) return;
|
|
23036
|
+
if (!this.replayQueue) this.events.emit("replay:end", { reason: "market" });
|
|
23037
|
+
else if (carry.playing) this.replayPlay();
|
|
23038
|
+
});
|
|
23039
|
+
return this.replayQueue !== null;
|
|
23040
|
+
}
|
|
23041
|
+
replayStepUpdate() {
|
|
23042
|
+
if (!this.replayQueue) return false;
|
|
23043
|
+
if (this.replayForming) {
|
|
23044
|
+
this.applyReplayTicks(1);
|
|
23045
|
+
return true;
|
|
23046
|
+
}
|
|
23047
|
+
const bar = this.replayQueue[0];
|
|
23048
|
+
if (!bar) return false;
|
|
23049
|
+
const entry = this.replayTickSource ? this.requestTicks(bar) : null;
|
|
23050
|
+
if (entry && !entry.ticks) {
|
|
23051
|
+
const epoch = this.replayTickAbort;
|
|
23052
|
+
void entry.done.then(() => {
|
|
23053
|
+
if (!this.replayPlaying && !this.replayForming && this.replayTickAbort === epoch && this.replayQueue?.[0] === bar) this.replayStepUpdate();
|
|
23054
|
+
});
|
|
23055
|
+
return true;
|
|
23056
|
+
}
|
|
23057
|
+
if (!entry?.ticks?.length) return this.replayStep();
|
|
23058
|
+
this.openFormingBar(entry.ticks, 1);
|
|
23059
|
+
return true;
|
|
23060
|
+
}
|
|
23061
|
+
replaySetTicks(source) {
|
|
23062
|
+
this.finishFormingBar();
|
|
23063
|
+
this.dropTickFetches();
|
|
23064
|
+
this.replayTickSource = source;
|
|
23065
|
+
this.prefetchTicks();
|
|
23066
|
+
}
|
|
23067
|
+
replayPlay(intervalMs2) {
|
|
23068
|
+
if (!this.replayQueue) return;
|
|
23069
|
+
if (intervalMs2 !== void 0) {
|
|
23070
|
+
if (!(Number.isFinite(intervalMs2) && intervalMs2 > 0)) {
|
|
23071
|
+
console.warn(`[vela] chart.replay.play(${intervalMs2}) ignored \u2014 the interval must be a positive number of ms`);
|
|
23072
|
+
return;
|
|
23073
|
+
}
|
|
23074
|
+
this.replayIntervalMs = intervalMs2;
|
|
23075
|
+
}
|
|
23076
|
+
this.clearReplayTimer();
|
|
23077
|
+
this.replayPlaying = true;
|
|
23078
|
+
if (this.replayForming) this.paceFormingBar(this.replayForming);
|
|
23079
|
+
this.scheduleReplayStep(this.replayForming?.delayMs);
|
|
23080
|
+
this.events.emit("replay:play", { intervalMs: this.replayIntervalMs });
|
|
23081
|
+
}
|
|
23082
|
+
replayPause() {
|
|
23083
|
+
if (!this.replayPlaying) return;
|
|
23084
|
+
this.clearReplayTimer();
|
|
23085
|
+
this.events.emit("replay:pause", void 0);
|
|
23086
|
+
}
|
|
23087
|
+
replayStop() {
|
|
23088
|
+
const carrying = this.replayCarrying;
|
|
23089
|
+
if (carrying) {
|
|
23090
|
+
carrying.cancelled = true;
|
|
23091
|
+
this.replayCarrying = null;
|
|
23092
|
+
this.events.emit("replay:end", { reason: "stopped" });
|
|
23093
|
+
}
|
|
23094
|
+
if (this.replayLoading) {
|
|
23095
|
+
const depth = this.replayDepth;
|
|
23096
|
+
this.abandonReplayLoad("stopped");
|
|
23097
|
+
void this.setMarket({ bars: depth?.bars ?? 500 });
|
|
23098
|
+
return;
|
|
23099
|
+
}
|
|
23100
|
+
this.endReplay("stopped", { restore: true, resume: true });
|
|
23101
|
+
}
|
|
23102
|
+
abandonReplayLoad(reason) {
|
|
23103
|
+
const loading = this.replayLoading;
|
|
23104
|
+
if (!loading) return;
|
|
23105
|
+
this.replayLoading = null;
|
|
23106
|
+
this.replayDepth = null;
|
|
23107
|
+
if (loading.seek) this.events.emit("replay:end", { reason });
|
|
23108
|
+
}
|
|
23109
|
+
replayState() {
|
|
23110
|
+
const active = this.replayQueue !== null;
|
|
23111
|
+
return {
|
|
23112
|
+
active: active || this.replayCarrying !== null,
|
|
23113
|
+
// a replay carried over a switch stays on while the new bars load
|
|
23114
|
+
playing: this.replayCarrying?.playing ?? this.replayPlaying,
|
|
23115
|
+
cursorTime: active ? this.rawBars[this.rawBars.length - 1]?.time ?? null : null,
|
|
23116
|
+
remaining: this.replayQueue?.length ?? 0,
|
|
23117
|
+
nextTime: this.replayForming?.bar.time ?? this.replayQueue?.[0]?.time ?? null,
|
|
23118
|
+
intervalMs: this.replayIntervalMs
|
|
23119
|
+
};
|
|
23120
|
+
}
|
|
23121
|
+
replayBounds() {
|
|
23122
|
+
const first = this.rawBars[0];
|
|
23123
|
+
const last = this.replayQueue?.[this.replayQueue.length - 1] ?? this.rawBars[this.rawBars.length - 1];
|
|
23124
|
+
return first && last ? { first: first.time, last: last.time } : null;
|
|
23125
|
+
}
|
|
23126
|
+
scheduleReplayStep(delayMs = this.replayIntervalMs) {
|
|
23127
|
+
this.replayTimer = setTimeout(() => {
|
|
23128
|
+
this.replayTimer = null;
|
|
23129
|
+
this.replayAdvance();
|
|
23130
|
+
}, delayMs);
|
|
23131
|
+
}
|
|
23132
|
+
/** One beat of timed playback: the forming bar's next ticks, else the next bar — whole,
|
|
23133
|
+
* or opened from its ticks (waiting for them when they have not landed yet). */
|
|
23134
|
+
replayAdvance() {
|
|
23135
|
+
if (!this.replayPlaying || !this.replayQueue) return;
|
|
23136
|
+
const forming = this.replayForming;
|
|
23137
|
+
if (forming) {
|
|
23138
|
+
const due = Math.floor((now() - forming.origin) / this.replayIntervalMs) + 1;
|
|
23139
|
+
this.applyReplayTicks(Math.max(forming.batch, due - forming.next));
|
|
23140
|
+
if (this.replayPlaying) this.scheduleReplayStep(forming.delayMs);
|
|
23141
|
+
return;
|
|
23142
|
+
}
|
|
23143
|
+
const bar = this.replayQueue[0];
|
|
23144
|
+
if (!bar) return;
|
|
23145
|
+
const entry = this.replayTickSource ? this.requestTicks(bar) : null;
|
|
23146
|
+
if (entry && !entry.ticks) {
|
|
23147
|
+
const epoch = this.replayTickAbort;
|
|
23148
|
+
void entry.done.then(() => {
|
|
23149
|
+
if (this.replayPlaying && this.replayTimer == null && this.replayTickAbort === epoch && this.replayQueue?.[0] === bar) this.replayAdvance();
|
|
23150
|
+
});
|
|
23151
|
+
return;
|
|
23152
|
+
}
|
|
23153
|
+
if (!entry?.ticks?.length) {
|
|
23154
|
+
if (this.replayStep() && this.replayPlaying) this.scheduleReplayStep();
|
|
23155
|
+
return;
|
|
23156
|
+
}
|
|
23157
|
+
this.openFormingBar(entry.ticks);
|
|
23158
|
+
if (this.replayPlaying) this.scheduleReplayStep(this.replayForming?.delayMs);
|
|
23159
|
+
}
|
|
23160
|
+
/** Take the next queued bar off the tape and open it at its first tick (the first
|
|
23161
|
+
* `count` ticks — default: one timed update's worth). */
|
|
23162
|
+
openFormingBar(ticks, count) {
|
|
23163
|
+
const bar = this.replayQueue.shift();
|
|
23164
|
+
const volumeEach = ticks.every((t) => t.volume === void 0) ? (bar.volume ?? 0) / ticks.length : null;
|
|
23165
|
+
const forming = { bar, ticks, next: 0, candle: null, volumeEach, batch: 1, delayMs: this.replayIntervalMs, origin: 0 };
|
|
23166
|
+
this.paceFormingBar(forming);
|
|
23167
|
+
this.replayForming = forming;
|
|
23168
|
+
this.applyReplayTicks(count ?? forming.batch);
|
|
23169
|
+
this.prefetchTicks();
|
|
23170
|
+
}
|
|
23171
|
+
/** One tick per interval, batched when the interval is shorter than a frame; the clock
|
|
23172
|
+
* is anchored on the ticks already applied (a resume or a pace change carries on from
|
|
23173
|
+
* there, never jumps). */
|
|
23174
|
+
paceFormingBar(f) {
|
|
23175
|
+
f.batch = Math.max(1, Math.ceil(MIN_TICK_UPDATE_MS / this.replayIntervalMs));
|
|
23176
|
+
f.delayMs = this.replayIntervalMs * f.batch;
|
|
23177
|
+
f.origin = now() - Math.max(0, f.next - 1) * this.replayIntervalMs;
|
|
23178
|
+
}
|
|
23179
|
+
/** Fold the next `count` ticks into the forming candle; the last one settles the bar. */
|
|
23180
|
+
applyReplayTicks(count) {
|
|
23181
|
+
const f = this.replayForming;
|
|
23182
|
+
if (!f) return;
|
|
23183
|
+
let c = f.candle;
|
|
23184
|
+
const stop = Math.min(f.ticks.length, f.next + count);
|
|
23185
|
+
for (; f.next < stop; f.next += 1) {
|
|
23186
|
+
const t = f.ticks[f.next];
|
|
23187
|
+
const v = f.volumeEach ?? t.volume ?? 0;
|
|
23188
|
+
const hi = Math.max(t.high ?? t.price, t.price);
|
|
23189
|
+
const lo = Math.min(t.low ?? t.price, t.price);
|
|
23190
|
+
if (c) {
|
|
23191
|
+
c = { ...c, high: Math.max(c.high, hi), low: Math.min(c.low, lo), close: t.price, volume: (c.volume ?? 0) + v };
|
|
23192
|
+
} else {
|
|
23193
|
+
const open = t.open ?? t.price;
|
|
23194
|
+
c = { time: f.bar.time, open, high: Math.max(open, hi), low: Math.min(open, lo), close: t.price, volume: v };
|
|
23195
|
+
}
|
|
23196
|
+
}
|
|
23197
|
+
f.candle = c;
|
|
23198
|
+
if (f.next >= f.ticks.length) {
|
|
23199
|
+
this.finishFormingBar();
|
|
23200
|
+
return;
|
|
23201
|
+
}
|
|
23202
|
+
this.applyBar(c);
|
|
23203
|
+
this.events.emit("replay:tick", { cursorTime: f.bar.time, index: f.next - 1, count: f.ticks.length });
|
|
23204
|
+
}
|
|
23205
|
+
/** Settle the forming bar on its stored values and announce it revealed. */
|
|
23206
|
+
finishFormingBar() {
|
|
23207
|
+
const f = this.replayForming;
|
|
23208
|
+
if (!f) return;
|
|
23209
|
+
this.replayForming = null;
|
|
23210
|
+
this.replayTickFetches.delete(f.bar.time);
|
|
23211
|
+
this.applyBar(f.bar);
|
|
23212
|
+
this.events.emit("replay:tick", { cursorTime: f.bar.time, index: f.ticks.length - 1, count: f.ticks.length });
|
|
23213
|
+
this.completeReveal(f.bar);
|
|
23214
|
+
}
|
|
23215
|
+
/** Put the forming bar back in its stored form, silently — a seek or an end rebuilds
|
|
23216
|
+
* from the tape, which must hold the stored bar. */
|
|
23217
|
+
settleFormingBar() {
|
|
23218
|
+
const f = this.replayForming;
|
|
23219
|
+
if (!f) return;
|
|
23220
|
+
this.replayForming = null;
|
|
23221
|
+
this.applyBar(f.bar);
|
|
23222
|
+
}
|
|
23223
|
+
/**
|
|
23224
|
+
* A history answer landing mid-replay (a progressive snapshot — the whole answer so far):
|
|
23225
|
+
* the tape keeps its cut and its hidden queue; bars older than the tape join its head,
|
|
23226
|
+
* bars newer than it join the queue's end.
|
|
23227
|
+
*/
|
|
23228
|
+
mergeHistoryIntoReplay(bars) {
|
|
23229
|
+
const queue = this.replayQueue;
|
|
23230
|
+
const head = this.rawBars[0];
|
|
23231
|
+
if (!queue || !head) return;
|
|
23232
|
+
const tail = queue[queue.length - 1] ?? this.rawBars[this.rawBars.length - 1];
|
|
23233
|
+
let older = 0;
|
|
23234
|
+
while (older < bars.length && bars[older].time < head.time) older += 1;
|
|
23235
|
+
let newer = bars.length;
|
|
23236
|
+
while (newer > older && bars[newer - 1].time > tail.time) newer -= 1;
|
|
23237
|
+
if (newer < bars.length) queue.push(...bars.slice(newer));
|
|
23238
|
+
if (older > 0) {
|
|
23239
|
+
this.setBarSeries([...bars.slice(0, older), ...this.rawBars], { preserveView: true });
|
|
23240
|
+
this.notifySessionsBars("backfill");
|
|
23241
|
+
}
|
|
23242
|
+
}
|
|
23243
|
+
/** A bar is fully on screen: announce it, and end the replay if it was the last. */
|
|
23244
|
+
completeReveal(bar) {
|
|
23245
|
+
const queue = this.replayQueue;
|
|
23246
|
+
if (!queue) return;
|
|
23247
|
+
this.events.emit("replay:step", { cursorTime: bar.time, remaining: queue.length });
|
|
23248
|
+
if (queue.length === 0) this.endReplay("finished", { restore: true, resume: true });
|
|
23249
|
+
}
|
|
23250
|
+
/** Ask for the ticks of the next hidden bar ahead of time. */
|
|
23251
|
+
prefetchTicks() {
|
|
23252
|
+
const next = this.replayQueue?.[0];
|
|
23253
|
+
if (next && this.replayTickSource) this.requestTicks(next);
|
|
23254
|
+
}
|
|
23255
|
+
/** The (shared) tick request of a queued bar — the source runs once per bar. */
|
|
23256
|
+
requestTicks(bar) {
|
|
23257
|
+
const known = this.replayTickFetches.get(bar.time);
|
|
23258
|
+
if (known) return known;
|
|
23259
|
+
const source = this.replayTickSource;
|
|
23260
|
+
this.replayTickAbort ?? (this.replayTickAbort = new AbortController());
|
|
23261
|
+
const signal = this.replayTickAbort.signal;
|
|
23262
|
+
const queue = this.replayQueue ?? [];
|
|
23263
|
+
const end = queue[queue.indexOf(bar) + 1]?.time ?? bar.time + this.barIntervalMs();
|
|
23264
|
+
const entry = { ticks: null, done: Promise.resolve() };
|
|
23265
|
+
const land = (ticks) => {
|
|
23266
|
+
entry.ticks = usableTicks(ticks);
|
|
23267
|
+
};
|
|
23268
|
+
const fail = (e) => {
|
|
23269
|
+
if (!signal.aborted) console.warn(`[vela] replay tick source failed \u2014 revealing the bar whole (${e instanceof Error ? e.message : String(e)})`);
|
|
23270
|
+
land([]);
|
|
23271
|
+
};
|
|
23272
|
+
try {
|
|
23273
|
+
const out = source(bar, { end, signal });
|
|
23274
|
+
if (Array.isArray(out)) land(out);
|
|
23275
|
+
else entry.done = Promise.resolve(out).then(land, fail);
|
|
23276
|
+
} catch (e) {
|
|
23277
|
+
fail(e);
|
|
23278
|
+
}
|
|
23279
|
+
this.replayTickFetches.set(bar.time, entry);
|
|
23280
|
+
return entry;
|
|
23281
|
+
}
|
|
23282
|
+
/** Abort the tick requests in flight and forget the landed ones. */
|
|
23283
|
+
dropTickFetches() {
|
|
23284
|
+
this.replayTickAbort?.abort();
|
|
23285
|
+
this.replayTickAbort = null;
|
|
23286
|
+
this.replayTickFetches.clear();
|
|
23287
|
+
}
|
|
23288
|
+
clearReplayTimer() {
|
|
23289
|
+
if (this.replayTimer != null) clearTimeout(this.replayTimer);
|
|
23290
|
+
this.replayTimer = null;
|
|
23291
|
+
this.replayPlaying = false;
|
|
23292
|
+
}
|
|
23293
|
+
/**
|
|
23294
|
+
* Leave replay. `restore` puts the queued bars back and restarts the consumers over the
|
|
23295
|
+
* full history (a market switch skips it: its load replaces everything anyway); `resume`
|
|
23296
|
+
* restarts live updates and heals the bars that closed while replaying. A null reason
|
|
23297
|
+
* leaves silently — a seek that re-enters right after.
|
|
23298
|
+
*/
|
|
23299
|
+
endReplay(reason, opts) {
|
|
23300
|
+
const queue = this.replayQueue;
|
|
23301
|
+
if (!queue) return;
|
|
23302
|
+
this.clearReplayTimer();
|
|
23303
|
+
if (opts.restore) this.settleFormingBar();
|
|
23304
|
+
else this.replayForming = null;
|
|
23305
|
+
this.dropTickFetches();
|
|
23306
|
+
this.replayQueue = null;
|
|
23307
|
+
const depth = reason ? this.replayDepth : null;
|
|
23308
|
+
if (reason) this.replayDepth = null;
|
|
23309
|
+
if (depth) {
|
|
23310
|
+
if (depth.bars === void 0) delete this.config.market.bars;
|
|
23311
|
+
else this.config.market.bars = depth.bars;
|
|
23312
|
+
}
|
|
23313
|
+
if (opts.restore) {
|
|
23314
|
+
const keep = depth ? depth.bars ?? 500 : Infinity;
|
|
23315
|
+
if (queue.length > 0 || this.rawBars.length > keep) {
|
|
23316
|
+
const full = [...this.rawBars, ...queue];
|
|
23317
|
+
this.setBarSeries(full.length > keep ? full.slice(full.length - keep) : full);
|
|
23318
|
+
this.restartBarConsumers({ blank: false });
|
|
23319
|
+
} else if (this.config.live) {
|
|
23320
|
+
this.restartNativeIndicators();
|
|
23321
|
+
this.restartChartTypeEngine();
|
|
23322
|
+
}
|
|
23323
|
+
}
|
|
23324
|
+
if (opts.resume && this.config.live) {
|
|
23325
|
+
this.startLive();
|
|
23326
|
+
const last = this.rawBars[this.rawBars.length - 1];
|
|
23327
|
+
if (last && this.canHeal()) void this.healGap(last.time);
|
|
23328
|
+
}
|
|
23329
|
+
if (reason) this.events.emit("replay:end", { reason });
|
|
23330
|
+
}
|
|
23331
|
+
/**
|
|
23332
|
+
* The bars were replaced by a cut or a restore of the SAME market: every consumer re-runs.
|
|
23333
|
+
* A cut also blanks the mounted models first — they still paint the hidden bars' values,
|
|
23334
|
+
* which must not show until the re-runs land.
|
|
23335
|
+
*/
|
|
23336
|
+
restartBarConsumers(opts) {
|
|
23337
|
+
if (opts.blank) this.blankIndicatorVisuals();
|
|
23338
|
+
this.restartNativeIndicators();
|
|
23339
|
+
this.restartChartTypeEngine();
|
|
23340
|
+
this.reexecuteIndicators();
|
|
23341
|
+
}
|
|
22938
23342
|
/** The primary symbol as the data layer resolves it, or null if no symbol is set. */
|
|
22939
23343
|
qualifiedSymbol() {
|
|
22940
23344
|
const market = this.config.market;
|
|
@@ -23006,7 +23410,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23006
23410
|
engine.start({
|
|
23007
23411
|
symbol: this.config.market.symbol ?? "TEST",
|
|
23008
23412
|
timeframe: this.config.market.timeframe ?? "60",
|
|
23009
|
-
live: this.config.live ?? false,
|
|
23413
|
+
live: (this.config.live ?? false) && this.replayQueue === null,
|
|
23010
23414
|
session: this.config.market.session,
|
|
23011
23415
|
bars: () => this.bars,
|
|
23012
23416
|
data: this.dataControl,
|
|
@@ -23132,6 +23536,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23132
23536
|
record.session.notifyBars(reason);
|
|
23133
23537
|
} else if (record.native) record.native.instance.onBars();
|
|
23134
23538
|
}
|
|
23539
|
+
if (this.activeEngineStyle) this.typeEngines.get(this.activeEngineStyle)?.onBars?.();
|
|
23135
23540
|
}
|
|
23136
23541
|
/** Gap healing needs a ranged feed and a provider-backed series (offline `data` has no source). */
|
|
23137
23542
|
canHeal() {
|
|
@@ -23154,10 +23559,11 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23154
23559
|
try {
|
|
23155
23560
|
const bars = await this.feed.loadRange(this.config.market, { from: fromMs });
|
|
23156
23561
|
if (this.generation !== gen) return;
|
|
23562
|
+
if (this.replayQueue) return;
|
|
23157
23563
|
for (const b of bars) this.applyBar(b, false);
|
|
23158
23564
|
} catch {
|
|
23159
23565
|
} finally {
|
|
23160
|
-
if (this.generation === gen) {
|
|
23566
|
+
if (this.generation === gen && !this.replayQueue) {
|
|
23161
23567
|
this.healing = false;
|
|
23162
23568
|
const pending = this.healBuffer;
|
|
23163
23569
|
this.healBuffer = [];
|
|
@@ -23462,6 +23868,13 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23462
23868
|
destroy() {
|
|
23463
23869
|
this.bumpGeneration();
|
|
23464
23870
|
this.resolveHistoryComplete();
|
|
23871
|
+
this.clearReplayTimer();
|
|
23872
|
+
this.replayQueue = null;
|
|
23873
|
+
this.replayForming = null;
|
|
23874
|
+
this.replayCarrying = null;
|
|
23875
|
+
this.dropTickFetches();
|
|
23876
|
+
this.replayDepth = null;
|
|
23877
|
+
this.replayLoading = null;
|
|
23465
23878
|
this.stopLive();
|
|
23466
23879
|
if (this.viewportTimer != null) clearTimeout(this.viewportTimer);
|
|
23467
23880
|
this.viewportUnsub?.();
|
|
@@ -23560,6 +23973,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23560
23973
|
this.emitScriptRun(id, cause, first);
|
|
23561
23974
|
},
|
|
23562
23975
|
onAlert: (a) => {
|
|
23976
|
+
if (this.replayQueue || this.replayCarrying) return;
|
|
23563
23977
|
const indicator = record.options?.title ?? record.prepared?.meta.title ?? record.title;
|
|
23564
23978
|
this.events.emit("alert", { ...a, indicator });
|
|
23565
23979
|
handle.emit("alert", { id: a.id, message: a.message, title: a.title, time: a.time });
|
|
@@ -23586,7 +24000,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23586
24000
|
id,
|
|
23587
24001
|
symbol: this.config.market.symbol ?? "TEST",
|
|
23588
24002
|
timeframe: this.config.market.timeframe ?? "60",
|
|
23589
|
-
live: this.config.live,
|
|
24003
|
+
live: this.config.live && this.replayQueue === null,
|
|
23590
24004
|
session: this.config.market.session,
|
|
23591
24005
|
bars: () => this.bars,
|
|
23592
24006
|
data: this.dataControl,
|
|
@@ -23989,17 +24403,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23989
24403
|
}
|
|
23990
24404
|
emitContextChanged(id) {
|
|
23991
24405
|
if (!this.registry.get(id)?.session?.getContext) return;
|
|
23992
|
-
const
|
|
23993
|
-
if (
|
|
23994
|
-
this.contextEmitAt.set(id,
|
|
24406
|
+
const now2 = Date.now();
|
|
24407
|
+
if (now2 - (this.contextEmitAt.get(id) ?? 0) < 1e3) return;
|
|
24408
|
+
this.contextEmitAt.set(id, now2);
|
|
23995
24409
|
this.events.emit("context:changed", { id });
|
|
23996
24410
|
}
|
|
23997
24411
|
emitScriptRun(id, cause, first) {
|
|
23998
24412
|
const record = this.registry.get(id);
|
|
23999
24413
|
if (!record || record.native || !this.events.hasListeners("script:run")) return;
|
|
24000
|
-
const
|
|
24001
|
-
if (cause === "tick" &&
|
|
24002
|
-
this.runEmitAt.set(id,
|
|
24414
|
+
const now2 = Date.now();
|
|
24415
|
+
if (cause === "tick" && now2 - (this.runEmitAt.get(id) ?? 0) < RUN_EMIT_THROTTLE_MS) return;
|
|
24416
|
+
this.runEmitAt.set(id, now2);
|
|
24003
24417
|
void Promise.resolve().then(() => this.buildScriptRun(record, cause, first)).then((run) => {
|
|
24004
24418
|
if (run && this.registry.get(id)) this.events.emit("script:run", run);
|
|
24005
24419
|
});
|
|
@@ -24052,6 +24466,16 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
24052
24466
|
handle?.emit("error", { error });
|
|
24053
24467
|
}
|
|
24054
24468
|
};
|
|
24469
|
+
function now() {
|
|
24470
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
24471
|
+
}
|
|
24472
|
+
function usableTicks(value) {
|
|
24473
|
+
if (!Array.isArray(value)) return [];
|
|
24474
|
+
const optional = (n) => n === void 0 || Number.isFinite(n);
|
|
24475
|
+
return value.filter(
|
|
24476
|
+
(t) => t != null && Number.isFinite(t.price) && optional(t.volume) && optional(t.high) && optional(t.low) && optional(t.open)
|
|
24477
|
+
);
|
|
24478
|
+
}
|
|
24055
24479
|
function yieldToPaint() {
|
|
24056
24480
|
if (typeof requestAnimationFrame === "function") {
|
|
24057
24481
|
return new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
|
|
@@ -28127,6 +28551,7 @@ var Vela = class {
|
|
|
28127
28551
|
this.panesControl = new PanesControl(this.orchestrator);
|
|
28128
28552
|
this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
|
|
28129
28553
|
this.marksControl = new MarksControl(this.orchestrator.marks);
|
|
28554
|
+
this.replayControl = new ReplayControl(this.orchestrator);
|
|
28130
28555
|
}
|
|
28131
28556
|
/**
|
|
28132
28557
|
* Register a scripting engine so `addIndicator({ language })` can run that
|
|
@@ -28366,6 +28791,16 @@ var Vela = class {
|
|
|
28366
28791
|
get marks() {
|
|
28367
28792
|
return this.marksControl;
|
|
28368
28793
|
}
|
|
28794
|
+
/**
|
|
28795
|
+
* The chart's bar-replay control surface: rewind to a past bar and reveal the
|
|
28796
|
+
* following ones by hand or on a timer, from the history already loaded —
|
|
28797
|
+
* `await chart.replay.start({ from })`, `chart.replay.play(2000)`, `chart.replay.step()`,
|
|
28798
|
+
* `chart.replay.stop()`. Revealed bars behave like live bars for indicators; live
|
|
28799
|
+
* updates pause meanwhile. Follow it with the `replay:*` events.
|
|
28800
|
+
*/
|
|
28801
|
+
get replay() {
|
|
28802
|
+
return this.replayControl;
|
|
28803
|
+
}
|
|
28369
28804
|
on(event, handler) {
|
|
28370
28805
|
return this.orchestrator.events.on(event, handler);
|
|
28371
28806
|
}
|
|
@@ -28424,4 +28859,4 @@ function resolveElement(container) {
|
|
|
28424
28859
|
return element;
|
|
28425
28860
|
}
|
|
28426
28861
|
|
|
28427
|
-
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MarksControl, MultiProviderFeed, NativeRenderer, RendererControl, SecondClock, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isExchangeTimezone, isGroupRow, normalizeSession, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, resolveTimezone, sharedBarStore, timeframeToMs, timezoneMenuRows, tzButtonLabel, tzMenuLabel, tzOffset };
|
|
28862
|
+
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MarksControl, MultiProviderFeed, NativeRenderer, RendererControl, ReplayControl, SecondClock, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isExchangeTimezone, isGroupRow, normalizeSession, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, resolveTimezone, sharedBarStore, timeframeToMs, timezoneMenuRows, tzButtonLabel, tzMenuLabel, tzOffset };
|