@luxalgo/vela 0.6.3 → 0.6.5
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-DHX4x6-r.d.ts → DataProvider-BmHkcwvJ.d.ts} +1 -1
- package/dist/{DataProvider-CNmk84SH.d.cts → DataProvider-DrN7ZIou.d.cts} +1 -1
- package/dist/{chunk-TP56QRMK.js → chunk-2R7BANEM.js} +1 -1
- package/dist/{chunk-7Y7CJ7EN.js → chunk-BEJ57HP4.js} +8 -1
- package/dist/{chunk-7D6YIF34.js → chunk-FQBT4NFM.js} +431 -74
- package/dist/{chunk-U5KCQHAC.js → chunk-N6MBJHMV.js} +129 -41
- package/dist/{chunk-QHZ7IXFL.js → chunk-OICPLNU7.js} +59 -9
- package/dist/{contributions-DDpv7hEL.d.cts → contributions-2bEmCF9S.d.cts} +2 -2
- package/dist/{contributions-ciV6Neyd.d.ts → contributions-DatoAzMx.d.ts} +2 -2
- package/dist/{history-CFqZm5re.d.ts → history-BmyjaVir.d.cts} +6 -6
- package/dist/{history-ChoT34nz.d.cts → history-CvOtsjT5.d.ts} +6 -6
- package/dist/index.cjs +500 -85
- package/dist/index.d.cts +63 -9
- package/dist/index.d.ts +63 -9
- package/dist/index.js +4 -4
- package/dist/{options-BqGeFHtp.d.ts → options-YdiaaVjt.d.cts} +20 -1
- package/dist/{options-BqGeFHtp.d.cts → options-YdiaaVjt.d.ts} +20 -1
- package/dist/{plugin-C97gUCVf.d.ts → plugin-Bcone6Vh.d.ts} +9 -5
- package/dist/{plugin-CSb_sTiN.d.cts → plugin-DO_DX0gp.d.cts} +9 -5
- package/dist/plugin.cjs +8 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/ui.cjs +59 -9
- package/dist/ui.d.cts +10 -2
- package/dist/ui.d.ts +10 -2
- package/dist/ui.js +2 -2
- package/dist/vela.global.js +500 -85
- package/dist/vela.global.min.js +84 -47
- package/dist/widget.cjs +801 -227
- package/dist/widget.d.cts +34 -8
- package/dist/widget.d.ts +34 -8
- package/dist/widget.js +145 -73
- package/dist/workspace.cjs +756 -178
- package/dist/workspace.d.cts +29 -5
- package/dist/workspace.d.ts +29 -5
- package/dist/workspace.js +143 -67
- package/package.json +1 -1
- /package/dist/{chunk-Q3XQHLIH.js → chunk-AOGZBKUE.js} +0 -0
|
@@ -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, 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, NEUTRAL, BEARISH, BULLISH, isDarkColor,
|
|
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-BEJ57HP4.js';
|
|
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-OICPLNU7.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-PN5KWFZ4.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
6
6
|
var TypedEventBus = class {
|
|
@@ -2184,7 +2184,7 @@ function attachChromeTooltip(anchor, opts) {
|
|
|
2184
2184
|
} else {
|
|
2185
2185
|
const left = Math.min(a.left - h.left, Math.max(0, h.width - tip.offsetWidth - 4));
|
|
2186
2186
|
tip.style.left = `${Math.max(0, left)}px`;
|
|
2187
|
-
tip.style.top = `${a.bottom - h.top + 6}px`;
|
|
2187
|
+
tip.style.top = opts.placement === "above" ? `${Math.max(0, a.top - h.top - tip.offsetHeight - 6)}px` : `${a.bottom - h.top + 6}px`;
|
|
2188
2188
|
}
|
|
2189
2189
|
};
|
|
2190
2190
|
const arm = (e) => {
|
|
@@ -2844,6 +2844,8 @@ var IndicatorInputsDialog = class {
|
|
|
2844
2844
|
this.row = null;
|
|
2845
2845
|
this.snapshot = null;
|
|
2846
2846
|
this.dialogTips = [];
|
|
2847
|
+
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
2848
|
+
this.refreshVisibility = null;
|
|
2847
2849
|
this.choicePop = null;
|
|
2848
2850
|
this.calendarPop = null;
|
|
2849
2851
|
this.calendarAnchor = null;
|
|
@@ -2906,14 +2908,17 @@ var IndicatorInputsDialog = class {
|
|
|
2906
2908
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
2907
2909
|
const tabEls = [];
|
|
2908
2910
|
const bodies = [];
|
|
2911
|
+
const gates = [];
|
|
2912
|
+
let active = 0;
|
|
2909
2913
|
const activate = (idx) => {
|
|
2914
|
+
active = idx;
|
|
2910
2915
|
for (let i = 0; i < tabEls.length; i += 1) {
|
|
2911
|
-
const
|
|
2916
|
+
const on = i === idx;
|
|
2912
2917
|
const el = tabEls[i];
|
|
2913
|
-
el.style.borderBottomColor =
|
|
2914
|
-
el.style.color =
|
|
2915
|
-
el.classList.toggle("vela-ind-tab-active",
|
|
2916
|
-
bodies[i].style.display =
|
|
2918
|
+
el.style.borderBottomColor = on ? fg : "transparent";
|
|
2919
|
+
el.style.color = on ? "inherit" : "var(--vela-fg-muted)";
|
|
2920
|
+
el.classList.toggle("vela-ind-tab-active", on);
|
|
2921
|
+
bodies[i].style.display = on ? "grid" : "none";
|
|
2917
2922
|
}
|
|
2918
2923
|
};
|
|
2919
2924
|
for (const [idx, def] of tabDefs.entries()) {
|
|
@@ -2924,11 +2929,24 @@ var IndicatorInputsDialog = class {
|
|
|
2924
2929
|
tab.addEventListener("click", () => activate(idx));
|
|
2925
2930
|
tabs.appendChild(tab);
|
|
2926
2931
|
tabEls.push(tab);
|
|
2927
|
-
bodies.push(this.buildTabBody(def.inputs, row));
|
|
2932
|
+
bodies.push(this.buildTabBody(def.inputs, row, gates));
|
|
2933
|
+
if (def.inputs.every((d) => d.when)) {
|
|
2934
|
+
gates.push({ el: tab, visible: (bag) => def.inputs.some((d) => inputVisible(d.when, bag)) });
|
|
2935
|
+
}
|
|
2928
2936
|
}
|
|
2929
2937
|
ui.body.appendChild(tabs);
|
|
2930
2938
|
for (const b of bodies) ui.body.appendChild(b);
|
|
2931
2939
|
activate(0);
|
|
2940
|
+
const refresh = () => {
|
|
2941
|
+
const bag = valuesBag(row);
|
|
2942
|
+
for (const g of gates) g.el.style.display = g.visible(bag) ? "" : "none";
|
|
2943
|
+
if (tabEls[active]?.style.display === "none") {
|
|
2944
|
+
const next = tabEls.findIndex((el) => el.style.display !== "none");
|
|
2945
|
+
if (next >= 0) activate(next);
|
|
2946
|
+
}
|
|
2947
|
+
};
|
|
2948
|
+
this.refreshVisibility = refresh;
|
|
2949
|
+
refresh();
|
|
2932
2950
|
const onBackdropDown = (e) => {
|
|
2933
2951
|
if (e.target !== ui.backdrop && e.target !== ui.positioner) return;
|
|
2934
2952
|
if (isPopoverOpen() || eventDismissedPopover(e)) {
|
|
@@ -2948,16 +2966,27 @@ var IndicatorInputsDialog = class {
|
|
|
2948
2966
|
}
|
|
2949
2967
|
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
2950
2968
|
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
2951
|
-
buildTabBody(inputs, row) {
|
|
2969
|
+
buildTabBody(inputs, row, gates) {
|
|
2952
2970
|
const body = document.createElement("div");
|
|
2953
2971
|
body.style.cssText = inputDialogBodyStyle(this.host.mobile());
|
|
2954
2972
|
for (const group of groupInputs(inputs)) {
|
|
2955
2973
|
const section = document.createElement("div");
|
|
2956
2974
|
section.style.cssText = "display:contents;";
|
|
2975
|
+
let header = null;
|
|
2957
2976
|
if (group.name) {
|
|
2958
|
-
|
|
2977
|
+
header = fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 });
|
|
2978
|
+
section.appendChild(header);
|
|
2979
|
+
}
|
|
2980
|
+
for (const inputRow of group.rows) {
|
|
2981
|
+
const el = this.buildInputRow(section, inputRow, row);
|
|
2982
|
+
if (inputRow.some((d) => d.when)) {
|
|
2983
|
+
gates.push({ el, visible: (bag) => rowVisible(inputRow, bag) });
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
if (header && group.rows.every((r) => r.every((d) => d.when))) {
|
|
2987
|
+
const rows = group.rows;
|
|
2988
|
+
gates.push({ el: header, visible: (bag) => rows.some((r) => rowVisible(r, bag)) });
|
|
2959
2989
|
}
|
|
2960
|
-
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
2961
2990
|
body.appendChild(section);
|
|
2962
2991
|
}
|
|
2963
2992
|
return body;
|
|
@@ -2965,6 +2994,7 @@ var IndicatorInputsDialog = class {
|
|
|
2965
2994
|
close() {
|
|
2966
2995
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
2967
2996
|
closeOpenPopovers();
|
|
2997
|
+
this.refreshVisibility = null;
|
|
2968
2998
|
this.choicePop = null;
|
|
2969
2999
|
this.calendarPop = null;
|
|
2970
3000
|
this.calendarAnchor = null;
|
|
@@ -2994,11 +3024,21 @@ var IndicatorInputsDialog = class {
|
|
|
2994
3024
|
}
|
|
2995
3025
|
this.close();
|
|
2996
3026
|
}
|
|
3027
|
+
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
3028
|
+
commit(row, key, value) {
|
|
3029
|
+
row.values[key] = value;
|
|
3030
|
+
this.host.onChange?.({ indicatorId: row.id, key, value });
|
|
3031
|
+
this.refreshVisibility?.();
|
|
3032
|
+
}
|
|
2997
3033
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
2998
3034
|
buildInputRow(grid, decls, row) {
|
|
2999
3035
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
3036
|
+
const append = (el) => {
|
|
3037
|
+
grid.appendChild(el);
|
|
3038
|
+
return el;
|
|
3039
|
+
};
|
|
3000
3040
|
if (decls.length > 1) {
|
|
3001
|
-
|
|
3041
|
+
return append(fieldRow({
|
|
3002
3042
|
label: "",
|
|
3003
3043
|
inline: decls.map((d) => ({
|
|
3004
3044
|
label: nameOf(d) || void 0,
|
|
@@ -3009,14 +3049,13 @@ var IndicatorInputsDialog = class {
|
|
|
3009
3049
|
})),
|
|
3010
3050
|
info: [...decls].reverse().find((d) => d.tooltip)?.tooltip ? this.infoButton([...decls].reverse().find((d) => d.tooltip).tooltip) : void 0
|
|
3011
3051
|
}));
|
|
3012
|
-
return;
|
|
3013
3052
|
}
|
|
3014
3053
|
const lead = decls[0];
|
|
3015
3054
|
const id = idOf(lead);
|
|
3016
3055
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
3017
3056
|
if (lead.type === "bool") {
|
|
3018
3057
|
const current = Boolean(row.values[lead.key] ?? lead.defval);
|
|
3019
|
-
|
|
3058
|
+
return append(fieldRow({
|
|
3020
3059
|
label: nameOf(lead),
|
|
3021
3060
|
id,
|
|
3022
3061
|
bool: true,
|
|
@@ -3024,26 +3063,21 @@ var IndicatorInputsDialog = class {
|
|
|
3024
3063
|
toggle: {
|
|
3025
3064
|
id,
|
|
3026
3065
|
checked: current,
|
|
3027
|
-
onChange: (v) =>
|
|
3028
|
-
row.values[lead.key] = v;
|
|
3029
|
-
this.host.onChange?.({ indicatorId: row.id, key: lead.key, value: v });
|
|
3030
|
-
}
|
|
3066
|
+
onChange: (v) => this.commit(row, lead.key, v)
|
|
3031
3067
|
}
|
|
3032
3068
|
}));
|
|
3033
|
-
return;
|
|
3034
3069
|
}
|
|
3035
3070
|
if (lead.type === "text_area") {
|
|
3036
|
-
|
|
3071
|
+
return append(fieldRow({
|
|
3037
3072
|
label: nameOf(lead),
|
|
3038
3073
|
id,
|
|
3039
3074
|
stacked: true,
|
|
3040
3075
|
info,
|
|
3041
3076
|
control: this.buildControl(row, lead, id)
|
|
3042
3077
|
}));
|
|
3043
|
-
return;
|
|
3044
3078
|
}
|
|
3045
3079
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
3046
|
-
|
|
3080
|
+
return append(fieldRow({
|
|
3047
3081
|
label: nameOf(lead),
|
|
3048
3082
|
id,
|
|
3049
3083
|
fit,
|
|
@@ -3055,10 +3089,7 @@ var IndicatorInputsDialog = class {
|
|
|
3055
3089
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
3056
3090
|
buildControl(row, inp, id) {
|
|
3057
3091
|
const current = row.values[inp.key] ?? inp.defval;
|
|
3058
|
-
const emit = (value) =>
|
|
3059
|
-
row.values[inp.key] = value;
|
|
3060
|
-
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value });
|
|
3061
|
-
};
|
|
3092
|
+
const emit = (value) => this.commit(row, inp.key, value);
|
|
3062
3093
|
if (inp.type === "bool") {
|
|
3063
3094
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
3064
3095
|
}
|
|
@@ -3479,6 +3510,14 @@ function tabInputs(inputs) {
|
|
|
3479
3510
|
}
|
|
3480
3511
|
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
3481
3512
|
}
|
|
3513
|
+
function rowVisible(decls, bag) {
|
|
3514
|
+
return decls.some((d) => inputVisible(d.when, bag));
|
|
3515
|
+
}
|
|
3516
|
+
function valuesBag(row) {
|
|
3517
|
+
const bag = {};
|
|
3518
|
+
for (const inp of row.inputs) bag[inp.key] = row.values[inp.key] ?? inp.defval;
|
|
3519
|
+
return bag;
|
|
3520
|
+
}
|
|
3482
3521
|
function groupInputs(inputs) {
|
|
3483
3522
|
const order = [];
|
|
3484
3523
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -3941,6 +3980,8 @@ var InputsUI = class {
|
|
|
3941
3980
|
host: this.container,
|
|
3942
3981
|
items,
|
|
3943
3982
|
placement: "bottom-start",
|
|
3983
|
+
// Right-click action menu — checked state reads as a leading ✓ (see the kit Menu).
|
|
3984
|
+
checkmarks: true,
|
|
3944
3985
|
onSelect: (itemId) => {
|
|
3945
3986
|
if (itemId !== "values" || !this.rowMenuId) return;
|
|
3946
3987
|
const target = this.rows.get(this.rowMenuId);
|
|
@@ -4276,6 +4317,8 @@ var InputsUI = class {
|
|
|
4276
4317
|
syncRowActions(row) {
|
|
4277
4318
|
const open = row.highlighted;
|
|
4278
4319
|
row.controlsEl.style.display = open || row.hidden ? "inline-flex" : "none";
|
|
4320
|
+
if (open) row.el.appendChild(row.statusEl);
|
|
4321
|
+
else row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
4279
4322
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
4280
4323
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
4281
4324
|
if (child === row.extrasEl) {
|
|
@@ -4519,6 +4562,148 @@ var PaneControls = class {
|
|
|
4519
4562
|
}
|
|
4520
4563
|
};
|
|
4521
4564
|
|
|
4565
|
+
// src/renderers/native/chrome/axisLayout.ts
|
|
4566
|
+
var AXIS_MASTER_W = 64;
|
|
4567
|
+
var AXIS_MERGED_W = 56;
|
|
4568
|
+
var PANE_SEPARATOR_PX = 3;
|
|
4569
|
+
function axisColumnX(dataW, column) {
|
|
4570
|
+
return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
// src/renderers/native/chrome/AxisScaleButtons.ts
|
|
4574
|
+
var STYLE_ID3 = "vela-axis-scale-buttons";
|
|
4575
|
+
var BTN_PX = 18;
|
|
4576
|
+
var BTN_GAP = 4;
|
|
4577
|
+
var AXIS_BORDER = 1;
|
|
4578
|
+
var CLUSTER_W = AXIS_MASTER_W - AXIS_BORDER;
|
|
4579
|
+
var PAD_TOP = 8;
|
|
4580
|
+
var PAD_BOTTOM = 6;
|
|
4581
|
+
var CLUSTER_H = BTN_PX + PAD_TOP + PAD_BOTTOM;
|
|
4582
|
+
var MIN_PANE_H = 48;
|
|
4583
|
+
function ensureStyles3() {
|
|
4584
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID3)) return;
|
|
4585
|
+
const st = document.createElement("style");
|
|
4586
|
+
st.id = STYLE_ID3;
|
|
4587
|
+
st.textContent = `
|
|
4588
|
+
.vela-axis-btn{display:inline-flex;align-items:center;justify-content:center;padding:0;border:none;border-radius:var(--vela-radius-sm);background:transparent;cursor:pointer;font:600 10px/1 -apple-system,Segoe UI,sans-serif;color:var(--vela-fg-muted);}
|
|
4589
|
+
.vela-axis-btn:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
4590
|
+
.vela-axis-on,.vela-axis-on:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
4591
|
+
`;
|
|
4592
|
+
document.head.appendChild(st);
|
|
4593
|
+
}
|
|
4594
|
+
var AxisScaleButtons = class {
|
|
4595
|
+
constructor(plot, theme, deps) {
|
|
4596
|
+
this.plot = plot;
|
|
4597
|
+
this.theme = theme;
|
|
4598
|
+
this.deps = deps;
|
|
4599
|
+
this.tipDisposers = [];
|
|
4600
|
+
this.hoverPaneId = null;
|
|
4601
|
+
/** Reveal over the master scale column of the pane under the cursor (mouse only —
|
|
4602
|
+
* touch has no hover, and a drag-rescale on the axis must not sprout buttons). */
|
|
4603
|
+
this.onPlotMove = (e) => {
|
|
4604
|
+
if (e.pointerType !== "mouse") return;
|
|
4605
|
+
const rect = this.plot.getBoundingClientRect();
|
|
4606
|
+
const x = e.clientX - rect.left;
|
|
4607
|
+
const gutterLeft = rect.width - this.deps.rightAxis();
|
|
4608
|
+
let hit = null;
|
|
4609
|
+
if (x >= gutterLeft && x <= gutterLeft + AXIS_MASTER_W) {
|
|
4610
|
+
const y = e.clientY - rect.top;
|
|
4611
|
+
for (const p of this.deps.panes()) {
|
|
4612
|
+
if (p.collapsed || p.height < MIN_PANE_H) continue;
|
|
4613
|
+
if (y >= p.top && y <= p.top + p.height) {
|
|
4614
|
+
hit = p.id;
|
|
4615
|
+
break;
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
this.setHoverPane(hit);
|
|
4620
|
+
if (hit) this.sync();
|
|
4621
|
+
};
|
|
4622
|
+
this.onPlotLeave = () => this.setHoverPane(null);
|
|
4623
|
+
ensureStyles3();
|
|
4624
|
+
this.cluster = document.createElement("div");
|
|
4625
|
+
Object.assign(this.cluster.style, {
|
|
4626
|
+
position: "absolute",
|
|
4627
|
+
display: "none",
|
|
4628
|
+
justifyContent: "center",
|
|
4629
|
+
alignItems: "center",
|
|
4630
|
+
gap: `${BTN_GAP}px`,
|
|
4631
|
+
padding: `${PAD_TOP}px 0 ${PAD_BOTTOM}px`,
|
|
4632
|
+
width: `${CLUSTER_W}px`,
|
|
4633
|
+
boxSizing: "border-box",
|
|
4634
|
+
zIndex: "6",
|
|
4635
|
+
pointerEvents: "auto"
|
|
4636
|
+
});
|
|
4637
|
+
this.applyTheme();
|
|
4638
|
+
this.autoBtn = this.button("A", "Auto (fits data to screen)", () => this.deps.onToggleAuto(this.hoverPaneId ?? ""));
|
|
4639
|
+
this.logBtn = this.button("L", "Logarithmic scale", () => this.deps.onToggleLog(this.hoverPaneId ?? ""));
|
|
4640
|
+
this.cluster.append(this.autoBtn, this.logBtn);
|
|
4641
|
+
this.plot.appendChild(this.cluster);
|
|
4642
|
+
this.plot.addEventListener("pointermove", this.onPlotMove);
|
|
4643
|
+
this.plot.addEventListener("pointerleave", this.onPlotLeave);
|
|
4644
|
+
}
|
|
4645
|
+
button(label, title, onClick) {
|
|
4646
|
+
const b = document.createElement("button");
|
|
4647
|
+
b.type = "button";
|
|
4648
|
+
b.setAttribute("aria-label", title);
|
|
4649
|
+
b.textContent = label;
|
|
4650
|
+
b.className = "vela-axis-btn";
|
|
4651
|
+
Object.assign(b.style, { width: `${BTN_PX}px`, height: `${BTN_PX}px` });
|
|
4652
|
+
this.tipDisposers.push(attachChromeTooltip(b, {
|
|
4653
|
+
host: this.plot,
|
|
4654
|
+
theme: () => this.theme,
|
|
4655
|
+
text: () => title,
|
|
4656
|
+
placement: "above"
|
|
4657
|
+
}));
|
|
4658
|
+
b.addEventListener("click", (e) => {
|
|
4659
|
+
e.stopPropagation();
|
|
4660
|
+
onClick();
|
|
4661
|
+
this.sync();
|
|
4662
|
+
});
|
|
4663
|
+
return b;
|
|
4664
|
+
}
|
|
4665
|
+
/** Re-tint the chip when the plot background/border change (theme swap, config edit). */
|
|
4666
|
+
setTheme(theme) {
|
|
4667
|
+
this.theme = theme;
|
|
4668
|
+
this.applyTheme();
|
|
4669
|
+
}
|
|
4670
|
+
/** The strip is SOLID chart background: the buttons cover axis labels, never blend with them. */
|
|
4671
|
+
applyTheme() {
|
|
4672
|
+
this.cluster.style.background = this.theme.background;
|
|
4673
|
+
}
|
|
4674
|
+
setHoverPane(paneId) {
|
|
4675
|
+
if (this.hoverPaneId === paneId) return;
|
|
4676
|
+
this.hoverPaneId = paneId;
|
|
4677
|
+
this.reposition();
|
|
4678
|
+
}
|
|
4679
|
+
/** Re-place (or hide) the cluster after a hover or pane-layout change. */
|
|
4680
|
+
reposition() {
|
|
4681
|
+
const pane = this.hoverPaneId ? this.deps.panes().find((p) => p.id === this.hoverPaneId) : void 0;
|
|
4682
|
+
if (!pane || pane.collapsed || pane.height < MIN_PANE_H) {
|
|
4683
|
+
this.cluster.style.display = "none";
|
|
4684
|
+
return;
|
|
4685
|
+
}
|
|
4686
|
+
this.cluster.style.right = `${this.deps.rightAxis() - AXIS_MASTER_W}px`;
|
|
4687
|
+
this.cluster.style.top = `${pane.top + pane.height - CLUSTER_H}px`;
|
|
4688
|
+
this.cluster.style.display = "flex";
|
|
4689
|
+
this.sync();
|
|
4690
|
+
}
|
|
4691
|
+
/** Reflect the hovered pane's current auto/log state on the buttons. */
|
|
4692
|
+
sync() {
|
|
4693
|
+
const pane = this.deps.panes().find((p) => p.id === this.hoverPaneId);
|
|
4694
|
+
if (!pane) return;
|
|
4695
|
+
this.autoBtn.classList.toggle("vela-axis-on", pane.auto);
|
|
4696
|
+
this.logBtn.classList.toggle("vela-axis-on", pane.log);
|
|
4697
|
+
}
|
|
4698
|
+
destroy() {
|
|
4699
|
+
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
4700
|
+
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
4701
|
+
for (const dispose of this.tipDisposers) dispose();
|
|
4702
|
+
this.tipDisposers.length = 0;
|
|
4703
|
+
this.cluster.remove();
|
|
4704
|
+
}
|
|
4705
|
+
};
|
|
4706
|
+
|
|
4522
4707
|
// src/renderers/shared/TableOverlay.ts
|
|
4523
4708
|
var SIZE_PX = {
|
|
4524
4709
|
auto: 13,
|
|
@@ -4876,6 +5061,8 @@ var BASELINE_BOTTOM_LINE = BEARISH;
|
|
|
4876
5061
|
var BASELINE_FILL_ALPHA = 0.25;
|
|
4877
5062
|
var BASELINE_FILL_ALPHA_FAR = 0.05;
|
|
4878
5063
|
var BASELINE_LEVEL_DEFAULT = 50;
|
|
5064
|
+
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
5065
|
+
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
4879
5066
|
function defaultChartStyle() {
|
|
4880
5067
|
return {
|
|
4881
5068
|
chartTypes: {},
|
|
@@ -4906,7 +5093,8 @@ function defaultChartStyle() {
|
|
|
4906
5093
|
bottomFillColor2: null,
|
|
4907
5094
|
width: 2,
|
|
4908
5095
|
baselineLevel: BASELINE_LEVEL_DEFAULT
|
|
4909
|
-
}
|
|
5096
|
+
},
|
|
5097
|
+
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
|
|
4910
5098
|
};
|
|
4911
5099
|
}
|
|
4912
5100
|
var LINE_STYLES = ["solid", "dashed", "dotted"];
|
|
@@ -5040,6 +5228,7 @@ function mergeConfig(base, patch) {
|
|
|
5040
5228
|
const area = asObject(p.area);
|
|
5041
5229
|
const baseline = asObject(p.baseline);
|
|
5042
5230
|
const series = asObject(p.series);
|
|
5231
|
+
const sessions = asObject(p.sessions);
|
|
5043
5232
|
const stacking = asObject(p.stacking);
|
|
5044
5233
|
const stackSeries = asObject(stacking.series);
|
|
5045
5234
|
return {
|
|
@@ -5132,6 +5321,10 @@ function mergeConfig(base, patch) {
|
|
|
5132
5321
|
baseline: series.baseline === null ? null : isNum(series.baseline) ? series.baseline : base.series.baseline,
|
|
5133
5322
|
spacing: isNum(series.spacing) ? clampSpacing(series.spacing) : base.series.spacing
|
|
5134
5323
|
},
|
|
5324
|
+
sessions: {
|
|
5325
|
+
premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
|
|
5326
|
+
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
|
|
5327
|
+
},
|
|
5135
5328
|
stacking: {
|
|
5136
5329
|
candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
|
|
5137
5330
|
// Additive like `chartTypes`: a patch names only the ids it carries; the rest keep
|
|
@@ -5378,6 +5571,8 @@ var SceneGraph = class {
|
|
|
5378
5571
|
this.tradeMarkers = defaultTradeMarkersState();
|
|
5379
5572
|
/** Renderer-owned shaded time bands (session highlighting), behind grid + data. */
|
|
5380
5573
|
this.highlights = [];
|
|
5574
|
+
/** Pre/post-market bands pushed by the host (`sessionZones` feature); null ⇒ no sessions. */
|
|
5575
|
+
this.sessionZones = null;
|
|
5381
5576
|
/** Draw-order key of the price candles, relative to indicator series z (see `seriesZ`).
|
|
5382
5577
|
* Indicators with z below this draw BEHIND the candles; at/above draw in front.
|
|
5383
5578
|
* Default 0 with indicators mounting at z < 0 ⇒ the price reads on top of every overlay,
|
|
@@ -5412,6 +5607,15 @@ var SceneGraph = class {
|
|
|
5412
5607
|
if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
|
|
5413
5608
|
return this.orderedCache;
|
|
5414
5609
|
}
|
|
5610
|
+
/** The session zones resolved into colored bands (pre/post-market washes from the
|
|
5611
|
+
* config's session colors) — consumed by the same painting path as {@link highlights}. */
|
|
5612
|
+
sessionHighlightBands() {
|
|
5613
|
+
if (!this.sessionZones) return [];
|
|
5614
|
+
const out = [];
|
|
5615
|
+
for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
|
|
5616
|
+
for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
|
|
5617
|
+
return out;
|
|
5618
|
+
}
|
|
5415
5619
|
indicatorsForPane(paneId) {
|
|
5416
5620
|
const out = [];
|
|
5417
5621
|
for (const model of this.indicators.values()) if (model.paneId === paneId) out.push(model);
|
|
@@ -5495,6 +5699,12 @@ var SceneGraph = class {
|
|
|
5495
5699
|
assignIndicatorZ(id) {
|
|
5496
5700
|
if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.bottomZ() - 1);
|
|
5497
5701
|
}
|
|
5702
|
+
/** Mount-time default for a LAYER-BACKED native (it paints on a canvas stacked above the
|
|
5703
|
+
* data canvas by default): top of the stack, so the recorded order tells the truth from
|
|
5704
|
+
* the first frame. Keeps an existing key, so a restored stack survives the remount. */
|
|
5705
|
+
assignIndicatorZTop(id) {
|
|
5706
|
+
if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.topZ() + 1);
|
|
5707
|
+
}
|
|
5498
5708
|
forgetIndicatorZ(id) {
|
|
5499
5709
|
this.seriesZ.delete(id);
|
|
5500
5710
|
}
|
|
@@ -6342,10 +6552,12 @@ var WebGL2Backend = class {
|
|
|
6342
6552
|
}
|
|
6343
6553
|
}
|
|
6344
6554
|
}
|
|
6345
|
-
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
6555
|
+
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
6556
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
6346
6557
|
emitHighlights(b, scene, pane, coords) {
|
|
6347
|
-
|
|
6348
|
-
|
|
6558
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
6559
|
+
if (bands.length === 0) return;
|
|
6560
|
+
for (const band of bands) {
|
|
6349
6561
|
const x1 = coords.timeToX(band.from);
|
|
6350
6562
|
const x2 = coords.timeToX(band.to);
|
|
6351
6563
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -8319,10 +8531,12 @@ var Canvas2dBackend = class {
|
|
|
8319
8531
|
ctx.fillStyle = bg.color;
|
|
8320
8532
|
ctx.fillRect(x1, pane.bounds.top, x2 - x1, pane.bounds.height);
|
|
8321
8533
|
}
|
|
8322
|
-
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
8534
|
+
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
8535
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
8323
8536
|
drawHighlights(ctx, scene, coords) {
|
|
8324
|
-
|
|
8325
|
-
|
|
8537
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
8538
|
+
if (bands.length === 0) return;
|
|
8539
|
+
for (const band of bands) {
|
|
8326
8540
|
const x1 = coords.timeToX(band.from);
|
|
8327
8541
|
const x2 = coords.timeToX(band.to);
|
|
8328
8542
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -9096,14 +9310,6 @@ var DrawingSceneRenderer = class {
|
|
|
9096
9310
|
}
|
|
9097
9311
|
};
|
|
9098
9312
|
|
|
9099
|
-
// src/renderers/native/chrome/axisLayout.ts
|
|
9100
|
-
var AXIS_MASTER_W = 64;
|
|
9101
|
-
var AXIS_MERGED_W = 56;
|
|
9102
|
-
var PANE_SEPARATOR_PX = 3;
|
|
9103
|
-
function axisColumnX(dataW, column) {
|
|
9104
|
-
return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
|
|
9105
|
-
}
|
|
9106
|
-
|
|
9107
9313
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
9108
9314
|
var ChromeRenderer = class {
|
|
9109
9315
|
constructor() {
|
|
@@ -9909,6 +10115,7 @@ var SettingsDialog = class {
|
|
|
9909
10115
|
for (const hr of hs.rows) {
|
|
9910
10116
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
9911
10117
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
10118
|
+
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
9912
10119
|
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options.map((o) => [o, o]), (v) => hr.set(v)));
|
|
9913
10120
|
}
|
|
9914
10121
|
}
|
|
@@ -12620,14 +12827,14 @@ function btn(label, primary, onClick) {
|
|
|
12620
12827
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
12621
12828
|
var BTN = 30;
|
|
12622
12829
|
var ICON = 21;
|
|
12623
|
-
var
|
|
12830
|
+
var STYLE_ID4 = "vela-drawing-popup-styles";
|
|
12624
12831
|
var STYLE_REV = "3";
|
|
12625
|
-
function
|
|
12832
|
+
function ensureStyles4() {
|
|
12626
12833
|
if (typeof document === "undefined") return;
|
|
12627
|
-
const existing = document.getElementById(
|
|
12834
|
+
const existing = document.getElementById(STYLE_ID4);
|
|
12628
12835
|
if (existing?.dataset.rev === STYLE_REV) return;
|
|
12629
12836
|
const s = existing ?? document.createElement("style");
|
|
12630
|
-
s.id =
|
|
12837
|
+
s.id = STYLE_ID4;
|
|
12631
12838
|
s.dataset.rev = STYLE_REV;
|
|
12632
12839
|
s.textContent = `
|
|
12633
12840
|
.vela-dpop-btn{background:transparent;color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
@@ -12684,7 +12891,7 @@ var DrawingSettingsPopup = class {
|
|
|
12684
12891
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
12685
12892
|
open(drawing, anchor, actions, onClose) {
|
|
12686
12893
|
this.close();
|
|
12687
|
-
|
|
12894
|
+
ensureStyles4();
|
|
12688
12895
|
this.onClose = onClose ?? null;
|
|
12689
12896
|
const t = this.theme;
|
|
12690
12897
|
const schema = drawing.schema();
|
|
@@ -14516,13 +14723,26 @@ var VolumeRenderer = class {
|
|
|
14516
14723
|
}
|
|
14517
14724
|
};
|
|
14518
14725
|
|
|
14726
|
+
// src/renderers/native/core/layerStacking.ts
|
|
14727
|
+
function stackLayers(entries, candleZ) {
|
|
14728
|
+
const keyed = entries.map((e) => ({
|
|
14729
|
+
id: e.id,
|
|
14730
|
+
key: e.ownerZ ?? (e.placement === "below-data" ? -Infinity : candleZ)
|
|
14731
|
+
}));
|
|
14732
|
+
keyed.sort((a, b) => a.key - b.key);
|
|
14733
|
+
const below = [];
|
|
14734
|
+
const above = [];
|
|
14735
|
+
for (const k of keyed) (k.key < candleZ ? below : above).push(k.id);
|
|
14736
|
+
return { below, above };
|
|
14737
|
+
}
|
|
14738
|
+
|
|
14519
14739
|
// src/renderers/native/chrome/luxalgo-logos.ts
|
|
14520
14740
|
var LUXALGO_SYMBOL_SVG = '<svg viewBox="0 0 45 40" aria-hidden="true"><g fill="currentColor"><path d="m40.25 38 4.58-7.998L28.802 2l-16.03 28 9.16-.001 6.87-12z"/><path d="M34.525 32.002 9.33 31.997 27.655 0h-9.158L.18 31.993 4.759 40h34.347z"/></g></svg>';
|
|
14521
14741
|
var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path fill="currentColor" fill-rule="evenodd" d="M 614 94.500 L 614 189 633.500 189 L 653 189 653 94.500 L 653 0 633.500 0 L 614 0 614 94.500 M 0 97.500 L 0 189 63.023 189 L 126.045 189 125.773 170.750 L 125.500 152.500 84.250 152.239 L 43 151.978 43 78.989 L 43 6 21.500 6 L 0 6 0 97.500 M 450.387 96.451 C 421.871 146.253, 398.306 186.994, 398.020 186.987 C 397.423 186.972, 362 124.724, 362 123.691 C 362 123.316, 370.100 108.818, 380 91.472 C 389.900 74.127, 398 59.500, 398 58.968 C 398 58.380, 391.007 58, 380.210 58 L 362.420 58 348.060 83.138 C 334.400 107.052, 333.637 108.160, 332.399 105.888 C 331.682 104.575, 325.287 93.375, 318.187 81 L 305.277 58.500 287.139 58.227 C 277.162 58.078, 269 58.186, 269 58.469 C 269 58.751, 277.154 73.274, 287.120 90.741 C 297.086 108.209, 305.261 122.983, 305.287 123.574 C 305.312 124.164, 297.318 138.789, 287.522 156.074 C 277.725 173.358, 269.524 187.838, 269.296 188.250 C 269.068 188.662, 276.977 189, 286.870 189 L 304.859 189 319.163 163.959 C 331.321 142.674, 333.647 139.165, 334.670 140.563 C 335.331 141.468, 341.864 152.737, 349.186 165.606 L 362.500 189.003 402.442 188.752 L 442.384 188.500 472.080 136.695 C 488.413 108.203, 501.939 84.729, 502.138 84.531 C 502.337 84.333, 516 107.757, 532.500 136.583 L 562.500 188.994 584.750 188.997 C 596.987 188.999, 607 188.854, 607 188.675 C 607 188.319, 503.014 6.686, 502.532 6.201 C 502.369 6.037, 478.904 46.649, 450.387 96.451 M 717.500 55.017 C 697.175 57.389, 680.641 68.752, 672.036 86.264 C 665.866 98.818, 663.840 108.024, 663.840 123.500 C 663.840 133.409, 664.328 138.355, 665.892 144.300 C 672.076 167.810, 689.191 184.757, 711.529 189.489 C 730.549 193.518, 747.264 189.540, 761.282 177.648 C 764.944 174.542, 768.178 172, 768.470 172 C 769.345 172, 769.030 185.018, 767.979 192.285 C 765.618 208.613, 755.148 218.672, 738.473 220.632 C 732.586 221.324, 723.136 219.599, 717.709 216.843 C 712.693 214.295, 706.636 207.946, 703.972 202.443 C 702.789 199.999, 701.749 198, 701.661 198.001 C 700.201 198.007, 665.596 205.071, 665.230 205.437 C 664.326 206.341, 669.156 218.313, 672.607 223.721 C 682.146 238.672, 700.008 248.257, 723.500 251.033 C 758.132 255.124, 788.930 241.875, 800.287 218 C 806.923 204.047, 807 203.001, 807 126.542 L 807 58 788.500 58 L 770 58 770 66.500 C 770 71.175, 769.641 75, 769.203 75 C 768.765 75, 766.627 73.142, 764.453 70.872 C 752.805 58.708, 735.614 52.903, 717.500 55.017 M 878.500 55.031 C 850.107 59.412, 830.755 75.064, 822.285 100.500 C 819.905 107.644, 819.614 110.051, 819.565 123 C 819.501 139.581, 820.786 145.825, 826.763 158 C 832.104 168.880, 842.842 179.424, 854.077 184.822 C 867.651 191.343, 873.132 192.462, 891.500 192.462 C 909.868 192.462, 915.349 191.343, 928.923 184.822 C 944.653 177.265, 956.667 162.306, 961.710 144 C 963.074 139.048, 963.481 134.047, 963.418 123 C 963.346 110.140, 963.038 107.595, 960.694 100.500 C 953.325 78.193, 936.695 62.854, 913.500 56.969 C 904.262 54.625, 887.240 53.682, 878.500 55.031 M 134.015 102.750 C 134.027 139.250, 134.310 148.689, 135.549 153.947 C 141.139 177.663, 159.815 193, 183.105 193 C 196.453 193, 210.163 186.171, 217.936 175.650 C 219.826 173.093, 221.738 171, 222.186 171 C 222.634 171, 223 175.050, 223 180 L 223 189 242.507 189 L 262.014 189 261.757 123.750 L 261.500 58.500 242.500 58.500 L 223.500 58.500 223 99.500 C 222.513 139.406, 222.441 140.610, 220.289 144.634 C 209.729 164.377, 183.014 164.880, 174.299 145.500 C 172.677 141.895, 172.469 137.382, 172.190 99.750 L 171.880 58 152.940 58 L 134 58 134.015 102.750 M 729.932 87.998 C 719.311 89.821, 710.155 97.164, 705.141 107.880 C 702.851 112.772, 702.510 114.798, 702.505 123.521 C 702.500 132.834, 702.732 134.014, 705.787 140.218 C 709.761 148.290, 715.989 154.097, 723.853 157.063 C 731.409 159.912, 744.359 159.466, 750.795 156.136 C 767.289 147.601, 774.103 127.713, 767.019 108.780 C 761.433 93.851, 746.051 85.232, 729.932 87.998 M 883.972 88.348 C 865.749 92.310, 854.873 109.966, 858.119 130.316 C 860.933 147.963, 872.346 158.830, 889.084 159.799 C 900.714 160.472, 906.867 158.234, 914.650 150.497 C 919.856 145.323, 921.123 143.340, 922.900 137.587 C 925.412 129.456, 925.655 118.570, 923.489 111.275 C 918.479 94.405, 901.478 84.541, 883.972 88.348"/></svg>';
|
|
14522
14742
|
|
|
14523
14743
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
14524
14744
|
var ATTRIBUTION_URL = "https://luxalgo.com/vela";
|
|
14525
|
-
var
|
|
14745
|
+
var STYLE_ID5 = "vela-attribution-styles";
|
|
14526
14746
|
var CSS = `
|
|
14527
14747
|
.vela-attribution {
|
|
14528
14748
|
text-decoration: none;
|
|
@@ -14585,11 +14805,11 @@ var CSS = `
|
|
|
14585
14805
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
14586
14806
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
14587
14807
|
`;
|
|
14588
|
-
function
|
|
14589
|
-
let s = doc.getElementById(
|
|
14808
|
+
function ensureStyles5(doc) {
|
|
14809
|
+
let s = doc.getElementById(STYLE_ID5);
|
|
14590
14810
|
if (!s) {
|
|
14591
14811
|
s = doc.createElement("style");
|
|
14592
|
-
s.id =
|
|
14812
|
+
s.id = STYLE_ID5;
|
|
14593
14813
|
doc.head.appendChild(s);
|
|
14594
14814
|
}
|
|
14595
14815
|
s.textContent = CSS;
|
|
@@ -14618,7 +14838,7 @@ function createCustomMark(doc, html, background) {
|
|
|
14618
14838
|
return el;
|
|
14619
14839
|
}
|
|
14620
14840
|
function createAttributionMark(doc, background) {
|
|
14621
|
-
|
|
14841
|
+
ensureStyles5(doc);
|
|
14622
14842
|
const a = doc.createElement("a");
|
|
14623
14843
|
a.className = "vela-attribution";
|
|
14624
14844
|
a.href = ATTRIBUTION_URL;
|
|
@@ -15016,6 +15236,8 @@ var NativeRenderer = class {
|
|
|
15016
15236
|
this.volumeRenderer = new VolumeRenderer();
|
|
15017
15237
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
15018
15238
|
this.extLayers = [];
|
|
15239
|
+
/** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
|
|
15240
|
+
this.layerOrderSig = "";
|
|
15019
15241
|
// The attribution mark (see chrome/AttributionMark + the NOTICE file): default-on;
|
|
15020
15242
|
// disabling requires an equivalent visible attribution elsewhere in the host UI.
|
|
15021
15243
|
this.attributionEl = null;
|
|
@@ -15130,6 +15352,8 @@ var NativeRenderer = class {
|
|
|
15130
15352
|
// total right-gutter width (master column + merged-scale columns)
|
|
15131
15353
|
this.paneControls = null;
|
|
15132
15354
|
// per-pane hover button cluster (top-right)
|
|
15355
|
+
this.axisScaleButtons = null;
|
|
15356
|
+
// A/L hover buttons at the bottom of each pane's price scale
|
|
15133
15357
|
this.paneActionCbs = /* @__PURE__ */ new Set();
|
|
15134
15358
|
// ── data window (crosshair OHLC + per-series readout, pulled by host panels) ──
|
|
15135
15359
|
this.hoverLogical = null;
|
|
@@ -15173,7 +15397,7 @@ var NativeRenderer = class {
|
|
|
15173
15397
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
15174
15398
|
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
15175
15399
|
this.name = "native";
|
|
15176
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
15400
|
+
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"];
|
|
15177
15401
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
15178
15402
|
* so moving onto the button doesn't count as leaving). */
|
|
15179
15403
|
this.onScrollProximityMove = (e) => {
|
|
@@ -15289,6 +15513,9 @@ var NativeRenderer = class {
|
|
|
15289
15513
|
case "highlights":
|
|
15290
15514
|
this.scene.highlights = sanitizeHighlights(value);
|
|
15291
15515
|
break;
|
|
15516
|
+
case "sessionZones":
|
|
15517
|
+
this.scene.sessionZones = sanitizeSessionZones(value);
|
|
15518
|
+
break;
|
|
15292
15519
|
case "gridlines":
|
|
15293
15520
|
this.scene.showGrid = Boolean(value);
|
|
15294
15521
|
break;
|
|
@@ -15405,6 +15632,8 @@ var NativeRenderer = class {
|
|
|
15405
15632
|
return this.scene.indicatorZOrder();
|
|
15406
15633
|
case "highlights":
|
|
15407
15634
|
return this.scene.highlights;
|
|
15635
|
+
case "sessionZones":
|
|
15636
|
+
return this.scene.sessionZones;
|
|
15408
15637
|
case "gridlines":
|
|
15409
15638
|
return this.scene.showGrid;
|
|
15410
15639
|
case "axisLabels":
|
|
@@ -15614,7 +15843,8 @@ var NativeRenderer = class {
|
|
|
15614
15843
|
baselineLevel: s.baseline.baselineLevel
|
|
15615
15844
|
};
|
|
15616
15845
|
})(),
|
|
15617
|
-
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale }
|
|
15846
|
+
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
15847
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
15618
15848
|
};
|
|
15619
15849
|
}
|
|
15620
15850
|
/**
|
|
@@ -15696,6 +15926,7 @@ var NativeRenderer = class {
|
|
|
15696
15926
|
width: next.baseline.width,
|
|
15697
15927
|
baselineLevel: next.baseline.baselineLevel
|
|
15698
15928
|
};
|
|
15929
|
+
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
|
|
15699
15930
|
this.setPriceStyle(next.series.style);
|
|
15700
15931
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
15701
15932
|
this.scene.baselineValue = next.series.baseline;
|
|
@@ -15709,6 +15940,7 @@ var NativeRenderer = class {
|
|
|
15709
15940
|
}
|
|
15710
15941
|
this.inputsUI?.setTheme(this.theme);
|
|
15711
15942
|
this.paneControls?.setTheme(this.theme);
|
|
15943
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
15712
15944
|
this.userDrawings?.setTheme(this.chromeTheme());
|
|
15713
15945
|
this.refreshScrollButtonTheme();
|
|
15714
15946
|
this.refreshAttributionColor();
|
|
@@ -15936,9 +16168,7 @@ var NativeRenderer = class {
|
|
|
15936
16168
|
if (el.getAttribute("data-vela-screenshot") === "under") rasterizeOverlay(ctx, el, frame);
|
|
15937
16169
|
}
|
|
15938
16170
|
}
|
|
15939
|
-
const
|
|
15940
|
-
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
15941
|
-
for (const canvas of [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas]) {
|
|
16171
|
+
for (const canvas of [...this.canvasPile(), this.chromeCanvas, this.drawingsCanvas]) {
|
|
15942
16172
|
if (canvas && canvas.width > 0 && canvas.height > 0) ctx.drawImage(canvas, 0, 0);
|
|
15943
16173
|
}
|
|
15944
16174
|
if (frame) {
|
|
@@ -16047,7 +16277,7 @@ var NativeRenderer = class {
|
|
|
16047
16277
|
this.surfaceBackground = theme.background;
|
|
16048
16278
|
this.surfaceTextColor = theme.textColor;
|
|
16049
16279
|
this.wrapper = document.createElement("div");
|
|
16050
|
-
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair" });
|
|
16280
|
+
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair", userSelect: "none", webkitUserSelect: "none" });
|
|
16051
16281
|
applyChromeTokens(this.wrapper, this.chromeTheme());
|
|
16052
16282
|
this.volumeCanvas = document.createElement("canvas");
|
|
16053
16283
|
Object.assign(this.volumeCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
@@ -16072,6 +16302,7 @@ var NativeRenderer = class {
|
|
|
16072
16302
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
16073
16303
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
16074
16304
|
this.plot.append(...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
16305
|
+
this.layerOrderSig = "";
|
|
16075
16306
|
this.wrapper.appendChild(this.plot);
|
|
16076
16307
|
this.factoryConfig = this.getConfig();
|
|
16077
16308
|
this.attributionEl = this.buildAttributionEl();
|
|
@@ -16216,6 +16447,15 @@ var NativeRenderer = class {
|
|
|
16216
16447
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
16217
16448
|
}
|
|
16218
16449
|
});
|
|
16450
|
+
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
16451
|
+
panes: () => this.axisScaleViews(),
|
|
16452
|
+
rightAxis: () => this.rightAxisW,
|
|
16453
|
+
onToggleAuto: (paneId) => this.togglePaneAuto(paneId),
|
|
16454
|
+
onToggleLog: (paneId) => {
|
|
16455
|
+
const pane = this.scene.panes.get(paneId);
|
|
16456
|
+
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
16457
|
+
}
|
|
16458
|
+
});
|
|
16219
16459
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
16220
16460
|
this.resizeObserver.observe(this.wrapper);
|
|
16221
16461
|
this.watchDpr();
|
|
@@ -16292,6 +16532,7 @@ var NativeRenderer = class {
|
|
|
16292
16532
|
this.applyBackground();
|
|
16293
16533
|
this.inputsUI.setTheme(theme);
|
|
16294
16534
|
this.paneControls?.setTheme(this.theme);
|
|
16535
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
16295
16536
|
this.settingsDialog?.refreshConfig(this.getConfig());
|
|
16296
16537
|
this.syncThemeControl();
|
|
16297
16538
|
this.settingsDialog?.setTheme(this.theme);
|
|
@@ -16340,6 +16581,7 @@ var NativeRenderer = class {
|
|
|
16340
16581
|
this.liveRegion = null;
|
|
16341
16582
|
this.inputsUI?.destroy();
|
|
16342
16583
|
this.paneControls?.destroy();
|
|
16584
|
+
this.axisScaleButtons?.destroy();
|
|
16343
16585
|
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
16344
16586
|
this.tableOverlays.clear();
|
|
16345
16587
|
this.resizeObserver?.disconnect();
|
|
@@ -16589,7 +16831,8 @@ var NativeRenderer = class {
|
|
|
16589
16831
|
mountIndicator(model) {
|
|
16590
16832
|
this.scene.indicators.set(model.id, model);
|
|
16591
16833
|
this.refreshAnchorOffset(model);
|
|
16592
|
-
this.scene.
|
|
16834
|
+
if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
|
|
16835
|
+
else this.scene.assignIndicatorZ(model.id);
|
|
16593
16836
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
16594
16837
|
native: !!model.native,
|
|
16595
16838
|
...model.shorttitle ? { settingsTitle: model.title } : {}
|
|
@@ -17408,10 +17651,17 @@ var NativeRenderer = class {
|
|
|
17408
17651
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
17409
17652
|
for (const l of this.extLayers) {
|
|
17410
17653
|
if (!l.def.repaintOnCursor) continue;
|
|
17411
|
-
|
|
17654
|
+
const lp = this.layerPane(l.def.id) ?? pane;
|
|
17655
|
+
if (lp.collapsed) continue;
|
|
17656
|
+
l.instance.render(this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs));
|
|
17412
17657
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
17413
17658
|
}
|
|
17414
17659
|
}
|
|
17660
|
+
/** Blank one SDK layer canvas (a collapsed host pane suppresses the layer's painting). */
|
|
17661
|
+
clearLayerCanvas(canvas) {
|
|
17662
|
+
if (canvas.width === 0 || canvas.height === 0) return;
|
|
17663
|
+
canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
|
|
17664
|
+
}
|
|
17415
17665
|
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
|
|
17416
17666
|
extLayerArgs(id, scale, bounds, nowMs) {
|
|
17417
17667
|
return {
|
|
@@ -17430,6 +17680,7 @@ var NativeRenderer = class {
|
|
|
17430
17680
|
}
|
|
17431
17681
|
/** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
|
|
17432
17682
|
paintData() {
|
|
17683
|
+
this.syncLayerCanvasOrder();
|
|
17433
17684
|
this.stampScaleInvert();
|
|
17434
17685
|
this.backend.modelAlpha = this.modelAlpha;
|
|
17435
17686
|
this.backend.candleBodyAlpha = this.candleBodyAlpha;
|
|
@@ -17460,9 +17711,14 @@ var NativeRenderer = class {
|
|
|
17460
17711
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
17461
17712
|
let folded = null;
|
|
17462
17713
|
for (const l of this.extLayers) {
|
|
17463
|
-
const
|
|
17714
|
+
const lp = this.layerPane(l.def.id) ?? pane;
|
|
17715
|
+
if (lp.collapsed) {
|
|
17716
|
+
this.clearLayerCanvas(l.canvas);
|
|
17717
|
+
continue;
|
|
17718
|
+
}
|
|
17719
|
+
const args = this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs);
|
|
17464
17720
|
l.instance.render(args);
|
|
17465
|
-
folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
17721
|
+
if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
17466
17722
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
17467
17723
|
}
|
|
17468
17724
|
if (folded) {
|
|
@@ -17624,6 +17880,9 @@ var NativeRenderer = class {
|
|
|
17624
17880
|
pane.scaleTarget = { min: 0, max: maxVol / VOLUME_PANE_FILL_FRAC };
|
|
17625
17881
|
pane.axisFormat = "volume";
|
|
17626
17882
|
}
|
|
17883
|
+
} else if (this.layerNativesOwnPane(pane, masterModels)) {
|
|
17884
|
+
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
17885
|
+
pane.percentBaseline = this.bars[i0]?.close ?? 0;
|
|
17627
17886
|
}
|
|
17628
17887
|
if (pane === pricePane && this.scene.tradeMarkers.visible && pane.bounds.height > 0) {
|
|
17629
17888
|
const th = this.tradesScaleHints(vr);
|
|
@@ -17710,6 +17969,59 @@ var NativeRenderer = class {
|
|
|
17710
17969
|
}
|
|
17711
17970
|
return null;
|
|
17712
17971
|
}
|
|
17972
|
+
/** The mounted native indicator that OWNS an SDK layer — the one whose type equals the
|
|
17973
|
+
* layer id (the id doubles as the data channel, so the pairing is the SDK's own
|
|
17974
|
+
* contract). Null for chart-type channels and while the owner is hidden (a hidden
|
|
17975
|
+
* indicator leaves the scene; its cleared data channel paints nothing anyway). */
|
|
17976
|
+
layerOwner(layerId) {
|
|
17977
|
+
for (const m of this.scene.indicators.values()) {
|
|
17978
|
+
if (m.native?.type === layerId) return m;
|
|
17979
|
+
}
|
|
17980
|
+
return null;
|
|
17981
|
+
}
|
|
17982
|
+
/** The pane an SDK layer paints on: its owner's pane, else the price pane. */
|
|
17983
|
+
layerPane(layerId) {
|
|
17984
|
+
const owner = this.layerOwner(layerId);
|
|
17985
|
+
const paneId = owner ? owner.paneId ?? PRICE_PANE_ID : PRICE_PANE_ID;
|
|
17986
|
+
return this.scene.panes.get(paneId) ?? null;
|
|
17987
|
+
}
|
|
17988
|
+
/** The SDK layer canvases split around the data canvas, each side back-to-front:
|
|
17989
|
+
* owned layers by their owner's z key against the candles' (an indicator restacked
|
|
17990
|
+
* below the candles takes its layer canvas along), unowned by declared placement. */
|
|
17991
|
+
orderedLayerCanvases() {
|
|
17992
|
+
const byId = new Map(this.extLayers.map((l) => [l.def.id, l.canvas]));
|
|
17993
|
+
const { below, above } = stackLayers(
|
|
17994
|
+
this.extLayers.map((l) => {
|
|
17995
|
+
const owner = this.layerOwner(l.def.id);
|
|
17996
|
+
return {
|
|
17997
|
+
id: l.def.id,
|
|
17998
|
+
placement: l.def.placement === "below-data" ? "below-data" : "above-data",
|
|
17999
|
+
ownerZ: owner ? this.scene.zOf(owner.id) : null
|
|
18000
|
+
};
|
|
18001
|
+
}),
|
|
18002
|
+
this.scene.candleZ
|
|
18003
|
+
);
|
|
18004
|
+
return { below: below.map((id) => byId.get(id)), above: above.map((id) => byId.get(id)) };
|
|
18005
|
+
}
|
|
18006
|
+
/** The full canvas pile in paint order (layers + data/volume/vpvr) — what the DOM
|
|
18007
|
+
* stacking and the screenshot compositor must both follow. */
|
|
18008
|
+
canvasPile() {
|
|
18009
|
+
const { below, above } = this.orderedLayerCanvases();
|
|
18010
|
+
return [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above];
|
|
18011
|
+
}
|
|
18012
|
+
/** Re-slot the SDK layer canvases in the plot when the computed order changed (a z
|
|
18013
|
+
* write, a restored config, an indicator mount/remove/restack). Runs at the top of
|
|
18014
|
+
* every data frame; a no-op when the signature is unchanged. Re-inserting an
|
|
18015
|
+
* absolutely-positioned, pointer-transparent canvas repaints nothing by itself. */
|
|
18016
|
+
syncLayerCanvasOrder() {
|
|
18017
|
+
if (this.extLayers.length === 0 || !this.plot) return;
|
|
18018
|
+
const { below, above } = this.orderedLayerCanvases();
|
|
18019
|
+
const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).def.id), "|", ...above.map((c) => this.extLayers.find((l) => l.canvas === c).def.id)].join(",");
|
|
18020
|
+
if (sig === this.layerOrderSig) return;
|
|
18021
|
+
this.layerOrderSig = sig;
|
|
18022
|
+
for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
|
|
18023
|
+
for (const c of above) this.plot.insertBefore(c, this.chromeCanvas);
|
|
18024
|
+
}
|
|
17713
18025
|
/** True when an active volume layer is this study pane's ONLY content — so its scale should
|
|
17714
18026
|
* come from volume, not the empty {0,1} placeholder. (In the price pane, or alongside a real
|
|
17715
18027
|
* series, volume stays a bottom overlay and the master scale wins.) */
|
|
@@ -17719,6 +18031,15 @@ var NativeRenderer = class {
|
|
|
17719
18031
|
if (this.nativeLayerPane("volume") !== pane) return false;
|
|
17720
18032
|
return masterModels.every((m) => m.native?.type === "volume");
|
|
17721
18033
|
}
|
|
18034
|
+
/** True when this study pane's master content is only SDK-layer natives (series-less
|
|
18035
|
+
* models whose type names a mounted layer) — its scale then follows the visible bars
|
|
18036
|
+
* (see the call site). Any real master series takes over the scale as usual. */
|
|
18037
|
+
layerNativesOwnPane(pane, masterModels) {
|
|
18038
|
+
if (pane.kind === "price" || masterModels.length === 0) return false;
|
|
18039
|
+
return masterModels.every(
|
|
18040
|
+
(m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
|
|
18041
|
+
);
|
|
18042
|
+
}
|
|
17722
18043
|
/** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
|
|
17723
18044
|
* band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
|
|
17724
18045
|
* Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
|
|
@@ -17734,6 +18055,25 @@ var NativeRenderer = class {
|
|
|
17734
18055
|
invert: paneInvert(this.scene, p)
|
|
17735
18056
|
}));
|
|
17736
18057
|
}
|
|
18058
|
+
/** Thin projection of the panes for the axis A/L hover buttons (top-to-bottom order). */
|
|
18059
|
+
axisScaleViews() {
|
|
18060
|
+
return this.scene.orderedPanes().map((p) => ({
|
|
18061
|
+
id: p.id,
|
|
18062
|
+
top: p.bounds.top,
|
|
18063
|
+
height: p.bounds.height,
|
|
18064
|
+
collapsed: p.collapsed,
|
|
18065
|
+
auto: p.manualScale == null,
|
|
18066
|
+
log: paneLogScale(this.scene, p)
|
|
18067
|
+
}));
|
|
18068
|
+
}
|
|
18069
|
+
/** Toggle one pane's autoscale (the A axis button): off freezes the current window into
|
|
18070
|
+
* manual mode, on drops it — the per-pane twin of the chart-level `autoScale` feature. */
|
|
18071
|
+
togglePaneAuto(paneId) {
|
|
18072
|
+
const pane = this.scene.panes.get(paneId);
|
|
18073
|
+
if (!pane) return;
|
|
18074
|
+
pane.manualScale = pane.manualScale == null ? { ...pane.scale } : null;
|
|
18075
|
+
this.scheduler?.invalidate(4 /* Full */);
|
|
18076
|
+
}
|
|
17737
18077
|
/** Set one pane's axis mode. The price pane routes to the scene-level setting (persisted +
|
|
17738
18078
|
* keyboard shortcuts); a study pane keeps its own, so panes never affect each other. */
|
|
17739
18079
|
setPaneScaleMode(paneId, mode) {
|
|
@@ -17745,19 +18085,18 @@ var NativeRenderer = class {
|
|
|
17745
18085
|
}
|
|
17746
18086
|
this.scheduler?.invalidate(4 /* Full */);
|
|
17747
18087
|
}
|
|
17748
|
-
/** Set one pane's logarithmic flag
|
|
17749
|
-
*
|
|
18088
|
+
/** Set one pane's logarithmic flag; the price pane routes to the scene-level flag.
|
|
18089
|
+
* Log and auto are INDEPENDENT: a frozen (manual) window is kept — its price bounds
|
|
18090
|
+
* stay put and only its `log` tag flips, so the same range re-renders in the new
|
|
18091
|
+
* space instead of snapping back to autoscale. */
|
|
17750
18092
|
setPaneLog(paneId, log) {
|
|
17751
|
-
|
|
17752
|
-
|
|
17753
|
-
|
|
17754
|
-
if (
|
|
17755
|
-
|
|
17756
|
-
const p = this.scene.panes.get(paneId);
|
|
17757
|
-
if (!p) return;
|
|
17758
|
-
p.logScale = log;
|
|
17759
|
-
p.manualScale = null;
|
|
18093
|
+
const pane = paneId === PRICE_PANE_ID ? this.scene.orderedPanes().find((p) => p.kind === "price") : this.scene.panes.get(paneId);
|
|
18094
|
+
if (paneId === PRICE_PANE_ID) this.scene.logScale = log;
|
|
18095
|
+
else {
|
|
18096
|
+
if (!pane) return;
|
|
18097
|
+
pane.logScale = log;
|
|
17760
18098
|
}
|
|
18099
|
+
if (pane?.manualScale) pane.manualScale.log = log;
|
|
17761
18100
|
this.scheduler?.invalidate(4 /* Full */);
|
|
17762
18101
|
}
|
|
17763
18102
|
/** Flip one pane's axis (high at the bottom). The price pane routes to the scene-level flag
|
|
@@ -17856,6 +18195,7 @@ var NativeRenderer = class {
|
|
|
17856
18195
|
}
|
|
17857
18196
|
this.inputsUI?.reposition();
|
|
17858
18197
|
this.paneControls?.reposition();
|
|
18198
|
+
this.axisScaleButtons?.reposition();
|
|
17859
18199
|
this.repositionScrollButton();
|
|
17860
18200
|
this.positionAttribution();
|
|
17861
18201
|
this.publishPricePaneBounds();
|
|
@@ -17874,6 +18214,7 @@ var NativeRenderer = class {
|
|
|
17874
18214
|
}
|
|
17875
18215
|
this.inputsUI?.reposition();
|
|
17876
18216
|
this.paneControls?.reposition();
|
|
18217
|
+
this.axisScaleButtons?.reposition();
|
|
17877
18218
|
this.repositionScrollButton();
|
|
17878
18219
|
this.positionAttribution();
|
|
17879
18220
|
this.publishPricePaneBounds();
|
|
@@ -18081,6 +18422,22 @@ function sanitizeHighlights(value) {
|
|
|
18081
18422
|
}
|
|
18082
18423
|
return out.sort((a, b) => a.from - b.from);
|
|
18083
18424
|
}
|
|
18425
|
+
function sanitizeSessionZones(value) {
|
|
18426
|
+
if (value == null || typeof value !== "object") return null;
|
|
18427
|
+
const v = value;
|
|
18428
|
+
const windows = (raw) => {
|
|
18429
|
+
if (!Array.isArray(raw)) return [];
|
|
18430
|
+
const out = [];
|
|
18431
|
+
for (const w of raw) {
|
|
18432
|
+
if (!Array.isArray(w)) continue;
|
|
18433
|
+
const from = Number(w[0]);
|
|
18434
|
+
const to = Number(w[1]);
|
|
18435
|
+
if (Number.isFinite(from) && Number.isFinite(to) && to > from) out.push([from, to]);
|
|
18436
|
+
}
|
|
18437
|
+
return out.sort((a, b) => a[0] - b[0]);
|
|
18438
|
+
};
|
|
18439
|
+
return { pre: windows(v.pre), post: windows(v.post) };
|
|
18440
|
+
}
|
|
18084
18441
|
function clamp012(v) {
|
|
18085
18442
|
return Math.max(0, Math.min(1, v));
|
|
18086
18443
|
}
|