@luxalgo/vela 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataProvider-Byo8pEv0.d.ts → DataProvider-BGlB1zSc.d.ts} +1 -1
- package/dist/{DataProvider-Bw58CjYy.d.cts → DataProvider-zJGp58kb.d.cts} +1 -1
- package/dist/{chunk-MOJ4T26N.js → chunk-4WAHYL7Y.js} +1151 -474
- package/dist/chunk-O6L27KRB.js +1331 -0
- package/dist/{chunk-WY5H4S4I.js → chunk-PZN4RS4T.js} +91 -36
- package/dist/{chunk-AUOB7A5Y.js → chunk-WHPQQYH7.js} +29 -13
- package/dist/{chunk-UNUVPQFW.js → chunk-XIRSR3D6.js} +2837 -1604
- package/dist/{chunk-ALTGHKIM.js → chunk-YRCL3WMD.js} +510 -406
- package/dist/{contributions-BY3zSYiZ.d.ts → contributions-BjefWpxQ.d.ts} +106 -25
- package/dist/{contributions-DOm40g5-.d.cts → contributions-CpCJisTC.d.cts} +106 -25
- package/dist/index.cjs +4485 -2163
- package/dist/index.d.cts +67 -25
- package/dist/index.d.ts +67 -25
- package/dist/index.js +5 -5
- package/dist/{keymap-CGOz5F5f.d.ts → keymap-B9_FLhfJ.d.cts} +9 -6
- package/dist/{keymap-CGOz5F5f.d.cts → keymap-UolhuXH1.d.ts} +9 -6
- package/dist/locales/ar.cjs +2260 -0
- package/dist/locales/ar.d.cts +5 -0
- package/dist/locales/ar.d.ts +5 -0
- package/dist/locales/ar.js +2258 -0
- package/dist/locales/de.cjs +2260 -0
- package/dist/locales/de.d.cts +5 -0
- package/dist/locales/de.d.ts +5 -0
- package/dist/locales/de.js +2258 -0
- package/dist/locales/en.cjs +2260 -0
- package/dist/locales/en.d.cts +5 -0
- package/dist/locales/en.d.ts +5 -0
- package/dist/locales/en.js +2258 -0
- package/dist/locales/es.cjs +2260 -0
- package/dist/locales/es.d.cts +5 -0
- package/dist/locales/es.d.ts +5 -0
- package/dist/locales/es.js +2258 -0
- package/dist/locales/fr.cjs +2260 -0
- package/dist/locales/fr.d.cts +5 -0
- package/dist/locales/fr.d.ts +5 -0
- package/dist/locales/fr.js +2258 -0
- package/dist/locales/he.cjs +2260 -0
- package/dist/locales/he.d.cts +5 -0
- package/dist/locales/he.d.ts +5 -0
- package/dist/locales/he.js +2258 -0
- package/dist/locales/hi.cjs +2260 -0
- package/dist/locales/hi.d.cts +5 -0
- package/dist/locales/hi.d.ts +5 -0
- package/dist/locales/hi.js +2258 -0
- package/dist/locales/id.cjs +2260 -0
- package/dist/locales/id.d.cts +5 -0
- package/dist/locales/id.d.ts +5 -0
- package/dist/locales/id.js +2258 -0
- package/dist/locales/it.cjs +2260 -0
- package/dist/locales/it.d.cts +5 -0
- package/dist/locales/it.d.ts +5 -0
- package/dist/locales/it.js +2258 -0
- package/dist/locales/ja.cjs +2260 -0
- package/dist/locales/ja.d.cts +5 -0
- package/dist/locales/ja.d.ts +5 -0
- package/dist/locales/ja.js +2258 -0
- package/dist/locales/ko.cjs +2260 -0
- package/dist/locales/ko.d.cts +5 -0
- package/dist/locales/ko.d.ts +5 -0
- package/dist/locales/ko.js +2258 -0
- package/dist/locales/ms.cjs +2260 -0
- package/dist/locales/ms.d.cts +5 -0
- package/dist/locales/ms.d.ts +5 -0
- package/dist/locales/ms.js +2258 -0
- package/dist/locales/pl.cjs +2260 -0
- package/dist/locales/pl.d.cts +5 -0
- package/dist/locales/pl.d.ts +5 -0
- package/dist/locales/pl.js +2258 -0
- package/dist/locales/pt-BR.cjs +2260 -0
- package/dist/locales/pt-BR.d.cts +5 -0
- package/dist/locales/pt-BR.d.ts +5 -0
- package/dist/locales/pt-BR.js +2258 -0
- package/dist/locales/ru.cjs +2260 -0
- package/dist/locales/ru.d.cts +5 -0
- package/dist/locales/ru.d.ts +5 -0
- package/dist/locales/ru.js +2258 -0
- package/dist/locales/th.cjs +2260 -0
- package/dist/locales/th.d.cts +5 -0
- package/dist/locales/th.d.ts +5 -0
- package/dist/locales/th.js +2258 -0
- package/dist/locales/tr.cjs +2260 -0
- package/dist/locales/tr.d.cts +5 -0
- package/dist/locales/tr.d.ts +5 -0
- package/dist/locales/tr.js +2258 -0
- package/dist/locales/vi.cjs +2260 -0
- package/dist/locales/vi.d.cts +5 -0
- package/dist/locales/vi.d.ts +5 -0
- package/dist/locales/vi.js +2258 -0
- package/dist/locales/zh-Hans.cjs +2260 -0
- package/dist/locales/zh-Hans.d.cts +5 -0
- package/dist/locales/zh-Hans.d.ts +5 -0
- package/dist/locales/zh-Hans.js +2258 -0
- package/dist/locales/zh-Hant.cjs +2260 -0
- package/dist/locales/zh-Hant.d.cts +5 -0
- package/dist/locales/zh-Hant.d.ts +5 -0
- package/dist/locales/zh-Hant.js +2258 -0
- package/dist/{options-CSXbjMM5.d.cts → options-CpegHml_.d.cts} +110 -10
- package/dist/{options-CSXbjMM5.d.ts → options-D4L3_hv_.d.ts} +110 -10
- package/dist/{plugin-BDxUhGO3.d.ts → plugin-DTmulpKM.d.ts} +92 -40
- package/dist/{plugin-CiQVyETg.d.cts → plugin-DpNp4aBi.d.cts} +92 -40
- package/dist/plugin.cjs +1267 -319
- package/dist/plugin.d.cts +7 -6
- package/dist/plugin.d.ts +7 -6
- package/dist/plugin.js +3 -3
- package/dist/providers/binance.cjs +1 -1
- package/dist/providers/binance.d.cts +3 -2
- package/dist/providers/binance.d.ts +3 -2
- package/dist/providers/binance.js +1 -1
- package/dist/providers/coinbase.cjs +1 -1
- package/dist/providers/coinbase.d.cts +3 -2
- package/dist/providers/coinbase.d.ts +3 -2
- package/dist/providers/coinbase.js +1 -1
- package/dist/providers/hyperliquid.cjs +1 -1
- package/dist/providers/hyperliquid.d.cts +3 -2
- package/dist/providers/hyperliquid.d.ts +3 -2
- package/dist/providers/hyperliquid.js +1 -1
- package/dist/{side-panel-_2UK1gSm.d.ts → side-panel-BqUnpfev.d.cts} +13 -2
- package/dist/{side-panel-_2UK1gSm.d.cts → side-panel-CYmH1Opm.d.ts} +13 -2
- package/dist/{statusline-model-C9tYXuux.d.ts → statusline-model-C3yIY9WE.d.ts} +11 -3
- package/dist/{statusline-model-DUBX6Fib.d.cts → statusline-model-pGs3Xvp-.d.cts} +11 -3
- package/dist/types-BlrXRLIR.d.cts +2515 -0
- package/dist/types-BlrXRLIR.d.ts +2515 -0
- package/dist/ui.cjs +1008 -56
- package/dist/ui.d.cts +21 -2
- package/dist/ui.d.ts +21 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +4451 -2131
- package/dist/vela.global.min.js +57 -57
- package/dist/widget.cjs +5819 -2780
- package/dist/widget.d.cts +100 -26
- package/dist/widget.d.ts +100 -26
- package/dist/widget.js +19 -11
- package/dist/workspace.cjs +5808 -2787
- package/dist/workspace.d.cts +63 -15
- package/dist/workspace.d.ts +63 -15
- package/dist/workspace.js +6 -6
- package/package.json +9 -2
- package/dist/chunk-I2U4NZD2.js +0 -340
- /package/dist/{chunk-W5YPYZ2P.js → chunk-3EYCX2L4.js} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, SecondClock, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, TypedEventBus, timeframeToMs, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, CANDLE_OVERRIDE_KEYS } from './chunk-
|
|
2
|
-
import {
|
|
3
|
-
import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-
|
|
4
|
-
import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-
|
|
5
|
-
import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, announceSurface, holdForExit, icon, categoricalColor } from './chunk-
|
|
1
|
+
import { priceStyleMessage, parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, SecondClock, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, TypedEventBus, timeframeToMs, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, relabelAttributionMark, applyAttributionMarkTheme, sharedBarStore, CANDLE_OVERRIDE_KEYS } from './chunk-XIRSR3D6.js';
|
|
2
|
+
import { resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, tzButtonLabel, resolveTimezone, timezoneMenuRows, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, paneScaleAt, composeMenu, priceAxisItems, scaleChoiceOf, timeAxisItems, bodyItems, scaleWrites, invertWrite, settingsSectionOf, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, normalizeTimezone, isExchangeTimezone, statePersistenceHandlers, topbarHas, rendererDefaults, mobilePlacement, widgetAttachments, chartType, getDrawingType, nativeIndicatorEntry, inputDeltas } from './chunk-YRCL3WMD.js';
|
|
3
|
+
import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-WHPQQYH7.js';
|
|
4
|
+
import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-PZN4RS4T.js';
|
|
5
|
+
import { msg, sourceText, registerIcon, svg16, textOf, localizeNumberText, injectStyles, iconEl, iconMarkup, englishTranslator, sliceGraphemes, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, LocaleState, pickLocalization, eventTimeText, announceSurface, holdForExit, icon, withValues, categoricalColor } from './chunk-O6L27KRB.js';
|
|
6
6
|
import { baseOf } from './chunk-W4EJWLEO.js';
|
|
7
7
|
|
|
8
8
|
// src/widget/timeframe.ts
|
|
@@ -16,15 +16,26 @@ var UNIT_MS = {
|
|
|
16
16
|
Y: 31536e6
|
|
17
17
|
};
|
|
18
18
|
var UNIT_NAME = {
|
|
19
|
-
S: "second",
|
|
20
|
-
MIN: "minute",
|
|
21
|
-
H: "hour",
|
|
22
|
-
D: "day",
|
|
23
|
-
W: "week",
|
|
24
|
-
M: "month",
|
|
25
|
-
Y: "year"
|
|
19
|
+
S: msg("widget.timeframe.seconds", "{count, plural, one {{n} second} other {{n} seconds}}"),
|
|
20
|
+
MIN: msg("widget.timeframe.minutes", "{count, plural, one {{n} minute} other {{n} minutes}}"),
|
|
21
|
+
H: msg("widget.timeframe.hours", "{count, plural, one {{n} hour} other {{n} hours}}"),
|
|
22
|
+
D: msg("widget.timeframe.days", "{count, plural, one {{n} day} other {{n} days}}"),
|
|
23
|
+
W: msg("widget.timeframe.weeks", "{count, plural, one {{n} week} other {{n} weeks}}"),
|
|
24
|
+
M: msg("widget.timeframe.months", "{count, plural, one {{n} month} other {{n} months}}"),
|
|
25
|
+
Y: msg("widget.timeframe.years", "{count, plural, one {{n} year} other {{n} years}}")
|
|
26
26
|
};
|
|
27
|
-
var UNIT_SHORT = {
|
|
27
|
+
var UNIT_SHORT = {
|
|
28
|
+
S: msg("widget.timeframe.short.seconds", "{n}s"),
|
|
29
|
+
MIN: msg("widget.timeframe.short.minutes", "{n}m"),
|
|
30
|
+
H: msg("widget.timeframe.short.hours", "{n}h"),
|
|
31
|
+
D: msg("widget.timeframe.short.days", "{n}D"),
|
|
32
|
+
W: msg("widget.timeframe.short.weeks", "{n}W"),
|
|
33
|
+
M: msg("widget.timeframe.short.months", "{n}M"),
|
|
34
|
+
Y: msg("widget.timeframe.short.years", "{n}Y")
|
|
35
|
+
};
|
|
36
|
+
function shortLabel(key, count, i18n) {
|
|
37
|
+
return textOf(i18n, UNIT_SHORT[key], { n: localizeNumberText(String(count), i18n) });
|
|
38
|
+
}
|
|
28
39
|
function unitKey(letter) {
|
|
29
40
|
if (letter === "") return "MIN";
|
|
30
41
|
return letter === "S" || letter === "H" || letter === "D" || letter === "W" || letter === "M" || letter === "Y" ? letter : null;
|
|
@@ -41,7 +52,7 @@ function canonicalFor(key, count) {
|
|
|
41
52
|
if (key === "S") return `${count}S`;
|
|
42
53
|
return `${count * UNIT_MINUTES[key]}`;
|
|
43
54
|
}
|
|
44
|
-
function parseTimeframe(text) {
|
|
55
|
+
function parseTimeframe(text, i18n) {
|
|
45
56
|
const raw = String(text ?? "").trim();
|
|
46
57
|
if (!raw) return { valid: false };
|
|
47
58
|
const m = /^(\d*)\s*([a-zA-Z]?)$/.exec(raw);
|
|
@@ -53,15 +64,14 @@ function parseTimeframe(text) {
|
|
|
53
64
|
const count = numStr === "" ? 1 : parseInt(numStr, 10);
|
|
54
65
|
if (!Number.isFinite(count) || count < 1) return { valid: false };
|
|
55
66
|
const ms = count * UNIT_MS[key];
|
|
56
|
-
const name = UNIT_NAME[key];
|
|
57
67
|
return {
|
|
58
68
|
valid: true,
|
|
59
69
|
count,
|
|
60
70
|
unit: key,
|
|
61
71
|
ms,
|
|
62
72
|
canonical: canonicalFor(key, count),
|
|
63
|
-
label:
|
|
64
|
-
short:
|
|
73
|
+
label: textOf(i18n, UNIT_NAME[key], { count, n: localizeNumberText(String(count), i18n) }),
|
|
74
|
+
short: shortLabel(key, count, i18n)
|
|
65
75
|
};
|
|
66
76
|
}
|
|
67
77
|
function timeframeMs(value) {
|
|
@@ -94,20 +104,26 @@ function favoriteTimeframeChips(favorites) {
|
|
|
94
104
|
return 0;
|
|
95
105
|
});
|
|
96
106
|
}
|
|
97
|
-
function timeframeLabel(value) {
|
|
98
|
-
const parsed = parseTimeframe(value);
|
|
107
|
+
function timeframeLabel(value, i18n) {
|
|
108
|
+
const parsed = parseTimeframe(value, i18n);
|
|
99
109
|
if (!parsed.valid) return value;
|
|
100
110
|
const ms = parsed.ms;
|
|
101
111
|
const order = ["Y", "M", "W", "D", "H", "MIN", "S"];
|
|
102
112
|
for (const key of order) {
|
|
103
113
|
if (ms % UNIT_MS[key] === 0 && ms / UNIT_MS[key] >= 1) {
|
|
104
|
-
return
|
|
114
|
+
return shortLabel(key, ms / UNIT_MS[key], i18n);
|
|
105
115
|
}
|
|
106
116
|
}
|
|
107
117
|
return parsed.short;
|
|
108
118
|
}
|
|
109
119
|
|
|
110
120
|
// src/widget/layout-picker.ts
|
|
121
|
+
var TEXT = {
|
|
122
|
+
layout: msg("widget.layoutPicker.layout", "Layout"),
|
|
123
|
+
sync: msg("widget.layoutPicker.sync", "Sync"),
|
|
124
|
+
gridHint: msg("widget.layoutPicker.gridHint", "Click a square to apply that columns × rows layout."),
|
|
125
|
+
gridSize: msg("widget.layoutPicker.gridSize", "{cols} × {rows}")
|
|
126
|
+
};
|
|
111
127
|
var STYLE_ID = "vela-widget-layout-picker-v15";
|
|
112
128
|
var CSS = `
|
|
113
129
|
.vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
|
|
@@ -172,7 +188,7 @@ var CSS = `
|
|
|
172
188
|
transition: background var(--vela-dur-fast) var(--vela-ease), border-color var(--vela-dur-fast) var(--vela-ease);
|
|
173
189
|
}
|
|
174
190
|
.vela-lp-sq:hover { background: var(--vela-hover-strong); border-color: var(--vela-border-strong); }
|
|
175
|
-
/* Selected/previewed squares
|
|
191
|
+
/* Selected/previewed squares — the shared monochrome selection chip. */
|
|
176
192
|
.vela-lp-sq[data-on='1'] {
|
|
177
193
|
background: var(--vela-selected-bg);
|
|
178
194
|
border-color: var(--vela-selected-bg);
|
|
@@ -186,7 +202,7 @@ var CSS = `
|
|
|
186
202
|
.vela-lp-sync-row { all: unset; display: flex; align-items: center; gap: 14px; padding: 6px 8px; border-radius: 5px; cursor: pointer; }
|
|
187
203
|
.vela-lp-sync-row:hover { background: var(--vela-hover); }
|
|
188
204
|
.vela-lp-sync-row .vela-lp-label { flex: 1 1 auto; }
|
|
189
|
-
/* Toggle pill
|
|
205
|
+
/* Toggle pill — same control language as the menu's switch rows, monochrome ON state. */
|
|
190
206
|
.vela-lp-switch {
|
|
191
207
|
position: relative;
|
|
192
208
|
flex: none;
|
|
@@ -212,7 +228,10 @@ var CSS = `
|
|
|
212
228
|
.vela-lp-switch.on::after { transform: translateX(16px); background: var(--vela-selected-fg); }
|
|
213
229
|
`;
|
|
214
230
|
var GRID = 4;
|
|
215
|
-
function
|
|
231
|
+
function layoutGridLabel(cols, rows, i18n) {
|
|
232
|
+
return textOf(i18n, TEXT.gridSize, { cols, rows });
|
|
233
|
+
}
|
|
234
|
+
function layoutGridCanvas(doc, i18n) {
|
|
216
235
|
injectStyles(STYLE_ID, CSS, doc);
|
|
217
236
|
const el = doc.createElement("div");
|
|
218
237
|
el.className = "vela-lp-grid";
|
|
@@ -223,7 +242,7 @@ function layoutGridCanvas(doc) {
|
|
|
223
242
|
sq.className = "vela-lp-sq";
|
|
224
243
|
sq.dataset.r = String(r);
|
|
225
244
|
sq.dataset.c = String(c);
|
|
226
|
-
sq.setAttribute("aria-label",
|
|
245
|
+
sq.setAttribute("aria-label", textOf(i18n, TEXT.gridSize, { cols: c + 1, rows: r + 1 }));
|
|
227
246
|
squares.push(sq);
|
|
228
247
|
el.appendChild(sq);
|
|
229
248
|
}
|
|
@@ -240,6 +259,8 @@ function paintLayoutGrid(squares, shape) {
|
|
|
240
259
|
var LayoutPicker = class {
|
|
241
260
|
constructor(opts) {
|
|
242
261
|
this.squares = [];
|
|
262
|
+
/** Fixed text re-read in the current language on every open. */
|
|
263
|
+
this.fixedText = [];
|
|
243
264
|
this.isOpen = false;
|
|
244
265
|
/** The card's exit animation after a close; a reopen cancels it. */
|
|
245
266
|
this.exit = null;
|
|
@@ -272,7 +293,8 @@ var LayoutPicker = class {
|
|
|
272
293
|
const layoutHeading = doc.createElement("div");
|
|
273
294
|
layoutHeading.className = "vela-lp-heading";
|
|
274
295
|
const headingText = doc.createElement("span");
|
|
275
|
-
headingText.textContent =
|
|
296
|
+
headingText.textContent = textOf(opts.i18n, TEXT.layout);
|
|
297
|
+
this.fixedText.push({ el: headingText, text: TEXT.layout });
|
|
276
298
|
const badge = doc.createElement("span");
|
|
277
299
|
badge.className = "vela-lp-badge";
|
|
278
300
|
badge.textContent = "?";
|
|
@@ -283,7 +305,7 @@ var LayoutPicker = class {
|
|
|
283
305
|
placement: "bottom",
|
|
284
306
|
content: () => this.tipNode()
|
|
285
307
|
});
|
|
286
|
-
const { el: grid, squares } = layoutGridCanvas(doc);
|
|
308
|
+
const { el: grid, squares } = layoutGridCanvas(doc, opts.i18n);
|
|
287
309
|
this.squares.push(...squares);
|
|
288
310
|
layoutCol.appendChild(grid);
|
|
289
311
|
this.presetsEl = doc.createElement("div");
|
|
@@ -296,7 +318,8 @@ var LayoutPicker = class {
|
|
|
296
318
|
const syncCol = doc.createElement("div");
|
|
297
319
|
const syncHeading = doc.createElement("div");
|
|
298
320
|
syncHeading.className = "vela-lp-heading";
|
|
299
|
-
syncHeading.textContent =
|
|
321
|
+
syncHeading.textContent = textOf(opts.i18n, TEXT.sync);
|
|
322
|
+
this.fixedText.push({ el: syncHeading, text: TEXT.sync });
|
|
300
323
|
syncCol.appendChild(syncHeading);
|
|
301
324
|
this.syncEl = doc.createElement("div");
|
|
302
325
|
this.syncEl.className = "vela-lp-sync";
|
|
@@ -340,6 +363,7 @@ var LayoutPicker = class {
|
|
|
340
363
|
this.exit?.cancel();
|
|
341
364
|
this.exit = null;
|
|
342
365
|
this.hover = null;
|
|
366
|
+
this.relabel();
|
|
343
367
|
this.refresh();
|
|
344
368
|
this.layer.style.display = "";
|
|
345
369
|
this.position();
|
|
@@ -375,6 +399,16 @@ var LayoutPicker = class {
|
|
|
375
399
|
this.infoTip.destroy();
|
|
376
400
|
this.layer.remove();
|
|
377
401
|
}
|
|
402
|
+
/** Fixed text and square names in the current language. */
|
|
403
|
+
relabel() {
|
|
404
|
+
const i18n = this.opts.i18n;
|
|
405
|
+
for (const { el, text } of this.fixedText) el.textContent = textOf(i18n, text);
|
|
406
|
+
this.infoTip.setContent(() => this.tipNode());
|
|
407
|
+
for (const sq of this.squares) {
|
|
408
|
+
const { r, c } = this.squarePos(sq);
|
|
409
|
+
sq.setAttribute("aria-label", textOf(i18n, TEXT.gridSize, { cols: c + 1, rows: r + 1 }));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
378
412
|
// ── internals ──
|
|
379
413
|
squareAt(e) {
|
|
380
414
|
const sq = e.target?.closest?.(".vela-lp-sq");
|
|
@@ -387,7 +421,7 @@ var LayoutPicker = class {
|
|
|
387
421
|
tipNode() {
|
|
388
422
|
const tip = this.doc.createElement("div");
|
|
389
423
|
tip.className = "vela-lp-tip";
|
|
390
|
-
tip.textContent =
|
|
424
|
+
tip.textContent = textOf(this.opts.i18n, TEXT.gridHint);
|
|
391
425
|
return tip;
|
|
392
426
|
}
|
|
393
427
|
/** Project the interaction state onto the DOM (the hover/current rectangle). */
|
|
@@ -504,7 +538,7 @@ var CSS2 = `
|
|
|
504
538
|
color: var(--vela-fg-muted);
|
|
505
539
|
}
|
|
506
540
|
.vela-widget-tf-caret:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
507
|
-
/* The merged trigger is a plain button (hover feedback only)
|
|
541
|
+
/* The merged trigger is a plain button (hover feedback only) — the highlight
|
|
508
542
|
background marks the CURRENT chip among favorites, and a lone trigger with a
|
|
509
543
|
permanent highlight would read as stuck-pressed. */
|
|
510
544
|
.vela-widget-tf-caret[data-solo='1'] {
|
|
@@ -518,8 +552,8 @@ var CSS2 = `
|
|
|
518
552
|
}
|
|
519
553
|
.vela-widget-topbar .vela-widget-tf-caret .vela-icon { font-size: 14px; width: 14px; height: 14px; }
|
|
520
554
|
.vela-widget-topbar .vela-icon { color: inherit; font-size: 16px; width: 16px; height: 16px; }
|
|
521
|
-
/* Width is set in syncHairlines() to exactly one device pixel
|
|
522
|
-
fractional DPR (1.25, 1.5
|
|
555
|
+
/* Width is set in syncHairlines() to exactly one device pixel — a CSS 1px at
|
|
556
|
+
fractional DPR (1.25, 1.5…) straddles two physical pixels and siblings end
|
|
523
557
|
up looking like different thicknesses depending on subpixel placement. */
|
|
524
558
|
.vela-sep { height: 22px; margin: 0 2px; flex: none; background: var(--vela-border-strong); }
|
|
525
559
|
.vela-alerts-badge {
|
|
@@ -538,15 +572,15 @@ var CSS2 = `
|
|
|
538
572
|
align-items: center;
|
|
539
573
|
justify-content: center;
|
|
540
574
|
}
|
|
541
|
-
/* The right side of the bar
|
|
575
|
+
/* The right side of the bar — whatever the composition puts there rides this one
|
|
542
576
|
auto-margin push (the flow-actions host used to carry it; composition can omit it). */
|
|
543
577
|
.vela-topbar-right { margin-left: auto; display: inline-flex; align-items: center; gap: var(--vela-space-1); }
|
|
544
578
|
.vela-widget-actions { display: inline-flex; gap: var(--vela-space-1); }
|
|
545
|
-
/* Left-aligned contributed actions
|
|
579
|
+
/* Left-aligned contributed actions — the primary-chrome cluster after the dropdowns. */
|
|
546
580
|
.vela-widget-actions-left { display: inline-flex; align-items: center; gap: var(--vela-space-1); }
|
|
547
581
|
/* One PINNED contributed action's slot (a composition entry naming the action's id). */
|
|
548
582
|
.vela-widget-action-pin { display: inline-flex; align-items: center; }
|
|
549
|
-
/* The side-panel toggles, one per docked panel
|
|
583
|
+
/* The side-panel toggles, one per docked panel — a group so the dock can rebuild them
|
|
550
584
|
without disturbing the tools around it. */
|
|
551
585
|
.vela-widget-panels { display: inline-flex; align-items: center; gap: var(--vela-space-1); }
|
|
552
586
|
.vela-widget-tool {
|
|
@@ -576,15 +610,8 @@ var CSS2 = `
|
|
|
576
610
|
}
|
|
577
611
|
.vela-widget-action:hover { background: var(--vela-hover); }
|
|
578
612
|
`;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
bars: "Bars",
|
|
582
|
-
line: "Line",
|
|
583
|
-
area: "Area",
|
|
584
|
-
baseline: "Baseline"
|
|
585
|
-
};
|
|
586
|
-
function priceStyleLabel(id) {
|
|
587
|
-
return chartType(id)?.label ?? BUILTIN_STYLE_LABELS[id] ?? id;
|
|
613
|
+
function priceStyleLabel(id, i18n) {
|
|
614
|
+
return textOf(i18n, priceStyleMessage(id));
|
|
588
615
|
}
|
|
589
616
|
function priceStyleIcon(id) {
|
|
590
617
|
const iconId = `style-${id}`;
|
|
@@ -596,6 +623,21 @@ function priceStyleIcon(id) {
|
|
|
596
623
|
}
|
|
597
624
|
return void 0;
|
|
598
625
|
}
|
|
626
|
+
var TEXT2 = {
|
|
627
|
+
timeframes: msg("widget.topbar.timeframes", "Timeframes"),
|
|
628
|
+
indicators: msg("widget.topbar.indicators", "Indicators"),
|
|
629
|
+
undo: msg("widget.topbar.undo", "Undo"),
|
|
630
|
+
redo: msg("widget.topbar.redo", "Redo"),
|
|
631
|
+
screenshot: msg("widget.topbar.screenshot", "Download screenshot"),
|
|
632
|
+
alerts: msg("widget.topbar.alerts", "Alerts"),
|
|
633
|
+
timeframe: msg("widget.topbar.timeframe", "Timeframe"),
|
|
634
|
+
chartStyle: msg("widget.topbar.chartStyle", "Chart style"),
|
|
635
|
+
layout: msg("widget.topbar.layout", "Layout"),
|
|
636
|
+
switchTimeframe: msg("widget.topbar.switchTimeframe", "Switch timeframe to {timeframe}"),
|
|
637
|
+
currentTimeframe: msg("widget.topbar.currentTimeframe", "Timeframe — {timeframe}"),
|
|
638
|
+
currentLayout: msg("widget.topbar.currentLayout", "Layout — {layout}"),
|
|
639
|
+
currentStyle: msg("widget.topbar.currentStyle", "Chart style — {style}")
|
|
640
|
+
};
|
|
599
641
|
var Topbar = class {
|
|
600
642
|
constructor(host, opts) {
|
|
601
643
|
this.layoutButton = null;
|
|
@@ -625,6 +667,8 @@ var Topbar = class {
|
|
|
625
667
|
};
|
|
626
668
|
this.hairlineRo = null;
|
|
627
669
|
this.hairlineRaf = 0;
|
|
670
|
+
/** Re-apply each fixed label in the current language (see {@link relocalize}). */
|
|
671
|
+
this.relabels = [];
|
|
628
672
|
this.opts = opts;
|
|
629
673
|
this.host = host;
|
|
630
674
|
this.timeframe = opts.timeframe;
|
|
@@ -645,7 +689,7 @@ var Topbar = class {
|
|
|
645
689
|
this.tfCaret = doc.createElement("button");
|
|
646
690
|
this.tfCaret.className = "vela-widget-tf-caret";
|
|
647
691
|
this.tfCaret.appendChild(iconEl("chevron-down", doc));
|
|
648
|
-
this.tfCaret.setAttribute("aria-label",
|
|
692
|
+
this.tfCaret.setAttribute("aria-label", this.t(TEXT2.timeframes));
|
|
649
693
|
const tfGroup = doc.createElement("span");
|
|
650
694
|
tfGroup.className = "vela-widget-tf-group";
|
|
651
695
|
tfGroup.append(this.tfChipsHost, this.tfCaret);
|
|
@@ -656,7 +700,9 @@ var Topbar = class {
|
|
|
656
700
|
if (opts.onIndicatorsClick && !topbarActionOverride("indicators")) {
|
|
657
701
|
indicatorsBtn = doc.createElement("button");
|
|
658
702
|
indicatorsBtn.className = "vela-widget-indicators";
|
|
659
|
-
|
|
703
|
+
const label = doc.createTextNode(this.t(TEXT2.indicators));
|
|
704
|
+
indicatorsBtn.append(iconEl("indicators", doc), label);
|
|
705
|
+
this.relabels.push(() => label.textContent = this.t(TEXT2.indicators));
|
|
660
706
|
indicatorsBtn.addEventListener("click", opts.onIndicatorsClick);
|
|
661
707
|
}
|
|
662
708
|
this.actionsHost = doc.createElement("span");
|
|
@@ -667,15 +713,15 @@ var Topbar = class {
|
|
|
667
713
|
this.panelsHost.className = "vela-widget-panels";
|
|
668
714
|
const tool = (cls, icon2, tip, onClick) => this.toolButton(cls, icon2, tip, onClick, this.tooltips);
|
|
669
715
|
if (vis("undo-redo")) {
|
|
670
|
-
this.undoBtn = tool("vela-widget-undo", "undo",
|
|
671
|
-
this.redoBtn = tool("vela-widget-redo", "redo",
|
|
716
|
+
this.undoBtn = tool("vela-widget-undo", "undo", TEXT2.undo, opts.onUndoClick);
|
|
717
|
+
this.redoBtn = tool("vela-widget-redo", "redo", TEXT2.redo, opts.onRedoClick);
|
|
672
718
|
} else {
|
|
673
719
|
this.undoBtn = doc.createElement("button");
|
|
674
720
|
this.redoBtn = doc.createElement("button");
|
|
675
721
|
}
|
|
676
722
|
this.setHistoryState(false, false);
|
|
677
|
-
const screenshotBtn = vis("screenshot") && !topbarActionOverride("screenshot") ? tool("vela-widget-screenshot", "camera",
|
|
678
|
-
this.alertsBtn = vis("alerts") ? tool("vela-widget-alerts", "bell",
|
|
723
|
+
const screenshotBtn = vis("screenshot") && !topbarActionOverride("screenshot") ? tool("vela-widget-screenshot", "camera", TEXT2.screenshot, opts.onScreenshotClick) : null;
|
|
724
|
+
this.alertsBtn = vis("alerts") ? tool("vela-widget-alerts", "bell", TEXT2.alerts) : doc.createElement("button");
|
|
679
725
|
this.alertsBtn.style.position = "relative";
|
|
680
726
|
if (opts.onAlertsClick) this.alertsBtn.addEventListener("click", () => opts.onAlertsClick(this.alertsBtn));
|
|
681
727
|
this.alertsBadge = doc.createElement("span");
|
|
@@ -759,14 +805,15 @@ var Topbar = class {
|
|
|
759
805
|
this.hairlineRo.observe(this.el);
|
|
760
806
|
doc.defaultView?.addEventListener("resize", this.onHairlineSync);
|
|
761
807
|
this.renderActions();
|
|
762
|
-
this.
|
|
763
|
-
this.
|
|
808
|
+
this.labeledTooltip(this.tfCaret, TEXT2.timeframe, "vela-topbar-tf", this.tooltips);
|
|
809
|
+
this.labeledTooltip(this.styleButton, TEXT2.chartStyle, "vela-topbar-style", this.tooltips);
|
|
764
810
|
if (this.layoutButton && opts.layout) {
|
|
765
|
-
this.
|
|
811
|
+
this.labeledTooltip(this.layoutButton, TEXT2.layout, "vela-topbar-layout", this.tooltips);
|
|
766
812
|
const layout = opts.layout;
|
|
767
813
|
this.layoutPicker = new LayoutPicker({
|
|
768
814
|
trigger: this.layoutButton,
|
|
769
815
|
host,
|
|
816
|
+
...opts.i18n ? { i18n: opts.i18n } : {},
|
|
770
817
|
shape: () => layout.shape(),
|
|
771
818
|
presets: () => layout.presets().map((p) => ({ ...p, checked: p.id === this.layoutId })),
|
|
772
819
|
onSelectGrid: (rows, cols) => layout.onSelectGrid(rows, cols),
|
|
@@ -779,6 +826,7 @@ var Topbar = class {
|
|
|
779
826
|
trigger: this.tfCaret,
|
|
780
827
|
triggerId: "vela-topbar-tf",
|
|
781
828
|
host,
|
|
829
|
+
i18n: opts.i18n ?? null,
|
|
782
830
|
items: this.tfItems(),
|
|
783
831
|
onSelect: (id) => opts.onTimeframe(id),
|
|
784
832
|
onFavorite: (id, on) => opts.onTimeframeFavorite?.(id, on),
|
|
@@ -795,6 +843,12 @@ var Topbar = class {
|
|
|
795
843
|
iconBadges: true
|
|
796
844
|
});
|
|
797
845
|
}
|
|
846
|
+
get i18n() {
|
|
847
|
+
return this.opts.i18n ?? null;
|
|
848
|
+
}
|
|
849
|
+
t(m, values) {
|
|
850
|
+
return textOf(this.i18n, m, values);
|
|
851
|
+
}
|
|
798
852
|
setSymbol(symbol) {
|
|
799
853
|
this.symbolEl.textContent = parseSymbol(symbol).ticker;
|
|
800
854
|
}
|
|
@@ -820,13 +874,13 @@ var Topbar = class {
|
|
|
820
874
|
for (const tf of shown) {
|
|
821
875
|
const b = doc.createElement("button");
|
|
822
876
|
b.className = "vela-widget-tf";
|
|
823
|
-
const label = timeframeLabel(tf);
|
|
877
|
+
const label = timeframeLabel(tf, this.i18n);
|
|
824
878
|
b.textContent = label;
|
|
825
879
|
if (tf === this.timeframe) {
|
|
826
880
|
b.dataset.current = "1";
|
|
827
881
|
b.setAttribute("aria-current", "true");
|
|
828
882
|
} else {
|
|
829
|
-
b.setAttribute("aria-label",
|
|
883
|
+
b.setAttribute("aria-label", this.t(TEXT2.switchTimeframe, { timeframe: label }));
|
|
830
884
|
b.addEventListener("click", () => this.opts.onTimeframe(tf));
|
|
831
885
|
}
|
|
832
886
|
this.tfChipsHost.appendChild(b);
|
|
@@ -834,12 +888,12 @@ var Topbar = class {
|
|
|
834
888
|
this.tfCaret.replaceChildren();
|
|
835
889
|
if (chips.length === 0) {
|
|
836
890
|
this.tfCaret.dataset.solo = "1";
|
|
837
|
-
this.tfCaret.append(doc.createTextNode(timeframeLabel(this.timeframe)), iconEl("chevron-down", doc));
|
|
838
|
-
this.tfCaret.setAttribute("aria-label",
|
|
891
|
+
this.tfCaret.append(doc.createTextNode(timeframeLabel(this.timeframe, this.i18n)), iconEl("chevron-down", doc));
|
|
892
|
+
this.tfCaret.setAttribute("aria-label", this.t(TEXT2.currentTimeframe, { timeframe: timeframeLabel(this.timeframe, this.i18n) }));
|
|
839
893
|
} else {
|
|
840
894
|
delete this.tfCaret.dataset.solo;
|
|
841
895
|
this.tfCaret.appendChild(iconEl("chevron-down", doc));
|
|
842
|
-
this.tfCaret.setAttribute("aria-label",
|
|
896
|
+
this.tfCaret.setAttribute("aria-label", this.t(TEXT2.timeframes));
|
|
843
897
|
}
|
|
844
898
|
this.onHairlineSync();
|
|
845
899
|
}
|
|
@@ -866,14 +920,14 @@ var Topbar = class {
|
|
|
866
920
|
} else {
|
|
867
921
|
this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
|
|
868
922
|
}
|
|
869
|
-
this.layoutButton.setAttribute("aria-label",
|
|
923
|
+
this.layoutButton.setAttribute("aria-label", this.t(TEXT2.currentLayout, { layout: this.layoutId ?? "" }));
|
|
870
924
|
}
|
|
871
925
|
renderStyleButton(doc) {
|
|
872
926
|
this.styleButton.replaceChildren();
|
|
873
927
|
const icon2 = priceStyleIcon(this.priceStyle);
|
|
874
928
|
if (icon2) this.styleButton.appendChild(iconEl(icon2, doc));
|
|
875
|
-
else this.styleButton.appendChild(doc.createTextNode(priceStyleLabel(this.priceStyle)));
|
|
876
|
-
this.styleButton.setAttribute("aria-label",
|
|
929
|
+
else this.styleButton.appendChild(doc.createTextNode(priceStyleLabel(this.priceStyle, this.i18n)));
|
|
930
|
+
this.styleButton.setAttribute("aria-label", this.t(TEXT2.currentStyle, { style: priceStyleLabel(this.priceStyle, this.i18n) }));
|
|
877
931
|
}
|
|
878
932
|
/** Re-project the contributed topbar actions (call after registrations change).
|
|
879
933
|
* An action PINNED by the composition renders into its named slot (list position
|
|
@@ -898,16 +952,17 @@ var Topbar = class {
|
|
|
898
952
|
const iconOnly = action.iconOnly === true && !!action.icon;
|
|
899
953
|
if (action.iconOnly === true && !action.icon && !this.warnedIconless.has(action.id)) {
|
|
900
954
|
this.warnedIconless.add(action.id);
|
|
901
|
-
console.warn(`[vela] widget action "${action.id}": iconOnly needs an \`icon\`
|
|
955
|
+
console.warn(`[vela] widget action "${action.id}": iconOnly needs an \`icon\` — rendering the label instead.`);
|
|
902
956
|
}
|
|
903
957
|
const b = doc.createElement("button");
|
|
904
958
|
b.className = left ? "vela-widget-action-left" : iconOnly ? "vela-widget-tool" : "vela-widget-action";
|
|
905
959
|
if (action.icon) b.appendChild(iconEl(action.icon, doc));
|
|
960
|
+
const label = textOf(ctx?.i18n ?? this.i18n, action.label);
|
|
906
961
|
if (iconOnly) {
|
|
907
|
-
b.setAttribute("aria-label",
|
|
908
|
-
this.actionTooltips.push(new Tooltip(b, { content:
|
|
962
|
+
b.setAttribute("aria-label", label);
|
|
963
|
+
this.actionTooltips.push(new Tooltip(b, { content: label, triggerId: `vela-action-${action.id}`, host: this.host }));
|
|
909
964
|
} else {
|
|
910
|
-
b.appendChild(doc.createTextNode(
|
|
965
|
+
b.appendChild(doc.createTextNode(label));
|
|
911
966
|
}
|
|
912
967
|
b.addEventListener("click", () => {
|
|
913
968
|
const c = this.opts.getContext?.();
|
|
@@ -968,11 +1023,33 @@ var Topbar = class {
|
|
|
968
1023
|
const b = doc.createElement("button");
|
|
969
1024
|
b.className = `vela-widget-tool ${cls}`;
|
|
970
1025
|
b.appendChild(iconEl(icon2, doc));
|
|
971
|
-
b.setAttribute("aria-label", tip);
|
|
972
|
-
|
|
1026
|
+
b.setAttribute("aria-label", this.t(tip));
|
|
1027
|
+
const tooltip = new Tooltip(b, { content: this.t(tip), triggerId: `vela-tool-${cls}`, host: this.host });
|
|
1028
|
+
sink.push(tooltip);
|
|
1029
|
+
this.relabels.push(() => {
|
|
1030
|
+
b.setAttribute("aria-label", this.t(tip));
|
|
1031
|
+
tooltip.setContent(this.t(tip));
|
|
1032
|
+
});
|
|
973
1033
|
if (onClick) b.addEventListener("click", onClick);
|
|
974
1034
|
return b;
|
|
975
1035
|
}
|
|
1036
|
+
/** A kit tooltip whose text follows the language. */
|
|
1037
|
+
labeledTooltip(el, text, triggerId, sink) {
|
|
1038
|
+
const tooltip = new Tooltip(el, { content: this.t(text), triggerId, host: this.host });
|
|
1039
|
+
sink.push(tooltip);
|
|
1040
|
+
this.relabels.push(() => tooltip.setContent(this.t(text)));
|
|
1041
|
+
}
|
|
1042
|
+
/** Re-read every label, tooltip and menu in the current language. */
|
|
1043
|
+
relocalize() {
|
|
1044
|
+
for (const relabel of this.relabels) relabel();
|
|
1045
|
+
this.renderTfChips();
|
|
1046
|
+
this.renderStyleButton(this.styleButton.ownerDocument);
|
|
1047
|
+
if (this.layoutButton) this.renderLayoutButton(this.layoutButton.ownerDocument);
|
|
1048
|
+
this.tfMenu.setItems(this.tfItems());
|
|
1049
|
+
this.styleMenu.setItems(this.styleItems());
|
|
1050
|
+
this.layoutPicker?.relabel();
|
|
1051
|
+
this.renderActions();
|
|
1052
|
+
}
|
|
976
1053
|
/** Paint each `.vela-sep` as exactly one device pixel, snapped to the pixel grid. */
|
|
977
1054
|
syncHairlines() {
|
|
978
1055
|
const win = this.el.ownerDocument.defaultView;
|
|
@@ -990,7 +1067,7 @@ var Topbar = class {
|
|
|
990
1067
|
const stars = this.opts.onTimeframeFavorite !== void 0;
|
|
991
1068
|
return this.opts.timeframes.map((tf) => ({
|
|
992
1069
|
id: tf,
|
|
993
|
-
label: timeframeLabel(tf),
|
|
1070
|
+
label: timeframeLabel(tf, this.i18n),
|
|
994
1071
|
checked: tf === this.timeframe,
|
|
995
1072
|
...stars ? { favorite: this.tfFavs.includes(tf) } : {}
|
|
996
1073
|
}));
|
|
@@ -998,7 +1075,7 @@ var Topbar = class {
|
|
|
998
1075
|
styleItems() {
|
|
999
1076
|
return priceStyleIds().map((id, i) => ({
|
|
1000
1077
|
id,
|
|
1001
|
-
label: priceStyleLabel(id),
|
|
1078
|
+
label: priceStyleLabel(id, this.i18n),
|
|
1002
1079
|
icon: priceStyleIcon(id),
|
|
1003
1080
|
checked: id === this.priceStyle,
|
|
1004
1081
|
separatorBefore: i === BUILTIN_PRICE_STYLES.length
|
|
@@ -1098,12 +1175,34 @@ var CSS3 = `
|
|
|
1098
1175
|
}
|
|
1099
1176
|
.vela-bb-settings:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
1100
1177
|
`;
|
|
1178
|
+
var TEXT3 = {
|
|
1179
|
+
timeZone: msg("widget.bottombar.timeZone", "Time zone"),
|
|
1180
|
+
session: msg("widget.bottombar.session", "Session — regular (RTH) vs extended (ETH) hours"),
|
|
1181
|
+
regular: msg("widget.bottombar.regular", "RTH"),
|
|
1182
|
+
extended: msg("widget.bottombar.extended", "ETH"),
|
|
1183
|
+
chartSettings: msg("widget.bottombar.chartSettings", "Chart settings")
|
|
1184
|
+
};
|
|
1185
|
+
var RANGE_LABELS = {
|
|
1186
|
+
"1D": msg("widget.bottombar.range.1d", "1D"),
|
|
1187
|
+
"7D": msg("widget.bottombar.range.7d", "7D"),
|
|
1188
|
+
"1M": msg("widget.bottombar.range.1m", "1M"),
|
|
1189
|
+
"3M": msg("widget.bottombar.range.3m", "3M"),
|
|
1190
|
+
"6M": msg("widget.bottombar.range.6m", "6M"),
|
|
1191
|
+
YTD: msg("widget.bottombar.range.ytd", "YTD"),
|
|
1192
|
+
"1Y": msg("widget.bottombar.range.1y", "1Y"),
|
|
1193
|
+
"5Y": msg("widget.bottombar.range.5y", "5Y"),
|
|
1194
|
+
ALL: msg("widget.bottombar.range.all", "ALL")
|
|
1195
|
+
};
|
|
1196
|
+
function rangeLabel(id, i18n) {
|
|
1197
|
+
return textOf(i18n, RANGE_LABELS[id] ?? id);
|
|
1198
|
+
}
|
|
1101
1199
|
var Bottombar = class {
|
|
1102
1200
|
constructor(host, opts) {
|
|
1103
1201
|
this.settingsTip = null;
|
|
1104
1202
|
this.rangeButtons = /* @__PURE__ */ new Map();
|
|
1105
1203
|
this.sessionButtons = /* @__PURE__ */ new Map();
|
|
1106
1204
|
this.sessionEl = null;
|
|
1205
|
+
this.i18n = opts.i18n ?? null;
|
|
1107
1206
|
this.timezone = opts.timezone;
|
|
1108
1207
|
this.exchangeTimezone = opts.exchangeTimezone;
|
|
1109
1208
|
const doc = host.ownerDocument;
|
|
@@ -1113,7 +1212,7 @@ var Bottombar = class {
|
|
|
1113
1212
|
for (const preset of RANGE_PRESETS) {
|
|
1114
1213
|
const b = doc.createElement("button");
|
|
1115
1214
|
b.className = "vela-bb-range";
|
|
1116
|
-
b.textContent = preset.id;
|
|
1215
|
+
b.textContent = this.t(RANGE_LABELS[preset.id] ?? preset.id);
|
|
1117
1216
|
b.addEventListener("click", () => {
|
|
1118
1217
|
this.setActiveRange(preset.id);
|
|
1119
1218
|
opts.onRange(preset);
|
|
@@ -1125,7 +1224,7 @@ var Bottombar = class {
|
|
|
1125
1224
|
spacer.className = "vela-bb-spacer";
|
|
1126
1225
|
this.tzButton = doc.createElement("button");
|
|
1127
1226
|
this.tzButton.className = "vela-bb-tz";
|
|
1128
|
-
this.tzButton.setAttribute("aria-label",
|
|
1227
|
+
this.tzButton.setAttribute("aria-label", this.t(TEXT3.timeZone));
|
|
1129
1228
|
this.clockEl = doc.createElement("span");
|
|
1130
1229
|
this.clockEl.className = "vela-bb-clock";
|
|
1131
1230
|
this.tzLabelEl = doc.createElement("span");
|
|
@@ -1135,11 +1234,11 @@ var Bottombar = class {
|
|
|
1135
1234
|
session.className = "vela-bb-session";
|
|
1136
1235
|
this.sessionEl = session;
|
|
1137
1236
|
session.style.display = "none";
|
|
1138
|
-
session.title =
|
|
1139
|
-
for (const [key, label] of [["regular",
|
|
1237
|
+
session.title = this.t(TEXT3.session);
|
|
1238
|
+
for (const [key, label] of [["regular", TEXT3.regular], ["extended", TEXT3.extended]]) {
|
|
1140
1239
|
const b = doc.createElement("button");
|
|
1141
1240
|
b.className = "vela-bb-session-btn" + (key === "regular" ? " is-active" : "");
|
|
1142
|
-
b.textContent = label;
|
|
1241
|
+
b.textContent = this.t(label);
|
|
1143
1242
|
b.disabled = true;
|
|
1144
1243
|
b.addEventListener("click", () => {
|
|
1145
1244
|
if (b.disabled) return;
|
|
@@ -1150,11 +1249,12 @@ var Bottombar = class {
|
|
|
1150
1249
|
session.appendChild(b);
|
|
1151
1250
|
}
|
|
1152
1251
|
const settingsBtn = doc.createElement("button");
|
|
1252
|
+
this.settingsBtn = settingsBtn;
|
|
1153
1253
|
settingsBtn.className = "vela-bb-settings";
|
|
1154
1254
|
settingsBtn.appendChild(iconEl("gear", doc));
|
|
1155
|
-
settingsBtn.setAttribute("aria-label",
|
|
1255
|
+
settingsBtn.setAttribute("aria-label", this.t(TEXT3.chartSettings));
|
|
1156
1256
|
if (opts.onSettingsClick) settingsBtn.addEventListener("click", opts.onSettingsClick);
|
|
1157
|
-
this.settingsTip = new Tooltip(settingsBtn, { content:
|
|
1257
|
+
this.settingsTip = new Tooltip(settingsBtn, { content: this.t(TEXT3.chartSettings), triggerId: "vela-bb-settings", host });
|
|
1158
1258
|
this.el.append(spacer, this.tzButton, session, settingsBtn);
|
|
1159
1259
|
host.appendChild(this.el);
|
|
1160
1260
|
this.tzMenu = new Menu({
|
|
@@ -1217,20 +1317,25 @@ var Bottombar = class {
|
|
|
1217
1317
|
this.el.remove();
|
|
1218
1318
|
}
|
|
1219
1319
|
tzItems() {
|
|
1220
|
-
return timezoneMenuRows(this.timezone).map((r) => ({ id: r.value, label: r.label, checked: r.checked }));
|
|
1320
|
+
return timezoneMenuRows(this.timezone, this.i18n).map((r) => ({ id: r.value, label: r.label, checked: r.checked }));
|
|
1321
|
+
}
|
|
1322
|
+
t(m) {
|
|
1323
|
+
return textOf(this.i18n, m);
|
|
1324
|
+
}
|
|
1325
|
+
/** Re-read every label, the zone menu and the clock in the current language. */
|
|
1326
|
+
relocalize() {
|
|
1327
|
+
for (const [id, b] of this.rangeButtons) b.textContent = this.t(RANGE_LABELS[id] ?? id);
|
|
1328
|
+
this.tzButton.setAttribute("aria-label", this.t(TEXT3.timeZone));
|
|
1329
|
+
if (this.sessionEl) this.sessionEl.title = this.t(TEXT3.session);
|
|
1330
|
+
this.sessionButtons.get("regular").textContent = this.t(TEXT3.regular);
|
|
1331
|
+
this.sessionButtons.get("extended").textContent = this.t(TEXT3.extended);
|
|
1332
|
+
this.settingsBtn.setAttribute("aria-label", this.t(TEXT3.chartSettings));
|
|
1333
|
+
this.settingsTip?.setContent(this.t(TEXT3.chartSettings));
|
|
1334
|
+
this.tzMenu.setItems(this.tzItems());
|
|
1335
|
+
this.tick();
|
|
1221
1336
|
}
|
|
1222
1337
|
tick() {
|
|
1223
|
-
|
|
1224
|
-
this.clockEl.textContent = new Intl.DateTimeFormat("en-GB", {
|
|
1225
|
-
hour: "2-digit",
|
|
1226
|
-
minute: "2-digit",
|
|
1227
|
-
second: "2-digit",
|
|
1228
|
-
hour12: false,
|
|
1229
|
-
timeZone: this.displayZone
|
|
1230
|
-
}).format(/* @__PURE__ */ new Date());
|
|
1231
|
-
} catch {
|
|
1232
|
-
this.clockEl.textContent = "";
|
|
1233
|
-
}
|
|
1338
|
+
this.clockEl.textContent = (this.i18n ?? englishTranslator()).format.clock(Date.now(), { timeZone: this.displayZone, surface: "clock" });
|
|
1234
1339
|
}
|
|
1235
1340
|
};
|
|
1236
1341
|
|
|
@@ -1269,23 +1374,29 @@ function tickerIconEl(doc, base, name, className, iconUrl) {
|
|
|
1269
1374
|
}
|
|
1270
1375
|
|
|
1271
1376
|
// src/widget/object-tree-model.ts
|
|
1377
|
+
var TEXT4 = {
|
|
1378
|
+
mainChart: msg("widget.objectTree.mainChart", "Main chart"),
|
|
1379
|
+
pane: msg("widget.objectTree.pane", "Panel {n}"),
|
|
1380
|
+
drawingWithText: msg("widget.objectTree.drawingWithText", "{name} — {text}"),
|
|
1381
|
+
group: msg("widget.objectTree.group", "Group {n}")
|
|
1382
|
+
};
|
|
1272
1383
|
var PRICE_PANE_ID = "price";
|
|
1273
|
-
|
|
1274
|
-
function drawingMeta(type) {
|
|
1275
|
-
const meta = getDrawingType(type);
|
|
1384
|
+
sourceText(TEXT4.mainChart);
|
|
1385
|
+
function drawingMeta(type, i18n) {
|
|
1386
|
+
const meta = getDrawingType(type, i18n ?? void 0);
|
|
1276
1387
|
if (meta) return { label: meta.label, icon: meta.icon };
|
|
1277
1388
|
return { label: String(type || "drawing").replace(/(^|\s)\S/g, (c) => c.toUpperCase()), icon: null };
|
|
1278
1389
|
}
|
|
1279
|
-
function drawingLabel(d) {
|
|
1280
|
-
const { label } = drawingMeta(d.type);
|
|
1390
|
+
function drawingLabel(d, i18n) {
|
|
1391
|
+
const { label } = drawingMeta(d.type, i18n);
|
|
1281
1392
|
const text = d.text?.value?.trim();
|
|
1282
|
-
return text ?
|
|
1393
|
+
return text ? textOf(i18n, TEXT4.drawingWithText, { name: label, text }) : label;
|
|
1283
1394
|
}
|
|
1284
|
-
function paneLabel(pane, fallbackTitle) {
|
|
1285
|
-
if (pane.kind === "price") return
|
|
1395
|
+
function paneLabel(pane, fallbackTitle, i18n) {
|
|
1396
|
+
if (pane.kind === "price") return textOf(i18n, TEXT4.mainChart);
|
|
1286
1397
|
const master = pane.indicators.find((i) => !i.ownScale) ?? pane.indicators[0];
|
|
1287
1398
|
if (master) return indicatorLabel(master, fallbackTitle);
|
|
1288
|
-
return
|
|
1399
|
+
return textOf(i18n, TEXT4.pane, { n: String(pane.order + 1) });
|
|
1289
1400
|
}
|
|
1290
1401
|
function indicatorLabel(i, fallbackTitle) {
|
|
1291
1402
|
return i.title || fallbackTitle(i.id) || i.id;
|
|
@@ -1372,13 +1483,14 @@ function groupState(members) {
|
|
|
1372
1483
|
function canGroup(ids, groups) {
|
|
1373
1484
|
return ids.length > 0 && ids.every((id) => groupOf(groups, id) === null);
|
|
1374
1485
|
}
|
|
1375
|
-
function nextGroupName(groups) {
|
|
1486
|
+
function nextGroupName(groups, i18n) {
|
|
1376
1487
|
const taken = new Set(groups.map((g) => g.name));
|
|
1488
|
+
const nameOf = (n) => textOf(i18n, TEXT4.group, { n: String(n) });
|
|
1377
1489
|
for (let n = 1; n <= groups.length; n += 1) {
|
|
1378
|
-
const name =
|
|
1490
|
+
const name = nameOf(n);
|
|
1379
1491
|
if (!taken.has(name)) return name;
|
|
1380
1492
|
}
|
|
1381
|
-
return
|
|
1493
|
+
return nameOf(groups.length + 1);
|
|
1382
1494
|
}
|
|
1383
1495
|
function withMembers(groups, keep) {
|
|
1384
1496
|
return groups.map((g) => ({ ...g, ids: g.ids.filter(keep) })).filter((g) => g.ids.length > 0);
|
|
@@ -1435,9 +1547,14 @@ function paneItems(snap, pane, units) {
|
|
|
1435
1547
|
return [...unitItems, ...rows2];
|
|
1436
1548
|
}
|
|
1437
1549
|
const zOf = new Map(snap.zOrder.map((e) => [e.id, e.z]));
|
|
1438
|
-
const
|
|
1439
|
-
|
|
1440
|
-
|
|
1550
|
+
const rank = new Map(snap.zOrder.map((e, i) => [e.id, i]));
|
|
1551
|
+
const rows = pane.indicators.map((i) => ({
|
|
1552
|
+
item: { kind: "row", row: indicatorRow(i) },
|
|
1553
|
+
z: zOf.get(i.id) ?? 0,
|
|
1554
|
+
rank: rank.get(i.id) ?? -1
|
|
1555
|
+
}));
|
|
1556
|
+
if (pane.kind === "price") rows.push({ item: { kind: "row", row: priceRow() }, z: snap.candleZ, rank: -Infinity });
|
|
1557
|
+
rows.sort((a, b) => b.z - a.z || b.rank - a.rank);
|
|
1441
1558
|
if (!snap.interleave) return [...unitItems, ...rows.map((r) => r.item)];
|
|
1442
1559
|
const entries = [...rows, ...units.map((unit) => ({ item: { kind: "unit", unit }, z: unitZ(unit) }))];
|
|
1443
1560
|
entries.sort((a, b) => a.z === b.z ? Number(a.item.kind === "unit") - Number(b.item.kind === "unit") : b.z - a.z);
|
|
@@ -1448,7 +1565,7 @@ function buildTree(snap) {
|
|
|
1448
1565
|
return snap.panes.map((pane) => ({
|
|
1449
1566
|
id: pane.id,
|
|
1450
1567
|
kind: pane.kind,
|
|
1451
|
-
label: paneLabel(pane, snap.handleTitle),
|
|
1568
|
+
label: paneLabel(pane, snap.handleTitle, snap.i18n),
|
|
1452
1569
|
order: pane.order,
|
|
1453
1570
|
collapsed: pane.collapsed,
|
|
1454
1571
|
maximized: pane.maximized,
|
|
@@ -1460,6 +1577,56 @@ function treeIsEmpty(panes) {
|
|
|
1460
1577
|
}
|
|
1461
1578
|
|
|
1462
1579
|
// src/widget/object-tree.ts
|
|
1580
|
+
var TEXT5 = {
|
|
1581
|
+
objectTree: msg("widget.objectTree.objectTree", "Object tree"),
|
|
1582
|
+
price: msg("widget.objectTree.price", "Price"),
|
|
1583
|
+
emptyHint: msg("widget.objectTree.emptyHint", "Add an indicator or a drawing to populate the tree"),
|
|
1584
|
+
groupSelected: msg("widget.objectTree.groupSelected", "Group the selected drawings"),
|
|
1585
|
+
groupDrawings: msg("widget.objectTree.groupDrawings", "{count, plural, one {Group this drawing} other {Group these drawings}}"),
|
|
1586
|
+
alreadyInAGroup: msg("widget.objectTree.alreadyInAGroup", "Already in a group"),
|
|
1587
|
+
duplicateSelected: msg("widget.objectTree.duplicateSelected", "Duplicate the selected drawings"),
|
|
1588
|
+
duplicate: msg("widget.objectTree.duplicate", "Duplicate"),
|
|
1589
|
+
movePaneUp: msg("widget.objectTree.movePaneUp", "Move panel up"),
|
|
1590
|
+
movePaneDown: msg("widget.objectTree.movePaneDown", "Move panel down"),
|
|
1591
|
+
expandPane: msg("widget.objectTree.expandPane", "Expand panel"),
|
|
1592
|
+
collapsePane: msg("widget.objectTree.collapsePane", "Collapse panel"),
|
|
1593
|
+
restorePanes: msg("widget.objectTree.restorePanes", "Restore panels"),
|
|
1594
|
+
maximizePane: msg("widget.objectTree.maximizePane", "Maximize panel"),
|
|
1595
|
+
hide: msg("widget.objectTree.hide", "Hide"),
|
|
1596
|
+
show: msg("widget.objectTree.show", "Show"),
|
|
1597
|
+
restackHint: msg("widget.objectTree.restackHint", "Drag to change what draws in front"),
|
|
1598
|
+
remove: msg("widget.objectTree.remove", "Remove"),
|
|
1599
|
+
moveIndicatorHint: msg("widget.objectTree.moveIndicatorHint", "Drag to another panel, or between panels for a new one"),
|
|
1600
|
+
unlockAll: msg("widget.objectTree.unlockAll", "Unlock all"),
|
|
1601
|
+
lockAll: msg("widget.objectTree.lockAll", "Lock all"),
|
|
1602
|
+
showAll: msg("widget.objectTree.showAll", "Show all"),
|
|
1603
|
+
hideAll: msg("widget.objectTree.hideAll", "Hide all"),
|
|
1604
|
+
removeAll: msg("widget.objectTree.removeAll", "Remove all"),
|
|
1605
|
+
unlock: msg("widget.objectTree.unlock", "Unlock"),
|
|
1606
|
+
lock: msg("widget.objectTree.lock", "Lock"),
|
|
1607
|
+
moveDrawingHint: msg("widget.objectTree.moveDrawingHint", "Drag to restack, or onto another panel to move it there"),
|
|
1608
|
+
ownScaleHint: msg("widget.objectTree.ownScaleHint", "Draws against its own price scale"),
|
|
1609
|
+
bringToFront: msg("widget.objectTree.bringToFront", "Bring to front"),
|
|
1610
|
+
sendToBack: msg("widget.objectTree.sendToBack", "Send to back"),
|
|
1611
|
+
indicatorSettings: msg("widget.objectTree.indicatorSettings", "Indicator settings"),
|
|
1612
|
+
moveTo: msg("widget.objectTree.moveTo", "Move to"),
|
|
1613
|
+
newPaneAbove: msg("widget.objectTree.newPaneAbove", "New panel above"),
|
|
1614
|
+
newPaneBelow: msg("widget.objectTree.newPaneBelow", "New panel below"),
|
|
1615
|
+
newGroup: msg("widget.objectTree.newGroup", "New group"),
|
|
1616
|
+
addToGroup: msg("widget.objectTree.addToGroup", "Add to group"),
|
|
1617
|
+
moveToGroup: msg("widget.objectTree.moveToGroup", "Move to group"),
|
|
1618
|
+
rename: msg("widget.objectTree.rename", "Rename…"),
|
|
1619
|
+
selectAll: msg("widget.objectTree.selectAll", "Select all"),
|
|
1620
|
+
bringAllToFront: msg("widget.objectTree.bringAllToFront", "Bring all to front"),
|
|
1621
|
+
sendAllToBack: msg("widget.objectTree.sendAllToBack", "Send all to back"),
|
|
1622
|
+
ungroup: msg("widget.objectTree.ungroup", "Ungroup"),
|
|
1623
|
+
selected: msg("widget.objectTree.selected", "{count, plural, one {# selected} other {# selected}}"),
|
|
1624
|
+
groupMembers: msg("widget.objectTree.groupMembers", "{count, plural, one {# drawing — drag to move it} other {# drawings — drag to move them together}}"),
|
|
1625
|
+
groupPick: msg("widget.objectTree.groupPick", "Group selection ({count})"),
|
|
1626
|
+
removeFromGroup: msg("widget.objectTree.removeFromGroup", "Remove from {group}"),
|
|
1627
|
+
scaleTag: msg("widget.objectTree.scaleTag", "scale")
|
|
1628
|
+
};
|
|
1629
|
+
var OBJECT_TREE_TITLE = TEXT5.objectTree;
|
|
1463
1630
|
var STYLE_ID4 = "vela-widget-objtree";
|
|
1464
1631
|
var CSS4 = `
|
|
1465
1632
|
/* One pane's block. The transparent border reserves the drop-target outline. */
|
|
@@ -1493,7 +1660,7 @@ var CSS4 = `
|
|
|
1493
1660
|
}
|
|
1494
1661
|
.vela-ot-row:hover { background: var(--vela-hover); }
|
|
1495
1662
|
.vela-ot-row > .vela-icon { color: var(--vela-fg-muted); width: 14px; height: 14px; font-size: 14px; justify-content: center; flex: none; }
|
|
1496
|
-
/* Drawing glyphs come from the type registry at toolbar scale
|
|
1663
|
+
/* Drawing glyphs come from the type registry at toolbar scale — bring them down to row size. */
|
|
1497
1664
|
.vela-ot-row > .vela-icon svg { width: 14px; height: 14px; }
|
|
1498
1665
|
.vela-ot-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1499
1666
|
.vela-ot-row[data-hidden] .vela-ot-name,
|
|
@@ -1553,9 +1720,9 @@ var CSS4 = `
|
|
|
1553
1720
|
.vela-ot-btn:disabled { opacity: 0.35; cursor: default; }
|
|
1554
1721
|
.vela-ot-empty { padding: 20px 10px; text-align: center; color: var(--vela-fg-muted); font-size: 12px; }
|
|
1555
1722
|
|
|
1556
|
-
/*
|
|
1723
|
+
/* ── drawing groups ── */
|
|
1557
1724
|
/* One top-level entry in a pane's drawing list: a lone drawing, or a whole group block. No
|
|
1558
|
-
border of its own
|
|
1725
|
+
border of its own — units stack flush, so consecutive highlighted rows read as one block; the
|
|
1559
1726
|
drop-into-group outline is drawn inside the box instead (see the drag-and-drop rules). */
|
|
1560
1727
|
.vela-ot-unit { border-radius: 6px; }
|
|
1561
1728
|
/* Adjacent picked rows merge into one contiguous highlight: the shared edge loses its rounding. */
|
|
@@ -1574,7 +1741,7 @@ var CSS4 = `
|
|
|
1574
1741
|
color: var(--vela-fg-bright);
|
|
1575
1742
|
font: inherit;
|
|
1576
1743
|
}
|
|
1577
|
-
/* The selection bar. Always there
|
|
1744
|
+
/* The selection bar. Always there — the actions it holds are the panel's, not a row's, so they
|
|
1578
1745
|
stay in place and simply go dim until a drawing is selected. It stays put at the top of the
|
|
1579
1746
|
list while the list scrolls under it, so they never scroll out of reach. */
|
|
1580
1747
|
.vela-ot-selbar {
|
|
@@ -1593,7 +1760,7 @@ var CSS4 = `
|
|
|
1593
1760
|
.vela-ot-selbar .vela-ot-btn { visibility: visible; width: 24px; height: 22px; }
|
|
1594
1761
|
.vela-ot-selbar .vela-ot-btn[data-icon='group'] .vela-icon { width: 16px; height: 16px; font-size: 16px; }
|
|
1595
1762
|
|
|
1596
|
-
/*
|
|
1763
|
+
/* ── drag-and-drop ── */
|
|
1597
1764
|
.vela-ot-row[data-drag] { cursor: grab; }
|
|
1598
1765
|
.vela-ot .vela-panel-body[data-dragging] .vela-ot-row[data-drag] { cursor: grabbing; }
|
|
1599
1766
|
/* The row in flight fades: the ghost under the pointer is the thing being moved. */
|
|
@@ -1679,8 +1846,8 @@ var MenuBuilder = class {
|
|
|
1679
1846
|
}
|
|
1680
1847
|
};
|
|
1681
1848
|
var ObjectTree = class extends SidePanel {
|
|
1682
|
-
constructor(host, iconFor) {
|
|
1683
|
-
super(host,
|
|
1849
|
+
constructor(host, iconFor, i18n) {
|
|
1850
|
+
super(host, TEXT5.objectTree, "vela-ot", i18n ? { i18n } : {});
|
|
1684
1851
|
this.iconFor = iconFor;
|
|
1685
1852
|
this.chart = null;
|
|
1686
1853
|
/** What the CHART has selected — every member of a multi-selection, mirrored as rows. */
|
|
@@ -1734,6 +1901,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1734
1901
|
for (const ev of REFRESH_EVENTS) {
|
|
1735
1902
|
this.unsubs.push(chart.on(ev, () => this.refresh()));
|
|
1736
1903
|
}
|
|
1904
|
+
this.unsubs.push(chart.renderer.onConfigChanged(() => this.refresh()));
|
|
1737
1905
|
this.unsubs.push(
|
|
1738
1906
|
chart.on("drawing:selected", ({ ids }) => {
|
|
1739
1907
|
this.selectedDrawings = new Set(ids);
|
|
@@ -1762,16 +1930,21 @@ var ObjectTree = class extends SidePanel {
|
|
|
1762
1930
|
panes: chart.panes.list(),
|
|
1763
1931
|
indicatorVisible: (id) => handle(id)?.visible !== false,
|
|
1764
1932
|
handleTitle: (id) => handle(id)?.title,
|
|
1933
|
+
i18n: this.i18n,
|
|
1765
1934
|
stackable,
|
|
1766
1935
|
interleave: stackable && chart.drawings.supported && chart.renderer.capabilities.drawingDepth === true,
|
|
1767
1936
|
zOrder: stackable ? chart.renderer.get("seriesOrder") ?? [] : [],
|
|
1768
1937
|
candleZ: stackable ? Number(chart.renderer.get("candleZOrder")) || 0 : 0,
|
|
1769
|
-
priceLabel: this.symbolName ||
|
|
1938
|
+
priceLabel: this.symbolName || this.t(TEXT5.price),
|
|
1770
1939
|
priceVisible: chart.renderer.get("candleVisible") !== false,
|
|
1771
1940
|
drawings: chart.drawings.supported ? chart.drawings.all() : [],
|
|
1772
1941
|
groups: this.groups
|
|
1773
1942
|
};
|
|
1774
1943
|
}
|
|
1944
|
+
relocalize() {
|
|
1945
|
+
super.relocalize();
|
|
1946
|
+
this.refresh();
|
|
1947
|
+
}
|
|
1775
1948
|
/**
|
|
1776
1949
|
* Repaint because something changed — unless the panel is mid-interaction. A live drag
|
|
1777
1950
|
* resolves drops against the rows as currently rendered, and a rename lives in an input
|
|
@@ -1802,7 +1975,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1802
1975
|
if (treeIsEmpty(panes)) {
|
|
1803
1976
|
const empty = doc.createElement("div");
|
|
1804
1977
|
empty.className = "vela-ot-empty";
|
|
1805
|
-
empty.textContent =
|
|
1978
|
+
empty.textContent = this.t(TEXT5.emptyHint);
|
|
1806
1979
|
this.body.appendChild(empty);
|
|
1807
1980
|
}
|
|
1808
1981
|
}
|
|
@@ -1817,7 +1990,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1817
1990
|
bar.appendChild(
|
|
1818
1991
|
this.btn({
|
|
1819
1992
|
icon: "group",
|
|
1820
|
-
title: ids.length === 0 ?
|
|
1993
|
+
title: ids.length === 0 ? this.t(TEXT5.groupSelected) : groupable ? this.t(TEXT5.groupDrawings, { count: ids.length }) : this.t(TEXT5.alreadyInAGroup),
|
|
1821
1994
|
disabled: !groupable,
|
|
1822
1995
|
run: () => this.makeGroup(ids)
|
|
1823
1996
|
})
|
|
@@ -1825,7 +1998,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1825
1998
|
bar.appendChild(
|
|
1826
1999
|
this.btn({
|
|
1827
2000
|
icon: "clone",
|
|
1828
|
-
title: ids.length === 0 ?
|
|
2001
|
+
title: ids.length === 0 ? this.t(TEXT5.duplicateSelected) : this.t(TEXT5.duplicate),
|
|
1829
2002
|
disabled: ids.length === 0,
|
|
1830
2003
|
run: () => {
|
|
1831
2004
|
for (const id of ids) this.cloneInto(pass, id);
|
|
@@ -1835,7 +2008,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1835
2008
|
);
|
|
1836
2009
|
const count = doc.createElement("span");
|
|
1837
2010
|
count.className = "vela-ot-selcount";
|
|
1838
|
-
count.textContent = ids.length > 1 ?
|
|
2011
|
+
count.textContent = ids.length > 1 ? this.t(TEXT5.selected, { count: ids.length }) : "";
|
|
1839
2012
|
bar.appendChild(count);
|
|
1840
2013
|
return bar;
|
|
1841
2014
|
}
|
|
@@ -1910,11 +2083,11 @@ var ObjectTree = class extends SidePanel {
|
|
|
1910
2083
|
acts.appendChild(this.btn({ icon: icon2, title, run }));
|
|
1911
2084
|
};
|
|
1912
2085
|
if (pane.kind !== "price") {
|
|
1913
|
-
if (pane.order > 1) op("arrow-up",
|
|
1914
|
-
if (pane.order < pass.panes.length - 1) op("arrow-down",
|
|
1915
|
-
op(pane.collapsed ? "expand" : "collapse", pane.collapsed ?
|
|
2086
|
+
if (pane.order > 1) op("arrow-up", this.t(TEXT5.movePaneUp), () => chart.panes.move(pane.id, "up"));
|
|
2087
|
+
if (pane.order < pass.panes.length - 1) op("arrow-down", this.t(TEXT5.movePaneDown), () => chart.panes.move(pane.id, "down"));
|
|
2088
|
+
op(pane.collapsed ? "expand" : "collapse", pane.collapsed ? this.t(TEXT5.expandPane) : this.t(TEXT5.collapsePane), () => chart.panes.collapse(pane.id, !pane.collapsed));
|
|
1916
2089
|
}
|
|
1917
|
-
op(pane.maximized ? "restore" : "maximize", pane.maximized ?
|
|
2090
|
+
op(pane.maximized ? "restore" : "maximize", pane.maximized ? this.t(TEXT5.restorePanes) : this.t(TEXT5.maximizePane), () => chart.panes.maximize(pane.maximized ? null : pane.id));
|
|
1918
2091
|
return head;
|
|
1919
2092
|
}
|
|
1920
2093
|
rowEl(pass, pane, row) {
|
|
@@ -1922,11 +2095,11 @@ var ObjectTree = class extends SidePanel {
|
|
|
1922
2095
|
const { chart } = pass;
|
|
1923
2096
|
if (row.kind === "price") {
|
|
1924
2097
|
const base = this.symbolName.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || this.symbolName;
|
|
1925
|
-
const icon2 = tickerIconEl(doc, base ||
|
|
2098
|
+
const icon2 = tickerIconEl(doc, base || sliceGraphemes(this.t(TEXT5.price), 1), this.symbolName || this.t(TEXT5.price), "vela-ot-avatar", this.symbolRaw ? this.iconFor?.(this.symbolRaw) : void 0);
|
|
1926
2099
|
const el2 = this.row(icon2, row.label, row.visible, [
|
|
1927
2100
|
{
|
|
1928
2101
|
icon: row.visible ? "eye" : "eye-off",
|
|
1929
|
-
title: row.visible ?
|
|
2102
|
+
title: row.visible ? this.t(TEXT5.hide) : this.t(TEXT5.show),
|
|
1930
2103
|
engaged: !row.visible,
|
|
1931
2104
|
run: () => {
|
|
1932
2105
|
chart.renderer.set("candleVisible", !row.visible);
|
|
@@ -1938,7 +2111,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1938
2111
|
el2.dataset.pane = pane.id;
|
|
1939
2112
|
if (pass.stackable) {
|
|
1940
2113
|
el2.dataset.drag = "1";
|
|
1941
|
-
el2.title =
|
|
2114
|
+
el2.title = this.t(TEXT5.restackHint);
|
|
1942
2115
|
}
|
|
1943
2116
|
return el2;
|
|
1944
2117
|
}
|
|
@@ -1950,7 +2123,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1950
2123
|
[
|
|
1951
2124
|
{
|
|
1952
2125
|
icon: row.visible ? "eye" : "eye-off",
|
|
1953
|
-
title: row.visible ?
|
|
2126
|
+
title: row.visible ? this.t(TEXT5.hide) : this.t(TEXT5.show),
|
|
1954
2127
|
engaged: !row.visible,
|
|
1955
2128
|
run: () => {
|
|
1956
2129
|
handle?.setVisible(!row.visible);
|
|
@@ -1959,21 +2132,21 @@ var ObjectTree = class extends SidePanel {
|
|
|
1959
2132
|
},
|
|
1960
2133
|
{
|
|
1961
2134
|
icon: "trash",
|
|
1962
|
-
title:
|
|
2135
|
+
title: this.t(TEXT5.remove),
|
|
1963
2136
|
run: () => {
|
|
1964
2137
|
handle?.remove();
|
|
1965
2138
|
this.refresh();
|
|
1966
2139
|
}
|
|
1967
2140
|
}
|
|
1968
2141
|
],
|
|
1969
|
-
row.ownScale ?
|
|
2142
|
+
row.ownScale ? this.t(TEXT5.scaleTag) : void 0
|
|
1970
2143
|
);
|
|
1971
2144
|
el.dataset.rowKind = "indicator";
|
|
1972
2145
|
el.dataset.id = row.id;
|
|
1973
2146
|
el.dataset.pane = pane.id;
|
|
1974
2147
|
if (pass.repanable || pass.stackable) {
|
|
1975
2148
|
el.dataset.drag = "1";
|
|
1976
|
-
el.title = pass.repanable ?
|
|
2149
|
+
el.title = pass.repanable ? this.t(TEXT5.moveIndicatorHint) : this.t(TEXT5.restackHint);
|
|
1977
2150
|
}
|
|
1978
2151
|
return el;
|
|
1979
2152
|
}
|
|
@@ -1992,22 +2165,22 @@ var ObjectTree = class extends SidePanel {
|
|
|
1992
2165
|
const el = this.row(iconEl(folded ? "chevron-right" : "chevron-down", doc), g.name, !allHidden, [
|
|
1993
2166
|
{
|
|
1994
2167
|
icon: allLocked ? "lock" : "unlock",
|
|
1995
|
-
title: allLocked ?
|
|
2168
|
+
title: allLocked ? this.t(TEXT5.unlockAll) : this.t(TEXT5.lockAll),
|
|
1996
2169
|
engaged: allLocked,
|
|
1997
2170
|
run: () => this.setMembers(pass, ids, { locked: !allLocked })
|
|
1998
2171
|
},
|
|
1999
2172
|
{
|
|
2000
2173
|
icon: allHidden ? "eye-off" : "eye",
|
|
2001
|
-
title: allHidden ?
|
|
2174
|
+
title: allHidden ? this.t(TEXT5.showAll) : this.t(TEXT5.hideAll),
|
|
2002
2175
|
engaged: allHidden,
|
|
2003
2176
|
run: () => this.setMembers(pass, ids, { visible: allHidden })
|
|
2004
2177
|
},
|
|
2005
|
-
{ icon: "trash", title:
|
|
2178
|
+
{ icon: "trash", title: this.t(TEXT5.removeAll), run: () => this.removeGroup(pass, g.id, true) }
|
|
2006
2179
|
]);
|
|
2007
2180
|
el.dataset.rowKind = "group";
|
|
2008
2181
|
el.dataset.id = g.id;
|
|
2009
2182
|
el.dataset.pane = pane.id;
|
|
2010
|
-
el.title =
|
|
2183
|
+
el.title = this.t(TEXT5.groupMembers, { count: members.length });
|
|
2011
2184
|
if (this.renaming === g.id) {
|
|
2012
2185
|
const name = el.querySelector(".vela-ot-name");
|
|
2013
2186
|
const input = doc.createElement("input");
|
|
@@ -2041,7 +2214,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2041
2214
|
drawRow(pass, d, inGroup = false) {
|
|
2042
2215
|
const doc = this.el.ownerDocument;
|
|
2043
2216
|
const { chart } = pass;
|
|
2044
|
-
const markup = drawingMeta(d.type).icon;
|
|
2217
|
+
const markup = drawingMeta(d.type, this.i18n).icon;
|
|
2045
2218
|
let icon2;
|
|
2046
2219
|
if (markup) {
|
|
2047
2220
|
icon2 = doc.createElement("span");
|
|
@@ -2051,10 +2224,10 @@ var ObjectTree = class extends SidePanel {
|
|
|
2051
2224
|
} else {
|
|
2052
2225
|
icon2 = iconEl("pen", doc);
|
|
2053
2226
|
}
|
|
2054
|
-
const el = this.row(icon2, drawingLabel(d), d.visible, [
|
|
2227
|
+
const el = this.row(icon2, drawingLabel(d, this.i18n), d.visible, [
|
|
2055
2228
|
{
|
|
2056
2229
|
icon: d.locked ? "lock" : "unlock",
|
|
2057
|
-
title: d.locked ?
|
|
2230
|
+
title: d.locked ? this.t(TEXT5.unlock) : this.t(TEXT5.lock),
|
|
2058
2231
|
engaged: d.locked,
|
|
2059
2232
|
run: () => {
|
|
2060
2233
|
chart.drawings.lock(d.id, !d.locked);
|
|
@@ -2063,7 +2236,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2063
2236
|
},
|
|
2064
2237
|
{
|
|
2065
2238
|
icon: d.visible ? "eye" : "eye-off",
|
|
2066
|
-
title: d.visible ?
|
|
2239
|
+
title: d.visible ? this.t(TEXT5.hide) : this.t(TEXT5.show),
|
|
2067
2240
|
engaged: !d.visible,
|
|
2068
2241
|
run: () => {
|
|
2069
2242
|
chart.drawings.show(d.id, !d.visible);
|
|
@@ -2072,7 +2245,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2072
2245
|
},
|
|
2073
2246
|
{
|
|
2074
2247
|
icon: "trash",
|
|
2075
|
-
title:
|
|
2248
|
+
title: this.t(TEXT5.remove),
|
|
2076
2249
|
run: () => {
|
|
2077
2250
|
chart.drawings.remove(d.id);
|
|
2078
2251
|
this.refresh();
|
|
@@ -2083,7 +2256,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2083
2256
|
el.dataset.id = d.id;
|
|
2084
2257
|
el.dataset.pane = d.paneId;
|
|
2085
2258
|
el.dataset.drag = "1";
|
|
2086
|
-
el.title =
|
|
2259
|
+
el.title = this.t(TEXT5.moveDrawingHint);
|
|
2087
2260
|
if (inGroup) el.classList.add("vela-ot-subrow");
|
|
2088
2261
|
if (this.selectedDrawings.has(d.id)) el.dataset.selected = "1";
|
|
2089
2262
|
if (this.picked.has(d.id)) el.dataset.picked = "1";
|
|
@@ -2131,7 +2304,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2131
2304
|
const t = doc.createElement("span");
|
|
2132
2305
|
t.className = "vela-ot-tag";
|
|
2133
2306
|
t.textContent = tag;
|
|
2134
|
-
t.title =
|
|
2307
|
+
t.title = this.t(TEXT5.ownScaleHint);
|
|
2135
2308
|
el.appendChild(t);
|
|
2136
2309
|
}
|
|
2137
2310
|
const acts = doc.createElement("span");
|
|
@@ -2162,7 +2335,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2162
2335
|
makeGroup(ids) {
|
|
2163
2336
|
if (!canGroup(ids, this.groups)) return;
|
|
2164
2337
|
this.groupSeq += 1;
|
|
2165
|
-
this.groups = [...this.groups, { id: `grp-${this.groupSeq}`, name: nextGroupName(this.groups), ids: [...ids] }];
|
|
2338
|
+
this.groups = [...this.groups, { id: `grp-${this.groupSeq}`, name: nextGroupName(this.groups, this.i18n), ids: [...ids] }];
|
|
2166
2339
|
this.picked.clear();
|
|
2167
2340
|
this.render();
|
|
2168
2341
|
}
|
|
@@ -2511,20 +2684,20 @@ var ObjectTree = class extends SidePanel {
|
|
|
2511
2684
|
const { chart } = pass;
|
|
2512
2685
|
const visible = chart.renderer.get("candleVisible") !== false;
|
|
2513
2686
|
const items = [
|
|
2514
|
-
b.entry(visible ?
|
|
2687
|
+
b.entry(visible ? this.t(TEXT5.hide) : this.t(TEXT5.show), visible ? "eye-off" : "eye", () => {
|
|
2515
2688
|
chart.renderer.set("candleVisible", !visible);
|
|
2516
2689
|
this.refresh();
|
|
2517
2690
|
})
|
|
2518
2691
|
];
|
|
2519
2692
|
if (pass.stackable) {
|
|
2520
2693
|
items.push(
|
|
2521
|
-
b.entry(
|
|
2694
|
+
b.entry(this.t(TEXT5.bringToFront), "arrow-up", () => {
|
|
2522
2695
|
chart.renderer.set("candleZOrder", this.stackBounds(pass, PRICE_PANE_ID).top + 1);
|
|
2523
2696
|
this.refresh();
|
|
2524
2697
|
}, true)
|
|
2525
2698
|
);
|
|
2526
2699
|
items.push(
|
|
2527
|
-
b.entry(
|
|
2700
|
+
b.entry(this.t(TEXT5.sendToBack), "arrow-down", () => {
|
|
2528
2701
|
chart.renderer.set("candleZOrder", this.stackBounds(pass, PRICE_PANE_ID).bottom - 1);
|
|
2529
2702
|
this.refresh();
|
|
2530
2703
|
})
|
|
@@ -2548,24 +2721,24 @@ var ObjectTree = class extends SidePanel {
|
|
|
2548
2721
|
const { chart } = pass;
|
|
2549
2722
|
const handle = pass.handle(row.id);
|
|
2550
2723
|
const items = [
|
|
2551
|
-
b.entry(row.visible ?
|
|
2724
|
+
b.entry(row.visible ? this.t(TEXT5.hide) : this.t(TEXT5.show), row.visible ? "eye-off" : "eye", () => {
|
|
2552
2725
|
handle?.setVisible(!row.visible);
|
|
2553
2726
|
this.refresh();
|
|
2554
2727
|
})
|
|
2555
2728
|
];
|
|
2556
2729
|
if (chart.renderer.supportsIndicatorSettings) {
|
|
2557
|
-
items.push(b.entry(
|
|
2730
|
+
items.push(b.entry(this.t(TEXT5.indicatorSettings), "gear", () => chart.renderer.openIndicatorSettings(row.id)));
|
|
2558
2731
|
}
|
|
2559
2732
|
if (chart.panes.supported) {
|
|
2560
2733
|
const moves = this.moveItems(b, pass, row.id, paneId);
|
|
2561
|
-
if (moves.length > 0) items.push(b.submenu(
|
|
2734
|
+
if (moves.length > 0) items.push(b.submenu(this.t(TEXT5.moveTo), "move-vertical", moves));
|
|
2562
2735
|
}
|
|
2563
2736
|
if (pass.stackable) {
|
|
2564
|
-
items.push(b.entry(
|
|
2565
|
-
items.push(b.entry(
|
|
2737
|
+
items.push(b.entry(this.t(TEXT5.bringToFront), "arrow-up", () => chart.renderer.set("seriesOrder", { id: row.id, z: this.stackBounds(pass, paneId).top + 1 }), true));
|
|
2738
|
+
items.push(b.entry(this.t(TEXT5.sendToBack), "arrow-down", () => chart.renderer.set("seriesOrder", { id: row.id, z: this.stackBounds(pass, paneId).bottom - 1 })));
|
|
2566
2739
|
}
|
|
2567
2740
|
items.push(
|
|
2568
|
-
b.entry(
|
|
2741
|
+
b.entry(this.t(TEXT5.remove), "trash", () => {
|
|
2569
2742
|
handle?.remove();
|
|
2570
2743
|
this.refresh();
|
|
2571
2744
|
}, true)
|
|
@@ -2583,33 +2756,33 @@ var ObjectTree = class extends SidePanel {
|
|
|
2583
2756
|
const current = pass.panes.find((p) => p.id === fromPane);
|
|
2584
2757
|
const alone = current !== void 0 && current.kind !== "price" && paneRows(current).length <= 1;
|
|
2585
2758
|
if (!alone) {
|
|
2586
|
-
if (fromPane !== PRICE_PANE_ID) items.push(b.entry(
|
|
2587
|
-
items.push(b.entry(
|
|
2759
|
+
if (fromPane !== PRICE_PANE_ID) items.push(b.entry(this.t(TEXT5.newPaneAbove), void 0, () => chart.panes.moveIndicator(id, { newPane: { before: fromPane } })));
|
|
2760
|
+
items.push(b.entry(this.t(TEXT5.newPaneBelow), void 0, () => chart.panes.moveIndicator(id, { newPane: { after: fromPane } })));
|
|
2588
2761
|
}
|
|
2589
2762
|
return items;
|
|
2590
2763
|
}
|
|
2591
2764
|
drawingMenu(b, pass, d) {
|
|
2592
2765
|
const { chart } = pass;
|
|
2593
2766
|
const items = [
|
|
2594
|
-
b.entry(d.visible ?
|
|
2767
|
+
b.entry(d.visible ? this.t(TEXT5.hide) : this.t(TEXT5.show), d.visible ? "eye-off" : "eye", () => {
|
|
2595
2768
|
chart.drawings.show(d.id, !d.visible);
|
|
2596
2769
|
this.refresh();
|
|
2597
2770
|
}),
|
|
2598
|
-
b.entry(d.locked ?
|
|
2771
|
+
b.entry(d.locked ? this.t(TEXT5.unlock) : this.t(TEXT5.lock), d.locked ? "unlock" : "lock", () => {
|
|
2599
2772
|
chart.drawings.lock(d.id, !d.locked);
|
|
2600
2773
|
this.refresh();
|
|
2601
2774
|
}),
|
|
2602
|
-
b.entry(
|
|
2775
|
+
b.entry(this.t(TEXT5.duplicate), "clone", () => {
|
|
2603
2776
|
this.cloneInto(pass, d.id);
|
|
2604
2777
|
this.render();
|
|
2605
2778
|
}),
|
|
2606
2779
|
// Front/back clear the WHOLE stack on a shared-z renderer — over or under the
|
|
2607
2780
|
// candles and every indicator, not just the other drawings.
|
|
2608
|
-
b.entry(
|
|
2609
|
-
b.entry(
|
|
2781
|
+
b.entry(this.t(TEXT5.bringToFront), "arrow-up", () => chart.drawings.bringToFront(d.id), true),
|
|
2782
|
+
b.entry(this.t(TEXT5.sendToBack), "arrow-down", () => chart.drawings.sendToBack(d.id))
|
|
2610
2783
|
];
|
|
2611
2784
|
items.push(...this.groupingItems(b, d.id));
|
|
2612
|
-
items.push(b.entry(
|
|
2785
|
+
items.push(b.entry(this.t(TEXT5.remove), "trash", () => chart.drawings.remove(d.id), true));
|
|
2613
2786
|
return items;
|
|
2614
2787
|
}
|
|
2615
2788
|
/** The grouping half of a drawing's menu: what it can do about the bundle it is (or isn't)
|
|
@@ -2619,15 +2792,15 @@ var ObjectTree = class extends SidePanel {
|
|
|
2619
2792
|
const mine = groupOf(this.groups, id);
|
|
2620
2793
|
const pick = this.picked.has(id) && this.picked.size > 1 ? [...this.picked] : null;
|
|
2621
2794
|
if (pick && canGroup(pick, this.groups)) {
|
|
2622
|
-
items.push(b.entry(
|
|
2795
|
+
items.push(b.entry(this.t(TEXT5.groupPick, { count: pick.length }), "group", () => this.makeGroup(pick), true));
|
|
2623
2796
|
} else if (!pick && mine === null) {
|
|
2624
|
-
items.push(b.entry(
|
|
2797
|
+
items.push(b.entry(this.t(TEXT5.newGroup), "group", () => this.makeGroup([id]), true));
|
|
2625
2798
|
}
|
|
2626
2799
|
const others = this.groups.filter((g) => g.id !== mine?.id);
|
|
2627
2800
|
if (!pick && others.length > 0) {
|
|
2628
2801
|
items.push(
|
|
2629
2802
|
b.submenu(
|
|
2630
|
-
mine === null ?
|
|
2803
|
+
mine === null ? this.t(TEXT5.addToGroup) : this.t(TEXT5.moveToGroup),
|
|
2631
2804
|
"folder-plus",
|
|
2632
2805
|
others.map(
|
|
2633
2806
|
(g) => b.entry(g.name, void 0, () => {
|
|
@@ -2641,7 +2814,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2641
2814
|
}
|
|
2642
2815
|
if (mine !== null) {
|
|
2643
2816
|
items.push(
|
|
2644
|
-
b.entry(
|
|
2817
|
+
b.entry(this.t(TEXT5.removeFromGroup, { group: mine.name }), "folder-minus", () => {
|
|
2645
2818
|
this.groups = removeFromGroups(this.groups, [id]);
|
|
2646
2819
|
this.render();
|
|
2647
2820
|
}, items.length === 0)
|
|
@@ -2654,24 +2827,24 @@ var ObjectTree = class extends SidePanel {
|
|
|
2654
2827
|
const members = pass.chart.drawings.all().filter((d) => g.ids.includes(d.id));
|
|
2655
2828
|
const { allHidden, allLocked } = groupState(members);
|
|
2656
2829
|
const items = [
|
|
2657
|
-
b.entry(
|
|
2830
|
+
b.entry(this.t(TEXT5.rename), "pen", () => {
|
|
2658
2831
|
this.renaming = g.id;
|
|
2659
2832
|
this.render();
|
|
2660
2833
|
}),
|
|
2661
|
-
b.entry(allHidden ?
|
|
2662
|
-
b.entry(allLocked ?
|
|
2663
|
-
b.entry(
|
|
2834
|
+
b.entry(allHidden ? this.t(TEXT5.showAll) : this.t(TEXT5.hideAll), allHidden ? "eye" : "eye-off", () => this.setMembers(pass, ids, { visible: allHidden }), true),
|
|
2835
|
+
b.entry(allLocked ? this.t(TEXT5.unlockAll) : this.t(TEXT5.lockAll), allLocked ? "unlock" : "lock", () => this.setMembers(pass, ids, { locked: !allLocked })),
|
|
2836
|
+
b.entry(this.t(TEXT5.selectAll), "group", () => {
|
|
2664
2837
|
this.picked = new Set(ids);
|
|
2665
2838
|
this.selectOnChart(ids);
|
|
2666
2839
|
this.render();
|
|
2667
2840
|
})
|
|
2668
2841
|
];
|
|
2669
2842
|
if (members.length > 0) {
|
|
2670
|
-
items.push(b.entry(
|
|
2671
|
-
items.push(b.entry(
|
|
2843
|
+
items.push(b.entry(this.t(TEXT5.bringAllToFront), "arrow-up", () => this.restackGroup(pass, g, "front")));
|
|
2844
|
+
items.push(b.entry(this.t(TEXT5.sendAllToBack), "arrow-down", () => this.restackGroup(pass, g, "back")));
|
|
2672
2845
|
}
|
|
2673
|
-
items.push(b.entry(
|
|
2674
|
-
items.push(b.entry(
|
|
2846
|
+
items.push(b.entry(this.t(TEXT5.ungroup), "ungroup", () => this.removeGroup(pass, g.id, false), true));
|
|
2847
|
+
items.push(b.entry(this.t(TEXT5.removeAll), "trash", () => this.removeGroup(pass, g.id, true)));
|
|
2675
2848
|
return items;
|
|
2676
2849
|
}
|
|
2677
2850
|
/** Forget everything that refers to a drawing or group the chart no longer has — otherwise a
|
|
@@ -2689,6 +2862,21 @@ var ObjectTree = class extends SidePanel {
|
|
|
2689
2862
|
};
|
|
2690
2863
|
|
|
2691
2864
|
// src/widget/data-window.ts
|
|
2865
|
+
var TEXT6 = {
|
|
2866
|
+
title: msg("widget.dataWindow.title", "Data window"),
|
|
2867
|
+
timeSection: msg("widget.dataWindow.timeSection", "Time"),
|
|
2868
|
+
date: msg("widget.dataWindow.date", "Date"),
|
|
2869
|
+
time: msg("widget.dataWindow.time", "Time"),
|
|
2870
|
+
open: msg("widget.dataWindow.open", "Open"),
|
|
2871
|
+
high: msg("widget.dataWindow.high", "High"),
|
|
2872
|
+
low: msg("widget.dataWindow.low", "Low"),
|
|
2873
|
+
close: msg("widget.dataWindow.close", "Close"),
|
|
2874
|
+
volume: msg("widget.dataWindow.volume", "Volume"),
|
|
2875
|
+
priceSection: msg("widget.dataWindow.priceSection", "Price"),
|
|
2876
|
+
noData: msg("widget.dataWindow.noData", "No data"),
|
|
2877
|
+
noReadout: msg("widget.dataWindow.noReadout", "This renderer provides no data readout.")
|
|
2878
|
+
};
|
|
2879
|
+
var DATA_WINDOW_TITLE = TEXT6.title;
|
|
2692
2880
|
var STYLE_ID5 = "vela-widget-datawindow";
|
|
2693
2881
|
var CSS5 = `
|
|
2694
2882
|
.vela-dw-group {
|
|
@@ -2714,19 +2902,21 @@ var CSS5 = `
|
|
|
2714
2902
|
}
|
|
2715
2903
|
.vela-dw-row:hover { background: var(--vela-hover); }
|
|
2716
2904
|
.vela-dw-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--vela-fg-bright); }
|
|
2717
|
-
|
|
2905
|
+
/* Values are left-to-right isolates (dir="ltr"), so a sign keeps its place in a right-to-left page. */
|
|
2906
|
+
.vela-dw-value { margin-left: auto; font-variant-numeric: tabular-nums; white-space: nowrap; unicode-bidi: isolate; }
|
|
2718
2907
|
.vela-dw-empty { padding: 20px 10px; text-align: center; color: var(--vela-fg-muted); font-size: 12px; }
|
|
2719
2908
|
`;
|
|
2720
2909
|
var REFRESH_EVENTS2 = ["bar", "context:changed", "indicator:added", "indicator:removed", "indicator:visibility", "indicator:moved", "pane:changed", "market:changed", "history:complete"];
|
|
2721
|
-
var EM_DASH = "
|
|
2722
|
-
function dataWindowSections(readout) {
|
|
2910
|
+
var EM_DASH = "—";
|
|
2911
|
+
function dataWindowSections(readout, i18n) {
|
|
2912
|
+
const t = (m) => textOf(i18n, m);
|
|
2723
2913
|
const sections = [];
|
|
2724
2914
|
if (readout.date || readout.time) {
|
|
2725
2915
|
sections.push({
|
|
2726
|
-
title:
|
|
2916
|
+
title: t(TEXT6.timeSection),
|
|
2727
2917
|
lines: [
|
|
2728
|
-
{ label:
|
|
2729
|
-
{ label:
|
|
2918
|
+
{ label: t(TEXT6.date), value: readout.date || EM_DASH, color: "" },
|
|
2919
|
+
{ label: t(TEXT6.time), value: readout.time || EM_DASH, color: "" }
|
|
2730
2920
|
]
|
|
2731
2921
|
});
|
|
2732
2922
|
}
|
|
@@ -2734,13 +2924,13 @@ function dataWindowSections(readout) {
|
|
|
2734
2924
|
if (ohlc) {
|
|
2735
2925
|
const color = ohlc.up ? "var(--vela-up)" : "var(--vela-down)";
|
|
2736
2926
|
const lines = [
|
|
2737
|
-
{ label:
|
|
2738
|
-
{ label:
|
|
2739
|
-
{ label:
|
|
2740
|
-
{ label:
|
|
2927
|
+
{ label: t(TEXT6.open), value: ohlc.o, color },
|
|
2928
|
+
{ label: t(TEXT6.high), value: ohlc.h, color },
|
|
2929
|
+
{ label: t(TEXT6.low), value: ohlc.l, color },
|
|
2930
|
+
{ label: t(TEXT6.close), value: ohlc.c, color }
|
|
2741
2931
|
];
|
|
2742
|
-
if (ohlc.vol !== void 0) lines.push({ label:
|
|
2743
|
-
sections.push({ title:
|
|
2932
|
+
if (ohlc.vol !== void 0) lines.push({ label: t(TEXT6.volume), value: ohlc.vol, color });
|
|
2933
|
+
sections.push({ title: t(TEXT6.priceSection), lines });
|
|
2744
2934
|
}
|
|
2745
2935
|
for (const group of readout.groups) {
|
|
2746
2936
|
sections.push({ title: group.name, lines: group.rows.map((r) => ({ label: r.label, value: r.value, color: r.color })) });
|
|
@@ -2748,8 +2938,8 @@ function dataWindowSections(readout) {
|
|
|
2748
2938
|
return sections;
|
|
2749
2939
|
}
|
|
2750
2940
|
var DataWindow = class extends SidePanel {
|
|
2751
|
-
constructor(host) {
|
|
2752
|
-
super(host,
|
|
2941
|
+
constructor(host, i18n) {
|
|
2942
|
+
super(host, TEXT6.title, "vela-dw", i18n ? { i18n } : {});
|
|
2753
2943
|
this.chart = null;
|
|
2754
2944
|
this.unsubs = [];
|
|
2755
2945
|
injectStyles(STYLE_ID5, CSS5, host.ownerDocument);
|
|
@@ -2770,6 +2960,10 @@ var DataWindow = class extends SidePanel {
|
|
|
2770
2960
|
this.detach();
|
|
2771
2961
|
super.destroy();
|
|
2772
2962
|
}
|
|
2963
|
+
relocalize() {
|
|
2964
|
+
super.relocalize();
|
|
2965
|
+
this.refresh();
|
|
2966
|
+
}
|
|
2773
2967
|
detach() {
|
|
2774
2968
|
for (const u of this.unsubs) u();
|
|
2775
2969
|
this.unsubs = [];
|
|
@@ -2780,11 +2974,11 @@ var DataWindow = class extends SidePanel {
|
|
|
2780
2974
|
const doc = this.el.ownerDocument;
|
|
2781
2975
|
const readout = this.chart.renderer.dataWindowReadout();
|
|
2782
2976
|
this.body.replaceChildren();
|
|
2783
|
-
const sections = readout ? dataWindowSections(readout) : [];
|
|
2977
|
+
const sections = readout ? dataWindowSections(readout, this.i18n) : [];
|
|
2784
2978
|
if (sections.length === 0) {
|
|
2785
2979
|
const empty = doc.createElement("div");
|
|
2786
2980
|
empty.className = "vela-dw-empty";
|
|
2787
|
-
empty.textContent = readout ?
|
|
2981
|
+
empty.textContent = this.t(readout ? TEXT6.noData : TEXT6.noReadout);
|
|
2788
2982
|
this.body.appendChild(empty);
|
|
2789
2983
|
return;
|
|
2790
2984
|
}
|
|
@@ -2801,6 +2995,7 @@ var DataWindow = class extends SidePanel {
|
|
|
2801
2995
|
label.textContent = line.label;
|
|
2802
2996
|
const value = doc.createElement("span");
|
|
2803
2997
|
value.className = "vela-dw-value";
|
|
2998
|
+
value.dir = "ltr";
|
|
2804
2999
|
value.textContent = line.value;
|
|
2805
3000
|
if (line.color) value.style.color = line.color;
|
|
2806
3001
|
row.append(label, value);
|
|
@@ -2846,7 +3041,8 @@ var PanelDock = class {
|
|
|
2846
3041
|
minWidth: desc.minWidth,
|
|
2847
3042
|
maxWidth: desc.maxWidth,
|
|
2848
3043
|
overlay: desc.overlay,
|
|
2849
|
-
maximizable: desc.maximizable
|
|
3044
|
+
maximizable: desc.maximizable,
|
|
3045
|
+
...this.deps.i18n ? { i18n: this.deps.i18n } : {}
|
|
2850
3046
|
});
|
|
2851
3047
|
const entry = {
|
|
2852
3048
|
id: desc.id,
|
|
@@ -2895,7 +3091,12 @@ var PanelDock = class {
|
|
|
2895
3091
|
/** The docked panels in dock order — what a non-topbar chrome (the mobile
|
|
2896
3092
|
* three-dots drawer) lists so every panel stays reachable there too. */
|
|
2897
3093
|
list() {
|
|
2898
|
-
return this.entries.map((e) => ({ id: e.id, title: e.title, icon: e.icon }));
|
|
3094
|
+
return this.entries.map((e) => ({ id: e.id, title: textOf(this.deps.i18n, e.title), icon: e.icon }));
|
|
3095
|
+
}
|
|
3096
|
+
/** Re-read every panel's header and the toggle tooltips in the current language. */
|
|
3097
|
+
relocalize() {
|
|
3098
|
+
for (const entry of this.entries) entry.panel.relocalize();
|
|
3099
|
+
this.publish();
|
|
2899
3100
|
}
|
|
2900
3101
|
/** The dock's persistable state, or null when there is nothing worth saving. */
|
|
2901
3102
|
getState() {
|
|
@@ -2972,7 +3173,7 @@ var PanelDock = class {
|
|
|
2972
3173
|
}
|
|
2973
3174
|
/** Push the current toggle group to the chrome, pressed states included. */
|
|
2974
3175
|
publish() {
|
|
2975
|
-
const buttons = this.
|
|
3176
|
+
const buttons = this.list().slice();
|
|
2976
3177
|
this.deps.chrome.setPanelButtons(buttons, (id) => this.toggle(id));
|
|
2977
3178
|
for (const entry of this.entries) this.deps.chrome.setPanelActive(entry.id, entry.panel.open);
|
|
2978
3179
|
}
|
|
@@ -3047,7 +3248,7 @@ var STYLE_ID6 = "vela-widget-symbolpicker";
|
|
|
3047
3248
|
var CSS6 = `
|
|
3048
3249
|
/* The dialog body is flush (a flex column): search + market tabs are a fixed head
|
|
3049
3250
|
and the result list is the ONLY scroller, in both layouts. A sticky head over a
|
|
3050
|
-
scrolling padded body is not an option
|
|
3251
|
+
scrolling padded body is not an option — browsers pin sticky boxes to the
|
|
3051
3252
|
scroller's content box, so the head lands one padding below the top and covers
|
|
3052
3253
|
the first row. */
|
|
3053
3254
|
.vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
|
|
@@ -3075,7 +3276,7 @@ var CSS6 = `
|
|
|
3075
3276
|
.vela-sp-input::placeholder { text-transform: none; }
|
|
3076
3277
|
.vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
|
|
3077
3278
|
/* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
|
|
3078
|
-
overflowing the body, and the result list stops capping itself
|
|
3279
|
+
overflowing the body, and the result list stops capping itself — it fills the
|
|
3079
3280
|
rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
|
|
3080
3281
|
dialog body carries it, flush or not, so the list adds none of its own. */
|
|
3081
3282
|
[data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
|
|
@@ -3142,7 +3343,7 @@ var CSS6 = `
|
|
|
3142
3343
|
letter-spacing: 0.06em;
|
|
3143
3344
|
text-transform: uppercase;
|
|
3144
3345
|
}
|
|
3145
|
-
/* Provider brand marks
|
|
3346
|
+
/* Provider brand marks — fixed by the venue, deliberately outside the theme palette. */
|
|
3146
3347
|
.vela-sp-badge[data-p='binance'] { color: #f0b90b; } /* palette-exempt: venue brand mark */
|
|
3147
3348
|
.vela-sp-badge[data-p='hyperliquid'] { color: #50d2c1; } /* palette-exempt: venue brand mark */
|
|
3148
3349
|
.vela-sp-empty { padding: var(--vela-space-3); color: var(--vela-fg-muted); text-align: center; }
|
|
@@ -3162,6 +3363,30 @@ var CSS6 = `
|
|
|
3162
3363
|
.vela-sp-expander:hover { background: var(--vela-surface-elev); color: var(--vela-fg); }
|
|
3163
3364
|
.vela-sp-row[data-member] { padding-left: 34px; }
|
|
3164
3365
|
`;
|
|
3366
|
+
var TEXT7 = {
|
|
3367
|
+
title: msg("widget.symbolPicker.title", "Symbol Search"),
|
|
3368
|
+
search: msg("widget.symbolPicker.search", "Search symbol…"),
|
|
3369
|
+
noMatch: msg("widget.symbolPicker.noMatch", "No symbols match."),
|
|
3370
|
+
empty: msg("widget.symbolPicker.empty", "No symbols available yet."),
|
|
3371
|
+
showContracts: msg("widget.symbolPicker.showContracts", "Show contracts")
|
|
3372
|
+
};
|
|
3373
|
+
var TABS = [
|
|
3374
|
+
{ id: "all", label: msg("widget.symbolPicker.tab.all", "All") },
|
|
3375
|
+
{ id: "stocks", label: msg("widget.symbolPicker.tab.stocks", "Stocks") },
|
|
3376
|
+
{ id: "etfs", label: msg("widget.symbolPicker.tab.etfs", "ETFs") },
|
|
3377
|
+
{ id: "crypto", label: msg("widget.symbolPicker.tab.crypto", "Crypto") },
|
|
3378
|
+
{ id: "futures", label: msg("widget.symbolPicker.tab.futures", "Futures") },
|
|
3379
|
+
{ id: "forex", label: msg("widget.symbolPicker.tab.forex", "Forex") },
|
|
3380
|
+
{ id: "commodities", label: msg("widget.symbolPicker.tab.commodities", "Commodities") }
|
|
3381
|
+
];
|
|
3382
|
+
var TAB_TYPES = {
|
|
3383
|
+
crypto: ["crypto"],
|
|
3384
|
+
stocks: ["stock"],
|
|
3385
|
+
etfs: ["etf"],
|
|
3386
|
+
futures: ["futures", "root"],
|
|
3387
|
+
forex: ["forex"],
|
|
3388
|
+
commodities: ["commodity"]
|
|
3389
|
+
};
|
|
3165
3390
|
var PAGE = 100;
|
|
3166
3391
|
var SymbolPicker = class {
|
|
3167
3392
|
constructor(opts) {
|
|
@@ -3170,7 +3395,8 @@ var SymbolPicker = class {
|
|
|
3170
3395
|
this.rows = [];
|
|
3171
3396
|
this.highlighted = 0;
|
|
3172
3397
|
this.seed = "";
|
|
3173
|
-
this.activeTab = "
|
|
3398
|
+
this.activeTab = "all";
|
|
3399
|
+
this.tabButtons = [];
|
|
3174
3400
|
this.visible = PAGE;
|
|
3175
3401
|
/** The last filter pass returned fewer raw rows than asked — the pool is drained
|
|
3176
3402
|
* (checked BEFORE folding: folding shortens pages without meaning exhaustion). */
|
|
@@ -3184,7 +3410,7 @@ var SymbolPicker = class {
|
|
|
3184
3410
|
injectStyles(STYLE_ID6, CSS6, doc);
|
|
3185
3411
|
this.input = doc.createElement("input");
|
|
3186
3412
|
this.input.className = "vela-sp-input";
|
|
3187
|
-
this.input.placeholder =
|
|
3413
|
+
this.input.placeholder = textOf(opts.i18n, TEXT7.search);
|
|
3188
3414
|
this.input.setAttribute("spellcheck", "false");
|
|
3189
3415
|
const searchRow = doc.createElement("div");
|
|
3190
3416
|
searchRow.className = "vela-sp-searchrow";
|
|
@@ -3193,13 +3419,15 @@ var SymbolPicker = class {
|
|
|
3193
3419
|
this.tabs.className = "vela-sp-tabs";
|
|
3194
3420
|
const head = doc.createElement("div");
|
|
3195
3421
|
head.className = "vela-sp-head";
|
|
3196
|
-
for (const
|
|
3422
|
+
for (const tab of TABS) {
|
|
3197
3423
|
const b = doc.createElement("button");
|
|
3198
3424
|
b.className = "vela-sp-tab";
|
|
3199
|
-
b.
|
|
3200
|
-
|
|
3425
|
+
b.dataset.tab = tab.id;
|
|
3426
|
+
b.textContent = textOf(opts.i18n, tab.label);
|
|
3427
|
+
this.tabButtons.push({ el: b, label: tab.label });
|
|
3428
|
+
if (tab.id === this.activeTab) b.dataset.active = "1";
|
|
3201
3429
|
b.addEventListener("click", () => {
|
|
3202
|
-
this.activeTab =
|
|
3430
|
+
this.activeTab = tab.id;
|
|
3203
3431
|
for (const c of this.tabs.children) delete c.dataset.active;
|
|
3204
3432
|
b.dataset.active = "1";
|
|
3205
3433
|
this.refresh();
|
|
@@ -3216,13 +3444,14 @@ var SymbolPicker = class {
|
|
|
3216
3444
|
this.grow();
|
|
3217
3445
|
});
|
|
3218
3446
|
this.dialog = new Dialog({
|
|
3219
|
-
title:
|
|
3447
|
+
title: textOf(opts.i18n, TEXT7.title),
|
|
3220
3448
|
host: opts.host,
|
|
3221
3449
|
closeOnInteractOutside: true,
|
|
3222
3450
|
flush: true,
|
|
3223
3451
|
content: (body) => body.append(head, this.list),
|
|
3224
3452
|
onOpenChange: (open) => {
|
|
3225
3453
|
if (open) {
|
|
3454
|
+
this.relabel();
|
|
3226
3455
|
this.input.value = this.seed.toUpperCase();
|
|
3227
3456
|
this.seed = "";
|
|
3228
3457
|
this.refresh();
|
|
@@ -3346,7 +3575,7 @@ var SymbolPicker = class {
|
|
|
3346
3575
|
Promise.resolve([...raw]).then((copy) => hook(copy)).then((out) => {
|
|
3347
3576
|
this.ranked = { key, result: dedupeSymbols(out) };
|
|
3348
3577
|
}).catch((err) => {
|
|
3349
|
-
console.warn("[vela] symbol ranking failed
|
|
3578
|
+
console.warn("[vela] symbol ranking failed — pool order kept:", err);
|
|
3350
3579
|
this.ranked = { key, result: [...raw] };
|
|
3351
3580
|
}).finally(() => {
|
|
3352
3581
|
this.ranking = false;
|
|
@@ -3355,10 +3584,17 @@ var SymbolPicker = class {
|
|
|
3355
3584
|
}
|
|
3356
3585
|
return this.ranked?.result ?? raw;
|
|
3357
3586
|
}
|
|
3587
|
+
/** The dialog's fixed text in the shell's language, re-read on every open. */
|
|
3588
|
+
relabel() {
|
|
3589
|
+
const i18n = this.opts.i18n;
|
|
3590
|
+
this.dialog.titleEl.textContent = textOf(i18n, TEXT7.title);
|
|
3591
|
+
this.input.placeholder = textOf(i18n, TEXT7.search);
|
|
3592
|
+
for (const { el, label } of this.tabButtons) el.textContent = textOf(i18n, label);
|
|
3593
|
+
}
|
|
3358
3594
|
computeRows() {
|
|
3359
|
-
const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Futures: ["futures", "root"], Forex: ["forex"], Commodities: ["commodity"] };
|
|
3360
3595
|
const all = this.pool();
|
|
3361
|
-
const
|
|
3596
|
+
const tab = this.activeTab;
|
|
3597
|
+
const pool = tab === "all" ? all : all.filter((s) => TAB_TYPES[tab].includes((s.type ?? "").toLowerCase()) || tab === "crypto" && (s.type ?? "").toLowerCase() === "futures");
|
|
3362
3598
|
const filtered = filterSymbols(pool, this.input.value, this.visible, symbolRanking() ? false : TOP_TICKERS);
|
|
3363
3599
|
this.exhausted = filtered.length < this.visible;
|
|
3364
3600
|
const folded = foldGroups(filtered);
|
|
@@ -3386,7 +3622,7 @@ var SymbolPicker = class {
|
|
|
3386
3622
|
if (!this.rows.length) {
|
|
3387
3623
|
const empty = doc.createElement("div");
|
|
3388
3624
|
empty.className = "vela-sp-empty";
|
|
3389
|
-
empty.textContent = this.input.value ?
|
|
3625
|
+
empty.textContent = textOf(this.opts.i18n, this.input.value ? TEXT7.noMatch : TEXT7.empty);
|
|
3390
3626
|
this.list.appendChild(empty);
|
|
3391
3627
|
return;
|
|
3392
3628
|
}
|
|
@@ -3422,7 +3658,7 @@ var SymbolPicker = class {
|
|
|
3422
3658
|
row.dataset.group = "1";
|
|
3423
3659
|
const expander = doc.createElement("button");
|
|
3424
3660
|
expander.className = "vela-sp-expander";
|
|
3425
|
-
expander.setAttribute("aria-label",
|
|
3661
|
+
expander.setAttribute("aria-label", textOf(this.opts.i18n, TEXT7.showContracts));
|
|
3426
3662
|
expander.appendChild(iconEl(this.expanded.has(groupKeyOf(s)) ? "chevron-down" : "chevron-right", doc));
|
|
3427
3663
|
row.appendChild(expander);
|
|
3428
3664
|
} else if (s.group != null && this.expanded.has(groupKeyOf(s))) row.dataset.member = "1";
|
|
@@ -3438,6 +3674,17 @@ var SymbolPicker = class {
|
|
|
3438
3674
|
};
|
|
3439
3675
|
|
|
3440
3676
|
// src/widget/indicator-picker.ts
|
|
3677
|
+
var TEXT8 = {
|
|
3678
|
+
title: msg("widget.indicatorPicker.title", "Indicators"),
|
|
3679
|
+
search: msg("widget.indicatorPicker.search", "Search…"),
|
|
3680
|
+
beta: msg("widget.indicatorPicker.beta", "beta"),
|
|
3681
|
+
remove: msg("widget.indicatorPicker.remove", "Remove from chart"),
|
|
3682
|
+
onChart: msg("widget.indicatorPicker.onChart", "On chart · {count}"),
|
|
3683
|
+
general: msg("widget.indicatorPicker.general", "General"),
|
|
3684
|
+
noMatch: msg("widget.indicatorPicker.noMatch", "No indicators match."),
|
|
3685
|
+
empty: msg("widget.indicatorPicker.empty", "No indicators in the manifest.")
|
|
3686
|
+
};
|
|
3687
|
+
var GENERAL = "General";
|
|
3441
3688
|
var STYLE_ID7 = "vela-widget-indpicker";
|
|
3442
3689
|
var CSS7 = `
|
|
3443
3690
|
.vela-ip-searchrow {
|
|
@@ -3455,7 +3702,7 @@ var CSS7 = `
|
|
|
3455
3702
|
.vela-ip-searchrow .vela-icon { color: var(--vela-fg-muted); }
|
|
3456
3703
|
.vela-ip-search { flex: 1; background: transparent; color: var(--vela-fg); border: none; font-size: 14px; outline: none; }
|
|
3457
3704
|
.vela-ip-list { max-height: 50vh; overflow: auto; min-width: 380px; }
|
|
3458
|
-
/* Mobile (fullscreen dialog): no width floor
|
|
3705
|
+
/* Mobile (fullscreen dialog): no width floor — 380px would overflow a phone —
|
|
3459
3706
|
and no height cap; the fullscreen body owns the scrolling. */
|
|
3460
3707
|
[data-layout='mobile'] .vela-ip-list { min-width: 0; max-height: none; }
|
|
3461
3708
|
.vela-ip-list::-webkit-scrollbar { width: 8px; }
|
|
@@ -3519,7 +3766,7 @@ var IndicatorPicker = class {
|
|
|
3519
3766
|
injectStyles(STYLE_ID7, CSS7, doc);
|
|
3520
3767
|
this.search = doc.createElement("input");
|
|
3521
3768
|
this.search.className = "vela-ip-search";
|
|
3522
|
-
this.search.placeholder =
|
|
3769
|
+
this.search.placeholder = this.t(TEXT8.search);
|
|
3523
3770
|
this.search.spellcheck = false;
|
|
3524
3771
|
this.search.addEventListener("input", () => this.refresh());
|
|
3525
3772
|
const searchRow = doc.createElement("div");
|
|
@@ -3528,7 +3775,7 @@ var IndicatorPicker = class {
|
|
|
3528
3775
|
this.list = doc.createElement("div");
|
|
3529
3776
|
this.list.className = "vela-ip-list";
|
|
3530
3777
|
this.dialog = new Dialog({
|
|
3531
|
-
title:
|
|
3778
|
+
title: this.t(TEXT8.title),
|
|
3532
3779
|
host: opts.host,
|
|
3533
3780
|
draggable: true,
|
|
3534
3781
|
closeOnInteractOutside: true,
|
|
@@ -3538,6 +3785,7 @@ var IndicatorPicker = class {
|
|
|
3538
3785
|
this.isOpen = open;
|
|
3539
3786
|
if (open) {
|
|
3540
3787
|
this.search.value = "";
|
|
3788
|
+
this.relabel();
|
|
3541
3789
|
this.refresh();
|
|
3542
3790
|
if (!this.search.closest('[data-layout="mobile"]')) setTimeout(() => this.search.focus(), 0);
|
|
3543
3791
|
}
|
|
@@ -3559,6 +3807,14 @@ var IndicatorPicker = class {
|
|
|
3559
3807
|
open() {
|
|
3560
3808
|
this.dialog.show();
|
|
3561
3809
|
}
|
|
3810
|
+
/** The dialog's fixed text in the shell's language, re-read on every open. */
|
|
3811
|
+
relabel() {
|
|
3812
|
+
this.dialog.titleEl.textContent = this.t(TEXT8.title);
|
|
3813
|
+
this.search.placeholder = this.t(TEXT8.search);
|
|
3814
|
+
}
|
|
3815
|
+
t(text, values) {
|
|
3816
|
+
return textOf(this.opts.i18n, text, values);
|
|
3817
|
+
}
|
|
3562
3818
|
/** Re-render the lists if the dialog is open (async catalog updates land late). */
|
|
3563
3819
|
sync() {
|
|
3564
3820
|
if (this.isOpen) this.refresh();
|
|
@@ -3571,11 +3827,21 @@ var IndicatorPicker = class {
|
|
|
3571
3827
|
}
|
|
3572
3828
|
refresh() {
|
|
3573
3829
|
const doc = this.list.ownerDocument;
|
|
3574
|
-
const
|
|
3830
|
+
const raw = this.search.value.trim();
|
|
3831
|
+
const locale = this.opts.i18n?.locale;
|
|
3832
|
+
const lower = (s) => {
|
|
3833
|
+
try {
|
|
3834
|
+
return s.toLocaleLowerCase(locale);
|
|
3835
|
+
} catch {
|
|
3836
|
+
return s.toLowerCase();
|
|
3837
|
+
}
|
|
3838
|
+
};
|
|
3839
|
+
const q = lower(raw);
|
|
3840
|
+
const qPlain = raw.toLowerCase();
|
|
3575
3841
|
const library = this.opts.library();
|
|
3576
3842
|
const instances = this.opts.onChart();
|
|
3577
3843
|
this.list.replaceChildren();
|
|
3578
|
-
const match = (r) => !
|
|
3844
|
+
const match = (r) => !raw || [r.name, ...r.aliases ?? []].some((n) => lower(n).includes(q) || n.toLowerCase().includes(qPlain));
|
|
3579
3845
|
const rowEl = (r, opts) => {
|
|
3580
3846
|
const row = doc.createElement("div");
|
|
3581
3847
|
row.className = "vela-ip-row";
|
|
@@ -3589,7 +3855,7 @@ var IndicatorPicker = class {
|
|
|
3589
3855
|
if (r.beta) {
|
|
3590
3856
|
const beta = doc.createElement("span");
|
|
3591
3857
|
beta.className = "vela-ip-badge";
|
|
3592
|
-
beta.textContent =
|
|
3858
|
+
beta.textContent = this.t(TEXT8.beta);
|
|
3593
3859
|
row.appendChild(beta);
|
|
3594
3860
|
}
|
|
3595
3861
|
if (opts.instance !== void 0) {
|
|
@@ -3603,7 +3869,7 @@ var IndicatorPicker = class {
|
|
|
3603
3869
|
const trash = doc.createElement("button");
|
|
3604
3870
|
trash.className = "vela-ip-trash";
|
|
3605
3871
|
trash.appendChild(iconEl("trash", doc));
|
|
3606
|
-
trash.title =
|
|
3872
|
+
trash.title = this.t(TEXT8.remove);
|
|
3607
3873
|
row.appendChild(trash);
|
|
3608
3874
|
}
|
|
3609
3875
|
return row;
|
|
@@ -3614,7 +3880,7 @@ var IndicatorPicker = class {
|
|
|
3614
3880
|
card.className = "vela-ip-oncard";
|
|
3615
3881
|
const title = doc.createElement("div");
|
|
3616
3882
|
title.className = "vela-ip-group";
|
|
3617
|
-
title.textContent =
|
|
3883
|
+
title.textContent = this.t(TEXT8.onChart, { count: onChart.length });
|
|
3618
3884
|
card.appendChild(title);
|
|
3619
3885
|
for (const [r, i] of onChart) card.appendChild(rowEl(r, { instance: i }));
|
|
3620
3886
|
this.list.appendChild(card);
|
|
@@ -3622,7 +3888,7 @@ var IndicatorPicker = class {
|
|
|
3622
3888
|
const byCat = /* @__PURE__ */ new Map();
|
|
3623
3889
|
library.forEach((r, i) => {
|
|
3624
3890
|
if (!match(r)) return;
|
|
3625
|
-
const cat = r.category ??
|
|
3891
|
+
const cat = r.category ?? GENERAL;
|
|
3626
3892
|
const bucket = byCat.get(cat) ?? [];
|
|
3627
3893
|
bucket.push([r, i]);
|
|
3628
3894
|
byCat.set(cat, bucket);
|
|
@@ -3630,14 +3896,14 @@ var IndicatorPicker = class {
|
|
|
3630
3896
|
for (const [cat, bucket] of byCat) {
|
|
3631
3897
|
const title = doc.createElement("div");
|
|
3632
3898
|
title.className = "vela-ip-group";
|
|
3633
|
-
title.textContent = cat;
|
|
3899
|
+
title.textContent = cat === GENERAL ? this.t(TEXT8.general) : cat;
|
|
3634
3900
|
this.list.appendChild(title);
|
|
3635
3901
|
for (const [r, i] of bucket) this.list.appendChild(rowEl(r, { library: i }));
|
|
3636
3902
|
}
|
|
3637
3903
|
if (!onChart.length && byCat.size === 0) {
|
|
3638
3904
|
const empty = doc.createElement("div");
|
|
3639
3905
|
empty.className = "vela-ip-empty";
|
|
3640
|
-
empty.textContent = library.length ?
|
|
3906
|
+
empty.textContent = this.t(library.length ? TEXT8.noMatch : TEXT8.empty);
|
|
3641
3907
|
this.list.appendChild(empty);
|
|
3642
3908
|
}
|
|
3643
3909
|
}
|
|
@@ -3665,8 +3931,18 @@ var CSS8 = `
|
|
|
3665
3931
|
.vela-tq-hint { margin-top: var(--vela-space-2); text-align: center; color: var(--vela-fg-muted); min-height: 1.2em; }
|
|
3666
3932
|
.vela-tq-hint[data-invalid] { color: var(--vela-danger); }
|
|
3667
3933
|
`;
|
|
3934
|
+
var TEXT9 = {
|
|
3935
|
+
title: msg("widget.timeframeQuick.title", "Change timeframe"),
|
|
3936
|
+
// The codes are what the field accepts, typed as is in every language.
|
|
3937
|
+
example: msg("widget.timeframeQuick.example", "e.g. 15, 4H, D, 3M"),
|
|
3938
|
+
invalid: msg("widget.timeframeQuick.invalid", "Not a timeframe")
|
|
3939
|
+
};
|
|
3668
3940
|
var TimeframeQuick = class {
|
|
3941
|
+
t(text) {
|
|
3942
|
+
return textOf(this.i18n, text);
|
|
3943
|
+
}
|
|
3669
3944
|
constructor(opts) {
|
|
3945
|
+
this.i18n = opts.i18n ?? null;
|
|
3670
3946
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
3671
3947
|
injectStyles(STYLE_ID8, CSS8, doc);
|
|
3672
3948
|
this.input = doc.createElement("input");
|
|
@@ -3675,7 +3951,7 @@ var TimeframeQuick = class {
|
|
|
3675
3951
|
this.hint = doc.createElement("div");
|
|
3676
3952
|
this.hint.className = "vela-tq-hint";
|
|
3677
3953
|
this.dialog = new Dialog({
|
|
3678
|
-
title:
|
|
3954
|
+
title: this.t(TEXT9.title),
|
|
3679
3955
|
host: opts.host,
|
|
3680
3956
|
draggable: true,
|
|
3681
3957
|
closeOnInteractOutside: true,
|
|
@@ -3697,7 +3973,12 @@ var TimeframeQuick = class {
|
|
|
3697
3973
|
}
|
|
3698
3974
|
});
|
|
3699
3975
|
}
|
|
3976
|
+
/** The dialog's title in the shell's language, re-read on every open. */
|
|
3977
|
+
relabel() {
|
|
3978
|
+
this.dialog.titleEl.textContent = this.t(TEXT9.title);
|
|
3979
|
+
}
|
|
3700
3980
|
open(seed = "") {
|
|
3981
|
+
this.relabel();
|
|
3701
3982
|
this.dialog.show();
|
|
3702
3983
|
this.input.value = seed.toUpperCase();
|
|
3703
3984
|
this.renderHint();
|
|
@@ -3728,21 +4009,32 @@ var TimeframeQuick = class {
|
|
|
3728
4009
|
}
|
|
3729
4010
|
renderHint() {
|
|
3730
4011
|
const raw = this.input.value.trim();
|
|
3731
|
-
const parsed = parseTimeframe(raw);
|
|
4012
|
+
const parsed = parseTimeframe(raw, this.i18n);
|
|
3732
4013
|
if (!raw) {
|
|
3733
|
-
this.hint.textContent =
|
|
4014
|
+
this.hint.textContent = this.t(TEXT9.example);
|
|
3734
4015
|
delete this.hint.dataset.invalid;
|
|
3735
4016
|
} else if (parsed.valid) {
|
|
3736
4017
|
this.hint.textContent = parsed.label ?? "";
|
|
3737
4018
|
delete this.hint.dataset.invalid;
|
|
3738
4019
|
} else {
|
|
3739
|
-
this.hint.textContent =
|
|
4020
|
+
this.hint.textContent = this.t(TEXT9.invalid);
|
|
3740
4021
|
this.hint.dataset.invalid = "1";
|
|
3741
4022
|
}
|
|
3742
4023
|
}
|
|
3743
4024
|
};
|
|
3744
4025
|
|
|
3745
4026
|
// src/widget/shortcuts-help.ts
|
|
4027
|
+
var TEXT10 = {
|
|
4028
|
+
title: msg("widget.shortcuts.title", "Keyboard shortcuts"),
|
|
4029
|
+
mouse: msg("widget.shortcuts.mouse", "Mouse"),
|
|
4030
|
+
scroll: msg("widget.shortcuts.scrollHistory", "Scroll through history"),
|
|
4031
|
+
scrollKeys: msg("widget.shortcuts.scrollHistoryKeys", "Shift+Scroll"),
|
|
4032
|
+
measure: msg("widget.shortcuts.measure", "Measure from the press point"),
|
|
4033
|
+
measureKeys: msg("widget.shortcuts.measureKeys", "Shift+Click"),
|
|
4034
|
+
erase: msg("widget.shortcuts.deleteDrawing", "Delete the drawing under the cursor"),
|
|
4035
|
+
eraseKeys: msg("widget.shortcuts.deleteDrawingKeys", "Middle-click"),
|
|
4036
|
+
typing: msg("widget.shortcuts.typing", "Typing a letter or 0 opens the symbol search; typing a digit from 1 to 9 opens the timeframe entry.")
|
|
4037
|
+
};
|
|
3746
4038
|
var STYLE_ID9 = "vela-widget-shortcuts";
|
|
3747
4039
|
var CSS9 = `
|
|
3748
4040
|
.vela-sh-cat {
|
|
@@ -3766,13 +4058,15 @@ var CSS9 = `
|
|
|
3766
4058
|
.vela-sh-static { color: var(--vela-fg-muted); font-size: var(--vela-font-size-sm); margin-top: var(--vela-space-2); }
|
|
3767
4059
|
`;
|
|
3768
4060
|
var ShortcutsHelp = class {
|
|
3769
|
-
|
|
4061
|
+
/** `i18n` returns the shell's current translator; the panel reads it each time it opens. */
|
|
4062
|
+
constructor(keymap, host, onOpenChange, i18n) {
|
|
3770
4063
|
this.keymap = keymap;
|
|
4064
|
+
this.i18n = i18n ?? englishTranslator;
|
|
3771
4065
|
const doc = (host ?? document.body).ownerDocument;
|
|
3772
4066
|
injectStyles(STYLE_ID9, CSS9, doc);
|
|
3773
4067
|
this.list = doc.createElement("div");
|
|
3774
4068
|
this.dialog = new Dialog({
|
|
3775
|
-
title:
|
|
4069
|
+
title: this.i18n().t(TEXT10.title),
|
|
3776
4070
|
host,
|
|
3777
4071
|
closeOnInteractOutside: true,
|
|
3778
4072
|
content: (body) => body.appendChild(this.list),
|
|
@@ -3793,6 +4087,9 @@ var ShortcutsHelp = class {
|
|
|
3793
4087
|
}
|
|
3794
4088
|
refresh() {
|
|
3795
4089
|
const doc = this.list.ownerDocument;
|
|
4090
|
+
const i18n = this.i18n();
|
|
4091
|
+
const t = (m) => i18n.t(m);
|
|
4092
|
+
this.dialog.titleEl.textContent = t(TEXT10.title);
|
|
3796
4093
|
this.list.replaceChildren();
|
|
3797
4094
|
const addCategory = (text) => {
|
|
3798
4095
|
const cat = doc.createElement("div");
|
|
@@ -3818,7 +4115,7 @@ var ShortcutsHelp = class {
|
|
|
3818
4115
|
this.list.appendChild(row);
|
|
3819
4116
|
};
|
|
3820
4117
|
const byCategory = /* @__PURE__ */ new Map();
|
|
3821
|
-
for (const b of this.keymap.bindings()) {
|
|
4118
|
+
for (const b of this.keymap.bindings(i18n)) {
|
|
3822
4119
|
const bucket = byCategory.get(b.category) ?? [];
|
|
3823
4120
|
bucket.push(b);
|
|
3824
4121
|
byCategory.set(b.category, bucket);
|
|
@@ -3827,13 +4124,13 @@ var ShortcutsHelp = class {
|
|
|
3827
4124
|
addCategory(category);
|
|
3828
4125
|
for (const b of bindings) addRow(b.label, b.display);
|
|
3829
4126
|
}
|
|
3830
|
-
addCategory(
|
|
3831
|
-
addRow(
|
|
3832
|
-
addRow(
|
|
3833
|
-
addRow(
|
|
4127
|
+
addCategory(t(TEXT10.mouse));
|
|
4128
|
+
addRow(t(TEXT10.scroll), [t(TEXT10.scrollKeys)]);
|
|
4129
|
+
addRow(t(TEXT10.measure), [t(TEXT10.measureKeys)]);
|
|
4130
|
+
addRow(t(TEXT10.erase), [t(TEXT10.eraseKeys)]);
|
|
3834
4131
|
const s = doc.createElement("div");
|
|
3835
4132
|
s.className = "vela-sh-static";
|
|
3836
|
-
s.textContent =
|
|
4133
|
+
s.textContent = t(TEXT10.typing);
|
|
3837
4134
|
this.list.appendChild(s);
|
|
3838
4135
|
}
|
|
3839
4136
|
};
|
|
@@ -3973,6 +4270,20 @@ function sanitizeDrawingTools(raw) {
|
|
|
3973
4270
|
if (typeof r.stay === "boolean") out.stay = r.stay;
|
|
3974
4271
|
return Object.keys(out).length > 0 ? out : null;
|
|
3975
4272
|
}
|
|
4273
|
+
function successorNative(entry) {
|
|
4274
|
+
const type = typeof entry === "string" ? entry : entry.type;
|
|
4275
|
+
if (type !== "linear-regression") return entry;
|
|
4276
|
+
const hidden = typeof entry === "object" && entry.hidden === true;
|
|
4277
|
+
const { mode, lineColor, ...rest } = typeof entry === "object" ? entry.inputs ?? {} : {};
|
|
4278
|
+
const slope = mode === "Slope";
|
|
4279
|
+
const inputs = slope ? { ...rest, ...lineColor !== void 0 ? { lineColor } : {} } : (
|
|
4280
|
+
// Saved deltas omit the curve's 100-bar default, which the LSMA's default is not.
|
|
4281
|
+
{ length: 100, ...rest, ...lineColor !== void 0 ? { neutralColor: lineColor } : {} }
|
|
4282
|
+
);
|
|
4283
|
+
const next = slope ? "linear-regression-slope" : "lsma";
|
|
4284
|
+
const hasInputs = Object.keys(inputs).length > 0;
|
|
4285
|
+
return hasInputs || hidden ? { type: next, ...hasInputs ? { inputs } : {}, ...hidden ? { hidden } : {} } : next;
|
|
4286
|
+
}
|
|
3976
4287
|
function sanitizeCell(raw) {
|
|
3977
4288
|
if (raw == null || typeof raw !== "object") return null;
|
|
3978
4289
|
const c = raw;
|
|
@@ -4012,7 +4323,7 @@ function sanitizeCell(raw) {
|
|
|
4012
4323
|
return [inputs || hidden ? { type: e.type, ...inputs ? { inputs } : {}, ...hidden ? { hidden } : {} } : e.type];
|
|
4013
4324
|
}
|
|
4014
4325
|
return [];
|
|
4015
|
-
}) : [];
|
|
4326
|
+
}).map(successorNative) : [];
|
|
4016
4327
|
out.indicators = { manifest, natives };
|
|
4017
4328
|
}
|
|
4018
4329
|
const ext = sanitizeExt(c.ext);
|
|
@@ -4170,7 +4481,7 @@ function decimalsFor(ref) {
|
|
|
4170
4481
|
return a >= 1 ? 2 : a >= 0.01 ? 4 : 6;
|
|
4171
4482
|
}
|
|
4172
4483
|
function fmtPrice(n, dp) {
|
|
4173
|
-
if (n == null || !Number.isFinite(n)) return "
|
|
4484
|
+
if (n == null || !Number.isFinite(n)) return "—";
|
|
4174
4485
|
const d = dp ?? decimalsFor(n);
|
|
4175
4486
|
return n.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
|
|
4176
4487
|
}
|
|
@@ -4181,13 +4492,25 @@ function fmtChange(open, close) {
|
|
|
4181
4492
|
const sign = diff >= 0 ? "+" : "";
|
|
4182
4493
|
return `${sign}${fmtPrice(diff, decimalsFor(close))} (${sign}${pct.toFixed(2)}%)`;
|
|
4183
4494
|
}
|
|
4184
|
-
function fmtChangePct(open, close) {
|
|
4185
|
-
if (open == null || close == null || !Number.isFinite(open) || !Number.isFinite(close) || open === 0) return "";
|
|
4186
|
-
const pct = (close - open) / open * 100;
|
|
4187
|
-
return `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%`;
|
|
4188
|
-
}
|
|
4189
4495
|
|
|
4190
4496
|
// src/widget/statusline-model.ts
|
|
4497
|
+
var CELL_LABELS = {
|
|
4498
|
+
open: msg("widget.statusline.openShort", "O"),
|
|
4499
|
+
high: msg("widget.statusline.highShort", "H"),
|
|
4500
|
+
low: msg("widget.statusline.lowShort", "L"),
|
|
4501
|
+
close: msg("widget.statusline.closeShort", "C")
|
|
4502
|
+
};
|
|
4503
|
+
var STATUSLINE_PART_LABELS = {
|
|
4504
|
+
logo: msg("widget.statusline.part.logo", "Symbol logo"),
|
|
4505
|
+
name: msg("widget.statusline.part.name", "Symbol name"),
|
|
4506
|
+
market: msg("widget.statusline.part.market", "Market status"),
|
|
4507
|
+
ohlc: msg("widget.statusline.part.ohlc", "OHLC values"),
|
|
4508
|
+
change: msg("widget.statusline.part.change", "Bar change values")
|
|
4509
|
+
};
|
|
4510
|
+
var TEXT11 = {
|
|
4511
|
+
hideChart: msg("widget.statusline.hideChart", "Hide chart"),
|
|
4512
|
+
showChart: msg("widget.statusline.showChart", "Show chart")
|
|
4513
|
+
};
|
|
4191
4514
|
function segmentVisibility(parts, chartHidden = false) {
|
|
4192
4515
|
return { avatar: parts.logo, symbol: parts.name, meta: parts.name, market: parts.market, ohlc: parts.ohlc && !chartHidden, change: parts.change && !chartHidden, eye: chartHidden };
|
|
4193
4516
|
}
|
|
@@ -4197,28 +4520,31 @@ var STATUSLINE_LADDER = [
|
|
|
4197
4520
|
{ stacked: true, level: "compact" },
|
|
4198
4521
|
{ stacked: true, level: "minimal" }
|
|
4199
4522
|
];
|
|
4200
|
-
function readoutCells(level, readout) {
|
|
4523
|
+
function readoutCells(level, readout, i18n) {
|
|
4201
4524
|
if (readout === "value") return [{ key: "close", label: "" }];
|
|
4525
|
+
const label = (key) => textOf(i18n, CELL_LABELS[key]);
|
|
4202
4526
|
if (level === "full") {
|
|
4203
4527
|
return [
|
|
4204
|
-
{ key: "open", label: "
|
|
4205
|
-
{ key: "high", label: "
|
|
4206
|
-
{ key: "low", label: "
|
|
4207
|
-
{ key: "close", label: "
|
|
4528
|
+
{ key: "open", label: label("open") },
|
|
4529
|
+
{ key: "high", label: label("high") },
|
|
4530
|
+
{ key: "low", label: label("low") },
|
|
4531
|
+
{ key: "close", label: label("close") }
|
|
4208
4532
|
];
|
|
4209
4533
|
}
|
|
4210
|
-
return [{ key: "close", label: level === "compact" ? "
|
|
4534
|
+
return [{ key: "close", label: level === "compact" ? label("close") : "" }];
|
|
4211
4535
|
}
|
|
4212
|
-
function statuslineMenuItems(parts, chartVisible) {
|
|
4536
|
+
function statuslineMenuItems(parts, chartVisible, i18n) {
|
|
4537
|
+
const t = (m) => textOf(i18n, m);
|
|
4213
4538
|
return [
|
|
4214
|
-
{ id: "part:logo", label:
|
|
4215
|
-
{ id: "part:name", label:
|
|
4216
|
-
{ id: "part:market", label:
|
|
4217
|
-
{ id: "part:ohlc", label:
|
|
4218
|
-
{ id: "part:change", label:
|
|
4219
|
-
{ id: "chart", label: chartVisible ?
|
|
4539
|
+
{ id: "part:logo", label: t(STATUSLINE_PART_LABELS.logo), checked: parts.logo },
|
|
4540
|
+
{ id: "part:name", label: t(STATUSLINE_PART_LABELS.name), checked: parts.name },
|
|
4541
|
+
{ id: "part:market", label: t(STATUSLINE_PART_LABELS.market), checked: parts.market },
|
|
4542
|
+
{ id: "part:ohlc", label: t(STATUSLINE_PART_LABELS.ohlc), checked: parts.ohlc },
|
|
4543
|
+
{ id: "part:change", label: t(STATUSLINE_PART_LABELS.change), checked: parts.change },
|
|
4544
|
+
{ id: "chart", label: t(chartVisible ? TEXT11.hideChart : TEXT11.showChart), separatorBefore: true }
|
|
4220
4545
|
];
|
|
4221
4546
|
}
|
|
4547
|
+
var SHOW_CHART_LABEL = TEXT11.showChart;
|
|
4222
4548
|
|
|
4223
4549
|
// src/widget/statusline.ts
|
|
4224
4550
|
var STYLE_ID10 = "vela-widget-statusline";
|
|
@@ -4227,7 +4553,7 @@ var CSS10 = `
|
|
|
4227
4553
|
position: absolute;
|
|
4228
4554
|
top: var(--vela-space-2);
|
|
4229
4555
|
/* Track the indicator legend's left edge: the renderer publishes its toolbar gutter
|
|
4230
|
-
* on the mount container, and the legend sits 10px into the plot to its right
|
|
4556
|
+
* on the mount container, and the legend sits 10px into the plot to its right —
|
|
4231
4557
|
* so the two columns stay aligned whether the toolbar is docked (44px), collapsed
|
|
4232
4558
|
* (16px), or absent entirely (a workspace cell: 0). */
|
|
4233
4559
|
left: calc(var(--vela-toolbar-gutter, 0px) + 10px);
|
|
@@ -4243,8 +4569,8 @@ var CSS10 = `
|
|
|
4243
4569
|
color: var(--vela-fg);
|
|
4244
4570
|
font-size: var(--vela-font-size-md);
|
|
4245
4571
|
/* Same chip treatment as the indicator legend rows (InputsUI): a translucent wash of
|
|
4246
|
-
* the chart background when idle
|
|
4247
|
-
* reach it
|
|
4572
|
+
* the chart background when idle — enough to keep the readout legible when candles
|
|
4573
|
+
* reach it — and the solid chart background on hover. Symmetric 7px padding with a
|
|
4248
4574
|
* compensating negative margin (mirroring the legend rows) keeps the avatar's left
|
|
4249
4575
|
* edge on the legend column's left edge (both at left:10px) while the chip itself
|
|
4250
4576
|
* extends 7px further left, so both columns' chips share the same left edge. */
|
|
@@ -4256,12 +4582,12 @@ var CSS10 = `
|
|
|
4256
4582
|
}
|
|
4257
4583
|
/* Hovering opens the chip the same way a legend row opens: solid chart background
|
|
4258
4584
|
* plus the same inset neutral outline the indicator rows wear (InputsUI's
|
|
4259
|
-
* setRowHighlighted)
|
|
4585
|
+
* setRowHighlighted) — the two columns read as one family. */
|
|
4260
4586
|
.vela-statusline:hover { background: var(--vela-bg); box-shadow: inset 0 0 0 1px var(--vela-border); }
|
|
4261
|
-
/* Chart hidden (the price series' eye
|
|
4587
|
+
/* Chart hidden (the price series' eye — renderer 'candleVisible'): the line dims to
|
|
4262
4588
|
* the same 0.5 wash a hidden indicator's legend row wears. */
|
|
4263
4589
|
.vela-statusline.vela-sl-chart-hidden { opacity: 0.5; }
|
|
4264
|
-
/* Two rows inside the chip
|
|
4590
|
+
/* Two rows inside the chip — the identity (logo / symbol / meta / market badge) and the
|
|
4265
4591
|
* value readout (O/H/L/C + change, or the show-chart eye). In the widest layout they sit
|
|
4266
4592
|
* side by side on one line; the STACKED layouts (fit() decides, see the ladder in
|
|
4267
4593
|
* statusline-model) turn the chip into a column so the values drop under the symbol. */
|
|
@@ -4278,7 +4604,7 @@ var CSS10 = `
|
|
|
4278
4604
|
* that follows the chip (see the stacked legend shift below). */
|
|
4279
4605
|
row-gap: var(--vela-space-1);
|
|
4280
4606
|
}
|
|
4281
|
-
/* Stacked, the identity row may shrink (the meta carries the ellipsis)
|
|
4607
|
+
/* Stacked, the identity row may shrink (the meta carries the ellipsis) — the values row
|
|
4282
4608
|
* never does, so its overflow is what fit() measures to keep descending the ladder. */
|
|
4283
4609
|
.vela-statusline.vela-sl-stacked .vela-sl-identity { max-width: 100%; }
|
|
4284
4610
|
.vela-statusline.vela-sl-stacked .vela-sl-meta {
|
|
@@ -4301,25 +4627,28 @@ var CSS10 = `
|
|
|
4301
4627
|
}
|
|
4302
4628
|
.vela-statusline .vela-sl-symbol { font-weight: 600; font-size: var(--vela-font-size-lg); }
|
|
4303
4629
|
.vela-statusline .vela-sl-meta { color: var(--vela-fg-muted); font-size: var(--vela-font-size-md); font-weight: 600; }
|
|
4304
|
-
/* The meta opens with "
|
|
4630
|
+
/* The meta opens with "· " — sit its dot one space-width after the ticker, as far as the
|
|
4305
4631
|
* venue sits after it, not a full row gap away. */
|
|
4306
4632
|
.vela-statusline .vela-sl-symbol + .vela-sl-meta { margin-left: calc(var(--vela-space-1) - var(--vela-space-2)); }
|
|
4307
|
-
/* Market status badge
|
|
4633
|
+
/* Market status badge — a kit callout bubble (icon-only 16px circle, label on hover
|
|
4308
4634
|
* via the kit tooltip); the session tint is applied per status in setMarketStatus. While
|
|
4309
4635
|
* the chart replays past bars it wears the replay badge instead (the inverse chip). */
|
|
4310
4636
|
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
4311
4637
|
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
4312
4638
|
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
4313
4639
|
/* Tabular figures: every digit has one advance, so a readout whose text changes only in
|
|
4314
|
-
* its digits keeps its width
|
|
4640
|
+
* its digits keeps its width — render() skips the ladder for those (see widthKey). */
|
|
4315
4641
|
.vela-statusline .vela-sl-values { font-variant-numeric: tabular-nums; }
|
|
4316
4642
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
4317
4643
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
4318
|
-
/*
|
|
4644
|
+
/* Prices and the signed change are left-to-right isolates (each carries dir="ltr"): inside a
|
|
4645
|
+
* right-to-left page the row's order mirrors, but a sign stays in front of its number. */
|
|
4646
|
+
.vela-statusline .vela-sl-ohlc b, .vela-statusline .vela-sl-change { unicode-bidi: isolate; }
|
|
4647
|
+
/* The change value wears the SAME ink as the OHLC values (set inline per render) —
|
|
4319
4648
|
* these are the pre-ink fallbacks only. */
|
|
4320
4649
|
.vela-statusline .vela-sl-change[data-dir='up'] { color: var(--vela-up); }
|
|
4321
4650
|
.vela-statusline .vela-sl-change[data-dir='down'] { color: var(--vela-down); }
|
|
4322
|
-
/* The show-chart eye
|
|
4651
|
+
/* The show-chart eye — out only while the chart is hidden (syncParts drives display),
|
|
4323
4652
|
* replacing the value readout it took away. Same footprint as a legend action button. */
|
|
4324
4653
|
.vela-statusline .vela-sl-eye {
|
|
4325
4654
|
align-self: center;
|
|
@@ -4338,18 +4667,18 @@ var CSS10 = `
|
|
|
4338
4667
|
}
|
|
4339
4668
|
.vela-statusline .vela-sl-eye:hover { color: var(--vela-fg); background: color-mix(in srgb, var(--vela-fg) 12%, transparent); }
|
|
4340
4669
|
/* Stack the TOP pane's legend below the status line (lower study panes stay put).
|
|
4341
|
-
* The renderer marks whichever legend sits at the plot's top edge
|
|
4342
|
-
* normally, or a maximized study pane filling the plot
|
|
4670
|
+
* The renderer marks whichever legend sits at the plot's top edge — the price pane
|
|
4671
|
+
* normally, or a maximized study pane filling the plot — so the legend never merges
|
|
4343
4672
|
* with the status line whichever pane owns the top. The renderer sets the legend's
|
|
4344
|
-
* inline top
|
|
4345
|
-
* CARRY a status line (the marker class set by the Statusline constructor)
|
|
4673
|
+
* inline top — shift with a transform, don't fight it. Scoped to hosts that actually
|
|
4674
|
+
* CARRY a status line (the marker class set by the Statusline constructor) — the
|
|
4346
4675
|
* stylesheet is document-global, so a bare attribute selector here would shift every
|
|
4347
4676
|
* chart on the page, including statusline-less ones. */
|
|
4348
4677
|
.vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(26px); }
|
|
4349
|
-
/* A stacked chip is two lines tall
|
|
4678
|
+
/* A stacked chip is two lines tall — the legend shifts one more line. fit() flags the
|
|
4350
4679
|
* host whenever the ladder lands on a stacked layout. */
|
|
4351
4680
|
.vela-has-statusline.vela-sl-stacked-host [${LEGEND_AT_TOP_ATTR}] { transform: translateY(40px); }
|
|
4352
|
-
/* Mobile: the same ladder at the phone's type scale
|
|
4681
|
+
/* Mobile: the same ladder at the phone's type scale — the width alone decides how much
|
|
4353
4682
|
* of the readout fits, exactly as on a narrow desktop chart. */
|
|
4354
4683
|
[data-layout='mobile'] .vela-statusline { font-size: var(--vela-font-size-sm); }
|
|
4355
4684
|
[data-layout='mobile'] .vela-statusline .vela-sl-symbol { font-size: var(--vela-font-size-md); }
|
|
@@ -4358,14 +4687,16 @@ function baseOfTicker(ticker) {
|
|
|
4358
4687
|
return ticker.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || ticker;
|
|
4359
4688
|
}
|
|
4360
4689
|
var MARKET_LABELS = {
|
|
4361
|
-
open: "Market Open",
|
|
4362
|
-
pre: "Pre-Market",
|
|
4363
|
-
post: "Post-Market",
|
|
4364
|
-
extended: "Extended Hours",
|
|
4365
|
-
closed: "Market Closed",
|
|
4366
|
-
holiday: "Market Holiday"
|
|
4690
|
+
open: msg("widget.statusline.market.open", "Market Open"),
|
|
4691
|
+
pre: msg("widget.statusline.market.pre", "Pre-Market"),
|
|
4692
|
+
post: msg("widget.statusline.market.post", "Post-Market"),
|
|
4693
|
+
extended: msg("widget.statusline.market.extended", "Extended Hours"),
|
|
4694
|
+
closed: msg("widget.statusline.market.closed", "Market Closed"),
|
|
4695
|
+
holiday: msg("widget.statusline.market.holiday", "Market Holiday")
|
|
4367
4696
|
};
|
|
4368
|
-
var REPLAY_LABEL = "Replay Mode";
|
|
4697
|
+
var REPLAY_LABEL = msg("widget.statusline.replay", "Replay Mode");
|
|
4698
|
+
var CHANGE_TEXT = msg("widget.statusline.change", "{change} ({percent})");
|
|
4699
|
+
var EM_DASH2 = "—";
|
|
4369
4700
|
var REPLAY_BADGE_SVG = '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="8" style="fill: var(--vela-selected-bg)"/><path d="M11.7 4.75v6.5L7.2 8zM7.2 4.75v6.5L2.7 8z" style="fill: var(--vela-selected-fg); stroke: var(--vela-selected-fg); stroke-width: 0.8; stroke-linejoin: round"/></svg>';
|
|
4370
4701
|
var MARKET_INKS = {
|
|
4371
4702
|
open: "var(--vela-up)",
|
|
@@ -4434,13 +4765,16 @@ var Statusline = class {
|
|
|
4434
4765
|
this.fitKey = null;
|
|
4435
4766
|
/** Web fonts landing after the last fit change every width without a resize. */
|
|
4436
4767
|
this.onFontsLoaded = () => this.fit();
|
|
4768
|
+
/** The bound chart's translator (English until {@link onChart}). */
|
|
4769
|
+
this.i18n = null;
|
|
4770
|
+
this.meta = null;
|
|
4437
4771
|
this.onContextMenu = (e) => {
|
|
4438
4772
|
if (!this.menu || !this.menuHooks) return;
|
|
4439
4773
|
e.preventDefault();
|
|
4440
4774
|
e.stopPropagation();
|
|
4441
4775
|
const chartVisible = this.menuHooks.chartVisible();
|
|
4442
4776
|
this.setChartHidden(!chartVisible);
|
|
4443
|
-
this.menu.setItems(statuslineMenuItems(this.parts, chartVisible));
|
|
4777
|
+
this.menu.setItems(statuslineMenuItems(this.parts, chartVisible, this.i18n));
|
|
4444
4778
|
this.menu.openAt(e.clientX, e.clientY);
|
|
4445
4779
|
};
|
|
4446
4780
|
const doc = host.ownerDocument;
|
|
@@ -4460,7 +4794,7 @@ var Statusline = class {
|
|
|
4460
4794
|
icon: "market-open",
|
|
4461
4795
|
background: `color-mix(in srgb, ${MARKET_INKS.open} 20%, transparent)`,
|
|
4462
4796
|
color: MARKET_INKS.open,
|
|
4463
|
-
label: MARKET_LABELS.open,
|
|
4797
|
+
label: this.t(MARKET_LABELS.open),
|
|
4464
4798
|
host
|
|
4465
4799
|
});
|
|
4466
4800
|
this.replayBadge = doc.createElement("span");
|
|
@@ -4474,11 +4808,12 @@ var Statusline = class {
|
|
|
4474
4808
|
this.ohlcEl.className = "vela-sl-ohlc";
|
|
4475
4809
|
this.changeEl = doc.createElement("span");
|
|
4476
4810
|
this.changeEl.className = "vela-sl-change";
|
|
4811
|
+
this.changeEl.dir = "ltr";
|
|
4477
4812
|
this.eyeEl = doc.createElement("button");
|
|
4478
4813
|
this.eyeEl.type = "button";
|
|
4479
4814
|
this.eyeEl.className = "vela-sl-eye";
|
|
4480
4815
|
this.eyeEl.innerHTML = iconAt("eye-off", 14);
|
|
4481
|
-
this.eyeEl.setAttribute("aria-label",
|
|
4816
|
+
this.eyeEl.setAttribute("aria-label", this.t(SHOW_CHART_LABEL));
|
|
4482
4817
|
this.eyeEl.style.display = "none";
|
|
4483
4818
|
this.eyeEl.addEventListener("click", (e) => {
|
|
4484
4819
|
e.stopPropagation();
|
|
@@ -4498,11 +4833,14 @@ var Statusline = class {
|
|
|
4498
4833
|
this.fitRO.observe(host);
|
|
4499
4834
|
}
|
|
4500
4835
|
doc.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
|
|
4501
|
-
this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
|
|
4502
|
-
this.eyeTip = new Tooltip(this.eyeEl, { content:
|
|
4836
|
+
this.marketTip = new Tooltip(this.marketEl, { content: this.t(MARKET_LABELS.open), placement: "bottom" });
|
|
4837
|
+
this.eyeTip = new Tooltip(this.eyeEl, { content: this.t(SHOW_CHART_LABEL), placement: "bottom" });
|
|
4503
4838
|
this.setMarketStatus("open");
|
|
4504
4839
|
this.render();
|
|
4505
4840
|
}
|
|
4841
|
+
t(text) {
|
|
4842
|
+
return textOf(this.i18n, text);
|
|
4843
|
+
}
|
|
4506
4844
|
setSymbol(symbol) {
|
|
4507
4845
|
const ticker = parseSymbol(symbol).ticker;
|
|
4508
4846
|
this.symbolEl.textContent = ticker;
|
|
@@ -4587,7 +4925,17 @@ var Statusline = class {
|
|
|
4587
4925
|
}
|
|
4588
4926
|
/** The "· BINANCE · 1h" segment after the symbol — venue first, then resolution. */
|
|
4589
4927
|
setMeta(timeframe, provider) {
|
|
4590
|
-
this.
|
|
4928
|
+
this.meta = { timeframe, provider };
|
|
4929
|
+
this.metaEl.textContent = `${provider ? `· ${provider.toUpperCase()} ` : ""}· ${timeframeLabel(timeframe, this.i18n)}`;
|
|
4930
|
+
this.fit();
|
|
4931
|
+
}
|
|
4932
|
+
/** Re-read every label and value in the chart's language. */
|
|
4933
|
+
relocalize() {
|
|
4934
|
+
this.eyeEl.setAttribute("aria-label", this.t(SHOW_CHART_LABEL));
|
|
4935
|
+
this.eyeTip.setContent(this.t(SHOW_CHART_LABEL));
|
|
4936
|
+
this.dressBadge();
|
|
4937
|
+
if (this.meta) this.setMeta(this.meta.timeframe, this.meta.provider);
|
|
4938
|
+
this.fitKey = null;
|
|
4591
4939
|
this.fit();
|
|
4592
4940
|
}
|
|
4593
4941
|
/** Dress the market badge for a session state: its icon, tinted circle, and the
|
|
@@ -4608,7 +4956,7 @@ var Statusline = class {
|
|
|
4608
4956
|
this.replayBadge.hidden = !this.replaying;
|
|
4609
4957
|
if (this.replaying) {
|
|
4610
4958
|
this.marketEl.dataset.status = "replay";
|
|
4611
|
-
this.marketTip.setContent(REPLAY_LABEL);
|
|
4959
|
+
this.marketTip.setContent(this.t(REPLAY_LABEL));
|
|
4612
4960
|
return;
|
|
4613
4961
|
}
|
|
4614
4962
|
const status = this.marketStatus;
|
|
@@ -4618,9 +4966,9 @@ var Statusline = class {
|
|
|
4618
4966
|
icon: `market-${status}`,
|
|
4619
4967
|
background: `color-mix(in srgb, ${ink} 20%, transparent)`,
|
|
4620
4968
|
color: ink,
|
|
4621
|
-
label: MARKET_LABELS[status]
|
|
4969
|
+
label: this.t(MARKET_LABELS[status])
|
|
4622
4970
|
});
|
|
4623
|
-
this.marketTip.setContent(MARKET_LABELS[status]);
|
|
4971
|
+
this.marketTip.setContent(this.t(MARKET_LABELS[status]));
|
|
4624
4972
|
}
|
|
4625
4973
|
/** Show/hide one part — the settings dialog's Status line tab and the right-click
|
|
4626
4974
|
* menu both drive these. 'name' owns the venue/timeframe meta too (see
|
|
@@ -4677,6 +5025,9 @@ var Statusline = class {
|
|
|
4677
5025
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
4678
5026
|
onChart(chart) {
|
|
4679
5027
|
this.detach();
|
|
5028
|
+
const relabel = this.i18n !== chart.i18n;
|
|
5029
|
+
this.i18n = chart.i18n;
|
|
5030
|
+
if (relabel) this.relocalize();
|
|
4680
5031
|
this.lastBar = null;
|
|
4681
5032
|
this.hoverBar = null;
|
|
4682
5033
|
this.unsubs.push(
|
|
@@ -4752,18 +5103,29 @@ var Statusline = class {
|
|
|
4752
5103
|
const doc = this.el.ownerDocument;
|
|
4753
5104
|
const up = this.isUp ? this.isUp(bar) : bar.close >= bar.open;
|
|
4754
5105
|
const ink = up ? this.upColor ?? "var(--vela-up)" : this.downColor ?? "var(--vela-down)";
|
|
5106
|
+
const format = this.i18n?.format;
|
|
5107
|
+
const price = (v) => !Number.isFinite(v) ? EM_DASH2 : format ? format.price(v, { decimals: dp, grouping: true, surface: "statusLine" }) : v.toLocaleString("en-US", { minimumFractionDigits: dp, maximumFractionDigits: dp });
|
|
5108
|
+
const percent = (v) => format ? format.percent(v, { surface: "statusLine" }) : `${v >= 0 ? "+" : "-"}${Math.abs(v).toFixed(2)}%`;
|
|
4755
5109
|
const cell = (k, v) => {
|
|
4756
5110
|
const s = doc.createElement("span");
|
|
4757
5111
|
if (k) s.append(`${k} `);
|
|
4758
5112
|
const b = doc.createElement("b");
|
|
4759
|
-
b.
|
|
5113
|
+
b.dir = "ltr";
|
|
5114
|
+
b.textContent = price(v);
|
|
4760
5115
|
b.style.color = ink;
|
|
4761
5116
|
s.appendChild(b);
|
|
4762
5117
|
return s;
|
|
4763
5118
|
};
|
|
4764
5119
|
const { level } = this.layout;
|
|
4765
|
-
this.ohlcEl.replaceChildren(...readoutCells(level, this.readout).map((c) => cell(c.label, bar[c.key])));
|
|
4766
|
-
|
|
5120
|
+
this.ohlcEl.replaceChildren(...readoutCells(level, this.readout, this.i18n).map((c) => cell(c.label, bar[c.key])));
|
|
5121
|
+
let change = "";
|
|
5122
|
+
if (Number.isFinite(bar.open) && Number.isFinite(bar.close) && bar.open !== 0) {
|
|
5123
|
+
const diff = bar.close - bar.open;
|
|
5124
|
+
const pct = percent(diff / bar.open * 100);
|
|
5125
|
+
const signed = (diff >= 0 ? "+" : "") + price(diff);
|
|
5126
|
+
change = level === "minimal" ? pct : textOf(this.i18n, CHANGE_TEXT, { change: signed, percent: pct });
|
|
5127
|
+
}
|
|
5128
|
+
this.changeEl.textContent = change;
|
|
4767
5129
|
this.changeEl.dataset.dir = up ? "up" : "down";
|
|
4768
5130
|
this.changeEl.style.color = ink;
|
|
4769
5131
|
}
|
|
@@ -4778,7 +5140,7 @@ var CSS11 = `
|
|
|
4778
5140
|
.vela-watermark {
|
|
4779
5141
|
position: absolute;
|
|
4780
5142
|
/* Insets follow the renderer-published gutters AND the price-pane vertical
|
|
4781
|
-
* bounds (mount container), so the mark centers on the PRICE PANE
|
|
5143
|
+
* bounds (mount container), so the mark centers on the PRICE PANE — never
|
|
4782
5144
|
* the study panes below, the drawings toolbar, or the price scale. */
|
|
4783
5145
|
top: var(--vela-price-pane-top, 0px);
|
|
4784
5146
|
bottom: var(--vela-price-pane-bottom, 0px);
|
|
@@ -4812,8 +5174,14 @@ function watermarkFontPx(availPx, textPxAtMax) {
|
|
|
4812
5174
|
if (textPxAtMax <= 0) return MAX_FONT_PX;
|
|
4813
5175
|
return Math.max(MIN_FONT_PX, Math.min(MAX_FONT_PX, Math.floor(MAX_FONT_PX * availPx * FILL / textPxAtMax)));
|
|
4814
5176
|
}
|
|
5177
|
+
var TEXT12 = {
|
|
5178
|
+
replay: msg("widget.watermark.replay", "Replay"),
|
|
5179
|
+
mark: msg("widget.watermark.mark", "{symbol} · {timeframe}")
|
|
5180
|
+
};
|
|
4815
5181
|
var Watermark = class {
|
|
4816
|
-
|
|
5182
|
+
/** `i18n` reads the chart's translator (absent: English). */
|
|
5183
|
+
constructor(host, symbol, timeframe, i18n = () => null) {
|
|
5184
|
+
this.i18n = i18n;
|
|
4817
5185
|
this.resizeObserver = null;
|
|
4818
5186
|
/** The host's visibility preference (the persisted watermark toggle). */
|
|
4819
5187
|
this.shown = true;
|
|
@@ -4825,6 +5193,8 @@ var Watermark = class {
|
|
|
4825
5193
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
4826
5194
|
* fires during chart construction, before any subscriber can see it. */
|
|
4827
5195
|
this.loading = true;
|
|
5196
|
+
this.symbol = "";
|
|
5197
|
+
this.timeframe = "";
|
|
4828
5198
|
injectStyles(STYLE_ID11, CSS11, host.ownerDocument);
|
|
4829
5199
|
this.el = host.ownerDocument.createElement("div");
|
|
4830
5200
|
this.el.className = "vela-watermark";
|
|
@@ -4832,7 +5202,8 @@ var Watermark = class {
|
|
|
4832
5202
|
this.text = host.ownerDocument.createElement("span");
|
|
4833
5203
|
this.replayLine = host.ownerDocument.createElement("span");
|
|
4834
5204
|
this.replayLine.className = "vela-watermark-replay";
|
|
4835
|
-
this.
|
|
5205
|
+
this.replayText = host.ownerDocument.createTextNode(textOf(this.i18n(), TEXT12.replay));
|
|
5206
|
+
this.replayLine.append(iconEl("replay", host.ownerDocument), this.replayText);
|
|
4836
5207
|
this.el.append(this.text, this.replayLine);
|
|
4837
5208
|
host.appendChild(this.el);
|
|
4838
5209
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -4868,9 +5239,17 @@ var Watermark = class {
|
|
|
4868
5239
|
this.el.style.display = (this.shown || replay) && !this.loading ? "" : "none";
|
|
4869
5240
|
}
|
|
4870
5241
|
update(symbol, timeframe) {
|
|
4871
|
-
this.
|
|
5242
|
+
this.symbol = symbol;
|
|
5243
|
+
this.timeframe = timeframe;
|
|
5244
|
+
const i18n = this.i18n();
|
|
5245
|
+
this.text.textContent = symbol ? textOf(i18n, TEXT12.mark, { symbol: parseSymbol(symbol).ticker, timeframe: timeframeLabel(timeframe, i18n) }) : "";
|
|
4872
5246
|
this.fit();
|
|
4873
5247
|
}
|
|
5248
|
+
/** Re-read the mark and the replay line in the chart's language. */
|
|
5249
|
+
relocalize() {
|
|
5250
|
+
this.replayText.data = textOf(this.i18n(), TEXT12.replay);
|
|
5251
|
+
this.update(this.symbol, this.timeframe);
|
|
5252
|
+
}
|
|
4874
5253
|
/** Measure the text at the cap and shrink it to the price pane's width. */
|
|
4875
5254
|
fit() {
|
|
4876
5255
|
if (this.el.clientWidth <= 0 || !this.text.textContent) return;
|
|
@@ -4887,7 +5266,7 @@ var Watermark = class {
|
|
|
4887
5266
|
// src/widget/context-menu.ts
|
|
4888
5267
|
var PRICE_AXIS_W = 60;
|
|
4889
5268
|
var TIME_AXIS_H = 26;
|
|
4890
|
-
var OFF_PLOT = { price: null, time: null, paneKind: null };
|
|
5269
|
+
var OFF_PLOT = { price: null, time: null, paneKind: null, paneId: null };
|
|
4891
5270
|
var ChartContextMenu = class {
|
|
4892
5271
|
constructor(host, cbs) {
|
|
4893
5272
|
this.cbs = cbs;
|
|
@@ -4930,7 +5309,7 @@ var ChartContextMenu = class {
|
|
|
4930
5309
|
this.chart = chart;
|
|
4931
5310
|
this.crosshair = OFF_PLOT;
|
|
4932
5311
|
this.offCrosshair = chart.renderer.onCrosshairMove((e) => {
|
|
4933
|
-
this.crosshair = { price: e.price, time: e.time, paneKind: e.paneKind ?? null };
|
|
5312
|
+
this.crosshair = { price: e.price, time: e.time, paneKind: e.paneKind ?? null, paneId: e.paneId ?? null };
|
|
4934
5313
|
});
|
|
4935
5314
|
}
|
|
4936
5315
|
destroy() {
|
|
@@ -4966,7 +5345,7 @@ var ChartContextMenu = class {
|
|
|
4966
5345
|
}
|
|
4967
5346
|
contributed(zone) {
|
|
4968
5347
|
return widgetActions(`context:${zone}`, this.context()).map((a) => ({
|
|
4969
|
-
item: { id: `action:${a.id}`, label: a.label, icon: a.icon },
|
|
5348
|
+
item: { id: `action:${a.id}`, label: textOf(this.chart?.i18n, a.label), icon: a.icon },
|
|
4970
5349
|
order: a.order
|
|
4971
5350
|
}));
|
|
4972
5351
|
}
|
|
@@ -4974,6 +5353,7 @@ var ChartContextMenu = class {
|
|
|
4974
5353
|
return composeMenu(zone, this.builtinItems(zone), this.contributed(zone));
|
|
4975
5354
|
}
|
|
4976
5355
|
builtinItems(zone) {
|
|
5356
|
+
const chart = this.chart;
|
|
4977
5357
|
if (zone === "price-axis") {
|
|
4978
5358
|
const pane = this.lastPane;
|
|
4979
5359
|
return priceAxisItems({
|
|
@@ -4984,16 +5364,18 @@ var ChartContextMenu = class {
|
|
|
4984
5364
|
priceLabel: this.flag("priceLabel"),
|
|
4985
5365
|
countdown: this.flag("countdown"),
|
|
4986
5366
|
priceLine: this.flag("currentPriceLine")
|
|
4987
|
-
});
|
|
5367
|
+
}, this.chart?.i18n);
|
|
4988
5368
|
}
|
|
4989
5369
|
if (zone === "time-axis") {
|
|
4990
|
-
return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"));
|
|
5370
|
+
return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"), chart?.i18n);
|
|
4991
5371
|
}
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
5372
|
+
return bodyItems(
|
|
5373
|
+
{
|
|
5374
|
+
drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
|
|
5375
|
+
indicators: chart?.indicators().length ?? 0
|
|
5376
|
+
},
|
|
5377
|
+
chart?.i18n
|
|
5378
|
+
);
|
|
4997
5379
|
}
|
|
4998
5380
|
run(id) {
|
|
4999
5381
|
const chart = this.chart;
|
|
@@ -5390,6 +5772,14 @@ var CONTROLS_BOTTOM_PX = TIME_AXIS_H2 + 12;
|
|
|
5390
5772
|
var CLUSTER_H = 24;
|
|
5391
5773
|
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
5392
5774
|
var CLUSTER_LEFT_CSS = "calc((100% + var(--vela-toolbar-gutter, 0px) - var(--vela-scale-gutter, 0px)) / 2)";
|
|
5775
|
+
var TEXT13 = {
|
|
5776
|
+
zoomOut: msg("widget.cellControls.zoomOut", "Zoom out"),
|
|
5777
|
+
zoomIn: msg("widget.cellControls.zoomIn", "Zoom in"),
|
|
5778
|
+
restoreLayout: msg("widget.cellControls.restoreLayout", "Restore layout"),
|
|
5779
|
+
maximizeChart: msg("widget.cellControls.maximizeChart", "Maximize chart"),
|
|
5780
|
+
resetChart: msg("widget.cellControls.resetChart", "Reset chart"),
|
|
5781
|
+
dragToMove: msg("widget.cellControls.dragToMove", "Drag to move chart")
|
|
5782
|
+
};
|
|
5393
5783
|
var STYLE_ID12 = "vela-cell-controls";
|
|
5394
5784
|
var CSS12 = `
|
|
5395
5785
|
.vela-cc-btn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;border-radius:var(--vela-radius-sm);background:transparent;line-height:0;font-size:12px;color:var(--vela-fg-muted);cursor:pointer;}
|
|
@@ -5450,17 +5840,17 @@ var CellControls = class {
|
|
|
5450
5840
|
this.host.appendChild(this.root);
|
|
5451
5841
|
this.refresh();
|
|
5452
5842
|
}
|
|
5453
|
-
/** Rebuild the buttons (the multi-cell gate
|
|
5843
|
+
/** Rebuild the buttons (the multi-cell gate, the maximized state or the language changed). */
|
|
5454
5844
|
refresh() {
|
|
5455
5845
|
this.root.textContent = "";
|
|
5456
5846
|
const multi = this.deps.multiCell();
|
|
5457
5847
|
const maximized = multi && this.deps.isMaximized();
|
|
5458
5848
|
if (multi && !maximized) this.root.appendChild(this.makeGrip());
|
|
5459
|
-
this.root.appendChild(this.button("minus",
|
|
5460
|
-
this.root.appendChild(this.button("plus",
|
|
5849
|
+
this.root.appendChild(this.button("minus", TEXT13.zoomOut, () => this.glider.zoom(ZOOM_OUT)));
|
|
5850
|
+
this.root.appendChild(this.button("plus", TEXT13.zoomIn, () => this.glider.zoom(ZOOM_IN)));
|
|
5461
5851
|
if (multi) {
|
|
5462
5852
|
this.root.appendChild(
|
|
5463
|
-
this.button(maximized ? "restore" : "maximize", maximized ?
|
|
5853
|
+
this.button(maximized ? "restore" : "maximize", maximized ? TEXT13.restoreLayout : TEXT13.maximizeChart, () => this.deps.toggleMaximize(), {
|
|
5464
5854
|
// The maximized state reads as an inverse chip (white-on-dark, dark-on-light),
|
|
5465
5855
|
// the same active-state affordance as a collapsed pane's expand button.
|
|
5466
5856
|
selected: maximized
|
|
@@ -5468,14 +5858,19 @@ var CellControls = class {
|
|
|
5468
5858
|
);
|
|
5469
5859
|
}
|
|
5470
5860
|
this.root.appendChild(
|
|
5471
|
-
this.button("reset",
|
|
5861
|
+
this.button("reset", TEXT13.resetChart, () => {
|
|
5472
5862
|
this.glider.stop();
|
|
5473
5863
|
this.deps.reset();
|
|
5474
5864
|
})
|
|
5475
5865
|
);
|
|
5476
5866
|
}
|
|
5477
|
-
|
|
5867
|
+
/** Text in the chart's language. */
|
|
5868
|
+
t(text) {
|
|
5869
|
+
return textOf(this.deps.chart()?.i18n, text);
|
|
5870
|
+
}
|
|
5871
|
+
button(iconId, text, onClick, opts = {}) {
|
|
5478
5872
|
const b = this.host.ownerDocument.createElement("button");
|
|
5873
|
+
const title = this.t(text);
|
|
5479
5874
|
b.type = "button";
|
|
5480
5875
|
b.title = title;
|
|
5481
5876
|
b.setAttribute("aria-label", title);
|
|
@@ -5493,8 +5888,8 @@ var CellControls = class {
|
|
|
5493
5888
|
makeGrip() {
|
|
5494
5889
|
const b = this.host.ownerDocument.createElement("button");
|
|
5495
5890
|
b.type = "button";
|
|
5496
|
-
b.title =
|
|
5497
|
-
b.setAttribute("aria-label",
|
|
5891
|
+
b.title = this.t(TEXT13.dragToMove);
|
|
5892
|
+
b.setAttribute("aria-label", b.title);
|
|
5498
5893
|
b.className = "vela-cc-btn vela-cc-grip";
|
|
5499
5894
|
b.innerHTML = icon("grip");
|
|
5500
5895
|
b.addEventListener("pointerdown", (e) => this.onGripDown(b, e));
|
|
@@ -5637,6 +6032,28 @@ var WidgetHistory = class {
|
|
|
5637
6032
|
};
|
|
5638
6033
|
|
|
5639
6034
|
// src/workspace/ChartCell.ts
|
|
6035
|
+
var TEXT14 = {
|
|
6036
|
+
regularHours: msg("workspace.settings.regularHours", "Regular hours (RTH)"),
|
|
6037
|
+
extendedHours: msg("workspace.settings.extendedHours", "Extended hours (ETH)"),
|
|
6038
|
+
extendedColor: msg("workspace.settings.extendedColor", "Extended hours"),
|
|
6039
|
+
premarketColor: msg("workspace.settings.premarketColor", "Pre-market"),
|
|
6040
|
+
postmarketColor: msg("workspace.settings.postmarketColor", "Post-market"),
|
|
6041
|
+
tradingSession: msg("workspace.settings.tradingSession", "Trading session"),
|
|
6042
|
+
session: msg("workspace.settings.session", "Session"),
|
|
6043
|
+
advanced: msg("workspace.settings.advanced", "Advanced"),
|
|
6044
|
+
barsToFetch: msg("workspace.settings.barsToFetch", "Bars to fetch"),
|
|
6045
|
+
timeZone: msg("workspace.settings.timeZone", "Time zone"),
|
|
6046
|
+
timeZoneRow: msg("workspace.settings.timeZoneRow", "Time zone"),
|
|
6047
|
+
watermark: msg("workspace.settings.watermark", "Watermark"),
|
|
6048
|
+
symbolWatermark: msg("workspace.settings.symbolWatermark", "Symbol watermark"),
|
|
6049
|
+
replayWatermark: msg("workspace.settings.replayWatermark", "Replay watermark"),
|
|
6050
|
+
statusLine: msg("workspace.settings.statusLine", "Status line"),
|
|
6051
|
+
statusLineParts: msg("workspace.settings.statusLineParts", "Status line"),
|
|
6052
|
+
indicators: msg("workspace.settings.indicators", "Indicators"),
|
|
6053
|
+
indicatorTitles: msg("workspace.settings.indicatorTitles", "Titles"),
|
|
6054
|
+
indicatorValues: msg("workspace.settings.indicatorValues", "Values"),
|
|
6055
|
+
builtIn: msg("workspace.indicators.builtIn", "Built-in")
|
|
6056
|
+
};
|
|
5640
6057
|
function seedDefaults(opts) {
|
|
5641
6058
|
return {
|
|
5642
6059
|
symbol: opts.symbol,
|
|
@@ -5668,6 +6085,13 @@ function instanceDeltas(handle) {
|
|
|
5668
6085
|
const props = inputDeltas(handle.props, handle.propValues());
|
|
5669
6086
|
return inputs || props ? { ...inputs ? { inputs } : {}, ...props ? { props } : {} } : void 0;
|
|
5670
6087
|
}
|
|
6088
|
+
function nativeAliases(type, i18n) {
|
|
6089
|
+
const entry = nativeIndicatorEntry(type);
|
|
6090
|
+
if (!entry) return {};
|
|
6091
|
+
const names = [sourceText(entry.title)];
|
|
6092
|
+
if (entry.shortTitle !== void 0) names.push(textOf(i18n, entry.shortTitle), sourceText(entry.shortTitle));
|
|
6093
|
+
return { aliases: [...new Set(names)] };
|
|
6094
|
+
}
|
|
5671
6095
|
var ChartCell = class {
|
|
5672
6096
|
constructor(id, gridHost, seed, deps) {
|
|
5673
6097
|
this.id = id;
|
|
@@ -5743,6 +6167,7 @@ var ChartCell = class {
|
|
|
5743
6167
|
this.host,
|
|
5744
6168
|
{
|
|
5745
6169
|
...deps.chartDefaults,
|
|
6170
|
+
...deps.localization?.(),
|
|
5746
6171
|
symbol,
|
|
5747
6172
|
timeframe: seed.timeframe,
|
|
5748
6173
|
bars: seed.bars,
|
|
@@ -5828,7 +6253,7 @@ var ChartCell = class {
|
|
|
5828
6253
|
if (!this.indicatorValuesOn) this.inner.renderer.set("indicatorValues", false);
|
|
5829
6254
|
this.watermarkOn = seed.watermark ?? deps.watermark;
|
|
5830
6255
|
this.replayWatermarkOn = seed.replayWatermark ?? true;
|
|
5831
|
-
this.watermark = deps.watermark ? new Watermark(this.host, symbol ?? "", seed.timeframe ?? "60") : null;
|
|
6256
|
+
this.watermark = deps.watermark ? new Watermark(this.host, symbol ?? "", seed.timeframe ?? "60", () => this.inner?.i18n) : null;
|
|
5832
6257
|
if (!this.watermarkOn) this.watermark?.setVisible(false);
|
|
5833
6258
|
if (!this.replayWatermarkOn) this.watermark?.setReplayVisible(false);
|
|
5834
6259
|
this.inner.on("replay:start", () => this.watermark?.setReplaying(true));
|
|
@@ -6056,12 +6481,10 @@ var ChartCell = class {
|
|
|
6056
6481
|
pushSettingsSections() {
|
|
6057
6482
|
const chart = this.inner;
|
|
6058
6483
|
if (!chart) return;
|
|
6059
|
-
const rth = "Regular hours (RTH)";
|
|
6060
|
-
const eth = "Extended hours (ETH)";
|
|
6061
6484
|
const shadeRows = this.sessionOvernightFlag ? [
|
|
6062
6485
|
{
|
|
6063
6486
|
kind: "color",
|
|
6064
|
-
label:
|
|
6487
|
+
label: TEXT14.extendedColor,
|
|
6065
6488
|
id: "extended-color",
|
|
6066
6489
|
get: () => this.sessionShadeColor("extendedColor"),
|
|
6067
6490
|
set: (v) => this.setSessionShadeColor("extendedColor", v)
|
|
@@ -6069,43 +6492,46 @@ var ChartCell = class {
|
|
|
6069
6492
|
] : [
|
|
6070
6493
|
{
|
|
6071
6494
|
kind: "color",
|
|
6072
|
-
label:
|
|
6495
|
+
label: TEXT14.premarketColor,
|
|
6073
6496
|
id: "premarket-color",
|
|
6074
6497
|
get: () => this.sessionShadeColor("premarketColor"),
|
|
6075
6498
|
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
6076
6499
|
},
|
|
6077
6500
|
{
|
|
6078
6501
|
kind: "color",
|
|
6079
|
-
label:
|
|
6502
|
+
label: TEXT14.postmarketColor,
|
|
6080
6503
|
id: "postmarket-color",
|
|
6081
6504
|
get: () => this.sessionShadeColor("postmarketColor"),
|
|
6082
6505
|
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
6083
6506
|
}
|
|
6084
6507
|
];
|
|
6085
6508
|
const sessionSection = {
|
|
6086
|
-
title:
|
|
6509
|
+
title: TEXT14.tradingSession,
|
|
6087
6510
|
id: "trading-session",
|
|
6088
6511
|
placement: "symbol",
|
|
6089
6512
|
rows: [
|
|
6090
6513
|
{
|
|
6091
6514
|
kind: "select",
|
|
6092
|
-
label:
|
|
6515
|
+
label: TEXT14.session,
|
|
6093
6516
|
id: "session",
|
|
6094
|
-
options: [
|
|
6095
|
-
|
|
6096
|
-
|
|
6517
|
+
options: [
|
|
6518
|
+
["regular", TEXT14.regularHours],
|
|
6519
|
+
["extended", TEXT14.extendedHours]
|
|
6520
|
+
],
|
|
6521
|
+
get: () => this.session === "extended" ? "extended" : "regular",
|
|
6522
|
+
set: (v) => this.setSession(v === "extended" ? "extended" : "regular")
|
|
6097
6523
|
},
|
|
6098
6524
|
...shadeRows
|
|
6099
6525
|
]
|
|
6100
6526
|
};
|
|
6101
6527
|
const advanced = {
|
|
6102
|
-
title:
|
|
6528
|
+
title: TEXT14.advanced,
|
|
6103
6529
|
id: "advanced",
|
|
6104
6530
|
placement: "end",
|
|
6105
6531
|
rows: [
|
|
6106
6532
|
{
|
|
6107
6533
|
kind: "select",
|
|
6108
|
-
label:
|
|
6534
|
+
label: TEXT14.barsToFetch,
|
|
6109
6535
|
id: "bars",
|
|
6110
6536
|
options: ["500", "1000", "2000", "5000", "10000", "20000", "50000", "60000", "80000", "100000"],
|
|
6111
6537
|
get: () => String(this.state.bars ?? 1e3),
|
|
@@ -6118,15 +6544,15 @@ var ChartCell = class {
|
|
|
6118
6544
|
]
|
|
6119
6545
|
};
|
|
6120
6546
|
const timezoneSection = {
|
|
6121
|
-
title:
|
|
6547
|
+
title: TEXT14.timeZone,
|
|
6122
6548
|
id: "time-zone",
|
|
6123
6549
|
placement: "symbol",
|
|
6124
6550
|
rows: [
|
|
6125
6551
|
{
|
|
6126
6552
|
kind: "select",
|
|
6127
|
-
label:
|
|
6553
|
+
label: TEXT14.timeZoneRow,
|
|
6128
6554
|
id: "zone",
|
|
6129
|
-
options: timezoneMenuRows(this.deps.timezone()).map((r) => [r.value, r.label]),
|
|
6555
|
+
options: timezoneMenuRows(this.deps.timezone(), chart.i18n).map((r) => [r.value, r.label]),
|
|
6130
6556
|
get: () => {
|
|
6131
6557
|
const choice = this.deps.timezone();
|
|
6132
6558
|
return isExchangeTimezone(choice) ? choice : normalizeTimezone(choice);
|
|
@@ -6136,20 +6562,20 @@ var ChartCell = class {
|
|
|
6136
6562
|
]
|
|
6137
6563
|
};
|
|
6138
6564
|
const watermarkSection = {
|
|
6139
|
-
title:
|
|
6565
|
+
title: TEXT14.watermark,
|
|
6140
6566
|
id: "watermark",
|
|
6141
6567
|
placement: "symbol",
|
|
6142
6568
|
rows: [
|
|
6143
6569
|
{
|
|
6144
6570
|
kind: "toggle",
|
|
6145
|
-
label:
|
|
6571
|
+
label: TEXT14.symbolWatermark,
|
|
6146
6572
|
id: "visible",
|
|
6147
6573
|
get: () => this.watermarkOn,
|
|
6148
6574
|
set: (v) => this.setWatermarkVisible(v)
|
|
6149
6575
|
},
|
|
6150
6576
|
{
|
|
6151
6577
|
kind: "toggle",
|
|
6152
|
-
label:
|
|
6578
|
+
label: TEXT14.replayWatermark,
|
|
6153
6579
|
id: "replay",
|
|
6154
6580
|
get: () => this.replayWatermarkOn,
|
|
6155
6581
|
set: (v) => this.setReplayWatermarkVisible(v)
|
|
@@ -6160,49 +6586,49 @@ var ChartCell = class {
|
|
|
6160
6586
|
if (this.statusline) {
|
|
6161
6587
|
const sl = this.statusline;
|
|
6162
6588
|
sections.push({
|
|
6163
|
-
title:
|
|
6589
|
+
title: TEXT14.statusLine,
|
|
6164
6590
|
id: "status-line",
|
|
6165
6591
|
rows: [
|
|
6166
|
-
{ kind: "heading", label:
|
|
6592
|
+
{ kind: "heading", label: TEXT14.statusLineParts, id: "parts" },
|
|
6167
6593
|
{
|
|
6168
6594
|
kind: "toggle",
|
|
6169
|
-
label:
|
|
6595
|
+
label: STATUSLINE_PART_LABELS.name,
|
|
6170
6596
|
id: "name",
|
|
6171
6597
|
get: () => sl.partVisible("name"),
|
|
6172
6598
|
set: (v) => this.setStatuslinePart("name", v)
|
|
6173
6599
|
},
|
|
6174
6600
|
{
|
|
6175
6601
|
kind: "toggle",
|
|
6176
|
-
label:
|
|
6602
|
+
label: STATUSLINE_PART_LABELS.market,
|
|
6177
6603
|
id: "market",
|
|
6178
6604
|
get: () => sl.partVisible("market"),
|
|
6179
6605
|
set: (v) => this.setStatuslinePart("market", v)
|
|
6180
6606
|
},
|
|
6181
6607
|
{
|
|
6182
6608
|
kind: "toggle",
|
|
6183
|
-
label:
|
|
6609
|
+
label: STATUSLINE_PART_LABELS.ohlc,
|
|
6184
6610
|
id: "ohlc",
|
|
6185
6611
|
get: () => sl.partVisible("ohlc"),
|
|
6186
6612
|
set: (v) => this.setStatuslinePart("ohlc", v)
|
|
6187
6613
|
},
|
|
6188
6614
|
{
|
|
6189
6615
|
kind: "toggle",
|
|
6190
|
-
label:
|
|
6616
|
+
label: STATUSLINE_PART_LABELS.change,
|
|
6191
6617
|
id: "change",
|
|
6192
6618
|
get: () => sl.partVisible("change"),
|
|
6193
6619
|
set: (v) => this.setStatuslinePart("change", v)
|
|
6194
6620
|
},
|
|
6195
|
-
{ kind: "heading", label:
|
|
6621
|
+
{ kind: "heading", label: TEXT14.indicators, id: "indicators" },
|
|
6196
6622
|
{
|
|
6197
6623
|
kind: "toggle",
|
|
6198
|
-
label:
|
|
6624
|
+
label: TEXT14.indicatorTitles,
|
|
6199
6625
|
id: "indicator-titles",
|
|
6200
6626
|
get: () => this.indicatorTitlesOn,
|
|
6201
6627
|
set: (v) => this.setIndicatorTitlesVisible(v)
|
|
6202
6628
|
},
|
|
6203
6629
|
{
|
|
6204
6630
|
kind: "toggle",
|
|
6205
|
-
label:
|
|
6631
|
+
label: TEXT14.indicatorValues,
|
|
6206
6632
|
id: "indicator-values",
|
|
6207
6633
|
get: () => this.indicatorValuesOn,
|
|
6208
6634
|
set: (v) => this.setIndicatorValuesVisible(v)
|
|
@@ -6279,6 +6705,16 @@ var ChartCell = class {
|
|
|
6279
6705
|
if (!this.inner) throw new Error(`[vela] cell "${this.id}" is destroyed`);
|
|
6280
6706
|
return this.inner;
|
|
6281
6707
|
}
|
|
6708
|
+
/** Re-read the cell's own chrome (status line, watermark, view controls, settings rows,
|
|
6709
|
+
* native indicator names) in the chart's language, after `chart.setLocale`. */
|
|
6710
|
+
relocalize() {
|
|
6711
|
+
if (!this.inner) return;
|
|
6712
|
+
this.statusline?.relocalize();
|
|
6713
|
+
this.watermark?.relocalize();
|
|
6714
|
+
this.cellControls.refresh();
|
|
6715
|
+
this.pushSettingsSections();
|
|
6716
|
+
this.refreshNativeCatalog();
|
|
6717
|
+
}
|
|
6282
6718
|
get symbol() {
|
|
6283
6719
|
return this.state.symbol ?? "";
|
|
6284
6720
|
}
|
|
@@ -6467,20 +6903,29 @@ var ChartCell = class {
|
|
|
6467
6903
|
* index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
|
|
6468
6904
|
* read it — indexing the unsorted catalog on add would land on a different study. */
|
|
6469
6905
|
supportedNatives() {
|
|
6470
|
-
|
|
6906
|
+
const locale = this.inner?.i18n.locale ?? "en";
|
|
6907
|
+
const collator = new Intl.Collator(locale, { sensitivity: "base" });
|
|
6908
|
+
return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => collator.compare(a.title, b.title));
|
|
6471
6909
|
}
|
|
6472
6910
|
/** The picker's library rows: supported natives first (see {@link supportedNatives}),
|
|
6473
6911
|
* then the manifest in the host's own order. */
|
|
6474
6912
|
libraryRows() {
|
|
6475
6913
|
return [
|
|
6476
|
-
...this.supportedNatives().map((n) => ({
|
|
6914
|
+
...this.supportedNatives().map((n) => ({
|
|
6915
|
+
name: n.title,
|
|
6916
|
+
...nativeAliases(n.type, this.inner?.i18n),
|
|
6917
|
+
category: textOf(this.inner?.i18n, TEXT14.builtIn),
|
|
6918
|
+
native: true,
|
|
6919
|
+
nativeType: n.type,
|
|
6920
|
+
beta: n.beta
|
|
6921
|
+
})),
|
|
6477
6922
|
...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
|
|
6478
6923
|
];
|
|
6479
6924
|
}
|
|
6480
6925
|
/** The picker's on-chart rows: native instances first, then live script instances. */
|
|
6481
6926
|
onChartRows() {
|
|
6482
6927
|
return [
|
|
6483
|
-
...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
|
|
6928
|
+
...this.nativeHandles().map((h) => ({ name: h.title, ...nativeAliases(h.nativeType, this.inner?.i18n), native: true, nativeType: h.nativeType })),
|
|
6484
6929
|
...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
|
|
6485
6930
|
];
|
|
6486
6931
|
}
|
|
@@ -6850,7 +7295,7 @@ var WorkspaceReplay = class {
|
|
|
6850
7295
|
if (!this.session) return this;
|
|
6851
7296
|
if (intervalMs !== void 0) {
|
|
6852
7297
|
if (!(Number.isFinite(intervalMs) && intervalMs > 0)) {
|
|
6853
|
-
console.warn(`[vela] workspace.replay.play(${intervalMs}) ignored
|
|
7298
|
+
console.warn(`[vela] workspace.replay.play(${intervalMs}) ignored — the interval must be a positive number of ms`);
|
|
6854
7299
|
return this;
|
|
6855
7300
|
}
|
|
6856
7301
|
this.intervalMs = intervalMs;
|
|
@@ -7078,27 +7523,37 @@ function monthSpan(timeframe) {
|
|
|
7078
7523
|
|
|
7079
7524
|
// src/workspace/layouts.ts
|
|
7080
7525
|
var registry = /* @__PURE__ */ new Map();
|
|
7526
|
+
var labels = /* @__PURE__ */ new Map();
|
|
7081
7527
|
function registerLayout(def) {
|
|
7082
|
-
registry.set(def.id, def);
|
|
7528
|
+
registry.set(def.id, typeof def.label === "string" ? def : { ...def, label: sourceText(def.label) });
|
|
7529
|
+
labels.set(def.id, def.label);
|
|
7083
7530
|
}
|
|
7084
7531
|
function unregisterLayout(id) {
|
|
7085
7532
|
registry.delete(id);
|
|
7533
|
+
labels.delete(id);
|
|
7534
|
+
}
|
|
7535
|
+
function inLanguage(def, i18n) {
|
|
7536
|
+
const grid = GRID_ID_RE.exec(def.id);
|
|
7537
|
+
const message = labels.get(def.id) ?? (grid ? gridLabel(Number(grid[1]), Number(grid[2])) : def.label);
|
|
7538
|
+
const label = textOf(i18n, message);
|
|
7539
|
+
return label === def.label ? def : { ...def, label };
|
|
7086
7540
|
}
|
|
7087
|
-
function layoutDefinition(id) {
|
|
7088
|
-
|
|
7541
|
+
function layoutDefinition(id, i18n) {
|
|
7542
|
+
const def = registry.get(id);
|
|
7543
|
+
return def && inLanguage(def, i18n);
|
|
7089
7544
|
}
|
|
7090
|
-
function layouts() {
|
|
7091
|
-
return [...registry.values()];
|
|
7545
|
+
function layouts(i18n) {
|
|
7546
|
+
return [...registry.values()].map((def) => inLanguage(def, i18n));
|
|
7092
7547
|
}
|
|
7093
7548
|
function slots(n) {
|
|
7094
7549
|
return Array.from({ length: n }, (_, i) => ({ id: `c${i + 1}` }));
|
|
7095
7550
|
}
|
|
7096
7551
|
function registerBuiltinLayouts() {
|
|
7097
|
-
registerLayout({ id: "1", label: "Single", cols: [1], rows: [1], cells: slots(1) });
|
|
7098
|
-
registerLayout({ id: "2h", label: "2 side by side", cols: [1, 1], rows: [1], cells: slots(2) });
|
|
7099
|
-
registerLayout({ id: "2v", label: "2 stacked", cols: [1], rows: [1, 1], cells: slots(2) });
|
|
7100
|
-
registerLayout({ id: "4", label: "4 grid", cols: [1, 1], rows: [1, 1], cells: slots(4) });
|
|
7101
|
-
registerLayout({ id: "8", label: "8 grid", cols: [1, 1, 1, 1], rows: [1, 1], cells: slots(8) });
|
|
7552
|
+
registerLayout({ id: "1", label: msg("workspace.layout.single", "Single"), cols: [1], rows: [1], cells: slots(1) });
|
|
7553
|
+
registerLayout({ id: "2h", label: msg("workspace.layout.sideBySide", "2 side by side"), cols: [1, 1], rows: [1], cells: slots(2) });
|
|
7554
|
+
registerLayout({ id: "2v", label: msg("workspace.layout.stacked", "2 stacked"), cols: [1], rows: [1, 1], cells: slots(2) });
|
|
7555
|
+
registerLayout({ id: "4", label: msg("workspace.layout.grid4", "4 grid"), cols: [1, 1], rows: [1, 1], cells: slots(4) });
|
|
7556
|
+
registerLayout({ id: "8", label: msg("workspace.layout.grid8", "8 grid"), cols: [1, 1, 1, 1], rows: [1, 1], cells: slots(8) });
|
|
7102
7557
|
}
|
|
7103
7558
|
var GRID_PICKER_MAX = 4;
|
|
7104
7559
|
var GRID_BUILTIN_IDS = {
|
|
@@ -7108,17 +7563,20 @@ var GRID_BUILTIN_IDS = {
|
|
|
7108
7563
|
"2x2": "4",
|
|
7109
7564
|
"2x4": "8"
|
|
7110
7565
|
};
|
|
7566
|
+
var GRID_LABEL = msg("workspace.layout.grid", "{cols} × {rows} grid");
|
|
7567
|
+
function gridLabel(rows, cols) {
|
|
7568
|
+
return withValues(GRID_LABEL, { cols, rows });
|
|
7569
|
+
}
|
|
7111
7570
|
var clampTrack = (n) => Math.max(1, Math.min(GRID_PICKER_MAX, Math.round(n)));
|
|
7112
|
-
function layoutForGrid(rows, cols) {
|
|
7571
|
+
function layoutForGrid(rows, cols, i18n) {
|
|
7113
7572
|
const r = clampTrack(rows);
|
|
7114
7573
|
const c = clampTrack(cols);
|
|
7115
7574
|
const builtin = GRID_BUILTIN_IDS[`${r}x${c}`];
|
|
7116
7575
|
const registered = builtin ? registry.get(builtin) : void 0;
|
|
7117
|
-
if (registered) return registered;
|
|
7576
|
+
if (registered) return inLanguage(registered, i18n);
|
|
7118
7577
|
return {
|
|
7119
7578
|
id: `g${r}x${c}`,
|
|
7120
|
-
|
|
7121
|
-
label: `${c} \xD7 ${r} grid`,
|
|
7579
|
+
label: textOf(i18n, gridLabel(r, c)),
|
|
7122
7580
|
cols: Array.from({ length: c }, () => 1),
|
|
7123
7581
|
rows: Array.from({ length: r }, () => 1),
|
|
7124
7582
|
cells: slots(r * c)
|
|
@@ -7141,7 +7599,7 @@ function layoutShape(def) {
|
|
|
7141
7599
|
function occupancyGrid(def) {
|
|
7142
7600
|
if (def.areas) return def.areas.map((row) => row.trim().split(/\s+/));
|
|
7143
7601
|
const cols = def.cols.length;
|
|
7144
|
-
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ??
|
|
7602
|
+
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `·${r}x${c}`));
|
|
7145
7603
|
}
|
|
7146
7604
|
var GLYPH_MIN = 1.5;
|
|
7147
7605
|
var GLYPH_SPAN = 13;
|
|
@@ -7432,8 +7890,8 @@ var TOOL_BINDINGS = [
|
|
|
7432
7890
|
["hline", "drawings.hline-cursor"],
|
|
7433
7891
|
["vline", "drawings.vline-cursor"]
|
|
7434
7892
|
];
|
|
7435
|
-
function toolShortcutHints(keymap) {
|
|
7436
|
-
const byId = new Map(keymap.bindings().map((b) => [b.id, b.display[0]]));
|
|
7893
|
+
function toolShortcutHints(keymap, i18n) {
|
|
7894
|
+
const byId = new Map(keymap.bindings(i18n).map((b) => [b.id, b.display[0]]));
|
|
7437
7895
|
const out = {};
|
|
7438
7896
|
for (const [type, id] of TOOL_BINDINGS) {
|
|
7439
7897
|
const display = byId.get(id);
|
|
@@ -7531,10 +7989,10 @@ var CSS14 = `
|
|
|
7531
7989
|
.vela-mb-item:active { background: var(--vela-hover); }
|
|
7532
7990
|
.vela-mb-item .vela-icon { font-size: 18px; width: 18px; height: 18px; }
|
|
7533
7991
|
/* A lit stop (the maximize toggle while something is isolated): the inverse
|
|
7534
|
-
"selected" chip
|
|
7992
|
+
"selected" chip — white on the dark theme, dark on the light one. */
|
|
7535
7993
|
.vela-mb-item.vela-mb-on, .vela-mb-item.vela-mb-on:active { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
7536
7994
|
/* Left-aligned contributed actions get their own stops (the built-in indicators
|
|
7537
|
-
slot)
|
|
7995
|
+
slot) — the wrapper is layout-transparent so each stop flexes like a sibling. */
|
|
7538
7996
|
.vela-mb-actions { display: contents; }
|
|
7539
7997
|
.vela-mb-symbol {
|
|
7540
7998
|
flex: 1.6 1 0;
|
|
@@ -7548,39 +8006,57 @@ var CSS14 = `
|
|
|
7548
8006
|
text-align: center;
|
|
7549
8007
|
}
|
|
7550
8008
|
`;
|
|
8009
|
+
var TEXT15 = {
|
|
8010
|
+
symbolSearch: msg("widget.mobileBar.symbolSearch", "Symbol search"),
|
|
8011
|
+
timeframe: msg("widget.mobileBar.timeframe", "Timeframe"),
|
|
8012
|
+
indicators: msg("widget.mobileBar.indicators", "Indicators"),
|
|
8013
|
+
drawings: msg("widget.mobileBar.drawings", "Drawings"),
|
|
8014
|
+
maximizeChart: msg("widget.mobileBar.maximizeChart", "Maximize chart"),
|
|
8015
|
+
restoreLayout: msg("widget.mobileBar.restoreLayout", "Restore layout"),
|
|
8016
|
+
more: msg("widget.mobileBar.more", "More"),
|
|
8017
|
+
chartSettings: msg("widget.mobileBar.chartSettings", "Chart settings")
|
|
8018
|
+
};
|
|
7551
8019
|
var MobileBar = class {
|
|
7552
8020
|
constructor(host, opts) {
|
|
8021
|
+
/** Each fixed stop's accessible name, re-read by {@link relocalize}. */
|
|
8022
|
+
this.labels = [];
|
|
8023
|
+
this.maximizeActive = false;
|
|
7553
8024
|
this.opts = opts;
|
|
7554
8025
|
const doc = host.ownerDocument;
|
|
7555
8026
|
injectStyles(STYLE_ID14, CSS14, doc);
|
|
7556
8027
|
this.el = doc.createElement("div");
|
|
7557
8028
|
this.el.className = "vela-mobilebar";
|
|
8029
|
+
this.timeframe = opts.timeframe;
|
|
7558
8030
|
const item = (cls, label, onClick, icon2) => {
|
|
7559
8031
|
const b = doc.createElement("button");
|
|
7560
8032
|
b.className = `vela-mb-item ${cls}`;
|
|
7561
|
-
b.setAttribute("aria-label", label);
|
|
8033
|
+
b.setAttribute("aria-label", this.t(label));
|
|
8034
|
+
this.labels.push({ el: b, label });
|
|
7562
8035
|
if (icon2) b.appendChild(iconEl(icon2, doc));
|
|
7563
8036
|
b.addEventListener("click", onClick);
|
|
7564
8037
|
return b;
|
|
7565
8038
|
};
|
|
7566
|
-
this.symbolEl = item("vela-mb-symbol",
|
|
8039
|
+
this.symbolEl = item("vela-mb-symbol", TEXT15.symbolSearch, opts.onSymbolClick);
|
|
7567
8040
|
this.symbolEl.textContent = parseSymbol(opts.symbol).ticker;
|
|
7568
|
-
this.tfEl = item("vela-mb-tf",
|
|
7569
|
-
this.tfEl.textContent = timeframeLabel(opts.timeframe);
|
|
8041
|
+
this.tfEl = item("vela-mb-tf", TEXT15.timeframe, opts.onTimeframeClick);
|
|
8042
|
+
this.tfEl.textContent = timeframeLabel(opts.timeframe, opts.i18n);
|
|
7570
8043
|
const onIndicators = opts.onIndicatorsClick;
|
|
7571
|
-
const indicators = onIndicators ? item("vela-mb-indicators",
|
|
8044
|
+
const indicators = onIndicators ? item("vela-mb-indicators", TEXT15.indicators, onIndicators, "indicators") : null;
|
|
7572
8045
|
this.actionsHost = doc.createElement("span");
|
|
7573
8046
|
this.actionsHost.className = "vela-mb-actions";
|
|
7574
8047
|
const onDrawings = opts.onDrawingsClick;
|
|
7575
|
-
const drawings = onDrawings ? item("vela-mb-drawings",
|
|
8048
|
+
const drawings = onDrawings ? item("vela-mb-drawings", TEXT15.drawings, onDrawings, "pen") : null;
|
|
7576
8049
|
const onMaximize = opts.onMaximizeClick;
|
|
7577
|
-
this.maxEl = onMaximize ? item("vela-mb-maximize",
|
|
7578
|
-
const more = item("vela-mb-more",
|
|
7579
|
-
const settings = item("vela-mb-settings",
|
|
8050
|
+
this.maxEl = onMaximize ? item("vela-mb-maximize", TEXT15.maximizeChart, onMaximize, "maximize") : null;
|
|
8051
|
+
const more = item("vela-mb-more", TEXT15.more, opts.onMoreClick, "kebab");
|
|
8052
|
+
const settings = item("vela-mb-settings", TEXT15.chartSettings, opts.onSettingsClick, "gear");
|
|
7580
8053
|
this.el.append(this.symbolEl, this.tfEl, ...indicators ? [indicators] : [], this.actionsHost, ...drawings ? [drawings] : [], ...this.maxEl ? [this.maxEl] : [], more, settings);
|
|
7581
8054
|
host.appendChild(this.el);
|
|
7582
8055
|
this.renderActions();
|
|
7583
8056
|
}
|
|
8057
|
+
t(text) {
|
|
8058
|
+
return textOf(this.opts.i18n, text);
|
|
8059
|
+
}
|
|
7584
8060
|
/** Re-project the contributed actions placed on the bar (left-aligned by default —
|
|
7585
8061
|
* see `mobilePlacement`) as icon-only stops in the indicators slot (call after
|
|
7586
8062
|
* registrations change). The rest are three-dots drawer rows. */
|
|
@@ -7593,9 +8069,10 @@ var MobileBar = class {
|
|
|
7593
8069
|
for (const action of widgetActions("topbar", ctx).filter((a) => mobilePlacement(a) === "bar" && !builtin.has(a.id))) {
|
|
7594
8070
|
const b = doc.createElement("button");
|
|
7595
8071
|
b.className = "vela-mb-item";
|
|
7596
|
-
|
|
8072
|
+
const label = textOf(ctx.i18n ?? this.opts.i18n, action.label);
|
|
8073
|
+
b.setAttribute("aria-label", label);
|
|
7597
8074
|
if (action.icon) b.appendChild(iconEl(action.icon, doc));
|
|
7598
|
-
else b.appendChild(doc.createTextNode(
|
|
8075
|
+
else b.appendChild(doc.createTextNode(label));
|
|
7599
8076
|
b.addEventListener("click", () => {
|
|
7600
8077
|
const c = this.opts.getContext?.();
|
|
7601
8078
|
if (c) action.run(c);
|
|
@@ -7607,14 +8084,23 @@ var MobileBar = class {
|
|
|
7607
8084
|
this.symbolEl.textContent = parseSymbol(symbol).ticker;
|
|
7608
8085
|
}
|
|
7609
8086
|
setTimeframe(tf) {
|
|
7610
|
-
this.
|
|
8087
|
+
this.timeframe = tf;
|
|
8088
|
+
this.tfEl.textContent = timeframeLabel(tf, this.opts.i18n);
|
|
8089
|
+
}
|
|
8090
|
+
/** Re-read every label in the current language. */
|
|
8091
|
+
relocalize() {
|
|
8092
|
+
for (const { el, label } of this.labels) el.setAttribute("aria-label", this.t(label));
|
|
8093
|
+
this.setTimeframe(this.timeframe);
|
|
8094
|
+
if (this.maxEl) this.setMaximizeActive(this.maximizeActive);
|
|
8095
|
+
this.renderActions();
|
|
7611
8096
|
}
|
|
7612
8097
|
/** Light the maximize stop while something is isolated (a chart over the grid,
|
|
7613
8098
|
* or a maximized pane inside the active chart) — inverse chip + restore glyph. */
|
|
7614
8099
|
setMaximizeActive(on) {
|
|
7615
8100
|
if (!this.maxEl) return;
|
|
8101
|
+
this.maximizeActive = on;
|
|
7616
8102
|
this.maxEl.classList.toggle("vela-mb-on", on);
|
|
7617
|
-
this.maxEl.setAttribute("aria-label", on ?
|
|
8103
|
+
this.maxEl.setAttribute("aria-label", this.t(on ? TEXT15.restoreLayout : TEXT15.maximizeChart));
|
|
7618
8104
|
this.maxEl.replaceChildren(iconEl(on ? "restore" : "maximize", this.el.ownerDocument));
|
|
7619
8105
|
}
|
|
7620
8106
|
/** Show the maximize stop only while the grid holds more than one chart — on a
|
|
@@ -7680,23 +8166,28 @@ var CSS15 = `
|
|
|
7680
8166
|
background: var(--vela-hover);
|
|
7681
8167
|
}
|
|
7682
8168
|
`;
|
|
8169
|
+
var TEXT16 = {
|
|
8170
|
+
dateRange: msg("widget.timeframeDrawer.dateRange", "Date Range"),
|
|
8171
|
+
timeframes: msg("widget.timeframeDrawer.timeframes", "Timeframes")
|
|
8172
|
+
};
|
|
7683
8173
|
var TimeframeDrawer = class {
|
|
7684
8174
|
constructor(opts) {
|
|
7685
8175
|
this.opts = opts;
|
|
7686
8176
|
this.rangeChips = /* @__PURE__ */ new Map();
|
|
7687
8177
|
this.tfChips = /* @__PURE__ */ new Map();
|
|
8178
|
+
this.headings = [];
|
|
7688
8179
|
const doc = opts.host.ownerDocument;
|
|
7689
8180
|
injectStyles(STYLE_ID15, CSS15, doc);
|
|
7690
8181
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
7691
8182
|
const rangeHeading = doc.createElement("div");
|
|
7692
8183
|
rangeHeading.className = "vela-tfd-heading";
|
|
7693
|
-
|
|
8184
|
+
this.headings.push({ el: rangeHeading, text: TEXT16.dateRange });
|
|
7694
8185
|
const ranges = doc.createElement("div");
|
|
7695
8186
|
ranges.className = "vela-tfd-ranges";
|
|
7696
8187
|
for (const preset of opts.ranges) {
|
|
7697
8188
|
const chip = doc.createElement("button");
|
|
7698
8189
|
chip.className = "vela-tfd-chip";
|
|
7699
|
-
chip.textContent = preset.id;
|
|
8190
|
+
chip.textContent = rangeLabel(preset.id, opts.i18n);
|
|
7700
8191
|
chip.addEventListener("click", () => {
|
|
7701
8192
|
opts.onRange(preset);
|
|
7702
8193
|
this.drawer.hide();
|
|
@@ -7706,13 +8197,13 @@ var TimeframeDrawer = class {
|
|
|
7706
8197
|
}
|
|
7707
8198
|
const tfHeading = doc.createElement("div");
|
|
7708
8199
|
tfHeading.className = "vela-tfd-heading";
|
|
7709
|
-
tfHeading.
|
|
8200
|
+
this.headings.push({ el: tfHeading, text: TEXT16.timeframes });
|
|
7710
8201
|
const grid = doc.createElement("div");
|
|
7711
8202
|
grid.className = "vela-tfd-grid";
|
|
7712
8203
|
for (const tf of opts.timeframes) {
|
|
7713
8204
|
const chip = doc.createElement("button");
|
|
7714
8205
|
chip.className = "vela-tfd-chip";
|
|
7715
|
-
chip.textContent = timeframeLabel(tf);
|
|
8206
|
+
chip.textContent = timeframeLabel(tf, opts.i18n);
|
|
7716
8207
|
chip.addEventListener("click", () => {
|
|
7717
8208
|
opts.onTimeframe(tf);
|
|
7718
8209
|
this.drawer.hide();
|
|
@@ -7721,8 +8212,17 @@ var TimeframeDrawer = class {
|
|
|
7721
8212
|
grid.appendChild(chip);
|
|
7722
8213
|
}
|
|
7723
8214
|
this.drawer.body.append(rangeHeading, ranges, tfHeading, grid);
|
|
8215
|
+
this.relabel();
|
|
8216
|
+
}
|
|
8217
|
+
/** Headings and chips in the current language. */
|
|
8218
|
+
relabel() {
|
|
8219
|
+
const i18n = this.opts.i18n;
|
|
8220
|
+
for (const { el, text } of this.headings) el.textContent = textOf(i18n, text);
|
|
8221
|
+
for (const [id, chip] of this.rangeChips) chip.textContent = rangeLabel(id, i18n);
|
|
8222
|
+
for (const [tf, chip] of this.tfChips) chip.textContent = timeframeLabel(tf, i18n);
|
|
7724
8223
|
}
|
|
7725
8224
|
open() {
|
|
8225
|
+
this.relabel();
|
|
7726
8226
|
const tf = this.opts.currentTimeframe();
|
|
7727
8227
|
const range = this.opts.activeRange();
|
|
7728
8228
|
for (const [id, chip] of this.rangeChips) {
|
|
@@ -7744,6 +8244,12 @@ var TimeframeDrawer = class {
|
|
|
7744
8244
|
};
|
|
7745
8245
|
|
|
7746
8246
|
// src/widget/drawings-drawer.ts
|
|
8247
|
+
var TEXT17 = {
|
|
8248
|
+
title: msg("widget.drawingsDrawer.title", "Drawings"),
|
|
8249
|
+
search: msg("widget.drawingsDrawer.search", "Search tools…"),
|
|
8250
|
+
favorite: msg("widget.drawingsDrawer.favorite", "Favorite {tool}"),
|
|
8251
|
+
noMatch: msg("widget.drawingsDrawer.noMatch", "No tools match.")
|
|
8252
|
+
};
|
|
7747
8253
|
var STYLE_ID16 = "vela-widget-drawings-drawer";
|
|
7748
8254
|
var CSS16 = `
|
|
7749
8255
|
/* Search + tabs stay pinned while the tool list scrolls underneath. */
|
|
@@ -7777,7 +8283,7 @@ var CSS16 = `
|
|
|
7777
8283
|
gap: 4px;
|
|
7778
8284
|
overflow-x: auto;
|
|
7779
8285
|
scrollbar-width: none;
|
|
7780
|
-
/* The strip scrolls sideways itself
|
|
8286
|
+
/* The strip scrolls sideways itself — its touches are native scroll, not tab swipes
|
|
7781
8287
|
(the drawer body is pan-y so everywhere ELSE a sideways move swipes the tabs). */
|
|
7782
8288
|
touch-action: pan-x;
|
|
7783
8289
|
padding: 0 2px 8px;
|
|
@@ -7850,7 +8356,7 @@ var DrawingsDrawer = class {
|
|
|
7850
8356
|
injectStyles(STYLE_ID16, CSS16, doc);
|
|
7851
8357
|
this.drawer = new Drawer({
|
|
7852
8358
|
host: opts.host,
|
|
7853
|
-
title:
|
|
8359
|
+
title: this.t(TEXT17.title),
|
|
7854
8360
|
onOpenChange: opts.onOpenChange,
|
|
7855
8361
|
// Swiping across the tool list pages through the group tabs.
|
|
7856
8362
|
onSwipe: (dir) => this.stepGroup(dir === "left" ? 1 : -1)
|
|
@@ -7862,7 +8368,7 @@ var DrawingsDrawer = class {
|
|
|
7862
8368
|
search.appendChild(iconEl("search", doc));
|
|
7863
8369
|
this.input = doc.createElement("input");
|
|
7864
8370
|
this.input.type = "text";
|
|
7865
|
-
this.input.placeholder =
|
|
8371
|
+
this.input.placeholder = this.t(TEXT17.search);
|
|
7866
8372
|
this.input.addEventListener("input", () => this.renderList());
|
|
7867
8373
|
search.appendChild(this.input);
|
|
7868
8374
|
this.tabs = doc.createElement("div");
|
|
@@ -7872,7 +8378,12 @@ var DrawingsDrawer = class {
|
|
|
7872
8378
|
this.list.className = "vela-dd-list";
|
|
7873
8379
|
this.drawer.body.append(sticky, this.list);
|
|
7874
8380
|
}
|
|
8381
|
+
t(m, values) {
|
|
8382
|
+
return textOf(this.opts.i18n?.() ?? null, m, values);
|
|
8383
|
+
}
|
|
7875
8384
|
open() {
|
|
8385
|
+
this.drawer.setTitle(this.t(TEXT17.title));
|
|
8386
|
+
this.input.placeholder = this.t(TEXT17.search);
|
|
7876
8387
|
this.definition = this.opts.toolbar();
|
|
7877
8388
|
if (!this.definition.groups.some((g) => g.id === this.activeGroup)) this.activeGroup = this.definition.groups[0]?.id ?? "";
|
|
7878
8389
|
this.input.value = "";
|
|
@@ -7904,7 +8415,7 @@ var DrawingsDrawer = class {
|
|
|
7904
8415
|
for (const group of this.definition.groups) {
|
|
7905
8416
|
const tab = doc.createElement("button");
|
|
7906
8417
|
tab.className = "vela-dd-tab";
|
|
7907
|
-
tab.textContent = group.label;
|
|
8418
|
+
tab.textContent = this.t(group.label);
|
|
7908
8419
|
if (group.id === this.activeGroup) tab.dataset.active = "1";
|
|
7909
8420
|
tab.addEventListener("click", () => {
|
|
7910
8421
|
this.activeGroup = group.id;
|
|
@@ -7918,7 +8429,15 @@ var DrawingsDrawer = class {
|
|
|
7918
8429
|
renderList() {
|
|
7919
8430
|
const doc = this.list.ownerDocument;
|
|
7920
8431
|
this.list.replaceChildren();
|
|
7921
|
-
const
|
|
8432
|
+
const locale = this.opts.i18n?.().locale;
|
|
8433
|
+
const lower = (s) => {
|
|
8434
|
+
try {
|
|
8435
|
+
return s.toLocaleLowerCase(locale);
|
|
8436
|
+
} catch {
|
|
8437
|
+
return s.toLowerCase();
|
|
8438
|
+
}
|
|
8439
|
+
};
|
|
8440
|
+
const query = lower(this.input.value.trim());
|
|
7922
8441
|
const active = this.opts.currentTool();
|
|
7923
8442
|
const section = (label) => {
|
|
7924
8443
|
const el = doc.createElement("div");
|
|
@@ -7935,7 +8454,7 @@ var DrawingsDrawer = class {
|
|
|
7935
8454
|
glyph.innerHTML = tool.icon;
|
|
7936
8455
|
const label = doc.createElement("span");
|
|
7937
8456
|
label.className = "vela-dd-label";
|
|
7938
|
-
label.textContent = tool.label;
|
|
8457
|
+
label.textContent = this.t(tool.label);
|
|
7939
8458
|
const star = doc.createElement("button");
|
|
7940
8459
|
star.className = "vela-dd-star";
|
|
7941
8460
|
const paintStar = (on) => {
|
|
@@ -7944,7 +8463,7 @@ var DrawingsDrawer = class {
|
|
|
7944
8463
|
else delete star.dataset.on;
|
|
7945
8464
|
};
|
|
7946
8465
|
paintStar(this.opts.isFavorite(tool.type));
|
|
7947
|
-
star.setAttribute("aria-label",
|
|
8466
|
+
star.setAttribute("aria-label", this.t(TEXT17.favorite, { tool: this.t(tool.label) }));
|
|
7948
8467
|
star.addEventListener("click", (e) => {
|
|
7949
8468
|
e.stopPropagation();
|
|
7950
8469
|
const on = !this.opts.isFavorite(tool.type);
|
|
@@ -7961,16 +8480,16 @@ var DrawingsDrawer = class {
|
|
|
7961
8480
|
if (query) {
|
|
7962
8481
|
let any = false;
|
|
7963
8482
|
for (const group2 of this.definition.groups) {
|
|
7964
|
-
const hits = group2.tools.filter((t) => t.label.toLowerCase().includes(query));
|
|
8483
|
+
const hits = group2.tools.filter((t) => lower(this.t(t.label)).includes(query) || sourceText(t.label).toLowerCase().includes(query));
|
|
7965
8484
|
if (hits.length === 0) continue;
|
|
7966
8485
|
any = true;
|
|
7967
|
-
section(group2.label);
|
|
8486
|
+
section(this.t(group2.label));
|
|
7968
8487
|
for (const tool of hits) row(tool);
|
|
7969
8488
|
}
|
|
7970
8489
|
if (!any) {
|
|
7971
8490
|
const empty = doc.createElement("div");
|
|
7972
8491
|
empty.className = "vela-dd-empty";
|
|
7973
|
-
empty.textContent =
|
|
8492
|
+
empty.textContent = this.t(TEXT17.noMatch);
|
|
7974
8493
|
this.list.appendChild(empty);
|
|
7975
8494
|
}
|
|
7976
8495
|
return;
|
|
@@ -7979,7 +8498,7 @@ var DrawingsDrawer = class {
|
|
|
7979
8498
|
if (!group) return;
|
|
7980
8499
|
if (group.sections && group.sections.length > 0) {
|
|
7981
8500
|
for (const s of group.sections) {
|
|
7982
|
-
section(s.label);
|
|
8501
|
+
section(this.t(s.label));
|
|
7983
8502
|
for (const tool of s.tools) row(tool);
|
|
7984
8503
|
}
|
|
7985
8504
|
} else {
|
|
@@ -7989,6 +8508,17 @@ var DrawingsDrawer = class {
|
|
|
7989
8508
|
};
|
|
7990
8509
|
|
|
7991
8510
|
// src/widget/more-drawer.ts
|
|
8511
|
+
var TEXT18 = {
|
|
8512
|
+
chartType: msg("widget.moreDrawer.chartType", "Chart type"),
|
|
8513
|
+
layout: msg("widget.moreDrawer.layout", "Layout"),
|
|
8514
|
+
alerts: msg("widget.moreDrawer.alerts", "Alerts"),
|
|
8515
|
+
undo: msg("widget.moreDrawer.undo", "Undo"),
|
|
8516
|
+
redo: msg("widget.moreDrawer.redo", "Redo"),
|
|
8517
|
+
screenshot: msg("widget.moreDrawer.screenshot", "Screenshot"),
|
|
8518
|
+
sync: msg("widget.moreDrawer.sync", "Sync"),
|
|
8519
|
+
noAlerts: msg("widget.moreDrawer.noAlerts", "No alerts yet."),
|
|
8520
|
+
alert: msg("widget.moreDrawer.alert", "{title}: {message}")
|
|
8521
|
+
};
|
|
7992
8522
|
var STYLE_ID17 = "vela-widget-more-drawer";
|
|
7993
8523
|
var CSS17 = `
|
|
7994
8524
|
.vela-md-actions {
|
|
@@ -8079,6 +8609,9 @@ var MoreDrawer = class {
|
|
|
8079
8609
|
destroy() {
|
|
8080
8610
|
this.drawer.destroy();
|
|
8081
8611
|
}
|
|
8612
|
+
t(text, values) {
|
|
8613
|
+
return textOf(this.opts.i18n, text, values);
|
|
8614
|
+
}
|
|
8082
8615
|
show(view) {
|
|
8083
8616
|
this.view = view;
|
|
8084
8617
|
this.render();
|
|
@@ -8090,7 +8623,7 @@ var MoreDrawer = class {
|
|
|
8090
8623
|
const back = doc.createElement("button");
|
|
8091
8624
|
back.className = "vela-md-back";
|
|
8092
8625
|
back.appendChild(iconEl("chevron-left", doc));
|
|
8093
|
-
back.appendChild(doc.createTextNode(this.view === "style" ?
|
|
8626
|
+
back.appendChild(doc.createTextNode(this.t(this.view === "style" ? TEXT18.chartType : this.view === "layout" ? TEXT18.layout : TEXT18.alerts)));
|
|
8094
8627
|
back.addEventListener("click", () => this.show("main"));
|
|
8095
8628
|
this.drawer.body.appendChild(back);
|
|
8096
8629
|
}
|
|
@@ -8130,25 +8663,25 @@ var MoreDrawer = class {
|
|
|
8130
8663
|
b.className = "vela-md-action";
|
|
8131
8664
|
b.disabled = !enabled;
|
|
8132
8665
|
b.appendChild(iconEl(icon2, doc));
|
|
8133
|
-
b.appendChild(doc.createTextNode(label));
|
|
8666
|
+
b.appendChild(doc.createTextNode(this.t(label)));
|
|
8134
8667
|
b.addEventListener("click", () => {
|
|
8135
8668
|
run();
|
|
8136
8669
|
this.drawer.hide();
|
|
8137
8670
|
});
|
|
8138
8671
|
actions.appendChild(b);
|
|
8139
8672
|
};
|
|
8140
|
-
if (this.opts.onUndo) action("undo",
|
|
8141
|
-
if (this.opts.onRedo) action("redo",
|
|
8142
|
-
if (this.opts.onScreenshot) action("camera",
|
|
8673
|
+
if (this.opts.onUndo) action("undo", TEXT18.undo, this.opts.canUndo(), this.opts.onUndo);
|
|
8674
|
+
if (this.opts.onRedo) action("redo", TEXT18.redo, this.opts.canRedo(), this.opts.onRedo);
|
|
8675
|
+
if (this.opts.onScreenshot) action("camera", TEXT18.screenshot, true, this.opts.onScreenshot);
|
|
8143
8676
|
if (actions.childElementCount > 0) this.drawer.body.appendChild(actions);
|
|
8144
8677
|
const list = doc.createElement("div");
|
|
8145
8678
|
list.className = "vela-md-list";
|
|
8146
8679
|
const current = this.opts.priceStyles().find((s) => s.id === this.opts.priceStyle());
|
|
8147
|
-
list.appendChild(this.row(doc,
|
|
8680
|
+
list.appendChild(this.row(doc, this.t(TEXT18.chartType), { icon: "style-line", value: current?.label, chevron: true, onClick: () => this.show("style") }));
|
|
8148
8681
|
if (this.opts.layout) {
|
|
8149
8682
|
const shape = this.opts.layout.shape();
|
|
8150
|
-
const value = shape ?
|
|
8151
|
-
list.appendChild(this.row(doc,
|
|
8683
|
+
const value = shape ? layoutGridLabel(shape.cols, shape.rows, this.opts.i18n) : this.opts.layout.presets().find((p) => p.checked)?.label;
|
|
8684
|
+
list.appendChild(this.row(doc, this.t(TEXT18.layout), { icon: "layout", glyph: this.opts.layout.glyph?.(), value, chevron: true, onClick: () => this.show("layout") }));
|
|
8152
8685
|
}
|
|
8153
8686
|
for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
|
|
8154
8687
|
for (const panel of this.opts.panels()) {
|
|
@@ -8164,7 +8697,7 @@ var MoreDrawer = class {
|
|
|
8164
8697
|
}
|
|
8165
8698
|
if (this.opts.alerts) {
|
|
8166
8699
|
const alertCount = this.opts.alerts().length;
|
|
8167
|
-
list.appendChild(this.row(doc,
|
|
8700
|
+
list.appendChild(this.row(doc, this.t(TEXT18.alerts), { icon: "bell", value: alertCount > 0 ? String(alertCount) : void 0, chevron: true, onClick: () => this.show("alerts") }));
|
|
8168
8701
|
}
|
|
8169
8702
|
for (const act of this.opts.actions()) list.appendChild(this.actionRow(doc, act));
|
|
8170
8703
|
this.drawer.body.appendChild(list);
|
|
@@ -8204,7 +8737,7 @@ var MoreDrawer = class {
|
|
|
8204
8737
|
list.className = "vela-md-list";
|
|
8205
8738
|
const wrap = doc.createElement("div");
|
|
8206
8739
|
wrap.className = "vela-md-gridwrap";
|
|
8207
|
-
const { el: grid, squares } = layoutGridCanvas(doc);
|
|
8740
|
+
const { el: grid, squares } = layoutGridCanvas(doc, this.opts.i18n);
|
|
8208
8741
|
paintLayoutGrid(squares, layout.shape());
|
|
8209
8742
|
grid.addEventListener("click", (e) => {
|
|
8210
8743
|
const sq = e.target?.closest?.(".vela-lp-sq");
|
|
@@ -8229,7 +8762,7 @@ var MoreDrawer = class {
|
|
|
8229
8762
|
if (syncs.length > 0) {
|
|
8230
8763
|
const heading = doc.createElement("div");
|
|
8231
8764
|
heading.className = "vela-md-section";
|
|
8232
|
-
heading.textContent =
|
|
8765
|
+
heading.textContent = this.t(TEXT18.sync);
|
|
8233
8766
|
list.appendChild(heading);
|
|
8234
8767
|
for (const s of syncs) {
|
|
8235
8768
|
list.appendChild(
|
|
@@ -8250,20 +8783,23 @@ var MoreDrawer = class {
|
|
|
8250
8783
|
if (alerts.length === 0) {
|
|
8251
8784
|
const empty = doc.createElement("div");
|
|
8252
8785
|
empty.className = "vela-md-empty";
|
|
8253
|
-
empty.textContent =
|
|
8786
|
+
empty.textContent = this.t(TEXT18.noAlerts);
|
|
8254
8787
|
this.drawer.body.appendChild(empty);
|
|
8255
8788
|
return;
|
|
8256
8789
|
}
|
|
8257
8790
|
const list = doc.createElement("div");
|
|
8258
8791
|
list.className = "vela-md-list";
|
|
8259
8792
|
for (const a of alerts) {
|
|
8260
|
-
list.appendChild(this.row(doc,
|
|
8793
|
+
list.appendChild(this.row(doc, this.t(TEXT18.alert, { title: a.title, message: a.message }), { value: eventTimeText(a.time, this.opts.i18n), onClick: () => void 0 }));
|
|
8261
8794
|
}
|
|
8262
8795
|
this.drawer.body.appendChild(list);
|
|
8263
8796
|
}
|
|
8264
8797
|
};
|
|
8265
8798
|
|
|
8266
8799
|
// src/widget/timezone-drawer.ts
|
|
8800
|
+
var TEXT19 = {
|
|
8801
|
+
title: msg("widget.timezoneDrawer.title", "Time zone")
|
|
8802
|
+
};
|
|
8267
8803
|
var STYLE_ID18 = "vela-widget-timezone-drawer";
|
|
8268
8804
|
var CSS18 = `
|
|
8269
8805
|
.vela-tzd-list { padding: 2px 0 4px; }
|
|
@@ -8285,9 +8821,10 @@ var TimezoneDrawer = class {
|
|
|
8285
8821
|
constructor(opts) {
|
|
8286
8822
|
this.opts = opts;
|
|
8287
8823
|
injectStyles(STYLE_ID18, CSS18, opts.host.ownerDocument);
|
|
8288
|
-
this.drawer = new Drawer({ host: opts.host, title:
|
|
8824
|
+
this.drawer = new Drawer({ host: opts.host, title: textOf(opts.i18n, TEXT19.title), onOpenChange: opts.onOpenChange });
|
|
8289
8825
|
}
|
|
8290
8826
|
open() {
|
|
8827
|
+
this.drawer.setTitle(textOf(this.opts.i18n, TEXT19.title));
|
|
8291
8828
|
this.render();
|
|
8292
8829
|
this.drawer.show();
|
|
8293
8830
|
}
|
|
@@ -8302,7 +8839,7 @@ var TimezoneDrawer = class {
|
|
|
8302
8839
|
this.drawer.body.replaceChildren();
|
|
8303
8840
|
const list = doc.createElement("div");
|
|
8304
8841
|
list.className = "vela-tzd-list";
|
|
8305
|
-
for (const tz of timezoneMenuRows(this.opts.timezone())) {
|
|
8842
|
+
for (const tz of timezoneMenuRows(this.opts.timezone(), this.opts.i18n)) {
|
|
8306
8843
|
const row = doc.createElement("div");
|
|
8307
8844
|
row.className = "vela-tzd-row";
|
|
8308
8845
|
const label = doc.createElement("span");
|
|
@@ -8347,17 +8884,21 @@ var CSS19 = `
|
|
|
8347
8884
|
color: var(--vela-fg-muted);
|
|
8348
8885
|
}
|
|
8349
8886
|
`;
|
|
8887
|
+
var TEXT20 = {
|
|
8888
|
+
title: msg("widget.priceScaleDrawer.title", "Price scale")
|
|
8889
|
+
};
|
|
8350
8890
|
var PriceScaleDrawer = class {
|
|
8351
8891
|
constructor(opts) {
|
|
8352
8892
|
this.opts = opts;
|
|
8353
8893
|
this.pane = null;
|
|
8354
8894
|
injectStyles(STYLE_ID19, CSS19, opts.host.ownerDocument);
|
|
8355
|
-
this.drawer = new Drawer({ host: opts.host, title:
|
|
8895
|
+
this.drawer = new Drawer({ host: opts.host, title: textOf(opts.chart()?.i18n, TEXT20.title), onOpenChange: opts.onOpenChange });
|
|
8356
8896
|
}
|
|
8357
8897
|
open() {
|
|
8358
8898
|
const chart = this.opts.chart();
|
|
8359
8899
|
const panes = chart?.renderer.get("paneScales");
|
|
8360
8900
|
this.pane = Array.isArray(panes) ? paneScaleAt(panes, this.opts.pressY()) : null;
|
|
8901
|
+
this.drawer.setTitle(textOf(chart?.i18n, TEXT20.title));
|
|
8361
8902
|
this.render();
|
|
8362
8903
|
this.drawer.show();
|
|
8363
8904
|
}
|
|
@@ -8384,7 +8925,7 @@ var PriceScaleDrawer = class {
|
|
|
8384
8925
|
priceLabel: this.flag("priceLabel"),
|
|
8385
8926
|
countdown: this.flag("countdown"),
|
|
8386
8927
|
priceLine: this.flag("currentPriceLine")
|
|
8387
|
-
});
|
|
8928
|
+
}, chart.i18n);
|
|
8388
8929
|
const list = doc.createElement("div");
|
|
8389
8930
|
list.className = "vela-psd-list";
|
|
8390
8931
|
const row = (label, opts) => {
|
|
@@ -8407,7 +8948,7 @@ var PriceScaleDrawer = class {
|
|
|
8407
8948
|
};
|
|
8408
8949
|
for (const item of items) {
|
|
8409
8950
|
if (item.id === "labels" && item.submenu) {
|
|
8410
|
-
section(
|
|
8951
|
+
section(item.label);
|
|
8411
8952
|
for (const sub of item.submenu) {
|
|
8412
8953
|
row(sub.label, {
|
|
8413
8954
|
checked: sub.checked,
|
|
@@ -8421,7 +8962,7 @@ var PriceScaleDrawer = class {
|
|
|
8421
8962
|
continue;
|
|
8422
8963
|
}
|
|
8423
8964
|
if (item.id === "levels" && item.submenu) {
|
|
8424
|
-
section(
|
|
8965
|
+
section(item.label);
|
|
8425
8966
|
for (const sub of item.submenu) {
|
|
8426
8967
|
row(sub.label, {
|
|
8427
8968
|
checked: sub.checked,
|
|
@@ -8487,6 +9028,12 @@ var PriceScaleDrawer = class {
|
|
|
8487
9028
|
};
|
|
8488
9029
|
|
|
8489
9030
|
// src/widget/drawing-pill.ts
|
|
9031
|
+
var TEXT21 = {
|
|
9032
|
+
magnet: msg("widget.drawingPill.magnet", "Magnet snap"),
|
|
9033
|
+
stay: msg("widget.drawingPill.stay", "Stay in drawing mode"),
|
|
9034
|
+
eraser: msg("widget.drawingPill.eraser", "Eraser"),
|
|
9035
|
+
exit: msg("widget.drawingPill.exit", "Exit drawing mode")
|
|
9036
|
+
};
|
|
8490
9037
|
var STYLE_ID20 = "vela-widget-drawing-pill";
|
|
8491
9038
|
var CSS20 = `
|
|
8492
9039
|
.vela-drawpill { display: none; }
|
|
@@ -8536,17 +9083,24 @@ var CSS20 = `
|
|
|
8536
9083
|
.vela-drawpill-btn .vela-icon { font-size: 17px; width: 17px; height: 17px; }
|
|
8537
9084
|
.vela-drawpill-badge {
|
|
8538
9085
|
position: absolute;
|
|
8539
|
-
top:
|
|
8540
|
-
right:
|
|
8541
|
-
|
|
8542
|
-
|
|
9086
|
+
top: 5px;
|
|
9087
|
+
right: 5px;
|
|
9088
|
+
display: none;
|
|
9089
|
+
align-items: flex-end;
|
|
9090
|
+
gap: 1px;
|
|
8543
9091
|
color: var(--vela-accent);
|
|
8544
9092
|
}
|
|
9093
|
+
.vela-drawpill-badge[data-strength] { display: inline-flex; }
|
|
9094
|
+
.vela-drawpill-badge > i { display: block; width: 2px; height: 4px; border-radius: 1px; background: currentColor; }
|
|
9095
|
+
.vela-drawpill-badge > i + i { height: 7px; opacity: 0.3; }
|
|
9096
|
+
.vela-drawpill-badge[data-strength='strong'] > i + i { opacity: 1; }
|
|
8545
9097
|
`;
|
|
8546
9098
|
var DrawingPill = class {
|
|
8547
9099
|
constructor(host) {
|
|
8548
9100
|
this.chart = null;
|
|
8549
9101
|
this.chartSubs = [];
|
|
9102
|
+
/** Each button's accessible name, re-read in the chart's language on every sync. */
|
|
9103
|
+
this.labels = [];
|
|
8550
9104
|
const doc = host.ownerDocument;
|
|
8551
9105
|
injectStyles(STYLE_ID20, CSS20, doc);
|
|
8552
9106
|
this.el = doc.createElement("div");
|
|
@@ -8557,32 +9111,38 @@ var DrawingPill = class {
|
|
|
8557
9111
|
const btn = (icon2, label, onClick) => {
|
|
8558
9112
|
const b = doc.createElement("button");
|
|
8559
9113
|
b.className = "vela-drawpill-btn";
|
|
8560
|
-
b.setAttribute("aria-label", label);
|
|
9114
|
+
b.setAttribute("aria-label", this.t(label));
|
|
9115
|
+
this.labels.push({ el: b, label });
|
|
8561
9116
|
b.appendChild(iconEl(icon2, doc));
|
|
8562
9117
|
b.addEventListener("click", onClick);
|
|
8563
9118
|
return b;
|
|
8564
9119
|
};
|
|
8565
|
-
this.magnetBtn = btn("magnet",
|
|
9120
|
+
this.magnetBtn = btn("magnet", TEXT21.magnet, () => {
|
|
8566
9121
|
const order = ["off", "weak", "strong"];
|
|
8567
9122
|
const cur = this.chart?.drawings.getSnapMode() ?? "off";
|
|
8568
9123
|
this.chart?.drawings.setSnapMode(order[(order.indexOf(cur) + 1) % order.length]);
|
|
8569
9124
|
});
|
|
8570
9125
|
this.magnetBadge = doc.createElement("span");
|
|
8571
9126
|
this.magnetBadge.className = "vela-drawpill-badge";
|
|
9127
|
+
this.magnetBadge.setAttribute("aria-hidden", "true");
|
|
9128
|
+
this.magnetBadge.append(doc.createElement("i"), doc.createElement("i"));
|
|
8572
9129
|
this.magnetBtn.appendChild(this.magnetBadge);
|
|
8573
|
-
this.stayBtn = btn(iconMarkup("pen-lock") ? "pen-lock" : "pen",
|
|
9130
|
+
this.stayBtn = btn(iconMarkup("pen-lock") ? "pen-lock" : "pen", TEXT21.stay, () => {
|
|
8574
9131
|
this.chart?.drawings.setStayMode(!this.chart.drawings.getStayMode());
|
|
8575
9132
|
});
|
|
8576
|
-
this.eraserBtn = btn("eraser",
|
|
9133
|
+
this.eraserBtn = btn("eraser", TEXT21.eraser, () => {
|
|
8577
9134
|
this.chart?.drawings.setMode(this.chart.drawings.getMode() === "eraser" ? null : "eraser");
|
|
8578
9135
|
});
|
|
8579
|
-
const close = btn("close",
|
|
9136
|
+
const close = btn("close", TEXT21.exit, () => {
|
|
8580
9137
|
this.chart?.drawings.setTool(null);
|
|
8581
9138
|
this.chart?.drawings.setMode(null);
|
|
8582
9139
|
});
|
|
8583
9140
|
this.el.append(this.toolGlyph, this.magnetBtn, this.stayBtn, this.eraserBtn, close);
|
|
8584
9141
|
host.appendChild(this.el);
|
|
8585
9142
|
}
|
|
9143
|
+
t(text) {
|
|
9144
|
+
return textOf(this.chart?.i18n, text);
|
|
9145
|
+
}
|
|
8586
9146
|
/** Rebind to another chart (a rebuild, or the workspace's active cell changing) —
|
|
8587
9147
|
* the previous chart's subscriptions are dropped so only one chart drives the pill. */
|
|
8588
9148
|
onChart(chart) {
|
|
@@ -8596,7 +9156,12 @@ var DrawingPill = class {
|
|
|
8596
9156
|
];
|
|
8597
9157
|
this.sync();
|
|
8598
9158
|
}
|
|
9159
|
+
/** Re-read the labels in the chart's language. */
|
|
9160
|
+
relocalize() {
|
|
9161
|
+
this.sync();
|
|
9162
|
+
}
|
|
8599
9163
|
sync() {
|
|
9164
|
+
for (const { el, label } of this.labels) el.setAttribute("aria-label", this.t(label));
|
|
8600
9165
|
const drawings = this.chart?.drawings;
|
|
8601
9166
|
if (!drawings || !drawings.supported) {
|
|
8602
9167
|
this.el.hidden = true;
|
|
@@ -8609,7 +9174,8 @@ var DrawingPill = class {
|
|
|
8609
9174
|
this.toolGlyph.innerHTML = tool !== null ? getDrawingType(tool)?.icon ?? "" : iconMarkup("eraser") ?? "";
|
|
8610
9175
|
const snap = drawings.getSnapMode();
|
|
8611
9176
|
this.magnetBtn.dataset.on = snap !== "off" ? "1" : "";
|
|
8612
|
-
|
|
9177
|
+
if (snap === "off") delete this.magnetBadge.dataset.strength;
|
|
9178
|
+
else this.magnetBadge.dataset.strength = snap;
|
|
8613
9179
|
this.stayBtn.dataset.on = drawings.getStayMode() ? "1" : "";
|
|
8614
9180
|
this.eraserBtn.dataset.on = eraser ? "1" : "";
|
|
8615
9181
|
}
|
|
@@ -8644,6 +9210,7 @@ function buildContext(host) {
|
|
|
8644
9210
|
togglePanel: (id, open) => host.togglePanel(id, open),
|
|
8645
9211
|
dockStrip: (el) => host.dockStrip(el),
|
|
8646
9212
|
host: host.root,
|
|
9213
|
+
i18n: host.i18n,
|
|
8647
9214
|
toast: (message, kind) => host.toast(message, kind),
|
|
8648
9215
|
addIndicator: (entry) => host.active()?.addExternalIndicator(entry),
|
|
8649
9216
|
addNativeIndicator: (type) => host.active()?.addNative(type),
|
|
@@ -8723,7 +9290,7 @@ var CSS21 = `
|
|
|
8723
9290
|
.vela-ws-grid { position: relative; flex: 1 1 auto; min-width: 0; display: grid; gap: ${GAP_PX}px; background: var(--vela-border-soft); }
|
|
8724
9291
|
.vela-cell { background: var(--vela-bg); position: relative; }
|
|
8725
9292
|
/* Active-cell highlight: an overlay ring ABOVE the chart's own canvas stack (a plain
|
|
8726
|
-
outline on the cell is painted under them)
|
|
9293
|
+
outline on the cell is painted under them) — inert to the pointer. Scoped to
|
|
8727
9294
|
multi-cell grids ([data-multi]): a single-cell layout always has an active cell,
|
|
8728
9295
|
and ringing the only chart would just be noise. */
|
|
8729
9296
|
.vela-ws-grid[data-multi='1'] .vela-cell[data-active='1']::after {
|
|
@@ -8740,11 +9307,11 @@ var CSS21 = `
|
|
|
8740
9307
|
.vela-ws-splitter:hover::after { content: ''; position: absolute; background: var(--vela-separator-hover-line); }
|
|
8741
9308
|
.vela-ws-splitter[data-axis='cols']:hover::after { left: calc(50% - 1px); top: 0; width: 2px; height: 100%; }
|
|
8742
9309
|
.vela-ws-splitter[data-axis='rows']:hover::after { top: calc(50% - 1px); left: 0; height: 2px; width: 100%; }
|
|
8743
|
-
/* Mobile: the docked drawing-toolbar column would eat a phone-width grid
|
|
9310
|
+
/* Mobile: the docked drawing-toolbar column would eat a phone-width grid — the shell's
|
|
8744
9311
|
drawings drawer + on-chart pill replace it (same policy as the widget's in-chart bar). */
|
|
8745
9312
|
[data-layout='mobile'] .vela-ws-toolbar { display: none; }
|
|
8746
9313
|
/* A maximized cell owns the whole grid: the splitter strips have no seams to grab and
|
|
8747
|
-
the active ring would just outline the only visible chart
|
|
9314
|
+
the active ring would just outline the only visible chart — both are noise here. */
|
|
8748
9315
|
.vela-ws-grid[data-maximized='1'] .vela-ws-splitter { display: none; }
|
|
8749
9316
|
.vela-ws-grid[data-maximized='1'] .vela-cell[data-active='1']::after { display: none; }
|
|
8750
9317
|
/* Drop-target preview while a cell's drag handle is held: a dashed ring + the same
|
|
@@ -8764,24 +9331,58 @@ function declaredOrder(cells) {
|
|
|
8764
9331
|
const names = Object.keys(cells ?? {});
|
|
8765
9332
|
for (const n of names) {
|
|
8766
9333
|
if (/^\d+$/.test(n)) {
|
|
8767
|
-
console.warn(`[vela] workspace cell "${n}" ignored
|
|
9334
|
+
console.warn(`[vela] workspace cell "${n}" ignored — a purely-numeric name cannot keep its declaration order (JS object key semantics); use e.g. "cell${n}"`);
|
|
8768
9335
|
}
|
|
8769
9336
|
}
|
|
8770
9337
|
return names.filter((n) => !/^\d+$/.test(n));
|
|
8771
9338
|
}
|
|
9339
|
+
var TEXT22 = {
|
|
9340
|
+
syncSymbol: msg("workspace.sync.symbol", "Symbol"),
|
|
9341
|
+
syncInterval: msg("workspace.sync.interval", "Interval"),
|
|
9342
|
+
syncCrosshair: msg("workspace.sync.crosshair", "Crosshair"),
|
|
9343
|
+
syncStyle: msg("workspace.sync.style", "Style"),
|
|
9344
|
+
alert: msg("workspace.alerts.untitled", "Alert"),
|
|
9345
|
+
alertToast: msg("workspace.alerts.toast", "{source} — {title} — {message}"),
|
|
9346
|
+
alertToastUntitled: msg("workspace.alerts.toastUntitled", "{source} — {message}"),
|
|
9347
|
+
alertRow: msg("workspace.alerts.row", "{source} · {time} · {title}: {message}"),
|
|
9348
|
+
alertDrawerTitle: msg("workspace.alerts.drawerTitle", "{source} · {title}"),
|
|
9349
|
+
noAlerts: msg("workspace.alerts.none", "No alerts yet"),
|
|
9350
|
+
screenshot: msg("workspace.keys.screenshot", "Download a screenshot of the layout"),
|
|
9351
|
+
resetView: msg("workspace.keys.resetView", "Reset view (all history)"),
|
|
9352
|
+
toggleLog: msg("workspace.keys.toggleLog", "Toggle logarithmic scale"),
|
|
9353
|
+
togglePercent: msg("workspace.keys.togglePercent", "Toggle percent scale"),
|
|
9354
|
+
trendline: msg("workspace.keys.trendline", "Arm the trend line tool"),
|
|
9355
|
+
hlineCursor: msg("workspace.keys.hlineCursor", "Horizontal line at the cursor price"),
|
|
9356
|
+
vlineCursor: msg("workspace.keys.vlineCursor", "Vertical line at the cursor time"),
|
|
9357
|
+
undo: msg("workspace.keys.undo", "Undo (active chart)"),
|
|
9358
|
+
redo: msg("workspace.keys.redo", "Redo (active chart)"),
|
|
9359
|
+
zoomIn: msg("workspace.keys.zoomIn", "Zoom in"),
|
|
9360
|
+
zoomOut: msg("workspace.keys.zoomOut", "Zoom out"),
|
|
9361
|
+
panLeft: msg("workspace.keys.panLeft", "Pan toward history"),
|
|
9362
|
+
panRight: msg("workspace.keys.panRight", "Pan toward now"),
|
|
9363
|
+
openIndicators: msg("workspace.keys.openIndicators", "Open the indicator picker"),
|
|
9364
|
+
showShortcuts: msg("workspace.keys.showShortcuts", "Show this shortcuts panel"),
|
|
9365
|
+
categoryChart: msg("workspace.keys.category.chart", "Chart"),
|
|
9366
|
+
categoryDrawings: msg("workspace.keys.category.drawings", "Drawings"),
|
|
9367
|
+
categoryEdit: msg("workspace.keys.category.edit", "Edit"),
|
|
9368
|
+
categoryIndicators: msg("workspace.keys.category.indicators", "Indicators"),
|
|
9369
|
+
categoryHelp: msg("workspace.keys.category.help", "Help")
|
|
9370
|
+
};
|
|
9371
|
+
var ALERT_ROW_MAX = 80;
|
|
8772
9372
|
function nextAutoCellId(taken) {
|
|
8773
9373
|
for (let i = 1; ; i += 1) {
|
|
8774
9374
|
if (!taken.has(`c${i}`)) return `c${i}`;
|
|
8775
9375
|
}
|
|
8776
9376
|
}
|
|
8777
9377
|
function resolveTyping(key, open) {
|
|
9378
|
+
const printable = [...key].length === 1;
|
|
8778
9379
|
if (open.dialogs > 0) {
|
|
8779
|
-
if (
|
|
9380
|
+
if (!printable) return null;
|
|
8780
9381
|
if (open.symbolSearch) return { target: "symbol", text: key.toUpperCase() };
|
|
8781
9382
|
if (open.timeframeEntry) return { target: "timeframe", text: key.toUpperCase() };
|
|
8782
9383
|
return null;
|
|
8783
9384
|
}
|
|
8784
|
-
if (/^[
|
|
9385
|
+
if (printable && /^[\p{L}0]$/u.test(key)) return { target: "symbol", text: key.toUpperCase() };
|
|
8785
9386
|
if (/^[1-9]$/.test(key)) return { target: "timeframe", text: key };
|
|
8786
9387
|
return null;
|
|
8787
9388
|
}
|
|
@@ -8897,6 +9498,7 @@ var VelaWorkspace = class {
|
|
|
8897
9498
|
const hostEl = typeof container === "string" ? document.querySelector(container) : container;
|
|
8898
9499
|
if (!hostEl) throw new Error(`VelaWorkspace: container not found: ${String(container)}`);
|
|
8899
9500
|
this.opts = opts;
|
|
9501
|
+
this.localeState = new LocaleState(pickLocalization(opts));
|
|
8900
9502
|
this.replay = new WorkspaceReplay({
|
|
8901
9503
|
cells: () => this.cells(),
|
|
8902
9504
|
activeId: () => this.activeId,
|
|
@@ -8947,12 +9549,14 @@ var VelaWorkspace = class {
|
|
|
8947
9549
|
this.root = doc.createElement("div");
|
|
8948
9550
|
this.root.className = "vela-workspace";
|
|
8949
9551
|
ensureUIHost(this.root, resolveTheme(opts.theme));
|
|
9552
|
+
this.applyRootAttributes();
|
|
8950
9553
|
for (const [name, make] of Object.entries(opts.providers ?? {})) void this.feed.registerProvider(name, make());
|
|
8951
9554
|
void this.feed.ready().then(() => {
|
|
8952
9555
|
if (!this.destroyed) this.refreshRetention();
|
|
8953
9556
|
});
|
|
8954
9557
|
this.symbolPicker = new SymbolPicker({
|
|
8955
9558
|
host: this.root,
|
|
9559
|
+
i18n: this.localeState,
|
|
8956
9560
|
// Row icons come from each descriptor's OWNING provider (resolveSymbolIcon).
|
|
8957
9561
|
iconFor: (d) => this.feed.symbolIconOf(d),
|
|
8958
9562
|
onSelect: (ticker) => this.active.setSymbol(ticker),
|
|
@@ -8964,6 +9568,7 @@ var VelaWorkspace = class {
|
|
|
8964
9568
|
this.symbolPicker.setSource(() => this.feed.symbols());
|
|
8965
9569
|
this.indicatorPicker = opts.indicatorPicker !== false && !this.indicatorsOverride && topbarHas(this.topbarComp, "indicators") ? new IndicatorPicker({
|
|
8966
9570
|
host: this.root,
|
|
9571
|
+
i18n: this.localeState,
|
|
8967
9572
|
library: () => this.active.libraryRows(),
|
|
8968
9573
|
onChart: () => this.active.onChartRows(),
|
|
8969
9574
|
onAdd: (i) => this.active.addFromLibrary(i),
|
|
@@ -8976,11 +9581,13 @@ var VelaWorkspace = class {
|
|
|
8976
9581
|
const picker = this.indicatorPicker;
|
|
8977
9582
|
this.tfQuick = new TimeframeQuick({
|
|
8978
9583
|
host: this.root,
|
|
9584
|
+
i18n: this.localeState,
|
|
8979
9585
|
onApply: (tf) => this.setActiveTimeframe(tf),
|
|
8980
9586
|
onOpenChange: (open) => this.trackDialog(open)
|
|
8981
9587
|
});
|
|
8982
9588
|
this.topbar = new Topbar(this.root, {
|
|
8983
9589
|
symbol: "",
|
|
9590
|
+
i18n: this.localeState,
|
|
8984
9591
|
// RAW option, not the resolved lists — the bar distinguishes a host-declared
|
|
8985
9592
|
// side (list is law) from a default one (an override's `order` may flow).
|
|
8986
9593
|
composition: opts.topbar,
|
|
@@ -9004,18 +9611,13 @@ var VelaWorkspace = class {
|
|
|
9004
9611
|
// The picker composes dynamic layouts on its grid canvas; registered
|
|
9005
9612
|
// presets the canvas cannot express (bespoke plugin areas) list as rows.
|
|
9006
9613
|
shape: () => layoutShape(this.def),
|
|
9007
|
-
presets: () => layouts().filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label })),
|
|
9614
|
+
presets: () => layouts(this.localeState).filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label })),
|
|
9008
9615
|
onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
|
|
9009
9616
|
onSelectPreset: (id) => this.setLayout(id),
|
|
9010
9617
|
// The SYNC switches reflect the simple all-cells form; flipping one
|
|
9011
9618
|
// OVERRIDES a host-set group record with plain on/off (groups stay an
|
|
9012
9619
|
// API-only shape).
|
|
9013
|
-
syncs: () =>
|
|
9014
|
-
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
9015
|
-
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
9016
|
-
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true },
|
|
9017
|
-
{ id: "style", label: "Style", checked: this.syncOpts.style === true }
|
|
9018
|
-
],
|
|
9620
|
+
syncs: () => this.syncRows(),
|
|
9019
9621
|
onToggleSync: (id) => {
|
|
9020
9622
|
const kind = id;
|
|
9021
9623
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
@@ -9026,7 +9628,7 @@ var VelaWorkspace = class {
|
|
|
9026
9628
|
});
|
|
9027
9629
|
const main = doc.createElement("div");
|
|
9028
9630
|
main.className = "vela-ws-main";
|
|
9029
|
-
const toolbar = buildToolbar(opts.drawings);
|
|
9631
|
+
const toolbar = buildToolbar(opts.drawings, this.localeState);
|
|
9030
9632
|
this.drawingsEnabled = opts.drawings !== false;
|
|
9031
9633
|
this.toolbarDef = toolbar.definition;
|
|
9032
9634
|
let toolbarHost = null;
|
|
@@ -9041,12 +9643,13 @@ var VelaWorkspace = class {
|
|
|
9041
9643
|
this.dock = new PanelDock(main, {
|
|
9042
9644
|
chrome: this.topbar,
|
|
9043
9645
|
context: () => this.context(),
|
|
9044
|
-
changed: () => this.markStateDirty()
|
|
9646
|
+
changed: () => this.markStateDirty(),
|
|
9647
|
+
i18n: this.localeState
|
|
9045
9648
|
});
|
|
9046
|
-
this.objectTree = new ObjectTree(main, (sym) => this.feed.symbolIcon(sym));
|
|
9047
|
-
this.dataWindow = new DataWindow(main);
|
|
9048
|
-
this.dock.addBuiltIn({ id: "dataWindow", title:
|
|
9049
|
-
this.dock.addBuiltIn({ id: "objects", title:
|
|
9649
|
+
this.objectTree = new ObjectTree(main, (sym) => this.feed.symbolIcon(sym), this.localeState);
|
|
9650
|
+
this.dataWindow = new DataWindow(main, this.localeState);
|
|
9651
|
+
this.dock.addBuiltIn({ id: "dataWindow", title: DATA_WINDOW_TITLE, icon: "datawindow", order: 10, panel: this.dataWindow, onChart: (c) => this.dataWindow.onChart(c) });
|
|
9652
|
+
this.dock.addBuiltIn({ id: "objects", title: OBJECT_TREE_TITLE, icon: "objects", order: 20, panel: this.objectTree, onChart: (c) => this.objectTree.onChart(c) });
|
|
9050
9653
|
this.dock.refresh();
|
|
9051
9654
|
if (boot?.panels) this.dock.applyState(boot.panels);
|
|
9052
9655
|
this.root.appendChild(main);
|
|
@@ -9057,7 +9660,7 @@ var VelaWorkspace = class {
|
|
|
9057
9660
|
const attribution = rendererDefaults().attribution;
|
|
9058
9661
|
if (attribution !== false) {
|
|
9059
9662
|
const background = resolveTheme(opts.theme).background;
|
|
9060
|
-
const mark = typeof attribution === "string" && attribution.trim() ? createCustomMark(doc, attribution, background) : createAttributionMark(doc, background);
|
|
9663
|
+
const mark = typeof attribution === "string" && attribution.trim() ? createCustomMark(doc, attribution, background) : createAttributionMark(doc, background, this.localeState);
|
|
9061
9664
|
Object.assign(mark.style, {
|
|
9062
9665
|
left: "calc(var(--vela-toolbar-gutter, 0px) + 12px)",
|
|
9063
9666
|
bottom: `calc(var(--vela-bottom-gutter, ${TIME_AXIS_H3}px) + 10px)`,
|
|
@@ -9096,6 +9699,7 @@ var VelaWorkspace = class {
|
|
|
9096
9699
|
},
|
|
9097
9700
|
{
|
|
9098
9701
|
dock: "static",
|
|
9702
|
+
i18n: () => this.localeState,
|
|
9099
9703
|
// Drawings sync is a WORKSPACE link (same model as the layout
|
|
9100
9704
|
// dropdown's switches) — the bar only hosts its toggle.
|
|
9101
9705
|
onDrawingsSync: (on) => {
|
|
@@ -9108,6 +9712,7 @@ var VelaWorkspace = class {
|
|
|
9108
9712
|
this.drawToolbar?.setVisible(true);
|
|
9109
9713
|
this.drawToolbar?.setDrawingsSyncMode(!!this.syncOpts.drawings);
|
|
9110
9714
|
this.bottombar = opts.bottombar !== false ? new Bottombar(this.root, {
|
|
9715
|
+
i18n: this.localeState,
|
|
9111
9716
|
timezone: this.timezone,
|
|
9112
9717
|
clock: this.clock,
|
|
9113
9718
|
onRange: (preset) => {
|
|
@@ -9121,6 +9726,7 @@ var VelaWorkspace = class {
|
|
|
9121
9726
|
onSettingsClick: () => this.active.chart.renderer.openSettings()
|
|
9122
9727
|
}) : null;
|
|
9123
9728
|
this.mobileBar = opts.bottombar !== false ? new MobileBar(this.root, {
|
|
9729
|
+
i18n: this.localeState,
|
|
9124
9730
|
symbol: "",
|
|
9125
9731
|
timeframe: "60",
|
|
9126
9732
|
onSymbolClick: () => this.symbolPicker.open(),
|
|
@@ -9154,7 +9760,7 @@ var VelaWorkspace = class {
|
|
|
9154
9760
|
this.keymap = new KeymapManager();
|
|
9155
9761
|
this.keymap.attach(this.root);
|
|
9156
9762
|
this.registerDefaultKeys();
|
|
9157
|
-
this.drawToolbar?.setShortcuts(toolShortcutHints(this.keymap));
|
|
9763
|
+
this.drawToolbar?.setShortcuts(toolShortcutHints(this.keymap, this.localeState));
|
|
9158
9764
|
this.root.addEventListener("keydown", this.onRootKeydown);
|
|
9159
9765
|
this.root.tabIndex = -1;
|
|
9160
9766
|
this.cellBackend = this.backendFor(this.def);
|
|
@@ -9278,6 +9884,61 @@ var VelaWorkspace = class {
|
|
|
9278
9884
|
on(event, handler) {
|
|
9279
9885
|
return this.events.on(event, handler);
|
|
9280
9886
|
}
|
|
9887
|
+
/**
|
|
9888
|
+
* The workspace's translator: its resolved locale and direction, `t()` for messages and
|
|
9889
|
+
* `format` for numbers and dates. Every cell's chart carries the same settings
|
|
9890
|
+
* (`cell.chart.i18n`), and contributed actions read it as `ctx.i18n`. The same object
|
|
9891
|
+
* for the workspace's lifetime; it reads the new settings after {@link setLocale}.
|
|
9892
|
+
*/
|
|
9893
|
+
get i18n() {
|
|
9894
|
+
return this.localeState;
|
|
9895
|
+
}
|
|
9896
|
+
/**
|
|
9897
|
+
* Change the language, direction or formatting of the whole workspace at runtime — a
|
|
9898
|
+
* locale tag (`ws.setLocale('de')`) or any {@link LocalizationOptions} fields. Fields
|
|
9899
|
+
* you pass replace their current value (an explicit `undefined` restores the default);
|
|
9900
|
+
* the others are kept. Every cell re-renders in place, the shared chrome follows, and
|
|
9901
|
+
* `locale:changed` fires. Register the locale's pack (`registerLocale`) first for
|
|
9902
|
+
* translated text.
|
|
9903
|
+
*/
|
|
9904
|
+
setLocale(next) {
|
|
9905
|
+
if (this.destroyed) return;
|
|
9906
|
+
const patch = typeof next === "string" ? { locale: next } : pickLocalization(next);
|
|
9907
|
+
this.localeState.update(patch);
|
|
9908
|
+
this.applyRootAttributes();
|
|
9909
|
+
for (const cell of this.cellsById.values()) {
|
|
9910
|
+
cell.chart.setLocale(patch);
|
|
9911
|
+
cell.relocalize();
|
|
9912
|
+
}
|
|
9913
|
+
this.relocalizeChrome();
|
|
9914
|
+
this.events.emit("locale:changed", { locale: this.localeState.locale, direction: this.localeState.direction });
|
|
9915
|
+
}
|
|
9916
|
+
/** Re-read the shared chrome in the current language. Dialogs, menus and drawers read
|
|
9917
|
+
* it on their next open; what stays on screen is relabeled here. */
|
|
9918
|
+
relocalizeChrome() {
|
|
9919
|
+
this.symbolPicker.relabel();
|
|
9920
|
+
this.indicatorPicker?.relabel();
|
|
9921
|
+
this.tfQuick.relabel();
|
|
9922
|
+
this.topbar.relocalize();
|
|
9923
|
+
this.bottombar?.relocalize();
|
|
9924
|
+
this.mobileBar?.relocalize();
|
|
9925
|
+
this.dock.relocalize();
|
|
9926
|
+
this.toolbarDef = buildToolbar(this.opts.drawings, this.localeState).definition;
|
|
9927
|
+
this.drawToolbar?.setDefinition(this.toolbarDef);
|
|
9928
|
+
this.drawToolbar?.relabel();
|
|
9929
|
+
this.drawToolbar?.setShortcuts(toolShortcutHints(this.keymap, this.localeState));
|
|
9930
|
+
this.drawingPill?.relocalize();
|
|
9931
|
+
if (this.attributionMark) relabelAttributionMark(this.attributionMark, this.localeState);
|
|
9932
|
+
}
|
|
9933
|
+
/** `lang` once the host set a locale, `dir` once it set a direction; otherwise the
|
|
9934
|
+
* attributes stay off and the host page decides. */
|
|
9935
|
+
applyRootAttributes() {
|
|
9936
|
+
const s = this.localeState;
|
|
9937
|
+
if (s.localeExplicit) this.root.setAttribute("lang", s.locale);
|
|
9938
|
+
else this.root.removeAttribute("lang");
|
|
9939
|
+
if (s.directionExplicit) this.root.setAttribute("dir", s.direction);
|
|
9940
|
+
else this.root.removeAttribute("dir");
|
|
9941
|
+
}
|
|
9281
9942
|
/** The context handed to contributed actions/attachments (rebuilt per invocation). */
|
|
9282
9943
|
context() {
|
|
9283
9944
|
return buildContext({
|
|
@@ -9294,6 +9955,7 @@ var VelaWorkspace = class {
|
|
|
9294
9955
|
};
|
|
9295
9956
|
},
|
|
9296
9957
|
root: this.root,
|
|
9958
|
+
i18n: this.localeState,
|
|
9297
9959
|
toast: (message, kind) => this.toastHost.show(message, kind),
|
|
9298
9960
|
stateDirty: () => this.markStateDirty(),
|
|
9299
9961
|
replay: this.replay
|
|
@@ -9794,7 +10456,7 @@ var VelaWorkspace = class {
|
|
|
9794
10456
|
resolveLayout(layout) {
|
|
9795
10457
|
if (typeof layout !== "string") return layout;
|
|
9796
10458
|
const def = ensureLayout(layout);
|
|
9797
|
-
if (!def) throw new Error(`[vela] unknown workspace layout "${layout}"
|
|
10459
|
+
if (!def) throw new Error(`[vela] unknown workspace layout "${layout}" — register it with registerLayout().`);
|
|
9798
10460
|
return def;
|
|
9799
10461
|
}
|
|
9800
10462
|
backendFor(def) {
|
|
@@ -9922,6 +10584,7 @@ var VelaWorkspace = class {
|
|
|
9922
10584
|
feed: this.feed,
|
|
9923
10585
|
engines: this.opts.engines ?? {},
|
|
9924
10586
|
chartDefaults: cellChartDefaults(this.opts),
|
|
10587
|
+
localization: () => this.localeState.options,
|
|
9925
10588
|
theme,
|
|
9926
10589
|
live: this.opts.live ?? false,
|
|
9927
10590
|
volume: this.opts.volume ?? true,
|
|
@@ -9974,10 +10637,11 @@ var VelaWorkspace = class {
|
|
|
9974
10637
|
chart.on("script:run", (run) => this.events.emit("script:run", { ...run, cell: cell.id }));
|
|
9975
10638
|
chart.on("priceStyle:change", ({ from, to }) => this.events.emit("cell:priceStyle", { id: cell.id, from, to }));
|
|
9976
10639
|
chart.on("alert", (alert) => {
|
|
9977
|
-
const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe), alert.indicator].filter(Boolean).join(" ");
|
|
9978
|
-
this.alerts.unshift({ cellId: cell.id, source, title: alert.title ??
|
|
10640
|
+
const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe, this.localeState), alert.indicator].filter(Boolean).join(" ");
|
|
10641
|
+
this.alerts.unshift({ cellId: cell.id, source, title: alert.title ?? this.t(TEXT22.alert), message: alert.message, time: alert.time });
|
|
9979
10642
|
if (this.alerts.length > this.alertCap) this.alerts.pop();
|
|
9980
|
-
this.
|
|
10643
|
+
const toast = alert.title ? this.t(TEXT22.alertToast, { source, title: alert.title, message: alert.message }) : this.t(TEXT22.alertToastUntitled, { source, message: alert.message });
|
|
10644
|
+
this.toastHost.show(toast, "info", 4e3);
|
|
9981
10645
|
this.topbar.setAlertCount(this.alerts.length);
|
|
9982
10646
|
});
|
|
9983
10647
|
chart.on("drawing:favorites", ({ favorites }) => {
|
|
@@ -10063,7 +10727,7 @@ var VelaWorkspace = class {
|
|
|
10063
10727
|
if (kind === "crosshair") {
|
|
10064
10728
|
for (const cell of this.cellsById.values()) cell.chart.renderer.setExternalCrosshair(null);
|
|
10065
10729
|
if (setting && ![...this.cellsById.values()].some((c) => c.chart.renderer.supportsExternalCrosshair)) {
|
|
10066
|
-
console.warn("[vela] crosshair sync: no cell renderer supports an external crosshair
|
|
10730
|
+
console.warn("[vela] crosshair sync: no cell renderer supports an external crosshair — nothing will show.");
|
|
10067
10731
|
}
|
|
10068
10732
|
if (this.topbar && this.def) this.topbar.setLayout(this.def.id);
|
|
10069
10733
|
this.markStateDirty();
|
|
@@ -10446,7 +11110,8 @@ var VelaWorkspace = class {
|
|
|
10446
11110
|
this.active.applyRange(preset);
|
|
10447
11111
|
this.bottombar?.setActiveRange(preset.id);
|
|
10448
11112
|
},
|
|
10449
|
-
onOpenChange: (open) => this.trackDialog(open)
|
|
11113
|
+
onOpenChange: (open) => this.trackDialog(open),
|
|
11114
|
+
i18n: this.localeState
|
|
10450
11115
|
}));
|
|
10451
11116
|
this.tfDrawer.open();
|
|
10452
11117
|
}
|
|
@@ -10459,7 +11124,8 @@ var VelaWorkspace = class {
|
|
|
10459
11124
|
isFavorite: (type) => this.active.chart.drawings.isFavorite(type),
|
|
10460
11125
|
onFavorite: (type, on) => this.active.chart.drawings.setFavorite(type, on),
|
|
10461
11126
|
onSelect: (type) => this.active.chart.drawings.setTool(type),
|
|
10462
|
-
onOpenChange: (open) => this.trackDialog(open)
|
|
11127
|
+
onOpenChange: (open) => this.trackDialog(open),
|
|
11128
|
+
i18n: () => this.localeState
|
|
10463
11129
|
}));
|
|
10464
11130
|
this.drawingsDrawer.open();
|
|
10465
11131
|
}
|
|
@@ -10467,16 +11133,17 @@ var VelaWorkspace = class {
|
|
|
10467
11133
|
const has = (id) => topbarHas(this.topbarComp, id);
|
|
10468
11134
|
this.moreDrawer ?? (this.moreDrawer = new MoreDrawer({
|
|
10469
11135
|
host: this.root,
|
|
11136
|
+
i18n: this.localeState,
|
|
10470
11137
|
...has("undo-redo") ? { onUndo: () => this.active.history.undo(), onRedo: () => this.active.history.redo() } : {},
|
|
10471
11138
|
...has("screenshot") ? { onScreenshot: this.screenshotOverride ? () => this.runOverride(this.screenshotOverride) : () => this.downloadScreenshot() } : {},
|
|
10472
11139
|
canUndo: () => this.active.history.canUndo,
|
|
10473
11140
|
canRedo: () => this.active.history.canRedo,
|
|
10474
|
-
priceStyles: () => priceStyleIds().map((id) => ({ id, label: priceStyleLabel(id), icon: priceStyleIcon(id) })),
|
|
11141
|
+
priceStyles: () => priceStyleIds().map((id) => ({ id, label: priceStyleLabel(id, this.localeState), icon: priceStyleIcon(id) })),
|
|
10475
11142
|
priceStyle: () => this.active.priceStyle,
|
|
10476
11143
|
onPriceStyle: (id) => this.active.setPriceStyle(id),
|
|
10477
11144
|
panels: () => has("panels") ? [...this.dock.list()] : [],
|
|
10478
11145
|
onTogglePanel: (id) => this.dock.toggle(id),
|
|
10479
|
-
...has("alerts") ? { alerts: () => this.alerts.map((a) => ({ title:
|
|
11146
|
+
...has("alerts") ? { alerts: () => this.alerts.map((a) => ({ title: this.t(TEXT22.alertDrawerTitle, { source: a.source, title: a.title }), message: a.message, time: a.time })) } : {},
|
|
10480
11147
|
// Actions placed on the bottom bar (left-aligned by default) have their stop
|
|
10481
11148
|
// there — only the menu-placed ones land in the drawer, or they would appear
|
|
10482
11149
|
// twice: left (primary) ones with the primary rows, right ones at the end.
|
|
@@ -10490,15 +11157,10 @@ var VelaWorkspace = class {
|
|
|
10490
11157
|
// single-chart mode and a composition without 'layout' omit it here too.
|
|
10491
11158
|
layout: this.monoLayout || !has("layout") ? void 0 : {
|
|
10492
11159
|
shape: () => layoutShape(this.def),
|
|
10493
|
-
presets: () => layouts().filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label, checked: l.id === this.def.id })),
|
|
11160
|
+
presets: () => layouts(this.localeState).filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label, checked: l.id === this.def.id })),
|
|
10494
11161
|
onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
|
|
10495
11162
|
onSelectPreset: (id) => this.setLayout(id),
|
|
10496
|
-
syncs: () =>
|
|
10497
|
-
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
10498
|
-
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
10499
|
-
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true },
|
|
10500
|
-
{ id: "style", label: "Style", checked: this.syncOpts.style === true }
|
|
10501
|
-
],
|
|
11163
|
+
syncs: () => this.syncRows(),
|
|
10502
11164
|
onToggleSync: (id) => {
|
|
10503
11165
|
const kind = id;
|
|
10504
11166
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
@@ -10512,14 +11174,28 @@ var VelaWorkspace = class {
|
|
|
10512
11174
|
/** The contributed topbar actions the mobile menu lists, from one cluster. */
|
|
10513
11175
|
drawerActions(cluster) {
|
|
10514
11176
|
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
10515
|
-
return widgetActions("topbar", this.context()).filter((a) => mobilePlacement(a) === "menu" && a.align === "left" === (cluster === "left") && !builtin.has(a.id)).map((a) => ({ label: a.label, icon: a.icon, run: () => a.run(this.context()) }));
|
|
11177
|
+
return widgetActions("topbar", this.context()).filter((a) => mobilePlacement(a) === "menu" && a.align === "left" === (cluster === "left") && !builtin.has(a.id)).map((a) => ({ label: this.t(a.label), icon: a.icon, run: () => a.run(this.context()) }));
|
|
11178
|
+
}
|
|
11179
|
+
/** Display text in the workspace's language. */
|
|
11180
|
+
t(text, values) {
|
|
11181
|
+
return textOf(this.localeState, text, values);
|
|
11182
|
+
}
|
|
11183
|
+
/** The layout SYNC switches (the topbar dropdown and the mobile drawer list the same). */
|
|
11184
|
+
syncRows() {
|
|
11185
|
+
return [
|
|
11186
|
+
{ id: "symbol", label: this.t(TEXT22.syncSymbol), checked: this.syncOpts.symbol === true },
|
|
11187
|
+
{ id: "timeframe", label: this.t(TEXT22.syncInterval), checked: this.syncOpts.timeframe === true },
|
|
11188
|
+
{ id: "crosshair", label: this.t(TEXT22.syncCrosshair), checked: this.syncOpts.crosshair === true },
|
|
11189
|
+
{ id: "style", label: this.t(TEXT22.syncStyle), checked: this.syncOpts.style === true }
|
|
11190
|
+
];
|
|
10516
11191
|
}
|
|
10517
11192
|
openTimezoneDrawer() {
|
|
10518
11193
|
this.timezoneDrawer ?? (this.timezoneDrawer = new TimezoneDrawer({
|
|
10519
11194
|
host: this.root,
|
|
10520
11195
|
timezone: () => this.timezone,
|
|
10521
11196
|
onTimezone: (zone) => this.setTimezone(zone),
|
|
10522
|
-
onOpenChange: (open) => this.trackDialog(open)
|
|
11197
|
+
onOpenChange: (open) => this.trackDialog(open),
|
|
11198
|
+
i18n: this.localeState
|
|
10523
11199
|
}));
|
|
10524
11200
|
this.timezoneDrawer.open();
|
|
10525
11201
|
}
|
|
@@ -10538,8 +11214,8 @@ var VelaWorkspace = class {
|
|
|
10538
11214
|
this.alertsMenu?.destroy();
|
|
10539
11215
|
const items = this.alerts.length ? this.alerts.map((a, i) => ({
|
|
10540
11216
|
id: String(i),
|
|
10541
|
-
label:
|
|
10542
|
-
})) : [{ id: "none", label:
|
|
11217
|
+
label: sliceGraphemes(this.t(TEXT22.alertRow, { source: a.source, time: eventTimeText(a.time, this.localeState), title: a.title, message: a.message }), ALERT_ROW_MAX)
|
|
11218
|
+
})) : [{ id: "none", label: this.t(TEXT22.noAlerts), disabled: true }];
|
|
10543
11219
|
this.alertsMenu = new Menu({
|
|
10544
11220
|
host: this.root,
|
|
10545
11221
|
items,
|
|
@@ -10574,29 +11250,29 @@ var VelaWorkspace = class {
|
|
|
10574
11250
|
this.keymap.register({
|
|
10575
11251
|
id: "chart.screenshot",
|
|
10576
11252
|
keys: "mod+alt+s",
|
|
10577
|
-
label: ov ? ov.label :
|
|
10578
|
-
category:
|
|
11253
|
+
label: ov ? ov.label : TEXT22.screenshot,
|
|
11254
|
+
category: TEXT22.categoryChart,
|
|
10579
11255
|
run: ov ? () => this.runOverride(ov) : () => this.downloadScreenshot()
|
|
10580
11256
|
});
|
|
10581
11257
|
}
|
|
10582
|
-
this.keymap.register({ id: "chart.reset-view", keys: "alt+r", label:
|
|
10583
|
-
this.keymap.register({ id: "chart.toggle-log", keys: "alt+l", label:
|
|
11258
|
+
this.keymap.register({ id: "chart.reset-view", keys: "alt+r", label: TEXT22.resetView, category: TEXT22.categoryChart, run: () => this.active.chart.setVisibleRangePreset("ALL") });
|
|
11259
|
+
this.keymap.register({ id: "chart.toggle-log", keys: "alt+l", label: TEXT22.toggleLog, category: TEXT22.categoryChart, run: () => this.active.chart.renderer.set("logScale", !this.active.chart.renderer.get("logScale")) });
|
|
10584
11260
|
this.keymap.register({
|
|
10585
11261
|
id: "chart.toggle-percent",
|
|
10586
11262
|
keys: "alt+p",
|
|
10587
|
-
label:
|
|
10588
|
-
category:
|
|
11263
|
+
label: TEXT22.togglePercent,
|
|
11264
|
+
category: TEXT22.categoryChart,
|
|
10589
11265
|
run: () => {
|
|
10590
11266
|
const mode = this.active.chart.renderer.get("scaleMode");
|
|
10591
11267
|
this.active.chart.renderer.set("scaleMode", mode === "percent" ? "price" : "percent");
|
|
10592
11268
|
}
|
|
10593
11269
|
});
|
|
10594
|
-
this.keymap.register({ id: "drawings.trendline", keys: "alt+t", label:
|
|
11270
|
+
this.keymap.register({ id: "drawings.trendline", keys: "alt+t", label: TEXT22.trendline, category: TEXT22.categoryDrawings, run: () => this.active.chart.drawings.setTool("trendline") });
|
|
10595
11271
|
this.keymap.register({
|
|
10596
11272
|
id: "drawings.hline-cursor",
|
|
10597
11273
|
keys: "alt+h",
|
|
10598
|
-
label:
|
|
10599
|
-
category:
|
|
11274
|
+
label: TEXT22.hlineCursor,
|
|
11275
|
+
category: TEXT22.categoryDrawings,
|
|
10600
11276
|
run: () => {
|
|
10601
11277
|
const c = this.active;
|
|
10602
11278
|
if (c.lastCrossTime != null && c.lastCrossPrice != null) c.chart.drawings.add("hline", { anchors: [{ time: c.lastCrossTime, price: c.lastCrossPrice }] });
|
|
@@ -10605,32 +11281,32 @@ var VelaWorkspace = class {
|
|
|
10605
11281
|
this.keymap.register({
|
|
10606
11282
|
id: "drawings.vline-cursor",
|
|
10607
11283
|
keys: "alt+v",
|
|
10608
|
-
label:
|
|
10609
|
-
category:
|
|
11284
|
+
label: TEXT22.vlineCursor,
|
|
11285
|
+
category: TEXT22.categoryDrawings,
|
|
10610
11286
|
run: () => {
|
|
10611
11287
|
const c = this.active;
|
|
10612
11288
|
if (c.lastCrossTime != null && c.lastCrossPrice != null) c.chart.drawings.add("vline", { anchors: [{ time: c.lastCrossTime, price: c.lastCrossPrice }] });
|
|
10613
11289
|
}
|
|
10614
11290
|
});
|
|
10615
|
-
this.keymap.register({ id: "history.undo", keys: ["mod+z"], label:
|
|
10616
|
-
this.keymap.register({ id: "history.redo", keys: ["mod+y", "mod+shift+z"], label:
|
|
10617
|
-
this.keymap.register({ id: "view.zoom-in", keys: "mod+arrowup", label:
|
|
10618
|
-
this.keymap.register({ id: "view.zoom-out", keys: "mod+arrowdown", label:
|
|
10619
|
-
this.keymap.register({ id: "view.pan-left", keys: "mod+arrowleft", label:
|
|
10620
|
-
this.keymap.register({ id: "view.pan-right", keys: "mod+arrowright", label:
|
|
11291
|
+
this.keymap.register({ id: "history.undo", keys: ["mod+z"], label: TEXT22.undo, category: TEXT22.categoryEdit, run: () => this.active.history.undo() });
|
|
11292
|
+
this.keymap.register({ id: "history.redo", keys: ["mod+y", "mod+shift+z"], label: TEXT22.redo, category: TEXT22.categoryEdit, run: () => this.active.history.redo() });
|
|
11293
|
+
this.keymap.register({ id: "view.zoom-in", keys: "mod+arrowup", label: TEXT22.zoomIn, category: TEXT22.categoryChart, run: () => this.glider.zoom(ZOOM_IN) });
|
|
11294
|
+
this.keymap.register({ id: "view.zoom-out", keys: "mod+arrowdown", label: TEXT22.zoomOut, category: TEXT22.categoryChart, run: () => this.glider.zoom(ZOOM_OUT) });
|
|
11295
|
+
this.keymap.register({ id: "view.pan-left", keys: "mod+arrowleft", label: TEXT22.panLeft, category: TEXT22.categoryChart, run: () => this.active.chart.panBy(-PAN_FAST) });
|
|
11296
|
+
this.keymap.register({ id: "view.pan-right", keys: "mod+arrowright", label: TEXT22.panRight, category: TEXT22.categoryChart, run: () => this.active.chart.panBy(PAN_FAST) });
|
|
10621
11297
|
const indOv = this.indicatorsOverride;
|
|
10622
11298
|
if (indOv && topbarHas(this.topbarComp, "indicators")) {
|
|
10623
|
-
this.keymap.register({ id: "indicators.open", keys: "/", label: indOv.label, category:
|
|
11299
|
+
this.keymap.register({ id: "indicators.open", keys: "/", label: indOv.label, category: TEXT22.categoryIndicators, run: () => this.runOverride(indOv) });
|
|
10624
11300
|
} else if (this.indicatorPicker) {
|
|
10625
|
-
this.keymap.register({ id: "indicators.open", keys: "/", label:
|
|
11301
|
+
this.keymap.register({ id: "indicators.open", keys: "/", label: TEXT22.openIndicators, category: TEXT22.categoryIndicators, run: () => this.indicatorPicker?.open() });
|
|
10626
11302
|
}
|
|
10627
11303
|
this.keymap.register({
|
|
10628
11304
|
id: "help.shortcuts",
|
|
10629
11305
|
keys: "?",
|
|
10630
|
-
label:
|
|
10631
|
-
category:
|
|
11306
|
+
label: TEXT22.showShortcuts,
|
|
11307
|
+
category: TEXT22.categoryHelp,
|
|
10632
11308
|
run: () => {
|
|
10633
|
-
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (open) => this.trackDialog(open)));
|
|
11309
|
+
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (open) => this.trackDialog(open), () => this.localeState));
|
|
10634
11310
|
this.shortcutsHelp.open();
|
|
10635
11311
|
}
|
|
10636
11312
|
});
|
|
@@ -10639,6 +11315,7 @@ var VelaWorkspace = class {
|
|
|
10639
11315
|
routeTyping(ev) {
|
|
10640
11316
|
if (this.destroyed) return;
|
|
10641
11317
|
if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
|
|
11318
|
+
if (ev.isComposing || ev.key === "Process") return;
|
|
10642
11319
|
if (isEditableTarget(ev)) return;
|
|
10643
11320
|
const hit = resolveTyping(ev.key, { dialogs: this.openDialogs, symbolSearch: this.symbolPicker.isOpen, timeframeEntry: this.tfQuick.isOpen });
|
|
10644
11321
|
if (!hit) return;
|