@luxalgo/vela 0.6.8 → 0.6.10
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-BSLlBpB9.d.ts → DataProvider-CWmp31dA.d.ts} +49 -1
- package/dist/{DataProvider-BsQM2WNH.d.cts → DataProvider-l_eLMly_.d.cts} +49 -1
- package/dist/{chunk-L3I2CCYO.js → chunk-4KZVZ7QQ.js} +49 -2
- package/dist/{chunk-SQETIBFU.js → chunk-FFOP37FQ.js} +996 -377
- package/dist/{chunk-2R7BANEM.js → chunk-G77Y7LK2.js} +2 -2
- package/dist/{chunk-PN5KWFZ4.js → chunk-KG4YT3TI.js} +5 -3
- package/dist/{chunk-OICPLNU7.js → chunk-MHY7MVXH.js} +192 -2
- package/dist/{chunk-N7LGMKCE.js → chunk-NMTQXNT4.js} +637 -164
- package/dist/{contributions-DLLdV9jD.d.ts → contributions-BCz6Dr6a.d.ts} +99 -7
- package/dist/{contributions-Vw-Hm58R.d.cts → contributions-D9vTzm5p.d.cts} +99 -7
- package/dist/index.cjs +1210 -383
- package/dist/index.d.cts +37 -11
- package/dist/index.d.ts +37 -11
- package/dist/index.js +4 -4
- package/dist/{options-BaVTMXaO.d.ts → options-DSqHsQyN.d.cts} +84 -7
- package/dist/{options-BaVTMXaO.d.cts → options-DSqHsQyN.d.ts} +84 -7
- package/dist/{plugin-BnJgjLAy.d.cts → plugin-Bt8hLR8Z.d.cts} +3 -3
- package/dist/{plugin-CC7rBrOY.d.ts → plugin-CH7pfMrE.d.ts} +3 -3
- package/dist/plugin.cjs +21 -3
- 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-CGkB2EOo.d.ts → statusline-5K7y4Ya2.d.ts} +5 -3
- package/dist/{statusline-DqzBqy42.d.cts → statusline-TYLQmoeh.d.cts} +5 -3
- package/dist/ui.cjs +208 -5
- package/dist/ui.d.cts +91 -2
- package/dist/ui.d.ts +91 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +1210 -383
- package/dist/vela.global.min.js +99 -48
- package/dist/widget.cjs +1771 -449
- package/dist/widget.d.cts +17 -6
- package/dist/widget.d.ts +17 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1771 -449
- package/dist/workspace.d.cts +100 -6
- package/dist/workspace.d.ts +100 -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-4KZVZ7QQ.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-MHY7MVXH.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-KG4YT3TI.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
|
|
@@ -687,6 +696,23 @@ var BarStore = class {
|
|
|
687
696
|
};
|
|
688
697
|
var sharedBarStore = new BarStore();
|
|
689
698
|
|
|
699
|
+
// src/data/symbol-groups.ts
|
|
700
|
+
function isGroupRow(d) {
|
|
701
|
+
return d.group != null && d.ticker === d.group;
|
|
702
|
+
}
|
|
703
|
+
function groupKeyOf(d) {
|
|
704
|
+
return `${(d.prefix ?? d.provider ?? "").toLowerCase()}:${(d.group ?? "").toUpperCase()}`;
|
|
705
|
+
}
|
|
706
|
+
function groupMembers(pool, groupRow) {
|
|
707
|
+
const key = groupKeyOf(groupRow);
|
|
708
|
+
return pool.filter((s) => s.group != null && !isGroupRow(s) && groupKeyOf(s) === key);
|
|
709
|
+
}
|
|
710
|
+
function defaultMemberOf(pool, groupRow) {
|
|
711
|
+
const members = groupMembers(pool, groupRow);
|
|
712
|
+
const defaults = members.filter((m) => m.default);
|
|
713
|
+
return (defaults.length === 1 ? defaults[0] : members[0]) ?? null;
|
|
714
|
+
}
|
|
715
|
+
|
|
690
716
|
// src/data/ProviderRegistry.ts
|
|
691
717
|
var normName = (n) => n.trim().toLowerCase();
|
|
692
718
|
var normTicker = (t) => t.trim().toUpperCase();
|
|
@@ -776,17 +802,24 @@ var ProviderRegistry = class {
|
|
|
776
802
|
resolve(raw, opts = {}) {
|
|
777
803
|
const { provider, ticker } = this.parse(raw);
|
|
778
804
|
if (provider) {
|
|
779
|
-
if (this.entries.has(provider))
|
|
805
|
+
if (this.entries.has(provider)) {
|
|
806
|
+
const d = this.entries.get(provider).byTicker?.get(normTicker(ticker));
|
|
807
|
+
return { provider, ticker: d != null && isGroupRow(d) ? this.loadableTicker(provider, d) : ticker };
|
|
808
|
+
}
|
|
780
809
|
const key = `${provider}:${normTicker(ticker)}`;
|
|
781
810
|
for (const name of this.candidateOrder(opts.default)) {
|
|
782
811
|
const d = this.entries.get(name).prefixIndex?.get(key);
|
|
783
|
-
if (d) return { provider: name, ticker:
|
|
812
|
+
if (d) return { provider: name, ticker: this.loadableTicker(name, d) };
|
|
784
813
|
}
|
|
785
814
|
return null;
|
|
786
815
|
}
|
|
787
816
|
const norm = normTicker(ticker);
|
|
788
817
|
for (const name of this.candidateOrder(opts.default)) {
|
|
789
|
-
|
|
818
|
+
const e = this.entries.get(name);
|
|
819
|
+
if (e.index?.has(norm)) {
|
|
820
|
+
const d = e.byTicker?.get(norm);
|
|
821
|
+
return { provider: name, ticker: d != null && isGroupRow(d) ? this.loadableTicker(name, d) : ticker };
|
|
822
|
+
}
|
|
790
823
|
}
|
|
791
824
|
if (opts.lenient && this.entries.size === 1) {
|
|
792
825
|
const only = this.names()[0];
|
|
@@ -795,6 +828,13 @@ var ProviderRegistry = class {
|
|
|
795
828
|
}
|
|
796
829
|
return null;
|
|
797
830
|
}
|
|
831
|
+
/** What a resolved descriptor LOADS: itself — unless it is a GROUP row (listed, never
|
|
832
|
+
* served), which loads its default member (single `default`, else first listed). A
|
|
833
|
+
* memberless group keeps its own ticker and fails downstream like any unknown symbol. */
|
|
834
|
+
loadableTicker(name, d) {
|
|
835
|
+
if (!isGroupRow(d)) return d.ticker;
|
|
836
|
+
return defaultMemberOf(this.entries.get(name)?.descriptors ?? [], d)?.ticker ?? d.ticker;
|
|
837
|
+
}
|
|
798
838
|
/**
|
|
799
839
|
* Resolve now if possible, else resolve later — re-attempting after each provider
|
|
800
840
|
* settles. The promise stays pending until some registered provider can serve the
|
|
@@ -938,6 +978,28 @@ var CachingDataFeed = class {
|
|
|
938
978
|
this.store.merge(key, dropForming(bars));
|
|
939
979
|
return bars;
|
|
940
980
|
}
|
|
981
|
+
/**
|
|
982
|
+
* Progressive twin of {@link load}: a COLD load streams through the inner feed's
|
|
983
|
+
* progressive path — batches forwarded verbatim, the FINAL answer cached exactly as
|
|
984
|
+
* `load` caches — while a cache-covered load answers once through `load` itself (a
|
|
985
|
+
* warm chart has nothing to stream). Falls back to `load` wholesale when the inner
|
|
986
|
+
* feed lacks the capability, so callers may prefer this method unconditionally.
|
|
987
|
+
*/
|
|
988
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
989
|
+
if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
|
|
990
|
+
if (!this.inner.loadProgressive) return null;
|
|
991
|
+
const symbol = cfg.symbol ?? "TEST";
|
|
992
|
+
const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
|
|
993
|
+
const cached = this.store.get(key);
|
|
994
|
+
const n = cfg.bars ?? 500;
|
|
995
|
+
const lastCached = cached?.[cached.length - 1];
|
|
996
|
+
if (cached && lastCached && cached.length >= n - 1 && this.inner.loadRange) return this.load(cfg);
|
|
997
|
+
this.store.retainSymbol(symbol);
|
|
998
|
+
const bars = await this.inner.loadProgressive(cfg, onBatch, opts);
|
|
999
|
+
if (bars == null) return null;
|
|
1000
|
+
this.store.merge(key, dropForming(bars));
|
|
1001
|
+
return bars;
|
|
1002
|
+
}
|
|
941
1003
|
/**
|
|
942
1004
|
* Cache-backed ranged fetch — the gateway engines use for secondary series
|
|
943
1005
|
* (`request.security` HTF/LTF/cross-symbol) and the orchestrator's backward
|
|
@@ -1183,6 +1245,17 @@ var MultiProviderFeed = class {
|
|
|
1183
1245
|
this.prefetchSymbolInfo(resolved);
|
|
1184
1246
|
return this.cache.load(canonical(cfg, resolved));
|
|
1185
1247
|
}
|
|
1248
|
+
/** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
|
|
1249
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
1250
|
+
if (cfg.data && cfg.data.length > 0) {
|
|
1251
|
+
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
1252
|
+
return cfg.data;
|
|
1253
|
+
}
|
|
1254
|
+
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
1255
|
+
this.primaryProvider = resolved.provider;
|
|
1256
|
+
this.prefetchSymbolInfo(resolved);
|
|
1257
|
+
return this.cache.loadProgressive(canonical(cfg, resolved), onBatch, opts);
|
|
1258
|
+
}
|
|
1186
1259
|
/**
|
|
1187
1260
|
* Synchronous per-symbol metadata for engines (Pine `syminfo.*`), served from the cache
|
|
1188
1261
|
* warmed by load(). Undefined until the prefetch lands (the engine then synthesizes a
|
|
@@ -1270,6 +1343,18 @@ var RegistryFetchFeed = class {
|
|
|
1270
1343
|
if (!provider) return Promise.resolve([]);
|
|
1271
1344
|
return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
|
|
1272
1345
|
}
|
|
1346
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
1347
|
+
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
1348
|
+
const provider = this.registry.get(name ?? "");
|
|
1349
|
+
if (!provider) return [];
|
|
1350
|
+
if (!provider.getBarsProgressive) return null;
|
|
1351
|
+
try {
|
|
1352
|
+
return await provider.getBarsProgressive(ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session }, onBatch, opts);
|
|
1353
|
+
} catch (e) {
|
|
1354
|
+
console.warn(`[vela] progressive fetch failed for ${ticker} ${cfg.timeframe ?? "60"} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
1355
|
+
return [];
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1273
1358
|
loadRange(cfg, range) {
|
|
1274
1359
|
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
1275
1360
|
const provider = this.registry.get(name ?? "");
|
|
@@ -2417,7 +2502,7 @@ var DrawingToolbar = class {
|
|
|
2417
2502
|
this.root.replaceChildren();
|
|
2418
2503
|
this.groupCells.clear();
|
|
2419
2504
|
this.groupIcons.clear();
|
|
2420
|
-
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.
|
|
2505
|
+
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onCursorClick());
|
|
2421
2506
|
this.root.appendChild(this.cursorBtn);
|
|
2422
2507
|
if (this.def.groups.length > 0) this.root.appendChild(this.divider());
|
|
2423
2508
|
for (const g of this.def.groups) {
|
|
@@ -2521,6 +2606,14 @@ var DrawingToolbar = class {
|
|
|
2521
2606
|
this.magnetIcon = icon2;
|
|
2522
2607
|
return cell;
|
|
2523
2608
|
}
|
|
2609
|
+
/** Cursor returns to select/idle: an active measure/eraser mode exits through its own
|
|
2610
|
+
* toggle callback (disarming a tool via `onArm(null)` alone can't — the host treats a
|
|
2611
|
+
* null arm as a no-op side effect of entering those modes), then the tool disarms. */
|
|
2612
|
+
onCursorClick() {
|
|
2613
|
+
if (this.measureActive) this.onMeasure();
|
|
2614
|
+
if (this.eraserActive) this.onEraser();
|
|
2615
|
+
this.onArm(null);
|
|
2616
|
+
}
|
|
2524
2617
|
/** Clicking the icon arms the group's last-used tool (it does NOT open the flyout). */
|
|
2525
2618
|
onGroupIconClick(group) {
|
|
2526
2619
|
const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
|
|
@@ -2898,6 +2991,10 @@ function isLineLikeSeries(spec) {
|
|
|
2898
2991
|
}
|
|
2899
2992
|
|
|
2900
2993
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
2994
|
+
var PROPS_TAB = "Properties";
|
|
2995
|
+
function bagOf(row, decl) {
|
|
2996
|
+
return decl.prop ? row.propValues : row.values;
|
|
2997
|
+
}
|
|
2901
2998
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
2902
2999
|
var IndicatorInputsDialog = class {
|
|
2903
3000
|
constructor(host) {
|
|
@@ -2908,6 +3005,7 @@ var IndicatorInputsDialog = class {
|
|
|
2908
3005
|
this.openId = null;
|
|
2909
3006
|
this.row = null;
|
|
2910
3007
|
this.snapshot = null;
|
|
3008
|
+
this.propSnapshot = null;
|
|
2911
3009
|
this.dialogTips = [];
|
|
2912
3010
|
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
2913
3011
|
this.refreshVisibility = null;
|
|
@@ -2930,10 +3028,11 @@ var IndicatorInputsDialog = class {
|
|
|
2930
3028
|
}
|
|
2931
3029
|
open(row) {
|
|
2932
3030
|
this.close();
|
|
2933
|
-
if (row.inputs.length === 0) return;
|
|
3031
|
+
if (row.inputs.length === 0 && row.props.length === 0) return;
|
|
2934
3032
|
this.row = row;
|
|
2935
3033
|
this.openId = row.id;
|
|
2936
3034
|
this.snapshot = { ...row.values };
|
|
3035
|
+
this.propSnapshot = { ...row.propValues };
|
|
2937
3036
|
ensureDialogStyles();
|
|
2938
3037
|
const t = this.host.theme();
|
|
2939
3038
|
const border = "var(--vela-border)";
|
|
@@ -2941,7 +3040,7 @@ var IndicatorInputsDialog = class {
|
|
|
2941
3040
|
const host = this.host.dialogHost() ?? this.host.container;
|
|
2942
3041
|
const ui = new Dialog({
|
|
2943
3042
|
host,
|
|
2944
|
-
title: row.
|
|
3043
|
+
title: row.title,
|
|
2945
3044
|
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
2946
3045
|
// whole body, killing the chart and the body-portaled popovers.
|
|
2947
3046
|
modal: false,
|
|
@@ -2973,6 +3072,16 @@ var IndicatorInputsDialog = class {
|
|
|
2973
3072
|
text: () => "Close"
|
|
2974
3073
|
}));
|
|
2975
3074
|
const tabDefs = tabInputs(row.inputs);
|
|
3075
|
+
if (row.props.length > 0) {
|
|
3076
|
+
const propDecls = row.props.map((p) => {
|
|
3077
|
+
const d = { ...p, prop: true };
|
|
3078
|
+
delete d.tab;
|
|
3079
|
+
return d;
|
|
3080
|
+
});
|
|
3081
|
+
const existing = tabDefs.find((t2) => t2.name === PROPS_TAB);
|
|
3082
|
+
if (existing) existing.inputs.push(...propDecls);
|
|
3083
|
+
else tabDefs.push({ name: PROPS_TAB, inputs: propDecls });
|
|
3084
|
+
}
|
|
2976
3085
|
const tabs = document.createElement("div");
|
|
2977
3086
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
2978
3087
|
const tabEls = [];
|
|
@@ -3075,9 +3184,10 @@ var IndicatorInputsDialog = class {
|
|
|
3075
3184
|
this.openId = null;
|
|
3076
3185
|
this.row = null;
|
|
3077
3186
|
this.snapshot = null;
|
|
3187
|
+
this.propSnapshot = null;
|
|
3078
3188
|
ui?.destroy();
|
|
3079
3189
|
}
|
|
3080
|
-
/** Restore every input to its open-time value (re-running the indicator), then close. */
|
|
3190
|
+
/** Restore every input and prop to its open-time value (re-running the indicator), then close. */
|
|
3081
3191
|
revertAndClose() {
|
|
3082
3192
|
const row = this.row;
|
|
3083
3193
|
const snap = this.snapshot;
|
|
@@ -3091,6 +3201,17 @@ var IndicatorInputsDialog = class {
|
|
|
3091
3201
|
}
|
|
3092
3202
|
}
|
|
3093
3203
|
}
|
|
3204
|
+
const propSnap = this.propSnapshot;
|
|
3205
|
+
if (row && propSnap) {
|
|
3206
|
+
for (const p of row.props) {
|
|
3207
|
+
const snapped = propSnap[p.key];
|
|
3208
|
+
const before = snapped !== void 0 ? snapped : p.defval;
|
|
3209
|
+
if (row.propValues[p.key] !== before) {
|
|
3210
|
+
row.propValues[p.key] = before;
|
|
3211
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: before, kind: "prop" });
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3094
3215
|
this.close();
|
|
3095
3216
|
}
|
|
3096
3217
|
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
@@ -3108,19 +3229,27 @@ var IndicatorInputsDialog = class {
|
|
|
3108
3229
|
const row = this.row;
|
|
3109
3230
|
if (!row) return;
|
|
3110
3231
|
const snap = this.snapshot;
|
|
3232
|
+
const propSnap = this.propSnapshot;
|
|
3111
3233
|
for (const inp of row.inputs) {
|
|
3112
3234
|
if (row.values[inp.key] !== inp.defval) {
|
|
3113
3235
|
row.values[inp.key] = inp.defval;
|
|
3114
3236
|
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
3115
3237
|
}
|
|
3116
3238
|
}
|
|
3239
|
+
for (const p of row.props) {
|
|
3240
|
+
if (row.propValues[p.key] !== p.defval) {
|
|
3241
|
+
row.propValues[p.key] = p.defval;
|
|
3242
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: p.defval, kind: "prop" });
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3117
3245
|
this.open(row);
|
|
3118
3246
|
if (snap) this.snapshot = snap;
|
|
3247
|
+
if (propSnap) this.propSnapshot = propSnap;
|
|
3119
3248
|
}
|
|
3120
3249
|
/** 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 });
|
|
3250
|
+
commit(row, decl, value) {
|
|
3251
|
+
bagOf(row, decl)[decl.key] = value;
|
|
3252
|
+
this.host.onChange?.({ indicatorId: row.id, key: decl.key, value, ...decl.prop ? { kind: "prop" } : {} });
|
|
3124
3253
|
this.refreshVisibility?.();
|
|
3125
3254
|
}
|
|
3126
3255
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
@@ -3147,7 +3276,7 @@ var IndicatorInputsDialog = class {
|
|
|
3147
3276
|
const id = idOf(lead);
|
|
3148
3277
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
3149
3278
|
if (lead.type === "bool") {
|
|
3150
|
-
const current = Boolean(row
|
|
3279
|
+
const current = Boolean(bagOf(row, lead)[lead.key] ?? lead.defval);
|
|
3151
3280
|
return append(fieldRow({
|
|
3152
3281
|
label: nameOf(lead),
|
|
3153
3282
|
id,
|
|
@@ -3156,7 +3285,7 @@ var IndicatorInputsDialog = class {
|
|
|
3156
3285
|
toggle: {
|
|
3157
3286
|
id,
|
|
3158
3287
|
checked: current,
|
|
3159
|
-
onChange: (v) => this.commit(row, lead
|
|
3288
|
+
onChange: (v) => this.commit(row, lead, v)
|
|
3160
3289
|
}
|
|
3161
3290
|
}));
|
|
3162
3291
|
}
|
|
@@ -3181,8 +3310,8 @@ var IndicatorInputsDialog = class {
|
|
|
3181
3310
|
}
|
|
3182
3311
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
3183
3312
|
buildControl(row, inp, id) {
|
|
3184
|
-
const current = row
|
|
3185
|
-
const emit = (value) => this.commit(row, inp
|
|
3313
|
+
const current = bagOf(row, inp)[inp.key] ?? inp.defval;
|
|
3314
|
+
const emit = (value) => this.commit(row, inp, value);
|
|
3186
3315
|
if (inp.type === "bool") {
|
|
3187
3316
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
3188
3317
|
}
|
|
@@ -3193,7 +3322,7 @@ var IndicatorInputsDialog = class {
|
|
|
3193
3322
|
kind: "color",
|
|
3194
3323
|
id,
|
|
3195
3324
|
theme: this.host.theme(),
|
|
3196
|
-
get: () => String(row
|
|
3325
|
+
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
3197
3326
|
onChange: (v) => emit(v)
|
|
3198
3327
|
}).el;
|
|
3199
3328
|
}
|
|
@@ -3756,6 +3885,7 @@ function timeParts(ts) {
|
|
|
3756
3885
|
}
|
|
3757
3886
|
|
|
3758
3887
|
// src/renderers/shared/InputsUI.ts
|
|
3888
|
+
var LEGEND_AT_TOP_ATTR = "data-vela-pane-at-top";
|
|
3759
3889
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
3760
3890
|
var LEGEND_ICON_PX2 = 16;
|
|
3761
3891
|
var LEGEND_CTL_PX = 18;
|
|
@@ -3773,6 +3903,9 @@ var CLOSE_SVG = iconAt("close", LEGEND_ICON_PX2);
|
|
|
3773
3903
|
var FOLD_SVG = iconAt("chevron-up", LEGEND_ICON_PX2);
|
|
3774
3904
|
var UNFOLD_SVG = iconAt("chevron-down", LEGEND_ICON_PX2);
|
|
3775
3905
|
var OVERVIEW_SVG = iconAt("objects", LEGEND_ICON_PX2);
|
|
3906
|
+
function legendCalloutsDisplay(open, hasCallouts) {
|
|
3907
|
+
return !open && hasCallouts ? "inline-flex" : "none";
|
|
3908
|
+
}
|
|
3776
3909
|
var InputsUI = class {
|
|
3777
3910
|
constructor(container, theme, paneBoundsOf) {
|
|
3778
3911
|
this.container = container;
|
|
@@ -3794,10 +3927,14 @@ var InputsUI = class {
|
|
|
3794
3927
|
this.moveApi = null;
|
|
3795
3928
|
/** Host-contributed legend actions, resolved PER ROW at render time (see setLegendActions). */
|
|
3796
3929
|
this.legendActions = null;
|
|
3930
|
+
/** Host-contributed callout bubbles, resolved PER ROW at render time (see setLegendCallouts). */
|
|
3931
|
+
this.legendCallouts = null;
|
|
3797
3932
|
/** Chrome-tooltip disposers, per row id — a tip open at removal must not outlive its row. */
|
|
3798
3933
|
this.rowTips = /* @__PURE__ */ new Map();
|
|
3799
3934
|
/** Same, for the contributed extras only (rebuilt independently by setLegendActions). */
|
|
3800
3935
|
this.extrasTips = /* @__PURE__ */ new Map();
|
|
3936
|
+
/** Same, for the contributed callouts only (rebuilt independently by setLegendCallouts). */
|
|
3937
|
+
this.calloutTips = /* @__PURE__ */ new Map();
|
|
3801
3938
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
3802
3939
|
this.moveMenu = null;
|
|
3803
3940
|
this.moveTargets = [];
|
|
@@ -3955,6 +4092,42 @@ var InputsUI = class {
|
|
|
3955
4092
|
extrasEl.appendChild(btn2);
|
|
3956
4093
|
}
|
|
3957
4094
|
}
|
|
4095
|
+
/**
|
|
4096
|
+
* Wire the host-contributed callout bubbles. Same contract as
|
|
4097
|
+
* {@link setLegendActions}: re-calling replaces the provider and re-projects the
|
|
4098
|
+
* rows already on screen.
|
|
4099
|
+
*/
|
|
4100
|
+
setLegendCallouts(provider) {
|
|
4101
|
+
this.legendCallouts = provider;
|
|
4102
|
+
for (const row of this.rows.values()) this.renderCallouts(row);
|
|
4103
|
+
}
|
|
4104
|
+
/** (Re)build one row's callout bubbles — tinted icon circles beside the title whose
|
|
4105
|
+
* click (when the view carries content) deploys a panel of text and actions. */
|
|
4106
|
+
renderCallouts(row) {
|
|
4107
|
+
this.disposeTips(this.calloutTips, row.id);
|
|
4108
|
+
for (const bubble of row.callouts) bubble.destroy();
|
|
4109
|
+
row.callouts = [];
|
|
4110
|
+
row.calloutsEl.replaceChildren();
|
|
4111
|
+
const views = this.legendCallouts?.(row.id) ?? [];
|
|
4112
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(row.highlighted, views.length > 0);
|
|
4113
|
+
for (const view of views) {
|
|
4114
|
+
const bubble = new CalloutBubble({
|
|
4115
|
+
icon: view.icon,
|
|
4116
|
+
background: view.background,
|
|
4117
|
+
...view.color !== void 0 ? { color: view.color } : {},
|
|
4118
|
+
label: view.tooltip,
|
|
4119
|
+
...view.content !== void 0 ? { panel: view.content } : {},
|
|
4120
|
+
// The panel portals into the plot host and self-tokens: it must work on
|
|
4121
|
+
// a bare chart, where no `.vela-ui` token ancestor exists.
|
|
4122
|
+
host: this.container,
|
|
4123
|
+
theme: () => this.theme
|
|
4124
|
+
});
|
|
4125
|
+
bubble.el.dataset.legendCallout = view.id;
|
|
4126
|
+
this.tip(this.calloutTips, row.id, bubble.el, view.tooltip);
|
|
4127
|
+
row.callouts.push(bubble);
|
|
4128
|
+
row.calloutsEl.appendChild(bubble.el);
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
3958
4131
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
3959
4132
|
reposition() {
|
|
3960
4133
|
for (const [paneId, lg] of this.legends) this.positionLegend(lg, paneId);
|
|
@@ -4122,6 +4295,7 @@ var InputsUI = class {
|
|
|
4122
4295
|
}
|
|
4123
4296
|
positionLegend(lg, paneId) {
|
|
4124
4297
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
4298
|
+
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
4125
4299
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
4126
4300
|
const collapsed = this.paneCollapse.has(paneId);
|
|
4127
4301
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
@@ -4197,13 +4371,13 @@ var InputsUI = class {
|
|
|
4197
4371
|
}
|
|
4198
4372
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
4199
4373
|
upsert(id, title, inputs, values, paneId = "price", opts = {}) {
|
|
4200
|
-
const settingsTitle = opts.settingsTitle ?? title;
|
|
4201
4374
|
const existing = this.rows.get(id);
|
|
4202
4375
|
if (existing) {
|
|
4203
4376
|
existing.title = title;
|
|
4204
|
-
existing.settingsTitle = settingsTitle;
|
|
4205
4377
|
existing.inputs = inputs;
|
|
4206
4378
|
existing.values = { ...values };
|
|
4379
|
+
existing.props = opts.props ?? [];
|
|
4380
|
+
existing.propValues = { ...opts.propValues ?? {} };
|
|
4207
4381
|
existing.titleEl.textContent = title;
|
|
4208
4382
|
if (existing.paneId !== paneId) {
|
|
4209
4383
|
existing.paneId = paneId;
|
|
@@ -4255,6 +4429,9 @@ var InputsUI = class {
|
|
|
4255
4429
|
titleWrap.appendChild(beta);
|
|
4256
4430
|
}
|
|
4257
4431
|
el.appendChild(titleWrap);
|
|
4432
|
+
const calloutsEl = document.createElement("span");
|
|
4433
|
+
calloutsEl.style.cssText = `display:none;align-items:center;gap:4px;flex:none;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;`;
|
|
4434
|
+
el.appendChild(calloutsEl);
|
|
4258
4435
|
el.appendChild(statusEl);
|
|
4259
4436
|
const valuesEl = document.createElement("span");
|
|
4260
4437
|
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 +4448,13 @@ var InputsUI = class {
|
|
|
4271
4448
|
eye.className = "vela-ind-ctl";
|
|
4272
4449
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
4273
4450
|
eye.addEventListener("click", () => {
|
|
4274
|
-
const
|
|
4275
|
-
this.onToggleVisible?.(id, Boolean(
|
|
4451
|
+
const row2 = this.rows.get(id);
|
|
4452
|
+
this.onToggleVisible?.(id, Boolean(row2?.hidden));
|
|
4276
4453
|
});
|
|
4277
4454
|
controlsEl.appendChild(eye);
|
|
4278
4455
|
eyeEl = eye;
|
|
4279
4456
|
}
|
|
4280
|
-
if (inputs.length > 0) {
|
|
4457
|
+
if (inputs.length > 0 || (opts.props ?? []).length > 0) {
|
|
4281
4458
|
const gear = document.createElement("button");
|
|
4282
4459
|
gear.type = "button";
|
|
4283
4460
|
gear.setAttribute("aria-label", "Settings");
|
|
@@ -4317,7 +4494,9 @@ var InputsUI = class {
|
|
|
4317
4494
|
controlsEl.appendChild(close);
|
|
4318
4495
|
el.appendChild(controlsEl);
|
|
4319
4496
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
4320
|
-
|
|
4497
|
+
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 };
|
|
4498
|
+
this.rows.set(id, row);
|
|
4499
|
+
this.renderCallouts(row);
|
|
4321
4500
|
this.syncFoldToggle();
|
|
4322
4501
|
}
|
|
4323
4502
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -4326,9 +4505,11 @@ var InputsUI = class {
|
|
|
4326
4505
|
else container.appendChild(el);
|
|
4327
4506
|
}
|
|
4328
4507
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
4329
|
-
setValues(id, values) {
|
|
4508
|
+
setValues(id, values, props) {
|
|
4330
4509
|
const row = this.rows.get(id);
|
|
4331
|
-
if (row)
|
|
4510
|
+
if (!row) return;
|
|
4511
|
+
row.values = { ...row.values, ...values };
|
|
4512
|
+
if (props) row.propValues = { ...row.propValues, ...props };
|
|
4332
4513
|
}
|
|
4333
4514
|
/**
|
|
4334
4515
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -4410,8 +4591,13 @@ var InputsUI = class {
|
|
|
4410
4591
|
syncRowActions(row) {
|
|
4411
4592
|
const open = row.highlighted;
|
|
4412
4593
|
row.controlsEl.style.display = open || row.hidden ? "inline-flex" : "none";
|
|
4413
|
-
if (open)
|
|
4414
|
-
|
|
4594
|
+
if (open) {
|
|
4595
|
+
row.el.appendChild(row.statusEl);
|
|
4596
|
+
for (const bubble of row.callouts) bubble.hidePanel();
|
|
4597
|
+
} else {
|
|
4598
|
+
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
4599
|
+
}
|
|
4600
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(open, row.callouts.length > 0);
|
|
4415
4601
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
4416
4602
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
4417
4603
|
if (child === row.extrasEl) {
|
|
@@ -4429,10 +4615,12 @@ var InputsUI = class {
|
|
|
4429
4615
|
}
|
|
4430
4616
|
remove(id) {
|
|
4431
4617
|
const row = this.rows.get(id);
|
|
4618
|
+
for (const bubble of row?.callouts ?? []) bubble.destroy();
|
|
4432
4619
|
row?.el.remove();
|
|
4433
4620
|
this.rows.delete(id);
|
|
4434
4621
|
this.disposeTips(this.rowTips, id);
|
|
4435
4622
|
this.disposeTips(this.extrasTips, id);
|
|
4623
|
+
this.disposeTips(this.calloutTips, id);
|
|
4436
4624
|
if (this.selectedId === id) this.selectedId = null;
|
|
4437
4625
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
4438
4626
|
this.syncFoldToggle();
|
|
@@ -4452,6 +4640,8 @@ var InputsUI = class {
|
|
|
4452
4640
|
this.rowMenu = null;
|
|
4453
4641
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
4454
4642
|
for (const id of [...this.extrasTips.keys()]) this.disposeTips(this.extrasTips, id);
|
|
4643
|
+
for (const id of [...this.calloutTips.keys()]) this.disposeTips(this.calloutTips, id);
|
|
4644
|
+
for (const row of this.rows.values()) for (const bubble of row.callouts) bubble.destroy();
|
|
4455
4645
|
for (const lg of this.legends.values()) lg.remove();
|
|
4456
4646
|
this.legends.clear();
|
|
4457
4647
|
this.rows.clear();
|
|
@@ -4497,7 +4687,7 @@ var ICONS = {
|
|
|
4497
4687
|
};
|
|
4498
4688
|
var STYLE_ID2 = "vela-pane-controls";
|
|
4499
4689
|
var ICON_PX = 12;
|
|
4500
|
-
var CLUSTER_PILL = "rgba(0,0,0,0.
|
|
4690
|
+
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
4501
4691
|
function ensureStyles2() {
|
|
4502
4692
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID2)) return;
|
|
4503
4693
|
const st = document.createElement("style");
|
|
@@ -4517,8 +4707,12 @@ var PaneControls = class {
|
|
|
4517
4707
|
this.deps = deps;
|
|
4518
4708
|
this.clusters = /* @__PURE__ */ new Map();
|
|
4519
4709
|
this.hoverPaneId = null;
|
|
4710
|
+
/** Mobile: hover clusters are meaningless without a cursor — suppressed; a
|
|
4711
|
+
* collapsed pane's standalone expand chip stays (the only way back up). */
|
|
4712
|
+
this.suspended = false;
|
|
4520
4713
|
/** Reveal the cluster for the pane under the cursor, resolved from the pointer's y in the plot. */
|
|
4521
4714
|
this.onPlotMove = (e) => {
|
|
4715
|
+
if (this.suspended) return;
|
|
4522
4716
|
const rect = this.plot.getBoundingClientRect();
|
|
4523
4717
|
const y = e.clientY - rect.top;
|
|
4524
4718
|
let hit = null;
|
|
@@ -4590,7 +4784,12 @@ var PaneControls = class {
|
|
|
4590
4784
|
}
|
|
4591
4785
|
if (p.count > 1) {
|
|
4592
4786
|
cluster.appendChild(
|
|
4593
|
-
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
4787
|
+
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
4788
|
+
role: "maximize",
|
|
4789
|
+
// Same inverse-chip treatment as the collapsed pane's expand toggle: the
|
|
4790
|
+
// maximized state must read as an active state, not just a swapped glyph.
|
|
4791
|
+
selected: p.maximized
|
|
4792
|
+
})
|
|
4594
4793
|
);
|
|
4595
4794
|
}
|
|
4596
4795
|
}
|
|
@@ -4627,17 +4826,18 @@ var PaneControls = class {
|
|
|
4627
4826
|
}
|
|
4628
4827
|
const hovered = id === this.hoverPaneId;
|
|
4629
4828
|
const hasButtons = cluster.children.length > 0;
|
|
4630
|
-
const
|
|
4829
|
+
const stateChipRole = p.collapsed ? "collapse" : !this.suspended && p.maximized ? "maximize" : null;
|
|
4830
|
+
const visible = hasButtons && (hovered || stateChipRole != null) && p.height > 8;
|
|
4631
4831
|
cluster.style.right = `${rightPx}px`;
|
|
4632
4832
|
cluster.style.top = p.collapsed ? `${p.top + Math.max(1, Math.round((p.height - 24) / 2))}px` : `${p.top + 4}px`;
|
|
4633
4833
|
cluster.style.display = visible ? "flex" : "none";
|
|
4634
4834
|
if (!visible) continue;
|
|
4635
|
-
const
|
|
4636
|
-
cluster.style.background =
|
|
4835
|
+
const soloChip = stateChipRole != null && !hovered;
|
|
4836
|
+
cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
|
|
4637
4837
|
for (const child of cluster.children) {
|
|
4638
4838
|
const btn2 = child;
|
|
4639
4839
|
btn2.style.display = "inline-flex";
|
|
4640
|
-
btn2.style.visibility =
|
|
4840
|
+
btn2.style.visibility = soloChip && btn2.dataset.role !== stateChipRole ? "hidden" : "visible";
|
|
4641
4841
|
}
|
|
4642
4842
|
}
|
|
4643
4843
|
}
|
|
@@ -4647,6 +4847,14 @@ var PaneControls = class {
|
|
|
4647
4847
|
this.hoverPaneId = paneId;
|
|
4648
4848
|
this.reposition();
|
|
4649
4849
|
}
|
|
4850
|
+
/** Mobile suppression: no hover clusters (touch has no cursor; the shell's own
|
|
4851
|
+
* chrome covers maximize), while collapsed panes keep their expand chips. */
|
|
4852
|
+
setSuspended(on) {
|
|
4853
|
+
if (on === this.suspended) return;
|
|
4854
|
+
this.suspended = on;
|
|
4855
|
+
if (on) this.hoverPaneId = null;
|
|
4856
|
+
this.reposition();
|
|
4857
|
+
}
|
|
4650
4858
|
destroy() {
|
|
4651
4859
|
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
4652
4860
|
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
@@ -4797,131 +5005,6 @@ var AxisScaleButtons = class {
|
|
|
4797
5005
|
}
|
|
4798
5006
|
};
|
|
4799
5007
|
|
|
4800
|
-
// src/renderers/shared/TableOverlay.ts
|
|
4801
|
-
var SIZE_PX = {
|
|
4802
|
-
auto: 13,
|
|
4803
|
-
tiny: 10,
|
|
4804
|
-
small: 11,
|
|
4805
|
-
normal: 13,
|
|
4806
|
-
large: 16,
|
|
4807
|
-
huge: 20
|
|
4808
|
-
};
|
|
4809
|
-
var TableOverlay = class {
|
|
4810
|
-
constructor(container, theme, paneBounds) {
|
|
4811
|
-
this.container = container;
|
|
4812
|
-
this.theme = theme;
|
|
4813
|
-
this.paneBounds = paneBounds;
|
|
4814
|
-
this.lastTables = [];
|
|
4815
|
-
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
4816
|
-
this.root = document.createElement("div");
|
|
4817
|
-
Object.assign(this.root.style, {
|
|
4818
|
-
position: "absolute",
|
|
4819
|
-
inset: "0",
|
|
4820
|
-
pointerEvents: "none",
|
|
4821
|
-
overflow: "hidden",
|
|
4822
|
-
zIndex: "3"
|
|
4823
|
-
});
|
|
4824
|
-
container.appendChild(this.root);
|
|
4825
|
-
}
|
|
4826
|
-
update(tables) {
|
|
4827
|
-
this.lastTables = tables;
|
|
4828
|
-
this.root.replaceChildren();
|
|
4829
|
-
for (const t of tables) this.root.appendChild(this.renderTable(t));
|
|
4830
|
-
}
|
|
4831
|
-
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
4832
|
-
reposition() {
|
|
4833
|
-
if (this.root.isConnected) this.update(this.lastTables);
|
|
4834
|
-
}
|
|
4835
|
-
/** Show/hide the whole overlay. Tables anchor to pane corners, not to bars, so unlike the
|
|
4836
|
-
* series content they DON'T vanish with an emptied chart — the loading state hides them. */
|
|
4837
|
-
setVisible(visible) {
|
|
4838
|
-
this.root.style.display = visible ? "" : "none";
|
|
4839
|
-
}
|
|
4840
|
-
destroy() {
|
|
4841
|
-
this.root.remove();
|
|
4842
|
-
}
|
|
4843
|
-
renderTable(t) {
|
|
4844
|
-
const wrap = document.createElement("div");
|
|
4845
|
-
wrap.style.position = "absolute";
|
|
4846
|
-
this.anchor(wrap, t.position, this.paneBounds(t.paneId));
|
|
4847
|
-
const table = document.createElement("table");
|
|
4848
|
-
Object.assign(table.style, {
|
|
4849
|
-
borderCollapse: "collapse",
|
|
4850
|
-
background: t.bgColor ?? "transparent",
|
|
4851
|
-
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
|
-
border: "none",
|
|
4856
|
-
tableLayout: "auto",
|
|
4857
|
-
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
4858
|
-
pointerEvents: "auto"
|
|
4859
|
-
});
|
|
4860
|
-
const span = /* @__PURE__ */ new Map();
|
|
4861
|
-
const skip = /* @__PURE__ */ new Set();
|
|
4862
|
-
for (const m of t.merges) {
|
|
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
|
-
}
|
|
4870
|
-
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
4871
|
-
for (let r = 0; r < t.rows; r += 1) {
|
|
4872
|
-
const tr = document.createElement("tr");
|
|
4873
|
-
for (let c = 0; c < t.columns; c += 1) {
|
|
4874
|
-
const cell = t.cells[r]?.[c] ?? null;
|
|
4875
|
-
if (skip.has(`${r}:${c}`) || cell?.merged) continue;
|
|
4876
|
-
const td = document.createElement("td");
|
|
4877
|
-
const sp = span.get(`${r}:${c}`);
|
|
4878
|
-
if (sp) {
|
|
4879
|
-
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
4880
|
-
if (sp.rs > 1) td.rowSpan = sp.rs;
|
|
4881
|
-
}
|
|
4882
|
-
Object.assign(td.style, {
|
|
4883
|
-
border: cellBorder,
|
|
4884
|
-
padding: "2px 6px",
|
|
4885
|
-
background: cell?.bgColor ?? "transparent",
|
|
4886
|
-
color: cell?.textColor ?? this.theme.textColor,
|
|
4887
|
-
textAlign: cell?.hAlign ?? "center",
|
|
4888
|
-
verticalAlign: cell?.vAlign === "top" ? "top" : cell?.vAlign === "bottom" ? "bottom" : "middle",
|
|
4889
|
-
fontSize: `${SIZE_PX[cell?.textSize ?? "normal"]}px`,
|
|
4890
|
-
fontFamily: cell?.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
4891
|
-
fontWeight: cell?.bold ? "bold" : "normal",
|
|
4892
|
-
fontStyle: cell?.italic ? "italic" : "normal",
|
|
4893
|
-
whiteSpace: "pre-line"
|
|
4894
|
-
});
|
|
4895
|
-
if (cell?.tooltip) td.title = cell.tooltip;
|
|
4896
|
-
td.textContent = cell?.text ?? "";
|
|
4897
|
-
tr.appendChild(td);
|
|
4898
|
-
}
|
|
4899
|
-
table.appendChild(tr);
|
|
4900
|
-
}
|
|
4901
|
-
wrap.appendChild(table);
|
|
4902
|
-
return wrap;
|
|
4903
|
-
}
|
|
4904
|
-
/**
|
|
4905
|
-
* Position the wrapper at a Pine `position.*` corner/edge of the table's PANE
|
|
4906
|
-
* (not the whole chart), inset past the right price axis so it never overlaps
|
|
4907
|
-
* the Y-axis labels.
|
|
4908
|
-
*/
|
|
4909
|
-
anchor(el, position, b) {
|
|
4910
|
-
const m = 6;
|
|
4911
|
-
const containerH = this.root.clientHeight || this.container.clientHeight;
|
|
4912
|
-
const paneBottom = b.top + b.height;
|
|
4913
|
-
if (position.startsWith("top")) el.style.top = `${b.top + m}px`;
|
|
4914
|
-
else if (position.startsWith("bottom")) el.style.bottom = `${Math.max(0, containerH - paneBottom) + m}px`;
|
|
4915
|
-
else el.style.top = `${b.top + b.height / 2}px`;
|
|
4916
|
-
if (position.endsWith("left")) el.style.left = `${m}px`;
|
|
4917
|
-
else if (position.endsWith("right")) el.style.right = `${b.rightAxis + m}px`;
|
|
4918
|
-
else el.style.left = `calc(50% - ${b.rightAxis / 2}px)`;
|
|
4919
|
-
const tx = position.endsWith("center") ? "-50%" : "0";
|
|
4920
|
-
const ty = position.startsWith("middle") ? "-50%" : "0";
|
|
4921
|
-
if (tx !== "0" || ty !== "0") el.style.transform = `translate(${tx}, ${ty})`;
|
|
4922
|
-
}
|
|
4923
|
-
};
|
|
4924
|
-
|
|
4925
5008
|
// src/renderers/native/capabilities.ts
|
|
4926
5009
|
var NATIVE_CAPABILITIES = {
|
|
4927
5010
|
panes: true,
|
|
@@ -4942,7 +5025,7 @@ var NATIVE_CAPABILITIES = {
|
|
|
4942
5025
|
drawingDepth: true,
|
|
4943
5026
|
// drawings share the series' z space (backend-composited interleave layers)
|
|
4944
5027
|
tables: true,
|
|
4945
|
-
//
|
|
5028
|
+
// canvas-painted into the owning indicator's interleave slice
|
|
4946
5029
|
trades: true,
|
|
4947
5030
|
// strategy order-fill markers (arrows + labels + fill-price ticks)
|
|
4948
5031
|
inputsUI: true
|
|
@@ -4973,6 +5056,9 @@ function candleTier(spacing) {
|
|
|
4973
5056
|
if (spacing < CANDLE_BODY_MIN_SPACING) return "wick";
|
|
4974
5057
|
return "full";
|
|
4975
5058
|
}
|
|
5059
|
+
function snapY(yCss, dpr) {
|
|
5060
|
+
return Math.round(yCss * dpr) / dpr;
|
|
5061
|
+
}
|
|
4976
5062
|
function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
4977
5063
|
const wickDev = Math.max(1, Math.round(wickWidth(spacing) * dpr));
|
|
4978
5064
|
const wickLeftDev = Math.round(xCss * dpr - wickDev / 2);
|
|
@@ -5778,8 +5864,12 @@ var WebGL2Backend = class {
|
|
|
5778
5864
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
5779
5865
|
};
|
|
5780
5866
|
b.alpha = this.modelAlpha;
|
|
5781
|
-
for (const m of models) for (const bgSpan of m.backgrounds) this.emitBackground(b, bgSpan, pane, coords);
|
|
5782
|
-
|
|
5867
|
+
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
5868
|
+
if (isPrice) {
|
|
5869
|
+
for (const m of scene.indicators.values()) {
|
|
5870
|
+
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5783
5873
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
5784
5874
|
let candleDrawn = false;
|
|
5785
5875
|
for (const m of models) {
|
|
@@ -5793,15 +5883,26 @@ var WebGL2Backend = class {
|
|
|
5793
5883
|
b.alpha = this.modelAlpha;
|
|
5794
5884
|
const off = scene.offsetOf(m.id);
|
|
5795
5885
|
const mp = effPane(m);
|
|
5796
|
-
for (const
|
|
5886
|
+
for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, i1, off);
|
|
5887
|
+
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
5797
5888
|
}
|
|
5798
5889
|
if (drawCandles && !candleDrawn) {
|
|
5799
5890
|
drawSlicesUpTo(scene.candleZ);
|
|
5800
5891
|
b.alpha = this.candleStructureAlpha;
|
|
5801
5892
|
this.emitPriceSeries(b, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
5802
5893
|
}
|
|
5803
|
-
drawSlicesUpTo(Infinity);
|
|
5804
5894
|
b.alpha = this.modelAlpha;
|
|
5895
|
+
if (isPrice) {
|
|
5896
|
+
for (const m of scene.indicators.values()) {
|
|
5897
|
+
const off = scene.offsetOf(m.id);
|
|
5898
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, off);
|
|
5899
|
+
}
|
|
5900
|
+
for (const m of scene.indicators.values()) {
|
|
5901
|
+
const off = scene.offsetOf(m.id);
|
|
5902
|
+
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
5903
|
+
}
|
|
5904
|
+
}
|
|
5905
|
+
drawSlicesUpTo(Infinity);
|
|
5805
5906
|
for (const m of models) {
|
|
5806
5907
|
const mp = effPane(m);
|
|
5807
5908
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -5848,7 +5949,7 @@ var WebGL2Backend = class {
|
|
|
5848
5949
|
for (const pane of scene.orderedPanes()) {
|
|
5849
5950
|
const b = this.glowBatch;
|
|
5850
5951
|
b.reset();
|
|
5851
|
-
this.emitGlowSources(b, scene
|
|
5952
|
+
this.emitGlowSources(b, scene, pane, coords, i0, i1);
|
|
5852
5953
|
if (b.vertexCount === 0) continue;
|
|
5853
5954
|
const topH = Math.round(pane.bounds.top * dpr * 0.5);
|
|
5854
5955
|
const botH = Math.round((pane.bounds.top + pane.bounds.height) * dpr * 0.5);
|
|
@@ -5917,15 +6018,23 @@ var WebGL2Backend = class {
|
|
|
5917
6018
|
gl.bindVertexArray(null);
|
|
5918
6019
|
return true;
|
|
5919
6020
|
}
|
|
5920
|
-
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
6021
|
+
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
6022
|
+
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
6023
|
+
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
6024
|
+
const emitOne = (s, off) => {
|
|
6025
|
+
if (!isLineLikeSeries(s) || s.visible === false) return;
|
|
6026
|
+
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
6027
|
+
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
6028
|
+
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
6029
|
+
};
|
|
6030
|
+
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
6031
|
+
const off = scene.offsetOf(m.id);
|
|
6032
|
+
for (const s of m.series) if (s.overlay !== true) emitOne(s, off);
|
|
6033
|
+
}
|
|
6034
|
+
if (pane.kind === "price") {
|
|
6035
|
+
for (const m of scene.indicators.values()) {
|
|
6036
|
+
const off = scene.offsetOf(m.id);
|
|
6037
|
+
for (const s of m.series) if (s.overlay === true) emitOne(s, off);
|
|
5929
6038
|
}
|
|
5930
6039
|
}
|
|
5931
6040
|
}
|
|
@@ -6188,12 +6297,12 @@ var WebGL2Backend = class {
|
|
|
6188
6297
|
if (drawBody) {
|
|
6189
6298
|
const oY = coords.priceToY(bar.open, pane.scale, pane.bounds);
|
|
6190
6299
|
const cY = coords.priceToY(bar.close, pane.scale, pane.bounds);
|
|
6191
|
-
bodyTop = Math.min(oY, cY);
|
|
6192
|
-
bodyH = Math.max(1, Math.
|
|
6300
|
+
bodyTop = snapY(Math.min(oY, cY), coords.dpr);
|
|
6301
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - bodyTop);
|
|
6193
6302
|
}
|
|
6194
6303
|
if (cs.wickVisible) {
|
|
6195
|
-
const hY = coords.priceToY(bar.high, pane.scale, pane.bounds);
|
|
6196
|
-
const lY = coords.priceToY(bar.low, pane.scale, pane.bounds);
|
|
6304
|
+
const hY = snapY(coords.priceToY(bar.high, pane.scale, pane.bounds), coords.dpr);
|
|
6305
|
+
const lY = snapY(coords.priceToY(bar.low, pane.scale, pane.bounds), coords.dpr);
|
|
6197
6306
|
b.alpha = this.candleStructureAlpha;
|
|
6198
6307
|
const wCol = parseColor((isUp ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : bodyColorStr));
|
|
6199
6308
|
if (drawBody) {
|
|
@@ -6208,9 +6317,9 @@ var WebGL2Backend = class {
|
|
|
6208
6317
|
b.alpha = this.candleBodyAlpha;
|
|
6209
6318
|
b.rect(g.bodyX, bodyTop, g.bodyW, bodyH, c);
|
|
6210
6319
|
}
|
|
6211
|
-
if (cs.borderVisible ||
|
|
6320
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
6212
6321
|
b.alpha = this.candleStructureAlpha;
|
|
6213
|
-
const bord = cs.borderVisible
|
|
6322
|
+
const bord = cs.borderVisible ? parseColor((isUp ? cs.borderUpColor : cs.borderDownColor) ?? bodyColorStr) : c;
|
|
6214
6323
|
const bw = Math.max(0, g.bodyW - 1);
|
|
6215
6324
|
const bh = Math.max(0, bodyH - 1);
|
|
6216
6325
|
b.rectStroke(g.bodyX + 0.5, bodyTop + 0.5, bw, bh, 1, bord);
|
|
@@ -7526,9 +7635,10 @@ var SceneGraph = class {
|
|
|
7526
7635
|
* so each indicator arrives behind the candles (and behind older indicators);
|
|
7527
7636
|
* `setIndicatorZ`/`bringToFront`/`sendToBack` change it. */
|
|
7528
7637
|
this.seriesZ = /* @__PURE__ */ new Map();
|
|
7529
|
-
/** Per-pane raster layers of
|
|
7638
|
+
/** Per-pane raster layers of drawings interleaved into the series stack (each
|
|
7639
|
+
* indicator's Pine drawings at its model's z, plus in-stack user drawings) — each a
|
|
7530
7640
|
* prepainted canvas the backend composites just before the series carrying `beforeZ`.
|
|
7531
|
-
* Rebuilt by the renderer per data frame
|
|
7641
|
+
* Rebuilt by the renderer per data frame. */
|
|
7532
7642
|
this.drawingSlices = /* @__PURE__ */ new Map();
|
|
7533
7643
|
/** Per-model index offset: the chart bar index of the model's `anchorTime` — its
|
|
7534
7644
|
* index-aligned payloads (dense series arrays, `bar_index` drawings) count from that
|
|
@@ -7762,8 +7872,12 @@ var Canvas2dBackend = class {
|
|
|
7762
7872
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
7763
7873
|
};
|
|
7764
7874
|
ctx.globalAlpha = this.modelAlpha;
|
|
7765
|
-
for (const m of models) for (const bg of m.backgrounds) this.drawBackground(ctx, bg, pane, coords);
|
|
7766
|
-
|
|
7875
|
+
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
7876
|
+
if (isPrice) {
|
|
7877
|
+
for (const m of scene.indicators.values()) {
|
|
7878
|
+
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
7879
|
+
}
|
|
7880
|
+
}
|
|
7767
7881
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
7768
7882
|
let si = 0;
|
|
7769
7883
|
const drawSlicesUpTo = (z) => {
|
|
@@ -7785,13 +7899,25 @@ var Canvas2dBackend = class {
|
|
|
7785
7899
|
ctx.globalAlpha = this.modelAlpha;
|
|
7786
7900
|
const off = scene.offsetOf(m.id);
|
|
7787
7901
|
const mp = effPane(m);
|
|
7788
|
-
for (const
|
|
7902
|
+
for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, i1, off);
|
|
7903
|
+
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
7789
7904
|
}
|
|
7790
7905
|
if (drawCandles && !candleDrawn) {
|
|
7791
7906
|
drawSlicesUpTo(scene.candleZ);
|
|
7792
7907
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
7793
7908
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
7794
7909
|
}
|
|
7910
|
+
if (isPrice) {
|
|
7911
|
+
ctx.globalAlpha = this.modelAlpha;
|
|
7912
|
+
for (const m of scene.indicators.values()) {
|
|
7913
|
+
const off = scene.offsetOf(m.id);
|
|
7914
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, off);
|
|
7915
|
+
}
|
|
7916
|
+
for (const m of scene.indicators.values()) {
|
|
7917
|
+
const off = scene.offsetOf(m.id);
|
|
7918
|
+
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
7919
|
+
}
|
|
7920
|
+
}
|
|
7795
7921
|
drawSlicesUpTo(Infinity);
|
|
7796
7922
|
ctx.globalAlpha = this.modelAlpha;
|
|
7797
7923
|
for (const m of models) {
|
|
@@ -8036,13 +8162,13 @@ var Canvas2dBackend = class {
|
|
|
8036
8162
|
if (drawBody) {
|
|
8037
8163
|
const oY = coords.priceToY(b.open, pane.scale, pane.bounds);
|
|
8038
8164
|
const cY = coords.priceToY(b.close, pane.scale, pane.bounds);
|
|
8039
|
-
top = Math.min(oY, cY);
|
|
8040
|
-
bodyH = Math.max(1, Math.
|
|
8165
|
+
top = snapY(Math.min(oY, cY), coords.dpr);
|
|
8166
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - top);
|
|
8041
8167
|
}
|
|
8042
8168
|
if (cs.wickVisible) {
|
|
8043
8169
|
const wick = (up ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : color);
|
|
8044
|
-
const hY = coords.priceToY(b.high, pane.scale, pane.bounds);
|
|
8045
|
-
const lY = coords.priceToY(b.low, pane.scale, pane.bounds);
|
|
8170
|
+
const hY = snapY(coords.priceToY(b.high, pane.scale, pane.bounds), coords.dpr);
|
|
8171
|
+
const lY = snapY(coords.priceToY(b.low, pane.scale, pane.bounds), coords.dpr);
|
|
8046
8172
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
8047
8173
|
ctx.strokeStyle = wick;
|
|
8048
8174
|
ctx.lineWidth = g.wickW;
|
|
@@ -8064,9 +8190,9 @@ var Canvas2dBackend = class {
|
|
|
8064
8190
|
ctx.fillStyle = color;
|
|
8065
8191
|
ctx.fillRect(g.bodyX, top, g.bodyW, bodyH);
|
|
8066
8192
|
}
|
|
8067
|
-
if (cs.borderVisible ||
|
|
8193
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
8068
8194
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
8069
|
-
ctx.strokeStyle = cs.borderVisible
|
|
8195
|
+
ctx.strokeStyle = cs.borderVisible ? (up ? cs.borderUpColor : cs.borderDownColor) ?? color : color;
|
|
8070
8196
|
ctx.lineWidth = 1;
|
|
8071
8197
|
const bw = Math.max(0, g.bodyW - 1);
|
|
8072
8198
|
const bh = Math.max(0, bodyH - 1);
|
|
@@ -8489,6 +8615,19 @@ function autoFontSize(lines, boxW, boxH, bold) {
|
|
|
8489
8615
|
|
|
8490
8616
|
// src/renderers/shared/DrawingSceneRenderer.ts
|
|
8491
8617
|
var EMPTY_DRAWING_SET = { lines: [], boxes: [], labels: [], polylines: [], linefills: [] };
|
|
8618
|
+
function modelDrawingSet(m, overlay) {
|
|
8619
|
+
const want = (d) => Boolean(d.overlay) === overlay;
|
|
8620
|
+
return {
|
|
8621
|
+
lines: (m.lines ?? []).filter(want),
|
|
8622
|
+
boxes: (m.boxes ?? []).filter(want),
|
|
8623
|
+
labels: (m.labels ?? []).filter(want),
|
|
8624
|
+
polylines: (m.polylines ?? []).filter(want),
|
|
8625
|
+
linefills: (m.linefills ?? []).filter(want)
|
|
8626
|
+
};
|
|
8627
|
+
}
|
|
8628
|
+
function drawingSetEmpty(s) {
|
|
8629
|
+
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
8630
|
+
}
|
|
8492
8631
|
function fontSizePx(size) {
|
|
8493
8632
|
return size === "auto" ? 12 : namedFontSize(size);
|
|
8494
8633
|
}
|
|
@@ -8498,6 +8637,8 @@ var DrawingSceneRenderer = class {
|
|
|
8498
8637
|
this.set = set;
|
|
8499
8638
|
/** measureText width cache, keyed by `${font} ${text}`, persists across frames. */
|
|
8500
8639
|
this.widthCache = /* @__PURE__ */ new Map();
|
|
8640
|
+
/** Tooltip hit-rects captured while drawing the CURRENT set (rebuilt per render). */
|
|
8641
|
+
this.tipRegions = [];
|
|
8501
8642
|
/**
|
|
8502
8643
|
* Index offset of the CURRENT set's model: its `xloc:'bar_index'` coordinates count
|
|
8503
8644
|
* from the model's anchor bar, so they shift by this to land on chart logical indices.
|
|
@@ -8518,8 +8659,13 @@ var DrawingSceneRenderer = class {
|
|
|
8518
8659
|
const s = this.set;
|
|
8519
8660
|
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
8520
8661
|
}
|
|
8662
|
+
/** Tooltip hit-rects of the labels drawn by the LAST `render` call (same coords as `ctx`). */
|
|
8663
|
+
labelTipRegions() {
|
|
8664
|
+
return this.tipRegions;
|
|
8665
|
+
}
|
|
8521
8666
|
/** Draw the whole set into `ctx` using the supplied coordinate closures. */
|
|
8522
8667
|
render(ctx, W, H, xOf, yOf) {
|
|
8668
|
+
this.tipRegions = [];
|
|
8523
8669
|
if (this.isEmpty()) return;
|
|
8524
8670
|
ctx.save();
|
|
8525
8671
|
this.drawLinefills(ctx, W, H, xOf, yOf);
|
|
@@ -8867,13 +9013,35 @@ var DrawingSceneRenderer = class {
|
|
|
8867
9013
|
if (this.isPointShape(lb.style)) {
|
|
8868
9014
|
if (!lb.noFill) this.drawLabelShape(ctx, lb.style, px, py, fontPx, color);
|
|
8869
9015
|
if (lb.text) this.drawLabelText(ctx, lb, px, py + fontPx, fontPx);
|
|
8870
|
-
|
|
8871
|
-
|
|
9016
|
+
if (lb.tooltip) {
|
|
9017
|
+
const r = Math.max(4, fontPx * 0.6) + 3;
|
|
9018
|
+
this.tipRegions.push({ left: px - r, top: py - r, right: px + r, bottom: py + r, text: lb.tooltip });
|
|
9019
|
+
}
|
|
9020
|
+
} else if (lb.style === "none" || lb.style === "text_outline") {
|
|
9021
|
+
if (lb.text) {
|
|
9022
|
+
this.drawLabelText(ctx, lb, px, py, fontPx, lb.style === "text_outline");
|
|
9023
|
+
if (lb.tooltip) this.tipRegions.push(this.textRegion(ctx, lb, px, py, fontPx, lb.tooltip));
|
|
9024
|
+
}
|
|
8872
9025
|
} else {
|
|
8873
|
-
this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
9026
|
+
const r = this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
9027
|
+
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
9028
|
}
|
|
8875
9029
|
}
|
|
8876
9030
|
}
|
|
9031
|
+
/** Canvas font for a label's text — family + Pine `text_formatting` (bold/italic). */
|
|
9032
|
+
labelFont(lb, fontPx) {
|
|
9033
|
+
const family = lb.fontFamily === "monospace" ? "monospace" : this.deps.theme.fontFamily || "sans-serif";
|
|
9034
|
+
return `${lb.italic ? "italic " : ""}${lb.bold ? "bold " : ""}${fontPx}px ${family}`;
|
|
9035
|
+
}
|
|
9036
|
+
/** Hover rect of a text-only label (style none/text_outline/noFill), centered like drawLabelText. */
|
|
9037
|
+
textRegion(ctx, lb, cx, cy, fontPx, text) {
|
|
9038
|
+
const font = this.labelFont(lb, fontPx);
|
|
9039
|
+
const lines = (lb.text ?? "").split("\n");
|
|
9040
|
+
const w = Math.max(1, ...lines.map((l) => this.measure(ctx, font, l)));
|
|
9041
|
+
const h = fontPx * 1.25 * lines.length;
|
|
9042
|
+
const left = lb.textAlign === "left" ? cx : lb.textAlign === "right" ? cx - w : cx - w / 2;
|
|
9043
|
+
return { left, top: cy - h / 2, right: left + w, bottom: cy + h / 2, text };
|
|
9044
|
+
}
|
|
8877
9045
|
isPointShape(style) {
|
|
8878
9046
|
switch (style) {
|
|
8879
9047
|
case "circle":
|
|
@@ -8892,8 +9060,7 @@ var DrawingSceneRenderer = class {
|
|
|
8892
9060
|
}
|
|
8893
9061
|
}
|
|
8894
9062
|
drawLabelText(ctx, lb, cx, cy, fontPx, outline = false) {
|
|
8895
|
-
|
|
8896
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
9063
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
8897
9064
|
ctx.textAlign = lb.textAlign === "left" ? "left" : lb.textAlign === "right" ? "right" : "center";
|
|
8898
9065
|
ctx.textBaseline = "middle";
|
|
8899
9066
|
const lines = lb.text.split("\n");
|
|
@@ -8973,9 +9140,9 @@ var DrawingSceneRenderer = class {
|
|
|
8973
9140
|
ctx.fill();
|
|
8974
9141
|
}
|
|
8975
9142
|
}
|
|
9143
|
+
/** Draw a bubble label; returns the bubble body rect (for tooltip hit-testing). */
|
|
8976
9144
|
drawBubble(ctx, lb, px, py, fontPx, color) {
|
|
8977
|
-
|
|
8978
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
9145
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
8979
9146
|
const lines = (lb.text ?? "").split("\n");
|
|
8980
9147
|
const padX = 6;
|
|
8981
9148
|
const padY = 4;
|
|
@@ -9033,38 +9200,51 @@ var DrawingSceneRenderer = class {
|
|
|
9033
9200
|
by = py - h - ptr;
|
|
9034
9201
|
pointer = "down";
|
|
9035
9202
|
}
|
|
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();
|
|
9203
|
+
if (!lb.noFill) {
|
|
9204
|
+
ctx.fillStyle = color;
|
|
9205
|
+
this.roundRect(ctx, bx, by, w, h, 4);
|
|
9059
9206
|
ctx.fill();
|
|
9207
|
+
if (pointer !== "none") {
|
|
9208
|
+
ctx.beginPath();
|
|
9209
|
+
if (pointer === "down") {
|
|
9210
|
+
ctx.moveTo(px - ptr, by + h);
|
|
9211
|
+
ctx.lineTo(px + ptr, by + h);
|
|
9212
|
+
ctx.lineTo(px, py);
|
|
9213
|
+
} else if (pointer === "up") {
|
|
9214
|
+
ctx.moveTo(px - ptr, by);
|
|
9215
|
+
ctx.lineTo(px + ptr, by);
|
|
9216
|
+
ctx.lineTo(px, py);
|
|
9217
|
+
} else if (pointer === "left") {
|
|
9218
|
+
ctx.moveTo(bx, py - ptr);
|
|
9219
|
+
ctx.lineTo(bx, py + ptr);
|
|
9220
|
+
ctx.lineTo(px, py);
|
|
9221
|
+
} else {
|
|
9222
|
+
ctx.moveTo(bx + w, py - ptr);
|
|
9223
|
+
ctx.lineTo(bx + w, py + ptr);
|
|
9224
|
+
ctx.lineTo(px, py);
|
|
9225
|
+
}
|
|
9226
|
+
ctx.closePath();
|
|
9227
|
+
ctx.fill();
|
|
9228
|
+
}
|
|
9060
9229
|
}
|
|
9061
9230
|
if (lb.text) {
|
|
9062
|
-
ctx.fillStyle = lb.textColor ?? contrastColor(color);
|
|
9063
|
-
ctx.textAlign = "center";
|
|
9231
|
+
ctx.fillStyle = lb.textColor ?? (lb.noFill ? this.deps.theme.textColor : contrastColor(color));
|
|
9064
9232
|
ctx.textBaseline = "middle";
|
|
9233
|
+
let tx;
|
|
9234
|
+
if (lb.textAlign === "left") {
|
|
9235
|
+
ctx.textAlign = "left";
|
|
9236
|
+
tx = bx + padX;
|
|
9237
|
+
} else if (lb.textAlign === "right") {
|
|
9238
|
+
ctx.textAlign = "right";
|
|
9239
|
+
tx = bx + w - padX;
|
|
9240
|
+
} else {
|
|
9241
|
+
ctx.textAlign = "center";
|
|
9242
|
+
tx = bx + w / 2;
|
|
9243
|
+
}
|
|
9065
9244
|
const startY = by + padY + lineH / 2;
|
|
9066
|
-
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i],
|
|
9245
|
+
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i], tx, startY + i * lineH);
|
|
9067
9246
|
}
|
|
9247
|
+
return { x: bx, y: by, w, h };
|
|
9068
9248
|
}
|
|
9069
9249
|
roundRect(ctx, x, y, w, h, r) {
|
|
9070
9250
|
const rr = Math.min(r, w / 2, h / 2);
|
|
@@ -9300,7 +9480,7 @@ var ChromeRenderer = class {
|
|
|
9300
9480
|
this.ctx = null;
|
|
9301
9481
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
9302
9482
|
this.axisTextColor = DARK_THEME.textColor;
|
|
9303
|
-
// Shared Pine-drawing renderer
|
|
9483
|
+
// Shared Pine-drawing renderer, used here for autoscale geometry only; widthCache persists.
|
|
9304
9484
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
9305
9485
|
}
|
|
9306
9486
|
mount(canvas) {
|
|
@@ -9325,8 +9505,8 @@ var ChromeRenderer = class {
|
|
|
9325
9505
|
*/
|
|
9326
9506
|
paneDrawingsRange(ownModels, scene, isPricePane, vr) {
|
|
9327
9507
|
let dr = null;
|
|
9328
|
-
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(
|
|
9329
|
-
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(
|
|
9508
|
+
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, false), vr, scene.offsetOf(m.id)));
|
|
9509
|
+
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, true), vr, scene.offsetOf(m.id)));
|
|
9330
9510
|
return dr;
|
|
9331
9511
|
}
|
|
9332
9512
|
/** Clear the chrome canvas and draw drawings + axes + current-price line.
|
|
@@ -9358,17 +9538,6 @@ var ChromeRenderer = class {
|
|
|
9358
9538
|
return;
|
|
9359
9539
|
}
|
|
9360
9540
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
9361
|
-
for (const pane of panes) {
|
|
9362
|
-
if (pane.collapsed) continue;
|
|
9363
|
-
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
9364
|
-
const sc = scene.scaleFor(m, pane);
|
|
9365
|
-
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
9366
|
-
this.renderDrawings(ctx, coords, this.ownDrawings(m), mp, dataW, scene.offsetOf(m.id));
|
|
9367
|
-
}
|
|
9368
|
-
}
|
|
9369
|
-
if (pricePane) {
|
|
9370
|
-
for (const m of scene.indicators.values()) this.renderDrawings(ctx, coords, this.overlayDrawings(m), pricePane, dataW, scene.offsetOf(m.id));
|
|
9371
|
-
}
|
|
9372
9541
|
if (pricePane && !pricePane.collapsed && scene.tradeMarkers.visible) {
|
|
9373
9542
|
for (const m of scene.indicators.values()) {
|
|
9374
9543
|
if (m.trades?.length) this.renderTrades(ctx, coords, scene, theme, m.trades, pricePane, dataW);
|
|
@@ -9384,25 +9553,6 @@ var ChromeRenderer = class {
|
|
|
9384
9553
|
this.canvas = null;
|
|
9385
9554
|
this.ctx = null;
|
|
9386
9555
|
}
|
|
9387
|
-
// ── Pine-drawing helpers (own vs force_overlay routing) ──
|
|
9388
|
-
ownDrawings(m) {
|
|
9389
|
-
return {
|
|
9390
|
-
lines: (m.lines ?? []).filter((d) => !d.overlay),
|
|
9391
|
-
boxes: (m.boxes ?? []).filter((d) => !d.overlay),
|
|
9392
|
-
labels: (m.labels ?? []).filter((d) => !d.overlay),
|
|
9393
|
-
polylines: (m.polylines ?? []).filter((d) => !d.overlay),
|
|
9394
|
-
linefills: (m.linefills ?? []).filter((d) => !d.overlay)
|
|
9395
|
-
};
|
|
9396
|
-
}
|
|
9397
|
-
overlayDrawings(m) {
|
|
9398
|
-
return {
|
|
9399
|
-
lines: (m.lines ?? []).filter((d) => d.overlay),
|
|
9400
|
-
boxes: (m.boxes ?? []).filter((d) => d.overlay),
|
|
9401
|
-
labels: (m.labels ?? []).filter((d) => d.overlay),
|
|
9402
|
-
polylines: (m.polylines ?? []).filter((d) => d.overlay),
|
|
9403
|
-
linefills: (m.linefills ?? []).filter((d) => d.overlay)
|
|
9404
|
-
};
|
|
9405
|
-
}
|
|
9406
9556
|
drawingsRange(set, vr, indexOffset = 0) {
|
|
9407
9557
|
this.drawScene.setSet(set, indexOffset);
|
|
9408
9558
|
if (this.drawScene.isEmpty()) return null;
|
|
@@ -9436,23 +9586,6 @@ var ChromeRenderer = class {
|
|
|
9436
9586
|
);
|
|
9437
9587
|
ctx.restore();
|
|
9438
9588
|
}
|
|
9439
|
-
renderDrawings(ctx, coords, set, pane, dataW, indexOffset = 0) {
|
|
9440
|
-
this.drawScene.setSet(set, indexOffset);
|
|
9441
|
-
if (this.drawScene.isEmpty()) return;
|
|
9442
|
-
ctx.save();
|
|
9443
|
-
ctx.translate(0, pane.bounds.top);
|
|
9444
|
-
ctx.beginPath();
|
|
9445
|
-
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
9446
|
-
ctx.clip();
|
|
9447
|
-
this.drawScene.render(
|
|
9448
|
-
ctx,
|
|
9449
|
-
dataW,
|
|
9450
|
-
pane.bounds.height,
|
|
9451
|
-
(l) => coords.logicalToX(l),
|
|
9452
|
-
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
9453
|
-
);
|
|
9454
|
-
ctx.restore();
|
|
9455
|
-
}
|
|
9456
9589
|
// ── axes ──
|
|
9457
9590
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
9458
9591
|
ctx.strokeStyle = scene.style.borderColor ?? theme.borderColor;
|
|
@@ -9668,6 +9801,68 @@ function formatCountdown(ms) {
|
|
|
9668
9801
|
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
9669
9802
|
}
|
|
9670
9803
|
|
|
9804
|
+
// src/renderers/native/chrome/LabelTooltip.ts
|
|
9805
|
+
var HOVER_DELAY_MS = 350;
|
|
9806
|
+
var LabelTooltip = class {
|
|
9807
|
+
constructor(plot, deps) {
|
|
9808
|
+
this.plot = plot;
|
|
9809
|
+
this.deps = deps;
|
|
9810
|
+
this.tip = null;
|
|
9811
|
+
this.timer = null;
|
|
9812
|
+
/** Text of the currently open OR armed tip — dedupes moves inside one label. */
|
|
9813
|
+
this.current = null;
|
|
9814
|
+
this.onMove = (e) => {
|
|
9815
|
+
if (e.pointerType !== "mouse") return;
|
|
9816
|
+
const rect = this.plot.getBoundingClientRect();
|
|
9817
|
+
const x = e.clientX - rect.left;
|
|
9818
|
+
const y = e.clientY - rect.top;
|
|
9819
|
+
const text = this.deps.lookup(x, y);
|
|
9820
|
+
if (!text) {
|
|
9821
|
+
this.clear();
|
|
9822
|
+
return;
|
|
9823
|
+
}
|
|
9824
|
+
if (text === this.current) return;
|
|
9825
|
+
this.clear();
|
|
9826
|
+
this.current = text;
|
|
9827
|
+
this.timer = window.setTimeout(() => this.show(text, x, y), HOVER_DELAY_MS);
|
|
9828
|
+
};
|
|
9829
|
+
this.onLeave = () => {
|
|
9830
|
+
this.clear();
|
|
9831
|
+
};
|
|
9832
|
+
plot.addEventListener("pointermove", this.onMove);
|
|
9833
|
+
plot.addEventListener("pointerleave", this.onLeave);
|
|
9834
|
+
plot.addEventListener("pointerdown", this.onLeave);
|
|
9835
|
+
}
|
|
9836
|
+
destroy() {
|
|
9837
|
+
this.plot.removeEventListener("pointermove", this.onMove);
|
|
9838
|
+
this.plot.removeEventListener("pointerleave", this.onLeave);
|
|
9839
|
+
this.plot.removeEventListener("pointerdown", this.onLeave);
|
|
9840
|
+
this.clear();
|
|
9841
|
+
}
|
|
9842
|
+
show(text, x, y) {
|
|
9843
|
+
const doc = this.plot.ownerDocument;
|
|
9844
|
+
const tip = doc.createElement("div");
|
|
9845
|
+
tip.textContent = text;
|
|
9846
|
+
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;";
|
|
9847
|
+
applyChromeTokens(tip, this.deps.theme());
|
|
9848
|
+
this.plot.appendChild(tip);
|
|
9849
|
+
const pw = this.plot.clientWidth;
|
|
9850
|
+
const ph = this.plot.clientHeight;
|
|
9851
|
+
tip.style.left = `${Math.max(0, Math.min(x + 12, pw - tip.offsetWidth - 4))}px`;
|
|
9852
|
+
tip.style.top = `${Math.max(0, Math.min(y + 16, ph - tip.offsetHeight - 4))}px`;
|
|
9853
|
+
this.tip = tip;
|
|
9854
|
+
}
|
|
9855
|
+
clear() {
|
|
9856
|
+
if (this.timer !== null) {
|
|
9857
|
+
clearTimeout(this.timer);
|
|
9858
|
+
this.timer = null;
|
|
9859
|
+
}
|
|
9860
|
+
this.tip?.remove();
|
|
9861
|
+
this.tip = null;
|
|
9862
|
+
this.current = null;
|
|
9863
|
+
}
|
|
9864
|
+
};
|
|
9865
|
+
|
|
9671
9866
|
// src/renderers/native/chrome/CrosshairRenderer.ts
|
|
9672
9867
|
var CrosshairRenderer = class {
|
|
9673
9868
|
constructor() {
|
|
@@ -13709,17 +13904,17 @@ function glyphIcon(glyph) {
|
|
|
13709
13904
|
return textGlyph(String(glyph), 15);
|
|
13710
13905
|
}
|
|
13711
13906
|
function stampSizeIcon(size) {
|
|
13712
|
-
return textGlyph("\u25CF", (
|
|
13907
|
+
return textGlyph("\u25CF", (SIZE_PX[String(size)] ?? 13) + 4);
|
|
13713
13908
|
}
|
|
13714
13909
|
function sizeIcon(size) {
|
|
13715
13910
|
return textGlyph(String(size).charAt(0).toUpperCase(), 15);
|
|
13716
13911
|
}
|
|
13717
|
-
var
|
|
13912
|
+
var SIZE_PX = { small: 10, normal: 13, large: 16, huge: 20 };
|
|
13718
13913
|
function numbersSizeIcon(size) {
|
|
13719
|
-
return textGlyph("12", (
|
|
13914
|
+
return textGlyph("12", (SIZE_PX[String(size)] ?? 13) - 1, 16.5, 'font-weight="600"');
|
|
13720
13915
|
}
|
|
13721
13916
|
function labelSizeIcon(size) {
|
|
13722
|
-
return textGlyph("T", (
|
|
13917
|
+
return textGlyph("T", (SIZE_PX[String(size)] ?? 13) + 2);
|
|
13723
13918
|
}
|
|
13724
13919
|
function capitalize(s) {
|
|
13725
13920
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -14694,6 +14889,315 @@ var UserDrawingController = class {
|
|
|
14694
14889
|
}
|
|
14695
14890
|
};
|
|
14696
14891
|
|
|
14892
|
+
// src/renderers/shared/TableOverlay.ts
|
|
14893
|
+
var SIZE_PX2 = {
|
|
14894
|
+
auto: 13,
|
|
14895
|
+
tiny: 10,
|
|
14896
|
+
small: 11,
|
|
14897
|
+
normal: 13,
|
|
14898
|
+
large: 16,
|
|
14899
|
+
huge: 20
|
|
14900
|
+
};
|
|
14901
|
+
function fontPxOf(size) {
|
|
14902
|
+
if (typeof size === "number") return size > 0 ? size : SIZE_PX2.auto;
|
|
14903
|
+
return SIZE_PX2[size] ?? SIZE_PX2.auto;
|
|
14904
|
+
}
|
|
14905
|
+
function tableHasContent(t) {
|
|
14906
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
14907
|
+
}
|
|
14908
|
+
function mergeRenderPlan(t) {
|
|
14909
|
+
const span = /* @__PURE__ */ new Map();
|
|
14910
|
+
const omit = /* @__PURE__ */ new Set();
|
|
14911
|
+
for (const m of t.merges) {
|
|
14912
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
14913
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
14914
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
14915
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
14916
|
+
}
|
|
14917
|
+
}
|
|
14918
|
+
}
|
|
14919
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
14920
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
14921
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
14922
|
+
}
|
|
14923
|
+
}
|
|
14924
|
+
for (const key of span.keys()) omit.delete(key);
|
|
14925
|
+
return { span, omit };
|
|
14926
|
+
}
|
|
14927
|
+
|
|
14928
|
+
// src/renderers/shared/TableCanvasRenderer.ts
|
|
14929
|
+
var PAD_X = 6;
|
|
14930
|
+
var PAD_Y = 2;
|
|
14931
|
+
var MARGIN = 6;
|
|
14932
|
+
var LINE_HEIGHT = 1.2;
|
|
14933
|
+
function paintTable(ctx, t, args, tips) {
|
|
14934
|
+
if (!tableHasContent(t)) return;
|
|
14935
|
+
const layout = layoutTable(ctx, t, args);
|
|
14936
|
+
if (!layout || layout.w <= 0 || layout.h <= 0) return;
|
|
14937
|
+
const fw = t.frameColor && t.frameWidth > 0 ? t.frameWidth : 0;
|
|
14938
|
+
const { x, y } = anchorOrigin(t.position, layout.w + 2 * fw, layout.h + 2 * fw, args);
|
|
14939
|
+
const x0 = x + fw;
|
|
14940
|
+
const y0 = y + fw;
|
|
14941
|
+
if (t.bgColor) {
|
|
14942
|
+
ctx.fillStyle = t.bgColor;
|
|
14943
|
+
ctx.fillRect(x0, y0, layout.w, layout.h);
|
|
14944
|
+
}
|
|
14945
|
+
if (fw > 0 && t.frameColor) {
|
|
14946
|
+
ctx.strokeStyle = t.frameColor;
|
|
14947
|
+
ctx.lineWidth = fw;
|
|
14948
|
+
ctx.strokeRect(x + fw / 2, y + fw / 2, layout.w + fw, layout.h + fw);
|
|
14949
|
+
}
|
|
14950
|
+
const colX = [0];
|
|
14951
|
+
for (const w of layout.colW) colX.push(colX[colX.length - 1] + w);
|
|
14952
|
+
const rowY = [0];
|
|
14953
|
+
for (const h of layout.rowH) rowY.push(rowY[rowY.length - 1] + h);
|
|
14954
|
+
const prevBaseline = ctx.textBaseline;
|
|
14955
|
+
const prevAlign = ctx.textAlign;
|
|
14956
|
+
ctx.textBaseline = "middle";
|
|
14957
|
+
for (const box of layout.boxes) {
|
|
14958
|
+
const rx = x0 + colX[box.c];
|
|
14959
|
+
const ry = y0 + rowY[box.r];
|
|
14960
|
+
const rw = colX[box.c + box.cs] - colX[box.c];
|
|
14961
|
+
const rh = rowY[box.r + box.rs] - rowY[box.r];
|
|
14962
|
+
const cell = box.cell;
|
|
14963
|
+
if (cell.bgColor) {
|
|
14964
|
+
ctx.fillStyle = cell.bgColor;
|
|
14965
|
+
ctx.fillRect(rx, ry, rw, rh);
|
|
14966
|
+
}
|
|
14967
|
+
const text = cell.text ?? "";
|
|
14968
|
+
if (text.length > 0) {
|
|
14969
|
+
const px = fontPxOf(cell.textSize);
|
|
14970
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
14971
|
+
ctx.fillStyle = cell.textColor ?? args.theme.textColor;
|
|
14972
|
+
const lines = text.split("\n");
|
|
14973
|
+
const blockH = lines.length * px * LINE_HEIGHT;
|
|
14974
|
+
const blockTop = cell.vAlign === "top" ? ry + PAD_Y : cell.vAlign === "bottom" ? ry + rh - PAD_Y - blockH : ry + (rh - blockH) / 2;
|
|
14975
|
+
const tx = cell.hAlign === "left" ? rx + PAD_X : cell.hAlign === "right" ? rx + rw - PAD_X : rx + rw / 2;
|
|
14976
|
+
ctx.textAlign = cell.hAlign;
|
|
14977
|
+
lines.forEach((line, i) => ctx.fillText(line, tx, blockTop + (i + 0.5) * px * LINE_HEIGHT));
|
|
14978
|
+
}
|
|
14979
|
+
if (cell.tooltip) tips.push({ left: rx, top: ry, right: rx + rw, bottom: ry + rh, text: cell.tooltip });
|
|
14980
|
+
}
|
|
14981
|
+
ctx.textBaseline = prevBaseline;
|
|
14982
|
+
ctx.textAlign = prevAlign;
|
|
14983
|
+
if (t.borderColor && t.borderWidth > 0) {
|
|
14984
|
+
ctx.strokeStyle = t.borderColor;
|
|
14985
|
+
ctx.lineWidth = t.borderWidth;
|
|
14986
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14987
|
+
ctx.beginPath();
|
|
14988
|
+
const edge = (ax, ay, bx, by) => {
|
|
14989
|
+
const key = `${ax},${ay},${bx},${by}`;
|
|
14990
|
+
if (seen.has(key)) return;
|
|
14991
|
+
seen.add(key);
|
|
14992
|
+
ctx.moveTo(ax, ay);
|
|
14993
|
+
ctx.lineTo(bx, by);
|
|
14994
|
+
};
|
|
14995
|
+
for (const box of layout.boxes) {
|
|
14996
|
+
const l = Math.round(x0 + colX[box.c]);
|
|
14997
|
+
const r = Math.round(x0 + colX[box.c + box.cs]);
|
|
14998
|
+
const tp = Math.round(y0 + rowY[box.r]);
|
|
14999
|
+
const bt = Math.round(y0 + rowY[box.r + box.rs]);
|
|
15000
|
+
edge(l, tp, r, tp);
|
|
15001
|
+
edge(l, bt, r, bt);
|
|
15002
|
+
edge(l, tp, l, bt);
|
|
15003
|
+
edge(r, tp, r, bt);
|
|
15004
|
+
}
|
|
15005
|
+
ctx.stroke();
|
|
15006
|
+
}
|
|
15007
|
+
}
|
|
15008
|
+
function layoutTable(ctx, t, args) {
|
|
15009
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
15010
|
+
const colW = new Array(t.columns).fill(0);
|
|
15011
|
+
const rowH = new Array(t.rows).fill(0);
|
|
15012
|
+
const boxes = [];
|
|
15013
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
15014
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
15015
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
15016
|
+
const cell = t.cells[r]?.[c];
|
|
15017
|
+
if (cell == null) continue;
|
|
15018
|
+
const sp = span.get(`${r}:${c}`);
|
|
15019
|
+
boxes.push({ cell, r, c, cs: Math.min(sp?.cs ?? 1, t.columns - c), rs: Math.min(sp?.rs ?? 1, t.rows - r) });
|
|
15020
|
+
}
|
|
15021
|
+
}
|
|
15022
|
+
if (boxes.length === 0) return null;
|
|
15023
|
+
const sizeOf = (cell) => {
|
|
15024
|
+
const px = fontPxOf(cell.textSize);
|
|
15025
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
15026
|
+
const lines = (cell.text ?? "").split("\n");
|
|
15027
|
+
let maxW = 0;
|
|
15028
|
+
for (const line of lines) maxW = Math.max(maxW, ctx.measureText(line).width);
|
|
15029
|
+
let w2 = Math.ceil(maxW) + 2 * PAD_X;
|
|
15030
|
+
let h2 = Math.ceil(lines.length * px * LINE_HEIGHT) + 2 * PAD_Y;
|
|
15031
|
+
if (cell.width) w2 = Math.max(w2, cell.width / 100 * args.plotWidth);
|
|
15032
|
+
if (cell.height) h2 = Math.max(h2, cell.height / 100 * args.paneHeight);
|
|
15033
|
+
return { w: w2, h: h2 };
|
|
15034
|
+
};
|
|
15035
|
+
const spanning = [];
|
|
15036
|
+
for (const box of boxes) {
|
|
15037
|
+
const { w: w2, h: h2 } = sizeOf(box.cell);
|
|
15038
|
+
if (box.cs === 1) colW[box.c] = Math.max(colW[box.c], w2);
|
|
15039
|
+
if (box.rs === 1) rowH[box.r] = Math.max(rowH[box.r], h2);
|
|
15040
|
+
if (box.cs > 1 || box.rs > 1) spanning.push({ box, w: w2, h: h2 });
|
|
15041
|
+
}
|
|
15042
|
+
for (const { box, w: w2, h: h2 } of spanning) {
|
|
15043
|
+
if (box.cs > 1) {
|
|
15044
|
+
let sum = 0;
|
|
15045
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
|
|
15046
|
+
if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
|
|
15047
|
+
}
|
|
15048
|
+
if (box.rs > 1) {
|
|
15049
|
+
let sum = 0;
|
|
15050
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
|
|
15051
|
+
if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
|
|
15052
|
+
}
|
|
15053
|
+
}
|
|
15054
|
+
let w = 0;
|
|
15055
|
+
for (const cw of colW) w += cw;
|
|
15056
|
+
let h = 0;
|
|
15057
|
+
for (const rh of rowH) h += rh;
|
|
15058
|
+
return { colW, rowH, w, h, boxes };
|
|
15059
|
+
}
|
|
15060
|
+
function anchorOrigin(position, totalW, totalH, args) {
|
|
15061
|
+
let y;
|
|
15062
|
+
if (position.startsWith("top")) y = MARGIN;
|
|
15063
|
+
else if (position.startsWith("bottom")) y = args.paneHeight - MARGIN - totalH;
|
|
15064
|
+
else y = args.paneHeight / 2 - totalH / 2;
|
|
15065
|
+
let x;
|
|
15066
|
+
if (position.endsWith("left")) x = MARGIN;
|
|
15067
|
+
else if (position.endsWith("right")) x = args.plotWidth - MARGIN - totalW;
|
|
15068
|
+
else x = args.plotWidth / 2 - totalW / 2;
|
|
15069
|
+
return { x, y };
|
|
15070
|
+
}
|
|
15071
|
+
function cellFont(cell, px, theme) {
|
|
15072
|
+
const family = cell.fontFamily === "monospace" ? "monospace" : theme.fontFamily || "sans-serif";
|
|
15073
|
+
return `${cell.italic ? "italic " : ""}${cell.bold ? "bold " : ""}${px}px ${family}`;
|
|
15074
|
+
}
|
|
15075
|
+
|
|
15076
|
+
// src/renderers/native/drawings/IndicatorDrawingSlices.ts
|
|
15077
|
+
function indicatorSliceKey(z, boundaries) {
|
|
15078
|
+
return boundaries.find((b) => b > z) ?? Infinity;
|
|
15079
|
+
}
|
|
15080
|
+
var IndicatorDrawingSlices = class {
|
|
15081
|
+
constructor() {
|
|
15082
|
+
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
15083
|
+
/** Slice canvas cache, keyed `paneId|beforeZ` — same lifecycle as the user-drawing cache. */
|
|
15084
|
+
this.sliceCache = /* @__PURE__ */ new Map();
|
|
15085
|
+
/** Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per prepare). */
|
|
15086
|
+
this.tips = [];
|
|
15087
|
+
}
|
|
15088
|
+
/**
|
|
15089
|
+
* Rebuild the per-indicator drawing slices for this data frame. `ref` is the data
|
|
15090
|
+
* canvas the slices must match pixel-for-pixel (the backend composites them 1:1).
|
|
15091
|
+
* Runs from the renderer's data paint, just before the backend composites the scene.
|
|
15092
|
+
*/
|
|
15093
|
+
prepare(scene, coords, theme, ref) {
|
|
15094
|
+
this.tips = [];
|
|
15095
|
+
const out = /* @__PURE__ */ new Map();
|
|
15096
|
+
if (ref.width === 0 || ref.height === 0) {
|
|
15097
|
+
this.sliceCache.clear();
|
|
15098
|
+
return out;
|
|
15099
|
+
}
|
|
15100
|
+
this.drawScene.setDeps({
|
|
15101
|
+
timeToLogical: (ms) => coords.timeToLogical(ms),
|
|
15102
|
+
barAt: (logical) => {
|
|
15103
|
+
const b = scene.bars[Math.round(logical)];
|
|
15104
|
+
return b ? { high: b.high, low: b.low } : null;
|
|
15105
|
+
},
|
|
15106
|
+
theme
|
|
15107
|
+
});
|
|
15108
|
+
const dpr = coords.dpr;
|
|
15109
|
+
const dataW = coords.width;
|
|
15110
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
15111
|
+
const add = (paneId, beforeZ, entry) => {
|
|
15112
|
+
const key = `${paneId}|${beforeZ}`;
|
|
15113
|
+
const bucket = buckets.get(key);
|
|
15114
|
+
if (bucket) bucket.entries.push(entry);
|
|
15115
|
+
else buckets.set(key, { paneId, beforeZ, entries: [entry] });
|
|
15116
|
+
};
|
|
15117
|
+
for (const pane of scene.orderedPanes()) {
|
|
15118
|
+
if (pane.collapsed) continue;
|
|
15119
|
+
const boundaries = scene.seriesBoundaries(pane.id);
|
|
15120
|
+
for (const m of scene.orderedIndicatorsForPane(pane.id)) {
|
|
15121
|
+
const set = modelDrawingSet(m, false);
|
|
15122
|
+
const tables = (m.tables ?? []).filter((t) => !t.overlay);
|
|
15123
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
15124
|
+
const sc = scene.scaleFor(m, pane);
|
|
15125
|
+
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
15126
|
+
const beforeZ = indicatorSliceKey(scene.zOf(m.id), boundaries);
|
|
15127
|
+
add(pane.id, beforeZ, { set, tables, pane: mp, indexOffset: scene.offsetOf(m.id) });
|
|
15128
|
+
}
|
|
15129
|
+
if (pane.kind === "price") {
|
|
15130
|
+
for (const m of scene.indicators.values()) {
|
|
15131
|
+
const set = modelDrawingSet(m, true);
|
|
15132
|
+
const tables = (m.tables ?? []).filter((t) => t.overlay === true);
|
|
15133
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
15134
|
+
add(pane.id, Infinity, { set, tables, pane, indexOffset: scene.offsetOf(m.id) });
|
|
15135
|
+
}
|
|
15136
|
+
}
|
|
15137
|
+
}
|
|
15138
|
+
for (const [key, { paneId, beforeZ, entries }] of buckets) {
|
|
15139
|
+
let canvas = this.sliceCache.get(key);
|
|
15140
|
+
if (!canvas) {
|
|
15141
|
+
canvas = document.createElement("canvas");
|
|
15142
|
+
this.sliceCache.set(key, canvas);
|
|
15143
|
+
}
|
|
15144
|
+
if (canvas.width !== ref.width || canvas.height !== ref.height) {
|
|
15145
|
+
canvas.width = ref.width;
|
|
15146
|
+
canvas.height = ref.height;
|
|
15147
|
+
}
|
|
15148
|
+
const ctx = canvas.getContext("2d");
|
|
15149
|
+
if (!ctx) continue;
|
|
15150
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
15151
|
+
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
15152
|
+
for (const e of entries) this.paintEntry(ctx, e, coords, dataW, theme);
|
|
15153
|
+
const slices = out.get(paneId) ?? [];
|
|
15154
|
+
slices.push({ beforeZ, canvas });
|
|
15155
|
+
out.set(paneId, slices);
|
|
15156
|
+
}
|
|
15157
|
+
for (const key of [...this.sliceCache.keys()]) if (!buckets.has(key)) this.sliceCache.delete(key);
|
|
15158
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
15159
|
+
return out;
|
|
15160
|
+
}
|
|
15161
|
+
paintEntry(ctx, e, coords, dataW, theme) {
|
|
15162
|
+
const { pane } = e;
|
|
15163
|
+
const paneTips = [];
|
|
15164
|
+
ctx.save();
|
|
15165
|
+
ctx.translate(0, pane.bounds.top);
|
|
15166
|
+
ctx.beginPath();
|
|
15167
|
+
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
15168
|
+
ctx.clip();
|
|
15169
|
+
this.drawScene.setSet(e.set, e.indexOffset);
|
|
15170
|
+
this.drawScene.render(
|
|
15171
|
+
ctx,
|
|
15172
|
+
dataW,
|
|
15173
|
+
pane.bounds.height,
|
|
15174
|
+
(l) => coords.logicalToX(l),
|
|
15175
|
+
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
15176
|
+
);
|
|
15177
|
+
paneTips.push(...this.drawScene.labelTipRegions());
|
|
15178
|
+
for (const t of e.tables) paintTable(ctx, t, { paneHeight: pane.bounds.height, plotWidth: dataW, theme }, paneTips);
|
|
15179
|
+
ctx.restore();
|
|
15180
|
+
for (const r of paneTips) {
|
|
15181
|
+
this.tips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
15182
|
+
}
|
|
15183
|
+
}
|
|
15184
|
+
/** Tooltip of the topmost label or table cell under a plot-space point, or null. Fed by the last prepare. */
|
|
15185
|
+
labelTooltipAt(x, y) {
|
|
15186
|
+
for (let i = this.tips.length - 1; i >= 0; i -= 1) {
|
|
15187
|
+
const r = this.tips[i];
|
|
15188
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
15189
|
+
}
|
|
15190
|
+
return null;
|
|
15191
|
+
}
|
|
15192
|
+
};
|
|
15193
|
+
function mergeSlices(indicator, user) {
|
|
15194
|
+
const out = /* @__PURE__ */ new Map();
|
|
15195
|
+
for (const [paneId, slices] of indicator) out.set(paneId, [...slices]);
|
|
15196
|
+
for (const [paneId, slices] of user) out.set(paneId, [...out.get(paneId) ?? [], ...slices]);
|
|
15197
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
15198
|
+
return out;
|
|
15199
|
+
}
|
|
15200
|
+
|
|
14697
15201
|
// src/renderers/native/drawings/Projector.ts
|
|
14698
15202
|
function createProjector(coords, paneOf, paneIdAtY, barsInRange) {
|
|
14699
15203
|
return {
|
|
@@ -14745,19 +15249,8 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
14745
15249
|
for (const model of models) {
|
|
14746
15250
|
const off = offsetOf(model.id);
|
|
14747
15251
|
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
|
-
}
|
|
15252
|
+
if (s.overlay === true) continue;
|
|
15253
|
+
considerSeries(s, i0, i1, off, consider);
|
|
14761
15254
|
}
|
|
14762
15255
|
for (const pl of model.priceLines) consider(pl.price);
|
|
14763
15256
|
}
|
|
@@ -14779,6 +15272,36 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
14779
15272
|
const span = max - min;
|
|
14780
15273
|
return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
|
|
14781
15274
|
}
|
|
15275
|
+
function considerSeries(s, i0, i1, off, consider) {
|
|
15276
|
+
if (s.kind === "candle" || s.kind === "bar") {
|
|
15277
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
15278
|
+
const b = s.bars[i - off];
|
|
15279
|
+
if (b) {
|
|
15280
|
+
consider(b.high);
|
|
15281
|
+
consider(b.low);
|
|
15282
|
+
}
|
|
15283
|
+
}
|
|
15284
|
+
} else if (isLineLikeSeries(s)) {
|
|
15285
|
+
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
15286
|
+
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
15287
|
+
else if (s.style?.base != null) consider(s.style.base);
|
|
15288
|
+
}
|
|
15289
|
+
}
|
|
15290
|
+
function overlaySeriesRange(models, i0, i1, offsetOf = () => 0) {
|
|
15291
|
+
let min = Infinity;
|
|
15292
|
+
let max = -Infinity;
|
|
15293
|
+
const consider = (v) => {
|
|
15294
|
+
if (v != null && Number.isFinite(v)) {
|
|
15295
|
+
if (v < min) min = v;
|
|
15296
|
+
if (v > max) max = v;
|
|
15297
|
+
}
|
|
15298
|
+
};
|
|
15299
|
+
for (const model of models) {
|
|
15300
|
+
const off = offsetOf(model.id);
|
|
15301
|
+
for (const s of model.series) if (s.overlay === true) considerSeries(s, i0, i1, off, consider);
|
|
15302
|
+
}
|
|
15303
|
+
return min === Infinity ? null : { min, max };
|
|
15304
|
+
}
|
|
14782
15305
|
function expandScaleByPixels(scale, heightPx, abovePx, belowPx) {
|
|
14783
15306
|
if (abovePx <= 0 && belowPx <= 0) return scale;
|
|
14784
15307
|
const content = heightPx - abovePx - belowPx;
|
|
@@ -15528,6 +16051,11 @@ var NativeRenderer = class {
|
|
|
15528
16051
|
this.vpvrRenderer = new VpvrRenderer();
|
|
15529
16052
|
this.resizeObserver = null;
|
|
15530
16053
|
this.dprMedia = null;
|
|
16054
|
+
/** Plot size in INTEGER device px, as last reported by the resize observer's
|
|
16055
|
+
* device-pixel-content-box — the browser's own statement of how many device pixels
|
|
16056
|
+
* it paints the plot into. `null` until the first report or where the box type is
|
|
16057
|
+
* unsupported (WebKit); syncSize then falls back to rounding the client rect. */
|
|
16058
|
+
this.plotDeviceSize = null;
|
|
15531
16059
|
this.coords = new CoordinateSystem();
|
|
15532
16060
|
this.scene = new SceneGraph();
|
|
15533
16061
|
// chosen at mount (WebGL2 if available, else canvas2d)
|
|
@@ -15535,6 +16063,10 @@ var NativeRenderer = class {
|
|
|
15535
16063
|
this.glowAmount = 0;
|
|
15536
16064
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
15537
16065
|
this.chrome = new ChromeRenderer();
|
|
16066
|
+
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
16067
|
+
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
16068
|
+
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
16069
|
+
this.labelTooltip = null;
|
|
15538
16070
|
this.crosshairLayer = new CrosshairRenderer();
|
|
15539
16071
|
/** 1 Hz repaint pump so the price-axis countdown-to-bar-close ticks; null when off. */
|
|
15540
16072
|
this.countdownTimer = null;
|
|
@@ -15545,6 +16077,8 @@ var NativeRenderer = class {
|
|
|
15545
16077
|
this.indicatorValuesOn = true;
|
|
15546
16078
|
/** Host-contributed legend actions — held here so a rebuild of the legend re-wires them. */
|
|
15547
16079
|
this.legendActionsProvider = null;
|
|
16080
|
+
/** Host-contributed legend callouts — held here so a rebuild of the legend re-wires them. */
|
|
16081
|
+
this.legendCalloutsProvider = null;
|
|
15548
16082
|
/** Host override of the legend's fold toggle — held here so a remount re-applies it. */
|
|
15549
16083
|
this.legendOverviewAction = null;
|
|
15550
16084
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -15679,7 +16213,6 @@ var NativeRenderer = class {
|
|
|
15679
16213
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
15680
16214
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
15681
16215
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
15682
|
-
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
15683
16216
|
this.name = "native";
|
|
15684
16217
|
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "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", "indicatorTitles", "indicatorValues"];
|
|
15685
16218
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
@@ -15993,7 +16526,6 @@ var NativeRenderer = class {
|
|
|
15993
16526
|
* hidden) on clear so a re-show picks up the current theme.
|
|
15994
16527
|
*/
|
|
15995
16528
|
setLoading(loading) {
|
|
15996
|
-
for (const overlay of this.tableOverlays.values()) overlay.setVisible(!loading);
|
|
15997
16529
|
if (!loading || !this.wrapper) {
|
|
15998
16530
|
this.loadingEl?.remove();
|
|
15999
16531
|
this.loadingEl = null;
|
|
@@ -16653,6 +17185,10 @@ var NativeRenderer = class {
|
|
|
16653
17185
|
this.plot.appendChild(this.scrollButton);
|
|
16654
17186
|
this.plot.addEventListener("pointermove", this.onScrollProximityMove);
|
|
16655
17187
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
17188
|
+
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
17189
|
+
theme: () => this.chromeTheme(),
|
|
17190
|
+
lookup: (x, y) => this.indicatorSlices.labelTooltipAt(x, y)
|
|
17191
|
+
});
|
|
16656
17192
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
16657
17193
|
projector: () => this.drawingProjector(),
|
|
16658
17194
|
dpr: () => this.coords.dpr,
|
|
@@ -16692,9 +17228,10 @@ var NativeRenderer = class {
|
|
|
16692
17228
|
this.inputsUI.setDialogHost(this.dialogHost);
|
|
16693
17229
|
this.inputsUI.setSymbolPicker(this.symbolPicker);
|
|
16694
17230
|
this.inputsUI.setLegendActions(this.legendActionsProvider);
|
|
17231
|
+
this.inputsUI.setLegendCallouts(this.legendCalloutsProvider);
|
|
16695
17232
|
this.inputsUI.setLegendOverviewAction(this.legendOverviewAction);
|
|
16696
17233
|
this.inputsUI.setOnChange((c) => {
|
|
16697
|
-
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value });
|
|
17234
|
+
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value, ...c.kind ? { kind: c.kind } : {} });
|
|
16698
17235
|
});
|
|
16699
17236
|
this.inputsUI.setOnRemove((id) => {
|
|
16700
17237
|
for (const cb of this.removeIndicatorCbs) cb(id);
|
|
@@ -16735,6 +17272,7 @@ var NativeRenderer = class {
|
|
|
16735
17272
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
16736
17273
|
}
|
|
16737
17274
|
});
|
|
17275
|
+
this.paneControls.setSuspended(this.layoutMode === "mobile");
|
|
16738
17276
|
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
16739
17277
|
panes: () => this.axisScaleViews(),
|
|
16740
17278
|
rightAxis: () => this.rightAxisW,
|
|
@@ -16744,8 +17282,19 @@ var NativeRenderer = class {
|
|
|
16744
17282
|
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
16745
17283
|
}
|
|
16746
17284
|
});
|
|
16747
|
-
this.resizeObserver = new ResizeObserver(() =>
|
|
17285
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
17286
|
+
for (const e of entries) {
|
|
17287
|
+
if (e.target !== this.plot) continue;
|
|
17288
|
+
const s = e.devicePixelContentBoxSize?.[0];
|
|
17289
|
+
if (s) this.plotDeviceSize = { width: s.inlineSize, height: s.blockSize };
|
|
17290
|
+
}
|
|
17291
|
+
this.resize();
|
|
17292
|
+
});
|
|
16748
17293
|
this.resizeObserver.observe(this.wrapper);
|
|
17294
|
+
try {
|
|
17295
|
+
this.resizeObserver.observe(this.plot, { box: "device-pixel-content-box" });
|
|
17296
|
+
} catch {
|
|
17297
|
+
}
|
|
16749
17298
|
this.watchDpr();
|
|
16750
17299
|
this.syncSize();
|
|
16751
17300
|
}
|
|
@@ -16870,8 +17419,6 @@ var NativeRenderer = class {
|
|
|
16870
17419
|
this.inputsUI?.destroy();
|
|
16871
17420
|
this.paneControls?.destroy();
|
|
16872
17421
|
this.axisScaleButtons?.destroy();
|
|
16873
|
-
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
16874
|
-
this.tableOverlays.clear();
|
|
16875
17422
|
this.resizeObserver?.disconnect();
|
|
16876
17423
|
this.resizeObserver = null;
|
|
16877
17424
|
this.dprMedia?.removeEventListener("change", this.onDprChange);
|
|
@@ -16884,6 +17431,8 @@ var NativeRenderer = class {
|
|
|
16884
17431
|
this.settingsButton = null;
|
|
16885
17432
|
this.plot?.removeEventListener("pointermove", this.onScrollProximityMove);
|
|
16886
17433
|
this.plot?.removeEventListener("pointerleave", this.onScrollProximityLeave);
|
|
17434
|
+
this.labelTooltip?.destroy();
|
|
17435
|
+
this.labelTooltip = null;
|
|
16887
17436
|
this.scrollButton?.remove();
|
|
16888
17437
|
this.scrollButton = null;
|
|
16889
17438
|
for (const l of this.extLayers) l.instance.destroy?.();
|
|
@@ -16898,6 +17447,7 @@ var NativeRenderer = class {
|
|
|
16898
17447
|
this.attributionEl = null;
|
|
16899
17448
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
16900
17449
|
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
17450
|
+
this.mountContainer?.style.removeProperty("--vela-bottom-gutter");
|
|
16901
17451
|
this.mountContainer?.style.removeProperty("--vela-price-pane-top");
|
|
16902
17452
|
this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
|
|
16903
17453
|
this.mountContainer = null;
|
|
@@ -16990,7 +17540,6 @@ var NativeRenderer = class {
|
|
|
16990
17540
|
ensurePane(pane) {
|
|
16991
17541
|
this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight ?? (pane.kind === "price" ? 3 : 1));
|
|
16992
17542
|
this.layoutPanes();
|
|
16993
|
-
this.repositionTables();
|
|
16994
17543
|
this.paneControls?.refresh();
|
|
16995
17544
|
this.scheduler.invalidate(4 /* Full */);
|
|
16996
17545
|
}
|
|
@@ -17012,17 +17561,14 @@ var NativeRenderer = class {
|
|
|
17012
17561
|
if (!model.ownScale) this.scene.dropIndicatorScale(handle.id);
|
|
17013
17562
|
this.inputsUI.setPane(handle.id, paneId);
|
|
17014
17563
|
this.refreshAnchorOffset(model);
|
|
17015
|
-
this.syncTables(model);
|
|
17016
17564
|
this.refreshAxisWidth();
|
|
17017
17565
|
this.layoutPanes();
|
|
17018
|
-
this.repositionTables();
|
|
17019
17566
|
this.paneControls?.refresh();
|
|
17020
17567
|
this.scheduler.invalidate(4 /* Full */);
|
|
17021
17568
|
}
|
|
17022
17569
|
orderPanes(orderedIds) {
|
|
17023
17570
|
this.scene.orderPanes(orderedIds);
|
|
17024
17571
|
this.layoutPanes();
|
|
17025
|
-
this.repositionTables();
|
|
17026
17572
|
this.paneControls?.refresh();
|
|
17027
17573
|
this.scheduler.invalidate(4 /* Full */);
|
|
17028
17574
|
}
|
|
@@ -17031,7 +17577,6 @@ var NativeRenderer = class {
|
|
|
17031
17577
|
if (!pane || pane.collapsed === collapsed) return;
|
|
17032
17578
|
pane.collapsed = collapsed;
|
|
17033
17579
|
this.layoutPanes();
|
|
17034
|
-
this.repositionTables();
|
|
17035
17580
|
this.paneControls?.refresh();
|
|
17036
17581
|
this.scheduler.invalidate(4 /* Full */);
|
|
17037
17582
|
}
|
|
@@ -17039,7 +17584,6 @@ var NativeRenderer = class {
|
|
|
17039
17584
|
if (paneId !== null && !this.scene.panes.has(paneId)) paneId = null;
|
|
17040
17585
|
this.maximizedPaneId = paneId;
|
|
17041
17586
|
this.layoutPanes();
|
|
17042
|
-
this.repositionTables();
|
|
17043
17587
|
this.paneControls?.refresh();
|
|
17044
17588
|
this.scheduler.invalidate(4 /* Full */);
|
|
17045
17589
|
}
|
|
@@ -17124,9 +17668,8 @@ var NativeRenderer = class {
|
|
|
17124
17668
|
else this.scene.assignIndicatorZ(model.id);
|
|
17125
17669
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
17126
17670
|
native: !!model.native,
|
|
17127
|
-
...model.
|
|
17671
|
+
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
17128
17672
|
});
|
|
17129
|
-
this.syncTables(model);
|
|
17130
17673
|
if (model.native?.type === "volume") {
|
|
17131
17674
|
this.volumeActive = true;
|
|
17132
17675
|
this.volumeHidden = false;
|
|
@@ -17150,7 +17693,6 @@ var NativeRenderer = class {
|
|
|
17150
17693
|
}
|
|
17151
17694
|
}
|
|
17152
17695
|
applyPatch(model, patch);
|
|
17153
|
-
this.syncTables(model);
|
|
17154
17696
|
this.scheduler.invalidate(3 /* Light */);
|
|
17155
17697
|
}
|
|
17156
17698
|
removeIndicator(handle) {
|
|
@@ -17169,14 +17711,12 @@ var NativeRenderer = class {
|
|
|
17169
17711
|
this.scene.forgetAnchorOffset(handle.id);
|
|
17170
17712
|
this.scene.dropIndicatorScale(handle.id);
|
|
17171
17713
|
this.inputsUI.remove(handle.id);
|
|
17172
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
17173
|
-
this.tableOverlays.delete(handle.id);
|
|
17174
17714
|
this.refreshAxisWidth();
|
|
17175
17715
|
this.paneControls?.refresh();
|
|
17176
17716
|
this.scheduler.invalidate(4 /* Full */);
|
|
17177
17717
|
}
|
|
17178
|
-
setIndicatorInputs(handle, values) {
|
|
17179
|
-
this.inputsUI.setValues(handle.id, values);
|
|
17718
|
+
setIndicatorInputs(handle, values, props) {
|
|
17719
|
+
this.inputsUI.setValues(handle.id, values, props);
|
|
17180
17720
|
}
|
|
17181
17721
|
setSymbolPicker(picker) {
|
|
17182
17722
|
this.symbolPicker = picker;
|
|
@@ -17186,6 +17726,10 @@ var NativeRenderer = class {
|
|
|
17186
17726
|
this.legendActionsProvider = provider;
|
|
17187
17727
|
this.inputsUI?.setLegendActions(provider);
|
|
17188
17728
|
}
|
|
17729
|
+
setLegendCallouts(provider) {
|
|
17730
|
+
this.legendCalloutsProvider = provider;
|
|
17731
|
+
this.inputsUI?.setLegendCallouts(provider);
|
|
17732
|
+
}
|
|
17189
17733
|
setLegendOverviewAction(action) {
|
|
17190
17734
|
this.legendOverviewAction = action;
|
|
17191
17735
|
this.inputsUI?.setLegendOverviewAction(action);
|
|
@@ -17210,8 +17754,6 @@ var NativeRenderer = class {
|
|
|
17210
17754
|
}
|
|
17211
17755
|
if (!visible) {
|
|
17212
17756
|
this.scene.indicators.delete(handle.id);
|
|
17213
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
17214
|
-
this.tableOverlays.delete(handle.id);
|
|
17215
17757
|
}
|
|
17216
17758
|
this.inputsUI.setVisible(handle.id, visible);
|
|
17217
17759
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -17257,6 +17799,7 @@ var NativeRenderer = class {
|
|
|
17257
17799
|
this.userDrawings?.setLayoutMode(mode);
|
|
17258
17800
|
this.settingsDialog?.setLayoutMode(mode);
|
|
17259
17801
|
this.inputsUI?.setLayoutMode(mode);
|
|
17802
|
+
this.paneControls?.setSuspended(mode === "mobile");
|
|
17260
17803
|
if (this.scrollButton) {
|
|
17261
17804
|
const px = mode === "mobile" ? SCROLL_BTN_SIZE_TOUCH : SCROLL_BTN_SIZE;
|
|
17262
17805
|
this.scrollButton.style.width = `${px}px`;
|
|
@@ -17652,7 +18195,6 @@ var NativeRenderer = class {
|
|
|
17652
18195
|
/** Relayout + repaint + refresh the hover buttons after a collapse/maximize/order change. */
|
|
17653
18196
|
afterPaneLayoutChange() {
|
|
17654
18197
|
this.layoutPanes();
|
|
17655
|
-
this.repositionTables();
|
|
17656
18198
|
this.paneControls?.refresh();
|
|
17657
18199
|
this.scheduler.invalidate(4 /* Full */);
|
|
17658
18200
|
}
|
|
@@ -17733,7 +18275,6 @@ var NativeRenderer = class {
|
|
|
17733
18275
|
above.heightWeight = next.above;
|
|
17734
18276
|
below.heightWeight = next.below;
|
|
17735
18277
|
this.layoutPanes();
|
|
17736
|
-
this.repositionTables();
|
|
17737
18278
|
this.scheduler.invalidate(4 /* Full */);
|
|
17738
18279
|
}
|
|
17739
18280
|
/** Double-click a separator → split the two adjacent panes evenly (each gets half of
|
|
@@ -17748,7 +18289,6 @@ var NativeRenderer = class {
|
|
|
17748
18289
|
above.heightWeight = half;
|
|
17749
18290
|
below.heightWeight = half;
|
|
17750
18291
|
this.layoutPanes();
|
|
17751
|
-
this.repositionTables();
|
|
17752
18292
|
this.scheduler.invalidate(4 /* Full */);
|
|
17753
18293
|
}
|
|
17754
18294
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -18027,7 +18567,10 @@ var NativeRenderer = class {
|
|
|
18027
18567
|
const liveActual = li >= 0 ? this.bars[li] : void 0;
|
|
18028
18568
|
const easeLive = !!liveActual && this.liveEaseTime === liveActual.time && (liveActual.high !== this.liveEaseHigh || liveActual.low !== this.liveEaseLow || liveActual.close !== this.liveEaseClose);
|
|
18029
18569
|
if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
|
|
18030
|
-
this.scene.drawingSlices =
|
|
18570
|
+
this.scene.drawingSlices = mergeSlices(
|
|
18571
|
+
this.indicatorSlices.prepare(this.scene, this.coords, this.theme, this.dataCanvas),
|
|
18572
|
+
this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map()
|
|
18573
|
+
);
|
|
18031
18574
|
this.backdropRenderer.render(this.scene, this.coords, this.theme, gridAlpha);
|
|
18032
18575
|
this.backend.render(this.scene, this.coords, this.theme);
|
|
18033
18576
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
@@ -18166,7 +18709,11 @@ var NativeRenderer = class {
|
|
|
18166
18709
|
}
|
|
18167
18710
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
18168
18711
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
18169
|
-
|
|
18712
|
+
let dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
18713
|
+
if (pane === pricePane) {
|
|
18714
|
+
const or = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
|
|
18715
|
+
if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
|
|
18716
|
+
}
|
|
18170
18717
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
18171
18718
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
18172
18719
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -18440,6 +18987,7 @@ var NativeRenderer = class {
|
|
|
18440
18987
|
for (const m of models) {
|
|
18441
18988
|
const off = this.scene.offsetOf(m.id);
|
|
18442
18989
|
for (const s of m.series) {
|
|
18990
|
+
if (s.overlay === true) continue;
|
|
18443
18991
|
if (isLineLikeSeries(s)) {
|
|
18444
18992
|
const v = s.points[i0 - off]?.value;
|
|
18445
18993
|
if (v != null && Number.isFinite(v)) return v;
|
|
@@ -18460,27 +19008,6 @@ var NativeRenderer = class {
|
|
|
18460
19008
|
}
|
|
18461
19009
|
return maxVol;
|
|
18462
19010
|
}
|
|
18463
|
-
/** Create/update/destroy an indicator's DOM table overlay (anchored off real pane geometry). */
|
|
18464
|
-
syncTables(model) {
|
|
18465
|
-
const tables = model.tables ?? [];
|
|
18466
|
-
let overlay = this.tableOverlays.get(model.id);
|
|
18467
|
-
if (tables.length === 0) {
|
|
18468
|
-
if (overlay) {
|
|
18469
|
-
overlay.destroy();
|
|
18470
|
-
this.tableOverlays.delete(model.id);
|
|
18471
|
-
}
|
|
18472
|
-
return;
|
|
18473
|
-
}
|
|
18474
|
-
if (!overlay) {
|
|
18475
|
-
overlay = new TableOverlay(this.plot, this.theme, (id) => this.paneBoundsFor(id));
|
|
18476
|
-
overlay.setVisible(this.loadingEl === null);
|
|
18477
|
-
this.tableOverlays.set(model.id, overlay);
|
|
18478
|
-
}
|
|
18479
|
-
overlay.update(tables);
|
|
18480
|
-
}
|
|
18481
|
-
repositionTables() {
|
|
18482
|
-
for (const overlay of this.tableOverlays.values()) overlay.reposition();
|
|
18483
|
-
}
|
|
18484
19011
|
layoutPanes() {
|
|
18485
19012
|
const panes = this.scene.orderedPanes();
|
|
18486
19013
|
const dataHeight = this.coords.height;
|
|
@@ -18531,6 +19058,7 @@ var NativeRenderer = class {
|
|
|
18531
19058
|
const visible = maxPane ? [maxPane] : this.scene.orderedPanes().filter((p) => !p.collapsed);
|
|
18532
19059
|
const paneBottom = visible.length ? Math.max(...visible.map((p) => p.bounds.top + p.bounds.height)) : dataHeight;
|
|
18533
19060
|
this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM + Math.max(0, dataHeight - paneBottom);
|
|
19061
|
+
this.mountContainer?.style.setProperty("--vela-bottom-gutter", `${TIME_AXIS_H + Math.max(0, dataHeight - paneBottom)}px`);
|
|
18534
19062
|
this.scrollBtnRightPx = this.rightAxisW + SCROLL_BTN_RIGHT_INSET;
|
|
18535
19063
|
if (this.scrollButton) {
|
|
18536
19064
|
this.scrollButton.style.bottom = `${this.scrollBtnBottomPx}px`;
|
|
@@ -18623,30 +19151,33 @@ var NativeRenderer = class {
|
|
|
18623
19151
|
if (w <= 0 || h <= 0) return;
|
|
18624
19152
|
const dpr = window.devicePixelRatio || 1;
|
|
18625
19153
|
this.plot.style.left = `${this.toolbarGutter}px`;
|
|
18626
|
-
const
|
|
18627
|
-
|
|
18628
|
-
|
|
18629
|
-
|
|
18630
|
-
|
|
18631
|
-
|
|
18632
|
-
|
|
18633
|
-
|
|
18634
|
-
|
|
18635
|
-
|
|
18636
|
-
|
|
18637
|
-
|
|
18638
|
-
|
|
18639
|
-
|
|
18640
|
-
|
|
18641
|
-
|
|
18642
|
-
this.
|
|
18643
|
-
this.
|
|
18644
|
-
this.
|
|
18645
|
-
|
|
19154
|
+
const rect = this.plot.getBoundingClientRect();
|
|
19155
|
+
let bw = Math.max(1, Math.round(rect.width * dpr));
|
|
19156
|
+
let bh = Math.max(1, Math.round(rect.height * dpr));
|
|
19157
|
+
const dev = this.plotDeviceSize;
|
|
19158
|
+
if (dev && Math.abs(dev.width - rect.width * dpr) <= 1 && Math.abs(dev.height - rect.height * dpr) <= 1) {
|
|
19159
|
+
bw = Math.max(1, dev.width);
|
|
19160
|
+
bh = Math.max(1, dev.height);
|
|
19161
|
+
}
|
|
19162
|
+
const pw = bw / dpr;
|
|
19163
|
+
const ph = bh / dpr;
|
|
19164
|
+
const size = (canvas) => {
|
|
19165
|
+
canvas.width = bw;
|
|
19166
|
+
canvas.height = bh;
|
|
19167
|
+
canvas.style.width = `${pw}px`;
|
|
19168
|
+
canvas.style.height = `${ph}px`;
|
|
19169
|
+
};
|
|
19170
|
+
size(this.dataCanvas);
|
|
19171
|
+
size(this.backdropCanvas);
|
|
19172
|
+
size(this.volumeCanvas);
|
|
19173
|
+
for (const l of this.extLayers) size(l.canvas);
|
|
19174
|
+
size(this.vpvrCanvas);
|
|
19175
|
+
size(this.chromeCanvas);
|
|
19176
|
+
size(this.drawingsCanvas);
|
|
19177
|
+
size(this.cursorCanvas);
|
|
18646
19178
|
this.coords.setSize(Math.max(1, pw - this.rightAxisW), Math.max(1, ph - TIME_AXIS_H), dpr);
|
|
18647
19179
|
this.scene.crosshair = null;
|
|
18648
19180
|
this.layoutPanes();
|
|
18649
|
-
this.repositionTables();
|
|
18650
19181
|
this.userDrawings?.onResize();
|
|
18651
19182
|
if (!this.didInitialFit && this.coords.barCount > 0) {
|
|
18652
19183
|
this.fitContent();
|
|
@@ -18820,6 +19351,7 @@ var IndicatorHandleImpl = class {
|
|
|
18820
19351
|
constructor(id, title, controller, source) {
|
|
18821
19352
|
this.controller = controller;
|
|
18822
19353
|
this.schema = [];
|
|
19354
|
+
this.propsSchema = [];
|
|
18823
19355
|
this.visibleState = true;
|
|
18824
19356
|
this.bus = new TypedEventBus();
|
|
18825
19357
|
this.id = id;
|
|
@@ -18829,6 +19361,9 @@ var IndicatorHandleImpl = class {
|
|
|
18829
19361
|
get inputs() {
|
|
18830
19362
|
return this.schema;
|
|
18831
19363
|
}
|
|
19364
|
+
get props() {
|
|
19365
|
+
return this.propsSchema;
|
|
19366
|
+
}
|
|
18832
19367
|
get visible() {
|
|
18833
19368
|
return this.visibleState;
|
|
18834
19369
|
}
|
|
@@ -18838,6 +19373,12 @@ var IndicatorHandleImpl = class {
|
|
|
18838
19373
|
setInputs(values) {
|
|
18839
19374
|
this.controller.applyInputs(this.id, values);
|
|
18840
19375
|
}
|
|
19376
|
+
setProp(key, value) {
|
|
19377
|
+
this.controller.applyProps(this.id, { [key]: value });
|
|
19378
|
+
}
|
|
19379
|
+
setProps(values) {
|
|
19380
|
+
this.controller.applyProps(this.id, values);
|
|
19381
|
+
}
|
|
18841
19382
|
setVisible(visible) {
|
|
18842
19383
|
this.controller.setVisible(this.id, visible);
|
|
18843
19384
|
}
|
|
@@ -18857,6 +19398,9 @@ var IndicatorHandleImpl = class {
|
|
|
18857
19398
|
setSchema(schema) {
|
|
18858
19399
|
this.schema = schema;
|
|
18859
19400
|
}
|
|
19401
|
+
setPropsSchema(schema) {
|
|
19402
|
+
this.propsSchema = schema;
|
|
19403
|
+
}
|
|
18860
19404
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
18861
19405
|
setVisibleState(visible) {
|
|
18862
19406
|
this.visibleState = visible;
|
|
@@ -18870,6 +19414,8 @@ var IndicatorHandleImpl = class {
|
|
|
18870
19414
|
function summarizeModel(model) {
|
|
18871
19415
|
const series = {};
|
|
18872
19416
|
for (const s of model.series) series[s.kind] = (series[s.kind] ?? 0) + 1;
|
|
19417
|
+
const countOverlay = (items) => items?.filter((i) => i.overlay === true).length ?? 0;
|
|
19418
|
+
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
19419
|
return {
|
|
18874
19420
|
id: model.id,
|
|
18875
19421
|
title: model.title,
|
|
@@ -18889,7 +19435,8 @@ function summarizeModel(model) {
|
|
|
18889
19435
|
tables: model.tables?.length ?? 0,
|
|
18890
19436
|
barColors: model.barColors?.length ?? 0,
|
|
18891
19437
|
trades: model.trades?.length ?? 0,
|
|
18892
|
-
inputs: model.inputs.length
|
|
19438
|
+
inputs: model.inputs.length,
|
|
19439
|
+
forcedOverlay
|
|
18893
19440
|
};
|
|
18894
19441
|
}
|
|
18895
19442
|
function inspectModels(models) {
|
|
@@ -18910,7 +19457,8 @@ function inspectModels(models) {
|
|
|
18910
19457
|
linefills: sum((s) => s.linefills),
|
|
18911
19458
|
tables: sum((s) => s.tables),
|
|
18912
19459
|
barColors: sum((s) => s.barColors),
|
|
18913
|
-
trades: sum((s) => s.trades)
|
|
19460
|
+
trades: sum((s) => s.trades),
|
|
19461
|
+
forcedOverlay: sum((s) => s.forcedOverlay)
|
|
18914
19462
|
}
|
|
18915
19463
|
};
|
|
18916
19464
|
}
|
|
@@ -19021,6 +19569,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19021
19569
|
/** Invalidates detached async work (backfill loops, in-flight loads, gap heals):
|
|
19022
19570
|
* bumped by init(), setMarket() and destroy(). */
|
|
19023
19571
|
this.generation = 0;
|
|
19572
|
+
/** Aborts the in-flight PROGRESSIVE load's source polling on supersession — an
|
|
19573
|
+
* abandoned stream left polling to its own budget starves the browser's per-host
|
|
19574
|
+
* connection pool, and the NEXT symbol's very first fetch with it (measured). */
|
|
19575
|
+
this.progressiveAbort = null;
|
|
19024
19576
|
/** Awaiters racing a superseded load (setMarket callers) — released on every bump so they never hang. */
|
|
19025
19577
|
this.supersedeWaiters = [];
|
|
19026
19578
|
/** `history:complete` fired for the CURRENT load. Each market load re-arms the cycle
|
|
@@ -19054,7 +19606,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19054
19606
|
for (const engine of engines) this.registerEngine(engine.language, engine);
|
|
19055
19607
|
this.defaultLanguage = config.defaultLanguage;
|
|
19056
19608
|
this.renderer.mount(container, config.theme);
|
|
19057
|
-
this.renderer.onInputChange(
|
|
19609
|
+
this.renderer.onInputChange(
|
|
19610
|
+
(e) => e.kind === "prop" ? this.applyProps(e.indicatorId, { [e.key]: e.value }) : this.applyInputs(e.indicatorId, { [e.key]: e.value })
|
|
19611
|
+
);
|
|
19058
19612
|
this.renderer.onRemoveIndicator((id) => this.removeIndicator(id));
|
|
19059
19613
|
this.renderer.onToggleIndicatorVisible?.((id, visible) => this.setVisible(id, visible));
|
|
19060
19614
|
this.paneActionUnsub = this.renderer.onPaneAction?.((a) => this.handlePaneAction(a)) ?? null;
|
|
@@ -19161,6 +19715,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19161
19715
|
* superseded setMarket awaiters so their promises resolve instead of hanging. */
|
|
19162
19716
|
bumpGeneration() {
|
|
19163
19717
|
const gen = ++this.generation;
|
|
19718
|
+
this.progressiveAbort?.abort();
|
|
19719
|
+
this.progressiveAbort = null;
|
|
19164
19720
|
for (const w of this.supersedeWaiters.splice(0)) w();
|
|
19165
19721
|
return gen;
|
|
19166
19722
|
}
|
|
@@ -19214,7 +19770,47 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19214
19770
|
const requested = market.bars ?? 500;
|
|
19215
19771
|
const initialRange = market.visibleRange;
|
|
19216
19772
|
const deep = !market.data?.length && initialRange == null && requested > SINGLE_LOAD_BARS;
|
|
19217
|
-
|
|
19773
|
+
let progressiveServed = false;
|
|
19774
|
+
if (!market.data?.length && initialRange == null && this.feed.loadProgressive) {
|
|
19775
|
+
let painted = false;
|
|
19776
|
+
const paint = (bars, final) => {
|
|
19777
|
+
if (this.generation !== gen || !final && bars.length === 0) return;
|
|
19778
|
+
this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
|
|
19779
|
+
if (!painted && bars.length > 0) {
|
|
19780
|
+
painted = true;
|
|
19781
|
+
if (opts.firstLoad) this.activateBarLayers();
|
|
19782
|
+
if (!final) this.historyState = "backfill";
|
|
19783
|
+
}
|
|
19784
|
+
};
|
|
19785
|
+
const abort = new AbortController();
|
|
19786
|
+
this.progressiveAbort = abort;
|
|
19787
|
+
progressiveServed = await new Promise((firstPaint) => {
|
|
19788
|
+
let signaled = false;
|
|
19789
|
+
const signal = (served) => {
|
|
19790
|
+
if (!signaled) {
|
|
19791
|
+
signaled = true;
|
|
19792
|
+
firstPaint(served);
|
|
19793
|
+
}
|
|
19794
|
+
};
|
|
19795
|
+
abort.signal.addEventListener("abort", () => signal(true), { once: true });
|
|
19796
|
+
this.feed.loadProgressive(market, (bars) => {
|
|
19797
|
+
paint(bars, false);
|
|
19798
|
+
if (painted) signal(true);
|
|
19799
|
+
}, { signal: abort.signal }).then((full) => {
|
|
19800
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
19801
|
+
if (full == null) return signal(false);
|
|
19802
|
+
if (this.generation !== gen) return signal(true);
|
|
19803
|
+
paint(full, true);
|
|
19804
|
+
this.completeHistory(full.length >= requested ? "depth" : "genesis");
|
|
19805
|
+
signal(true);
|
|
19806
|
+
}).catch(() => {
|
|
19807
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
19808
|
+
if (this.generation === gen) this.completeHistory("aborted");
|
|
19809
|
+
signal(true);
|
|
19810
|
+
});
|
|
19811
|
+
});
|
|
19812
|
+
}
|
|
19813
|
+
if (progressiveServed) ; else if (deep && this.feed.loadRange) {
|
|
19218
19814
|
const head = await this.feed.load({ ...market, bars: Math.min(requested, CHUNK_BARS) });
|
|
19219
19815
|
if (this.generation !== gen) return;
|
|
19220
19816
|
this.setBarSeries(head);
|
|
@@ -19734,7 +20330,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19734
20330
|
const title = options.title ?? "Indicator";
|
|
19735
20331
|
const handle = new IndicatorHandleImpl(id, title, this, source);
|
|
19736
20332
|
this.handles.set(id, handle);
|
|
19737
|
-
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} } });
|
|
20333
|
+
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} }, propValues: { ...options.props ?? {} } });
|
|
19738
20334
|
void this.startIndicator(id, source, options, handle);
|
|
19739
20335
|
return handle;
|
|
19740
20336
|
}
|
|
@@ -19757,7 +20353,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19757
20353
|
return handle;
|
|
19758
20354
|
}
|
|
19759
20355
|
const inputValues = { ...descriptor.defaultInputs(), ...options.inputs ?? {} };
|
|
19760
|
-
this.registry.add({ id, title, source: type, inputValues, native: { type, instance: descriptor.create(), descriptor } });
|
|
20356
|
+
this.registry.add({ id, title, source: type, inputValues, propValues: {}, native: { type, instance: descriptor.create(), descriptor } });
|
|
19761
20357
|
handle.setSchema(descriptor.inputsSchema());
|
|
19762
20358
|
void this.startNativeIndicator(id, handle);
|
|
19763
20359
|
return handle;
|
|
@@ -19810,6 +20406,19 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19810
20406
|
if (record.session) record.session.update(record.inputValues);
|
|
19811
20407
|
else if (record.native && !record.hidden) record.native.instance.setInputs(record.inputValues);
|
|
19812
20408
|
}
|
|
20409
|
+
/** IndicatorController: re-run an indicator with merged declaration-prop overrides.
|
|
20410
|
+
* Same lifecycle as {@link applyInputs} — a prop change replays the whole script.
|
|
20411
|
+
* Script indicators only: natives have no declaration props. */
|
|
20412
|
+
applyProps(id, values) {
|
|
20413
|
+
const record = this.registry.get(id);
|
|
20414
|
+
if (!record?.session) return;
|
|
20415
|
+
record.propValues = { ...record.propValues, ...values };
|
|
20416
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20417
|
+
record.pendingStructural = true;
|
|
20418
|
+
if (!record.hidden) this.setLoading(record, true);
|
|
20419
|
+
record.pendingCause = "inputs";
|
|
20420
|
+
record.session.update(record.inputValues, record.propValues);
|
|
20421
|
+
}
|
|
19813
20422
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
19814
20423
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
19815
20424
|
listIndicators() {
|
|
@@ -19958,6 +20567,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19958
20567
|
for (const input of prepared.inputs) defaults[input.key] = input.defval;
|
|
19959
20568
|
record.inputValues = { ...defaults, ...record.inputValues };
|
|
19960
20569
|
handle.setSchema(prepared.inputs);
|
|
20570
|
+
const propDefaults = {};
|
|
20571
|
+
for (const prop of prepared.props ?? []) propDefaults[prop.key] = prop.defval;
|
|
20572
|
+
record.propValues = { ...propDefaults, ...record.propValues };
|
|
20573
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
19961
20574
|
this.mountLoadingPlaceholder(id, record);
|
|
19962
20575
|
this.executeIndicator(id, handle);
|
|
19963
20576
|
} catch (err) {
|
|
@@ -19981,6 +20594,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19981
20594
|
getBars: () => this.bars,
|
|
19982
20595
|
fetchSeries: (sym, tf, range) => this.fetchSeries(sym, tf, range),
|
|
19983
20596
|
inputs: record.inputValues,
|
|
20597
|
+
props: record.propValues,
|
|
19984
20598
|
visibleRange: this.currentVisibleRange(),
|
|
19985
20599
|
mode,
|
|
19986
20600
|
// Mid-backfill session starts (add / re-show / price-style re-execute) let the
|
|
@@ -20074,6 +20688,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20074
20688
|
const meta = record.prepared.meta;
|
|
20075
20689
|
const model = {
|
|
20076
20690
|
id,
|
|
20691
|
+
// Deliberately the FULL title, never a shorttitle: while the script loads the
|
|
20692
|
+
// legend identifies it by its full name; the compact shorttitle arrives with
|
|
20693
|
+
// the first computed model and takes over from there.
|
|
20077
20694
|
title: record.options?.title ?? meta.title,
|
|
20078
20695
|
overlay: meta.overlay,
|
|
20079
20696
|
paneHint: meta.overlay ? "price" : "new",
|
|
@@ -20082,7 +20699,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20082
20699
|
backgrounds: [],
|
|
20083
20700
|
priceLines: [],
|
|
20084
20701
|
inputs: record.prepared.inputs,
|
|
20085
|
-
inputValues: record.inputValues
|
|
20702
|
+
inputValues: record.inputValues,
|
|
20703
|
+
...record.prepared.props ? { props: record.prepared.props, propValues: record.propValues } : {}
|
|
20086
20704
|
};
|
|
20087
20705
|
const paneId = this.routePane(id, model, record.options ?? {});
|
|
20088
20706
|
this.placeModel(model, id, paneId);
|
|
@@ -20315,7 +20933,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20315
20933
|
record.model = model;
|
|
20316
20934
|
if (record.pendingStructural) {
|
|
20317
20935
|
record.renderHandle = this.renderer.mountIndicator(model);
|
|
20318
|
-
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues);
|
|
20936
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20319
20937
|
record.pendingStructural = false;
|
|
20320
20938
|
} else {
|
|
20321
20939
|
this.renderer.updateIndicator(record.renderHandle, modelToValuePatch(model));
|
|
@@ -20342,16 +20960,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20342
20960
|
placeModel(model, id, paneId) {
|
|
20343
20961
|
model.id = id;
|
|
20344
20962
|
model.paneId = paneId;
|
|
20345
|
-
|
|
20346
|
-
for (const
|
|
20347
|
-
for (const
|
|
20963
|
+
const paneOf = (item) => item.overlay === true ? "price" : paneId;
|
|
20964
|
+
for (const series of model.series) series.paneId = paneOf(series);
|
|
20965
|
+
for (const fill of model.fills) fill.paneId = paneOf(fill);
|
|
20966
|
+
for (const bg of model.backgrounds) bg.paneId = paneOf(bg);
|
|
20348
20967
|
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 =
|
|
20968
|
+
if (model.lines) for (const ln of model.lines) ln.paneId = paneOf(ln);
|
|
20969
|
+
if (model.boxes) for (const bx of model.boxes) bx.paneId = paneOf(bx);
|
|
20970
|
+
if (model.labels) for (const lb of model.labels) lb.paneId = paneOf(lb);
|
|
20971
|
+
if (model.polylines) for (const pl of model.polylines) pl.paneId = paneOf(pl);
|
|
20972
|
+
if (model.linefills) for (const lf of model.linefills) lf.paneId = paneOf(lf);
|
|
20973
|
+
if (model.tables) for (const tb of model.tables) tb.paneId = paneOf(tb);
|
|
20355
20974
|
}
|
|
20356
20975
|
emitContextChanged(id) {
|
|
20357
20976
|
if (!this.registry.get(id)?.session?.getContext) return;
|
|
@@ -21011,4 +21630,4 @@ function resolveElement(container) {
|
|
|
21011
21630
|
return element;
|
|
21012
21631
|
}
|
|
21013
21632
|
|
|
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 };
|
|
21633
|
+
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, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isGroupRow, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs, tzButtonLabel, tzMenuLabel, tzOffset };
|