@luxalgo/vela 0.8.5 → 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-BUdlkx9I.d.ts → DataProvider-BGlB1zSc.d.ts} +1 -1
- package/dist/{DataProvider-DdqMxKdl.d.cts → DataProvider-zJGp58kb.d.cts} +1 -1
- package/dist/{chunk-BN4DNS3O.js → chunk-4WAHYL7Y.js} +1125 -468
- 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-QYIO37K7.js → chunk-XIRSR3D6.js} +2374 -1529
- package/dist/{chunk-ALTGHKIM.js → chunk-YRCL3WMD.js} +510 -406
- package/dist/{contributions-D8xuYc9U.d.cts → contributions-BjefWpxQ.d.ts} +71 -22
- package/dist/{contributions-P9fBBuY1.d.ts → contributions-CpCJisTC.d.cts} +71 -22
- package/dist/index.cjs +4022 -2088
- package/dist/index.d.cts +51 -25
- package/dist/index.d.ts +51 -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-BI5Tp32b.d.cts → options-CpegHml_.d.cts} +74 -9
- package/dist/{options-BI5Tp32b.d.ts → options-D4L3_hv_.d.ts} +74 -9
- package/dist/{plugin-D5Z-Q6TX.d.ts → plugin-DTmulpKM.d.ts} +81 -35
- package/dist/{plugin-B8F1nor8.d.cts → plugin-DpNp4aBi.d.cts} +81 -35
- 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-CTwtDIca.d.cts → statusline-model-C3yIY9WE.d.ts} +11 -3
- package/dist/{statusline-model-D9kWC9X5.d.ts → 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 +3987 -2055
- package/dist/vela.global.min.js +57 -57
- package/dist/widget.cjs +5330 -2699
- package/dist/widget.d.cts +100 -26
- package/dist/widget.d.ts +100 -26
- package/dist/widget.js +19 -11
- package/dist/workspace.cjs +5319 -2706
- 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);
|
|
@@ -1453,7 +1565,7 @@ function buildTree(snap) {
|
|
|
1453
1565
|
return snap.panes.map((pane) => ({
|
|
1454
1566
|
id: pane.id,
|
|
1455
1567
|
kind: pane.kind,
|
|
1456
|
-
label: paneLabel(pane, snap.handleTitle),
|
|
1568
|
+
label: paneLabel(pane, snap.handleTitle, snap.i18n),
|
|
1457
1569
|
order: pane.order,
|
|
1458
1570
|
collapsed: pane.collapsed,
|
|
1459
1571
|
maximized: pane.maximized,
|
|
@@ -1465,6 +1577,56 @@ function treeIsEmpty(panes) {
|
|
|
1465
1577
|
}
|
|
1466
1578
|
|
|
1467
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;
|
|
1468
1630
|
var STYLE_ID4 = "vela-widget-objtree";
|
|
1469
1631
|
var CSS4 = `
|
|
1470
1632
|
/* One pane's block. The transparent border reserves the drop-target outline. */
|
|
@@ -1498,7 +1660,7 @@ var CSS4 = `
|
|
|
1498
1660
|
}
|
|
1499
1661
|
.vela-ot-row:hover { background: var(--vela-hover); }
|
|
1500
1662
|
.vela-ot-row > .vela-icon { color: var(--vela-fg-muted); width: 14px; height: 14px; font-size: 14px; justify-content: center; flex: none; }
|
|
1501
|
-
/* 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. */
|
|
1502
1664
|
.vela-ot-row > .vela-icon svg { width: 14px; height: 14px; }
|
|
1503
1665
|
.vela-ot-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1504
1666
|
.vela-ot-row[data-hidden] .vela-ot-name,
|
|
@@ -1558,9 +1720,9 @@ var CSS4 = `
|
|
|
1558
1720
|
.vela-ot-btn:disabled { opacity: 0.35; cursor: default; }
|
|
1559
1721
|
.vela-ot-empty { padding: 20px 10px; text-align: center; color: var(--vela-fg-muted); font-size: 12px; }
|
|
1560
1722
|
|
|
1561
|
-
/*
|
|
1723
|
+
/* ── drawing groups ── */
|
|
1562
1724
|
/* One top-level entry in a pane's drawing list: a lone drawing, or a whole group block. No
|
|
1563
|
-
border of its own
|
|
1725
|
+
border of its own — units stack flush, so consecutive highlighted rows read as one block; the
|
|
1564
1726
|
drop-into-group outline is drawn inside the box instead (see the drag-and-drop rules). */
|
|
1565
1727
|
.vela-ot-unit { border-radius: 6px; }
|
|
1566
1728
|
/* Adjacent picked rows merge into one contiguous highlight: the shared edge loses its rounding. */
|
|
@@ -1579,7 +1741,7 @@ var CSS4 = `
|
|
|
1579
1741
|
color: var(--vela-fg-bright);
|
|
1580
1742
|
font: inherit;
|
|
1581
1743
|
}
|
|
1582
|
-
/* 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
|
|
1583
1745
|
stay in place and simply go dim until a drawing is selected. It stays put at the top of the
|
|
1584
1746
|
list while the list scrolls under it, so they never scroll out of reach. */
|
|
1585
1747
|
.vela-ot-selbar {
|
|
@@ -1598,7 +1760,7 @@ var CSS4 = `
|
|
|
1598
1760
|
.vela-ot-selbar .vela-ot-btn { visibility: visible; width: 24px; height: 22px; }
|
|
1599
1761
|
.vela-ot-selbar .vela-ot-btn[data-icon='group'] .vela-icon { width: 16px; height: 16px; font-size: 16px; }
|
|
1600
1762
|
|
|
1601
|
-
/*
|
|
1763
|
+
/* ── drag-and-drop ── */
|
|
1602
1764
|
.vela-ot-row[data-drag] { cursor: grab; }
|
|
1603
1765
|
.vela-ot .vela-panel-body[data-dragging] .vela-ot-row[data-drag] { cursor: grabbing; }
|
|
1604
1766
|
/* The row in flight fades: the ghost under the pointer is the thing being moved. */
|
|
@@ -1684,8 +1846,8 @@ var MenuBuilder = class {
|
|
|
1684
1846
|
}
|
|
1685
1847
|
};
|
|
1686
1848
|
var ObjectTree = class extends SidePanel {
|
|
1687
|
-
constructor(host, iconFor) {
|
|
1688
|
-
super(host,
|
|
1849
|
+
constructor(host, iconFor, i18n) {
|
|
1850
|
+
super(host, TEXT5.objectTree, "vela-ot", i18n ? { i18n } : {});
|
|
1689
1851
|
this.iconFor = iconFor;
|
|
1690
1852
|
this.chart = null;
|
|
1691
1853
|
/** What the CHART has selected — every member of a multi-selection, mirrored as rows. */
|
|
@@ -1768,16 +1930,21 @@ var ObjectTree = class extends SidePanel {
|
|
|
1768
1930
|
panes: chart.panes.list(),
|
|
1769
1931
|
indicatorVisible: (id) => handle(id)?.visible !== false,
|
|
1770
1932
|
handleTitle: (id) => handle(id)?.title,
|
|
1933
|
+
i18n: this.i18n,
|
|
1771
1934
|
stackable,
|
|
1772
1935
|
interleave: stackable && chart.drawings.supported && chart.renderer.capabilities.drawingDepth === true,
|
|
1773
1936
|
zOrder: stackable ? chart.renderer.get("seriesOrder") ?? [] : [],
|
|
1774
1937
|
candleZ: stackable ? Number(chart.renderer.get("candleZOrder")) || 0 : 0,
|
|
1775
|
-
priceLabel: this.symbolName ||
|
|
1938
|
+
priceLabel: this.symbolName || this.t(TEXT5.price),
|
|
1776
1939
|
priceVisible: chart.renderer.get("candleVisible") !== false,
|
|
1777
1940
|
drawings: chart.drawings.supported ? chart.drawings.all() : [],
|
|
1778
1941
|
groups: this.groups
|
|
1779
1942
|
};
|
|
1780
1943
|
}
|
|
1944
|
+
relocalize() {
|
|
1945
|
+
super.relocalize();
|
|
1946
|
+
this.refresh();
|
|
1947
|
+
}
|
|
1781
1948
|
/**
|
|
1782
1949
|
* Repaint because something changed — unless the panel is mid-interaction. A live drag
|
|
1783
1950
|
* resolves drops against the rows as currently rendered, and a rename lives in an input
|
|
@@ -1808,7 +1975,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1808
1975
|
if (treeIsEmpty(panes)) {
|
|
1809
1976
|
const empty = doc.createElement("div");
|
|
1810
1977
|
empty.className = "vela-ot-empty";
|
|
1811
|
-
empty.textContent =
|
|
1978
|
+
empty.textContent = this.t(TEXT5.emptyHint);
|
|
1812
1979
|
this.body.appendChild(empty);
|
|
1813
1980
|
}
|
|
1814
1981
|
}
|
|
@@ -1823,7 +1990,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1823
1990
|
bar.appendChild(
|
|
1824
1991
|
this.btn({
|
|
1825
1992
|
icon: "group",
|
|
1826
|
-
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),
|
|
1827
1994
|
disabled: !groupable,
|
|
1828
1995
|
run: () => this.makeGroup(ids)
|
|
1829
1996
|
})
|
|
@@ -1831,7 +1998,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1831
1998
|
bar.appendChild(
|
|
1832
1999
|
this.btn({
|
|
1833
2000
|
icon: "clone",
|
|
1834
|
-
title: ids.length === 0 ?
|
|
2001
|
+
title: ids.length === 0 ? this.t(TEXT5.duplicateSelected) : this.t(TEXT5.duplicate),
|
|
1835
2002
|
disabled: ids.length === 0,
|
|
1836
2003
|
run: () => {
|
|
1837
2004
|
for (const id of ids) this.cloneInto(pass, id);
|
|
@@ -1841,7 +2008,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1841
2008
|
);
|
|
1842
2009
|
const count = doc.createElement("span");
|
|
1843
2010
|
count.className = "vela-ot-selcount";
|
|
1844
|
-
count.textContent = ids.length > 1 ?
|
|
2011
|
+
count.textContent = ids.length > 1 ? this.t(TEXT5.selected, { count: ids.length }) : "";
|
|
1845
2012
|
bar.appendChild(count);
|
|
1846
2013
|
return bar;
|
|
1847
2014
|
}
|
|
@@ -1916,11 +2083,11 @@ var ObjectTree = class extends SidePanel {
|
|
|
1916
2083
|
acts.appendChild(this.btn({ icon: icon2, title, run }));
|
|
1917
2084
|
};
|
|
1918
2085
|
if (pane.kind !== "price") {
|
|
1919
|
-
if (pane.order > 1) op("arrow-up",
|
|
1920
|
-
if (pane.order < pass.panes.length - 1) op("arrow-down",
|
|
1921
|
-
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));
|
|
1922
2089
|
}
|
|
1923
|
-
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));
|
|
1924
2091
|
return head;
|
|
1925
2092
|
}
|
|
1926
2093
|
rowEl(pass, pane, row) {
|
|
@@ -1928,11 +2095,11 @@ var ObjectTree = class extends SidePanel {
|
|
|
1928
2095
|
const { chart } = pass;
|
|
1929
2096
|
if (row.kind === "price") {
|
|
1930
2097
|
const base = this.symbolName.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || this.symbolName;
|
|
1931
|
-
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);
|
|
1932
2099
|
const el2 = this.row(icon2, row.label, row.visible, [
|
|
1933
2100
|
{
|
|
1934
2101
|
icon: row.visible ? "eye" : "eye-off",
|
|
1935
|
-
title: row.visible ?
|
|
2102
|
+
title: row.visible ? this.t(TEXT5.hide) : this.t(TEXT5.show),
|
|
1936
2103
|
engaged: !row.visible,
|
|
1937
2104
|
run: () => {
|
|
1938
2105
|
chart.renderer.set("candleVisible", !row.visible);
|
|
@@ -1944,7 +2111,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1944
2111
|
el2.dataset.pane = pane.id;
|
|
1945
2112
|
if (pass.stackable) {
|
|
1946
2113
|
el2.dataset.drag = "1";
|
|
1947
|
-
el2.title =
|
|
2114
|
+
el2.title = this.t(TEXT5.restackHint);
|
|
1948
2115
|
}
|
|
1949
2116
|
return el2;
|
|
1950
2117
|
}
|
|
@@ -1956,7 +2123,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
1956
2123
|
[
|
|
1957
2124
|
{
|
|
1958
2125
|
icon: row.visible ? "eye" : "eye-off",
|
|
1959
|
-
title: row.visible ?
|
|
2126
|
+
title: row.visible ? this.t(TEXT5.hide) : this.t(TEXT5.show),
|
|
1960
2127
|
engaged: !row.visible,
|
|
1961
2128
|
run: () => {
|
|
1962
2129
|
handle?.setVisible(!row.visible);
|
|
@@ -1965,21 +2132,21 @@ var ObjectTree = class extends SidePanel {
|
|
|
1965
2132
|
},
|
|
1966
2133
|
{
|
|
1967
2134
|
icon: "trash",
|
|
1968
|
-
title:
|
|
2135
|
+
title: this.t(TEXT5.remove),
|
|
1969
2136
|
run: () => {
|
|
1970
2137
|
handle?.remove();
|
|
1971
2138
|
this.refresh();
|
|
1972
2139
|
}
|
|
1973
2140
|
}
|
|
1974
2141
|
],
|
|
1975
|
-
row.ownScale ?
|
|
2142
|
+
row.ownScale ? this.t(TEXT5.scaleTag) : void 0
|
|
1976
2143
|
);
|
|
1977
2144
|
el.dataset.rowKind = "indicator";
|
|
1978
2145
|
el.dataset.id = row.id;
|
|
1979
2146
|
el.dataset.pane = pane.id;
|
|
1980
2147
|
if (pass.repanable || pass.stackable) {
|
|
1981
2148
|
el.dataset.drag = "1";
|
|
1982
|
-
el.title = pass.repanable ?
|
|
2149
|
+
el.title = pass.repanable ? this.t(TEXT5.moveIndicatorHint) : this.t(TEXT5.restackHint);
|
|
1983
2150
|
}
|
|
1984
2151
|
return el;
|
|
1985
2152
|
}
|
|
@@ -1998,22 +2165,22 @@ var ObjectTree = class extends SidePanel {
|
|
|
1998
2165
|
const el = this.row(iconEl(folded ? "chevron-right" : "chevron-down", doc), g.name, !allHidden, [
|
|
1999
2166
|
{
|
|
2000
2167
|
icon: allLocked ? "lock" : "unlock",
|
|
2001
|
-
title: allLocked ?
|
|
2168
|
+
title: allLocked ? this.t(TEXT5.unlockAll) : this.t(TEXT5.lockAll),
|
|
2002
2169
|
engaged: allLocked,
|
|
2003
2170
|
run: () => this.setMembers(pass, ids, { locked: !allLocked })
|
|
2004
2171
|
},
|
|
2005
2172
|
{
|
|
2006
2173
|
icon: allHidden ? "eye-off" : "eye",
|
|
2007
|
-
title: allHidden ?
|
|
2174
|
+
title: allHidden ? this.t(TEXT5.showAll) : this.t(TEXT5.hideAll),
|
|
2008
2175
|
engaged: allHidden,
|
|
2009
2176
|
run: () => this.setMembers(pass, ids, { visible: allHidden })
|
|
2010
2177
|
},
|
|
2011
|
-
{ icon: "trash", title:
|
|
2178
|
+
{ icon: "trash", title: this.t(TEXT5.removeAll), run: () => this.removeGroup(pass, g.id, true) }
|
|
2012
2179
|
]);
|
|
2013
2180
|
el.dataset.rowKind = "group";
|
|
2014
2181
|
el.dataset.id = g.id;
|
|
2015
2182
|
el.dataset.pane = pane.id;
|
|
2016
|
-
el.title =
|
|
2183
|
+
el.title = this.t(TEXT5.groupMembers, { count: members.length });
|
|
2017
2184
|
if (this.renaming === g.id) {
|
|
2018
2185
|
const name = el.querySelector(".vela-ot-name");
|
|
2019
2186
|
const input = doc.createElement("input");
|
|
@@ -2047,7 +2214,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2047
2214
|
drawRow(pass, d, inGroup = false) {
|
|
2048
2215
|
const doc = this.el.ownerDocument;
|
|
2049
2216
|
const { chart } = pass;
|
|
2050
|
-
const markup = drawingMeta(d.type).icon;
|
|
2217
|
+
const markup = drawingMeta(d.type, this.i18n).icon;
|
|
2051
2218
|
let icon2;
|
|
2052
2219
|
if (markup) {
|
|
2053
2220
|
icon2 = doc.createElement("span");
|
|
@@ -2057,10 +2224,10 @@ var ObjectTree = class extends SidePanel {
|
|
|
2057
2224
|
} else {
|
|
2058
2225
|
icon2 = iconEl("pen", doc);
|
|
2059
2226
|
}
|
|
2060
|
-
const el = this.row(icon2, drawingLabel(d), d.visible, [
|
|
2227
|
+
const el = this.row(icon2, drawingLabel(d, this.i18n), d.visible, [
|
|
2061
2228
|
{
|
|
2062
2229
|
icon: d.locked ? "lock" : "unlock",
|
|
2063
|
-
title: d.locked ?
|
|
2230
|
+
title: d.locked ? this.t(TEXT5.unlock) : this.t(TEXT5.lock),
|
|
2064
2231
|
engaged: d.locked,
|
|
2065
2232
|
run: () => {
|
|
2066
2233
|
chart.drawings.lock(d.id, !d.locked);
|
|
@@ -2069,7 +2236,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2069
2236
|
},
|
|
2070
2237
|
{
|
|
2071
2238
|
icon: d.visible ? "eye" : "eye-off",
|
|
2072
|
-
title: d.visible ?
|
|
2239
|
+
title: d.visible ? this.t(TEXT5.hide) : this.t(TEXT5.show),
|
|
2073
2240
|
engaged: !d.visible,
|
|
2074
2241
|
run: () => {
|
|
2075
2242
|
chart.drawings.show(d.id, !d.visible);
|
|
@@ -2078,7 +2245,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2078
2245
|
},
|
|
2079
2246
|
{
|
|
2080
2247
|
icon: "trash",
|
|
2081
|
-
title:
|
|
2248
|
+
title: this.t(TEXT5.remove),
|
|
2082
2249
|
run: () => {
|
|
2083
2250
|
chart.drawings.remove(d.id);
|
|
2084
2251
|
this.refresh();
|
|
@@ -2089,7 +2256,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2089
2256
|
el.dataset.id = d.id;
|
|
2090
2257
|
el.dataset.pane = d.paneId;
|
|
2091
2258
|
el.dataset.drag = "1";
|
|
2092
|
-
el.title =
|
|
2259
|
+
el.title = this.t(TEXT5.moveDrawingHint);
|
|
2093
2260
|
if (inGroup) el.classList.add("vela-ot-subrow");
|
|
2094
2261
|
if (this.selectedDrawings.has(d.id)) el.dataset.selected = "1";
|
|
2095
2262
|
if (this.picked.has(d.id)) el.dataset.picked = "1";
|
|
@@ -2137,7 +2304,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2137
2304
|
const t = doc.createElement("span");
|
|
2138
2305
|
t.className = "vela-ot-tag";
|
|
2139
2306
|
t.textContent = tag;
|
|
2140
|
-
t.title =
|
|
2307
|
+
t.title = this.t(TEXT5.ownScaleHint);
|
|
2141
2308
|
el.appendChild(t);
|
|
2142
2309
|
}
|
|
2143
2310
|
const acts = doc.createElement("span");
|
|
@@ -2168,7 +2335,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2168
2335
|
makeGroup(ids) {
|
|
2169
2336
|
if (!canGroup(ids, this.groups)) return;
|
|
2170
2337
|
this.groupSeq += 1;
|
|
2171
|
-
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] }];
|
|
2172
2339
|
this.picked.clear();
|
|
2173
2340
|
this.render();
|
|
2174
2341
|
}
|
|
@@ -2517,20 +2684,20 @@ var ObjectTree = class extends SidePanel {
|
|
|
2517
2684
|
const { chart } = pass;
|
|
2518
2685
|
const visible = chart.renderer.get("candleVisible") !== false;
|
|
2519
2686
|
const items = [
|
|
2520
|
-
b.entry(visible ?
|
|
2687
|
+
b.entry(visible ? this.t(TEXT5.hide) : this.t(TEXT5.show), visible ? "eye-off" : "eye", () => {
|
|
2521
2688
|
chart.renderer.set("candleVisible", !visible);
|
|
2522
2689
|
this.refresh();
|
|
2523
2690
|
})
|
|
2524
2691
|
];
|
|
2525
2692
|
if (pass.stackable) {
|
|
2526
2693
|
items.push(
|
|
2527
|
-
b.entry(
|
|
2694
|
+
b.entry(this.t(TEXT5.bringToFront), "arrow-up", () => {
|
|
2528
2695
|
chart.renderer.set("candleZOrder", this.stackBounds(pass, PRICE_PANE_ID).top + 1);
|
|
2529
2696
|
this.refresh();
|
|
2530
2697
|
}, true)
|
|
2531
2698
|
);
|
|
2532
2699
|
items.push(
|
|
2533
|
-
b.entry(
|
|
2700
|
+
b.entry(this.t(TEXT5.sendToBack), "arrow-down", () => {
|
|
2534
2701
|
chart.renderer.set("candleZOrder", this.stackBounds(pass, PRICE_PANE_ID).bottom - 1);
|
|
2535
2702
|
this.refresh();
|
|
2536
2703
|
})
|
|
@@ -2554,24 +2721,24 @@ var ObjectTree = class extends SidePanel {
|
|
|
2554
2721
|
const { chart } = pass;
|
|
2555
2722
|
const handle = pass.handle(row.id);
|
|
2556
2723
|
const items = [
|
|
2557
|
-
b.entry(row.visible ?
|
|
2724
|
+
b.entry(row.visible ? this.t(TEXT5.hide) : this.t(TEXT5.show), row.visible ? "eye-off" : "eye", () => {
|
|
2558
2725
|
handle?.setVisible(!row.visible);
|
|
2559
2726
|
this.refresh();
|
|
2560
2727
|
})
|
|
2561
2728
|
];
|
|
2562
2729
|
if (chart.renderer.supportsIndicatorSettings) {
|
|
2563
|
-
items.push(b.entry(
|
|
2730
|
+
items.push(b.entry(this.t(TEXT5.indicatorSettings), "gear", () => chart.renderer.openIndicatorSettings(row.id)));
|
|
2564
2731
|
}
|
|
2565
2732
|
if (chart.panes.supported) {
|
|
2566
2733
|
const moves = this.moveItems(b, pass, row.id, paneId);
|
|
2567
|
-
if (moves.length > 0) items.push(b.submenu(
|
|
2734
|
+
if (moves.length > 0) items.push(b.submenu(this.t(TEXT5.moveTo), "move-vertical", moves));
|
|
2568
2735
|
}
|
|
2569
2736
|
if (pass.stackable) {
|
|
2570
|
-
items.push(b.entry(
|
|
2571
|
-
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 })));
|
|
2572
2739
|
}
|
|
2573
2740
|
items.push(
|
|
2574
|
-
b.entry(
|
|
2741
|
+
b.entry(this.t(TEXT5.remove), "trash", () => {
|
|
2575
2742
|
handle?.remove();
|
|
2576
2743
|
this.refresh();
|
|
2577
2744
|
}, true)
|
|
@@ -2589,33 +2756,33 @@ var ObjectTree = class extends SidePanel {
|
|
|
2589
2756
|
const current = pass.panes.find((p) => p.id === fromPane);
|
|
2590
2757
|
const alone = current !== void 0 && current.kind !== "price" && paneRows(current).length <= 1;
|
|
2591
2758
|
if (!alone) {
|
|
2592
|
-
if (fromPane !== PRICE_PANE_ID) items.push(b.entry(
|
|
2593
|
-
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 } })));
|
|
2594
2761
|
}
|
|
2595
2762
|
return items;
|
|
2596
2763
|
}
|
|
2597
2764
|
drawingMenu(b, pass, d) {
|
|
2598
2765
|
const { chart } = pass;
|
|
2599
2766
|
const items = [
|
|
2600
|
-
b.entry(d.visible ?
|
|
2767
|
+
b.entry(d.visible ? this.t(TEXT5.hide) : this.t(TEXT5.show), d.visible ? "eye-off" : "eye", () => {
|
|
2601
2768
|
chart.drawings.show(d.id, !d.visible);
|
|
2602
2769
|
this.refresh();
|
|
2603
2770
|
}),
|
|
2604
|
-
b.entry(d.locked ?
|
|
2771
|
+
b.entry(d.locked ? this.t(TEXT5.unlock) : this.t(TEXT5.lock), d.locked ? "unlock" : "lock", () => {
|
|
2605
2772
|
chart.drawings.lock(d.id, !d.locked);
|
|
2606
2773
|
this.refresh();
|
|
2607
2774
|
}),
|
|
2608
|
-
b.entry(
|
|
2775
|
+
b.entry(this.t(TEXT5.duplicate), "clone", () => {
|
|
2609
2776
|
this.cloneInto(pass, d.id);
|
|
2610
2777
|
this.render();
|
|
2611
2778
|
}),
|
|
2612
2779
|
// Front/back clear the WHOLE stack on a shared-z renderer — over or under the
|
|
2613
2780
|
// candles and every indicator, not just the other drawings.
|
|
2614
|
-
b.entry(
|
|
2615
|
-
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))
|
|
2616
2783
|
];
|
|
2617
2784
|
items.push(...this.groupingItems(b, d.id));
|
|
2618
|
-
items.push(b.entry(
|
|
2785
|
+
items.push(b.entry(this.t(TEXT5.remove), "trash", () => chart.drawings.remove(d.id), true));
|
|
2619
2786
|
return items;
|
|
2620
2787
|
}
|
|
2621
2788
|
/** The grouping half of a drawing's menu: what it can do about the bundle it is (or isn't)
|
|
@@ -2625,15 +2792,15 @@ var ObjectTree = class extends SidePanel {
|
|
|
2625
2792
|
const mine = groupOf(this.groups, id);
|
|
2626
2793
|
const pick = this.picked.has(id) && this.picked.size > 1 ? [...this.picked] : null;
|
|
2627
2794
|
if (pick && canGroup(pick, this.groups)) {
|
|
2628
|
-
items.push(b.entry(
|
|
2795
|
+
items.push(b.entry(this.t(TEXT5.groupPick, { count: pick.length }), "group", () => this.makeGroup(pick), true));
|
|
2629
2796
|
} else if (!pick && mine === null) {
|
|
2630
|
-
items.push(b.entry(
|
|
2797
|
+
items.push(b.entry(this.t(TEXT5.newGroup), "group", () => this.makeGroup([id]), true));
|
|
2631
2798
|
}
|
|
2632
2799
|
const others = this.groups.filter((g) => g.id !== mine?.id);
|
|
2633
2800
|
if (!pick && others.length > 0) {
|
|
2634
2801
|
items.push(
|
|
2635
2802
|
b.submenu(
|
|
2636
|
-
mine === null ?
|
|
2803
|
+
mine === null ? this.t(TEXT5.addToGroup) : this.t(TEXT5.moveToGroup),
|
|
2637
2804
|
"folder-plus",
|
|
2638
2805
|
others.map(
|
|
2639
2806
|
(g) => b.entry(g.name, void 0, () => {
|
|
@@ -2647,7 +2814,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2647
2814
|
}
|
|
2648
2815
|
if (mine !== null) {
|
|
2649
2816
|
items.push(
|
|
2650
|
-
b.entry(
|
|
2817
|
+
b.entry(this.t(TEXT5.removeFromGroup, { group: mine.name }), "folder-minus", () => {
|
|
2651
2818
|
this.groups = removeFromGroups(this.groups, [id]);
|
|
2652
2819
|
this.render();
|
|
2653
2820
|
}, items.length === 0)
|
|
@@ -2660,24 +2827,24 @@ var ObjectTree = class extends SidePanel {
|
|
|
2660
2827
|
const members = pass.chart.drawings.all().filter((d) => g.ids.includes(d.id));
|
|
2661
2828
|
const { allHidden, allLocked } = groupState(members);
|
|
2662
2829
|
const items = [
|
|
2663
|
-
b.entry(
|
|
2830
|
+
b.entry(this.t(TEXT5.rename), "pen", () => {
|
|
2664
2831
|
this.renaming = g.id;
|
|
2665
2832
|
this.render();
|
|
2666
2833
|
}),
|
|
2667
|
-
b.entry(allHidden ?
|
|
2668
|
-
b.entry(allLocked ?
|
|
2669
|
-
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", () => {
|
|
2670
2837
|
this.picked = new Set(ids);
|
|
2671
2838
|
this.selectOnChart(ids);
|
|
2672
2839
|
this.render();
|
|
2673
2840
|
})
|
|
2674
2841
|
];
|
|
2675
2842
|
if (members.length > 0) {
|
|
2676
|
-
items.push(b.entry(
|
|
2677
|
-
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")));
|
|
2678
2845
|
}
|
|
2679
|
-
items.push(b.entry(
|
|
2680
|
-
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)));
|
|
2681
2848
|
return items;
|
|
2682
2849
|
}
|
|
2683
2850
|
/** Forget everything that refers to a drawing or group the chart no longer has — otherwise a
|
|
@@ -2695,6 +2862,21 @@ var ObjectTree = class extends SidePanel {
|
|
|
2695
2862
|
};
|
|
2696
2863
|
|
|
2697
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;
|
|
2698
2880
|
var STYLE_ID5 = "vela-widget-datawindow";
|
|
2699
2881
|
var CSS5 = `
|
|
2700
2882
|
.vela-dw-group {
|
|
@@ -2720,19 +2902,21 @@ var CSS5 = `
|
|
|
2720
2902
|
}
|
|
2721
2903
|
.vela-dw-row:hover { background: var(--vela-hover); }
|
|
2722
2904
|
.vela-dw-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--vela-fg-bright); }
|
|
2723
|
-
|
|
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; }
|
|
2724
2907
|
.vela-dw-empty { padding: 20px 10px; text-align: center; color: var(--vela-fg-muted); font-size: 12px; }
|
|
2725
2908
|
`;
|
|
2726
2909
|
var REFRESH_EVENTS2 = ["bar", "context:changed", "indicator:added", "indicator:removed", "indicator:visibility", "indicator:moved", "pane:changed", "market:changed", "history:complete"];
|
|
2727
|
-
var EM_DASH = "
|
|
2728
|
-
function dataWindowSections(readout) {
|
|
2910
|
+
var EM_DASH = "—";
|
|
2911
|
+
function dataWindowSections(readout, i18n) {
|
|
2912
|
+
const t = (m) => textOf(i18n, m);
|
|
2729
2913
|
const sections = [];
|
|
2730
2914
|
if (readout.date || readout.time) {
|
|
2731
2915
|
sections.push({
|
|
2732
|
-
title:
|
|
2916
|
+
title: t(TEXT6.timeSection),
|
|
2733
2917
|
lines: [
|
|
2734
|
-
{ label:
|
|
2735
|
-
{ label:
|
|
2918
|
+
{ label: t(TEXT6.date), value: readout.date || EM_DASH, color: "" },
|
|
2919
|
+
{ label: t(TEXT6.time), value: readout.time || EM_DASH, color: "" }
|
|
2736
2920
|
]
|
|
2737
2921
|
});
|
|
2738
2922
|
}
|
|
@@ -2740,13 +2924,13 @@ function dataWindowSections(readout) {
|
|
|
2740
2924
|
if (ohlc) {
|
|
2741
2925
|
const color = ohlc.up ? "var(--vela-up)" : "var(--vela-down)";
|
|
2742
2926
|
const lines = [
|
|
2743
|
-
{ label:
|
|
2744
|
-
{ label:
|
|
2745
|
-
{ label:
|
|
2746
|
-
{ 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 }
|
|
2747
2931
|
];
|
|
2748
|
-
if (ohlc.vol !== void 0) lines.push({ label:
|
|
2749
|
-
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 });
|
|
2750
2934
|
}
|
|
2751
2935
|
for (const group of readout.groups) {
|
|
2752
2936
|
sections.push({ title: group.name, lines: group.rows.map((r) => ({ label: r.label, value: r.value, color: r.color })) });
|
|
@@ -2754,8 +2938,8 @@ function dataWindowSections(readout) {
|
|
|
2754
2938
|
return sections;
|
|
2755
2939
|
}
|
|
2756
2940
|
var DataWindow = class extends SidePanel {
|
|
2757
|
-
constructor(host) {
|
|
2758
|
-
super(host,
|
|
2941
|
+
constructor(host, i18n) {
|
|
2942
|
+
super(host, TEXT6.title, "vela-dw", i18n ? { i18n } : {});
|
|
2759
2943
|
this.chart = null;
|
|
2760
2944
|
this.unsubs = [];
|
|
2761
2945
|
injectStyles(STYLE_ID5, CSS5, host.ownerDocument);
|
|
@@ -2776,6 +2960,10 @@ var DataWindow = class extends SidePanel {
|
|
|
2776
2960
|
this.detach();
|
|
2777
2961
|
super.destroy();
|
|
2778
2962
|
}
|
|
2963
|
+
relocalize() {
|
|
2964
|
+
super.relocalize();
|
|
2965
|
+
this.refresh();
|
|
2966
|
+
}
|
|
2779
2967
|
detach() {
|
|
2780
2968
|
for (const u of this.unsubs) u();
|
|
2781
2969
|
this.unsubs = [];
|
|
@@ -2786,11 +2974,11 @@ var DataWindow = class extends SidePanel {
|
|
|
2786
2974
|
const doc = this.el.ownerDocument;
|
|
2787
2975
|
const readout = this.chart.renderer.dataWindowReadout();
|
|
2788
2976
|
this.body.replaceChildren();
|
|
2789
|
-
const sections = readout ? dataWindowSections(readout) : [];
|
|
2977
|
+
const sections = readout ? dataWindowSections(readout, this.i18n) : [];
|
|
2790
2978
|
if (sections.length === 0) {
|
|
2791
2979
|
const empty = doc.createElement("div");
|
|
2792
2980
|
empty.className = "vela-dw-empty";
|
|
2793
|
-
empty.textContent = readout ?
|
|
2981
|
+
empty.textContent = this.t(readout ? TEXT6.noData : TEXT6.noReadout);
|
|
2794
2982
|
this.body.appendChild(empty);
|
|
2795
2983
|
return;
|
|
2796
2984
|
}
|
|
@@ -2807,6 +2995,7 @@ var DataWindow = class extends SidePanel {
|
|
|
2807
2995
|
label.textContent = line.label;
|
|
2808
2996
|
const value = doc.createElement("span");
|
|
2809
2997
|
value.className = "vela-dw-value";
|
|
2998
|
+
value.dir = "ltr";
|
|
2810
2999
|
value.textContent = line.value;
|
|
2811
3000
|
if (line.color) value.style.color = line.color;
|
|
2812
3001
|
row.append(label, value);
|
|
@@ -2852,7 +3041,8 @@ var PanelDock = class {
|
|
|
2852
3041
|
minWidth: desc.minWidth,
|
|
2853
3042
|
maxWidth: desc.maxWidth,
|
|
2854
3043
|
overlay: desc.overlay,
|
|
2855
|
-
maximizable: desc.maximizable
|
|
3044
|
+
maximizable: desc.maximizable,
|
|
3045
|
+
...this.deps.i18n ? { i18n: this.deps.i18n } : {}
|
|
2856
3046
|
});
|
|
2857
3047
|
const entry = {
|
|
2858
3048
|
id: desc.id,
|
|
@@ -2901,7 +3091,12 @@ var PanelDock = class {
|
|
|
2901
3091
|
/** The docked panels in dock order — what a non-topbar chrome (the mobile
|
|
2902
3092
|
* three-dots drawer) lists so every panel stays reachable there too. */
|
|
2903
3093
|
list() {
|
|
2904
|
-
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();
|
|
2905
3100
|
}
|
|
2906
3101
|
/** The dock's persistable state, or null when there is nothing worth saving. */
|
|
2907
3102
|
getState() {
|
|
@@ -2978,7 +3173,7 @@ var PanelDock = class {
|
|
|
2978
3173
|
}
|
|
2979
3174
|
/** Push the current toggle group to the chrome, pressed states included. */
|
|
2980
3175
|
publish() {
|
|
2981
|
-
const buttons = this.
|
|
3176
|
+
const buttons = this.list().slice();
|
|
2982
3177
|
this.deps.chrome.setPanelButtons(buttons, (id) => this.toggle(id));
|
|
2983
3178
|
for (const entry of this.entries) this.deps.chrome.setPanelActive(entry.id, entry.panel.open);
|
|
2984
3179
|
}
|
|
@@ -3053,7 +3248,7 @@ var STYLE_ID6 = "vela-widget-symbolpicker";
|
|
|
3053
3248
|
var CSS6 = `
|
|
3054
3249
|
/* The dialog body is flush (a flex column): search + market tabs are a fixed head
|
|
3055
3250
|
and the result list is the ONLY scroller, in both layouts. A sticky head over a
|
|
3056
|
-
scrolling padded body is not an option
|
|
3251
|
+
scrolling padded body is not an option — browsers pin sticky boxes to the
|
|
3057
3252
|
scroller's content box, so the head lands one padding below the top and covers
|
|
3058
3253
|
the first row. */
|
|
3059
3254
|
.vela-sp-head { flex: none; padding: var(--vela-space-4) var(--vela-space-4) 0; }
|
|
@@ -3081,7 +3276,7 @@ var CSS6 = `
|
|
|
3081
3276
|
.vela-sp-input::placeholder { text-transform: none; }
|
|
3082
3277
|
.vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
|
|
3083
3278
|
/* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
|
|
3084
|
-
overflowing the body, and the result list stops capping itself
|
|
3279
|
+
overflowing the body, and the result list stops capping itself — it fills the
|
|
3085
3280
|
rest of the screen. The bottom inset (+ safe area) is the kit's: every mobile
|
|
3086
3281
|
dialog body carries it, flush or not, so the list adds none of its own. */
|
|
3087
3282
|
[data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
|
|
@@ -3148,7 +3343,7 @@ var CSS6 = `
|
|
|
3148
3343
|
letter-spacing: 0.06em;
|
|
3149
3344
|
text-transform: uppercase;
|
|
3150
3345
|
}
|
|
3151
|
-
/* Provider brand marks
|
|
3346
|
+
/* Provider brand marks — fixed by the venue, deliberately outside the theme palette. */
|
|
3152
3347
|
.vela-sp-badge[data-p='binance'] { color: #f0b90b; } /* palette-exempt: venue brand mark */
|
|
3153
3348
|
.vela-sp-badge[data-p='hyperliquid'] { color: #50d2c1; } /* palette-exempt: venue brand mark */
|
|
3154
3349
|
.vela-sp-empty { padding: var(--vela-space-3); color: var(--vela-fg-muted); text-align: center; }
|
|
@@ -3168,6 +3363,30 @@ var CSS6 = `
|
|
|
3168
3363
|
.vela-sp-expander:hover { background: var(--vela-surface-elev); color: var(--vela-fg); }
|
|
3169
3364
|
.vela-sp-row[data-member] { padding-left: 34px; }
|
|
3170
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
|
+
};
|
|
3171
3390
|
var PAGE = 100;
|
|
3172
3391
|
var SymbolPicker = class {
|
|
3173
3392
|
constructor(opts) {
|
|
@@ -3176,7 +3395,8 @@ var SymbolPicker = class {
|
|
|
3176
3395
|
this.rows = [];
|
|
3177
3396
|
this.highlighted = 0;
|
|
3178
3397
|
this.seed = "";
|
|
3179
|
-
this.activeTab = "
|
|
3398
|
+
this.activeTab = "all";
|
|
3399
|
+
this.tabButtons = [];
|
|
3180
3400
|
this.visible = PAGE;
|
|
3181
3401
|
/** The last filter pass returned fewer raw rows than asked — the pool is drained
|
|
3182
3402
|
* (checked BEFORE folding: folding shortens pages without meaning exhaustion). */
|
|
@@ -3190,7 +3410,7 @@ var SymbolPicker = class {
|
|
|
3190
3410
|
injectStyles(STYLE_ID6, CSS6, doc);
|
|
3191
3411
|
this.input = doc.createElement("input");
|
|
3192
3412
|
this.input.className = "vela-sp-input";
|
|
3193
|
-
this.input.placeholder =
|
|
3413
|
+
this.input.placeholder = textOf(opts.i18n, TEXT7.search);
|
|
3194
3414
|
this.input.setAttribute("spellcheck", "false");
|
|
3195
3415
|
const searchRow = doc.createElement("div");
|
|
3196
3416
|
searchRow.className = "vela-sp-searchrow";
|
|
@@ -3199,13 +3419,15 @@ var SymbolPicker = class {
|
|
|
3199
3419
|
this.tabs.className = "vela-sp-tabs";
|
|
3200
3420
|
const head = doc.createElement("div");
|
|
3201
3421
|
head.className = "vela-sp-head";
|
|
3202
|
-
for (const
|
|
3422
|
+
for (const tab of TABS) {
|
|
3203
3423
|
const b = doc.createElement("button");
|
|
3204
3424
|
b.className = "vela-sp-tab";
|
|
3205
|
-
b.
|
|
3206
|
-
|
|
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";
|
|
3207
3429
|
b.addEventListener("click", () => {
|
|
3208
|
-
this.activeTab =
|
|
3430
|
+
this.activeTab = tab.id;
|
|
3209
3431
|
for (const c of this.tabs.children) delete c.dataset.active;
|
|
3210
3432
|
b.dataset.active = "1";
|
|
3211
3433
|
this.refresh();
|
|
@@ -3222,13 +3444,14 @@ var SymbolPicker = class {
|
|
|
3222
3444
|
this.grow();
|
|
3223
3445
|
});
|
|
3224
3446
|
this.dialog = new Dialog({
|
|
3225
|
-
title:
|
|
3447
|
+
title: textOf(opts.i18n, TEXT7.title),
|
|
3226
3448
|
host: opts.host,
|
|
3227
3449
|
closeOnInteractOutside: true,
|
|
3228
3450
|
flush: true,
|
|
3229
3451
|
content: (body) => body.append(head, this.list),
|
|
3230
3452
|
onOpenChange: (open) => {
|
|
3231
3453
|
if (open) {
|
|
3454
|
+
this.relabel();
|
|
3232
3455
|
this.input.value = this.seed.toUpperCase();
|
|
3233
3456
|
this.seed = "";
|
|
3234
3457
|
this.refresh();
|
|
@@ -3352,7 +3575,7 @@ var SymbolPicker = class {
|
|
|
3352
3575
|
Promise.resolve([...raw]).then((copy) => hook(copy)).then((out) => {
|
|
3353
3576
|
this.ranked = { key, result: dedupeSymbols(out) };
|
|
3354
3577
|
}).catch((err) => {
|
|
3355
|
-
console.warn("[vela] symbol ranking failed
|
|
3578
|
+
console.warn("[vela] symbol ranking failed — pool order kept:", err);
|
|
3356
3579
|
this.ranked = { key, result: [...raw] };
|
|
3357
3580
|
}).finally(() => {
|
|
3358
3581
|
this.ranking = false;
|
|
@@ -3361,10 +3584,17 @@ var SymbolPicker = class {
|
|
|
3361
3584
|
}
|
|
3362
3585
|
return this.ranked?.result ?? raw;
|
|
3363
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
|
+
}
|
|
3364
3594
|
computeRows() {
|
|
3365
|
-
const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Futures: ["futures", "root"], Forex: ["forex"], Commodities: ["commodity"] };
|
|
3366
3595
|
const all = this.pool();
|
|
3367
|
-
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");
|
|
3368
3598
|
const filtered = filterSymbols(pool, this.input.value, this.visible, symbolRanking() ? false : TOP_TICKERS);
|
|
3369
3599
|
this.exhausted = filtered.length < this.visible;
|
|
3370
3600
|
const folded = foldGroups(filtered);
|
|
@@ -3392,7 +3622,7 @@ var SymbolPicker = class {
|
|
|
3392
3622
|
if (!this.rows.length) {
|
|
3393
3623
|
const empty = doc.createElement("div");
|
|
3394
3624
|
empty.className = "vela-sp-empty";
|
|
3395
|
-
empty.textContent = this.input.value ?
|
|
3625
|
+
empty.textContent = textOf(this.opts.i18n, this.input.value ? TEXT7.noMatch : TEXT7.empty);
|
|
3396
3626
|
this.list.appendChild(empty);
|
|
3397
3627
|
return;
|
|
3398
3628
|
}
|
|
@@ -3428,7 +3658,7 @@ var SymbolPicker = class {
|
|
|
3428
3658
|
row.dataset.group = "1";
|
|
3429
3659
|
const expander = doc.createElement("button");
|
|
3430
3660
|
expander.className = "vela-sp-expander";
|
|
3431
|
-
expander.setAttribute("aria-label",
|
|
3661
|
+
expander.setAttribute("aria-label", textOf(this.opts.i18n, TEXT7.showContracts));
|
|
3432
3662
|
expander.appendChild(iconEl(this.expanded.has(groupKeyOf(s)) ? "chevron-down" : "chevron-right", doc));
|
|
3433
3663
|
row.appendChild(expander);
|
|
3434
3664
|
} else if (s.group != null && this.expanded.has(groupKeyOf(s))) row.dataset.member = "1";
|
|
@@ -3444,6 +3674,17 @@ var SymbolPicker = class {
|
|
|
3444
3674
|
};
|
|
3445
3675
|
|
|
3446
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";
|
|
3447
3688
|
var STYLE_ID7 = "vela-widget-indpicker";
|
|
3448
3689
|
var CSS7 = `
|
|
3449
3690
|
.vela-ip-searchrow {
|
|
@@ -3461,7 +3702,7 @@ var CSS7 = `
|
|
|
3461
3702
|
.vela-ip-searchrow .vela-icon { color: var(--vela-fg-muted); }
|
|
3462
3703
|
.vela-ip-search { flex: 1; background: transparent; color: var(--vela-fg); border: none; font-size: 14px; outline: none; }
|
|
3463
3704
|
.vela-ip-list { max-height: 50vh; overflow: auto; min-width: 380px; }
|
|
3464
|
-
/* Mobile (fullscreen dialog): no width floor
|
|
3705
|
+
/* Mobile (fullscreen dialog): no width floor — 380px would overflow a phone —
|
|
3465
3706
|
and no height cap; the fullscreen body owns the scrolling. */
|
|
3466
3707
|
[data-layout='mobile'] .vela-ip-list { min-width: 0; max-height: none; }
|
|
3467
3708
|
.vela-ip-list::-webkit-scrollbar { width: 8px; }
|
|
@@ -3525,7 +3766,7 @@ var IndicatorPicker = class {
|
|
|
3525
3766
|
injectStyles(STYLE_ID7, CSS7, doc);
|
|
3526
3767
|
this.search = doc.createElement("input");
|
|
3527
3768
|
this.search.className = "vela-ip-search";
|
|
3528
|
-
this.search.placeholder =
|
|
3769
|
+
this.search.placeholder = this.t(TEXT8.search);
|
|
3529
3770
|
this.search.spellcheck = false;
|
|
3530
3771
|
this.search.addEventListener("input", () => this.refresh());
|
|
3531
3772
|
const searchRow = doc.createElement("div");
|
|
@@ -3534,7 +3775,7 @@ var IndicatorPicker = class {
|
|
|
3534
3775
|
this.list = doc.createElement("div");
|
|
3535
3776
|
this.list.className = "vela-ip-list";
|
|
3536
3777
|
this.dialog = new Dialog({
|
|
3537
|
-
title:
|
|
3778
|
+
title: this.t(TEXT8.title),
|
|
3538
3779
|
host: opts.host,
|
|
3539
3780
|
draggable: true,
|
|
3540
3781
|
closeOnInteractOutside: true,
|
|
@@ -3544,6 +3785,7 @@ var IndicatorPicker = class {
|
|
|
3544
3785
|
this.isOpen = open;
|
|
3545
3786
|
if (open) {
|
|
3546
3787
|
this.search.value = "";
|
|
3788
|
+
this.relabel();
|
|
3547
3789
|
this.refresh();
|
|
3548
3790
|
if (!this.search.closest('[data-layout="mobile"]')) setTimeout(() => this.search.focus(), 0);
|
|
3549
3791
|
}
|
|
@@ -3565,6 +3807,14 @@ var IndicatorPicker = class {
|
|
|
3565
3807
|
open() {
|
|
3566
3808
|
this.dialog.show();
|
|
3567
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
|
+
}
|
|
3568
3818
|
/** Re-render the lists if the dialog is open (async catalog updates land late). */
|
|
3569
3819
|
sync() {
|
|
3570
3820
|
if (this.isOpen) this.refresh();
|
|
@@ -3577,11 +3827,21 @@ var IndicatorPicker = class {
|
|
|
3577
3827
|
}
|
|
3578
3828
|
refresh() {
|
|
3579
3829
|
const doc = this.list.ownerDocument;
|
|
3580
|
-
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();
|
|
3581
3841
|
const library = this.opts.library();
|
|
3582
3842
|
const instances = this.opts.onChart();
|
|
3583
3843
|
this.list.replaceChildren();
|
|
3584
|
-
const match = (r) => !
|
|
3844
|
+
const match = (r) => !raw || [r.name, ...r.aliases ?? []].some((n) => lower(n).includes(q) || n.toLowerCase().includes(qPlain));
|
|
3585
3845
|
const rowEl = (r, opts) => {
|
|
3586
3846
|
const row = doc.createElement("div");
|
|
3587
3847
|
row.className = "vela-ip-row";
|
|
@@ -3595,7 +3855,7 @@ var IndicatorPicker = class {
|
|
|
3595
3855
|
if (r.beta) {
|
|
3596
3856
|
const beta = doc.createElement("span");
|
|
3597
3857
|
beta.className = "vela-ip-badge";
|
|
3598
|
-
beta.textContent =
|
|
3858
|
+
beta.textContent = this.t(TEXT8.beta);
|
|
3599
3859
|
row.appendChild(beta);
|
|
3600
3860
|
}
|
|
3601
3861
|
if (opts.instance !== void 0) {
|
|
@@ -3609,7 +3869,7 @@ var IndicatorPicker = class {
|
|
|
3609
3869
|
const trash = doc.createElement("button");
|
|
3610
3870
|
trash.className = "vela-ip-trash";
|
|
3611
3871
|
trash.appendChild(iconEl("trash", doc));
|
|
3612
|
-
trash.title =
|
|
3872
|
+
trash.title = this.t(TEXT8.remove);
|
|
3613
3873
|
row.appendChild(trash);
|
|
3614
3874
|
}
|
|
3615
3875
|
return row;
|
|
@@ -3620,7 +3880,7 @@ var IndicatorPicker = class {
|
|
|
3620
3880
|
card.className = "vela-ip-oncard";
|
|
3621
3881
|
const title = doc.createElement("div");
|
|
3622
3882
|
title.className = "vela-ip-group";
|
|
3623
|
-
title.textContent =
|
|
3883
|
+
title.textContent = this.t(TEXT8.onChart, { count: onChart.length });
|
|
3624
3884
|
card.appendChild(title);
|
|
3625
3885
|
for (const [r, i] of onChart) card.appendChild(rowEl(r, { instance: i }));
|
|
3626
3886
|
this.list.appendChild(card);
|
|
@@ -3628,7 +3888,7 @@ var IndicatorPicker = class {
|
|
|
3628
3888
|
const byCat = /* @__PURE__ */ new Map();
|
|
3629
3889
|
library.forEach((r, i) => {
|
|
3630
3890
|
if (!match(r)) return;
|
|
3631
|
-
const cat = r.category ??
|
|
3891
|
+
const cat = r.category ?? GENERAL;
|
|
3632
3892
|
const bucket = byCat.get(cat) ?? [];
|
|
3633
3893
|
bucket.push([r, i]);
|
|
3634
3894
|
byCat.set(cat, bucket);
|
|
@@ -3636,14 +3896,14 @@ var IndicatorPicker = class {
|
|
|
3636
3896
|
for (const [cat, bucket] of byCat) {
|
|
3637
3897
|
const title = doc.createElement("div");
|
|
3638
3898
|
title.className = "vela-ip-group";
|
|
3639
|
-
title.textContent = cat;
|
|
3899
|
+
title.textContent = cat === GENERAL ? this.t(TEXT8.general) : cat;
|
|
3640
3900
|
this.list.appendChild(title);
|
|
3641
3901
|
for (const [r, i] of bucket) this.list.appendChild(rowEl(r, { library: i }));
|
|
3642
3902
|
}
|
|
3643
3903
|
if (!onChart.length && byCat.size === 0) {
|
|
3644
3904
|
const empty = doc.createElement("div");
|
|
3645
3905
|
empty.className = "vela-ip-empty";
|
|
3646
|
-
empty.textContent = library.length ?
|
|
3906
|
+
empty.textContent = this.t(library.length ? TEXT8.noMatch : TEXT8.empty);
|
|
3647
3907
|
this.list.appendChild(empty);
|
|
3648
3908
|
}
|
|
3649
3909
|
}
|
|
@@ -3671,8 +3931,18 @@ var CSS8 = `
|
|
|
3671
3931
|
.vela-tq-hint { margin-top: var(--vela-space-2); text-align: center; color: var(--vela-fg-muted); min-height: 1.2em; }
|
|
3672
3932
|
.vela-tq-hint[data-invalid] { color: var(--vela-danger); }
|
|
3673
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
|
+
};
|
|
3674
3940
|
var TimeframeQuick = class {
|
|
3941
|
+
t(text) {
|
|
3942
|
+
return textOf(this.i18n, text);
|
|
3943
|
+
}
|
|
3675
3944
|
constructor(opts) {
|
|
3945
|
+
this.i18n = opts.i18n ?? null;
|
|
3676
3946
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
3677
3947
|
injectStyles(STYLE_ID8, CSS8, doc);
|
|
3678
3948
|
this.input = doc.createElement("input");
|
|
@@ -3681,7 +3951,7 @@ var TimeframeQuick = class {
|
|
|
3681
3951
|
this.hint = doc.createElement("div");
|
|
3682
3952
|
this.hint.className = "vela-tq-hint";
|
|
3683
3953
|
this.dialog = new Dialog({
|
|
3684
|
-
title:
|
|
3954
|
+
title: this.t(TEXT9.title),
|
|
3685
3955
|
host: opts.host,
|
|
3686
3956
|
draggable: true,
|
|
3687
3957
|
closeOnInteractOutside: true,
|
|
@@ -3703,7 +3973,12 @@ var TimeframeQuick = class {
|
|
|
3703
3973
|
}
|
|
3704
3974
|
});
|
|
3705
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
|
+
}
|
|
3706
3980
|
open(seed = "") {
|
|
3981
|
+
this.relabel();
|
|
3707
3982
|
this.dialog.show();
|
|
3708
3983
|
this.input.value = seed.toUpperCase();
|
|
3709
3984
|
this.renderHint();
|
|
@@ -3734,21 +4009,32 @@ var TimeframeQuick = class {
|
|
|
3734
4009
|
}
|
|
3735
4010
|
renderHint() {
|
|
3736
4011
|
const raw = this.input.value.trim();
|
|
3737
|
-
const parsed = parseTimeframe(raw);
|
|
4012
|
+
const parsed = parseTimeframe(raw, this.i18n);
|
|
3738
4013
|
if (!raw) {
|
|
3739
|
-
this.hint.textContent =
|
|
4014
|
+
this.hint.textContent = this.t(TEXT9.example);
|
|
3740
4015
|
delete this.hint.dataset.invalid;
|
|
3741
4016
|
} else if (parsed.valid) {
|
|
3742
4017
|
this.hint.textContent = parsed.label ?? "";
|
|
3743
4018
|
delete this.hint.dataset.invalid;
|
|
3744
4019
|
} else {
|
|
3745
|
-
this.hint.textContent =
|
|
4020
|
+
this.hint.textContent = this.t(TEXT9.invalid);
|
|
3746
4021
|
this.hint.dataset.invalid = "1";
|
|
3747
4022
|
}
|
|
3748
4023
|
}
|
|
3749
4024
|
};
|
|
3750
4025
|
|
|
3751
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
|
+
};
|
|
3752
4038
|
var STYLE_ID9 = "vela-widget-shortcuts";
|
|
3753
4039
|
var CSS9 = `
|
|
3754
4040
|
.vela-sh-cat {
|
|
@@ -3772,13 +4058,15 @@ var CSS9 = `
|
|
|
3772
4058
|
.vela-sh-static { color: var(--vela-fg-muted); font-size: var(--vela-font-size-sm); margin-top: var(--vela-space-2); }
|
|
3773
4059
|
`;
|
|
3774
4060
|
var ShortcutsHelp = class {
|
|
3775
|
-
|
|
4061
|
+
/** `i18n` returns the shell's current translator; the panel reads it each time it opens. */
|
|
4062
|
+
constructor(keymap, host, onOpenChange, i18n) {
|
|
3776
4063
|
this.keymap = keymap;
|
|
4064
|
+
this.i18n = i18n ?? englishTranslator;
|
|
3777
4065
|
const doc = (host ?? document.body).ownerDocument;
|
|
3778
4066
|
injectStyles(STYLE_ID9, CSS9, doc);
|
|
3779
4067
|
this.list = doc.createElement("div");
|
|
3780
4068
|
this.dialog = new Dialog({
|
|
3781
|
-
title:
|
|
4069
|
+
title: this.i18n().t(TEXT10.title),
|
|
3782
4070
|
host,
|
|
3783
4071
|
closeOnInteractOutside: true,
|
|
3784
4072
|
content: (body) => body.appendChild(this.list),
|
|
@@ -3799,6 +4087,9 @@ var ShortcutsHelp = class {
|
|
|
3799
4087
|
}
|
|
3800
4088
|
refresh() {
|
|
3801
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);
|
|
3802
4093
|
this.list.replaceChildren();
|
|
3803
4094
|
const addCategory = (text) => {
|
|
3804
4095
|
const cat = doc.createElement("div");
|
|
@@ -3824,7 +4115,7 @@ var ShortcutsHelp = class {
|
|
|
3824
4115
|
this.list.appendChild(row);
|
|
3825
4116
|
};
|
|
3826
4117
|
const byCategory = /* @__PURE__ */ new Map();
|
|
3827
|
-
for (const b of this.keymap.bindings()) {
|
|
4118
|
+
for (const b of this.keymap.bindings(i18n)) {
|
|
3828
4119
|
const bucket = byCategory.get(b.category) ?? [];
|
|
3829
4120
|
bucket.push(b);
|
|
3830
4121
|
byCategory.set(b.category, bucket);
|
|
@@ -3833,13 +4124,13 @@ var ShortcutsHelp = class {
|
|
|
3833
4124
|
addCategory(category);
|
|
3834
4125
|
for (const b of bindings) addRow(b.label, b.display);
|
|
3835
4126
|
}
|
|
3836
|
-
addCategory(
|
|
3837
|
-
addRow(
|
|
3838
|
-
addRow(
|
|
3839
|
-
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)]);
|
|
3840
4131
|
const s = doc.createElement("div");
|
|
3841
4132
|
s.className = "vela-sh-static";
|
|
3842
|
-
s.textContent =
|
|
4133
|
+
s.textContent = t(TEXT10.typing);
|
|
3843
4134
|
this.list.appendChild(s);
|
|
3844
4135
|
}
|
|
3845
4136
|
};
|
|
@@ -4190,7 +4481,7 @@ function decimalsFor(ref) {
|
|
|
4190
4481
|
return a >= 1 ? 2 : a >= 0.01 ? 4 : 6;
|
|
4191
4482
|
}
|
|
4192
4483
|
function fmtPrice(n, dp) {
|
|
4193
|
-
if (n == null || !Number.isFinite(n)) return "
|
|
4484
|
+
if (n == null || !Number.isFinite(n)) return "—";
|
|
4194
4485
|
const d = dp ?? decimalsFor(n);
|
|
4195
4486
|
return n.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
|
|
4196
4487
|
}
|
|
@@ -4201,13 +4492,25 @@ function fmtChange(open, close) {
|
|
|
4201
4492
|
const sign = diff >= 0 ? "+" : "";
|
|
4202
4493
|
return `${sign}${fmtPrice(diff, decimalsFor(close))} (${sign}${pct.toFixed(2)}%)`;
|
|
4203
4494
|
}
|
|
4204
|
-
function fmtChangePct(open, close) {
|
|
4205
|
-
if (open == null || close == null || !Number.isFinite(open) || !Number.isFinite(close) || open === 0) return "";
|
|
4206
|
-
const pct = (close - open) / open * 100;
|
|
4207
|
-
return `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%`;
|
|
4208
|
-
}
|
|
4209
4495
|
|
|
4210
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
|
+
};
|
|
4211
4514
|
function segmentVisibility(parts, chartHidden = false) {
|
|
4212
4515
|
return { avatar: parts.logo, symbol: parts.name, meta: parts.name, market: parts.market, ohlc: parts.ohlc && !chartHidden, change: parts.change && !chartHidden, eye: chartHidden };
|
|
4213
4516
|
}
|
|
@@ -4217,28 +4520,31 @@ var STATUSLINE_LADDER = [
|
|
|
4217
4520
|
{ stacked: true, level: "compact" },
|
|
4218
4521
|
{ stacked: true, level: "minimal" }
|
|
4219
4522
|
];
|
|
4220
|
-
function readoutCells(level, readout) {
|
|
4523
|
+
function readoutCells(level, readout, i18n) {
|
|
4221
4524
|
if (readout === "value") return [{ key: "close", label: "" }];
|
|
4525
|
+
const label = (key) => textOf(i18n, CELL_LABELS[key]);
|
|
4222
4526
|
if (level === "full") {
|
|
4223
4527
|
return [
|
|
4224
|
-
{ key: "open", label: "
|
|
4225
|
-
{ key: "high", label: "
|
|
4226
|
-
{ key: "low", label: "
|
|
4227
|
-
{ 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") }
|
|
4228
4532
|
];
|
|
4229
4533
|
}
|
|
4230
|
-
return [{ key: "close", label: level === "compact" ? "
|
|
4534
|
+
return [{ key: "close", label: level === "compact" ? label("close") : "" }];
|
|
4231
4535
|
}
|
|
4232
|
-
function statuslineMenuItems(parts, chartVisible) {
|
|
4536
|
+
function statuslineMenuItems(parts, chartVisible, i18n) {
|
|
4537
|
+
const t = (m) => textOf(i18n, m);
|
|
4233
4538
|
return [
|
|
4234
|
-
{ id: "part:logo", label:
|
|
4235
|
-
{ id: "part:name", label:
|
|
4236
|
-
{ id: "part:market", label:
|
|
4237
|
-
{ id: "part:ohlc", label:
|
|
4238
|
-
{ id: "part:change", label:
|
|
4239
|
-
{ 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 }
|
|
4240
4545
|
];
|
|
4241
4546
|
}
|
|
4547
|
+
var SHOW_CHART_LABEL = TEXT11.showChart;
|
|
4242
4548
|
|
|
4243
4549
|
// src/widget/statusline.ts
|
|
4244
4550
|
var STYLE_ID10 = "vela-widget-statusline";
|
|
@@ -4247,7 +4553,7 @@ var CSS10 = `
|
|
|
4247
4553
|
position: absolute;
|
|
4248
4554
|
top: var(--vela-space-2);
|
|
4249
4555
|
/* Track the indicator legend's left edge: the renderer publishes its toolbar gutter
|
|
4250
|
-
* 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 —
|
|
4251
4557
|
* so the two columns stay aligned whether the toolbar is docked (44px), collapsed
|
|
4252
4558
|
* (16px), or absent entirely (a workspace cell: 0). */
|
|
4253
4559
|
left: calc(var(--vela-toolbar-gutter, 0px) + 10px);
|
|
@@ -4263,8 +4569,8 @@ var CSS10 = `
|
|
|
4263
4569
|
color: var(--vela-fg);
|
|
4264
4570
|
font-size: var(--vela-font-size-md);
|
|
4265
4571
|
/* Same chip treatment as the indicator legend rows (InputsUI): a translucent wash of
|
|
4266
|
-
* the chart background when idle
|
|
4267
|
-
* 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
|
|
4268
4574
|
* compensating negative margin (mirroring the legend rows) keeps the avatar's left
|
|
4269
4575
|
* edge on the legend column's left edge (both at left:10px) while the chip itself
|
|
4270
4576
|
* extends 7px further left, so both columns' chips share the same left edge. */
|
|
@@ -4276,12 +4582,12 @@ var CSS10 = `
|
|
|
4276
4582
|
}
|
|
4277
4583
|
/* Hovering opens the chip the same way a legend row opens: solid chart background
|
|
4278
4584
|
* plus the same inset neutral outline the indicator rows wear (InputsUI's
|
|
4279
|
-
* setRowHighlighted)
|
|
4585
|
+
* setRowHighlighted) — the two columns read as one family. */
|
|
4280
4586
|
.vela-statusline:hover { background: var(--vela-bg); box-shadow: inset 0 0 0 1px var(--vela-border); }
|
|
4281
|
-
/* Chart hidden (the price series' eye
|
|
4587
|
+
/* Chart hidden (the price series' eye — renderer 'candleVisible'): the line dims to
|
|
4282
4588
|
* the same 0.5 wash a hidden indicator's legend row wears. */
|
|
4283
4589
|
.vela-statusline.vela-sl-chart-hidden { opacity: 0.5; }
|
|
4284
|
-
/* Two rows inside the chip
|
|
4590
|
+
/* Two rows inside the chip — the identity (logo / symbol / meta / market badge) and the
|
|
4285
4591
|
* value readout (O/H/L/C + change, or the show-chart eye). In the widest layout they sit
|
|
4286
4592
|
* side by side on one line; the STACKED layouts (fit() decides, see the ladder in
|
|
4287
4593
|
* statusline-model) turn the chip into a column so the values drop under the symbol. */
|
|
@@ -4298,7 +4604,7 @@ var CSS10 = `
|
|
|
4298
4604
|
* that follows the chip (see the stacked legend shift below). */
|
|
4299
4605
|
row-gap: var(--vela-space-1);
|
|
4300
4606
|
}
|
|
4301
|
-
/* 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
|
|
4302
4608
|
* never does, so its overflow is what fit() measures to keep descending the ladder. */
|
|
4303
4609
|
.vela-statusline.vela-sl-stacked .vela-sl-identity { max-width: 100%; }
|
|
4304
4610
|
.vela-statusline.vela-sl-stacked .vela-sl-meta {
|
|
@@ -4321,25 +4627,28 @@ var CSS10 = `
|
|
|
4321
4627
|
}
|
|
4322
4628
|
.vela-statusline .vela-sl-symbol { font-weight: 600; font-size: var(--vela-font-size-lg); }
|
|
4323
4629
|
.vela-statusline .vela-sl-meta { color: var(--vela-fg-muted); font-size: var(--vela-font-size-md); font-weight: 600; }
|
|
4324
|
-
/* The meta opens with "
|
|
4630
|
+
/* The meta opens with "· " — sit its dot one space-width after the ticker, as far as the
|
|
4325
4631
|
* venue sits after it, not a full row gap away. */
|
|
4326
4632
|
.vela-statusline .vela-sl-symbol + .vela-sl-meta { margin-left: calc(var(--vela-space-1) - var(--vela-space-2)); }
|
|
4327
|
-
/* Market status badge
|
|
4633
|
+
/* Market status badge — a kit callout bubble (icon-only 16px circle, label on hover
|
|
4328
4634
|
* via the kit tooltip); the session tint is applied per status in setMarketStatus. While
|
|
4329
4635
|
* the chart replays past bars it wears the replay badge instead (the inverse chip). */
|
|
4330
4636
|
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
4331
4637
|
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
4332
4638
|
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
4333
4639
|
/* Tabular figures: every digit has one advance, so a readout whose text changes only in
|
|
4334
|
-
* its digits keeps its width
|
|
4640
|
+
* its digits keeps its width — render() skips the ladder for those (see widthKey). */
|
|
4335
4641
|
.vela-statusline .vela-sl-values { font-variant-numeric: tabular-nums; }
|
|
4336
4642
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
4337
4643
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
4338
|
-
/*
|
|
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) —
|
|
4339
4648
|
* these are the pre-ink fallbacks only. */
|
|
4340
4649
|
.vela-statusline .vela-sl-change[data-dir='up'] { color: var(--vela-up); }
|
|
4341
4650
|
.vela-statusline .vela-sl-change[data-dir='down'] { color: var(--vela-down); }
|
|
4342
|
-
/* The show-chart eye
|
|
4651
|
+
/* The show-chart eye — out only while the chart is hidden (syncParts drives display),
|
|
4343
4652
|
* replacing the value readout it took away. Same footprint as a legend action button. */
|
|
4344
4653
|
.vela-statusline .vela-sl-eye {
|
|
4345
4654
|
align-self: center;
|
|
@@ -4358,18 +4667,18 @@ var CSS10 = `
|
|
|
4358
4667
|
}
|
|
4359
4668
|
.vela-statusline .vela-sl-eye:hover { color: var(--vela-fg); background: color-mix(in srgb, var(--vela-fg) 12%, transparent); }
|
|
4360
4669
|
/* Stack the TOP pane's legend below the status line (lower study panes stay put).
|
|
4361
|
-
* The renderer marks whichever legend sits at the plot's top edge
|
|
4362
|
-
* 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
|
|
4363
4672
|
* with the status line whichever pane owns the top. The renderer sets the legend's
|
|
4364
|
-
* inline top
|
|
4365
|
-
* 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
|
|
4366
4675
|
* stylesheet is document-global, so a bare attribute selector here would shift every
|
|
4367
4676
|
* chart on the page, including statusline-less ones. */
|
|
4368
4677
|
.vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(26px); }
|
|
4369
|
-
/* A stacked chip is two lines tall
|
|
4678
|
+
/* A stacked chip is two lines tall — the legend shifts one more line. fit() flags the
|
|
4370
4679
|
* host whenever the ladder lands on a stacked layout. */
|
|
4371
4680
|
.vela-has-statusline.vela-sl-stacked-host [${LEGEND_AT_TOP_ATTR}] { transform: translateY(40px); }
|
|
4372
|
-
/* 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
|
|
4373
4682
|
* of the readout fits, exactly as on a narrow desktop chart. */
|
|
4374
4683
|
[data-layout='mobile'] .vela-statusline { font-size: var(--vela-font-size-sm); }
|
|
4375
4684
|
[data-layout='mobile'] .vela-statusline .vela-sl-symbol { font-size: var(--vela-font-size-md); }
|
|
@@ -4378,14 +4687,16 @@ function baseOfTicker(ticker) {
|
|
|
4378
4687
|
return ticker.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || ticker;
|
|
4379
4688
|
}
|
|
4380
4689
|
var MARKET_LABELS = {
|
|
4381
|
-
open: "Market Open",
|
|
4382
|
-
pre: "Pre-Market",
|
|
4383
|
-
post: "Post-Market",
|
|
4384
|
-
extended: "Extended Hours",
|
|
4385
|
-
closed: "Market Closed",
|
|
4386
|
-
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")
|
|
4387
4696
|
};
|
|
4388
|
-
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 = "—";
|
|
4389
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>';
|
|
4390
4701
|
var MARKET_INKS = {
|
|
4391
4702
|
open: "var(--vela-up)",
|
|
@@ -4454,13 +4765,16 @@ var Statusline = class {
|
|
|
4454
4765
|
this.fitKey = null;
|
|
4455
4766
|
/** Web fonts landing after the last fit change every width without a resize. */
|
|
4456
4767
|
this.onFontsLoaded = () => this.fit();
|
|
4768
|
+
/** The bound chart's translator (English until {@link onChart}). */
|
|
4769
|
+
this.i18n = null;
|
|
4770
|
+
this.meta = null;
|
|
4457
4771
|
this.onContextMenu = (e) => {
|
|
4458
4772
|
if (!this.menu || !this.menuHooks) return;
|
|
4459
4773
|
e.preventDefault();
|
|
4460
4774
|
e.stopPropagation();
|
|
4461
4775
|
const chartVisible = this.menuHooks.chartVisible();
|
|
4462
4776
|
this.setChartHidden(!chartVisible);
|
|
4463
|
-
this.menu.setItems(statuslineMenuItems(this.parts, chartVisible));
|
|
4777
|
+
this.menu.setItems(statuslineMenuItems(this.parts, chartVisible, this.i18n));
|
|
4464
4778
|
this.menu.openAt(e.clientX, e.clientY);
|
|
4465
4779
|
};
|
|
4466
4780
|
const doc = host.ownerDocument;
|
|
@@ -4480,7 +4794,7 @@ var Statusline = class {
|
|
|
4480
4794
|
icon: "market-open",
|
|
4481
4795
|
background: `color-mix(in srgb, ${MARKET_INKS.open} 20%, transparent)`,
|
|
4482
4796
|
color: MARKET_INKS.open,
|
|
4483
|
-
label: MARKET_LABELS.open,
|
|
4797
|
+
label: this.t(MARKET_LABELS.open),
|
|
4484
4798
|
host
|
|
4485
4799
|
});
|
|
4486
4800
|
this.replayBadge = doc.createElement("span");
|
|
@@ -4494,11 +4808,12 @@ var Statusline = class {
|
|
|
4494
4808
|
this.ohlcEl.className = "vela-sl-ohlc";
|
|
4495
4809
|
this.changeEl = doc.createElement("span");
|
|
4496
4810
|
this.changeEl.className = "vela-sl-change";
|
|
4811
|
+
this.changeEl.dir = "ltr";
|
|
4497
4812
|
this.eyeEl = doc.createElement("button");
|
|
4498
4813
|
this.eyeEl.type = "button";
|
|
4499
4814
|
this.eyeEl.className = "vela-sl-eye";
|
|
4500
4815
|
this.eyeEl.innerHTML = iconAt("eye-off", 14);
|
|
4501
|
-
this.eyeEl.setAttribute("aria-label",
|
|
4816
|
+
this.eyeEl.setAttribute("aria-label", this.t(SHOW_CHART_LABEL));
|
|
4502
4817
|
this.eyeEl.style.display = "none";
|
|
4503
4818
|
this.eyeEl.addEventListener("click", (e) => {
|
|
4504
4819
|
e.stopPropagation();
|
|
@@ -4518,11 +4833,14 @@ var Statusline = class {
|
|
|
4518
4833
|
this.fitRO.observe(host);
|
|
4519
4834
|
}
|
|
4520
4835
|
doc.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
|
|
4521
|
-
this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
|
|
4522
|
-
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" });
|
|
4523
4838
|
this.setMarketStatus("open");
|
|
4524
4839
|
this.render();
|
|
4525
4840
|
}
|
|
4841
|
+
t(text) {
|
|
4842
|
+
return textOf(this.i18n, text);
|
|
4843
|
+
}
|
|
4526
4844
|
setSymbol(symbol) {
|
|
4527
4845
|
const ticker = parseSymbol(symbol).ticker;
|
|
4528
4846
|
this.symbolEl.textContent = ticker;
|
|
@@ -4607,7 +4925,17 @@ var Statusline = class {
|
|
|
4607
4925
|
}
|
|
4608
4926
|
/** The "· BINANCE · 1h" segment after the symbol — venue first, then resolution. */
|
|
4609
4927
|
setMeta(timeframe, provider) {
|
|
4610
|
-
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;
|
|
4611
4939
|
this.fit();
|
|
4612
4940
|
}
|
|
4613
4941
|
/** Dress the market badge for a session state: its icon, tinted circle, and the
|
|
@@ -4628,7 +4956,7 @@ var Statusline = class {
|
|
|
4628
4956
|
this.replayBadge.hidden = !this.replaying;
|
|
4629
4957
|
if (this.replaying) {
|
|
4630
4958
|
this.marketEl.dataset.status = "replay";
|
|
4631
|
-
this.marketTip.setContent(REPLAY_LABEL);
|
|
4959
|
+
this.marketTip.setContent(this.t(REPLAY_LABEL));
|
|
4632
4960
|
return;
|
|
4633
4961
|
}
|
|
4634
4962
|
const status = this.marketStatus;
|
|
@@ -4638,9 +4966,9 @@ var Statusline = class {
|
|
|
4638
4966
|
icon: `market-${status}`,
|
|
4639
4967
|
background: `color-mix(in srgb, ${ink} 20%, transparent)`,
|
|
4640
4968
|
color: ink,
|
|
4641
|
-
label: MARKET_LABELS[status]
|
|
4969
|
+
label: this.t(MARKET_LABELS[status])
|
|
4642
4970
|
});
|
|
4643
|
-
this.marketTip.setContent(MARKET_LABELS[status]);
|
|
4971
|
+
this.marketTip.setContent(this.t(MARKET_LABELS[status]));
|
|
4644
4972
|
}
|
|
4645
4973
|
/** Show/hide one part — the settings dialog's Status line tab and the right-click
|
|
4646
4974
|
* menu both drive these. 'name' owns the venue/timeframe meta too (see
|
|
@@ -4697,6 +5025,9 @@ var Statusline = class {
|
|
|
4697
5025
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
4698
5026
|
onChart(chart) {
|
|
4699
5027
|
this.detach();
|
|
5028
|
+
const relabel = this.i18n !== chart.i18n;
|
|
5029
|
+
this.i18n = chart.i18n;
|
|
5030
|
+
if (relabel) this.relocalize();
|
|
4700
5031
|
this.lastBar = null;
|
|
4701
5032
|
this.hoverBar = null;
|
|
4702
5033
|
this.unsubs.push(
|
|
@@ -4772,18 +5103,29 @@ var Statusline = class {
|
|
|
4772
5103
|
const doc = this.el.ownerDocument;
|
|
4773
5104
|
const up = this.isUp ? this.isUp(bar) : bar.close >= bar.open;
|
|
4774
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)}%`;
|
|
4775
5109
|
const cell = (k, v) => {
|
|
4776
5110
|
const s = doc.createElement("span");
|
|
4777
5111
|
if (k) s.append(`${k} `);
|
|
4778
5112
|
const b = doc.createElement("b");
|
|
4779
|
-
b.
|
|
5113
|
+
b.dir = "ltr";
|
|
5114
|
+
b.textContent = price(v);
|
|
4780
5115
|
b.style.color = ink;
|
|
4781
5116
|
s.appendChild(b);
|
|
4782
5117
|
return s;
|
|
4783
5118
|
};
|
|
4784
5119
|
const { level } = this.layout;
|
|
4785
|
-
this.ohlcEl.replaceChildren(...readoutCells(level, this.readout).map((c) => cell(c.label, bar[c.key])));
|
|
4786
|
-
|
|
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;
|
|
4787
5129
|
this.changeEl.dataset.dir = up ? "up" : "down";
|
|
4788
5130
|
this.changeEl.style.color = ink;
|
|
4789
5131
|
}
|
|
@@ -4798,7 +5140,7 @@ var CSS11 = `
|
|
|
4798
5140
|
.vela-watermark {
|
|
4799
5141
|
position: absolute;
|
|
4800
5142
|
/* Insets follow the renderer-published gutters AND the price-pane vertical
|
|
4801
|
-
* bounds (mount container), so the mark centers on the PRICE PANE
|
|
5143
|
+
* bounds (mount container), so the mark centers on the PRICE PANE — never
|
|
4802
5144
|
* the study panes below, the drawings toolbar, or the price scale. */
|
|
4803
5145
|
top: var(--vela-price-pane-top, 0px);
|
|
4804
5146
|
bottom: var(--vela-price-pane-bottom, 0px);
|
|
@@ -4832,8 +5174,14 @@ function watermarkFontPx(availPx, textPxAtMax) {
|
|
|
4832
5174
|
if (textPxAtMax <= 0) return MAX_FONT_PX;
|
|
4833
5175
|
return Math.max(MIN_FONT_PX, Math.min(MAX_FONT_PX, Math.floor(MAX_FONT_PX * availPx * FILL / textPxAtMax)));
|
|
4834
5176
|
}
|
|
5177
|
+
var TEXT12 = {
|
|
5178
|
+
replay: msg("widget.watermark.replay", "Replay"),
|
|
5179
|
+
mark: msg("widget.watermark.mark", "{symbol} · {timeframe}")
|
|
5180
|
+
};
|
|
4835
5181
|
var Watermark = class {
|
|
4836
|
-
|
|
5182
|
+
/** `i18n` reads the chart's translator (absent: English). */
|
|
5183
|
+
constructor(host, symbol, timeframe, i18n = () => null) {
|
|
5184
|
+
this.i18n = i18n;
|
|
4837
5185
|
this.resizeObserver = null;
|
|
4838
5186
|
/** The host's visibility preference (the persisted watermark toggle). */
|
|
4839
5187
|
this.shown = true;
|
|
@@ -4845,6 +5193,8 @@ var Watermark = class {
|
|
|
4845
5193
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
4846
5194
|
* fires during chart construction, before any subscriber can see it. */
|
|
4847
5195
|
this.loading = true;
|
|
5196
|
+
this.symbol = "";
|
|
5197
|
+
this.timeframe = "";
|
|
4848
5198
|
injectStyles(STYLE_ID11, CSS11, host.ownerDocument);
|
|
4849
5199
|
this.el = host.ownerDocument.createElement("div");
|
|
4850
5200
|
this.el.className = "vela-watermark";
|
|
@@ -4852,7 +5202,8 @@ var Watermark = class {
|
|
|
4852
5202
|
this.text = host.ownerDocument.createElement("span");
|
|
4853
5203
|
this.replayLine = host.ownerDocument.createElement("span");
|
|
4854
5204
|
this.replayLine.className = "vela-watermark-replay";
|
|
4855
|
-
this.
|
|
5205
|
+
this.replayText = host.ownerDocument.createTextNode(textOf(this.i18n(), TEXT12.replay));
|
|
5206
|
+
this.replayLine.append(iconEl("replay", host.ownerDocument), this.replayText);
|
|
4856
5207
|
this.el.append(this.text, this.replayLine);
|
|
4857
5208
|
host.appendChild(this.el);
|
|
4858
5209
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -4888,9 +5239,17 @@ var Watermark = class {
|
|
|
4888
5239
|
this.el.style.display = (this.shown || replay) && !this.loading ? "" : "none";
|
|
4889
5240
|
}
|
|
4890
5241
|
update(symbol, timeframe) {
|
|
4891
|
-
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) }) : "";
|
|
4892
5246
|
this.fit();
|
|
4893
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
|
+
}
|
|
4894
5253
|
/** Measure the text at the cap and shrink it to the price pane's width. */
|
|
4895
5254
|
fit() {
|
|
4896
5255
|
if (this.el.clientWidth <= 0 || !this.text.textContent) return;
|
|
@@ -4986,7 +5345,7 @@ var ChartContextMenu = class {
|
|
|
4986
5345
|
}
|
|
4987
5346
|
contributed(zone) {
|
|
4988
5347
|
return widgetActions(`context:${zone}`, this.context()).map((a) => ({
|
|
4989
|
-
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 },
|
|
4990
5349
|
order: a.order
|
|
4991
5350
|
}));
|
|
4992
5351
|
}
|
|
@@ -4994,6 +5353,7 @@ var ChartContextMenu = class {
|
|
|
4994
5353
|
return composeMenu(zone, this.builtinItems(zone), this.contributed(zone));
|
|
4995
5354
|
}
|
|
4996
5355
|
builtinItems(zone) {
|
|
5356
|
+
const chart = this.chart;
|
|
4997
5357
|
if (zone === "price-axis") {
|
|
4998
5358
|
const pane = this.lastPane;
|
|
4999
5359
|
return priceAxisItems({
|
|
@@ -5004,16 +5364,18 @@ var ChartContextMenu = class {
|
|
|
5004
5364
|
priceLabel: this.flag("priceLabel"),
|
|
5005
5365
|
countdown: this.flag("countdown"),
|
|
5006
5366
|
priceLine: this.flag("currentPriceLine")
|
|
5007
|
-
});
|
|
5367
|
+
}, this.chart?.i18n);
|
|
5008
5368
|
}
|
|
5009
5369
|
if (zone === "time-axis") {
|
|
5010
|
-
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);
|
|
5011
5371
|
}
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
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
|
+
);
|
|
5017
5379
|
}
|
|
5018
5380
|
run(id) {
|
|
5019
5381
|
const chart = this.chart;
|
|
@@ -5410,6 +5772,14 @@ var CONTROLS_BOTTOM_PX = TIME_AXIS_H2 + 12;
|
|
|
5410
5772
|
var CLUSTER_H = 24;
|
|
5411
5773
|
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
5412
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
|
+
};
|
|
5413
5783
|
var STYLE_ID12 = "vela-cell-controls";
|
|
5414
5784
|
var CSS12 = `
|
|
5415
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;}
|
|
@@ -5470,17 +5840,17 @@ var CellControls = class {
|
|
|
5470
5840
|
this.host.appendChild(this.root);
|
|
5471
5841
|
this.refresh();
|
|
5472
5842
|
}
|
|
5473
|
-
/** Rebuild the buttons (the multi-cell gate
|
|
5843
|
+
/** Rebuild the buttons (the multi-cell gate, the maximized state or the language changed). */
|
|
5474
5844
|
refresh() {
|
|
5475
5845
|
this.root.textContent = "";
|
|
5476
5846
|
const multi = this.deps.multiCell();
|
|
5477
5847
|
const maximized = multi && this.deps.isMaximized();
|
|
5478
5848
|
if (multi && !maximized) this.root.appendChild(this.makeGrip());
|
|
5479
|
-
this.root.appendChild(this.button("minus",
|
|
5480
|
-
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)));
|
|
5481
5851
|
if (multi) {
|
|
5482
5852
|
this.root.appendChild(
|
|
5483
|
-
this.button(maximized ? "restore" : "maximize", maximized ?
|
|
5853
|
+
this.button(maximized ? "restore" : "maximize", maximized ? TEXT13.restoreLayout : TEXT13.maximizeChart, () => this.deps.toggleMaximize(), {
|
|
5484
5854
|
// The maximized state reads as an inverse chip (white-on-dark, dark-on-light),
|
|
5485
5855
|
// the same active-state affordance as a collapsed pane's expand button.
|
|
5486
5856
|
selected: maximized
|
|
@@ -5488,14 +5858,19 @@ var CellControls = class {
|
|
|
5488
5858
|
);
|
|
5489
5859
|
}
|
|
5490
5860
|
this.root.appendChild(
|
|
5491
|
-
this.button("reset",
|
|
5861
|
+
this.button("reset", TEXT13.resetChart, () => {
|
|
5492
5862
|
this.glider.stop();
|
|
5493
5863
|
this.deps.reset();
|
|
5494
5864
|
})
|
|
5495
5865
|
);
|
|
5496
5866
|
}
|
|
5497
|
-
|
|
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 = {}) {
|
|
5498
5872
|
const b = this.host.ownerDocument.createElement("button");
|
|
5873
|
+
const title = this.t(text);
|
|
5499
5874
|
b.type = "button";
|
|
5500
5875
|
b.title = title;
|
|
5501
5876
|
b.setAttribute("aria-label", title);
|
|
@@ -5513,8 +5888,8 @@ var CellControls = class {
|
|
|
5513
5888
|
makeGrip() {
|
|
5514
5889
|
const b = this.host.ownerDocument.createElement("button");
|
|
5515
5890
|
b.type = "button";
|
|
5516
|
-
b.title =
|
|
5517
|
-
b.setAttribute("aria-label",
|
|
5891
|
+
b.title = this.t(TEXT13.dragToMove);
|
|
5892
|
+
b.setAttribute("aria-label", b.title);
|
|
5518
5893
|
b.className = "vela-cc-btn vela-cc-grip";
|
|
5519
5894
|
b.innerHTML = icon("grip");
|
|
5520
5895
|
b.addEventListener("pointerdown", (e) => this.onGripDown(b, e));
|
|
@@ -5657,6 +6032,28 @@ var WidgetHistory = class {
|
|
|
5657
6032
|
};
|
|
5658
6033
|
|
|
5659
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
|
+
};
|
|
5660
6057
|
function seedDefaults(opts) {
|
|
5661
6058
|
return {
|
|
5662
6059
|
symbol: opts.symbol,
|
|
@@ -5688,6 +6085,13 @@ function instanceDeltas(handle) {
|
|
|
5688
6085
|
const props = inputDeltas(handle.props, handle.propValues());
|
|
5689
6086
|
return inputs || props ? { ...inputs ? { inputs } : {}, ...props ? { props } : {} } : void 0;
|
|
5690
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
|
+
}
|
|
5691
6095
|
var ChartCell = class {
|
|
5692
6096
|
constructor(id, gridHost, seed, deps) {
|
|
5693
6097
|
this.id = id;
|
|
@@ -5763,6 +6167,7 @@ var ChartCell = class {
|
|
|
5763
6167
|
this.host,
|
|
5764
6168
|
{
|
|
5765
6169
|
...deps.chartDefaults,
|
|
6170
|
+
...deps.localization?.(),
|
|
5766
6171
|
symbol,
|
|
5767
6172
|
timeframe: seed.timeframe,
|
|
5768
6173
|
bars: seed.bars,
|
|
@@ -5848,7 +6253,7 @@ var ChartCell = class {
|
|
|
5848
6253
|
if (!this.indicatorValuesOn) this.inner.renderer.set("indicatorValues", false);
|
|
5849
6254
|
this.watermarkOn = seed.watermark ?? deps.watermark;
|
|
5850
6255
|
this.replayWatermarkOn = seed.replayWatermark ?? true;
|
|
5851
|
-
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;
|
|
5852
6257
|
if (!this.watermarkOn) this.watermark?.setVisible(false);
|
|
5853
6258
|
if (!this.replayWatermarkOn) this.watermark?.setReplayVisible(false);
|
|
5854
6259
|
this.inner.on("replay:start", () => this.watermark?.setReplaying(true));
|
|
@@ -6076,12 +6481,10 @@ var ChartCell = class {
|
|
|
6076
6481
|
pushSettingsSections() {
|
|
6077
6482
|
const chart = this.inner;
|
|
6078
6483
|
if (!chart) return;
|
|
6079
|
-
const rth = "Regular hours (RTH)";
|
|
6080
|
-
const eth = "Extended hours (ETH)";
|
|
6081
6484
|
const shadeRows = this.sessionOvernightFlag ? [
|
|
6082
6485
|
{
|
|
6083
6486
|
kind: "color",
|
|
6084
|
-
label:
|
|
6487
|
+
label: TEXT14.extendedColor,
|
|
6085
6488
|
id: "extended-color",
|
|
6086
6489
|
get: () => this.sessionShadeColor("extendedColor"),
|
|
6087
6490
|
set: (v) => this.setSessionShadeColor("extendedColor", v)
|
|
@@ -6089,43 +6492,46 @@ var ChartCell = class {
|
|
|
6089
6492
|
] : [
|
|
6090
6493
|
{
|
|
6091
6494
|
kind: "color",
|
|
6092
|
-
label:
|
|
6495
|
+
label: TEXT14.premarketColor,
|
|
6093
6496
|
id: "premarket-color",
|
|
6094
6497
|
get: () => this.sessionShadeColor("premarketColor"),
|
|
6095
6498
|
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
6096
6499
|
},
|
|
6097
6500
|
{
|
|
6098
6501
|
kind: "color",
|
|
6099
|
-
label:
|
|
6502
|
+
label: TEXT14.postmarketColor,
|
|
6100
6503
|
id: "postmarket-color",
|
|
6101
6504
|
get: () => this.sessionShadeColor("postmarketColor"),
|
|
6102
6505
|
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
6103
6506
|
}
|
|
6104
6507
|
];
|
|
6105
6508
|
const sessionSection = {
|
|
6106
|
-
title:
|
|
6509
|
+
title: TEXT14.tradingSession,
|
|
6107
6510
|
id: "trading-session",
|
|
6108
6511
|
placement: "symbol",
|
|
6109
6512
|
rows: [
|
|
6110
6513
|
{
|
|
6111
6514
|
kind: "select",
|
|
6112
|
-
label:
|
|
6515
|
+
label: TEXT14.session,
|
|
6113
6516
|
id: "session",
|
|
6114
|
-
options: [
|
|
6115
|
-
|
|
6116
|
-
|
|
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")
|
|
6117
6523
|
},
|
|
6118
6524
|
...shadeRows
|
|
6119
6525
|
]
|
|
6120
6526
|
};
|
|
6121
6527
|
const advanced = {
|
|
6122
|
-
title:
|
|
6528
|
+
title: TEXT14.advanced,
|
|
6123
6529
|
id: "advanced",
|
|
6124
6530
|
placement: "end",
|
|
6125
6531
|
rows: [
|
|
6126
6532
|
{
|
|
6127
6533
|
kind: "select",
|
|
6128
|
-
label:
|
|
6534
|
+
label: TEXT14.barsToFetch,
|
|
6129
6535
|
id: "bars",
|
|
6130
6536
|
options: ["500", "1000", "2000", "5000", "10000", "20000", "50000", "60000", "80000", "100000"],
|
|
6131
6537
|
get: () => String(this.state.bars ?? 1e3),
|
|
@@ -6138,15 +6544,15 @@ var ChartCell = class {
|
|
|
6138
6544
|
]
|
|
6139
6545
|
};
|
|
6140
6546
|
const timezoneSection = {
|
|
6141
|
-
title:
|
|
6547
|
+
title: TEXT14.timeZone,
|
|
6142
6548
|
id: "time-zone",
|
|
6143
6549
|
placement: "symbol",
|
|
6144
6550
|
rows: [
|
|
6145
6551
|
{
|
|
6146
6552
|
kind: "select",
|
|
6147
|
-
label:
|
|
6553
|
+
label: TEXT14.timeZoneRow,
|
|
6148
6554
|
id: "zone",
|
|
6149
|
-
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]),
|
|
6150
6556
|
get: () => {
|
|
6151
6557
|
const choice = this.deps.timezone();
|
|
6152
6558
|
return isExchangeTimezone(choice) ? choice : normalizeTimezone(choice);
|
|
@@ -6156,20 +6562,20 @@ var ChartCell = class {
|
|
|
6156
6562
|
]
|
|
6157
6563
|
};
|
|
6158
6564
|
const watermarkSection = {
|
|
6159
|
-
title:
|
|
6565
|
+
title: TEXT14.watermark,
|
|
6160
6566
|
id: "watermark",
|
|
6161
6567
|
placement: "symbol",
|
|
6162
6568
|
rows: [
|
|
6163
6569
|
{
|
|
6164
6570
|
kind: "toggle",
|
|
6165
|
-
label:
|
|
6571
|
+
label: TEXT14.symbolWatermark,
|
|
6166
6572
|
id: "visible",
|
|
6167
6573
|
get: () => this.watermarkOn,
|
|
6168
6574
|
set: (v) => this.setWatermarkVisible(v)
|
|
6169
6575
|
},
|
|
6170
6576
|
{
|
|
6171
6577
|
kind: "toggle",
|
|
6172
|
-
label:
|
|
6578
|
+
label: TEXT14.replayWatermark,
|
|
6173
6579
|
id: "replay",
|
|
6174
6580
|
get: () => this.replayWatermarkOn,
|
|
6175
6581
|
set: (v) => this.setReplayWatermarkVisible(v)
|
|
@@ -6180,49 +6586,49 @@ var ChartCell = class {
|
|
|
6180
6586
|
if (this.statusline) {
|
|
6181
6587
|
const sl = this.statusline;
|
|
6182
6588
|
sections.push({
|
|
6183
|
-
title:
|
|
6589
|
+
title: TEXT14.statusLine,
|
|
6184
6590
|
id: "status-line",
|
|
6185
6591
|
rows: [
|
|
6186
|
-
{ kind: "heading", label:
|
|
6592
|
+
{ kind: "heading", label: TEXT14.statusLineParts, id: "parts" },
|
|
6187
6593
|
{
|
|
6188
6594
|
kind: "toggle",
|
|
6189
|
-
label:
|
|
6595
|
+
label: STATUSLINE_PART_LABELS.name,
|
|
6190
6596
|
id: "name",
|
|
6191
6597
|
get: () => sl.partVisible("name"),
|
|
6192
6598
|
set: (v) => this.setStatuslinePart("name", v)
|
|
6193
6599
|
},
|
|
6194
6600
|
{
|
|
6195
6601
|
kind: "toggle",
|
|
6196
|
-
label:
|
|
6602
|
+
label: STATUSLINE_PART_LABELS.market,
|
|
6197
6603
|
id: "market",
|
|
6198
6604
|
get: () => sl.partVisible("market"),
|
|
6199
6605
|
set: (v) => this.setStatuslinePart("market", v)
|
|
6200
6606
|
},
|
|
6201
6607
|
{
|
|
6202
6608
|
kind: "toggle",
|
|
6203
|
-
label:
|
|
6609
|
+
label: STATUSLINE_PART_LABELS.ohlc,
|
|
6204
6610
|
id: "ohlc",
|
|
6205
6611
|
get: () => sl.partVisible("ohlc"),
|
|
6206
6612
|
set: (v) => this.setStatuslinePart("ohlc", v)
|
|
6207
6613
|
},
|
|
6208
6614
|
{
|
|
6209
6615
|
kind: "toggle",
|
|
6210
|
-
label:
|
|
6616
|
+
label: STATUSLINE_PART_LABELS.change,
|
|
6211
6617
|
id: "change",
|
|
6212
6618
|
get: () => sl.partVisible("change"),
|
|
6213
6619
|
set: (v) => this.setStatuslinePart("change", v)
|
|
6214
6620
|
},
|
|
6215
|
-
{ kind: "heading", label:
|
|
6621
|
+
{ kind: "heading", label: TEXT14.indicators, id: "indicators" },
|
|
6216
6622
|
{
|
|
6217
6623
|
kind: "toggle",
|
|
6218
|
-
label:
|
|
6624
|
+
label: TEXT14.indicatorTitles,
|
|
6219
6625
|
id: "indicator-titles",
|
|
6220
6626
|
get: () => this.indicatorTitlesOn,
|
|
6221
6627
|
set: (v) => this.setIndicatorTitlesVisible(v)
|
|
6222
6628
|
},
|
|
6223
6629
|
{
|
|
6224
6630
|
kind: "toggle",
|
|
6225
|
-
label:
|
|
6631
|
+
label: TEXT14.indicatorValues,
|
|
6226
6632
|
id: "indicator-values",
|
|
6227
6633
|
get: () => this.indicatorValuesOn,
|
|
6228
6634
|
set: (v) => this.setIndicatorValuesVisible(v)
|
|
@@ -6299,6 +6705,16 @@ var ChartCell = class {
|
|
|
6299
6705
|
if (!this.inner) throw new Error(`[vela] cell "${this.id}" is destroyed`);
|
|
6300
6706
|
return this.inner;
|
|
6301
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
|
+
}
|
|
6302
6718
|
get symbol() {
|
|
6303
6719
|
return this.state.symbol ?? "";
|
|
6304
6720
|
}
|
|
@@ -6487,20 +6903,29 @@ var ChartCell = class {
|
|
|
6487
6903
|
* index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
|
|
6488
6904
|
* read it — indexing the unsorted catalog on add would land on a different study. */
|
|
6489
6905
|
supportedNatives() {
|
|
6490
|
-
|
|
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));
|
|
6491
6909
|
}
|
|
6492
6910
|
/** The picker's library rows: supported natives first (see {@link supportedNatives}),
|
|
6493
6911
|
* then the manifest in the host's own order. */
|
|
6494
6912
|
libraryRows() {
|
|
6495
6913
|
return [
|
|
6496
|
-
...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
|
+
})),
|
|
6497
6922
|
...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
|
|
6498
6923
|
];
|
|
6499
6924
|
}
|
|
6500
6925
|
/** The picker's on-chart rows: native instances first, then live script instances. */
|
|
6501
6926
|
onChartRows() {
|
|
6502
6927
|
return [
|
|
6503
|
-
...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 })),
|
|
6504
6929
|
...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
|
|
6505
6930
|
];
|
|
6506
6931
|
}
|
|
@@ -6870,7 +7295,7 @@ var WorkspaceReplay = class {
|
|
|
6870
7295
|
if (!this.session) return this;
|
|
6871
7296
|
if (intervalMs !== void 0) {
|
|
6872
7297
|
if (!(Number.isFinite(intervalMs) && intervalMs > 0)) {
|
|
6873
|
-
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`);
|
|
6874
7299
|
return this;
|
|
6875
7300
|
}
|
|
6876
7301
|
this.intervalMs = intervalMs;
|
|
@@ -7098,27 +7523,37 @@ function monthSpan(timeframe) {
|
|
|
7098
7523
|
|
|
7099
7524
|
// src/workspace/layouts.ts
|
|
7100
7525
|
var registry = /* @__PURE__ */ new Map();
|
|
7526
|
+
var labels = /* @__PURE__ */ new Map();
|
|
7101
7527
|
function registerLayout(def) {
|
|
7102
|
-
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);
|
|
7103
7530
|
}
|
|
7104
7531
|
function unregisterLayout(id) {
|
|
7105
7532
|
registry.delete(id);
|
|
7533
|
+
labels.delete(id);
|
|
7106
7534
|
}
|
|
7107
|
-
function
|
|
7108
|
-
|
|
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 };
|
|
7109
7540
|
}
|
|
7110
|
-
function
|
|
7111
|
-
|
|
7541
|
+
function layoutDefinition(id, i18n) {
|
|
7542
|
+
const def = registry.get(id);
|
|
7543
|
+
return def && inLanguage(def, i18n);
|
|
7544
|
+
}
|
|
7545
|
+
function layouts(i18n) {
|
|
7546
|
+
return [...registry.values()].map((def) => inLanguage(def, i18n));
|
|
7112
7547
|
}
|
|
7113
7548
|
function slots(n) {
|
|
7114
7549
|
return Array.from({ length: n }, (_, i) => ({ id: `c${i + 1}` }));
|
|
7115
7550
|
}
|
|
7116
7551
|
function registerBuiltinLayouts() {
|
|
7117
|
-
registerLayout({ id: "1", label: "Single", cols: [1], rows: [1], cells: slots(1) });
|
|
7118
|
-
registerLayout({ id: "2h", label: "2 side by side", cols: [1, 1], rows: [1], cells: slots(2) });
|
|
7119
|
-
registerLayout({ id: "2v", label: "2 stacked", cols: [1], rows: [1, 1], cells: slots(2) });
|
|
7120
|
-
registerLayout({ id: "4", label: "4 grid", cols: [1, 1], rows: [1, 1], cells: slots(4) });
|
|
7121
|
-
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) });
|
|
7122
7557
|
}
|
|
7123
7558
|
var GRID_PICKER_MAX = 4;
|
|
7124
7559
|
var GRID_BUILTIN_IDS = {
|
|
@@ -7128,17 +7563,20 @@ var GRID_BUILTIN_IDS = {
|
|
|
7128
7563
|
"2x2": "4",
|
|
7129
7564
|
"2x4": "8"
|
|
7130
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
|
+
}
|
|
7131
7570
|
var clampTrack = (n) => Math.max(1, Math.min(GRID_PICKER_MAX, Math.round(n)));
|
|
7132
|
-
function layoutForGrid(rows, cols) {
|
|
7571
|
+
function layoutForGrid(rows, cols, i18n) {
|
|
7133
7572
|
const r = clampTrack(rows);
|
|
7134
7573
|
const c = clampTrack(cols);
|
|
7135
7574
|
const builtin = GRID_BUILTIN_IDS[`${r}x${c}`];
|
|
7136
7575
|
const registered = builtin ? registry.get(builtin) : void 0;
|
|
7137
|
-
if (registered) return registered;
|
|
7576
|
+
if (registered) return inLanguage(registered, i18n);
|
|
7138
7577
|
return {
|
|
7139
7578
|
id: `g${r}x${c}`,
|
|
7140
|
-
|
|
7141
|
-
label: `${c} \xD7 ${r} grid`,
|
|
7579
|
+
label: textOf(i18n, gridLabel(r, c)),
|
|
7142
7580
|
cols: Array.from({ length: c }, () => 1),
|
|
7143
7581
|
rows: Array.from({ length: r }, () => 1),
|
|
7144
7582
|
cells: slots(r * c)
|
|
@@ -7161,7 +7599,7 @@ function layoutShape(def) {
|
|
|
7161
7599
|
function occupancyGrid(def) {
|
|
7162
7600
|
if (def.areas) return def.areas.map((row) => row.trim().split(/\s+/));
|
|
7163
7601
|
const cols = def.cols.length;
|
|
7164
|
-
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}`));
|
|
7165
7603
|
}
|
|
7166
7604
|
var GLYPH_MIN = 1.5;
|
|
7167
7605
|
var GLYPH_SPAN = 13;
|
|
@@ -7452,8 +7890,8 @@ var TOOL_BINDINGS = [
|
|
|
7452
7890
|
["hline", "drawings.hline-cursor"],
|
|
7453
7891
|
["vline", "drawings.vline-cursor"]
|
|
7454
7892
|
];
|
|
7455
|
-
function toolShortcutHints(keymap) {
|
|
7456
|
-
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]]));
|
|
7457
7895
|
const out = {};
|
|
7458
7896
|
for (const [type, id] of TOOL_BINDINGS) {
|
|
7459
7897
|
const display = byId.get(id);
|
|
@@ -7551,10 +7989,10 @@ var CSS14 = `
|
|
|
7551
7989
|
.vela-mb-item:active { background: var(--vela-hover); }
|
|
7552
7990
|
.vela-mb-item .vela-icon { font-size: 18px; width: 18px; height: 18px; }
|
|
7553
7991
|
/* A lit stop (the maximize toggle while something is isolated): the inverse
|
|
7554
|
-
"selected" chip
|
|
7992
|
+
"selected" chip — white on the dark theme, dark on the light one. */
|
|
7555
7993
|
.vela-mb-item.vela-mb-on, .vela-mb-item.vela-mb-on:active { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
7556
7994
|
/* Left-aligned contributed actions get their own stops (the built-in indicators
|
|
7557
|
-
slot)
|
|
7995
|
+
slot) — the wrapper is layout-transparent so each stop flexes like a sibling. */
|
|
7558
7996
|
.vela-mb-actions { display: contents; }
|
|
7559
7997
|
.vela-mb-symbol {
|
|
7560
7998
|
flex: 1.6 1 0;
|
|
@@ -7568,39 +8006,57 @@ var CSS14 = `
|
|
|
7568
8006
|
text-align: center;
|
|
7569
8007
|
}
|
|
7570
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
|
+
};
|
|
7571
8019
|
var MobileBar = class {
|
|
7572
8020
|
constructor(host, opts) {
|
|
8021
|
+
/** Each fixed stop's accessible name, re-read by {@link relocalize}. */
|
|
8022
|
+
this.labels = [];
|
|
8023
|
+
this.maximizeActive = false;
|
|
7573
8024
|
this.opts = opts;
|
|
7574
8025
|
const doc = host.ownerDocument;
|
|
7575
8026
|
injectStyles(STYLE_ID14, CSS14, doc);
|
|
7576
8027
|
this.el = doc.createElement("div");
|
|
7577
8028
|
this.el.className = "vela-mobilebar";
|
|
8029
|
+
this.timeframe = opts.timeframe;
|
|
7578
8030
|
const item = (cls, label, onClick, icon2) => {
|
|
7579
8031
|
const b = doc.createElement("button");
|
|
7580
8032
|
b.className = `vela-mb-item ${cls}`;
|
|
7581
|
-
b.setAttribute("aria-label", label);
|
|
8033
|
+
b.setAttribute("aria-label", this.t(label));
|
|
8034
|
+
this.labels.push({ el: b, label });
|
|
7582
8035
|
if (icon2) b.appendChild(iconEl(icon2, doc));
|
|
7583
8036
|
b.addEventListener("click", onClick);
|
|
7584
8037
|
return b;
|
|
7585
8038
|
};
|
|
7586
|
-
this.symbolEl = item("vela-mb-symbol",
|
|
8039
|
+
this.symbolEl = item("vela-mb-symbol", TEXT15.symbolSearch, opts.onSymbolClick);
|
|
7587
8040
|
this.symbolEl.textContent = parseSymbol(opts.symbol).ticker;
|
|
7588
|
-
this.tfEl = item("vela-mb-tf",
|
|
7589
|
-
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);
|
|
7590
8043
|
const onIndicators = opts.onIndicatorsClick;
|
|
7591
|
-
const indicators = onIndicators ? item("vela-mb-indicators",
|
|
8044
|
+
const indicators = onIndicators ? item("vela-mb-indicators", TEXT15.indicators, onIndicators, "indicators") : null;
|
|
7592
8045
|
this.actionsHost = doc.createElement("span");
|
|
7593
8046
|
this.actionsHost.className = "vela-mb-actions";
|
|
7594
8047
|
const onDrawings = opts.onDrawingsClick;
|
|
7595
|
-
const drawings = onDrawings ? item("vela-mb-drawings",
|
|
8048
|
+
const drawings = onDrawings ? item("vela-mb-drawings", TEXT15.drawings, onDrawings, "pen") : null;
|
|
7596
8049
|
const onMaximize = opts.onMaximizeClick;
|
|
7597
|
-
this.maxEl = onMaximize ? item("vela-mb-maximize",
|
|
7598
|
-
const more = item("vela-mb-more",
|
|
7599
|
-
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");
|
|
7600
8053
|
this.el.append(this.symbolEl, this.tfEl, ...indicators ? [indicators] : [], this.actionsHost, ...drawings ? [drawings] : [], ...this.maxEl ? [this.maxEl] : [], more, settings);
|
|
7601
8054
|
host.appendChild(this.el);
|
|
7602
8055
|
this.renderActions();
|
|
7603
8056
|
}
|
|
8057
|
+
t(text) {
|
|
8058
|
+
return textOf(this.opts.i18n, text);
|
|
8059
|
+
}
|
|
7604
8060
|
/** Re-project the contributed actions placed on the bar (left-aligned by default —
|
|
7605
8061
|
* see `mobilePlacement`) as icon-only stops in the indicators slot (call after
|
|
7606
8062
|
* registrations change). The rest are three-dots drawer rows. */
|
|
@@ -7613,9 +8069,10 @@ var MobileBar = class {
|
|
|
7613
8069
|
for (const action of widgetActions("topbar", ctx).filter((a) => mobilePlacement(a) === "bar" && !builtin.has(a.id))) {
|
|
7614
8070
|
const b = doc.createElement("button");
|
|
7615
8071
|
b.className = "vela-mb-item";
|
|
7616
|
-
|
|
8072
|
+
const label = textOf(ctx.i18n ?? this.opts.i18n, action.label);
|
|
8073
|
+
b.setAttribute("aria-label", label);
|
|
7617
8074
|
if (action.icon) b.appendChild(iconEl(action.icon, doc));
|
|
7618
|
-
else b.appendChild(doc.createTextNode(
|
|
8075
|
+
else b.appendChild(doc.createTextNode(label));
|
|
7619
8076
|
b.addEventListener("click", () => {
|
|
7620
8077
|
const c = this.opts.getContext?.();
|
|
7621
8078
|
if (c) action.run(c);
|
|
@@ -7627,14 +8084,23 @@ var MobileBar = class {
|
|
|
7627
8084
|
this.symbolEl.textContent = parseSymbol(symbol).ticker;
|
|
7628
8085
|
}
|
|
7629
8086
|
setTimeframe(tf) {
|
|
7630
|
-
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();
|
|
7631
8096
|
}
|
|
7632
8097
|
/** Light the maximize stop while something is isolated (a chart over the grid,
|
|
7633
8098
|
* or a maximized pane inside the active chart) — inverse chip + restore glyph. */
|
|
7634
8099
|
setMaximizeActive(on) {
|
|
7635
8100
|
if (!this.maxEl) return;
|
|
8101
|
+
this.maximizeActive = on;
|
|
7636
8102
|
this.maxEl.classList.toggle("vela-mb-on", on);
|
|
7637
|
-
this.maxEl.setAttribute("aria-label", on ?
|
|
8103
|
+
this.maxEl.setAttribute("aria-label", this.t(on ? TEXT15.restoreLayout : TEXT15.maximizeChart));
|
|
7638
8104
|
this.maxEl.replaceChildren(iconEl(on ? "restore" : "maximize", this.el.ownerDocument));
|
|
7639
8105
|
}
|
|
7640
8106
|
/** Show the maximize stop only while the grid holds more than one chart — on a
|
|
@@ -7700,23 +8166,28 @@ var CSS15 = `
|
|
|
7700
8166
|
background: var(--vela-hover);
|
|
7701
8167
|
}
|
|
7702
8168
|
`;
|
|
8169
|
+
var TEXT16 = {
|
|
8170
|
+
dateRange: msg("widget.timeframeDrawer.dateRange", "Date Range"),
|
|
8171
|
+
timeframes: msg("widget.timeframeDrawer.timeframes", "Timeframes")
|
|
8172
|
+
};
|
|
7703
8173
|
var TimeframeDrawer = class {
|
|
7704
8174
|
constructor(opts) {
|
|
7705
8175
|
this.opts = opts;
|
|
7706
8176
|
this.rangeChips = /* @__PURE__ */ new Map();
|
|
7707
8177
|
this.tfChips = /* @__PURE__ */ new Map();
|
|
8178
|
+
this.headings = [];
|
|
7708
8179
|
const doc = opts.host.ownerDocument;
|
|
7709
8180
|
injectStyles(STYLE_ID15, CSS15, doc);
|
|
7710
8181
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
7711
8182
|
const rangeHeading = doc.createElement("div");
|
|
7712
8183
|
rangeHeading.className = "vela-tfd-heading";
|
|
7713
|
-
|
|
8184
|
+
this.headings.push({ el: rangeHeading, text: TEXT16.dateRange });
|
|
7714
8185
|
const ranges = doc.createElement("div");
|
|
7715
8186
|
ranges.className = "vela-tfd-ranges";
|
|
7716
8187
|
for (const preset of opts.ranges) {
|
|
7717
8188
|
const chip = doc.createElement("button");
|
|
7718
8189
|
chip.className = "vela-tfd-chip";
|
|
7719
|
-
chip.textContent = preset.id;
|
|
8190
|
+
chip.textContent = rangeLabel(preset.id, opts.i18n);
|
|
7720
8191
|
chip.addEventListener("click", () => {
|
|
7721
8192
|
opts.onRange(preset);
|
|
7722
8193
|
this.drawer.hide();
|
|
@@ -7726,13 +8197,13 @@ var TimeframeDrawer = class {
|
|
|
7726
8197
|
}
|
|
7727
8198
|
const tfHeading = doc.createElement("div");
|
|
7728
8199
|
tfHeading.className = "vela-tfd-heading";
|
|
7729
|
-
tfHeading.
|
|
8200
|
+
this.headings.push({ el: tfHeading, text: TEXT16.timeframes });
|
|
7730
8201
|
const grid = doc.createElement("div");
|
|
7731
8202
|
grid.className = "vela-tfd-grid";
|
|
7732
8203
|
for (const tf of opts.timeframes) {
|
|
7733
8204
|
const chip = doc.createElement("button");
|
|
7734
8205
|
chip.className = "vela-tfd-chip";
|
|
7735
|
-
chip.textContent = timeframeLabel(tf);
|
|
8206
|
+
chip.textContent = timeframeLabel(tf, opts.i18n);
|
|
7736
8207
|
chip.addEventListener("click", () => {
|
|
7737
8208
|
opts.onTimeframe(tf);
|
|
7738
8209
|
this.drawer.hide();
|
|
@@ -7741,8 +8212,17 @@ var TimeframeDrawer = class {
|
|
|
7741
8212
|
grid.appendChild(chip);
|
|
7742
8213
|
}
|
|
7743
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);
|
|
7744
8223
|
}
|
|
7745
8224
|
open() {
|
|
8225
|
+
this.relabel();
|
|
7746
8226
|
const tf = this.opts.currentTimeframe();
|
|
7747
8227
|
const range = this.opts.activeRange();
|
|
7748
8228
|
for (const [id, chip] of this.rangeChips) {
|
|
@@ -7764,6 +8244,12 @@ var TimeframeDrawer = class {
|
|
|
7764
8244
|
};
|
|
7765
8245
|
|
|
7766
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
|
+
};
|
|
7767
8253
|
var STYLE_ID16 = "vela-widget-drawings-drawer";
|
|
7768
8254
|
var CSS16 = `
|
|
7769
8255
|
/* Search + tabs stay pinned while the tool list scrolls underneath. */
|
|
@@ -7797,7 +8283,7 @@ var CSS16 = `
|
|
|
7797
8283
|
gap: 4px;
|
|
7798
8284
|
overflow-x: auto;
|
|
7799
8285
|
scrollbar-width: none;
|
|
7800
|
-
/* The strip scrolls sideways itself
|
|
8286
|
+
/* The strip scrolls sideways itself — its touches are native scroll, not tab swipes
|
|
7801
8287
|
(the drawer body is pan-y so everywhere ELSE a sideways move swipes the tabs). */
|
|
7802
8288
|
touch-action: pan-x;
|
|
7803
8289
|
padding: 0 2px 8px;
|
|
@@ -7870,7 +8356,7 @@ var DrawingsDrawer = class {
|
|
|
7870
8356
|
injectStyles(STYLE_ID16, CSS16, doc);
|
|
7871
8357
|
this.drawer = new Drawer({
|
|
7872
8358
|
host: opts.host,
|
|
7873
|
-
title:
|
|
8359
|
+
title: this.t(TEXT17.title),
|
|
7874
8360
|
onOpenChange: opts.onOpenChange,
|
|
7875
8361
|
// Swiping across the tool list pages through the group tabs.
|
|
7876
8362
|
onSwipe: (dir) => this.stepGroup(dir === "left" ? 1 : -1)
|
|
@@ -7882,7 +8368,7 @@ var DrawingsDrawer = class {
|
|
|
7882
8368
|
search.appendChild(iconEl("search", doc));
|
|
7883
8369
|
this.input = doc.createElement("input");
|
|
7884
8370
|
this.input.type = "text";
|
|
7885
|
-
this.input.placeholder =
|
|
8371
|
+
this.input.placeholder = this.t(TEXT17.search);
|
|
7886
8372
|
this.input.addEventListener("input", () => this.renderList());
|
|
7887
8373
|
search.appendChild(this.input);
|
|
7888
8374
|
this.tabs = doc.createElement("div");
|
|
@@ -7892,7 +8378,12 @@ var DrawingsDrawer = class {
|
|
|
7892
8378
|
this.list.className = "vela-dd-list";
|
|
7893
8379
|
this.drawer.body.append(sticky, this.list);
|
|
7894
8380
|
}
|
|
8381
|
+
t(m, values) {
|
|
8382
|
+
return textOf(this.opts.i18n?.() ?? null, m, values);
|
|
8383
|
+
}
|
|
7895
8384
|
open() {
|
|
8385
|
+
this.drawer.setTitle(this.t(TEXT17.title));
|
|
8386
|
+
this.input.placeholder = this.t(TEXT17.search);
|
|
7896
8387
|
this.definition = this.opts.toolbar();
|
|
7897
8388
|
if (!this.definition.groups.some((g) => g.id === this.activeGroup)) this.activeGroup = this.definition.groups[0]?.id ?? "";
|
|
7898
8389
|
this.input.value = "";
|
|
@@ -7924,7 +8415,7 @@ var DrawingsDrawer = class {
|
|
|
7924
8415
|
for (const group of this.definition.groups) {
|
|
7925
8416
|
const tab = doc.createElement("button");
|
|
7926
8417
|
tab.className = "vela-dd-tab";
|
|
7927
|
-
tab.textContent = group.label;
|
|
8418
|
+
tab.textContent = this.t(group.label);
|
|
7928
8419
|
if (group.id === this.activeGroup) tab.dataset.active = "1";
|
|
7929
8420
|
tab.addEventListener("click", () => {
|
|
7930
8421
|
this.activeGroup = group.id;
|
|
@@ -7938,7 +8429,15 @@ var DrawingsDrawer = class {
|
|
|
7938
8429
|
renderList() {
|
|
7939
8430
|
const doc = this.list.ownerDocument;
|
|
7940
8431
|
this.list.replaceChildren();
|
|
7941
|
-
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());
|
|
7942
8441
|
const active = this.opts.currentTool();
|
|
7943
8442
|
const section = (label) => {
|
|
7944
8443
|
const el = doc.createElement("div");
|
|
@@ -7955,7 +8454,7 @@ var DrawingsDrawer = class {
|
|
|
7955
8454
|
glyph.innerHTML = tool.icon;
|
|
7956
8455
|
const label = doc.createElement("span");
|
|
7957
8456
|
label.className = "vela-dd-label";
|
|
7958
|
-
label.textContent = tool.label;
|
|
8457
|
+
label.textContent = this.t(tool.label);
|
|
7959
8458
|
const star = doc.createElement("button");
|
|
7960
8459
|
star.className = "vela-dd-star";
|
|
7961
8460
|
const paintStar = (on) => {
|
|
@@ -7964,7 +8463,7 @@ var DrawingsDrawer = class {
|
|
|
7964
8463
|
else delete star.dataset.on;
|
|
7965
8464
|
};
|
|
7966
8465
|
paintStar(this.opts.isFavorite(tool.type));
|
|
7967
|
-
star.setAttribute("aria-label",
|
|
8466
|
+
star.setAttribute("aria-label", this.t(TEXT17.favorite, { tool: this.t(tool.label) }));
|
|
7968
8467
|
star.addEventListener("click", (e) => {
|
|
7969
8468
|
e.stopPropagation();
|
|
7970
8469
|
const on = !this.opts.isFavorite(tool.type);
|
|
@@ -7981,16 +8480,16 @@ var DrawingsDrawer = class {
|
|
|
7981
8480
|
if (query) {
|
|
7982
8481
|
let any = false;
|
|
7983
8482
|
for (const group2 of this.definition.groups) {
|
|
7984
|
-
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));
|
|
7985
8484
|
if (hits.length === 0) continue;
|
|
7986
8485
|
any = true;
|
|
7987
|
-
section(group2.label);
|
|
8486
|
+
section(this.t(group2.label));
|
|
7988
8487
|
for (const tool of hits) row(tool);
|
|
7989
8488
|
}
|
|
7990
8489
|
if (!any) {
|
|
7991
8490
|
const empty = doc.createElement("div");
|
|
7992
8491
|
empty.className = "vela-dd-empty";
|
|
7993
|
-
empty.textContent =
|
|
8492
|
+
empty.textContent = this.t(TEXT17.noMatch);
|
|
7994
8493
|
this.list.appendChild(empty);
|
|
7995
8494
|
}
|
|
7996
8495
|
return;
|
|
@@ -7999,7 +8498,7 @@ var DrawingsDrawer = class {
|
|
|
7999
8498
|
if (!group) return;
|
|
8000
8499
|
if (group.sections && group.sections.length > 0) {
|
|
8001
8500
|
for (const s of group.sections) {
|
|
8002
|
-
section(s.label);
|
|
8501
|
+
section(this.t(s.label));
|
|
8003
8502
|
for (const tool of s.tools) row(tool);
|
|
8004
8503
|
}
|
|
8005
8504
|
} else {
|
|
@@ -8009,6 +8508,17 @@ var DrawingsDrawer = class {
|
|
|
8009
8508
|
};
|
|
8010
8509
|
|
|
8011
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
|
+
};
|
|
8012
8522
|
var STYLE_ID17 = "vela-widget-more-drawer";
|
|
8013
8523
|
var CSS17 = `
|
|
8014
8524
|
.vela-md-actions {
|
|
@@ -8099,6 +8609,9 @@ var MoreDrawer = class {
|
|
|
8099
8609
|
destroy() {
|
|
8100
8610
|
this.drawer.destroy();
|
|
8101
8611
|
}
|
|
8612
|
+
t(text, values) {
|
|
8613
|
+
return textOf(this.opts.i18n, text, values);
|
|
8614
|
+
}
|
|
8102
8615
|
show(view) {
|
|
8103
8616
|
this.view = view;
|
|
8104
8617
|
this.render();
|
|
@@ -8110,7 +8623,7 @@ var MoreDrawer = class {
|
|
|
8110
8623
|
const back = doc.createElement("button");
|
|
8111
8624
|
back.className = "vela-md-back";
|
|
8112
8625
|
back.appendChild(iconEl("chevron-left", doc));
|
|
8113
|
-
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)));
|
|
8114
8627
|
back.addEventListener("click", () => this.show("main"));
|
|
8115
8628
|
this.drawer.body.appendChild(back);
|
|
8116
8629
|
}
|
|
@@ -8150,25 +8663,25 @@ var MoreDrawer = class {
|
|
|
8150
8663
|
b.className = "vela-md-action";
|
|
8151
8664
|
b.disabled = !enabled;
|
|
8152
8665
|
b.appendChild(iconEl(icon2, doc));
|
|
8153
|
-
b.appendChild(doc.createTextNode(label));
|
|
8666
|
+
b.appendChild(doc.createTextNode(this.t(label)));
|
|
8154
8667
|
b.addEventListener("click", () => {
|
|
8155
8668
|
run();
|
|
8156
8669
|
this.drawer.hide();
|
|
8157
8670
|
});
|
|
8158
8671
|
actions.appendChild(b);
|
|
8159
8672
|
};
|
|
8160
|
-
if (this.opts.onUndo) action("undo",
|
|
8161
|
-
if (this.opts.onRedo) action("redo",
|
|
8162
|
-
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);
|
|
8163
8676
|
if (actions.childElementCount > 0) this.drawer.body.appendChild(actions);
|
|
8164
8677
|
const list = doc.createElement("div");
|
|
8165
8678
|
list.className = "vela-md-list";
|
|
8166
8679
|
const current = this.opts.priceStyles().find((s) => s.id === this.opts.priceStyle());
|
|
8167
|
-
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") }));
|
|
8168
8681
|
if (this.opts.layout) {
|
|
8169
8682
|
const shape = this.opts.layout.shape();
|
|
8170
|
-
const value = shape ?
|
|
8171
|
-
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") }));
|
|
8172
8685
|
}
|
|
8173
8686
|
for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
|
|
8174
8687
|
for (const panel of this.opts.panels()) {
|
|
@@ -8184,7 +8697,7 @@ var MoreDrawer = class {
|
|
|
8184
8697
|
}
|
|
8185
8698
|
if (this.opts.alerts) {
|
|
8186
8699
|
const alertCount = this.opts.alerts().length;
|
|
8187
|
-
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") }));
|
|
8188
8701
|
}
|
|
8189
8702
|
for (const act of this.opts.actions()) list.appendChild(this.actionRow(doc, act));
|
|
8190
8703
|
this.drawer.body.appendChild(list);
|
|
@@ -8224,7 +8737,7 @@ var MoreDrawer = class {
|
|
|
8224
8737
|
list.className = "vela-md-list";
|
|
8225
8738
|
const wrap = doc.createElement("div");
|
|
8226
8739
|
wrap.className = "vela-md-gridwrap";
|
|
8227
|
-
const { el: grid, squares } = layoutGridCanvas(doc);
|
|
8740
|
+
const { el: grid, squares } = layoutGridCanvas(doc, this.opts.i18n);
|
|
8228
8741
|
paintLayoutGrid(squares, layout.shape());
|
|
8229
8742
|
grid.addEventListener("click", (e) => {
|
|
8230
8743
|
const sq = e.target?.closest?.(".vela-lp-sq");
|
|
@@ -8249,7 +8762,7 @@ var MoreDrawer = class {
|
|
|
8249
8762
|
if (syncs.length > 0) {
|
|
8250
8763
|
const heading = doc.createElement("div");
|
|
8251
8764
|
heading.className = "vela-md-section";
|
|
8252
|
-
heading.textContent =
|
|
8765
|
+
heading.textContent = this.t(TEXT18.sync);
|
|
8253
8766
|
list.appendChild(heading);
|
|
8254
8767
|
for (const s of syncs) {
|
|
8255
8768
|
list.appendChild(
|
|
@@ -8270,20 +8783,23 @@ var MoreDrawer = class {
|
|
|
8270
8783
|
if (alerts.length === 0) {
|
|
8271
8784
|
const empty = doc.createElement("div");
|
|
8272
8785
|
empty.className = "vela-md-empty";
|
|
8273
|
-
empty.textContent =
|
|
8786
|
+
empty.textContent = this.t(TEXT18.noAlerts);
|
|
8274
8787
|
this.drawer.body.appendChild(empty);
|
|
8275
8788
|
return;
|
|
8276
8789
|
}
|
|
8277
8790
|
const list = doc.createElement("div");
|
|
8278
8791
|
list.className = "vela-md-list";
|
|
8279
8792
|
for (const a of alerts) {
|
|
8280
|
-
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 }));
|
|
8281
8794
|
}
|
|
8282
8795
|
this.drawer.body.appendChild(list);
|
|
8283
8796
|
}
|
|
8284
8797
|
};
|
|
8285
8798
|
|
|
8286
8799
|
// src/widget/timezone-drawer.ts
|
|
8800
|
+
var TEXT19 = {
|
|
8801
|
+
title: msg("widget.timezoneDrawer.title", "Time zone")
|
|
8802
|
+
};
|
|
8287
8803
|
var STYLE_ID18 = "vela-widget-timezone-drawer";
|
|
8288
8804
|
var CSS18 = `
|
|
8289
8805
|
.vela-tzd-list { padding: 2px 0 4px; }
|
|
@@ -8305,9 +8821,10 @@ var TimezoneDrawer = class {
|
|
|
8305
8821
|
constructor(opts) {
|
|
8306
8822
|
this.opts = opts;
|
|
8307
8823
|
injectStyles(STYLE_ID18, CSS18, opts.host.ownerDocument);
|
|
8308
|
-
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 });
|
|
8309
8825
|
}
|
|
8310
8826
|
open() {
|
|
8827
|
+
this.drawer.setTitle(textOf(this.opts.i18n, TEXT19.title));
|
|
8311
8828
|
this.render();
|
|
8312
8829
|
this.drawer.show();
|
|
8313
8830
|
}
|
|
@@ -8322,7 +8839,7 @@ var TimezoneDrawer = class {
|
|
|
8322
8839
|
this.drawer.body.replaceChildren();
|
|
8323
8840
|
const list = doc.createElement("div");
|
|
8324
8841
|
list.className = "vela-tzd-list";
|
|
8325
|
-
for (const tz of timezoneMenuRows(this.opts.timezone())) {
|
|
8842
|
+
for (const tz of timezoneMenuRows(this.opts.timezone(), this.opts.i18n)) {
|
|
8326
8843
|
const row = doc.createElement("div");
|
|
8327
8844
|
row.className = "vela-tzd-row";
|
|
8328
8845
|
const label = doc.createElement("span");
|
|
@@ -8367,17 +8884,21 @@ var CSS19 = `
|
|
|
8367
8884
|
color: var(--vela-fg-muted);
|
|
8368
8885
|
}
|
|
8369
8886
|
`;
|
|
8887
|
+
var TEXT20 = {
|
|
8888
|
+
title: msg("widget.priceScaleDrawer.title", "Price scale")
|
|
8889
|
+
};
|
|
8370
8890
|
var PriceScaleDrawer = class {
|
|
8371
8891
|
constructor(opts) {
|
|
8372
8892
|
this.opts = opts;
|
|
8373
8893
|
this.pane = null;
|
|
8374
8894
|
injectStyles(STYLE_ID19, CSS19, opts.host.ownerDocument);
|
|
8375
|
-
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 });
|
|
8376
8896
|
}
|
|
8377
8897
|
open() {
|
|
8378
8898
|
const chart = this.opts.chart();
|
|
8379
8899
|
const panes = chart?.renderer.get("paneScales");
|
|
8380
8900
|
this.pane = Array.isArray(panes) ? paneScaleAt(panes, this.opts.pressY()) : null;
|
|
8901
|
+
this.drawer.setTitle(textOf(chart?.i18n, TEXT20.title));
|
|
8381
8902
|
this.render();
|
|
8382
8903
|
this.drawer.show();
|
|
8383
8904
|
}
|
|
@@ -8404,7 +8925,7 @@ var PriceScaleDrawer = class {
|
|
|
8404
8925
|
priceLabel: this.flag("priceLabel"),
|
|
8405
8926
|
countdown: this.flag("countdown"),
|
|
8406
8927
|
priceLine: this.flag("currentPriceLine")
|
|
8407
|
-
});
|
|
8928
|
+
}, chart.i18n);
|
|
8408
8929
|
const list = doc.createElement("div");
|
|
8409
8930
|
list.className = "vela-psd-list";
|
|
8410
8931
|
const row = (label, opts) => {
|
|
@@ -8427,7 +8948,7 @@ var PriceScaleDrawer = class {
|
|
|
8427
8948
|
};
|
|
8428
8949
|
for (const item of items) {
|
|
8429
8950
|
if (item.id === "labels" && item.submenu) {
|
|
8430
|
-
section(
|
|
8951
|
+
section(item.label);
|
|
8431
8952
|
for (const sub of item.submenu) {
|
|
8432
8953
|
row(sub.label, {
|
|
8433
8954
|
checked: sub.checked,
|
|
@@ -8441,7 +8962,7 @@ var PriceScaleDrawer = class {
|
|
|
8441
8962
|
continue;
|
|
8442
8963
|
}
|
|
8443
8964
|
if (item.id === "levels" && item.submenu) {
|
|
8444
|
-
section(
|
|
8965
|
+
section(item.label);
|
|
8445
8966
|
for (const sub of item.submenu) {
|
|
8446
8967
|
row(sub.label, {
|
|
8447
8968
|
checked: sub.checked,
|
|
@@ -8507,6 +9028,12 @@ var PriceScaleDrawer = class {
|
|
|
8507
9028
|
};
|
|
8508
9029
|
|
|
8509
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
|
+
};
|
|
8510
9037
|
var STYLE_ID20 = "vela-widget-drawing-pill";
|
|
8511
9038
|
var CSS20 = `
|
|
8512
9039
|
.vela-drawpill { display: none; }
|
|
@@ -8556,17 +9083,24 @@ var CSS20 = `
|
|
|
8556
9083
|
.vela-drawpill-btn .vela-icon { font-size: 17px; width: 17px; height: 17px; }
|
|
8557
9084
|
.vela-drawpill-badge {
|
|
8558
9085
|
position: absolute;
|
|
8559
|
-
top:
|
|
8560
|
-
right:
|
|
8561
|
-
|
|
8562
|
-
|
|
9086
|
+
top: 5px;
|
|
9087
|
+
right: 5px;
|
|
9088
|
+
display: none;
|
|
9089
|
+
align-items: flex-end;
|
|
9090
|
+
gap: 1px;
|
|
8563
9091
|
color: var(--vela-accent);
|
|
8564
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; }
|
|
8565
9097
|
`;
|
|
8566
9098
|
var DrawingPill = class {
|
|
8567
9099
|
constructor(host) {
|
|
8568
9100
|
this.chart = null;
|
|
8569
9101
|
this.chartSubs = [];
|
|
9102
|
+
/** Each button's accessible name, re-read in the chart's language on every sync. */
|
|
9103
|
+
this.labels = [];
|
|
8570
9104
|
const doc = host.ownerDocument;
|
|
8571
9105
|
injectStyles(STYLE_ID20, CSS20, doc);
|
|
8572
9106
|
this.el = doc.createElement("div");
|
|
@@ -8577,32 +9111,38 @@ var DrawingPill = class {
|
|
|
8577
9111
|
const btn = (icon2, label, onClick) => {
|
|
8578
9112
|
const b = doc.createElement("button");
|
|
8579
9113
|
b.className = "vela-drawpill-btn";
|
|
8580
|
-
b.setAttribute("aria-label", label);
|
|
9114
|
+
b.setAttribute("aria-label", this.t(label));
|
|
9115
|
+
this.labels.push({ el: b, label });
|
|
8581
9116
|
b.appendChild(iconEl(icon2, doc));
|
|
8582
9117
|
b.addEventListener("click", onClick);
|
|
8583
9118
|
return b;
|
|
8584
9119
|
};
|
|
8585
|
-
this.magnetBtn = btn("magnet",
|
|
9120
|
+
this.magnetBtn = btn("magnet", TEXT21.magnet, () => {
|
|
8586
9121
|
const order = ["off", "weak", "strong"];
|
|
8587
9122
|
const cur = this.chart?.drawings.getSnapMode() ?? "off";
|
|
8588
9123
|
this.chart?.drawings.setSnapMode(order[(order.indexOf(cur) + 1) % order.length]);
|
|
8589
9124
|
});
|
|
8590
9125
|
this.magnetBadge = doc.createElement("span");
|
|
8591
9126
|
this.magnetBadge.className = "vela-drawpill-badge";
|
|
9127
|
+
this.magnetBadge.setAttribute("aria-hidden", "true");
|
|
9128
|
+
this.magnetBadge.append(doc.createElement("i"), doc.createElement("i"));
|
|
8592
9129
|
this.magnetBtn.appendChild(this.magnetBadge);
|
|
8593
|
-
this.stayBtn = btn(iconMarkup("pen-lock") ? "pen-lock" : "pen",
|
|
9130
|
+
this.stayBtn = btn(iconMarkup("pen-lock") ? "pen-lock" : "pen", TEXT21.stay, () => {
|
|
8594
9131
|
this.chart?.drawings.setStayMode(!this.chart.drawings.getStayMode());
|
|
8595
9132
|
});
|
|
8596
|
-
this.eraserBtn = btn("eraser",
|
|
9133
|
+
this.eraserBtn = btn("eraser", TEXT21.eraser, () => {
|
|
8597
9134
|
this.chart?.drawings.setMode(this.chart.drawings.getMode() === "eraser" ? null : "eraser");
|
|
8598
9135
|
});
|
|
8599
|
-
const close = btn("close",
|
|
9136
|
+
const close = btn("close", TEXT21.exit, () => {
|
|
8600
9137
|
this.chart?.drawings.setTool(null);
|
|
8601
9138
|
this.chart?.drawings.setMode(null);
|
|
8602
9139
|
});
|
|
8603
9140
|
this.el.append(this.toolGlyph, this.magnetBtn, this.stayBtn, this.eraserBtn, close);
|
|
8604
9141
|
host.appendChild(this.el);
|
|
8605
9142
|
}
|
|
9143
|
+
t(text) {
|
|
9144
|
+
return textOf(this.chart?.i18n, text);
|
|
9145
|
+
}
|
|
8606
9146
|
/** Rebind to another chart (a rebuild, or the workspace's active cell changing) —
|
|
8607
9147
|
* the previous chart's subscriptions are dropped so only one chart drives the pill. */
|
|
8608
9148
|
onChart(chart) {
|
|
@@ -8616,7 +9156,12 @@ var DrawingPill = class {
|
|
|
8616
9156
|
];
|
|
8617
9157
|
this.sync();
|
|
8618
9158
|
}
|
|
9159
|
+
/** Re-read the labels in the chart's language. */
|
|
9160
|
+
relocalize() {
|
|
9161
|
+
this.sync();
|
|
9162
|
+
}
|
|
8619
9163
|
sync() {
|
|
9164
|
+
for (const { el, label } of this.labels) el.setAttribute("aria-label", this.t(label));
|
|
8620
9165
|
const drawings = this.chart?.drawings;
|
|
8621
9166
|
if (!drawings || !drawings.supported) {
|
|
8622
9167
|
this.el.hidden = true;
|
|
@@ -8629,7 +9174,8 @@ var DrawingPill = class {
|
|
|
8629
9174
|
this.toolGlyph.innerHTML = tool !== null ? getDrawingType(tool)?.icon ?? "" : iconMarkup("eraser") ?? "";
|
|
8630
9175
|
const snap = drawings.getSnapMode();
|
|
8631
9176
|
this.magnetBtn.dataset.on = snap !== "off" ? "1" : "";
|
|
8632
|
-
|
|
9177
|
+
if (snap === "off") delete this.magnetBadge.dataset.strength;
|
|
9178
|
+
else this.magnetBadge.dataset.strength = snap;
|
|
8633
9179
|
this.stayBtn.dataset.on = drawings.getStayMode() ? "1" : "";
|
|
8634
9180
|
this.eraserBtn.dataset.on = eraser ? "1" : "";
|
|
8635
9181
|
}
|
|
@@ -8664,6 +9210,7 @@ function buildContext(host) {
|
|
|
8664
9210
|
togglePanel: (id, open) => host.togglePanel(id, open),
|
|
8665
9211
|
dockStrip: (el) => host.dockStrip(el),
|
|
8666
9212
|
host: host.root,
|
|
9213
|
+
i18n: host.i18n,
|
|
8667
9214
|
toast: (message, kind) => host.toast(message, kind),
|
|
8668
9215
|
addIndicator: (entry) => host.active()?.addExternalIndicator(entry),
|
|
8669
9216
|
addNativeIndicator: (type) => host.active()?.addNative(type),
|
|
@@ -8743,7 +9290,7 @@ var CSS21 = `
|
|
|
8743
9290
|
.vela-ws-grid { position: relative; flex: 1 1 auto; min-width: 0; display: grid; gap: ${GAP_PX}px; background: var(--vela-border-soft); }
|
|
8744
9291
|
.vela-cell { background: var(--vela-bg); position: relative; }
|
|
8745
9292
|
/* Active-cell highlight: an overlay ring ABOVE the chart's own canvas stack (a plain
|
|
8746
|
-
outline on the cell is painted under them)
|
|
9293
|
+
outline on the cell is painted under them) — inert to the pointer. Scoped to
|
|
8747
9294
|
multi-cell grids ([data-multi]): a single-cell layout always has an active cell,
|
|
8748
9295
|
and ringing the only chart would just be noise. */
|
|
8749
9296
|
.vela-ws-grid[data-multi='1'] .vela-cell[data-active='1']::after {
|
|
@@ -8760,11 +9307,11 @@ var CSS21 = `
|
|
|
8760
9307
|
.vela-ws-splitter:hover::after { content: ''; position: absolute; background: var(--vela-separator-hover-line); }
|
|
8761
9308
|
.vela-ws-splitter[data-axis='cols']:hover::after { left: calc(50% - 1px); top: 0; width: 2px; height: 100%; }
|
|
8762
9309
|
.vela-ws-splitter[data-axis='rows']:hover::after { top: calc(50% - 1px); left: 0; height: 2px; width: 100%; }
|
|
8763
|
-
/* 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
|
|
8764
9311
|
drawings drawer + on-chart pill replace it (same policy as the widget's in-chart bar). */
|
|
8765
9312
|
[data-layout='mobile'] .vela-ws-toolbar { display: none; }
|
|
8766
9313
|
/* A maximized cell owns the whole grid: the splitter strips have no seams to grab and
|
|
8767
|
-
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. */
|
|
8768
9315
|
.vela-ws-grid[data-maximized='1'] .vela-ws-splitter { display: none; }
|
|
8769
9316
|
.vela-ws-grid[data-maximized='1'] .vela-cell[data-active='1']::after { display: none; }
|
|
8770
9317
|
/* Drop-target preview while a cell's drag handle is held: a dashed ring + the same
|
|
@@ -8784,24 +9331,58 @@ function declaredOrder(cells) {
|
|
|
8784
9331
|
const names = Object.keys(cells ?? {});
|
|
8785
9332
|
for (const n of names) {
|
|
8786
9333
|
if (/^\d+$/.test(n)) {
|
|
8787
|
-
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}"`);
|
|
8788
9335
|
}
|
|
8789
9336
|
}
|
|
8790
9337
|
return names.filter((n) => !/^\d+$/.test(n));
|
|
8791
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;
|
|
8792
9372
|
function nextAutoCellId(taken) {
|
|
8793
9373
|
for (let i = 1; ; i += 1) {
|
|
8794
9374
|
if (!taken.has(`c${i}`)) return `c${i}`;
|
|
8795
9375
|
}
|
|
8796
9376
|
}
|
|
8797
9377
|
function resolveTyping(key, open) {
|
|
9378
|
+
const printable = [...key].length === 1;
|
|
8798
9379
|
if (open.dialogs > 0) {
|
|
8799
|
-
if (
|
|
9380
|
+
if (!printable) return null;
|
|
8800
9381
|
if (open.symbolSearch) return { target: "symbol", text: key.toUpperCase() };
|
|
8801
9382
|
if (open.timeframeEntry) return { target: "timeframe", text: key.toUpperCase() };
|
|
8802
9383
|
return null;
|
|
8803
9384
|
}
|
|
8804
|
-
if (/^[
|
|
9385
|
+
if (printable && /^[\p{L}0]$/u.test(key)) return { target: "symbol", text: key.toUpperCase() };
|
|
8805
9386
|
if (/^[1-9]$/.test(key)) return { target: "timeframe", text: key };
|
|
8806
9387
|
return null;
|
|
8807
9388
|
}
|
|
@@ -8917,6 +9498,7 @@ var VelaWorkspace = class {
|
|
|
8917
9498
|
const hostEl = typeof container === "string" ? document.querySelector(container) : container;
|
|
8918
9499
|
if (!hostEl) throw new Error(`VelaWorkspace: container not found: ${String(container)}`);
|
|
8919
9500
|
this.opts = opts;
|
|
9501
|
+
this.localeState = new LocaleState(pickLocalization(opts));
|
|
8920
9502
|
this.replay = new WorkspaceReplay({
|
|
8921
9503
|
cells: () => this.cells(),
|
|
8922
9504
|
activeId: () => this.activeId,
|
|
@@ -8967,12 +9549,14 @@ var VelaWorkspace = class {
|
|
|
8967
9549
|
this.root = doc.createElement("div");
|
|
8968
9550
|
this.root.className = "vela-workspace";
|
|
8969
9551
|
ensureUIHost(this.root, resolveTheme(opts.theme));
|
|
9552
|
+
this.applyRootAttributes();
|
|
8970
9553
|
for (const [name, make] of Object.entries(opts.providers ?? {})) void this.feed.registerProvider(name, make());
|
|
8971
9554
|
void this.feed.ready().then(() => {
|
|
8972
9555
|
if (!this.destroyed) this.refreshRetention();
|
|
8973
9556
|
});
|
|
8974
9557
|
this.symbolPicker = new SymbolPicker({
|
|
8975
9558
|
host: this.root,
|
|
9559
|
+
i18n: this.localeState,
|
|
8976
9560
|
// Row icons come from each descriptor's OWNING provider (resolveSymbolIcon).
|
|
8977
9561
|
iconFor: (d) => this.feed.symbolIconOf(d),
|
|
8978
9562
|
onSelect: (ticker) => this.active.setSymbol(ticker),
|
|
@@ -8984,6 +9568,7 @@ var VelaWorkspace = class {
|
|
|
8984
9568
|
this.symbolPicker.setSource(() => this.feed.symbols());
|
|
8985
9569
|
this.indicatorPicker = opts.indicatorPicker !== false && !this.indicatorsOverride && topbarHas(this.topbarComp, "indicators") ? new IndicatorPicker({
|
|
8986
9570
|
host: this.root,
|
|
9571
|
+
i18n: this.localeState,
|
|
8987
9572
|
library: () => this.active.libraryRows(),
|
|
8988
9573
|
onChart: () => this.active.onChartRows(),
|
|
8989
9574
|
onAdd: (i) => this.active.addFromLibrary(i),
|
|
@@ -8996,11 +9581,13 @@ var VelaWorkspace = class {
|
|
|
8996
9581
|
const picker = this.indicatorPicker;
|
|
8997
9582
|
this.tfQuick = new TimeframeQuick({
|
|
8998
9583
|
host: this.root,
|
|
9584
|
+
i18n: this.localeState,
|
|
8999
9585
|
onApply: (tf) => this.setActiveTimeframe(tf),
|
|
9000
9586
|
onOpenChange: (open) => this.trackDialog(open)
|
|
9001
9587
|
});
|
|
9002
9588
|
this.topbar = new Topbar(this.root, {
|
|
9003
9589
|
symbol: "",
|
|
9590
|
+
i18n: this.localeState,
|
|
9004
9591
|
// RAW option, not the resolved lists — the bar distinguishes a host-declared
|
|
9005
9592
|
// side (list is law) from a default one (an override's `order` may flow).
|
|
9006
9593
|
composition: opts.topbar,
|
|
@@ -9024,18 +9611,13 @@ var VelaWorkspace = class {
|
|
|
9024
9611
|
// The picker composes dynamic layouts on its grid canvas; registered
|
|
9025
9612
|
// presets the canvas cannot express (bespoke plugin areas) list as rows.
|
|
9026
9613
|
shape: () => layoutShape(this.def),
|
|
9027
|
-
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 })),
|
|
9028
9615
|
onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
|
|
9029
9616
|
onSelectPreset: (id) => this.setLayout(id),
|
|
9030
9617
|
// The SYNC switches reflect the simple all-cells form; flipping one
|
|
9031
9618
|
// OVERRIDES a host-set group record with plain on/off (groups stay an
|
|
9032
9619
|
// API-only shape).
|
|
9033
|
-
syncs: () =>
|
|
9034
|
-
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
9035
|
-
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
9036
|
-
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true },
|
|
9037
|
-
{ id: "style", label: "Style", checked: this.syncOpts.style === true }
|
|
9038
|
-
],
|
|
9620
|
+
syncs: () => this.syncRows(),
|
|
9039
9621
|
onToggleSync: (id) => {
|
|
9040
9622
|
const kind = id;
|
|
9041
9623
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
@@ -9046,7 +9628,7 @@ var VelaWorkspace = class {
|
|
|
9046
9628
|
});
|
|
9047
9629
|
const main = doc.createElement("div");
|
|
9048
9630
|
main.className = "vela-ws-main";
|
|
9049
|
-
const toolbar = buildToolbar(opts.drawings);
|
|
9631
|
+
const toolbar = buildToolbar(opts.drawings, this.localeState);
|
|
9050
9632
|
this.drawingsEnabled = opts.drawings !== false;
|
|
9051
9633
|
this.toolbarDef = toolbar.definition;
|
|
9052
9634
|
let toolbarHost = null;
|
|
@@ -9061,12 +9643,13 @@ var VelaWorkspace = class {
|
|
|
9061
9643
|
this.dock = new PanelDock(main, {
|
|
9062
9644
|
chrome: this.topbar,
|
|
9063
9645
|
context: () => this.context(),
|
|
9064
|
-
changed: () => this.markStateDirty()
|
|
9646
|
+
changed: () => this.markStateDirty(),
|
|
9647
|
+
i18n: this.localeState
|
|
9065
9648
|
});
|
|
9066
|
-
this.objectTree = new ObjectTree(main, (sym) => this.feed.symbolIcon(sym));
|
|
9067
|
-
this.dataWindow = new DataWindow(main);
|
|
9068
|
-
this.dock.addBuiltIn({ id: "dataWindow", title:
|
|
9069
|
-
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) });
|
|
9070
9653
|
this.dock.refresh();
|
|
9071
9654
|
if (boot?.panels) this.dock.applyState(boot.panels);
|
|
9072
9655
|
this.root.appendChild(main);
|
|
@@ -9077,7 +9660,7 @@ var VelaWorkspace = class {
|
|
|
9077
9660
|
const attribution = rendererDefaults().attribution;
|
|
9078
9661
|
if (attribution !== false) {
|
|
9079
9662
|
const background = resolveTheme(opts.theme).background;
|
|
9080
|
-
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);
|
|
9081
9664
|
Object.assign(mark.style, {
|
|
9082
9665
|
left: "calc(var(--vela-toolbar-gutter, 0px) + 12px)",
|
|
9083
9666
|
bottom: `calc(var(--vela-bottom-gutter, ${TIME_AXIS_H3}px) + 10px)`,
|
|
@@ -9116,6 +9699,7 @@ var VelaWorkspace = class {
|
|
|
9116
9699
|
},
|
|
9117
9700
|
{
|
|
9118
9701
|
dock: "static",
|
|
9702
|
+
i18n: () => this.localeState,
|
|
9119
9703
|
// Drawings sync is a WORKSPACE link (same model as the layout
|
|
9120
9704
|
// dropdown's switches) — the bar only hosts its toggle.
|
|
9121
9705
|
onDrawingsSync: (on) => {
|
|
@@ -9128,6 +9712,7 @@ var VelaWorkspace = class {
|
|
|
9128
9712
|
this.drawToolbar?.setVisible(true);
|
|
9129
9713
|
this.drawToolbar?.setDrawingsSyncMode(!!this.syncOpts.drawings);
|
|
9130
9714
|
this.bottombar = opts.bottombar !== false ? new Bottombar(this.root, {
|
|
9715
|
+
i18n: this.localeState,
|
|
9131
9716
|
timezone: this.timezone,
|
|
9132
9717
|
clock: this.clock,
|
|
9133
9718
|
onRange: (preset) => {
|
|
@@ -9141,6 +9726,7 @@ var VelaWorkspace = class {
|
|
|
9141
9726
|
onSettingsClick: () => this.active.chart.renderer.openSettings()
|
|
9142
9727
|
}) : null;
|
|
9143
9728
|
this.mobileBar = opts.bottombar !== false ? new MobileBar(this.root, {
|
|
9729
|
+
i18n: this.localeState,
|
|
9144
9730
|
symbol: "",
|
|
9145
9731
|
timeframe: "60",
|
|
9146
9732
|
onSymbolClick: () => this.symbolPicker.open(),
|
|
@@ -9174,7 +9760,7 @@ var VelaWorkspace = class {
|
|
|
9174
9760
|
this.keymap = new KeymapManager();
|
|
9175
9761
|
this.keymap.attach(this.root);
|
|
9176
9762
|
this.registerDefaultKeys();
|
|
9177
|
-
this.drawToolbar?.setShortcuts(toolShortcutHints(this.keymap));
|
|
9763
|
+
this.drawToolbar?.setShortcuts(toolShortcutHints(this.keymap, this.localeState));
|
|
9178
9764
|
this.root.addEventListener("keydown", this.onRootKeydown);
|
|
9179
9765
|
this.root.tabIndex = -1;
|
|
9180
9766
|
this.cellBackend = this.backendFor(this.def);
|
|
@@ -9298,6 +9884,61 @@ var VelaWorkspace = class {
|
|
|
9298
9884
|
on(event, handler) {
|
|
9299
9885
|
return this.events.on(event, handler);
|
|
9300
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
|
+
}
|
|
9301
9942
|
/** The context handed to contributed actions/attachments (rebuilt per invocation). */
|
|
9302
9943
|
context() {
|
|
9303
9944
|
return buildContext({
|
|
@@ -9314,6 +9955,7 @@ var VelaWorkspace = class {
|
|
|
9314
9955
|
};
|
|
9315
9956
|
},
|
|
9316
9957
|
root: this.root,
|
|
9958
|
+
i18n: this.localeState,
|
|
9317
9959
|
toast: (message, kind) => this.toastHost.show(message, kind),
|
|
9318
9960
|
stateDirty: () => this.markStateDirty(),
|
|
9319
9961
|
replay: this.replay
|
|
@@ -9814,7 +10456,7 @@ var VelaWorkspace = class {
|
|
|
9814
10456
|
resolveLayout(layout) {
|
|
9815
10457
|
if (typeof layout !== "string") return layout;
|
|
9816
10458
|
const def = ensureLayout(layout);
|
|
9817
|
-
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().`);
|
|
9818
10460
|
return def;
|
|
9819
10461
|
}
|
|
9820
10462
|
backendFor(def) {
|
|
@@ -9942,6 +10584,7 @@ var VelaWorkspace = class {
|
|
|
9942
10584
|
feed: this.feed,
|
|
9943
10585
|
engines: this.opts.engines ?? {},
|
|
9944
10586
|
chartDefaults: cellChartDefaults(this.opts),
|
|
10587
|
+
localization: () => this.localeState.options,
|
|
9945
10588
|
theme,
|
|
9946
10589
|
live: this.opts.live ?? false,
|
|
9947
10590
|
volume: this.opts.volume ?? true,
|
|
@@ -9994,10 +10637,11 @@ var VelaWorkspace = class {
|
|
|
9994
10637
|
chart.on("script:run", (run) => this.events.emit("script:run", { ...run, cell: cell.id }));
|
|
9995
10638
|
chart.on("priceStyle:change", ({ from, to }) => this.events.emit("cell:priceStyle", { id: cell.id, from, to }));
|
|
9996
10639
|
chart.on("alert", (alert) => {
|
|
9997
|
-
const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe), alert.indicator].filter(Boolean).join(" ");
|
|
9998
|
-
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 });
|
|
9999
10642
|
if (this.alerts.length > this.alertCap) this.alerts.pop();
|
|
10000
|
-
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);
|
|
10001
10645
|
this.topbar.setAlertCount(this.alerts.length);
|
|
10002
10646
|
});
|
|
10003
10647
|
chart.on("drawing:favorites", ({ favorites }) => {
|
|
@@ -10083,7 +10727,7 @@ var VelaWorkspace = class {
|
|
|
10083
10727
|
if (kind === "crosshair") {
|
|
10084
10728
|
for (const cell of this.cellsById.values()) cell.chart.renderer.setExternalCrosshair(null);
|
|
10085
10729
|
if (setting && ![...this.cellsById.values()].some((c) => c.chart.renderer.supportsExternalCrosshair)) {
|
|
10086
|
-
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.");
|
|
10087
10731
|
}
|
|
10088
10732
|
if (this.topbar && this.def) this.topbar.setLayout(this.def.id);
|
|
10089
10733
|
this.markStateDirty();
|
|
@@ -10466,7 +11110,8 @@ var VelaWorkspace = class {
|
|
|
10466
11110
|
this.active.applyRange(preset);
|
|
10467
11111
|
this.bottombar?.setActiveRange(preset.id);
|
|
10468
11112
|
},
|
|
10469
|
-
onOpenChange: (open) => this.trackDialog(open)
|
|
11113
|
+
onOpenChange: (open) => this.trackDialog(open),
|
|
11114
|
+
i18n: this.localeState
|
|
10470
11115
|
}));
|
|
10471
11116
|
this.tfDrawer.open();
|
|
10472
11117
|
}
|
|
@@ -10479,7 +11124,8 @@ var VelaWorkspace = class {
|
|
|
10479
11124
|
isFavorite: (type) => this.active.chart.drawings.isFavorite(type),
|
|
10480
11125
|
onFavorite: (type, on) => this.active.chart.drawings.setFavorite(type, on),
|
|
10481
11126
|
onSelect: (type) => this.active.chart.drawings.setTool(type),
|
|
10482
|
-
onOpenChange: (open) => this.trackDialog(open)
|
|
11127
|
+
onOpenChange: (open) => this.trackDialog(open),
|
|
11128
|
+
i18n: () => this.localeState
|
|
10483
11129
|
}));
|
|
10484
11130
|
this.drawingsDrawer.open();
|
|
10485
11131
|
}
|
|
@@ -10487,16 +11133,17 @@ var VelaWorkspace = class {
|
|
|
10487
11133
|
const has = (id) => topbarHas(this.topbarComp, id);
|
|
10488
11134
|
this.moreDrawer ?? (this.moreDrawer = new MoreDrawer({
|
|
10489
11135
|
host: this.root,
|
|
11136
|
+
i18n: this.localeState,
|
|
10490
11137
|
...has("undo-redo") ? { onUndo: () => this.active.history.undo(), onRedo: () => this.active.history.redo() } : {},
|
|
10491
11138
|
...has("screenshot") ? { onScreenshot: this.screenshotOverride ? () => this.runOverride(this.screenshotOverride) : () => this.downloadScreenshot() } : {},
|
|
10492
11139
|
canUndo: () => this.active.history.canUndo,
|
|
10493
11140
|
canRedo: () => this.active.history.canRedo,
|
|
10494
|
-
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) })),
|
|
10495
11142
|
priceStyle: () => this.active.priceStyle,
|
|
10496
11143
|
onPriceStyle: (id) => this.active.setPriceStyle(id),
|
|
10497
11144
|
panels: () => has("panels") ? [...this.dock.list()] : [],
|
|
10498
11145
|
onTogglePanel: (id) => this.dock.toggle(id),
|
|
10499
|
-
...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 })) } : {},
|
|
10500
11147
|
// Actions placed on the bottom bar (left-aligned by default) have their stop
|
|
10501
11148
|
// there — only the menu-placed ones land in the drawer, or they would appear
|
|
10502
11149
|
// twice: left (primary) ones with the primary rows, right ones at the end.
|
|
@@ -10510,15 +11157,10 @@ var VelaWorkspace = class {
|
|
|
10510
11157
|
// single-chart mode and a composition without 'layout' omit it here too.
|
|
10511
11158
|
layout: this.monoLayout || !has("layout") ? void 0 : {
|
|
10512
11159
|
shape: () => layoutShape(this.def),
|
|
10513
|
-
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 })),
|
|
10514
11161
|
onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
|
|
10515
11162
|
onSelectPreset: (id) => this.setLayout(id),
|
|
10516
|
-
syncs: () =>
|
|
10517
|
-
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
10518
|
-
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
10519
|
-
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true },
|
|
10520
|
-
{ id: "style", label: "Style", checked: this.syncOpts.style === true }
|
|
10521
|
-
],
|
|
11163
|
+
syncs: () => this.syncRows(),
|
|
10522
11164
|
onToggleSync: (id) => {
|
|
10523
11165
|
const kind = id;
|
|
10524
11166
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
@@ -10532,14 +11174,28 @@ var VelaWorkspace = class {
|
|
|
10532
11174
|
/** The contributed topbar actions the mobile menu lists, from one cluster. */
|
|
10533
11175
|
drawerActions(cluster) {
|
|
10534
11176
|
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
10535
|
-
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
|
+
];
|
|
10536
11191
|
}
|
|
10537
11192
|
openTimezoneDrawer() {
|
|
10538
11193
|
this.timezoneDrawer ?? (this.timezoneDrawer = new TimezoneDrawer({
|
|
10539
11194
|
host: this.root,
|
|
10540
11195
|
timezone: () => this.timezone,
|
|
10541
11196
|
onTimezone: (zone) => this.setTimezone(zone),
|
|
10542
|
-
onOpenChange: (open) => this.trackDialog(open)
|
|
11197
|
+
onOpenChange: (open) => this.trackDialog(open),
|
|
11198
|
+
i18n: this.localeState
|
|
10543
11199
|
}));
|
|
10544
11200
|
this.timezoneDrawer.open();
|
|
10545
11201
|
}
|
|
@@ -10558,8 +11214,8 @@ var VelaWorkspace = class {
|
|
|
10558
11214
|
this.alertsMenu?.destroy();
|
|
10559
11215
|
const items = this.alerts.length ? this.alerts.map((a, i) => ({
|
|
10560
11216
|
id: String(i),
|
|
10561
|
-
label:
|
|
10562
|
-
})) : [{ 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 }];
|
|
10563
11219
|
this.alertsMenu = new Menu({
|
|
10564
11220
|
host: this.root,
|
|
10565
11221
|
items,
|
|
@@ -10594,29 +11250,29 @@ var VelaWorkspace = class {
|
|
|
10594
11250
|
this.keymap.register({
|
|
10595
11251
|
id: "chart.screenshot",
|
|
10596
11252
|
keys: "mod+alt+s",
|
|
10597
|
-
label: ov ? ov.label :
|
|
10598
|
-
category:
|
|
11253
|
+
label: ov ? ov.label : TEXT22.screenshot,
|
|
11254
|
+
category: TEXT22.categoryChart,
|
|
10599
11255
|
run: ov ? () => this.runOverride(ov) : () => this.downloadScreenshot()
|
|
10600
11256
|
});
|
|
10601
11257
|
}
|
|
10602
|
-
this.keymap.register({ id: "chart.reset-view", keys: "alt+r", label:
|
|
10603
|
-
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")) });
|
|
10604
11260
|
this.keymap.register({
|
|
10605
11261
|
id: "chart.toggle-percent",
|
|
10606
11262
|
keys: "alt+p",
|
|
10607
|
-
label:
|
|
10608
|
-
category:
|
|
11263
|
+
label: TEXT22.togglePercent,
|
|
11264
|
+
category: TEXT22.categoryChart,
|
|
10609
11265
|
run: () => {
|
|
10610
11266
|
const mode = this.active.chart.renderer.get("scaleMode");
|
|
10611
11267
|
this.active.chart.renderer.set("scaleMode", mode === "percent" ? "price" : "percent");
|
|
10612
11268
|
}
|
|
10613
11269
|
});
|
|
10614
|
-
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") });
|
|
10615
11271
|
this.keymap.register({
|
|
10616
11272
|
id: "drawings.hline-cursor",
|
|
10617
11273
|
keys: "alt+h",
|
|
10618
|
-
label:
|
|
10619
|
-
category:
|
|
11274
|
+
label: TEXT22.hlineCursor,
|
|
11275
|
+
category: TEXT22.categoryDrawings,
|
|
10620
11276
|
run: () => {
|
|
10621
11277
|
const c = this.active;
|
|
10622
11278
|
if (c.lastCrossTime != null && c.lastCrossPrice != null) c.chart.drawings.add("hline", { anchors: [{ time: c.lastCrossTime, price: c.lastCrossPrice }] });
|
|
@@ -10625,32 +11281,32 @@ var VelaWorkspace = class {
|
|
|
10625
11281
|
this.keymap.register({
|
|
10626
11282
|
id: "drawings.vline-cursor",
|
|
10627
11283
|
keys: "alt+v",
|
|
10628
|
-
label:
|
|
10629
|
-
category:
|
|
11284
|
+
label: TEXT22.vlineCursor,
|
|
11285
|
+
category: TEXT22.categoryDrawings,
|
|
10630
11286
|
run: () => {
|
|
10631
11287
|
const c = this.active;
|
|
10632
11288
|
if (c.lastCrossTime != null && c.lastCrossPrice != null) c.chart.drawings.add("vline", { anchors: [{ time: c.lastCrossTime, price: c.lastCrossPrice }] });
|
|
10633
11289
|
}
|
|
10634
11290
|
});
|
|
10635
|
-
this.keymap.register({ id: "history.undo", keys: ["mod+z"], label:
|
|
10636
|
-
this.keymap.register({ id: "history.redo", keys: ["mod+y", "mod+shift+z"], label:
|
|
10637
|
-
this.keymap.register({ id: "view.zoom-in", keys: "mod+arrowup", label:
|
|
10638
|
-
this.keymap.register({ id: "view.zoom-out", keys: "mod+arrowdown", label:
|
|
10639
|
-
this.keymap.register({ id: "view.pan-left", keys: "mod+arrowleft", label:
|
|
10640
|
-
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) });
|
|
10641
11297
|
const indOv = this.indicatorsOverride;
|
|
10642
11298
|
if (indOv && topbarHas(this.topbarComp, "indicators")) {
|
|
10643
|
-
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) });
|
|
10644
11300
|
} else if (this.indicatorPicker) {
|
|
10645
|
-
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() });
|
|
10646
11302
|
}
|
|
10647
11303
|
this.keymap.register({
|
|
10648
11304
|
id: "help.shortcuts",
|
|
10649
11305
|
keys: "?",
|
|
10650
|
-
label:
|
|
10651
|
-
category:
|
|
11306
|
+
label: TEXT22.showShortcuts,
|
|
11307
|
+
category: TEXT22.categoryHelp,
|
|
10652
11308
|
run: () => {
|
|
10653
|
-
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));
|
|
10654
11310
|
this.shortcutsHelp.open();
|
|
10655
11311
|
}
|
|
10656
11312
|
});
|
|
@@ -10659,6 +11315,7 @@ var VelaWorkspace = class {
|
|
|
10659
11315
|
routeTyping(ev) {
|
|
10660
11316
|
if (this.destroyed) return;
|
|
10661
11317
|
if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
|
|
11318
|
+
if (ev.isComposing || ev.key === "Process") return;
|
|
10662
11319
|
if (isEditableTarget(ev)) return;
|
|
10663
11320
|
const hit = resolveTyping(ev.key, { dialogs: this.openDialogs, symbolSearch: this.symbolPicker.isOpen, timeframeEntry: this.tfQuick.isOpen });
|
|
10664
11321
|
if (!hit) return;
|