@luxalgo/vela 0.7.6 → 0.7.7
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/{chunk-ZADTVRUO.js → chunk-PVGWNF7V.js} +1 -1
- package/dist/{chunk-JSVCWYBF.js → chunk-RVQWJOEE.js} +232 -112
- package/dist/{chunk-BKHSQ4YM.js → chunk-WTWGEB7K.js} +23 -9
- package/dist/{chunk-EMB53WNQ.js → chunk-ZA2XWTGH.js} +84 -31
- package/dist/{contributions-D6p1RB38.d.cts → contributions-DF_Ea07m.d.cts} +17 -0
- package/dist/{contributions-C7Wsm_S9.d.ts → contributions-nKOSeLy8.d.ts} +17 -0
- package/dist/index.cjs +240 -119
- package/dist/index.d.cts +19 -4
- package/dist/index.d.ts +19 -4
- package/dist/index.js +2 -2
- package/dist/{plugin-SodaQhjQ.d.ts → plugin-0oFDnexb.d.ts} +1 -1
- package/dist/{plugin-CxWfg6vK.d.cts → plugin-DPyVJFoA.d.cts} +1 -1
- package/dist/plugin.d.cts +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/{statusline-model-gWHrsOy3.d.cts → statusline-model-BoWJ3cWQ.d.cts} +16 -3
- package/dist/{statusline-model-xBiVsKoE.d.ts → statusline-model-DAFyqVmR.d.ts} +16 -3
- package/dist/ui.cjs +23 -9
- package/dist/ui.d.cts +19 -2
- package/dist/ui.d.ts +19 -2
- package/dist/ui.js +2 -2
- package/dist/vela.global.js +240 -119
- package/dist/vela.global.min.js +61 -51
- package/dist/widget.cjs +334 -147
- package/dist/widget.d.cts +5 -5
- package/dist/widget.d.ts +5 -5
- package/dist/widget.js +5 -5
- package/dist/workspace.cjs +334 -147
- package/dist/workspace.d.cts +33 -6
- package/dist/workspace.d.ts +33 -6
- package/dist/workspace.js +4 -4
- package/package.json +21 -1
package/dist/workspace.cjs
CHANGED
|
@@ -3521,7 +3521,8 @@ function addRecent(hex6) {
|
|
|
3521
3521
|
recents.unshift(hex6);
|
|
3522
3522
|
if (recents.length > 10) recents.length = 10;
|
|
3523
3523
|
}
|
|
3524
|
-
function buildColorPicker(color, theme, onChange) {
|
|
3524
|
+
function buildColorPicker(color, theme, onChange, options = {}) {
|
|
3525
|
+
const commit = options.commit ?? "live";
|
|
3525
3526
|
const parsed = splitColor(color);
|
|
3526
3527
|
let curHex = parsed.hex6;
|
|
3527
3528
|
let curAlpha = parsed.alpha;
|
|
@@ -3604,7 +3605,12 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
3604
3605
|
const r = track.getBoundingClientRect();
|
|
3605
3606
|
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
3606
3607
|
paintOpacity();
|
|
3607
|
-
emit();
|
|
3608
|
+
if (commit === "live") emit();
|
|
3609
|
+
};
|
|
3610
|
+
const endDrag = () => {
|
|
3611
|
+
if (!dragging) return;
|
|
3612
|
+
dragging = false;
|
|
3613
|
+
if (commit === "release") emit();
|
|
3608
3614
|
};
|
|
3609
3615
|
track.addEventListener("pointerdown", (e) => {
|
|
3610
3616
|
e.stopPropagation();
|
|
@@ -3615,7 +3621,8 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
3615
3621
|
track.addEventListener("pointermove", (e) => {
|
|
3616
3622
|
if (dragging) onDrag(e.clientX);
|
|
3617
3623
|
});
|
|
3618
|
-
track.addEventListener("pointerup",
|
|
3624
|
+
track.addEventListener("pointerup", endDrag);
|
|
3625
|
+
track.addEventListener("pointercancel", endDrag);
|
|
3619
3626
|
function emit() {
|
|
3620
3627
|
onChange(combineColor(curHex, curAlpha));
|
|
3621
3628
|
}
|
|
@@ -3638,7 +3645,7 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
3638
3645
|
return root;
|
|
3639
3646
|
}
|
|
3640
3647
|
function colorField(theme, getVal, onVal, opts) {
|
|
3641
|
-
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover }).el;
|
|
3648
|
+
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover, commit: opts?.commit }).el;
|
|
3642
3649
|
}
|
|
3643
3650
|
var ColorField = class {
|
|
3644
3651
|
constructor(opts) {
|
|
@@ -3647,6 +3654,7 @@ var ColorField = class {
|
|
|
3647
3654
|
this.getVal = opts.getVal;
|
|
3648
3655
|
this.onVal = opts.onVal;
|
|
3649
3656
|
this.popoverOpts = opts.popover;
|
|
3657
|
+
this.commit = opts.commit;
|
|
3650
3658
|
const trigger = document.createElement("button");
|
|
3651
3659
|
trigger.type = "button";
|
|
3652
3660
|
if (opts.id) trigger.id = opts.id;
|
|
@@ -3686,10 +3694,15 @@ var ColorField = class {
|
|
|
3686
3694
|
boundary: this.popoverOpts?.boundary,
|
|
3687
3695
|
zIndex: this.popoverOpts?.zIndex,
|
|
3688
3696
|
className: "vela-color-field-pop",
|
|
3689
|
-
content: buildColorPicker(
|
|
3690
|
-
this.
|
|
3691
|
-
this.
|
|
3692
|
-
|
|
3697
|
+
content: buildColorPicker(
|
|
3698
|
+
this.getVal(),
|
|
3699
|
+
this.theme,
|
|
3700
|
+
(val) => {
|
|
3701
|
+
this.onVal(val);
|
|
3702
|
+
this.paint();
|
|
3703
|
+
},
|
|
3704
|
+
{ commit: this.commit }
|
|
3705
|
+
)
|
|
3693
3706
|
});
|
|
3694
3707
|
pop.show();
|
|
3695
3708
|
}
|
|
@@ -4277,7 +4290,8 @@ function buildFieldControl(desc) {
|
|
|
4277
4290
|
const el2 = colorField(desc.theme, desc.get, desc.onChange, {
|
|
4278
4291
|
shape: "circle",
|
|
4279
4292
|
id: desc.id,
|
|
4280
|
-
popover: desc.popover
|
|
4293
|
+
popover: desc.popover,
|
|
4294
|
+
commit: desc.commit
|
|
4281
4295
|
});
|
|
4282
4296
|
if (desc.title) el2.title = desc.title;
|
|
4283
4297
|
return { el: el2 };
|
|
@@ -5016,10 +5030,12 @@ var BASELINE_LEVEL_DEFAULT = 50;
|
|
|
5016
5030
|
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
5017
5031
|
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
5018
5032
|
var EXTENDED_SHADE = POSTMARKET_SHADE;
|
|
5033
|
+
var DEFAULT_MARGINS = { top: 10, bottom: 10, right: 10 };
|
|
5019
5034
|
function defaultChartStyle() {
|
|
5020
5035
|
return {
|
|
5021
5036
|
chartTypes: {},
|
|
5022
5037
|
fontSize: 11,
|
|
5038
|
+
margins: { ...DEFAULT_MARGINS },
|
|
5023
5039
|
gridVert: { visible: true, color: null },
|
|
5024
5040
|
gridHorz: { visible: true, color: null },
|
|
5025
5041
|
borderColor: null,
|
|
@@ -5143,6 +5159,12 @@ function clampLevel(v) {
|
|
|
5143
5159
|
function clampSpacing(v) {
|
|
5144
5160
|
return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
|
|
5145
5161
|
}
|
|
5162
|
+
function clampMarginPct(v) {
|
|
5163
|
+
return v < 0 ? 0 : v > 40 ? 40 : v;
|
|
5164
|
+
}
|
|
5165
|
+
function clampMarginBars(v) {
|
|
5166
|
+
return Math.round(v < 0 ? 0 : v > 200 ? 200 : v);
|
|
5167
|
+
}
|
|
5146
5168
|
function factoryResetConfig(factory, priceStyle = factory.series.style) {
|
|
5147
5169
|
const bag = {};
|
|
5148
5170
|
for (const t of chartTypes()) {
|
|
@@ -5191,6 +5213,7 @@ function mergeConfig(base, patch) {
|
|
|
5191
5213
|
const ps = asObject(p.priceScale);
|
|
5192
5214
|
const anim = asObject(p.animations);
|
|
5193
5215
|
const panes = asObject(p.panes);
|
|
5216
|
+
const margins = asObject(p.margins);
|
|
5194
5217
|
const trades = asObject(p.trades);
|
|
5195
5218
|
const ts = asObject(p.timeScale);
|
|
5196
5219
|
const marks = asObject(p.marks);
|
|
@@ -5250,6 +5273,11 @@ function mergeConfig(base, patch) {
|
|
|
5250
5273
|
panes: {
|
|
5251
5274
|
separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
|
|
5252
5275
|
},
|
|
5276
|
+
margins: {
|
|
5277
|
+
top: isNum(margins.top) ? clampMarginPct(margins.top) : base.margins.top,
|
|
5278
|
+
bottom: isNum(margins.bottom) ? clampMarginPct(margins.bottom) : base.margins.bottom,
|
|
5279
|
+
right: isNum(margins.right) ? clampMarginBars(margins.right) : base.margins.right
|
|
5280
|
+
},
|
|
5253
5281
|
trades: {
|
|
5254
5282
|
visible: isBool(trades.visible) ? trades.visible : base.trades.visible,
|
|
5255
5283
|
labels: isBool(trades.labels) ? trades.labels : base.trades.labels,
|
|
@@ -6071,9 +6099,22 @@ var TIMEZONES = [
|
|
|
6071
6099
|
{ value: "Pacific/Apia", label: "Apia" },
|
|
6072
6100
|
{ value: "Pacific/Kiritimati", label: "Kiritimati" }
|
|
6073
6101
|
];
|
|
6102
|
+
var EXCHANGE_TIMEZONE = "exchange";
|
|
6103
|
+
function isExchangeTimezone(zone) {
|
|
6104
|
+
return zone === EXCHANGE_TIMEZONE;
|
|
6105
|
+
}
|
|
6106
|
+
function resolveTimezone(zone, exchangeZone) {
|
|
6107
|
+
if (!isExchangeTimezone(zone)) return zone;
|
|
6108
|
+
return exchangeZone && exchangeZone !== "" ? exchangeZone : "Etc/UTC";
|
|
6109
|
+
}
|
|
6074
6110
|
function normalizeTimezone(zone) {
|
|
6075
6111
|
return zone === "UTC" || zone === "Etc/UTC" || zone === "Etc/GMT" ? "Etc/UTC" : zone;
|
|
6076
6112
|
}
|
|
6113
|
+
function timezoneMenuRows(current) {
|
|
6114
|
+
const active = normalizeTimezone(current);
|
|
6115
|
+
const [utc, ...zones] = TIMEZONES.map((t) => ({ value: t.value, label: tzMenuLabel(t.value, t.label), checked: t.value === active }));
|
|
6116
|
+
return [utc, { value: EXCHANGE_TIMEZONE, label: "Exchange", checked: isExchangeTimezone(current) }, ...zones];
|
|
6117
|
+
}
|
|
6077
6118
|
function tzOffset(zone, date = /* @__PURE__ */ new Date()) {
|
|
6078
6119
|
try {
|
|
6079
6120
|
const parts = new Intl.DateTimeFormat("en-US", { timeZone: zone, timeZoneName: "shortOffset" }).formatToParts(date);
|
|
@@ -6196,6 +6237,7 @@ var Bottombar = class {
|
|
|
6196
6237
|
this.sessionButtons = /* @__PURE__ */ new Map();
|
|
6197
6238
|
this.sessionEl = null;
|
|
6198
6239
|
this.timezone = opts.timezone;
|
|
6240
|
+
this.exchangeTimezone = opts.exchangeTimezone;
|
|
6199
6241
|
const doc = host.ownerDocument;
|
|
6200
6242
|
injectStyles(STYLE_ID3, CSS4, doc);
|
|
6201
6243
|
this.el = doc.createElement("div");
|
|
@@ -6219,7 +6261,7 @@ var Bottombar = class {
|
|
|
6219
6261
|
this.clockEl = doc.createElement("span");
|
|
6220
6262
|
this.clockEl.className = "vela-bb-clock";
|
|
6221
6263
|
this.tzLabelEl = doc.createElement("span");
|
|
6222
|
-
this.tzLabelEl.textContent = tzButtonLabel(this.
|
|
6264
|
+
this.tzLabelEl.textContent = tzButtonLabel(this.displayZone);
|
|
6223
6265
|
this.tzButton.append(this.clockEl, this.tzLabelEl);
|
|
6224
6266
|
const session = doc.createElement("span");
|
|
6225
6267
|
session.className = "vela-bb-session";
|
|
@@ -6254,19 +6296,32 @@ var Bottombar = class {
|
|
|
6254
6296
|
placement: "top-end",
|
|
6255
6297
|
items: this.tzItems(),
|
|
6256
6298
|
onSelect: (zone) => {
|
|
6257
|
-
this.setTimezone(zone);
|
|
6299
|
+
this.setTimezone(zone, this.exchangeTimezone);
|
|
6258
6300
|
opts.onTimezone(zone);
|
|
6259
6301
|
}
|
|
6260
6302
|
});
|
|
6261
6303
|
this.tick();
|
|
6262
6304
|
this.unsubClock = (opts.clock ?? new SecondClock()).onTick(() => this.tick());
|
|
6263
6305
|
}
|
|
6264
|
-
|
|
6306
|
+
/**
|
|
6307
|
+
* Reflect the stored choice AND the active chart's market zone. The clock and the
|
|
6308
|
+
* offset label read the RESOLVED zone, so a workspace on the exchange rule re-labels
|
|
6309
|
+
* when the active cell (or its symbol) changes market — the host re-projects on
|
|
6310
|
+
* both. Idempotent: unchanged inputs leave the menu alone.
|
|
6311
|
+
*/
|
|
6312
|
+
setTimezone(zone, exchangeTimezone) {
|
|
6313
|
+
if (zone === this.timezone && exchangeTimezone === this.exchangeTimezone) return;
|
|
6314
|
+
const choiceChanged = zone !== this.timezone;
|
|
6265
6315
|
this.timezone = zone;
|
|
6266
|
-
this.
|
|
6267
|
-
this.
|
|
6316
|
+
this.exchangeTimezone = exchangeTimezone;
|
|
6317
|
+
this.tzLabelEl.textContent = tzButtonLabel(this.displayZone);
|
|
6318
|
+
if (choiceChanged) this.tzMenu.setItems(this.tzItems());
|
|
6268
6319
|
this.tick();
|
|
6269
6320
|
}
|
|
6321
|
+
/** The zone the bar's clock and label render in. */
|
|
6322
|
+
get displayZone() {
|
|
6323
|
+
return resolveTimezone(this.timezone, this.exchangeTimezone);
|
|
6324
|
+
}
|
|
6270
6325
|
/** Highlight (or clear with null) the active range chip — cleared on manual tf changes. */
|
|
6271
6326
|
setActiveRange(id) {
|
|
6272
6327
|
for (const [key, b] of this.rangeButtons) {
|
|
@@ -6294,11 +6349,7 @@ var Bottombar = class {
|
|
|
6294
6349
|
this.el.remove();
|
|
6295
6350
|
}
|
|
6296
6351
|
tzItems() {
|
|
6297
|
-
return
|
|
6298
|
-
id: t.value,
|
|
6299
|
-
label: tzMenuLabel(t.value, t.label),
|
|
6300
|
-
checked: t.value === normalizeTimezone(this.timezone)
|
|
6301
|
-
}));
|
|
6352
|
+
return timezoneMenuRows(this.timezone).map((r) => ({ id: r.value, label: r.label, checked: r.checked }));
|
|
6302
6353
|
}
|
|
6303
6354
|
tick() {
|
|
6304
6355
|
try {
|
|
@@ -6307,7 +6358,7 @@ var Bottombar = class {
|
|
|
6307
6358
|
minute: "2-digit",
|
|
6308
6359
|
second: "2-digit",
|
|
6309
6360
|
hour12: false,
|
|
6310
|
-
timeZone: this.
|
|
6361
|
+
timeZone: this.displayZone
|
|
6311
6362
|
}).format(/* @__PURE__ */ new Date());
|
|
6312
6363
|
} catch {
|
|
6313
6364
|
this.clockEl.textContent = "";
|
|
@@ -15929,15 +15980,14 @@ var TimezoneDrawer = class {
|
|
|
15929
15980
|
this.drawer.body.replaceChildren();
|
|
15930
15981
|
const list = doc.createElement("div");
|
|
15931
15982
|
list.className = "vela-tzd-list";
|
|
15932
|
-
const
|
|
15933
|
-
for (const tz of TIMEZONES) {
|
|
15983
|
+
for (const tz of timezoneMenuRows(this.opts.timezone())) {
|
|
15934
15984
|
const row = doc.createElement("div");
|
|
15935
15985
|
row.className = "vela-tzd-row";
|
|
15936
15986
|
const label = doc.createElement("span");
|
|
15937
15987
|
label.className = "vela-tzd-row-label";
|
|
15938
|
-
label.textContent =
|
|
15988
|
+
label.textContent = tz.label;
|
|
15939
15989
|
row.appendChild(label);
|
|
15940
|
-
if (tz.
|
|
15990
|
+
if (tz.checked) row.appendChild(iconEl("check", doc));
|
|
15941
15991
|
row.addEventListener("click", () => {
|
|
15942
15992
|
this.opts.onTimezone(tz.value);
|
|
15943
15993
|
this.drawer.hide();
|
|
@@ -16024,12 +16074,11 @@ function priceAxisItems(s) {
|
|
|
16024
16074
|
];
|
|
16025
16075
|
}
|
|
16026
16076
|
function timeAxisItems(timezone) {
|
|
16027
|
-
const active = timezone === "UTC" ? "Etc/UTC" : timezone;
|
|
16028
16077
|
return [
|
|
16029
16078
|
{
|
|
16030
16079
|
id: "timezone",
|
|
16031
16080
|
label: "Time zone",
|
|
16032
|
-
submenu:
|
|
16081
|
+
submenu: timezoneMenuRows(timezone).map((r) => ({ id: `tz:${r.value}`, label: r.label, checked: r.checked }))
|
|
16033
16082
|
},
|
|
16034
16083
|
settingsItem("time-axis", "More settings\u2026")
|
|
16035
16084
|
];
|
|
@@ -21702,7 +21751,10 @@ var IndicatorInputsDialog = class {
|
|
|
21702
21751
|
id,
|
|
21703
21752
|
theme: this.host.theme(),
|
|
21704
21753
|
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
21705
|
-
onChange: (v) => emit(v)
|
|
21754
|
+
onChange: (v) => emit(v),
|
|
21755
|
+
// An input change re-executes the script over its whole history: commit the
|
|
21756
|
+
// opacity drag once on release, not once per pointer move.
|
|
21757
|
+
commit: "release"
|
|
21706
21758
|
}).el;
|
|
21707
21759
|
}
|
|
21708
21760
|
if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
|
|
@@ -21949,10 +22001,6 @@ var IndicatorInputsDialog = class {
|
|
|
21949
22001
|
}
|
|
21950
22002
|
/** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
|
|
21951
22003
|
openCalendar(anchor, current, onPick) {
|
|
21952
|
-
const parsed = parseIsoDate(current);
|
|
21953
|
-
let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
21954
|
-
let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
|
|
21955
|
-
const selected = parsed ? isoDate(parsed) : current;
|
|
21956
22004
|
const pop = new Popover({
|
|
21957
22005
|
trigger: anchor,
|
|
21958
22006
|
theme: this.host.theme(),
|
|
@@ -21964,80 +22012,10 @@ var IndicatorInputsDialog = class {
|
|
|
21964
22012
|
this.calendarPop = null;
|
|
21965
22013
|
this.calendarAnchor = null;
|
|
21966
22014
|
},
|
|
21967
|
-
content: (el) => {
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
21971
|
-
prev2.type = "button";
|
|
21972
|
-
prev2.className = "vela-ind-cal-nav";
|
|
21973
|
-
prev2.setAttribute("aria-label", "Previous month");
|
|
21974
|
-
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
21975
|
-
const next = document.createElement("button");
|
|
21976
|
-
next.type = "button";
|
|
21977
|
-
next.className = "vela-ind-cal-nav";
|
|
21978
|
-
next.setAttribute("aria-label", "Next month");
|
|
21979
|
-
next.innerHTML = iconAt("chevron-right", 14);
|
|
21980
|
-
const head = document.createElement("div");
|
|
21981
|
-
head.className = "vela-ind-cal-head";
|
|
21982
|
-
head.append(prev2, title, next);
|
|
21983
|
-
const week = document.createElement("div");
|
|
21984
|
-
week.className = "vela-ind-cal-week";
|
|
21985
|
-
for (const d of WEEKDAY_LABELS) {
|
|
21986
|
-
const cell = document.createElement("span");
|
|
21987
|
-
cell.textContent = d;
|
|
21988
|
-
week.appendChild(cell);
|
|
21989
|
-
}
|
|
21990
|
-
const grid = document.createElement("div");
|
|
21991
|
-
grid.className = "vela-ind-cal-grid";
|
|
21992
|
-
const paint = () => {
|
|
21993
|
-
title.textContent = `${MONTH_LABELS[month]} ${year}`;
|
|
21994
|
-
grid.replaceChildren();
|
|
21995
|
-
const first = new Date(year, month, 1);
|
|
21996
|
-
const startPad = first.getDay();
|
|
21997
|
-
const days = new Date(year, month + 1, 0).getDate();
|
|
21998
|
-
const today = isoDate(/* @__PURE__ */ new Date());
|
|
21999
|
-
for (let i = 0; i < startPad; i++) {
|
|
22000
|
-
const blank = document.createElement("span");
|
|
22001
|
-
blank.className = "vela-ind-cal-blank";
|
|
22002
|
-
grid.appendChild(blank);
|
|
22003
|
-
}
|
|
22004
|
-
for (let day = 1; day <= days; day++) {
|
|
22005
|
-
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
22006
|
-
const b = document.createElement("button");
|
|
22007
|
-
b.type = "button";
|
|
22008
|
-
b.className = "vela-ind-cal-day";
|
|
22009
|
-
b.textContent = String(day);
|
|
22010
|
-
if (iso === selected) b.dataset.checked = "1";
|
|
22011
|
-
if (iso === today) b.dataset.today = "1";
|
|
22012
|
-
b.addEventListener("click", (e) => {
|
|
22013
|
-
e.stopPropagation();
|
|
22014
|
-
pop.hide();
|
|
22015
|
-
onPick(iso);
|
|
22016
|
-
});
|
|
22017
|
-
grid.appendChild(b);
|
|
22018
|
-
}
|
|
22019
|
-
};
|
|
22020
|
-
prev2.addEventListener("click", (e) => {
|
|
22021
|
-
e.stopPropagation();
|
|
22022
|
-
month -= 1;
|
|
22023
|
-
if (month < 0) {
|
|
22024
|
-
month = 11;
|
|
22025
|
-
year -= 1;
|
|
22026
|
-
}
|
|
22027
|
-
paint();
|
|
22028
|
-
});
|
|
22029
|
-
next.addEventListener("click", (e) => {
|
|
22030
|
-
e.stopPropagation();
|
|
22031
|
-
month += 1;
|
|
22032
|
-
if (month > 11) {
|
|
22033
|
-
month = 0;
|
|
22034
|
-
year += 1;
|
|
22035
|
-
}
|
|
22036
|
-
paint();
|
|
22037
|
-
});
|
|
22038
|
-
paint();
|
|
22039
|
-
el.append(head, week, grid);
|
|
22040
|
-
}
|
|
22015
|
+
content: (el) => fillCalendar(el, current, (iso) => {
|
|
22016
|
+
pop.hide();
|
|
22017
|
+
onPick(iso);
|
|
22018
|
+
})
|
|
22041
22019
|
});
|
|
22042
22020
|
this.calendarPop = pop;
|
|
22043
22021
|
this.calendarAnchor = anchor;
|
|
@@ -22146,7 +22124,7 @@ function groupInputs(inputs) {
|
|
|
22146
22124
|
var CALENDAR_SVG = iconAt("calendar", 14);
|
|
22147
22125
|
var CLOCK_SVG = iconAt("clock", 14);
|
|
22148
22126
|
var DIALOG_STYLE_ID2 = "vela-ind-dialog-styles";
|
|
22149
|
-
var DIALOG_STYLE_REV = "
|
|
22127
|
+
var DIALOG_STYLE_REV = "30";
|
|
22150
22128
|
var LEGEND_ICON_PX = 16;
|
|
22151
22129
|
function ensureDialogStyles() {
|
|
22152
22130
|
if (typeof document === "undefined") return;
|
|
@@ -22162,8 +22140,12 @@ function ensureDialogStyles() {
|
|
|
22162
22140
|
.vela-ind-combo-chevron{position:absolute;right:0;top:0;bottom:0;width:26px;border:none;background:transparent;color:inherit;opacity:0.55;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;}
|
|
22163
22141
|
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
22164
22142
|
.vela-ind-cal{background:var(--vela-bg);color:var(--vela-fg);border:none;border-radius:6px;box-shadow:var(--vela-shadow);font:14px var(--vela-font);padding:10px 12px;user-select:none;}
|
|
22143
|
+
.vela-ind-cal [hidden]{display:none !important;}
|
|
22165
22144
|
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
22166
|
-
.vela-ind-cal-title{flex:1;
|
|
22145
|
+
.vela-ind-cal-title{flex:1;display:flex;align-items:center;justify-content:center;gap:2px;min-width:0;}
|
|
22146
|
+
.vela-ind-cal-switch{border:none;background:transparent;color:var(--vela-fg-bright);font:inherit;font-weight:600;font-size:14px;padding:2px 6px;border-radius:4px;cursor:pointer;}
|
|
22147
|
+
.vela-ind-cal-switch:not(:disabled):hover{background:var(--vela-hover);}
|
|
22148
|
+
.vela-ind-cal-switch:disabled{cursor:default;}
|
|
22167
22149
|
.vela-ind-cal-nav{width:24px;height:24px;border:none;background:transparent;color:var(--vela-fg-muted);border-radius:4px;padding:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;}
|
|
22168
22150
|
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
22169
22151
|
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
@@ -22174,6 +22156,12 @@ function ensureDialogStyles() {
|
|
|
22174
22156
|
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
22175
22157
|
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
22176
22158
|
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
22159
|
+
.vela-ind-cal-cells{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;width:calc(7 * 28px + 6 * 2px);}
|
|
22160
|
+
.vela-ind-cal-cell{height:36px;border:none;background:transparent;color:inherit;border-radius:4px;padding:0 4px;cursor:pointer;font:inherit;font-size:14px;}
|
|
22161
|
+
.vela-ind-cal-cell:hover{background:var(--vela-hover);}
|
|
22162
|
+
.vela-ind-cal-cell[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
22163
|
+
.vela-ind-cal-cell[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
22164
|
+
.vela-ind-cal-cell[data-outside]{opacity:0.45;}
|
|
22177
22165
|
${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
22178
22166
|
.vela-ind-tab{font-weight:600;font-size:13px;line-height:20px;transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
22179
22167
|
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
@@ -22208,6 +22196,7 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
|
|
22208
22196
|
return { value: v, label: v };
|
|
22209
22197
|
});
|
|
22210
22198
|
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
22199
|
+
var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
22211
22200
|
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
22212
22201
|
function normalizeDateInput(raw) {
|
|
22213
22202
|
const t = raw.trim();
|
|
@@ -22229,6 +22218,151 @@ function parseIsoDate(raw) {
|
|
|
22229
22218
|
function isoDate(d) {
|
|
22230
22219
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
22231
22220
|
}
|
|
22221
|
+
function fillCalendar(el, current, onPick) {
|
|
22222
|
+
const today = /* @__PURE__ */ new Date();
|
|
22223
|
+
const selected = parseIsoDate(current);
|
|
22224
|
+
let year = selected?.getFullYear() ?? today.getFullYear();
|
|
22225
|
+
let month = selected?.getMonth() ?? today.getMonth();
|
|
22226
|
+
let mode = "date";
|
|
22227
|
+
const prev2 = document.createElement("button");
|
|
22228
|
+
prev2.type = "button";
|
|
22229
|
+
prev2.className = "vela-ind-cal-nav";
|
|
22230
|
+
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
22231
|
+
const next = document.createElement("button");
|
|
22232
|
+
next.type = "button";
|
|
22233
|
+
next.className = "vela-ind-cal-nav";
|
|
22234
|
+
next.innerHTML = iconAt("chevron-right", 14);
|
|
22235
|
+
const monthBtn = document.createElement("button");
|
|
22236
|
+
monthBtn.type = "button";
|
|
22237
|
+
monthBtn.className = "vela-ind-cal-switch";
|
|
22238
|
+
monthBtn.setAttribute("aria-label", "Choose month");
|
|
22239
|
+
const yearBtn = document.createElement("button");
|
|
22240
|
+
yearBtn.type = "button";
|
|
22241
|
+
yearBtn.className = "vela-ind-cal-switch";
|
|
22242
|
+
yearBtn.setAttribute("aria-label", "Choose year");
|
|
22243
|
+
const title = document.createElement("div");
|
|
22244
|
+
title.className = "vela-ind-cal-title";
|
|
22245
|
+
title.append(monthBtn, yearBtn);
|
|
22246
|
+
const head = document.createElement("div");
|
|
22247
|
+
head.className = "vela-ind-cal-head";
|
|
22248
|
+
head.append(prev2, title, next);
|
|
22249
|
+
const week = document.createElement("div");
|
|
22250
|
+
week.className = "vela-ind-cal-week";
|
|
22251
|
+
for (const d of WEEKDAY_LABELS) {
|
|
22252
|
+
const cell = document.createElement("span");
|
|
22253
|
+
cell.textContent = d;
|
|
22254
|
+
week.appendChild(cell);
|
|
22255
|
+
}
|
|
22256
|
+
const grid = document.createElement("div");
|
|
22257
|
+
const paint = () => {
|
|
22258
|
+
const decade = Math.floor(year / 10) * 10;
|
|
22259
|
+
monthBtn.hidden = mode !== "date";
|
|
22260
|
+
monthBtn.textContent = MONTH_LABELS[month] ?? "";
|
|
22261
|
+
yearBtn.textContent = mode === "year" ? `${decade}-${decade + 9}` : String(year);
|
|
22262
|
+
yearBtn.disabled = mode === "year";
|
|
22263
|
+
week.hidden = mode !== "date";
|
|
22264
|
+
prev2.setAttribute("aria-label", mode === "date" ? "Previous month" : mode === "month" ? "Previous year" : "Previous decade");
|
|
22265
|
+
next.setAttribute("aria-label", mode === "date" ? "Next month" : mode === "month" ? "Next year" : "Next decade");
|
|
22266
|
+
grid.className = mode === "date" ? "vela-ind-cal-grid" : "vela-ind-cal-cells";
|
|
22267
|
+
grid.replaceChildren();
|
|
22268
|
+
if (mode === "date") {
|
|
22269
|
+
const startPad = new Date(year, month, 1).getDay();
|
|
22270
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
22271
|
+
const todayIso = isoDate(today);
|
|
22272
|
+
const selectedIso = selected ? isoDate(selected) : "";
|
|
22273
|
+
for (let i = 0; i < startPad; i++) {
|
|
22274
|
+
const blank = document.createElement("span");
|
|
22275
|
+
blank.className = "vela-ind-cal-blank";
|
|
22276
|
+
grid.appendChild(blank);
|
|
22277
|
+
}
|
|
22278
|
+
for (let day = 1; day <= days; day++) {
|
|
22279
|
+
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
22280
|
+
const b = document.createElement("button");
|
|
22281
|
+
b.type = "button";
|
|
22282
|
+
b.className = "vela-ind-cal-day";
|
|
22283
|
+
b.textContent = String(day);
|
|
22284
|
+
if (iso === selectedIso) b.dataset.checked = "1";
|
|
22285
|
+
if (iso === todayIso) b.dataset.today = "1";
|
|
22286
|
+
b.addEventListener("click", (e) => {
|
|
22287
|
+
e.stopPropagation();
|
|
22288
|
+
onPick(iso);
|
|
22289
|
+
});
|
|
22290
|
+
grid.appendChild(b);
|
|
22291
|
+
}
|
|
22292
|
+
return;
|
|
22293
|
+
}
|
|
22294
|
+
if (mode === "month") {
|
|
22295
|
+
for (let m = 0; m < 12; m++) {
|
|
22296
|
+
const b = document.createElement("button");
|
|
22297
|
+
b.type = "button";
|
|
22298
|
+
b.className = "vela-ind-cal-cell";
|
|
22299
|
+
b.textContent = MONTH_SHORT[m] ?? "";
|
|
22300
|
+
if (selected?.getFullYear() === year && selected.getMonth() === m) b.dataset.checked = "1";
|
|
22301
|
+
if (today.getFullYear() === year && today.getMonth() === m) b.dataset.today = "1";
|
|
22302
|
+
b.addEventListener("click", (e) => {
|
|
22303
|
+
e.stopPropagation();
|
|
22304
|
+
month = m;
|
|
22305
|
+
mode = "date";
|
|
22306
|
+
paint();
|
|
22307
|
+
});
|
|
22308
|
+
grid.appendChild(b);
|
|
22309
|
+
}
|
|
22310
|
+
return;
|
|
22311
|
+
}
|
|
22312
|
+
for (let y = decade - 1; y <= decade + 10; y++) {
|
|
22313
|
+
const b = document.createElement("button");
|
|
22314
|
+
b.type = "button";
|
|
22315
|
+
b.className = "vela-ind-cal-cell";
|
|
22316
|
+
b.textContent = String(y);
|
|
22317
|
+
if (y < decade || y > decade + 9) b.dataset.outside = "1";
|
|
22318
|
+
if (selected?.getFullYear() === y) b.dataset.checked = "1";
|
|
22319
|
+
if (today.getFullYear() === y) b.dataset.today = "1";
|
|
22320
|
+
b.addEventListener("click", (e) => {
|
|
22321
|
+
e.stopPropagation();
|
|
22322
|
+
year = y;
|
|
22323
|
+
mode = "month";
|
|
22324
|
+
paint();
|
|
22325
|
+
});
|
|
22326
|
+
grid.appendChild(b);
|
|
22327
|
+
}
|
|
22328
|
+
};
|
|
22329
|
+
const step = (dir) => {
|
|
22330
|
+
if (mode === "date") {
|
|
22331
|
+
month += dir;
|
|
22332
|
+
if (month < 0) {
|
|
22333
|
+
month = 11;
|
|
22334
|
+
year -= 1;
|
|
22335
|
+
}
|
|
22336
|
+
if (month > 11) {
|
|
22337
|
+
month = 0;
|
|
22338
|
+
year += 1;
|
|
22339
|
+
}
|
|
22340
|
+
} else {
|
|
22341
|
+
year += mode === "month" ? dir : dir * 10;
|
|
22342
|
+
}
|
|
22343
|
+
paint();
|
|
22344
|
+
};
|
|
22345
|
+
prev2.addEventListener("click", (e) => {
|
|
22346
|
+
e.stopPropagation();
|
|
22347
|
+
step(-1);
|
|
22348
|
+
});
|
|
22349
|
+
next.addEventListener("click", (e) => {
|
|
22350
|
+
e.stopPropagation();
|
|
22351
|
+
step(1);
|
|
22352
|
+
});
|
|
22353
|
+
monthBtn.addEventListener("click", (e) => {
|
|
22354
|
+
e.stopPropagation();
|
|
22355
|
+
mode = "month";
|
|
22356
|
+
paint();
|
|
22357
|
+
});
|
|
22358
|
+
yearBtn.addEventListener("click", (e) => {
|
|
22359
|
+
e.stopPropagation();
|
|
22360
|
+
mode = "year";
|
|
22361
|
+
paint();
|
|
22362
|
+
});
|
|
22363
|
+
paint();
|
|
22364
|
+
el.append(head, week, grid);
|
|
22365
|
+
}
|
|
22232
22366
|
function normalizeTimeInput(raw) {
|
|
22233
22367
|
const t = raw.trim();
|
|
22234
22368
|
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
@@ -28716,6 +28850,10 @@ var BUILTIN_SETTINGS_IDS = [
|
|
|
28716
28850
|
"canvas.grid",
|
|
28717
28851
|
"canvas.grid.vertical",
|
|
28718
28852
|
"canvas.grid.horizontal",
|
|
28853
|
+
"canvas.margins",
|
|
28854
|
+
"canvas.margins.top",
|
|
28855
|
+
"canvas.margins.bottom",
|
|
28856
|
+
"canvas.margins.right",
|
|
28719
28857
|
"canvas.theme"
|
|
28720
28858
|
];
|
|
28721
28859
|
function settingsIdCatalog(hostSections, markGroups = []) {
|
|
@@ -29246,6 +29384,10 @@ var SettingsDialog = class {
|
|
|
29246
29384
|
body.append(sid(this.toggleRow("Horizontal", config.grid.horzLines.visible, (v) => this.emit({ grid: { horzLines: { visible: v } } }), [
|
|
29247
29385
|
this.swatch(config.grid.horzLines.color, (v) => this.emit({ grid: { horzLines: { color: v } } }))
|
|
29248
29386
|
]), "canvas.grid.horizontal"));
|
|
29387
|
+
body.append(sid(this.sectionTitle("Margins"), "canvas.margins"));
|
|
29388
|
+
body.append(sid(this.numberRow("Top", config.margins.top, 0, 40, 1, (v) => this.emit({ margins: { top: v } }), "%"), "canvas.margins.top"));
|
|
29389
|
+
body.append(sid(this.numberRow("Bottom", config.margins.bottom, 0, 40, 1, (v) => this.emit({ margins: { bottom: v } }), "%"), "canvas.margins.bottom"));
|
|
29390
|
+
body.append(sid(this.numberRow("Right", config.margins.right, 0, 200, 1, (v) => this.emit({ margins: { right: v } }), "bars"), "canvas.margins.right"));
|
|
29249
29391
|
if (this.themeControl) {
|
|
29250
29392
|
const tc = this.themeControl;
|
|
29251
29393
|
body.append(sid(this.sectionTitle("Theme"), "canvas.theme"));
|
|
@@ -29817,23 +29959,16 @@ var SettingsDialog = class {
|
|
|
29817
29959
|
this.hintTips.push(dispose);
|
|
29818
29960
|
return el;
|
|
29819
29961
|
}
|
|
29820
|
-
|
|
29821
|
-
|
|
29822
|
-
|
|
29823
|
-
|
|
29824
|
-
|
|
29825
|
-
|
|
29826
|
-
|
|
29827
|
-
|
|
29828
|
-
|
|
29829
|
-
|
|
29830
|
-
step,
|
|
29831
|
-
fill: false,
|
|
29832
|
-
commit: "live",
|
|
29833
|
-
steppers: true,
|
|
29834
|
-
onChange
|
|
29835
|
-
}).el
|
|
29836
|
-
});
|
|
29962
|
+
/** `unit` (optional) trails the input as muted text — "%", "bars". */
|
|
29963
|
+
numberRow(label, value, min, max, step, onChange, unit) {
|
|
29964
|
+
const controls = [buildFieldControl({ kind: "number", value, min, max, step, fill: false, commit: "live", steppers: true, onChange }).el];
|
|
29965
|
+
if (unit) {
|
|
29966
|
+
const u = document.createElement("span");
|
|
29967
|
+
u.textContent = unit;
|
|
29968
|
+
u.style.cssText = "color:var(--vela-fg-muted);";
|
|
29969
|
+
controls.push(u);
|
|
29970
|
+
}
|
|
29971
|
+
return this.rowWith(label, controls);
|
|
29837
29972
|
}
|
|
29838
29973
|
/** A dropdown whose option values differ from their display labels. */
|
|
29839
29974
|
selectRowLabeled(label, value, options, onChange) {
|
|
@@ -34707,9 +34842,7 @@ function createProjector(coords, paneOf, paneIdAtY, barsInRange, seriesInRange)
|
|
|
34707
34842
|
}
|
|
34708
34843
|
|
|
34709
34844
|
// src/renderers/native/core/autoscale.ts
|
|
34710
|
-
|
|
34711
|
-
var MARGIN_BOTTOM = 1 / 7;
|
|
34712
|
-
function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0) {
|
|
34845
|
+
function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0, margins = DEFAULT_MARGINS) {
|
|
34713
34846
|
let min = Infinity;
|
|
34714
34847
|
let max = -Infinity;
|
|
34715
34848
|
const consider = (v) => {
|
|
@@ -34744,14 +34877,17 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
34744
34877
|
const pad = Math.abs(min) * 0.1 || 1;
|
|
34745
34878
|
return { min: min - pad, max: max + pad, log: log && min - pad > 0 };
|
|
34746
34879
|
}
|
|
34880
|
+
const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
|
|
34881
|
+
const above = margins.top / 100 / content;
|
|
34882
|
+
const below = margins.bottom / 100 / content;
|
|
34747
34883
|
if (log && min > 0) {
|
|
34748
34884
|
const lmin = Math.log(min);
|
|
34749
34885
|
const lmax = Math.log(max);
|
|
34750
34886
|
const lspan = lmax - lmin;
|
|
34751
|
-
return { min: Math.exp(lmin - lspan *
|
|
34887
|
+
return { min: Math.exp(lmin - lspan * below), max: Math.exp(lmax + lspan * above), log: true };
|
|
34752
34888
|
}
|
|
34753
34889
|
const span = max - min;
|
|
34754
|
-
return { min: min - span *
|
|
34890
|
+
return { min: min - span * below, max: max + span * above };
|
|
34755
34891
|
}
|
|
34756
34892
|
function considerSeries(s, i0, i1, off, consider) {
|
|
34757
34893
|
if (!seriesInScale(s)) return;
|
|
@@ -36442,6 +36578,7 @@ var NativeRenderer = class {
|
|
|
36442
36578
|
intro: this.intro.style !== false
|
|
36443
36579
|
},
|
|
36444
36580
|
panes: { separatorColor: s.separatorColor ?? t.borderColor },
|
|
36581
|
+
margins: { ...s.margins },
|
|
36445
36582
|
trades: {
|
|
36446
36583
|
visible: this.scene.tradeMarkers.visible,
|
|
36447
36584
|
labels: this.scene.tradeMarkers.labels,
|
|
@@ -36554,6 +36691,10 @@ var NativeRenderer = class {
|
|
|
36554
36691
|
this.animAutoscale.toggle(next.animations.autoscale);
|
|
36555
36692
|
this.intro = { style: next.animations.intro ? this.introOnStyle : false, duration: this.intro.duration || INTRO_DURATION_DEFAULT_MS };
|
|
36556
36693
|
s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
|
|
36694
|
+
if (next.margins.right !== s.margins.right && this.coords.barCount > 0) {
|
|
36695
|
+
this.applyViewport({ barSpacing: this.coords.getViewport().barSpacing, rightOffset: next.margins.right });
|
|
36696
|
+
}
|
|
36697
|
+
s.margins = { ...next.margins };
|
|
36557
36698
|
this.scene.tradeMarkers = {
|
|
36558
36699
|
visible: next.trades.visible,
|
|
36559
36700
|
labels: next.trades.labels,
|
|
@@ -36797,7 +36938,7 @@ var NativeRenderer = class {
|
|
|
36797
36938
|
/** Glide the view back to the most recent bars, keeping the current zoom (barSpacing). */
|
|
36798
36939
|
scrollToRealtime() {
|
|
36799
36940
|
if (this.coords.barCount === 0) return;
|
|
36800
|
-
this.glideRightOffset(
|
|
36941
|
+
this.glideRightOffset(this.scene.style.margins.right);
|
|
36801
36942
|
}
|
|
36802
36943
|
/** Ease rightOffset to `target` at constant zoom (see animTick's scroll glide);
|
|
36803
36944
|
* instant when the scroll glide is off. Shared by scroll-to-latest and panBy. */
|
|
@@ -38739,6 +38880,7 @@ var NativeRenderer = class {
|
|
|
38739
38880
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
38740
38881
|
this.chrome.prepare(this.scene, this.coords, this.theme);
|
|
38741
38882
|
const animating = this.animator.active;
|
|
38883
|
+
const margins = this.scene.style.margins;
|
|
38742
38884
|
for (const pane of panes) {
|
|
38743
38885
|
if (pane.manualScale) {
|
|
38744
38886
|
pane.scaleTarget = pane.manualScale;
|
|
@@ -38754,7 +38896,7 @@ var NativeRenderer = class {
|
|
|
38754
38896
|
if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
|
|
38755
38897
|
}
|
|
38756
38898
|
const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
|
|
38757
|
-
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
38899
|
+
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
|
|
38758
38900
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
38759
38901
|
pane.axisFormat = void 0;
|
|
38760
38902
|
pane.axisBands = void 0;
|
|
@@ -38765,7 +38907,7 @@ var NativeRenderer = class {
|
|
|
38765
38907
|
pane.axisFormat = "volume";
|
|
38766
38908
|
}
|
|
38767
38909
|
} else if (this.layerNativesOwnPane(pane, masterModels)) {
|
|
38768
|
-
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
38910
|
+
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
|
|
38769
38911
|
pane.percentBaseline = this.bars[i0]?.close ?? 0;
|
|
38770
38912
|
if (masterModels.every((m) => m.paneAxis != null)) {
|
|
38771
38913
|
pane.axisFormat = "none";
|
|
@@ -38794,7 +38936,7 @@ var NativeRenderer = class {
|
|
|
38794
38936
|
continue;
|
|
38795
38937
|
}
|
|
38796
38938
|
const mdr = this.chrome.paneDrawingsRange([model], this.scene, false, vr);
|
|
38797
|
-
sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id));
|
|
38939
|
+
sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id), margins);
|
|
38798
38940
|
if (!animating || !sl.initialized) {
|
|
38799
38941
|
sl.scale = { ...sl.scaleTarget };
|
|
38800
38942
|
sl.initialized = true;
|
|
@@ -38835,13 +38977,13 @@ var NativeRenderer = class {
|
|
|
38835
38977
|
for (const pane of this.scene.panes.values()) pane.manualScale = null;
|
|
38836
38978
|
for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
|
|
38837
38979
|
const visibleBars = Math.min(n, 200);
|
|
38838
|
-
const rightOffset =
|
|
38980
|
+
const rightOffset = this.scene.style.margins.right;
|
|
38839
38981
|
const v = this.clampViewport(w / ((visibleBars + rightOffset) * this.coords.spacingScale), rightOffset);
|
|
38840
38982
|
this.coords.setViewport(v);
|
|
38841
38983
|
this.targetBarSpacing = v.barSpacing;
|
|
38842
38984
|
}
|
|
38843
38985
|
/** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
|
|
38844
|
-
* zoom (bar spacing), re-anchor the newest bars at the
|
|
38986
|
+
* zoom (bar spacing), re-anchor the newest bars at the configured right margin.
|
|
38845
38987
|
* `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
|
|
38846
38988
|
* head may still be backfilling toward the previous depth, and raising the spacing
|
|
38847
38989
|
* to its temporary bar count would lose the zoom this exists to keep. */
|
|
@@ -38850,7 +38992,7 @@ var NativeRenderer = class {
|
|
|
38850
38992
|
this.panVelocity = 0;
|
|
38851
38993
|
for (const pane of this.scene.panes.values()) pane.manualScale = null;
|
|
38852
38994
|
for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
|
|
38853
|
-
const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset:
|
|
38995
|
+
const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: this.scene.style.margins.right };
|
|
38854
38996
|
this.coords.setViewport(v);
|
|
38855
38997
|
this.targetBarSpacing = v.barSpacing;
|
|
38856
38998
|
}
|
|
@@ -43462,7 +43604,7 @@ var ChartContextMenu = class {
|
|
|
43462
43604
|
} else if (id.startsWith("tz:")) {
|
|
43463
43605
|
const zone = id.slice("tz:".length);
|
|
43464
43606
|
if (this.cbs.setTimezone) this.cbs.setTimezone(zone);
|
|
43465
|
-
else chart.renderer.set("timezone", zone);
|
|
43607
|
+
else chart.renderer.set("timezone", resolveTimezone(zone, void 0));
|
|
43466
43608
|
} else if (id === "auto") {
|
|
43467
43609
|
chart.renderer.set("autoScale", chart.renderer.get("autoScale") === false);
|
|
43468
43610
|
} else if (id === "invert") {
|
|
@@ -43695,7 +43837,7 @@ var ChartCell = class {
|
|
|
43695
43837
|
this.history.onChart(this.inner);
|
|
43696
43838
|
this.inner.renderer.onConfigChanged(() => {
|
|
43697
43839
|
const zone = this.inner?.renderer.get("timezone");
|
|
43698
|
-
if (typeof zone === "string" && normalizeTimezone(zone) !== normalizeTimezone(this.
|
|
43840
|
+
if (typeof zone === "string" && normalizeTimezone(zone) !== normalizeTimezone(this.displayTimezone)) {
|
|
43699
43841
|
this.deps.setTimezone(normalizeTimezone(zone));
|
|
43700
43842
|
}
|
|
43701
43843
|
const style = this.priceStyle;
|
|
@@ -43737,8 +43879,7 @@ var ChartCell = class {
|
|
|
43737
43879
|
this.refreshSessionShading();
|
|
43738
43880
|
});
|
|
43739
43881
|
this.inner.on("viewport:changed", (range) => this.sessionShading.updateRange(range));
|
|
43740
|
-
|
|
43741
|
-
if (tz !== "Etc/UTC") this.inner.renderer.set("timezone", tz);
|
|
43882
|
+
this.applyTimezone();
|
|
43742
43883
|
this.indicatorTitlesOn = seed.indicatorTitles ?? true;
|
|
43743
43884
|
if (!this.indicatorTitlesOn) this.inner.renderer.set("indicatorTitles", false);
|
|
43744
43885
|
this.indicatorValuesOn = seed.indicatorValues ?? true;
|
|
@@ -43760,7 +43901,7 @@ var ChartCell = class {
|
|
|
43760
43901
|
this.statusline?.setSymbol(this.state.symbol);
|
|
43761
43902
|
this.statusline?.setMeta(this.state.timeframe ?? "60", this.inner.data.displayPrefix(this.state.symbol) ?? this.state.provider ?? "");
|
|
43762
43903
|
}
|
|
43763
|
-
this.
|
|
43904
|
+
this.refreshSymbolMetadata();
|
|
43764
43905
|
if (this.inner && this.state.symbol) this.marketStatus?.track(this.inner.data, this.state.symbol);
|
|
43765
43906
|
});
|
|
43766
43907
|
this.syncStatuslineColors();
|
|
@@ -43834,7 +43975,7 @@ var ChartCell = class {
|
|
|
43834
43975
|
this.offMarket = this.inner.on("market:changed", ({ symbol: symbol2, timeframe }) => {
|
|
43835
43976
|
this.projectMarket(symbol2, timeframe);
|
|
43836
43977
|
this.refreshNativeCatalog();
|
|
43837
|
-
this.
|
|
43978
|
+
this.refreshSymbolMetadata();
|
|
43838
43979
|
if (this.inner) this.marketStatus?.track(this.inner.data, symbol2);
|
|
43839
43980
|
this.deps.onMarketChanged(this.id);
|
|
43840
43981
|
});
|
|
@@ -43875,7 +44016,35 @@ var ChartCell = class {
|
|
|
43875
44016
|
this.deps.onStateDirty();
|
|
43876
44017
|
void this.inner?.setMarket({ session });
|
|
43877
44018
|
}
|
|
43878
|
-
|
|
44019
|
+
/**
|
|
44020
|
+
* The symbol's own trading zone, once its metadata has landed — what the exchange
|
|
44021
|
+
* rule resolves to on this cell (the workspace labels the shared bottom bar with the
|
|
44022
|
+
* ACTIVE cell's). Undefined = unknown or undeclared (the rule then renders UTC).
|
|
44023
|
+
*/
|
|
44024
|
+
get exchangeTimezone() {
|
|
44025
|
+
return this.exchangeZone;
|
|
44026
|
+
}
|
|
44027
|
+
/** The IANA zone this cell's axis renders in: the workspace choice, resolved. */
|
|
44028
|
+
get displayTimezone() {
|
|
44029
|
+
return resolveTimezone(this.deps.timezone(), this.exchangeZone);
|
|
44030
|
+
}
|
|
44031
|
+
/**
|
|
44032
|
+
* Push the resolved zone to the renderer — on the workspace choice changing, and on
|
|
44033
|
+
* this cell's market zone changing under the exchange rule. Skips the write when the
|
|
44034
|
+
* renderer already holds it (its config default is the bare `'UTC'` alias).
|
|
44035
|
+
*/
|
|
44036
|
+
applyTimezone() {
|
|
44037
|
+
const chart = this.inner;
|
|
44038
|
+
if (!chart) return;
|
|
44039
|
+
const zone = this.displayTimezone;
|
|
44040
|
+
const current = chart.renderer.get("timezone");
|
|
44041
|
+
const held = typeof current === "string" && current ? current : "UTC";
|
|
44042
|
+
if (normalizeTimezone(held) === normalizeTimezone(zone)) return;
|
|
44043
|
+
chart.renderer.set("timezone", zone);
|
|
44044
|
+
}
|
|
44045
|
+
/** Re-read the symbol's metadata: session posture (RTH/ETH toggle, shading) and its
|
|
44046
|
+
* trading zone (the exchange rule). Async — the workspace re-projects when a verdict lands. */
|
|
44047
|
+
refreshSymbolMetadata() {
|
|
43879
44048
|
const chart = this.inner;
|
|
43880
44049
|
const symbol = this.state.symbol;
|
|
43881
44050
|
if (!chart || !symbol) return;
|
|
@@ -43883,7 +44052,13 @@ var ChartCell = class {
|
|
|
43883
44052
|
if (this.inner !== chart) return;
|
|
43884
44053
|
const available = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
43885
44054
|
const overnight = parseSessionSpec(si)?.overnight === true;
|
|
43886
|
-
|
|
44055
|
+
const zone = typeof si?.timezone === "string" && si.timezone !== "" ? si.timezone : void 0;
|
|
44056
|
+
const zoneChanged = zone !== this.exchangeZone;
|
|
44057
|
+
if (zoneChanged) {
|
|
44058
|
+
this.exchangeZone = zone;
|
|
44059
|
+
this.applyTimezone();
|
|
44060
|
+
}
|
|
44061
|
+
if (available !== this.sessionAvailableFlag || overnight !== this.sessionOvernightFlag || zoneChanged) {
|
|
43887
44062
|
this.sessionAvailableFlag = available;
|
|
43888
44063
|
this.sessionOvernightFlag = overnight;
|
|
43889
44064
|
this.deps.onMarketChanged(this.id);
|
|
@@ -45659,13 +45834,23 @@ var VelaWorkspace = class {
|
|
|
45659
45834
|
}
|
|
45660
45835
|
}
|
|
45661
45836
|
}
|
|
45662
|
-
/**
|
|
45837
|
+
/**
|
|
45838
|
+
* Set the workspace-global display timezone — applied to EVERY cell. An IANA zone,
|
|
45839
|
+
* or `'exchange'` (the exchange rule): each cell then renders in its OWN market's
|
|
45840
|
+
* zone (Chicago for a CME future, New York for a US equity, UTC for crypto), and the
|
|
45841
|
+
* bottom bar follows the active cell's.
|
|
45842
|
+
*/
|
|
45663
45843
|
setTimezone(zone) {
|
|
45664
45844
|
this.timezone = zone;
|
|
45665
|
-
this.
|
|
45666
|
-
for (const cell of this.cellsById.values()) cell.
|
|
45845
|
+
this.projectTimezone();
|
|
45846
|
+
for (const cell of this.cellsById.values()) cell.applyTimezone();
|
|
45667
45847
|
this.markStateDirty();
|
|
45668
45848
|
}
|
|
45849
|
+
/** Bottom bar ⇐ the stored choice + the ACTIVE cell's market zone (labels the exchange row). */
|
|
45850
|
+
projectTimezone() {
|
|
45851
|
+
const active = this.activeId ? this.cellsById.get(this.activeId) : void 0;
|
|
45852
|
+
this.bottombar?.setTimezone(this.timezone, active?.exchangeTimezone);
|
|
45853
|
+
}
|
|
45669
45854
|
/**
|
|
45670
45855
|
* Swap the workspace theme at runtime — `'dark'`, `'light'`, or a full custom theme,
|
|
45671
45856
|
* applied to the shared chrome (topbar, panels, drawing toolbar) and EVERY cell.
|
|
@@ -45870,6 +46055,7 @@ var VelaWorkspace = class {
|
|
|
45870
46055
|
this.dock.onChart(cell.chart);
|
|
45871
46056
|
this.bottombar?.setActiveRange(cell.activeRangeId);
|
|
45872
46057
|
this.bottombar?.setSession({ session: cell.session, enabled: cell.sessionAvailable });
|
|
46058
|
+
this.projectTimezone();
|
|
45873
46059
|
this.indicatorPicker?.sync();
|
|
45874
46060
|
this.glider.stop();
|
|
45875
46061
|
const d = cell.chart.drawings;
|
|
@@ -46454,6 +46640,7 @@ var VelaWorkspace = class {
|
|
|
46454
46640
|
this.mobileBar?.setTimeframe(cell.timeframe);
|
|
46455
46641
|
this.objectTree.setSymbol(cell.symbol);
|
|
46456
46642
|
this.bottombar?.setSession({ session: cell.session, enabled: cell.sessionAvailable });
|
|
46643
|
+
this.projectTimezone();
|
|
46457
46644
|
this.bottombar?.setActiveRange(cell.activeRangeId);
|
|
46458
46645
|
}
|
|
46459
46646
|
/** Trigger ② — a cell's price style changed: the topbar button/menu only if active.
|