@luxalgo/vela 0.8.5 → 0.9.1
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-DdqMxKdl.d.cts → DataProvider-B5i0_yY4.d.cts} +1 -1
- package/dist/{DataProvider-BUdlkx9I.d.ts → DataProvider-CPFgSyAm.d.ts} +1 -1
- package/dist/{chunk-QYIO37K7.js → chunk-3H6JO4VL.js} +2405 -1542
- package/dist/{chunk-BN4DNS3O.js → chunk-BLVL7WXF.js} +1156 -468
- package/dist/chunk-O6L27KRB.js +1331 -0
- package/dist/{chunk-WY5H4S4I.js → chunk-PZN4RS4T.js} +91 -36
- package/dist/{chunk-ALTGHKIM.js → chunk-Q7POFYNE.js} +516 -406
- package/dist/{chunk-AUOB7A5Y.js → chunk-WHPQQYH7.js} +29 -13
- package/dist/{contributions-D8xuYc9U.d.cts → contributions-BfBaF6eE.d.ts} +71 -22
- package/dist/{contributions-P9fBBuY1.d.ts → contributions-DupwEkzc.d.cts} +71 -22
- package/dist/index.cjs +4040 -2087
- package/dist/index.d.cts +53 -25
- package/dist/index.d.ts +53 -25
- package/dist/index.js +5 -5
- package/dist/{keymap-CGOz5F5f.d.cts → keymap-C48ughm6.d.ts} +9 -6
- package/dist/{keymap-CGOz5F5f.d.ts → keymap-W0hiajU6.d.cts} +9 -6
- package/dist/locales/ar.cjs +2262 -0
- package/dist/locales/ar.d.cts +5 -0
- package/dist/locales/ar.d.ts +5 -0
- package/dist/locales/ar.js +2260 -0
- package/dist/locales/de.cjs +2262 -0
- package/dist/locales/de.d.cts +5 -0
- package/dist/locales/de.d.ts +5 -0
- package/dist/locales/de.js +2260 -0
- package/dist/locales/en.cjs +2262 -0
- package/dist/locales/en.d.cts +5 -0
- package/dist/locales/en.d.ts +5 -0
- package/dist/locales/en.js +2260 -0
- package/dist/locales/es.cjs +2262 -0
- package/dist/locales/es.d.cts +5 -0
- package/dist/locales/es.d.ts +5 -0
- package/dist/locales/es.js +2260 -0
- package/dist/locales/fr.cjs +2262 -0
- package/dist/locales/fr.d.cts +5 -0
- package/dist/locales/fr.d.ts +5 -0
- package/dist/locales/fr.js +2260 -0
- package/dist/locales/he.cjs +2262 -0
- package/dist/locales/he.d.cts +5 -0
- package/dist/locales/he.d.ts +5 -0
- package/dist/locales/he.js +2260 -0
- package/dist/locales/hi.cjs +2262 -0
- package/dist/locales/hi.d.cts +5 -0
- package/dist/locales/hi.d.ts +5 -0
- package/dist/locales/hi.js +2260 -0
- package/dist/locales/id.cjs +2262 -0
- package/dist/locales/id.d.cts +5 -0
- package/dist/locales/id.d.ts +5 -0
- package/dist/locales/id.js +2260 -0
- package/dist/locales/it.cjs +2262 -0
- package/dist/locales/it.d.cts +5 -0
- package/dist/locales/it.d.ts +5 -0
- package/dist/locales/it.js +2260 -0
- package/dist/locales/ja.cjs +2262 -0
- package/dist/locales/ja.d.cts +5 -0
- package/dist/locales/ja.d.ts +5 -0
- package/dist/locales/ja.js +2260 -0
- package/dist/locales/ko.cjs +2262 -0
- package/dist/locales/ko.d.cts +5 -0
- package/dist/locales/ko.d.ts +5 -0
- package/dist/locales/ko.js +2260 -0
- package/dist/locales/ms.cjs +2262 -0
- package/dist/locales/ms.d.cts +5 -0
- package/dist/locales/ms.d.ts +5 -0
- package/dist/locales/ms.js +2260 -0
- package/dist/locales/pl.cjs +2262 -0
- package/dist/locales/pl.d.cts +5 -0
- package/dist/locales/pl.d.ts +5 -0
- package/dist/locales/pl.js +2260 -0
- package/dist/locales/pt-BR.cjs +2262 -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 +2260 -0
- package/dist/locales/ru.cjs +2262 -0
- package/dist/locales/ru.d.cts +5 -0
- package/dist/locales/ru.d.ts +5 -0
- package/dist/locales/ru.js +2260 -0
- package/dist/locales/th.cjs +2262 -0
- package/dist/locales/th.d.cts +5 -0
- package/dist/locales/th.d.ts +5 -0
- package/dist/locales/th.js +2260 -0
- package/dist/locales/tr.cjs +2262 -0
- package/dist/locales/tr.d.cts +5 -0
- package/dist/locales/tr.d.ts +5 -0
- package/dist/locales/tr.js +2260 -0
- package/dist/locales/vi.cjs +2262 -0
- package/dist/locales/vi.d.cts +5 -0
- package/dist/locales/vi.d.ts +5 -0
- package/dist/locales/vi.js +2260 -0
- package/dist/locales/zh-Hans.cjs +2262 -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 +2260 -0
- package/dist/locales/zh-Hant.cjs +2262 -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 +2260 -0
- package/dist/{options-BI5Tp32b.d.ts → options-B7aL1pbf.d.ts} +74 -9
- package/dist/{options-BI5Tp32b.d.cts → options-I1H2HDWw.d.cts} +74 -9
- package/dist/{plugin-B8F1nor8.d.cts → plugin-BGz-Vsdv.d.cts} +81 -35
- package/dist/{plugin-D5Z-Q6TX.d.ts → plugin-BZHypj8h.d.ts} +81 -35
- package/dist/plugin.cjs +1268 -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-BEQJqSjR.d.cts} +14 -2
- package/dist/{side-panel-_2UK1gSm.d.cts → side-panel-Ud0SBAnE.d.ts} +14 -2
- package/dist/{statusline-model-CTwtDIca.d.cts → statusline-model-Dfb_GD9u.d.ts} +14 -3
- package/dist/{statusline-model-D9kWC9X5.d.ts → statusline-model-PHt0YhmI.d.cts} +14 -3
- package/dist/types-Bwf63xGI.d.cts +2518 -0
- package/dist/types-Bwf63xGI.d.ts +2518 -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 +4005 -2054
- package/dist/vela.global.min.js +57 -57
- package/dist/widget.cjs +5384 -2698
- package/dist/widget.d.cts +100 -26
- package/dist/widget.d.ts +100 -26
- package/dist/widget.js +19 -11
- package/dist/workspace.cjs +5373 -2705
- 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-3H6JO4VL.js';
|
|
2
|
+
import { resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, tzButtonLabel, resolveTimezone, timezoneMenuRows, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, paneScaleAt, composeMenu, priceAxisItems, scaleChoiceOf, priceOnlyState, timeAxisItems, bodyItems, scaleWrites, invertWrite, settingsSectionOf, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, normalizeTimezone, isExchangeTimezone, statePersistenceHandlers, topbarHas, rendererDefaults, mobilePlacement, widgetAttachments, chartType, getDrawingType, nativeIndicatorEntry, inputDeltas } from './chunk-Q7POFYNE.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
|
};
|
|
@@ -3993,6 +4284,7 @@ function successorNative(entry) {
|
|
|
3993
4284
|
const hasInputs = Object.keys(inputs).length > 0;
|
|
3994
4285
|
return hasInputs || hidden ? { type: next, ...hasInputs ? { inputs } : {}, ...hidden ? { hidden } : {} } : next;
|
|
3995
4286
|
}
|
|
4287
|
+
var STATUSLINE_PART_KEYS = ["logo", "name", "market", "ohlc", "change"];
|
|
3996
4288
|
function sanitizeCell(raw) {
|
|
3997
4289
|
if (raw == null || typeof raw !== "object") return null;
|
|
3998
4290
|
const c = raw;
|
|
@@ -4007,6 +4299,12 @@ function sanitizeCell(raw) {
|
|
|
4007
4299
|
if (typeof c.replayWatermark === "boolean") out.replayWatermark = c.replayWatermark;
|
|
4008
4300
|
if (typeof c.indicatorTitles === "boolean") out.indicatorTitles = c.indicatorTitles;
|
|
4009
4301
|
if (typeof c.indicatorValues === "boolean") out.indicatorValues = c.indicatorValues;
|
|
4302
|
+
if (c.statuslineParts != null && typeof c.statuslineParts === "object") {
|
|
4303
|
+
const bag = c.statuslineParts;
|
|
4304
|
+
const parts = {};
|
|
4305
|
+
for (const key of STATUSLINE_PART_KEYS) if (typeof bag[key] === "boolean") parts[key] = bag[key];
|
|
4306
|
+
if (Object.keys(parts).length > 0) out.statuslineParts = parts;
|
|
4307
|
+
}
|
|
4010
4308
|
if (c.rendererConfig != null && typeof c.rendererConfig === "object") out.rendererConfig = c.rendererConfig;
|
|
4011
4309
|
if (c.drawings != null && typeof c.drawings === "object") out.drawings = c.drawings;
|
|
4012
4310
|
const ind = c.indicators;
|
|
@@ -4190,7 +4488,7 @@ function decimalsFor(ref) {
|
|
|
4190
4488
|
return a >= 1 ? 2 : a >= 0.01 ? 4 : 6;
|
|
4191
4489
|
}
|
|
4192
4490
|
function fmtPrice(n, dp) {
|
|
4193
|
-
if (n == null || !Number.isFinite(n)) return "
|
|
4491
|
+
if (n == null || !Number.isFinite(n)) return "—";
|
|
4194
4492
|
const d = dp ?? decimalsFor(n);
|
|
4195
4493
|
return n.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
|
|
4196
4494
|
}
|
|
@@ -4201,13 +4499,25 @@ function fmtChange(open, close) {
|
|
|
4201
4499
|
const sign = diff >= 0 ? "+" : "";
|
|
4202
4500
|
return `${sign}${fmtPrice(diff, decimalsFor(close))} (${sign}${pct.toFixed(2)}%)`;
|
|
4203
4501
|
}
|
|
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
4502
|
|
|
4210
4503
|
// src/widget/statusline-model.ts
|
|
4504
|
+
var CELL_LABELS = {
|
|
4505
|
+
open: msg("widget.statusline.openShort", "O"),
|
|
4506
|
+
high: msg("widget.statusline.highShort", "H"),
|
|
4507
|
+
low: msg("widget.statusline.lowShort", "L"),
|
|
4508
|
+
close: msg("widget.statusline.closeShort", "C")
|
|
4509
|
+
};
|
|
4510
|
+
var STATUSLINE_PART_LABELS = {
|
|
4511
|
+
logo: msg("widget.statusline.part.logo", "Symbol logo"),
|
|
4512
|
+
name: msg("widget.statusline.part.name", "Symbol name"),
|
|
4513
|
+
market: msg("widget.statusline.part.market", "Market status"),
|
|
4514
|
+
ohlc: msg("widget.statusline.part.ohlc", "OHLC values"),
|
|
4515
|
+
change: msg("widget.statusline.part.change", "Bar change values")
|
|
4516
|
+
};
|
|
4517
|
+
var TEXT11 = {
|
|
4518
|
+
hideChart: msg("widget.statusline.hideChart", "Hide chart"),
|
|
4519
|
+
showChart: msg("widget.statusline.showChart", "Show chart")
|
|
4520
|
+
};
|
|
4211
4521
|
function segmentVisibility(parts, chartHidden = false) {
|
|
4212
4522
|
return { avatar: parts.logo, symbol: parts.name, meta: parts.name, market: parts.market, ohlc: parts.ohlc && !chartHidden, change: parts.change && !chartHidden, eye: chartHidden };
|
|
4213
4523
|
}
|
|
@@ -4217,28 +4527,31 @@ var STATUSLINE_LADDER = [
|
|
|
4217
4527
|
{ stacked: true, level: "compact" },
|
|
4218
4528
|
{ stacked: true, level: "minimal" }
|
|
4219
4529
|
];
|
|
4220
|
-
function readoutCells(level, readout) {
|
|
4530
|
+
function readoutCells(level, readout, i18n) {
|
|
4221
4531
|
if (readout === "value") return [{ key: "close", label: "" }];
|
|
4532
|
+
const label = (key) => textOf(i18n, CELL_LABELS[key]);
|
|
4222
4533
|
if (level === "full") {
|
|
4223
4534
|
return [
|
|
4224
|
-
{ key: "open", label: "
|
|
4225
|
-
{ key: "high", label: "
|
|
4226
|
-
{ key: "low", label: "
|
|
4227
|
-
{ key: "close", label: "
|
|
4535
|
+
{ key: "open", label: label("open") },
|
|
4536
|
+
{ key: "high", label: label("high") },
|
|
4537
|
+
{ key: "low", label: label("low") },
|
|
4538
|
+
{ key: "close", label: label("close") }
|
|
4228
4539
|
];
|
|
4229
4540
|
}
|
|
4230
|
-
return [{ key: "close", label: level === "compact" ? "
|
|
4541
|
+
return [{ key: "close", label: level === "compact" ? label("close") : "" }];
|
|
4231
4542
|
}
|
|
4232
|
-
function statuslineMenuItems(parts, chartVisible) {
|
|
4543
|
+
function statuslineMenuItems(parts, chartVisible, i18n) {
|
|
4544
|
+
const t = (m) => textOf(i18n, m);
|
|
4233
4545
|
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 ?
|
|
4546
|
+
{ id: "part:logo", label: t(STATUSLINE_PART_LABELS.logo), checked: parts.logo },
|
|
4547
|
+
{ id: "part:name", label: t(STATUSLINE_PART_LABELS.name), checked: parts.name },
|
|
4548
|
+
{ id: "part:market", label: t(STATUSLINE_PART_LABELS.market), checked: parts.market },
|
|
4549
|
+
{ id: "part:ohlc", label: t(STATUSLINE_PART_LABELS.ohlc), checked: parts.ohlc },
|
|
4550
|
+
{ id: "part:change", label: t(STATUSLINE_PART_LABELS.change), checked: parts.change },
|
|
4551
|
+
{ id: "chart", label: t(chartVisible ? TEXT11.hideChart : TEXT11.showChart), separatorBefore: true }
|
|
4240
4552
|
];
|
|
4241
4553
|
}
|
|
4554
|
+
var SHOW_CHART_LABEL = TEXT11.showChart;
|
|
4242
4555
|
|
|
4243
4556
|
// src/widget/statusline.ts
|
|
4244
4557
|
var STYLE_ID10 = "vela-widget-statusline";
|
|
@@ -4247,7 +4560,7 @@ var CSS10 = `
|
|
|
4247
4560
|
position: absolute;
|
|
4248
4561
|
top: var(--vela-space-2);
|
|
4249
4562
|
/* 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
|
|
4563
|
+
* on the mount container, and the legend sits 10px into the plot to its right —
|
|
4251
4564
|
* so the two columns stay aligned whether the toolbar is docked (44px), collapsed
|
|
4252
4565
|
* (16px), or absent entirely (a workspace cell: 0). */
|
|
4253
4566
|
left: calc(var(--vela-toolbar-gutter, 0px) + 10px);
|
|
@@ -4263,8 +4576,8 @@ var CSS10 = `
|
|
|
4263
4576
|
color: var(--vela-fg);
|
|
4264
4577
|
font-size: var(--vela-font-size-md);
|
|
4265
4578
|
/* Same chip treatment as the indicator legend rows (InputsUI): a translucent wash of
|
|
4266
|
-
* the chart background when idle
|
|
4267
|
-
* reach it
|
|
4579
|
+
* the chart background when idle — enough to keep the readout legible when candles
|
|
4580
|
+
* reach it — and the solid chart background on hover. Symmetric 7px padding with a
|
|
4268
4581
|
* compensating negative margin (mirroring the legend rows) keeps the avatar's left
|
|
4269
4582
|
* edge on the legend column's left edge (both at left:10px) while the chip itself
|
|
4270
4583
|
* extends 7px further left, so both columns' chips share the same left edge. */
|
|
@@ -4276,12 +4589,12 @@ var CSS10 = `
|
|
|
4276
4589
|
}
|
|
4277
4590
|
/* Hovering opens the chip the same way a legend row opens: solid chart background
|
|
4278
4591
|
* plus the same inset neutral outline the indicator rows wear (InputsUI's
|
|
4279
|
-
* setRowHighlighted)
|
|
4592
|
+
* setRowHighlighted) — the two columns read as one family. */
|
|
4280
4593
|
.vela-statusline:hover { background: var(--vela-bg); box-shadow: inset 0 0 0 1px var(--vela-border); }
|
|
4281
|
-
/* Chart hidden (the price series' eye
|
|
4594
|
+
/* Chart hidden (the price series' eye — renderer 'candleVisible'): the line dims to
|
|
4282
4595
|
* the same 0.5 wash a hidden indicator's legend row wears. */
|
|
4283
4596
|
.vela-statusline.vela-sl-chart-hidden { opacity: 0.5; }
|
|
4284
|
-
/* Two rows inside the chip
|
|
4597
|
+
/* Two rows inside the chip — the identity (logo / symbol / meta / market badge) and the
|
|
4285
4598
|
* value readout (O/H/L/C + change, or the show-chart eye). In the widest layout they sit
|
|
4286
4599
|
* side by side on one line; the STACKED layouts (fit() decides, see the ladder in
|
|
4287
4600
|
* statusline-model) turn the chip into a column so the values drop under the symbol. */
|
|
@@ -4298,7 +4611,7 @@ var CSS10 = `
|
|
|
4298
4611
|
* that follows the chip (see the stacked legend shift below). */
|
|
4299
4612
|
row-gap: var(--vela-space-1);
|
|
4300
4613
|
}
|
|
4301
|
-
/* Stacked, the identity row may shrink (the meta carries the ellipsis)
|
|
4614
|
+
/* Stacked, the identity row may shrink (the meta carries the ellipsis) — the values row
|
|
4302
4615
|
* never does, so its overflow is what fit() measures to keep descending the ladder. */
|
|
4303
4616
|
.vela-statusline.vela-sl-stacked .vela-sl-identity { max-width: 100%; }
|
|
4304
4617
|
.vela-statusline.vela-sl-stacked .vela-sl-meta {
|
|
@@ -4321,25 +4634,28 @@ var CSS10 = `
|
|
|
4321
4634
|
}
|
|
4322
4635
|
.vela-statusline .vela-sl-symbol { font-weight: 600; font-size: var(--vela-font-size-lg); }
|
|
4323
4636
|
.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 "
|
|
4637
|
+
/* The meta opens with "· " — sit its dot one space-width after the ticker, as far as the
|
|
4325
4638
|
* venue sits after it, not a full row gap away. */
|
|
4326
4639
|
.vela-statusline .vela-sl-symbol + .vela-sl-meta { margin-left: calc(var(--vela-space-1) - var(--vela-space-2)); }
|
|
4327
|
-
/* Market status badge
|
|
4640
|
+
/* Market status badge — a kit callout bubble (icon-only 16px circle, label on hover
|
|
4328
4641
|
* via the kit tooltip); the session tint is applied per status in setMarketStatus. While
|
|
4329
4642
|
* the chart replays past bars it wears the replay badge instead (the inverse chip). */
|
|
4330
4643
|
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
4331
4644
|
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
4332
4645
|
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
4333
4646
|
/* Tabular figures: every digit has one advance, so a readout whose text changes only in
|
|
4334
|
-
* its digits keeps its width
|
|
4647
|
+
* its digits keeps its width — render() skips the ladder for those (see widthKey). */
|
|
4335
4648
|
.vela-statusline .vela-sl-values { font-variant-numeric: tabular-nums; }
|
|
4336
4649
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
4337
4650
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
4338
|
-
/*
|
|
4651
|
+
/* Prices and the signed change are left-to-right isolates (each carries dir="ltr"): inside a
|
|
4652
|
+
* right-to-left page the row's order mirrors, but a sign stays in front of its number. */
|
|
4653
|
+
.vela-statusline .vela-sl-ohlc b, .vela-statusline .vela-sl-change { unicode-bidi: isolate; }
|
|
4654
|
+
/* The change value wears the SAME ink as the OHLC values (set inline per render) —
|
|
4339
4655
|
* these are the pre-ink fallbacks only. */
|
|
4340
4656
|
.vela-statusline .vela-sl-change[data-dir='up'] { color: var(--vela-up); }
|
|
4341
4657
|
.vela-statusline .vela-sl-change[data-dir='down'] { color: var(--vela-down); }
|
|
4342
|
-
/* The show-chart eye
|
|
4658
|
+
/* The show-chart eye — out only while the chart is hidden (syncParts drives display),
|
|
4343
4659
|
* replacing the value readout it took away. Same footprint as a legend action button. */
|
|
4344
4660
|
.vela-statusline .vela-sl-eye {
|
|
4345
4661
|
align-self: center;
|
|
@@ -4358,18 +4674,18 @@ var CSS10 = `
|
|
|
4358
4674
|
}
|
|
4359
4675
|
.vela-statusline .vela-sl-eye:hover { color: var(--vela-fg); background: color-mix(in srgb, var(--vela-fg) 12%, transparent); }
|
|
4360
4676
|
/* 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
|
|
4677
|
+
* The renderer marks whichever legend sits at the plot's top edge — the price pane
|
|
4678
|
+
* normally, or a maximized study pane filling the plot — so the legend never merges
|
|
4363
4679
|
* 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)
|
|
4680
|
+
* inline top — shift with a transform, don't fight it. Scoped to hosts that actually
|
|
4681
|
+
* CARRY a status line (the marker class set by the Statusline constructor) — the
|
|
4366
4682
|
* stylesheet is document-global, so a bare attribute selector here would shift every
|
|
4367
4683
|
* chart on the page, including statusline-less ones. */
|
|
4368
4684
|
.vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(26px); }
|
|
4369
|
-
/* A stacked chip is two lines tall
|
|
4685
|
+
/* A stacked chip is two lines tall — the legend shifts one more line. fit() flags the
|
|
4370
4686
|
* host whenever the ladder lands on a stacked layout. */
|
|
4371
4687
|
.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
|
|
4688
|
+
/* Mobile: the same ladder at the phone's type scale — the width alone decides how much
|
|
4373
4689
|
* of the readout fits, exactly as on a narrow desktop chart. */
|
|
4374
4690
|
[data-layout='mobile'] .vela-statusline { font-size: var(--vela-font-size-sm); }
|
|
4375
4691
|
[data-layout='mobile'] .vela-statusline .vela-sl-symbol { font-size: var(--vela-font-size-md); }
|
|
@@ -4378,14 +4694,16 @@ function baseOfTicker(ticker) {
|
|
|
4378
4694
|
return ticker.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || ticker;
|
|
4379
4695
|
}
|
|
4380
4696
|
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"
|
|
4697
|
+
open: msg("widget.statusline.market.open", "Market Open"),
|
|
4698
|
+
pre: msg("widget.statusline.market.pre", "Pre-Market"),
|
|
4699
|
+
post: msg("widget.statusline.market.post", "Post-Market"),
|
|
4700
|
+
extended: msg("widget.statusline.market.extended", "Extended Hours"),
|
|
4701
|
+
closed: msg("widget.statusline.market.closed", "Market Closed"),
|
|
4702
|
+
holiday: msg("widget.statusline.market.holiday", "Market Holiday")
|
|
4387
4703
|
};
|
|
4388
|
-
var REPLAY_LABEL = "Replay Mode";
|
|
4704
|
+
var REPLAY_LABEL = msg("widget.statusline.replay", "Replay Mode");
|
|
4705
|
+
var CHANGE_TEXT = msg("widget.statusline.change", "{change} ({percent})");
|
|
4706
|
+
var EM_DASH2 = "—";
|
|
4389
4707
|
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
4708
|
var MARKET_INKS = {
|
|
4391
4709
|
open: "var(--vela-up)",
|
|
@@ -4454,13 +4772,16 @@ var Statusline = class {
|
|
|
4454
4772
|
this.fitKey = null;
|
|
4455
4773
|
/** Web fonts landing after the last fit change every width without a resize. */
|
|
4456
4774
|
this.onFontsLoaded = () => this.fit();
|
|
4775
|
+
/** The bound chart's translator (English until {@link onChart}). */
|
|
4776
|
+
this.i18n = null;
|
|
4777
|
+
this.meta = null;
|
|
4457
4778
|
this.onContextMenu = (e) => {
|
|
4458
4779
|
if (!this.menu || !this.menuHooks) return;
|
|
4459
4780
|
e.preventDefault();
|
|
4460
4781
|
e.stopPropagation();
|
|
4461
4782
|
const chartVisible = this.menuHooks.chartVisible();
|
|
4462
4783
|
this.setChartHidden(!chartVisible);
|
|
4463
|
-
this.menu.setItems(statuslineMenuItems(this.parts, chartVisible));
|
|
4784
|
+
this.menu.setItems(statuslineMenuItems(this.parts, chartVisible, this.i18n));
|
|
4464
4785
|
this.menu.openAt(e.clientX, e.clientY);
|
|
4465
4786
|
};
|
|
4466
4787
|
const doc = host.ownerDocument;
|
|
@@ -4480,7 +4801,7 @@ var Statusline = class {
|
|
|
4480
4801
|
icon: "market-open",
|
|
4481
4802
|
background: `color-mix(in srgb, ${MARKET_INKS.open} 20%, transparent)`,
|
|
4482
4803
|
color: MARKET_INKS.open,
|
|
4483
|
-
label: MARKET_LABELS.open,
|
|
4804
|
+
label: this.t(MARKET_LABELS.open),
|
|
4484
4805
|
host
|
|
4485
4806
|
});
|
|
4486
4807
|
this.replayBadge = doc.createElement("span");
|
|
@@ -4494,11 +4815,12 @@ var Statusline = class {
|
|
|
4494
4815
|
this.ohlcEl.className = "vela-sl-ohlc";
|
|
4495
4816
|
this.changeEl = doc.createElement("span");
|
|
4496
4817
|
this.changeEl.className = "vela-sl-change";
|
|
4818
|
+
this.changeEl.dir = "ltr";
|
|
4497
4819
|
this.eyeEl = doc.createElement("button");
|
|
4498
4820
|
this.eyeEl.type = "button";
|
|
4499
4821
|
this.eyeEl.className = "vela-sl-eye";
|
|
4500
4822
|
this.eyeEl.innerHTML = iconAt("eye-off", 14);
|
|
4501
|
-
this.eyeEl.setAttribute("aria-label",
|
|
4823
|
+
this.eyeEl.setAttribute("aria-label", this.t(SHOW_CHART_LABEL));
|
|
4502
4824
|
this.eyeEl.style.display = "none";
|
|
4503
4825
|
this.eyeEl.addEventListener("click", (e) => {
|
|
4504
4826
|
e.stopPropagation();
|
|
@@ -4518,11 +4840,14 @@ var Statusline = class {
|
|
|
4518
4840
|
this.fitRO.observe(host);
|
|
4519
4841
|
}
|
|
4520
4842
|
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:
|
|
4843
|
+
this.marketTip = new Tooltip(this.marketEl, { content: this.t(MARKET_LABELS.open), placement: "bottom" });
|
|
4844
|
+
this.eyeTip = new Tooltip(this.eyeEl, { content: this.t(SHOW_CHART_LABEL), placement: "bottom" });
|
|
4523
4845
|
this.setMarketStatus("open");
|
|
4524
4846
|
this.render();
|
|
4525
4847
|
}
|
|
4848
|
+
t(text) {
|
|
4849
|
+
return textOf(this.i18n, text);
|
|
4850
|
+
}
|
|
4526
4851
|
setSymbol(symbol) {
|
|
4527
4852
|
const ticker = parseSymbol(symbol).ticker;
|
|
4528
4853
|
this.symbolEl.textContent = ticker;
|
|
@@ -4607,7 +4932,17 @@ var Statusline = class {
|
|
|
4607
4932
|
}
|
|
4608
4933
|
/** The "· BINANCE · 1h" segment after the symbol — venue first, then resolution. */
|
|
4609
4934
|
setMeta(timeframe, provider) {
|
|
4610
|
-
this.
|
|
4935
|
+
this.meta = { timeframe, provider };
|
|
4936
|
+
this.metaEl.textContent = `${provider ? `· ${provider.toUpperCase()} ` : ""}· ${timeframeLabel(timeframe, this.i18n)}`;
|
|
4937
|
+
this.fit();
|
|
4938
|
+
}
|
|
4939
|
+
/** Re-read every label and value in the chart's language. */
|
|
4940
|
+
relocalize() {
|
|
4941
|
+
this.eyeEl.setAttribute("aria-label", this.t(SHOW_CHART_LABEL));
|
|
4942
|
+
this.eyeTip.setContent(this.t(SHOW_CHART_LABEL));
|
|
4943
|
+
this.dressBadge();
|
|
4944
|
+
if (this.meta) this.setMeta(this.meta.timeframe, this.meta.provider);
|
|
4945
|
+
this.fitKey = null;
|
|
4611
4946
|
this.fit();
|
|
4612
4947
|
}
|
|
4613
4948
|
/** Dress the market badge for a session state: its icon, tinted circle, and the
|
|
@@ -4628,7 +4963,7 @@ var Statusline = class {
|
|
|
4628
4963
|
this.replayBadge.hidden = !this.replaying;
|
|
4629
4964
|
if (this.replaying) {
|
|
4630
4965
|
this.marketEl.dataset.status = "replay";
|
|
4631
|
-
this.marketTip.setContent(REPLAY_LABEL);
|
|
4966
|
+
this.marketTip.setContent(this.t(REPLAY_LABEL));
|
|
4632
4967
|
return;
|
|
4633
4968
|
}
|
|
4634
4969
|
const status = this.marketStatus;
|
|
@@ -4638,9 +4973,9 @@ var Statusline = class {
|
|
|
4638
4973
|
icon: `market-${status}`,
|
|
4639
4974
|
background: `color-mix(in srgb, ${ink} 20%, transparent)`,
|
|
4640
4975
|
color: ink,
|
|
4641
|
-
label: MARKET_LABELS[status]
|
|
4976
|
+
label: this.t(MARKET_LABELS[status])
|
|
4642
4977
|
});
|
|
4643
|
-
this.marketTip.setContent(MARKET_LABELS[status]);
|
|
4978
|
+
this.marketTip.setContent(this.t(MARKET_LABELS[status]));
|
|
4644
4979
|
}
|
|
4645
4980
|
/** Show/hide one part — the settings dialog's Status line tab and the right-click
|
|
4646
4981
|
* menu both drive these. 'name' owns the venue/timeframe meta too (see
|
|
@@ -4697,6 +5032,9 @@ var Statusline = class {
|
|
|
4697
5032
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
4698
5033
|
onChart(chart) {
|
|
4699
5034
|
this.detach();
|
|
5035
|
+
const relabel = this.i18n !== chart.i18n;
|
|
5036
|
+
this.i18n = chart.i18n;
|
|
5037
|
+
if (relabel) this.relocalize();
|
|
4700
5038
|
this.lastBar = null;
|
|
4701
5039
|
this.hoverBar = null;
|
|
4702
5040
|
this.unsubs.push(
|
|
@@ -4772,18 +5110,29 @@ var Statusline = class {
|
|
|
4772
5110
|
const doc = this.el.ownerDocument;
|
|
4773
5111
|
const up = this.isUp ? this.isUp(bar) : bar.close >= bar.open;
|
|
4774
5112
|
const ink = up ? this.upColor ?? "var(--vela-up)" : this.downColor ?? "var(--vela-down)";
|
|
5113
|
+
const format = this.i18n?.format;
|
|
5114
|
+
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 });
|
|
5115
|
+
const percent = (v) => format ? format.percent(v, { surface: "statusLine" }) : `${v >= 0 ? "+" : "-"}${Math.abs(v).toFixed(2)}%`;
|
|
4775
5116
|
const cell = (k, v) => {
|
|
4776
5117
|
const s = doc.createElement("span");
|
|
4777
5118
|
if (k) s.append(`${k} `);
|
|
4778
5119
|
const b = doc.createElement("b");
|
|
4779
|
-
b.
|
|
5120
|
+
b.dir = "ltr";
|
|
5121
|
+
b.textContent = price(v);
|
|
4780
5122
|
b.style.color = ink;
|
|
4781
5123
|
s.appendChild(b);
|
|
4782
5124
|
return s;
|
|
4783
5125
|
};
|
|
4784
5126
|
const { level } = this.layout;
|
|
4785
|
-
this.ohlcEl.replaceChildren(...readoutCells(level, this.readout).map((c) => cell(c.label, bar[c.key])));
|
|
4786
|
-
|
|
5127
|
+
this.ohlcEl.replaceChildren(...readoutCells(level, this.readout, this.i18n).map((c) => cell(c.label, bar[c.key])));
|
|
5128
|
+
let change = "";
|
|
5129
|
+
if (Number.isFinite(bar.open) && Number.isFinite(bar.close) && bar.open !== 0) {
|
|
5130
|
+
const diff = bar.close - bar.open;
|
|
5131
|
+
const pct = percent(diff / bar.open * 100);
|
|
5132
|
+
const signed = (diff >= 0 ? "+" : "") + price(diff);
|
|
5133
|
+
change = level === "minimal" ? pct : textOf(this.i18n, CHANGE_TEXT, { change: signed, percent: pct });
|
|
5134
|
+
}
|
|
5135
|
+
this.changeEl.textContent = change;
|
|
4787
5136
|
this.changeEl.dataset.dir = up ? "up" : "down";
|
|
4788
5137
|
this.changeEl.style.color = ink;
|
|
4789
5138
|
}
|
|
@@ -4798,7 +5147,7 @@ var CSS11 = `
|
|
|
4798
5147
|
.vela-watermark {
|
|
4799
5148
|
position: absolute;
|
|
4800
5149
|
/* Insets follow the renderer-published gutters AND the price-pane vertical
|
|
4801
|
-
* bounds (mount container), so the mark centers on the PRICE PANE
|
|
5150
|
+
* bounds (mount container), so the mark centers on the PRICE PANE — never
|
|
4802
5151
|
* the study panes below, the drawings toolbar, or the price scale. */
|
|
4803
5152
|
top: var(--vela-price-pane-top, 0px);
|
|
4804
5153
|
bottom: var(--vela-price-pane-bottom, 0px);
|
|
@@ -4832,8 +5181,14 @@ function watermarkFontPx(availPx, textPxAtMax) {
|
|
|
4832
5181
|
if (textPxAtMax <= 0) return MAX_FONT_PX;
|
|
4833
5182
|
return Math.max(MIN_FONT_PX, Math.min(MAX_FONT_PX, Math.floor(MAX_FONT_PX * availPx * FILL / textPxAtMax)));
|
|
4834
5183
|
}
|
|
5184
|
+
var TEXT12 = {
|
|
5185
|
+
replay: msg("widget.watermark.replay", "Replay"),
|
|
5186
|
+
mark: msg("widget.watermark.mark", "{symbol} · {timeframe}")
|
|
5187
|
+
};
|
|
4835
5188
|
var Watermark = class {
|
|
4836
|
-
|
|
5189
|
+
/** `i18n` reads the chart's translator (absent: English). */
|
|
5190
|
+
constructor(host, symbol, timeframe, i18n = () => null) {
|
|
5191
|
+
this.i18n = i18n;
|
|
4837
5192
|
this.resizeObserver = null;
|
|
4838
5193
|
/** The host's visibility preference (the persisted watermark toggle). */
|
|
4839
5194
|
this.shown = true;
|
|
@@ -4845,6 +5200,8 @@ var Watermark = class {
|
|
|
4845
5200
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
4846
5201
|
* fires during chart construction, before any subscriber can see it. */
|
|
4847
5202
|
this.loading = true;
|
|
5203
|
+
this.symbol = "";
|
|
5204
|
+
this.timeframe = "";
|
|
4848
5205
|
injectStyles(STYLE_ID11, CSS11, host.ownerDocument);
|
|
4849
5206
|
this.el = host.ownerDocument.createElement("div");
|
|
4850
5207
|
this.el.className = "vela-watermark";
|
|
@@ -4852,7 +5209,8 @@ var Watermark = class {
|
|
|
4852
5209
|
this.text = host.ownerDocument.createElement("span");
|
|
4853
5210
|
this.replayLine = host.ownerDocument.createElement("span");
|
|
4854
5211
|
this.replayLine.className = "vela-watermark-replay";
|
|
4855
|
-
this.
|
|
5212
|
+
this.replayText = host.ownerDocument.createTextNode(textOf(this.i18n(), TEXT12.replay));
|
|
5213
|
+
this.replayLine.append(iconEl("replay", host.ownerDocument), this.replayText);
|
|
4856
5214
|
this.el.append(this.text, this.replayLine);
|
|
4857
5215
|
host.appendChild(this.el);
|
|
4858
5216
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -4888,9 +5246,17 @@ var Watermark = class {
|
|
|
4888
5246
|
this.el.style.display = (this.shown || replay) && !this.loading ? "" : "none";
|
|
4889
5247
|
}
|
|
4890
5248
|
update(symbol, timeframe) {
|
|
4891
|
-
this.
|
|
5249
|
+
this.symbol = symbol;
|
|
5250
|
+
this.timeframe = timeframe;
|
|
5251
|
+
const i18n = this.i18n();
|
|
5252
|
+
this.text.textContent = symbol ? textOf(i18n, TEXT12.mark, { symbol: parseSymbol(symbol).ticker, timeframe: timeframeLabel(timeframe, i18n) }) : "";
|
|
4892
5253
|
this.fit();
|
|
4893
5254
|
}
|
|
5255
|
+
/** Re-read the mark and the replay line in the chart's language. */
|
|
5256
|
+
relocalize() {
|
|
5257
|
+
this.replayText.data = textOf(this.i18n(), TEXT12.replay);
|
|
5258
|
+
this.update(this.symbol, this.timeframe);
|
|
5259
|
+
}
|
|
4894
5260
|
/** Measure the text at the cap and shrink it to the price pane's width. */
|
|
4895
5261
|
fit() {
|
|
4896
5262
|
if (this.el.clientWidth <= 0 || !this.text.textContent) return;
|
|
@@ -4986,7 +5352,7 @@ var ChartContextMenu = class {
|
|
|
4986
5352
|
}
|
|
4987
5353
|
contributed(zone) {
|
|
4988
5354
|
return widgetActions(`context:${zone}`, this.context()).map((a) => ({
|
|
4989
|
-
item: { id: `action:${a.id}`, label: a.label, icon: a.icon },
|
|
5355
|
+
item: { id: `action:${a.id}`, label: textOf(this.chart?.i18n, a.label), icon: a.icon },
|
|
4990
5356
|
order: a.order
|
|
4991
5357
|
}));
|
|
4992
5358
|
}
|
|
@@ -4994,26 +5360,30 @@ var ChartContextMenu = class {
|
|
|
4994
5360
|
return composeMenu(zone, this.builtinItems(zone), this.contributed(zone));
|
|
4995
5361
|
}
|
|
4996
5362
|
builtinItems(zone) {
|
|
5363
|
+
const chart = this.chart;
|
|
4997
5364
|
if (zone === "price-axis") {
|
|
4998
5365
|
const pane = this.lastPane;
|
|
4999
5366
|
return priceAxisItems({
|
|
5000
5367
|
auto: this.chart?.renderer.get("autoScale") !== false,
|
|
5368
|
+
priceOnly: priceOnlyState(pane, this.flag("scalePriceOnly")),
|
|
5001
5369
|
invert: pane ? pane.invert : this.flag("invertScale"),
|
|
5002
5370
|
choice: scaleChoiceOf(pane ?? { mode: String(this.chart?.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
|
|
5003
5371
|
axisLabels: this.flag("axisLabels"),
|
|
5004
5372
|
priceLabel: this.flag("priceLabel"),
|
|
5005
5373
|
countdown: this.flag("countdown"),
|
|
5006
5374
|
priceLine: this.flag("currentPriceLine")
|
|
5007
|
-
});
|
|
5375
|
+
}, this.chart?.i18n);
|
|
5008
5376
|
}
|
|
5009
5377
|
if (zone === "time-axis") {
|
|
5010
|
-
return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"));
|
|
5378
|
+
return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"), chart?.i18n);
|
|
5011
5379
|
}
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5380
|
+
return bodyItems(
|
|
5381
|
+
{
|
|
5382
|
+
drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
|
|
5383
|
+
indicators: chart?.indicators().length ?? 0
|
|
5384
|
+
},
|
|
5385
|
+
chart?.i18n
|
|
5386
|
+
);
|
|
5017
5387
|
}
|
|
5018
5388
|
run(id) {
|
|
5019
5389
|
const chart = this.chart;
|
|
@@ -5034,6 +5404,8 @@ var ChartContextMenu = class {
|
|
|
5034
5404
|
else chart.renderer.set("timezone", resolveTimezone(zone, void 0));
|
|
5035
5405
|
} else if (id === "auto") {
|
|
5036
5406
|
chart.renderer.set("autoScale", chart.renderer.get("autoScale") === false);
|
|
5407
|
+
} else if (id === "price-only") {
|
|
5408
|
+
chart.renderer.set("scalePriceOnly", !this.flag("scalePriceOnly"));
|
|
5037
5409
|
} else if (id === "invert") {
|
|
5038
5410
|
const pane = this.lastPane;
|
|
5039
5411
|
const [feature, value] = invertWrite(!(pane ? pane.invert : this.flag("invertScale")), pane);
|
|
@@ -5410,6 +5782,14 @@ var CONTROLS_BOTTOM_PX = TIME_AXIS_H2 + 12;
|
|
|
5410
5782
|
var CLUSTER_H = 24;
|
|
5411
5783
|
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
5412
5784
|
var CLUSTER_LEFT_CSS = "calc((100% + var(--vela-toolbar-gutter, 0px) - var(--vela-scale-gutter, 0px)) / 2)";
|
|
5785
|
+
var TEXT13 = {
|
|
5786
|
+
zoomOut: msg("widget.cellControls.zoomOut", "Zoom out"),
|
|
5787
|
+
zoomIn: msg("widget.cellControls.zoomIn", "Zoom in"),
|
|
5788
|
+
restoreLayout: msg("widget.cellControls.restoreLayout", "Restore layout"),
|
|
5789
|
+
maximizeChart: msg("widget.cellControls.maximizeChart", "Maximize chart"),
|
|
5790
|
+
resetChart: msg("widget.cellControls.resetChart", "Reset chart"),
|
|
5791
|
+
dragToMove: msg("widget.cellControls.dragToMove", "Drag to move chart")
|
|
5792
|
+
};
|
|
5413
5793
|
var STYLE_ID12 = "vela-cell-controls";
|
|
5414
5794
|
var CSS12 = `
|
|
5415
5795
|
.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 +5850,17 @@ var CellControls = class {
|
|
|
5470
5850
|
this.host.appendChild(this.root);
|
|
5471
5851
|
this.refresh();
|
|
5472
5852
|
}
|
|
5473
|
-
/** Rebuild the buttons (the multi-cell gate
|
|
5853
|
+
/** Rebuild the buttons (the multi-cell gate, the maximized state or the language changed). */
|
|
5474
5854
|
refresh() {
|
|
5475
5855
|
this.root.textContent = "";
|
|
5476
5856
|
const multi = this.deps.multiCell();
|
|
5477
5857
|
const maximized = multi && this.deps.isMaximized();
|
|
5478
5858
|
if (multi && !maximized) this.root.appendChild(this.makeGrip());
|
|
5479
|
-
this.root.appendChild(this.button("minus",
|
|
5480
|
-
this.root.appendChild(this.button("plus",
|
|
5859
|
+
this.root.appendChild(this.button("minus", TEXT13.zoomOut, () => this.glider.zoom(ZOOM_OUT)));
|
|
5860
|
+
this.root.appendChild(this.button("plus", TEXT13.zoomIn, () => this.glider.zoom(ZOOM_IN)));
|
|
5481
5861
|
if (multi) {
|
|
5482
5862
|
this.root.appendChild(
|
|
5483
|
-
this.button(maximized ? "restore" : "maximize", maximized ?
|
|
5863
|
+
this.button(maximized ? "restore" : "maximize", maximized ? TEXT13.restoreLayout : TEXT13.maximizeChart, () => this.deps.toggleMaximize(), {
|
|
5484
5864
|
// The maximized state reads as an inverse chip (white-on-dark, dark-on-light),
|
|
5485
5865
|
// the same active-state affordance as a collapsed pane's expand button.
|
|
5486
5866
|
selected: maximized
|
|
@@ -5488,14 +5868,19 @@ var CellControls = class {
|
|
|
5488
5868
|
);
|
|
5489
5869
|
}
|
|
5490
5870
|
this.root.appendChild(
|
|
5491
|
-
this.button("reset",
|
|
5871
|
+
this.button("reset", TEXT13.resetChart, () => {
|
|
5492
5872
|
this.glider.stop();
|
|
5493
5873
|
this.deps.reset();
|
|
5494
5874
|
})
|
|
5495
5875
|
);
|
|
5496
5876
|
}
|
|
5497
|
-
|
|
5877
|
+
/** Text in the chart's language. */
|
|
5878
|
+
t(text) {
|
|
5879
|
+
return textOf(this.deps.chart()?.i18n, text);
|
|
5880
|
+
}
|
|
5881
|
+
button(iconId, text, onClick, opts = {}) {
|
|
5498
5882
|
const b = this.host.ownerDocument.createElement("button");
|
|
5883
|
+
const title = this.t(text);
|
|
5499
5884
|
b.type = "button";
|
|
5500
5885
|
b.title = title;
|
|
5501
5886
|
b.setAttribute("aria-label", title);
|
|
@@ -5513,8 +5898,8 @@ var CellControls = class {
|
|
|
5513
5898
|
makeGrip() {
|
|
5514
5899
|
const b = this.host.ownerDocument.createElement("button");
|
|
5515
5900
|
b.type = "button";
|
|
5516
|
-
b.title =
|
|
5517
|
-
b.setAttribute("aria-label",
|
|
5901
|
+
b.title = this.t(TEXT13.dragToMove);
|
|
5902
|
+
b.setAttribute("aria-label", b.title);
|
|
5518
5903
|
b.className = "vela-cc-btn vela-cc-grip";
|
|
5519
5904
|
b.innerHTML = icon("grip");
|
|
5520
5905
|
b.addEventListener("pointerdown", (e) => this.onGripDown(b, e));
|
|
@@ -5657,6 +6042,28 @@ var WidgetHistory = class {
|
|
|
5657
6042
|
};
|
|
5658
6043
|
|
|
5659
6044
|
// src/workspace/ChartCell.ts
|
|
6045
|
+
var TEXT14 = {
|
|
6046
|
+
regularHours: msg("workspace.settings.regularHours", "Regular hours (RTH)"),
|
|
6047
|
+
extendedHours: msg("workspace.settings.extendedHours", "Extended hours (ETH)"),
|
|
6048
|
+
extendedColor: msg("workspace.settings.extendedColor", "Extended hours"),
|
|
6049
|
+
premarketColor: msg("workspace.settings.premarketColor", "Pre-market"),
|
|
6050
|
+
postmarketColor: msg("workspace.settings.postmarketColor", "Post-market"),
|
|
6051
|
+
tradingSession: msg("workspace.settings.tradingSession", "Trading session"),
|
|
6052
|
+
session: msg("workspace.settings.session", "Session"),
|
|
6053
|
+
advanced: msg("workspace.settings.advanced", "Advanced"),
|
|
6054
|
+
barsToFetch: msg("workspace.settings.barsToFetch", "Bars to fetch"),
|
|
6055
|
+
timeZone: msg("workspace.settings.timeZone", "Time zone"),
|
|
6056
|
+
timeZoneRow: msg("workspace.settings.timeZoneRow", "Time zone"),
|
|
6057
|
+
watermark: msg("workspace.settings.watermark", "Watermark"),
|
|
6058
|
+
symbolWatermark: msg("workspace.settings.symbolWatermark", "Symbol watermark"),
|
|
6059
|
+
replayWatermark: msg("workspace.settings.replayWatermark", "Replay watermark"),
|
|
6060
|
+
statusLine: msg("workspace.settings.statusLine", "Status line"),
|
|
6061
|
+
statusLineParts: msg("workspace.settings.statusLineParts", "Status line"),
|
|
6062
|
+
indicators: msg("workspace.settings.indicators", "Indicators"),
|
|
6063
|
+
indicatorTitles: msg("workspace.settings.indicatorTitles", "Titles"),
|
|
6064
|
+
indicatorValues: msg("workspace.settings.indicatorValues", "Values"),
|
|
6065
|
+
builtIn: msg("workspace.indicators.builtIn", "Built-in")
|
|
6066
|
+
};
|
|
5660
6067
|
function seedDefaults(opts) {
|
|
5661
6068
|
return {
|
|
5662
6069
|
symbol: opts.symbol,
|
|
@@ -5688,6 +6095,16 @@ function instanceDeltas(handle) {
|
|
|
5688
6095
|
const props = inputDeltas(handle.props, handle.propValues());
|
|
5689
6096
|
return inputs || props ? { ...inputs ? { inputs } : {}, ...props ? { props } : {} } : void 0;
|
|
5690
6097
|
}
|
|
6098
|
+
function statuslinePartEntries(parts) {
|
|
6099
|
+
return Object.entries(parts ?? {}).filter((e) => typeof e[1] === "boolean");
|
|
6100
|
+
}
|
|
6101
|
+
function nativeAliases(type, i18n) {
|
|
6102
|
+
const entry = nativeIndicatorEntry(type);
|
|
6103
|
+
if (!entry) return {};
|
|
6104
|
+
const names = [sourceText(entry.title)];
|
|
6105
|
+
if (entry.shortTitle !== void 0) names.push(textOf(i18n, entry.shortTitle), sourceText(entry.shortTitle));
|
|
6106
|
+
return { aliases: [...new Set(names)] };
|
|
6107
|
+
}
|
|
5691
6108
|
var ChartCell = class {
|
|
5692
6109
|
constructor(id, gridHost, seed, deps) {
|
|
5693
6110
|
this.id = id;
|
|
@@ -5763,6 +6180,7 @@ var ChartCell = class {
|
|
|
5763
6180
|
this.host,
|
|
5764
6181
|
{
|
|
5765
6182
|
...deps.chartDefaults,
|
|
6183
|
+
...deps.localization?.(),
|
|
5766
6184
|
symbol,
|
|
5767
6185
|
timeframe: seed.timeframe,
|
|
5768
6186
|
bars: seed.bars,
|
|
@@ -5848,13 +6266,14 @@ var ChartCell = class {
|
|
|
5848
6266
|
if (!this.indicatorValuesOn) this.inner.renderer.set("indicatorValues", false);
|
|
5849
6267
|
this.watermarkOn = seed.watermark ?? deps.watermark;
|
|
5850
6268
|
this.replayWatermarkOn = seed.replayWatermark ?? true;
|
|
5851
|
-
this.watermark = deps.watermark ? new Watermark(this.host, symbol ?? "", seed.timeframe ?? "60") : null;
|
|
6269
|
+
this.watermark = deps.watermark ? new Watermark(this.host, symbol ?? "", seed.timeframe ?? "60", () => this.inner?.i18n) : null;
|
|
5852
6270
|
if (!this.watermarkOn) this.watermark?.setVisible(false);
|
|
5853
6271
|
if (!this.replayWatermarkOn) this.watermark?.setReplayVisible(false);
|
|
5854
6272
|
this.inner.on("replay:start", () => this.watermark?.setReplaying(true));
|
|
5855
6273
|
this.inner.on("replay:end", () => this.watermark?.setReplaying(false));
|
|
5856
6274
|
this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "", (sym) => this.inner?.data.symbolIcon(sym)) : null;
|
|
5857
6275
|
this.statusline?.setMeta(seed.timeframe ?? "60", this.state.provider ?? "");
|
|
6276
|
+
for (const [part, visible] of statuslinePartEntries(seed.statuslineParts)) this.statusline?.setPartVisible(part, visible);
|
|
5858
6277
|
this.statusline?.onChart(this.inner);
|
|
5859
6278
|
this.statusline?.attachMenu({
|
|
5860
6279
|
setPart: (part, visible) => this.setStatuslinePart(part, visible),
|
|
@@ -6076,12 +6495,10 @@ var ChartCell = class {
|
|
|
6076
6495
|
pushSettingsSections() {
|
|
6077
6496
|
const chart = this.inner;
|
|
6078
6497
|
if (!chart) return;
|
|
6079
|
-
const rth = "Regular hours (RTH)";
|
|
6080
|
-
const eth = "Extended hours (ETH)";
|
|
6081
6498
|
const shadeRows = this.sessionOvernightFlag ? [
|
|
6082
6499
|
{
|
|
6083
6500
|
kind: "color",
|
|
6084
|
-
label:
|
|
6501
|
+
label: TEXT14.extendedColor,
|
|
6085
6502
|
id: "extended-color",
|
|
6086
6503
|
get: () => this.sessionShadeColor("extendedColor"),
|
|
6087
6504
|
set: (v) => this.setSessionShadeColor("extendedColor", v)
|
|
@@ -6089,43 +6506,46 @@ var ChartCell = class {
|
|
|
6089
6506
|
] : [
|
|
6090
6507
|
{
|
|
6091
6508
|
kind: "color",
|
|
6092
|
-
label:
|
|
6509
|
+
label: TEXT14.premarketColor,
|
|
6093
6510
|
id: "premarket-color",
|
|
6094
6511
|
get: () => this.sessionShadeColor("premarketColor"),
|
|
6095
6512
|
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
6096
6513
|
},
|
|
6097
6514
|
{
|
|
6098
6515
|
kind: "color",
|
|
6099
|
-
label:
|
|
6516
|
+
label: TEXT14.postmarketColor,
|
|
6100
6517
|
id: "postmarket-color",
|
|
6101
6518
|
get: () => this.sessionShadeColor("postmarketColor"),
|
|
6102
6519
|
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
6103
6520
|
}
|
|
6104
6521
|
];
|
|
6105
6522
|
const sessionSection = {
|
|
6106
|
-
title:
|
|
6523
|
+
title: TEXT14.tradingSession,
|
|
6107
6524
|
id: "trading-session",
|
|
6108
6525
|
placement: "symbol",
|
|
6109
6526
|
rows: [
|
|
6110
6527
|
{
|
|
6111
6528
|
kind: "select",
|
|
6112
|
-
label:
|
|
6529
|
+
label: TEXT14.session,
|
|
6113
6530
|
id: "session",
|
|
6114
|
-
options: [
|
|
6115
|
-
|
|
6116
|
-
|
|
6531
|
+
options: [
|
|
6532
|
+
["regular", TEXT14.regularHours],
|
|
6533
|
+
["extended", TEXT14.extendedHours]
|
|
6534
|
+
],
|
|
6535
|
+
get: () => this.session === "extended" ? "extended" : "regular",
|
|
6536
|
+
set: (v) => this.setSession(v === "extended" ? "extended" : "regular")
|
|
6117
6537
|
},
|
|
6118
6538
|
...shadeRows
|
|
6119
6539
|
]
|
|
6120
6540
|
};
|
|
6121
6541
|
const advanced = {
|
|
6122
|
-
title:
|
|
6542
|
+
title: TEXT14.advanced,
|
|
6123
6543
|
id: "advanced",
|
|
6124
6544
|
placement: "end",
|
|
6125
6545
|
rows: [
|
|
6126
6546
|
{
|
|
6127
6547
|
kind: "select",
|
|
6128
|
-
label:
|
|
6548
|
+
label: TEXT14.barsToFetch,
|
|
6129
6549
|
id: "bars",
|
|
6130
6550
|
options: ["500", "1000", "2000", "5000", "10000", "20000", "50000", "60000", "80000", "100000"],
|
|
6131
6551
|
get: () => String(this.state.bars ?? 1e3),
|
|
@@ -6138,15 +6558,15 @@ var ChartCell = class {
|
|
|
6138
6558
|
]
|
|
6139
6559
|
};
|
|
6140
6560
|
const timezoneSection = {
|
|
6141
|
-
title:
|
|
6561
|
+
title: TEXT14.timeZone,
|
|
6142
6562
|
id: "time-zone",
|
|
6143
6563
|
placement: "symbol",
|
|
6144
6564
|
rows: [
|
|
6145
6565
|
{
|
|
6146
6566
|
kind: "select",
|
|
6147
|
-
label:
|
|
6567
|
+
label: TEXT14.timeZoneRow,
|
|
6148
6568
|
id: "zone",
|
|
6149
|
-
options: timezoneMenuRows(this.deps.timezone()).map((r) => [r.value, r.label]),
|
|
6569
|
+
options: timezoneMenuRows(this.deps.timezone(), chart.i18n).map((r) => [r.value, r.label]),
|
|
6150
6570
|
get: () => {
|
|
6151
6571
|
const choice = this.deps.timezone();
|
|
6152
6572
|
return isExchangeTimezone(choice) ? choice : normalizeTimezone(choice);
|
|
@@ -6156,20 +6576,20 @@ var ChartCell = class {
|
|
|
6156
6576
|
]
|
|
6157
6577
|
};
|
|
6158
6578
|
const watermarkSection = {
|
|
6159
|
-
title:
|
|
6579
|
+
title: TEXT14.watermark,
|
|
6160
6580
|
id: "watermark",
|
|
6161
6581
|
placement: "symbol",
|
|
6162
6582
|
rows: [
|
|
6163
6583
|
{
|
|
6164
6584
|
kind: "toggle",
|
|
6165
|
-
label:
|
|
6585
|
+
label: TEXT14.symbolWatermark,
|
|
6166
6586
|
id: "visible",
|
|
6167
6587
|
get: () => this.watermarkOn,
|
|
6168
6588
|
set: (v) => this.setWatermarkVisible(v)
|
|
6169
6589
|
},
|
|
6170
6590
|
{
|
|
6171
6591
|
kind: "toggle",
|
|
6172
|
-
label:
|
|
6592
|
+
label: TEXT14.replayWatermark,
|
|
6173
6593
|
id: "replay",
|
|
6174
6594
|
get: () => this.replayWatermarkOn,
|
|
6175
6595
|
set: (v) => this.setReplayWatermarkVisible(v)
|
|
@@ -6180,49 +6600,49 @@ var ChartCell = class {
|
|
|
6180
6600
|
if (this.statusline) {
|
|
6181
6601
|
const sl = this.statusline;
|
|
6182
6602
|
sections.push({
|
|
6183
|
-
title:
|
|
6603
|
+
title: TEXT14.statusLine,
|
|
6184
6604
|
id: "status-line",
|
|
6185
6605
|
rows: [
|
|
6186
|
-
{ kind: "heading", label:
|
|
6606
|
+
{ kind: "heading", label: TEXT14.statusLineParts, id: "parts" },
|
|
6187
6607
|
{
|
|
6188
6608
|
kind: "toggle",
|
|
6189
|
-
label:
|
|
6609
|
+
label: STATUSLINE_PART_LABELS.name,
|
|
6190
6610
|
id: "name",
|
|
6191
6611
|
get: () => sl.partVisible("name"),
|
|
6192
6612
|
set: (v) => this.setStatuslinePart("name", v)
|
|
6193
6613
|
},
|
|
6194
6614
|
{
|
|
6195
6615
|
kind: "toggle",
|
|
6196
|
-
label:
|
|
6616
|
+
label: STATUSLINE_PART_LABELS.market,
|
|
6197
6617
|
id: "market",
|
|
6198
6618
|
get: () => sl.partVisible("market"),
|
|
6199
6619
|
set: (v) => this.setStatuslinePart("market", v)
|
|
6200
6620
|
},
|
|
6201
6621
|
{
|
|
6202
6622
|
kind: "toggle",
|
|
6203
|
-
label:
|
|
6623
|
+
label: STATUSLINE_PART_LABELS.ohlc,
|
|
6204
6624
|
id: "ohlc",
|
|
6205
6625
|
get: () => sl.partVisible("ohlc"),
|
|
6206
6626
|
set: (v) => this.setStatuslinePart("ohlc", v)
|
|
6207
6627
|
},
|
|
6208
6628
|
{
|
|
6209
6629
|
kind: "toggle",
|
|
6210
|
-
label:
|
|
6630
|
+
label: STATUSLINE_PART_LABELS.change,
|
|
6211
6631
|
id: "change",
|
|
6212
6632
|
get: () => sl.partVisible("change"),
|
|
6213
6633
|
set: (v) => this.setStatuslinePart("change", v)
|
|
6214
6634
|
},
|
|
6215
|
-
{ kind: "heading", label:
|
|
6635
|
+
{ kind: "heading", label: TEXT14.indicators, id: "indicators" },
|
|
6216
6636
|
{
|
|
6217
6637
|
kind: "toggle",
|
|
6218
|
-
label:
|
|
6638
|
+
label: TEXT14.indicatorTitles,
|
|
6219
6639
|
id: "indicator-titles",
|
|
6220
6640
|
get: () => this.indicatorTitlesOn,
|
|
6221
6641
|
set: (v) => this.setIndicatorTitlesVisible(v)
|
|
6222
6642
|
},
|
|
6223
6643
|
{
|
|
6224
6644
|
kind: "toggle",
|
|
6225
|
-
label:
|
|
6645
|
+
label: TEXT14.indicatorValues,
|
|
6226
6646
|
id: "indicator-values",
|
|
6227
6647
|
get: () => this.indicatorValuesOn,
|
|
6228
6648
|
set: (v) => this.setIndicatorValuesVisible(v)
|
|
@@ -6266,6 +6686,7 @@ var ChartCell = class {
|
|
|
6266
6686
|
/** Show/hide one status-line segment (the settings dialog's Status line tab). */
|
|
6267
6687
|
setStatuslinePart(part, visible) {
|
|
6268
6688
|
this.statusline?.setPartVisible(part, visible);
|
|
6689
|
+
this.deps.onStateDirty();
|
|
6269
6690
|
this.deps.onStatusPrefsChanged(this.id);
|
|
6270
6691
|
}
|
|
6271
6692
|
/** This cell's Status line tab and watermark prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
@@ -6299,6 +6720,16 @@ var ChartCell = class {
|
|
|
6299
6720
|
if (!this.inner) throw new Error(`[vela] cell "${this.id}" is destroyed`);
|
|
6300
6721
|
return this.inner;
|
|
6301
6722
|
}
|
|
6723
|
+
/** Re-read the cell's own chrome (status line, watermark, view controls, settings rows,
|
|
6724
|
+
* native indicator names) in the chart's language, after `chart.setLocale`. */
|
|
6725
|
+
relocalize() {
|
|
6726
|
+
if (!this.inner) return;
|
|
6727
|
+
this.statusline?.relocalize();
|
|
6728
|
+
this.watermark?.relocalize();
|
|
6729
|
+
this.cellControls.refresh();
|
|
6730
|
+
this.pushSettingsSections();
|
|
6731
|
+
this.refreshNativeCatalog();
|
|
6732
|
+
}
|
|
6302
6733
|
get symbol() {
|
|
6303
6734
|
return this.state.symbol ?? "";
|
|
6304
6735
|
}
|
|
@@ -6487,20 +6918,29 @@ var ChartCell = class {
|
|
|
6487
6918
|
* index space the picker hands back, so `libraryRows` and `addFromLibrary` MUST both
|
|
6488
6919
|
* read it — indexing the unsorted catalog on add would land on a different study. */
|
|
6489
6920
|
supportedNatives() {
|
|
6490
|
-
|
|
6921
|
+
const locale = this.inner?.i18n.locale ?? "en";
|
|
6922
|
+
const collator = new Intl.Collator(locale, { sensitivity: "base" });
|
|
6923
|
+
return this.nativeCatalog.filter((n) => n.supported).sort((a, b) => collator.compare(a.title, b.title));
|
|
6491
6924
|
}
|
|
6492
6925
|
/** The picker's library rows: supported natives first (see {@link supportedNatives}),
|
|
6493
6926
|
* then the manifest in the host's own order. */
|
|
6494
6927
|
libraryRows() {
|
|
6495
6928
|
return [
|
|
6496
|
-
...this.supportedNatives().map((n) => ({
|
|
6929
|
+
...this.supportedNatives().map((n) => ({
|
|
6930
|
+
name: n.title,
|
|
6931
|
+
...nativeAliases(n.type, this.inner?.i18n),
|
|
6932
|
+
category: textOf(this.inner?.i18n, TEXT14.builtIn),
|
|
6933
|
+
native: true,
|
|
6934
|
+
nativeType: n.type,
|
|
6935
|
+
beta: n.beta
|
|
6936
|
+
})),
|
|
6497
6937
|
...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
|
|
6498
6938
|
];
|
|
6499
6939
|
}
|
|
6500
6940
|
/** The picker's on-chart rows: native instances first, then live script instances. */
|
|
6501
6941
|
onChartRows() {
|
|
6502
6942
|
return [
|
|
6503
|
-
...this.nativeHandles().map((h) => ({ name: h.title, native: true, nativeType: h.nativeType })),
|
|
6943
|
+
...this.nativeHandles().map((h) => ({ name: h.title, ...nativeAliases(h.nativeType, this.inner?.i18n), native: true, nativeType: h.nativeType })),
|
|
6504
6944
|
...this.instances.map((it) => ({ name: it.entry.name, language: it.entry.language }))
|
|
6505
6945
|
];
|
|
6506
6946
|
}
|
|
@@ -6729,6 +7169,9 @@ var ChartCell = class {
|
|
|
6729
7169
|
if (cs.replayWatermark !== void 0 && cs.replayWatermark !== this.replayWatermarkOn) this.setReplayWatermarkVisible(cs.replayWatermark);
|
|
6730
7170
|
if (cs.indicatorTitles !== void 0 && cs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(cs.indicatorTitles);
|
|
6731
7171
|
if (cs.indicatorValues !== void 0 && cs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(cs.indicatorValues);
|
|
7172
|
+
for (const [part, visible] of statuslinePartEntries(cs.statuslineParts)) {
|
|
7173
|
+
if (this.statusline && this.statusline.partVisible(part) !== visible) this.setStatuslinePart(part, visible);
|
|
7174
|
+
}
|
|
6732
7175
|
if (cs.rendererConfig != null) this.inner.renderer.applyConfig(cs.rendererConfig);
|
|
6733
7176
|
if (cs.drawings != null) this.inner.drawings.fromJSON(cs.drawings);
|
|
6734
7177
|
if (cs.indicators) this.applyIndicatorLedger(cs.indicators);
|
|
@@ -6756,6 +7199,7 @@ var ChartCell = class {
|
|
|
6756
7199
|
dehydrate() {
|
|
6757
7200
|
const live = this.inner?.market;
|
|
6758
7201
|
const ext = this.inner ? this.dehydrateExt() : Object.keys(this.extState).length > 0 ? { ...this.extState } : void 0;
|
|
7202
|
+
const statuslineParts = this.statusPrefs().parts;
|
|
6759
7203
|
return {
|
|
6760
7204
|
...this.state,
|
|
6761
7205
|
...live ? { symbol: live.symbol, provider: live.provider, timeframe: live.timeframe } : {},
|
|
@@ -6764,6 +7208,7 @@ var ChartCell = class {
|
|
|
6764
7208
|
replayWatermark: this.replayWatermarkOn,
|
|
6765
7209
|
indicatorTitles: this.indicatorTitlesOn,
|
|
6766
7210
|
indicatorValues: this.indicatorValuesOn,
|
|
7211
|
+
...statuslineParts ? { statuslineParts } : {},
|
|
6767
7212
|
rendererConfig: this.inner?.renderer.getConfig() ?? void 0,
|
|
6768
7213
|
drawings: this.inner ? this.inner.drawings.toJSON() : void 0,
|
|
6769
7214
|
// Natives from the chart's SYNC registry read — an async catalog mirror here
|
|
@@ -6870,7 +7315,7 @@ var WorkspaceReplay = class {
|
|
|
6870
7315
|
if (!this.session) return this;
|
|
6871
7316
|
if (intervalMs !== void 0) {
|
|
6872
7317
|
if (!(Number.isFinite(intervalMs) && intervalMs > 0)) {
|
|
6873
|
-
console.warn(`[vela] workspace.replay.play(${intervalMs}) ignored
|
|
7318
|
+
console.warn(`[vela] workspace.replay.play(${intervalMs}) ignored — the interval must be a positive number of ms`);
|
|
6874
7319
|
return this;
|
|
6875
7320
|
}
|
|
6876
7321
|
this.intervalMs = intervalMs;
|
|
@@ -7098,27 +7543,37 @@ function monthSpan(timeframe) {
|
|
|
7098
7543
|
|
|
7099
7544
|
// src/workspace/layouts.ts
|
|
7100
7545
|
var registry = /* @__PURE__ */ new Map();
|
|
7546
|
+
var labels = /* @__PURE__ */ new Map();
|
|
7101
7547
|
function registerLayout(def) {
|
|
7102
|
-
registry.set(def.id, def);
|
|
7548
|
+
registry.set(def.id, typeof def.label === "string" ? def : { ...def, label: sourceText(def.label) });
|
|
7549
|
+
labels.set(def.id, def.label);
|
|
7103
7550
|
}
|
|
7104
7551
|
function unregisterLayout(id) {
|
|
7105
7552
|
registry.delete(id);
|
|
7553
|
+
labels.delete(id);
|
|
7106
7554
|
}
|
|
7107
|
-
function
|
|
7108
|
-
|
|
7555
|
+
function inLanguage(def, i18n) {
|
|
7556
|
+
const grid = GRID_ID_RE.exec(def.id);
|
|
7557
|
+
const message = labels.get(def.id) ?? (grid ? gridLabel(Number(grid[1]), Number(grid[2])) : def.label);
|
|
7558
|
+
const label = textOf(i18n, message);
|
|
7559
|
+
return label === def.label ? def : { ...def, label };
|
|
7109
7560
|
}
|
|
7110
|
-
function
|
|
7111
|
-
|
|
7561
|
+
function layoutDefinition(id, i18n) {
|
|
7562
|
+
const def = registry.get(id);
|
|
7563
|
+
return def && inLanguage(def, i18n);
|
|
7564
|
+
}
|
|
7565
|
+
function layouts(i18n) {
|
|
7566
|
+
return [...registry.values()].map((def) => inLanguage(def, i18n));
|
|
7112
7567
|
}
|
|
7113
7568
|
function slots(n) {
|
|
7114
7569
|
return Array.from({ length: n }, (_, i) => ({ id: `c${i + 1}` }));
|
|
7115
7570
|
}
|
|
7116
7571
|
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) });
|
|
7572
|
+
registerLayout({ id: "1", label: msg("workspace.layout.single", "Single"), cols: [1], rows: [1], cells: slots(1) });
|
|
7573
|
+
registerLayout({ id: "2h", label: msg("workspace.layout.sideBySide", "2 side by side"), cols: [1, 1], rows: [1], cells: slots(2) });
|
|
7574
|
+
registerLayout({ id: "2v", label: msg("workspace.layout.stacked", "2 stacked"), cols: [1], rows: [1, 1], cells: slots(2) });
|
|
7575
|
+
registerLayout({ id: "4", label: msg("workspace.layout.grid4", "4 grid"), cols: [1, 1], rows: [1, 1], cells: slots(4) });
|
|
7576
|
+
registerLayout({ id: "8", label: msg("workspace.layout.grid8", "8 grid"), cols: [1, 1, 1, 1], rows: [1, 1], cells: slots(8) });
|
|
7122
7577
|
}
|
|
7123
7578
|
var GRID_PICKER_MAX = 4;
|
|
7124
7579
|
var GRID_BUILTIN_IDS = {
|
|
@@ -7128,17 +7583,20 @@ var GRID_BUILTIN_IDS = {
|
|
|
7128
7583
|
"2x2": "4",
|
|
7129
7584
|
"2x4": "8"
|
|
7130
7585
|
};
|
|
7586
|
+
var GRID_LABEL = msg("workspace.layout.grid", "{cols} × {rows} grid");
|
|
7587
|
+
function gridLabel(rows, cols) {
|
|
7588
|
+
return withValues(GRID_LABEL, { cols, rows });
|
|
7589
|
+
}
|
|
7131
7590
|
var clampTrack = (n) => Math.max(1, Math.min(GRID_PICKER_MAX, Math.round(n)));
|
|
7132
|
-
function layoutForGrid(rows, cols) {
|
|
7591
|
+
function layoutForGrid(rows, cols, i18n) {
|
|
7133
7592
|
const r = clampTrack(rows);
|
|
7134
7593
|
const c = clampTrack(cols);
|
|
7135
7594
|
const builtin = GRID_BUILTIN_IDS[`${r}x${c}`];
|
|
7136
7595
|
const registered = builtin ? registry.get(builtin) : void 0;
|
|
7137
|
-
if (registered) return registered;
|
|
7596
|
+
if (registered) return inLanguage(registered, i18n);
|
|
7138
7597
|
return {
|
|
7139
7598
|
id: `g${r}x${c}`,
|
|
7140
|
-
|
|
7141
|
-
label: `${c} \xD7 ${r} grid`,
|
|
7599
|
+
label: textOf(i18n, gridLabel(r, c)),
|
|
7142
7600
|
cols: Array.from({ length: c }, () => 1),
|
|
7143
7601
|
rows: Array.from({ length: r }, () => 1),
|
|
7144
7602
|
cells: slots(r * c)
|
|
@@ -7161,7 +7619,7 @@ function layoutShape(def) {
|
|
|
7161
7619
|
function occupancyGrid(def) {
|
|
7162
7620
|
if (def.areas) return def.areas.map((row) => row.trim().split(/\s+/));
|
|
7163
7621
|
const cols = def.cols.length;
|
|
7164
|
-
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ??
|
|
7622
|
+
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `·${r}x${c}`));
|
|
7165
7623
|
}
|
|
7166
7624
|
var GLYPH_MIN = 1.5;
|
|
7167
7625
|
var GLYPH_SPAN = 13;
|
|
@@ -7452,8 +7910,8 @@ var TOOL_BINDINGS = [
|
|
|
7452
7910
|
["hline", "drawings.hline-cursor"],
|
|
7453
7911
|
["vline", "drawings.vline-cursor"]
|
|
7454
7912
|
];
|
|
7455
|
-
function toolShortcutHints(keymap) {
|
|
7456
|
-
const byId = new Map(keymap.bindings().map((b) => [b.id, b.display[0]]));
|
|
7913
|
+
function toolShortcutHints(keymap, i18n) {
|
|
7914
|
+
const byId = new Map(keymap.bindings(i18n).map((b) => [b.id, b.display[0]]));
|
|
7457
7915
|
const out = {};
|
|
7458
7916
|
for (const [type, id] of TOOL_BINDINGS) {
|
|
7459
7917
|
const display = byId.get(id);
|
|
@@ -7551,10 +8009,10 @@ var CSS14 = `
|
|
|
7551
8009
|
.vela-mb-item:active { background: var(--vela-hover); }
|
|
7552
8010
|
.vela-mb-item .vela-icon { font-size: 18px; width: 18px; height: 18px; }
|
|
7553
8011
|
/* A lit stop (the maximize toggle while something is isolated): the inverse
|
|
7554
|
-
"selected" chip
|
|
8012
|
+
"selected" chip — white on the dark theme, dark on the light one. */
|
|
7555
8013
|
.vela-mb-item.vela-mb-on, .vela-mb-item.vela-mb-on:active { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
7556
8014
|
/* Left-aligned contributed actions get their own stops (the built-in indicators
|
|
7557
|
-
slot)
|
|
8015
|
+
slot) — the wrapper is layout-transparent so each stop flexes like a sibling. */
|
|
7558
8016
|
.vela-mb-actions { display: contents; }
|
|
7559
8017
|
.vela-mb-symbol {
|
|
7560
8018
|
flex: 1.6 1 0;
|
|
@@ -7568,39 +8026,57 @@ var CSS14 = `
|
|
|
7568
8026
|
text-align: center;
|
|
7569
8027
|
}
|
|
7570
8028
|
`;
|
|
8029
|
+
var TEXT15 = {
|
|
8030
|
+
symbolSearch: msg("widget.mobileBar.symbolSearch", "Symbol search"),
|
|
8031
|
+
timeframe: msg("widget.mobileBar.timeframe", "Timeframe"),
|
|
8032
|
+
indicators: msg("widget.mobileBar.indicators", "Indicators"),
|
|
8033
|
+
drawings: msg("widget.mobileBar.drawings", "Drawings"),
|
|
8034
|
+
maximizeChart: msg("widget.mobileBar.maximizeChart", "Maximize chart"),
|
|
8035
|
+
restoreLayout: msg("widget.mobileBar.restoreLayout", "Restore layout"),
|
|
8036
|
+
more: msg("widget.mobileBar.more", "More"),
|
|
8037
|
+
chartSettings: msg("widget.mobileBar.chartSettings", "Chart settings")
|
|
8038
|
+
};
|
|
7571
8039
|
var MobileBar = class {
|
|
7572
8040
|
constructor(host, opts) {
|
|
8041
|
+
/** Each fixed stop's accessible name, re-read by {@link relocalize}. */
|
|
8042
|
+
this.labels = [];
|
|
8043
|
+
this.maximizeActive = false;
|
|
7573
8044
|
this.opts = opts;
|
|
7574
8045
|
const doc = host.ownerDocument;
|
|
7575
8046
|
injectStyles(STYLE_ID14, CSS14, doc);
|
|
7576
8047
|
this.el = doc.createElement("div");
|
|
7577
8048
|
this.el.className = "vela-mobilebar";
|
|
8049
|
+
this.timeframe = opts.timeframe;
|
|
7578
8050
|
const item = (cls, label, onClick, icon2) => {
|
|
7579
8051
|
const b = doc.createElement("button");
|
|
7580
8052
|
b.className = `vela-mb-item ${cls}`;
|
|
7581
|
-
b.setAttribute("aria-label", label);
|
|
8053
|
+
b.setAttribute("aria-label", this.t(label));
|
|
8054
|
+
this.labels.push({ el: b, label });
|
|
7582
8055
|
if (icon2) b.appendChild(iconEl(icon2, doc));
|
|
7583
8056
|
b.addEventListener("click", onClick);
|
|
7584
8057
|
return b;
|
|
7585
8058
|
};
|
|
7586
|
-
this.symbolEl = item("vela-mb-symbol",
|
|
8059
|
+
this.symbolEl = item("vela-mb-symbol", TEXT15.symbolSearch, opts.onSymbolClick);
|
|
7587
8060
|
this.symbolEl.textContent = parseSymbol(opts.symbol).ticker;
|
|
7588
|
-
this.tfEl = item("vela-mb-tf",
|
|
7589
|
-
this.tfEl.textContent = timeframeLabel(opts.timeframe);
|
|
8061
|
+
this.tfEl = item("vela-mb-tf", TEXT15.timeframe, opts.onTimeframeClick);
|
|
8062
|
+
this.tfEl.textContent = timeframeLabel(opts.timeframe, opts.i18n);
|
|
7590
8063
|
const onIndicators = opts.onIndicatorsClick;
|
|
7591
|
-
const indicators = onIndicators ? item("vela-mb-indicators",
|
|
8064
|
+
const indicators = onIndicators ? item("vela-mb-indicators", TEXT15.indicators, onIndicators, "indicators") : null;
|
|
7592
8065
|
this.actionsHost = doc.createElement("span");
|
|
7593
8066
|
this.actionsHost.className = "vela-mb-actions";
|
|
7594
8067
|
const onDrawings = opts.onDrawingsClick;
|
|
7595
|
-
const drawings = onDrawings ? item("vela-mb-drawings",
|
|
8068
|
+
const drawings = onDrawings ? item("vela-mb-drawings", TEXT15.drawings, onDrawings, "pen") : null;
|
|
7596
8069
|
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",
|
|
8070
|
+
this.maxEl = onMaximize ? item("vela-mb-maximize", TEXT15.maximizeChart, onMaximize, "maximize") : null;
|
|
8071
|
+
const more = item("vela-mb-more", TEXT15.more, opts.onMoreClick, "kebab");
|
|
8072
|
+
const settings = item("vela-mb-settings", TEXT15.chartSettings, opts.onSettingsClick, "gear");
|
|
7600
8073
|
this.el.append(this.symbolEl, this.tfEl, ...indicators ? [indicators] : [], this.actionsHost, ...drawings ? [drawings] : [], ...this.maxEl ? [this.maxEl] : [], more, settings);
|
|
7601
8074
|
host.appendChild(this.el);
|
|
7602
8075
|
this.renderActions();
|
|
7603
8076
|
}
|
|
8077
|
+
t(text) {
|
|
8078
|
+
return textOf(this.opts.i18n, text);
|
|
8079
|
+
}
|
|
7604
8080
|
/** Re-project the contributed actions placed on the bar (left-aligned by default —
|
|
7605
8081
|
* see `mobilePlacement`) as icon-only stops in the indicators slot (call after
|
|
7606
8082
|
* registrations change). The rest are three-dots drawer rows. */
|
|
@@ -7613,9 +8089,10 @@ var MobileBar = class {
|
|
|
7613
8089
|
for (const action of widgetActions("topbar", ctx).filter((a) => mobilePlacement(a) === "bar" && !builtin.has(a.id))) {
|
|
7614
8090
|
const b = doc.createElement("button");
|
|
7615
8091
|
b.className = "vela-mb-item";
|
|
7616
|
-
|
|
8092
|
+
const label = textOf(ctx.i18n ?? this.opts.i18n, action.label);
|
|
8093
|
+
b.setAttribute("aria-label", label);
|
|
7617
8094
|
if (action.icon) b.appendChild(iconEl(action.icon, doc));
|
|
7618
|
-
else b.appendChild(doc.createTextNode(
|
|
8095
|
+
else b.appendChild(doc.createTextNode(label));
|
|
7619
8096
|
b.addEventListener("click", () => {
|
|
7620
8097
|
const c = this.opts.getContext?.();
|
|
7621
8098
|
if (c) action.run(c);
|
|
@@ -7627,14 +8104,23 @@ var MobileBar = class {
|
|
|
7627
8104
|
this.symbolEl.textContent = parseSymbol(symbol).ticker;
|
|
7628
8105
|
}
|
|
7629
8106
|
setTimeframe(tf) {
|
|
7630
|
-
this.
|
|
8107
|
+
this.timeframe = tf;
|
|
8108
|
+
this.tfEl.textContent = timeframeLabel(tf, this.opts.i18n);
|
|
8109
|
+
}
|
|
8110
|
+
/** Re-read every label in the current language. */
|
|
8111
|
+
relocalize() {
|
|
8112
|
+
for (const { el, label } of this.labels) el.setAttribute("aria-label", this.t(label));
|
|
8113
|
+
this.setTimeframe(this.timeframe);
|
|
8114
|
+
if (this.maxEl) this.setMaximizeActive(this.maximizeActive);
|
|
8115
|
+
this.renderActions();
|
|
7631
8116
|
}
|
|
7632
8117
|
/** Light the maximize stop while something is isolated (a chart over the grid,
|
|
7633
8118
|
* or a maximized pane inside the active chart) — inverse chip + restore glyph. */
|
|
7634
8119
|
setMaximizeActive(on) {
|
|
7635
8120
|
if (!this.maxEl) return;
|
|
8121
|
+
this.maximizeActive = on;
|
|
7636
8122
|
this.maxEl.classList.toggle("vela-mb-on", on);
|
|
7637
|
-
this.maxEl.setAttribute("aria-label", on ?
|
|
8123
|
+
this.maxEl.setAttribute("aria-label", this.t(on ? TEXT15.restoreLayout : TEXT15.maximizeChart));
|
|
7638
8124
|
this.maxEl.replaceChildren(iconEl(on ? "restore" : "maximize", this.el.ownerDocument));
|
|
7639
8125
|
}
|
|
7640
8126
|
/** Show the maximize stop only while the grid holds more than one chart — on a
|
|
@@ -7700,23 +8186,28 @@ var CSS15 = `
|
|
|
7700
8186
|
background: var(--vela-hover);
|
|
7701
8187
|
}
|
|
7702
8188
|
`;
|
|
8189
|
+
var TEXT16 = {
|
|
8190
|
+
dateRange: msg("widget.timeframeDrawer.dateRange", "Date Range"),
|
|
8191
|
+
timeframes: msg("widget.timeframeDrawer.timeframes", "Timeframes")
|
|
8192
|
+
};
|
|
7703
8193
|
var TimeframeDrawer = class {
|
|
7704
8194
|
constructor(opts) {
|
|
7705
8195
|
this.opts = opts;
|
|
7706
8196
|
this.rangeChips = /* @__PURE__ */ new Map();
|
|
7707
8197
|
this.tfChips = /* @__PURE__ */ new Map();
|
|
8198
|
+
this.headings = [];
|
|
7708
8199
|
const doc = opts.host.ownerDocument;
|
|
7709
8200
|
injectStyles(STYLE_ID15, CSS15, doc);
|
|
7710
8201
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
7711
8202
|
const rangeHeading = doc.createElement("div");
|
|
7712
8203
|
rangeHeading.className = "vela-tfd-heading";
|
|
7713
|
-
|
|
8204
|
+
this.headings.push({ el: rangeHeading, text: TEXT16.dateRange });
|
|
7714
8205
|
const ranges = doc.createElement("div");
|
|
7715
8206
|
ranges.className = "vela-tfd-ranges";
|
|
7716
8207
|
for (const preset of opts.ranges) {
|
|
7717
8208
|
const chip = doc.createElement("button");
|
|
7718
8209
|
chip.className = "vela-tfd-chip";
|
|
7719
|
-
chip.textContent = preset.id;
|
|
8210
|
+
chip.textContent = rangeLabel(preset.id, opts.i18n);
|
|
7720
8211
|
chip.addEventListener("click", () => {
|
|
7721
8212
|
opts.onRange(preset);
|
|
7722
8213
|
this.drawer.hide();
|
|
@@ -7726,13 +8217,13 @@ var TimeframeDrawer = class {
|
|
|
7726
8217
|
}
|
|
7727
8218
|
const tfHeading = doc.createElement("div");
|
|
7728
8219
|
tfHeading.className = "vela-tfd-heading";
|
|
7729
|
-
tfHeading.
|
|
8220
|
+
this.headings.push({ el: tfHeading, text: TEXT16.timeframes });
|
|
7730
8221
|
const grid = doc.createElement("div");
|
|
7731
8222
|
grid.className = "vela-tfd-grid";
|
|
7732
8223
|
for (const tf of opts.timeframes) {
|
|
7733
8224
|
const chip = doc.createElement("button");
|
|
7734
8225
|
chip.className = "vela-tfd-chip";
|
|
7735
|
-
chip.textContent = timeframeLabel(tf);
|
|
8226
|
+
chip.textContent = timeframeLabel(tf, opts.i18n);
|
|
7736
8227
|
chip.addEventListener("click", () => {
|
|
7737
8228
|
opts.onTimeframe(tf);
|
|
7738
8229
|
this.drawer.hide();
|
|
@@ -7741,8 +8232,17 @@ var TimeframeDrawer = class {
|
|
|
7741
8232
|
grid.appendChild(chip);
|
|
7742
8233
|
}
|
|
7743
8234
|
this.drawer.body.append(rangeHeading, ranges, tfHeading, grid);
|
|
8235
|
+
this.relabel();
|
|
8236
|
+
}
|
|
8237
|
+
/** Headings and chips in the current language. */
|
|
8238
|
+
relabel() {
|
|
8239
|
+
const i18n = this.opts.i18n;
|
|
8240
|
+
for (const { el, text } of this.headings) el.textContent = textOf(i18n, text);
|
|
8241
|
+
for (const [id, chip] of this.rangeChips) chip.textContent = rangeLabel(id, i18n);
|
|
8242
|
+
for (const [tf, chip] of this.tfChips) chip.textContent = timeframeLabel(tf, i18n);
|
|
7744
8243
|
}
|
|
7745
8244
|
open() {
|
|
8245
|
+
this.relabel();
|
|
7746
8246
|
const tf = this.opts.currentTimeframe();
|
|
7747
8247
|
const range = this.opts.activeRange();
|
|
7748
8248
|
for (const [id, chip] of this.rangeChips) {
|
|
@@ -7764,6 +8264,12 @@ var TimeframeDrawer = class {
|
|
|
7764
8264
|
};
|
|
7765
8265
|
|
|
7766
8266
|
// src/widget/drawings-drawer.ts
|
|
8267
|
+
var TEXT17 = {
|
|
8268
|
+
title: msg("widget.drawingsDrawer.title", "Drawings"),
|
|
8269
|
+
search: msg("widget.drawingsDrawer.search", "Search tools…"),
|
|
8270
|
+
favorite: msg("widget.drawingsDrawer.favorite", "Favorite {tool}"),
|
|
8271
|
+
noMatch: msg("widget.drawingsDrawer.noMatch", "No tools match.")
|
|
8272
|
+
};
|
|
7767
8273
|
var STYLE_ID16 = "vela-widget-drawings-drawer";
|
|
7768
8274
|
var CSS16 = `
|
|
7769
8275
|
/* Search + tabs stay pinned while the tool list scrolls underneath. */
|
|
@@ -7797,7 +8303,7 @@ var CSS16 = `
|
|
|
7797
8303
|
gap: 4px;
|
|
7798
8304
|
overflow-x: auto;
|
|
7799
8305
|
scrollbar-width: none;
|
|
7800
|
-
/* The strip scrolls sideways itself
|
|
8306
|
+
/* The strip scrolls sideways itself — its touches are native scroll, not tab swipes
|
|
7801
8307
|
(the drawer body is pan-y so everywhere ELSE a sideways move swipes the tabs). */
|
|
7802
8308
|
touch-action: pan-x;
|
|
7803
8309
|
padding: 0 2px 8px;
|
|
@@ -7870,7 +8376,7 @@ var DrawingsDrawer = class {
|
|
|
7870
8376
|
injectStyles(STYLE_ID16, CSS16, doc);
|
|
7871
8377
|
this.drawer = new Drawer({
|
|
7872
8378
|
host: opts.host,
|
|
7873
|
-
title:
|
|
8379
|
+
title: this.t(TEXT17.title),
|
|
7874
8380
|
onOpenChange: opts.onOpenChange,
|
|
7875
8381
|
// Swiping across the tool list pages through the group tabs.
|
|
7876
8382
|
onSwipe: (dir) => this.stepGroup(dir === "left" ? 1 : -1)
|
|
@@ -7882,7 +8388,7 @@ var DrawingsDrawer = class {
|
|
|
7882
8388
|
search.appendChild(iconEl("search", doc));
|
|
7883
8389
|
this.input = doc.createElement("input");
|
|
7884
8390
|
this.input.type = "text";
|
|
7885
|
-
this.input.placeholder =
|
|
8391
|
+
this.input.placeholder = this.t(TEXT17.search);
|
|
7886
8392
|
this.input.addEventListener("input", () => this.renderList());
|
|
7887
8393
|
search.appendChild(this.input);
|
|
7888
8394
|
this.tabs = doc.createElement("div");
|
|
@@ -7892,7 +8398,12 @@ var DrawingsDrawer = class {
|
|
|
7892
8398
|
this.list.className = "vela-dd-list";
|
|
7893
8399
|
this.drawer.body.append(sticky, this.list);
|
|
7894
8400
|
}
|
|
8401
|
+
t(m, values) {
|
|
8402
|
+
return textOf(this.opts.i18n?.() ?? null, m, values);
|
|
8403
|
+
}
|
|
7895
8404
|
open() {
|
|
8405
|
+
this.drawer.setTitle(this.t(TEXT17.title));
|
|
8406
|
+
this.input.placeholder = this.t(TEXT17.search);
|
|
7896
8407
|
this.definition = this.opts.toolbar();
|
|
7897
8408
|
if (!this.definition.groups.some((g) => g.id === this.activeGroup)) this.activeGroup = this.definition.groups[0]?.id ?? "";
|
|
7898
8409
|
this.input.value = "";
|
|
@@ -7924,7 +8435,7 @@ var DrawingsDrawer = class {
|
|
|
7924
8435
|
for (const group of this.definition.groups) {
|
|
7925
8436
|
const tab = doc.createElement("button");
|
|
7926
8437
|
tab.className = "vela-dd-tab";
|
|
7927
|
-
tab.textContent = group.label;
|
|
8438
|
+
tab.textContent = this.t(group.label);
|
|
7928
8439
|
if (group.id === this.activeGroup) tab.dataset.active = "1";
|
|
7929
8440
|
tab.addEventListener("click", () => {
|
|
7930
8441
|
this.activeGroup = group.id;
|
|
@@ -7938,7 +8449,15 @@ var DrawingsDrawer = class {
|
|
|
7938
8449
|
renderList() {
|
|
7939
8450
|
const doc = this.list.ownerDocument;
|
|
7940
8451
|
this.list.replaceChildren();
|
|
7941
|
-
const
|
|
8452
|
+
const locale = this.opts.i18n?.().locale;
|
|
8453
|
+
const lower = (s) => {
|
|
8454
|
+
try {
|
|
8455
|
+
return s.toLocaleLowerCase(locale);
|
|
8456
|
+
} catch {
|
|
8457
|
+
return s.toLowerCase();
|
|
8458
|
+
}
|
|
8459
|
+
};
|
|
8460
|
+
const query = lower(this.input.value.trim());
|
|
7942
8461
|
const active = this.opts.currentTool();
|
|
7943
8462
|
const section = (label) => {
|
|
7944
8463
|
const el = doc.createElement("div");
|
|
@@ -7955,7 +8474,7 @@ var DrawingsDrawer = class {
|
|
|
7955
8474
|
glyph.innerHTML = tool.icon;
|
|
7956
8475
|
const label = doc.createElement("span");
|
|
7957
8476
|
label.className = "vela-dd-label";
|
|
7958
|
-
label.textContent = tool.label;
|
|
8477
|
+
label.textContent = this.t(tool.label);
|
|
7959
8478
|
const star = doc.createElement("button");
|
|
7960
8479
|
star.className = "vela-dd-star";
|
|
7961
8480
|
const paintStar = (on) => {
|
|
@@ -7964,7 +8483,7 @@ var DrawingsDrawer = class {
|
|
|
7964
8483
|
else delete star.dataset.on;
|
|
7965
8484
|
};
|
|
7966
8485
|
paintStar(this.opts.isFavorite(tool.type));
|
|
7967
|
-
star.setAttribute("aria-label",
|
|
8486
|
+
star.setAttribute("aria-label", this.t(TEXT17.favorite, { tool: this.t(tool.label) }));
|
|
7968
8487
|
star.addEventListener("click", (e) => {
|
|
7969
8488
|
e.stopPropagation();
|
|
7970
8489
|
const on = !this.opts.isFavorite(tool.type);
|
|
@@ -7981,16 +8500,16 @@ var DrawingsDrawer = class {
|
|
|
7981
8500
|
if (query) {
|
|
7982
8501
|
let any = false;
|
|
7983
8502
|
for (const group2 of this.definition.groups) {
|
|
7984
|
-
const hits = group2.tools.filter((t) => t.label.toLowerCase().includes(query));
|
|
8503
|
+
const hits = group2.tools.filter((t) => lower(this.t(t.label)).includes(query) || sourceText(t.label).toLowerCase().includes(query));
|
|
7985
8504
|
if (hits.length === 0) continue;
|
|
7986
8505
|
any = true;
|
|
7987
|
-
section(group2.label);
|
|
8506
|
+
section(this.t(group2.label));
|
|
7988
8507
|
for (const tool of hits) row(tool);
|
|
7989
8508
|
}
|
|
7990
8509
|
if (!any) {
|
|
7991
8510
|
const empty = doc.createElement("div");
|
|
7992
8511
|
empty.className = "vela-dd-empty";
|
|
7993
|
-
empty.textContent =
|
|
8512
|
+
empty.textContent = this.t(TEXT17.noMatch);
|
|
7994
8513
|
this.list.appendChild(empty);
|
|
7995
8514
|
}
|
|
7996
8515
|
return;
|
|
@@ -7999,7 +8518,7 @@ var DrawingsDrawer = class {
|
|
|
7999
8518
|
if (!group) return;
|
|
8000
8519
|
if (group.sections && group.sections.length > 0) {
|
|
8001
8520
|
for (const s of group.sections) {
|
|
8002
|
-
section(s.label);
|
|
8521
|
+
section(this.t(s.label));
|
|
8003
8522
|
for (const tool of s.tools) row(tool);
|
|
8004
8523
|
}
|
|
8005
8524
|
} else {
|
|
@@ -8009,6 +8528,17 @@ var DrawingsDrawer = class {
|
|
|
8009
8528
|
};
|
|
8010
8529
|
|
|
8011
8530
|
// src/widget/more-drawer.ts
|
|
8531
|
+
var TEXT18 = {
|
|
8532
|
+
chartType: msg("widget.moreDrawer.chartType", "Chart type"),
|
|
8533
|
+
layout: msg("widget.moreDrawer.layout", "Layout"),
|
|
8534
|
+
alerts: msg("widget.moreDrawer.alerts", "Alerts"),
|
|
8535
|
+
undo: msg("widget.moreDrawer.undo", "Undo"),
|
|
8536
|
+
redo: msg("widget.moreDrawer.redo", "Redo"),
|
|
8537
|
+
screenshot: msg("widget.moreDrawer.screenshot", "Screenshot"),
|
|
8538
|
+
sync: msg("widget.moreDrawer.sync", "Sync"),
|
|
8539
|
+
noAlerts: msg("widget.moreDrawer.noAlerts", "No alerts yet."),
|
|
8540
|
+
alert: msg("widget.moreDrawer.alert", "{title}: {message}")
|
|
8541
|
+
};
|
|
8012
8542
|
var STYLE_ID17 = "vela-widget-more-drawer";
|
|
8013
8543
|
var CSS17 = `
|
|
8014
8544
|
.vela-md-actions {
|
|
@@ -8099,6 +8629,9 @@ var MoreDrawer = class {
|
|
|
8099
8629
|
destroy() {
|
|
8100
8630
|
this.drawer.destroy();
|
|
8101
8631
|
}
|
|
8632
|
+
t(text, values) {
|
|
8633
|
+
return textOf(this.opts.i18n, text, values);
|
|
8634
|
+
}
|
|
8102
8635
|
show(view) {
|
|
8103
8636
|
this.view = view;
|
|
8104
8637
|
this.render();
|
|
@@ -8110,7 +8643,7 @@ var MoreDrawer = class {
|
|
|
8110
8643
|
const back = doc.createElement("button");
|
|
8111
8644
|
back.className = "vela-md-back";
|
|
8112
8645
|
back.appendChild(iconEl("chevron-left", doc));
|
|
8113
|
-
back.appendChild(doc.createTextNode(this.view === "style" ?
|
|
8646
|
+
back.appendChild(doc.createTextNode(this.t(this.view === "style" ? TEXT18.chartType : this.view === "layout" ? TEXT18.layout : TEXT18.alerts)));
|
|
8114
8647
|
back.addEventListener("click", () => this.show("main"));
|
|
8115
8648
|
this.drawer.body.appendChild(back);
|
|
8116
8649
|
}
|
|
@@ -8150,25 +8683,25 @@ var MoreDrawer = class {
|
|
|
8150
8683
|
b.className = "vela-md-action";
|
|
8151
8684
|
b.disabled = !enabled;
|
|
8152
8685
|
b.appendChild(iconEl(icon2, doc));
|
|
8153
|
-
b.appendChild(doc.createTextNode(label));
|
|
8686
|
+
b.appendChild(doc.createTextNode(this.t(label)));
|
|
8154
8687
|
b.addEventListener("click", () => {
|
|
8155
8688
|
run();
|
|
8156
8689
|
this.drawer.hide();
|
|
8157
8690
|
});
|
|
8158
8691
|
actions.appendChild(b);
|
|
8159
8692
|
};
|
|
8160
|
-
if (this.opts.onUndo) action("undo",
|
|
8161
|
-
if (this.opts.onRedo) action("redo",
|
|
8162
|
-
if (this.opts.onScreenshot) action("camera",
|
|
8693
|
+
if (this.opts.onUndo) action("undo", TEXT18.undo, this.opts.canUndo(), this.opts.onUndo);
|
|
8694
|
+
if (this.opts.onRedo) action("redo", TEXT18.redo, this.opts.canRedo(), this.opts.onRedo);
|
|
8695
|
+
if (this.opts.onScreenshot) action("camera", TEXT18.screenshot, true, this.opts.onScreenshot);
|
|
8163
8696
|
if (actions.childElementCount > 0) this.drawer.body.appendChild(actions);
|
|
8164
8697
|
const list = doc.createElement("div");
|
|
8165
8698
|
list.className = "vela-md-list";
|
|
8166
8699
|
const current = this.opts.priceStyles().find((s) => s.id === this.opts.priceStyle());
|
|
8167
|
-
list.appendChild(this.row(doc,
|
|
8700
|
+
list.appendChild(this.row(doc, this.t(TEXT18.chartType), { icon: "style-line", value: current?.label, chevron: true, onClick: () => this.show("style") }));
|
|
8168
8701
|
if (this.opts.layout) {
|
|
8169
8702
|
const shape = this.opts.layout.shape();
|
|
8170
|
-
const value = shape ?
|
|
8171
|
-
list.appendChild(this.row(doc,
|
|
8703
|
+
const value = shape ? layoutGridLabel(shape.cols, shape.rows, this.opts.i18n) : this.opts.layout.presets().find((p) => p.checked)?.label;
|
|
8704
|
+
list.appendChild(this.row(doc, this.t(TEXT18.layout), { icon: "layout", glyph: this.opts.layout.glyph?.(), value, chevron: true, onClick: () => this.show("layout") }));
|
|
8172
8705
|
}
|
|
8173
8706
|
for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
|
|
8174
8707
|
for (const panel of this.opts.panels()) {
|
|
@@ -8184,7 +8717,7 @@ var MoreDrawer = class {
|
|
|
8184
8717
|
}
|
|
8185
8718
|
if (this.opts.alerts) {
|
|
8186
8719
|
const alertCount = this.opts.alerts().length;
|
|
8187
|
-
list.appendChild(this.row(doc,
|
|
8720
|
+
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
8721
|
}
|
|
8189
8722
|
for (const act of this.opts.actions()) list.appendChild(this.actionRow(doc, act));
|
|
8190
8723
|
this.drawer.body.appendChild(list);
|
|
@@ -8224,7 +8757,7 @@ var MoreDrawer = class {
|
|
|
8224
8757
|
list.className = "vela-md-list";
|
|
8225
8758
|
const wrap = doc.createElement("div");
|
|
8226
8759
|
wrap.className = "vela-md-gridwrap";
|
|
8227
|
-
const { el: grid, squares } = layoutGridCanvas(doc);
|
|
8760
|
+
const { el: grid, squares } = layoutGridCanvas(doc, this.opts.i18n);
|
|
8228
8761
|
paintLayoutGrid(squares, layout.shape());
|
|
8229
8762
|
grid.addEventListener("click", (e) => {
|
|
8230
8763
|
const sq = e.target?.closest?.(".vela-lp-sq");
|
|
@@ -8249,7 +8782,7 @@ var MoreDrawer = class {
|
|
|
8249
8782
|
if (syncs.length > 0) {
|
|
8250
8783
|
const heading = doc.createElement("div");
|
|
8251
8784
|
heading.className = "vela-md-section";
|
|
8252
|
-
heading.textContent =
|
|
8785
|
+
heading.textContent = this.t(TEXT18.sync);
|
|
8253
8786
|
list.appendChild(heading);
|
|
8254
8787
|
for (const s of syncs) {
|
|
8255
8788
|
list.appendChild(
|
|
@@ -8270,20 +8803,23 @@ var MoreDrawer = class {
|
|
|
8270
8803
|
if (alerts.length === 0) {
|
|
8271
8804
|
const empty = doc.createElement("div");
|
|
8272
8805
|
empty.className = "vela-md-empty";
|
|
8273
|
-
empty.textContent =
|
|
8806
|
+
empty.textContent = this.t(TEXT18.noAlerts);
|
|
8274
8807
|
this.drawer.body.appendChild(empty);
|
|
8275
8808
|
return;
|
|
8276
8809
|
}
|
|
8277
8810
|
const list = doc.createElement("div");
|
|
8278
8811
|
list.className = "vela-md-list";
|
|
8279
8812
|
for (const a of alerts) {
|
|
8280
|
-
list.appendChild(this.row(doc,
|
|
8813
|
+
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
8814
|
}
|
|
8282
8815
|
this.drawer.body.appendChild(list);
|
|
8283
8816
|
}
|
|
8284
8817
|
};
|
|
8285
8818
|
|
|
8286
8819
|
// src/widget/timezone-drawer.ts
|
|
8820
|
+
var TEXT19 = {
|
|
8821
|
+
title: msg("widget.timezoneDrawer.title", "Time zone")
|
|
8822
|
+
};
|
|
8287
8823
|
var STYLE_ID18 = "vela-widget-timezone-drawer";
|
|
8288
8824
|
var CSS18 = `
|
|
8289
8825
|
.vela-tzd-list { padding: 2px 0 4px; }
|
|
@@ -8305,9 +8841,10 @@ var TimezoneDrawer = class {
|
|
|
8305
8841
|
constructor(opts) {
|
|
8306
8842
|
this.opts = opts;
|
|
8307
8843
|
injectStyles(STYLE_ID18, CSS18, opts.host.ownerDocument);
|
|
8308
|
-
this.drawer = new Drawer({ host: opts.host, title:
|
|
8844
|
+
this.drawer = new Drawer({ host: opts.host, title: textOf(opts.i18n, TEXT19.title), onOpenChange: opts.onOpenChange });
|
|
8309
8845
|
}
|
|
8310
8846
|
open() {
|
|
8847
|
+
this.drawer.setTitle(textOf(this.opts.i18n, TEXT19.title));
|
|
8311
8848
|
this.render();
|
|
8312
8849
|
this.drawer.show();
|
|
8313
8850
|
}
|
|
@@ -8322,7 +8859,7 @@ var TimezoneDrawer = class {
|
|
|
8322
8859
|
this.drawer.body.replaceChildren();
|
|
8323
8860
|
const list = doc.createElement("div");
|
|
8324
8861
|
list.className = "vela-tzd-list";
|
|
8325
|
-
for (const tz of timezoneMenuRows(this.opts.timezone())) {
|
|
8862
|
+
for (const tz of timezoneMenuRows(this.opts.timezone(), this.opts.i18n)) {
|
|
8326
8863
|
const row = doc.createElement("div");
|
|
8327
8864
|
row.className = "vela-tzd-row";
|
|
8328
8865
|
const label = doc.createElement("span");
|
|
@@ -8367,17 +8904,21 @@ var CSS19 = `
|
|
|
8367
8904
|
color: var(--vela-fg-muted);
|
|
8368
8905
|
}
|
|
8369
8906
|
`;
|
|
8907
|
+
var TEXT20 = {
|
|
8908
|
+
title: msg("widget.priceScaleDrawer.title", "Price scale")
|
|
8909
|
+
};
|
|
8370
8910
|
var PriceScaleDrawer = class {
|
|
8371
8911
|
constructor(opts) {
|
|
8372
8912
|
this.opts = opts;
|
|
8373
8913
|
this.pane = null;
|
|
8374
8914
|
injectStyles(STYLE_ID19, CSS19, opts.host.ownerDocument);
|
|
8375
|
-
this.drawer = new Drawer({ host: opts.host, title:
|
|
8915
|
+
this.drawer = new Drawer({ host: opts.host, title: textOf(opts.chart()?.i18n, TEXT20.title), onOpenChange: opts.onOpenChange });
|
|
8376
8916
|
}
|
|
8377
8917
|
open() {
|
|
8378
8918
|
const chart = this.opts.chart();
|
|
8379
8919
|
const panes = chart?.renderer.get("paneScales");
|
|
8380
8920
|
this.pane = Array.isArray(panes) ? paneScaleAt(panes, this.opts.pressY()) : null;
|
|
8921
|
+
this.drawer.setTitle(textOf(chart?.i18n, TEXT20.title));
|
|
8381
8922
|
this.render();
|
|
8382
8923
|
this.drawer.show();
|
|
8383
8924
|
}
|
|
@@ -8398,13 +8939,14 @@ var PriceScaleDrawer = class {
|
|
|
8398
8939
|
const pane = this.pane;
|
|
8399
8940
|
const items = priceAxisItems({
|
|
8400
8941
|
auto: chart.renderer.get("autoScale") !== false,
|
|
8942
|
+
priceOnly: priceOnlyState(pane, this.flag("scalePriceOnly")),
|
|
8401
8943
|
invert: pane ? pane.invert : this.flag("invertScale"),
|
|
8402
8944
|
choice: scaleChoiceOf(pane ?? { mode: String(chart.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
|
|
8403
8945
|
axisLabels: this.flag("axisLabels"),
|
|
8404
8946
|
priceLabel: this.flag("priceLabel"),
|
|
8405
8947
|
countdown: this.flag("countdown"),
|
|
8406
8948
|
priceLine: this.flag("currentPriceLine")
|
|
8407
|
-
});
|
|
8949
|
+
}, chart.i18n);
|
|
8408
8950
|
const list = doc.createElement("div");
|
|
8409
8951
|
list.className = "vela-psd-list";
|
|
8410
8952
|
const row = (label, opts) => {
|
|
@@ -8427,7 +8969,7 @@ var PriceScaleDrawer = class {
|
|
|
8427
8969
|
};
|
|
8428
8970
|
for (const item of items) {
|
|
8429
8971
|
if (item.id === "labels" && item.submenu) {
|
|
8430
|
-
section(
|
|
8972
|
+
section(item.label);
|
|
8431
8973
|
for (const sub of item.submenu) {
|
|
8432
8974
|
row(sub.label, {
|
|
8433
8975
|
checked: sub.checked,
|
|
@@ -8441,7 +8983,7 @@ var PriceScaleDrawer = class {
|
|
|
8441
8983
|
continue;
|
|
8442
8984
|
}
|
|
8443
8985
|
if (item.id === "levels" && item.submenu) {
|
|
8444
|
-
section(
|
|
8986
|
+
section(item.label);
|
|
8445
8987
|
for (const sub of item.submenu) {
|
|
8446
8988
|
row(sub.label, {
|
|
8447
8989
|
checked: sub.checked,
|
|
@@ -8474,6 +9016,16 @@ var PriceScaleDrawer = class {
|
|
|
8474
9016
|
});
|
|
8475
9017
|
continue;
|
|
8476
9018
|
}
|
|
9019
|
+
if (item.id === "price-only") {
|
|
9020
|
+
row(item.label, {
|
|
9021
|
+
checked: item.checked,
|
|
9022
|
+
onClick: () => {
|
|
9023
|
+
chart.renderer.set("scalePriceOnly", !this.flag("scalePriceOnly"));
|
|
9024
|
+
this.render();
|
|
9025
|
+
}
|
|
9026
|
+
});
|
|
9027
|
+
continue;
|
|
9028
|
+
}
|
|
8477
9029
|
if (item.id === "invert") {
|
|
8478
9030
|
row(item.label, {
|
|
8479
9031
|
checked: item.checked,
|
|
@@ -8507,6 +9059,12 @@ var PriceScaleDrawer = class {
|
|
|
8507
9059
|
};
|
|
8508
9060
|
|
|
8509
9061
|
// src/widget/drawing-pill.ts
|
|
9062
|
+
var TEXT21 = {
|
|
9063
|
+
magnet: msg("widget.drawingPill.magnet", "Magnet snap"),
|
|
9064
|
+
stay: msg("widget.drawingPill.stay", "Stay in drawing mode"),
|
|
9065
|
+
eraser: msg("widget.drawingPill.eraser", "Eraser"),
|
|
9066
|
+
exit: msg("widget.drawingPill.exit", "Exit drawing mode")
|
|
9067
|
+
};
|
|
8510
9068
|
var STYLE_ID20 = "vela-widget-drawing-pill";
|
|
8511
9069
|
var CSS20 = `
|
|
8512
9070
|
.vela-drawpill { display: none; }
|
|
@@ -8556,17 +9114,24 @@ var CSS20 = `
|
|
|
8556
9114
|
.vela-drawpill-btn .vela-icon { font-size: 17px; width: 17px; height: 17px; }
|
|
8557
9115
|
.vela-drawpill-badge {
|
|
8558
9116
|
position: absolute;
|
|
8559
|
-
top:
|
|
8560
|
-
right:
|
|
8561
|
-
|
|
8562
|
-
|
|
9117
|
+
top: 5px;
|
|
9118
|
+
right: 5px;
|
|
9119
|
+
display: none;
|
|
9120
|
+
align-items: flex-end;
|
|
9121
|
+
gap: 1px;
|
|
8563
9122
|
color: var(--vela-accent);
|
|
8564
9123
|
}
|
|
9124
|
+
.vela-drawpill-badge[data-strength] { display: inline-flex; }
|
|
9125
|
+
.vela-drawpill-badge > i { display: block; width: 2px; height: 4px; border-radius: 1px; background: currentColor; }
|
|
9126
|
+
.vela-drawpill-badge > i + i { height: 7px; opacity: 0.3; }
|
|
9127
|
+
.vela-drawpill-badge[data-strength='strong'] > i + i { opacity: 1; }
|
|
8565
9128
|
`;
|
|
8566
9129
|
var DrawingPill = class {
|
|
8567
9130
|
constructor(host) {
|
|
8568
9131
|
this.chart = null;
|
|
8569
9132
|
this.chartSubs = [];
|
|
9133
|
+
/** Each button's accessible name, re-read in the chart's language on every sync. */
|
|
9134
|
+
this.labels = [];
|
|
8570
9135
|
const doc = host.ownerDocument;
|
|
8571
9136
|
injectStyles(STYLE_ID20, CSS20, doc);
|
|
8572
9137
|
this.el = doc.createElement("div");
|
|
@@ -8577,32 +9142,38 @@ var DrawingPill = class {
|
|
|
8577
9142
|
const btn = (icon2, label, onClick) => {
|
|
8578
9143
|
const b = doc.createElement("button");
|
|
8579
9144
|
b.className = "vela-drawpill-btn";
|
|
8580
|
-
b.setAttribute("aria-label", label);
|
|
9145
|
+
b.setAttribute("aria-label", this.t(label));
|
|
9146
|
+
this.labels.push({ el: b, label });
|
|
8581
9147
|
b.appendChild(iconEl(icon2, doc));
|
|
8582
9148
|
b.addEventListener("click", onClick);
|
|
8583
9149
|
return b;
|
|
8584
9150
|
};
|
|
8585
|
-
this.magnetBtn = btn("magnet",
|
|
9151
|
+
this.magnetBtn = btn("magnet", TEXT21.magnet, () => {
|
|
8586
9152
|
const order = ["off", "weak", "strong"];
|
|
8587
9153
|
const cur = this.chart?.drawings.getSnapMode() ?? "off";
|
|
8588
9154
|
this.chart?.drawings.setSnapMode(order[(order.indexOf(cur) + 1) % order.length]);
|
|
8589
9155
|
});
|
|
8590
9156
|
this.magnetBadge = doc.createElement("span");
|
|
8591
9157
|
this.magnetBadge.className = "vela-drawpill-badge";
|
|
9158
|
+
this.magnetBadge.setAttribute("aria-hidden", "true");
|
|
9159
|
+
this.magnetBadge.append(doc.createElement("i"), doc.createElement("i"));
|
|
8592
9160
|
this.magnetBtn.appendChild(this.magnetBadge);
|
|
8593
|
-
this.stayBtn = btn(iconMarkup("pen-lock") ? "pen-lock" : "pen",
|
|
9161
|
+
this.stayBtn = btn(iconMarkup("pen-lock") ? "pen-lock" : "pen", TEXT21.stay, () => {
|
|
8594
9162
|
this.chart?.drawings.setStayMode(!this.chart.drawings.getStayMode());
|
|
8595
9163
|
});
|
|
8596
|
-
this.eraserBtn = btn("eraser",
|
|
9164
|
+
this.eraserBtn = btn("eraser", TEXT21.eraser, () => {
|
|
8597
9165
|
this.chart?.drawings.setMode(this.chart.drawings.getMode() === "eraser" ? null : "eraser");
|
|
8598
9166
|
});
|
|
8599
|
-
const close = btn("close",
|
|
9167
|
+
const close = btn("close", TEXT21.exit, () => {
|
|
8600
9168
|
this.chart?.drawings.setTool(null);
|
|
8601
9169
|
this.chart?.drawings.setMode(null);
|
|
8602
9170
|
});
|
|
8603
9171
|
this.el.append(this.toolGlyph, this.magnetBtn, this.stayBtn, this.eraserBtn, close);
|
|
8604
9172
|
host.appendChild(this.el);
|
|
8605
9173
|
}
|
|
9174
|
+
t(text) {
|
|
9175
|
+
return textOf(this.chart?.i18n, text);
|
|
9176
|
+
}
|
|
8606
9177
|
/** Rebind to another chart (a rebuild, or the workspace's active cell changing) —
|
|
8607
9178
|
* the previous chart's subscriptions are dropped so only one chart drives the pill. */
|
|
8608
9179
|
onChart(chart) {
|
|
@@ -8616,7 +9187,12 @@ var DrawingPill = class {
|
|
|
8616
9187
|
];
|
|
8617
9188
|
this.sync();
|
|
8618
9189
|
}
|
|
9190
|
+
/** Re-read the labels in the chart's language. */
|
|
9191
|
+
relocalize() {
|
|
9192
|
+
this.sync();
|
|
9193
|
+
}
|
|
8619
9194
|
sync() {
|
|
9195
|
+
for (const { el, label } of this.labels) el.setAttribute("aria-label", this.t(label));
|
|
8620
9196
|
const drawings = this.chart?.drawings;
|
|
8621
9197
|
if (!drawings || !drawings.supported) {
|
|
8622
9198
|
this.el.hidden = true;
|
|
@@ -8629,7 +9205,8 @@ var DrawingPill = class {
|
|
|
8629
9205
|
this.toolGlyph.innerHTML = tool !== null ? getDrawingType(tool)?.icon ?? "" : iconMarkup("eraser") ?? "";
|
|
8630
9206
|
const snap = drawings.getSnapMode();
|
|
8631
9207
|
this.magnetBtn.dataset.on = snap !== "off" ? "1" : "";
|
|
8632
|
-
|
|
9208
|
+
if (snap === "off") delete this.magnetBadge.dataset.strength;
|
|
9209
|
+
else this.magnetBadge.dataset.strength = snap;
|
|
8633
9210
|
this.stayBtn.dataset.on = drawings.getStayMode() ? "1" : "";
|
|
8634
9211
|
this.eraserBtn.dataset.on = eraser ? "1" : "";
|
|
8635
9212
|
}
|
|
@@ -8664,6 +9241,7 @@ function buildContext(host) {
|
|
|
8664
9241
|
togglePanel: (id, open) => host.togglePanel(id, open),
|
|
8665
9242
|
dockStrip: (el) => host.dockStrip(el),
|
|
8666
9243
|
host: host.root,
|
|
9244
|
+
i18n: host.i18n,
|
|
8667
9245
|
toast: (message, kind) => host.toast(message, kind),
|
|
8668
9246
|
addIndicator: (entry) => host.active()?.addExternalIndicator(entry),
|
|
8669
9247
|
addNativeIndicator: (type) => host.active()?.addNative(type),
|
|
@@ -8743,7 +9321,7 @@ var CSS21 = `
|
|
|
8743
9321
|
.vela-ws-grid { position: relative; flex: 1 1 auto; min-width: 0; display: grid; gap: ${GAP_PX}px; background: var(--vela-border-soft); }
|
|
8744
9322
|
.vela-cell { background: var(--vela-bg); position: relative; }
|
|
8745
9323
|
/* Active-cell highlight: an overlay ring ABOVE the chart's own canvas stack (a plain
|
|
8746
|
-
outline on the cell is painted under them)
|
|
9324
|
+
outline on the cell is painted under them) — inert to the pointer. Scoped to
|
|
8747
9325
|
multi-cell grids ([data-multi]): a single-cell layout always has an active cell,
|
|
8748
9326
|
and ringing the only chart would just be noise. */
|
|
8749
9327
|
.vela-ws-grid[data-multi='1'] .vela-cell[data-active='1']::after {
|
|
@@ -8760,11 +9338,11 @@ var CSS21 = `
|
|
|
8760
9338
|
.vela-ws-splitter:hover::after { content: ''; position: absolute; background: var(--vela-separator-hover-line); }
|
|
8761
9339
|
.vela-ws-splitter[data-axis='cols']:hover::after { left: calc(50% - 1px); top: 0; width: 2px; height: 100%; }
|
|
8762
9340
|
.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
|
|
9341
|
+
/* Mobile: the docked drawing-toolbar column would eat a phone-width grid — the shell's
|
|
8764
9342
|
drawings drawer + on-chart pill replace it (same policy as the widget's in-chart bar). */
|
|
8765
9343
|
[data-layout='mobile'] .vela-ws-toolbar { display: none; }
|
|
8766
9344
|
/* 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
|
|
9345
|
+
the active ring would just outline the only visible chart — both are noise here. */
|
|
8768
9346
|
.vela-ws-grid[data-maximized='1'] .vela-ws-splitter { display: none; }
|
|
8769
9347
|
.vela-ws-grid[data-maximized='1'] .vela-cell[data-active='1']::after { display: none; }
|
|
8770
9348
|
/* Drop-target preview while a cell's drag handle is held: a dashed ring + the same
|
|
@@ -8784,24 +9362,58 @@ function declaredOrder(cells) {
|
|
|
8784
9362
|
const names = Object.keys(cells ?? {});
|
|
8785
9363
|
for (const n of names) {
|
|
8786
9364
|
if (/^\d+$/.test(n)) {
|
|
8787
|
-
console.warn(`[vela] workspace cell "${n}" ignored
|
|
9365
|
+
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
9366
|
}
|
|
8789
9367
|
}
|
|
8790
9368
|
return names.filter((n) => !/^\d+$/.test(n));
|
|
8791
9369
|
}
|
|
9370
|
+
var TEXT22 = {
|
|
9371
|
+
syncSymbol: msg("workspace.sync.symbol", "Symbol"),
|
|
9372
|
+
syncInterval: msg("workspace.sync.interval", "Interval"),
|
|
9373
|
+
syncCrosshair: msg("workspace.sync.crosshair", "Crosshair"),
|
|
9374
|
+
syncStyle: msg("workspace.sync.style", "Style"),
|
|
9375
|
+
alert: msg("workspace.alerts.untitled", "Alert"),
|
|
9376
|
+
alertToast: msg("workspace.alerts.toast", "{source} — {title} — {message}"),
|
|
9377
|
+
alertToastUntitled: msg("workspace.alerts.toastUntitled", "{source} — {message}"),
|
|
9378
|
+
alertRow: msg("workspace.alerts.row", "{source} · {time} · {title}: {message}"),
|
|
9379
|
+
alertDrawerTitle: msg("workspace.alerts.drawerTitle", "{source} · {title}"),
|
|
9380
|
+
noAlerts: msg("workspace.alerts.none", "No alerts yet"),
|
|
9381
|
+
screenshot: msg("workspace.keys.screenshot", "Download a screenshot of the layout"),
|
|
9382
|
+
resetView: msg("workspace.keys.resetView", "Reset view (all history)"),
|
|
9383
|
+
toggleLog: msg("workspace.keys.toggleLog", "Toggle logarithmic scale"),
|
|
9384
|
+
togglePercent: msg("workspace.keys.togglePercent", "Toggle percent scale"),
|
|
9385
|
+
trendline: msg("workspace.keys.trendline", "Arm the trend line tool"),
|
|
9386
|
+
hlineCursor: msg("workspace.keys.hlineCursor", "Horizontal line at the cursor price"),
|
|
9387
|
+
vlineCursor: msg("workspace.keys.vlineCursor", "Vertical line at the cursor time"),
|
|
9388
|
+
undo: msg("workspace.keys.undo", "Undo (active chart)"),
|
|
9389
|
+
redo: msg("workspace.keys.redo", "Redo (active chart)"),
|
|
9390
|
+
zoomIn: msg("workspace.keys.zoomIn", "Zoom in"),
|
|
9391
|
+
zoomOut: msg("workspace.keys.zoomOut", "Zoom out"),
|
|
9392
|
+
panLeft: msg("workspace.keys.panLeft", "Pan toward history"),
|
|
9393
|
+
panRight: msg("workspace.keys.panRight", "Pan toward now"),
|
|
9394
|
+
openIndicators: msg("workspace.keys.openIndicators", "Open the indicator picker"),
|
|
9395
|
+
showShortcuts: msg("workspace.keys.showShortcuts", "Show this shortcuts panel"),
|
|
9396
|
+
categoryChart: msg("workspace.keys.category.chart", "Chart"),
|
|
9397
|
+
categoryDrawings: msg("workspace.keys.category.drawings", "Drawings"),
|
|
9398
|
+
categoryEdit: msg("workspace.keys.category.edit", "Edit"),
|
|
9399
|
+
categoryIndicators: msg("workspace.keys.category.indicators", "Indicators"),
|
|
9400
|
+
categoryHelp: msg("workspace.keys.category.help", "Help")
|
|
9401
|
+
};
|
|
9402
|
+
var ALERT_ROW_MAX = 80;
|
|
8792
9403
|
function nextAutoCellId(taken) {
|
|
8793
9404
|
for (let i = 1; ; i += 1) {
|
|
8794
9405
|
if (!taken.has(`c${i}`)) return `c${i}`;
|
|
8795
9406
|
}
|
|
8796
9407
|
}
|
|
8797
9408
|
function resolveTyping(key, open) {
|
|
9409
|
+
const printable = [...key].length === 1;
|
|
8798
9410
|
if (open.dialogs > 0) {
|
|
8799
|
-
if (
|
|
9411
|
+
if (!printable) return null;
|
|
8800
9412
|
if (open.symbolSearch) return { target: "symbol", text: key.toUpperCase() };
|
|
8801
9413
|
if (open.timeframeEntry) return { target: "timeframe", text: key.toUpperCase() };
|
|
8802
9414
|
return null;
|
|
8803
9415
|
}
|
|
8804
|
-
if (/^[
|
|
9416
|
+
if (printable && /^[\p{L}0]$/u.test(key)) return { target: "symbol", text: key.toUpperCase() };
|
|
8805
9417
|
if (/^[1-9]$/.test(key)) return { target: "timeframe", text: key };
|
|
8806
9418
|
return null;
|
|
8807
9419
|
}
|
|
@@ -8917,6 +9529,7 @@ var VelaWorkspace = class {
|
|
|
8917
9529
|
const hostEl = typeof container === "string" ? document.querySelector(container) : container;
|
|
8918
9530
|
if (!hostEl) throw new Error(`VelaWorkspace: container not found: ${String(container)}`);
|
|
8919
9531
|
this.opts = opts;
|
|
9532
|
+
this.localeState = new LocaleState(pickLocalization(opts));
|
|
8920
9533
|
this.replay = new WorkspaceReplay({
|
|
8921
9534
|
cells: () => this.cells(),
|
|
8922
9535
|
activeId: () => this.activeId,
|
|
@@ -8967,12 +9580,14 @@ var VelaWorkspace = class {
|
|
|
8967
9580
|
this.root = doc.createElement("div");
|
|
8968
9581
|
this.root.className = "vela-workspace";
|
|
8969
9582
|
ensureUIHost(this.root, resolveTheme(opts.theme));
|
|
9583
|
+
this.applyRootAttributes();
|
|
8970
9584
|
for (const [name, make] of Object.entries(opts.providers ?? {})) void this.feed.registerProvider(name, make());
|
|
8971
9585
|
void this.feed.ready().then(() => {
|
|
8972
9586
|
if (!this.destroyed) this.refreshRetention();
|
|
8973
9587
|
});
|
|
8974
9588
|
this.symbolPicker = new SymbolPicker({
|
|
8975
9589
|
host: this.root,
|
|
9590
|
+
i18n: this.localeState,
|
|
8976
9591
|
// Row icons come from each descriptor's OWNING provider (resolveSymbolIcon).
|
|
8977
9592
|
iconFor: (d) => this.feed.symbolIconOf(d),
|
|
8978
9593
|
onSelect: (ticker) => this.active.setSymbol(ticker),
|
|
@@ -8984,6 +9599,7 @@ var VelaWorkspace = class {
|
|
|
8984
9599
|
this.symbolPicker.setSource(() => this.feed.symbols());
|
|
8985
9600
|
this.indicatorPicker = opts.indicatorPicker !== false && !this.indicatorsOverride && topbarHas(this.topbarComp, "indicators") ? new IndicatorPicker({
|
|
8986
9601
|
host: this.root,
|
|
9602
|
+
i18n: this.localeState,
|
|
8987
9603
|
library: () => this.active.libraryRows(),
|
|
8988
9604
|
onChart: () => this.active.onChartRows(),
|
|
8989
9605
|
onAdd: (i) => this.active.addFromLibrary(i),
|
|
@@ -8996,11 +9612,13 @@ var VelaWorkspace = class {
|
|
|
8996
9612
|
const picker = this.indicatorPicker;
|
|
8997
9613
|
this.tfQuick = new TimeframeQuick({
|
|
8998
9614
|
host: this.root,
|
|
9615
|
+
i18n: this.localeState,
|
|
8999
9616
|
onApply: (tf) => this.setActiveTimeframe(tf),
|
|
9000
9617
|
onOpenChange: (open) => this.trackDialog(open)
|
|
9001
9618
|
});
|
|
9002
9619
|
this.topbar = new Topbar(this.root, {
|
|
9003
9620
|
symbol: "",
|
|
9621
|
+
i18n: this.localeState,
|
|
9004
9622
|
// RAW option, not the resolved lists — the bar distinguishes a host-declared
|
|
9005
9623
|
// side (list is law) from a default one (an override's `order` may flow).
|
|
9006
9624
|
composition: opts.topbar,
|
|
@@ -9024,18 +9642,13 @@ var VelaWorkspace = class {
|
|
|
9024
9642
|
// The picker composes dynamic layouts on its grid canvas; registered
|
|
9025
9643
|
// presets the canvas cannot express (bespoke plugin areas) list as rows.
|
|
9026
9644
|
shape: () => layoutShape(this.def),
|
|
9027
|
-
presets: () => layouts().filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label })),
|
|
9645
|
+
presets: () => layouts(this.localeState).filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label })),
|
|
9028
9646
|
onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
|
|
9029
9647
|
onSelectPreset: (id) => this.setLayout(id),
|
|
9030
9648
|
// The SYNC switches reflect the simple all-cells form; flipping one
|
|
9031
9649
|
// OVERRIDES a host-set group record with plain on/off (groups stay an
|
|
9032
9650
|
// 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
|
-
],
|
|
9651
|
+
syncs: () => this.syncRows(),
|
|
9039
9652
|
onToggleSync: (id) => {
|
|
9040
9653
|
const kind = id;
|
|
9041
9654
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
@@ -9046,7 +9659,7 @@ var VelaWorkspace = class {
|
|
|
9046
9659
|
});
|
|
9047
9660
|
const main = doc.createElement("div");
|
|
9048
9661
|
main.className = "vela-ws-main";
|
|
9049
|
-
const toolbar = buildToolbar(opts.drawings);
|
|
9662
|
+
const toolbar = buildToolbar(opts.drawings, this.localeState);
|
|
9050
9663
|
this.drawingsEnabled = opts.drawings !== false;
|
|
9051
9664
|
this.toolbarDef = toolbar.definition;
|
|
9052
9665
|
let toolbarHost = null;
|
|
@@ -9061,12 +9674,13 @@ var VelaWorkspace = class {
|
|
|
9061
9674
|
this.dock = new PanelDock(main, {
|
|
9062
9675
|
chrome: this.topbar,
|
|
9063
9676
|
context: () => this.context(),
|
|
9064
|
-
changed: () => this.markStateDirty()
|
|
9677
|
+
changed: () => this.markStateDirty(),
|
|
9678
|
+
i18n: this.localeState
|
|
9065
9679
|
});
|
|
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:
|
|
9680
|
+
this.objectTree = new ObjectTree(main, (sym) => this.feed.symbolIcon(sym), this.localeState);
|
|
9681
|
+
this.dataWindow = new DataWindow(main, this.localeState);
|
|
9682
|
+
this.dock.addBuiltIn({ id: "dataWindow", title: DATA_WINDOW_TITLE, icon: "datawindow", order: 10, panel: this.dataWindow, onChart: (c) => this.dataWindow.onChart(c) });
|
|
9683
|
+
this.dock.addBuiltIn({ id: "objects", title: OBJECT_TREE_TITLE, icon: "objects", order: 20, panel: this.objectTree, onChart: (c) => this.objectTree.onChart(c) });
|
|
9070
9684
|
this.dock.refresh();
|
|
9071
9685
|
if (boot?.panels) this.dock.applyState(boot.panels);
|
|
9072
9686
|
this.root.appendChild(main);
|
|
@@ -9077,7 +9691,7 @@ var VelaWorkspace = class {
|
|
|
9077
9691
|
const attribution = rendererDefaults().attribution;
|
|
9078
9692
|
if (attribution !== false) {
|
|
9079
9693
|
const background = resolveTheme(opts.theme).background;
|
|
9080
|
-
const mark = typeof attribution === "string" && attribution.trim() ? createCustomMark(doc, attribution, background) : createAttributionMark(doc, background);
|
|
9694
|
+
const mark = typeof attribution === "string" && attribution.trim() ? createCustomMark(doc, attribution, background) : createAttributionMark(doc, background, this.localeState);
|
|
9081
9695
|
Object.assign(mark.style, {
|
|
9082
9696
|
left: "calc(var(--vela-toolbar-gutter, 0px) + 12px)",
|
|
9083
9697
|
bottom: `calc(var(--vela-bottom-gutter, ${TIME_AXIS_H3}px) + 10px)`,
|
|
@@ -9116,6 +9730,7 @@ var VelaWorkspace = class {
|
|
|
9116
9730
|
},
|
|
9117
9731
|
{
|
|
9118
9732
|
dock: "static",
|
|
9733
|
+
i18n: () => this.localeState,
|
|
9119
9734
|
// Drawings sync is a WORKSPACE link (same model as the layout
|
|
9120
9735
|
// dropdown's switches) — the bar only hosts its toggle.
|
|
9121
9736
|
onDrawingsSync: (on) => {
|
|
@@ -9128,6 +9743,7 @@ var VelaWorkspace = class {
|
|
|
9128
9743
|
this.drawToolbar?.setVisible(true);
|
|
9129
9744
|
this.drawToolbar?.setDrawingsSyncMode(!!this.syncOpts.drawings);
|
|
9130
9745
|
this.bottombar = opts.bottombar !== false ? new Bottombar(this.root, {
|
|
9746
|
+
i18n: this.localeState,
|
|
9131
9747
|
timezone: this.timezone,
|
|
9132
9748
|
clock: this.clock,
|
|
9133
9749
|
onRange: (preset) => {
|
|
@@ -9141,6 +9757,7 @@ var VelaWorkspace = class {
|
|
|
9141
9757
|
onSettingsClick: () => this.active.chart.renderer.openSettings()
|
|
9142
9758
|
}) : null;
|
|
9143
9759
|
this.mobileBar = opts.bottombar !== false ? new MobileBar(this.root, {
|
|
9760
|
+
i18n: this.localeState,
|
|
9144
9761
|
symbol: "",
|
|
9145
9762
|
timeframe: "60",
|
|
9146
9763
|
onSymbolClick: () => this.symbolPicker.open(),
|
|
@@ -9174,7 +9791,7 @@ var VelaWorkspace = class {
|
|
|
9174
9791
|
this.keymap = new KeymapManager();
|
|
9175
9792
|
this.keymap.attach(this.root);
|
|
9176
9793
|
this.registerDefaultKeys();
|
|
9177
|
-
this.drawToolbar?.setShortcuts(toolShortcutHints(this.keymap));
|
|
9794
|
+
this.drawToolbar?.setShortcuts(toolShortcutHints(this.keymap, this.localeState));
|
|
9178
9795
|
this.root.addEventListener("keydown", this.onRootKeydown);
|
|
9179
9796
|
this.root.tabIndex = -1;
|
|
9180
9797
|
this.cellBackend = this.backendFor(this.def);
|
|
@@ -9298,6 +9915,61 @@ var VelaWorkspace = class {
|
|
|
9298
9915
|
on(event, handler) {
|
|
9299
9916
|
return this.events.on(event, handler);
|
|
9300
9917
|
}
|
|
9918
|
+
/**
|
|
9919
|
+
* The workspace's translator: its resolved locale and direction, `t()` for messages and
|
|
9920
|
+
* `format` for numbers and dates. Every cell's chart carries the same settings
|
|
9921
|
+
* (`cell.chart.i18n`), and contributed actions read it as `ctx.i18n`. The same object
|
|
9922
|
+
* for the workspace's lifetime; it reads the new settings after {@link setLocale}.
|
|
9923
|
+
*/
|
|
9924
|
+
get i18n() {
|
|
9925
|
+
return this.localeState;
|
|
9926
|
+
}
|
|
9927
|
+
/**
|
|
9928
|
+
* Change the language, direction or formatting of the whole workspace at runtime — a
|
|
9929
|
+
* locale tag (`ws.setLocale('de')`) or any {@link LocalizationOptions} fields. Fields
|
|
9930
|
+
* you pass replace their current value (an explicit `undefined` restores the default);
|
|
9931
|
+
* the others are kept. Every cell re-renders in place, the shared chrome follows, and
|
|
9932
|
+
* `locale:changed` fires. Register the locale's pack (`registerLocale`) first for
|
|
9933
|
+
* translated text.
|
|
9934
|
+
*/
|
|
9935
|
+
setLocale(next) {
|
|
9936
|
+
if (this.destroyed) return;
|
|
9937
|
+
const patch = typeof next === "string" ? { locale: next } : pickLocalization(next);
|
|
9938
|
+
this.localeState.update(patch);
|
|
9939
|
+
this.applyRootAttributes();
|
|
9940
|
+
for (const cell of this.cellsById.values()) {
|
|
9941
|
+
cell.chart.setLocale(patch);
|
|
9942
|
+
cell.relocalize();
|
|
9943
|
+
}
|
|
9944
|
+
this.relocalizeChrome();
|
|
9945
|
+
this.events.emit("locale:changed", { locale: this.localeState.locale, direction: this.localeState.direction });
|
|
9946
|
+
}
|
|
9947
|
+
/** Re-read the shared chrome in the current language. Dialogs, menus and drawers read
|
|
9948
|
+
* it on their next open; what stays on screen is relabeled here. */
|
|
9949
|
+
relocalizeChrome() {
|
|
9950
|
+
this.symbolPicker.relabel();
|
|
9951
|
+
this.indicatorPicker?.relabel();
|
|
9952
|
+
this.tfQuick.relabel();
|
|
9953
|
+
this.topbar.relocalize();
|
|
9954
|
+
this.bottombar?.relocalize();
|
|
9955
|
+
this.mobileBar?.relocalize();
|
|
9956
|
+
this.dock.relocalize();
|
|
9957
|
+
this.toolbarDef = buildToolbar(this.opts.drawings, this.localeState).definition;
|
|
9958
|
+
this.drawToolbar?.setDefinition(this.toolbarDef);
|
|
9959
|
+
this.drawToolbar?.relabel();
|
|
9960
|
+
this.drawToolbar?.setShortcuts(toolShortcutHints(this.keymap, this.localeState));
|
|
9961
|
+
this.drawingPill?.relocalize();
|
|
9962
|
+
if (this.attributionMark) relabelAttributionMark(this.attributionMark, this.localeState);
|
|
9963
|
+
}
|
|
9964
|
+
/** `lang` once the host set a locale, `dir` once it set a direction; otherwise the
|
|
9965
|
+
* attributes stay off and the host page decides. */
|
|
9966
|
+
applyRootAttributes() {
|
|
9967
|
+
const s = this.localeState;
|
|
9968
|
+
if (s.localeExplicit) this.root.setAttribute("lang", s.locale);
|
|
9969
|
+
else this.root.removeAttribute("lang");
|
|
9970
|
+
if (s.directionExplicit) this.root.setAttribute("dir", s.direction);
|
|
9971
|
+
else this.root.removeAttribute("dir");
|
|
9972
|
+
}
|
|
9301
9973
|
/** The context handed to contributed actions/attachments (rebuilt per invocation). */
|
|
9302
9974
|
context() {
|
|
9303
9975
|
return buildContext({
|
|
@@ -9314,6 +9986,7 @@ var VelaWorkspace = class {
|
|
|
9314
9986
|
};
|
|
9315
9987
|
},
|
|
9316
9988
|
root: this.root,
|
|
9989
|
+
i18n: this.localeState,
|
|
9317
9990
|
toast: (message, kind) => this.toastHost.show(message, kind),
|
|
9318
9991
|
stateDirty: () => this.markStateDirty(),
|
|
9319
9992
|
replay: this.replay
|
|
@@ -9814,7 +10487,7 @@ var VelaWorkspace = class {
|
|
|
9814
10487
|
resolveLayout(layout) {
|
|
9815
10488
|
if (typeof layout !== "string") return layout;
|
|
9816
10489
|
const def = ensureLayout(layout);
|
|
9817
|
-
if (!def) throw new Error(`[vela] unknown workspace layout "${layout}"
|
|
10490
|
+
if (!def) throw new Error(`[vela] unknown workspace layout "${layout}" — register it with registerLayout().`);
|
|
9818
10491
|
return def;
|
|
9819
10492
|
}
|
|
9820
10493
|
backendFor(def) {
|
|
@@ -9942,6 +10615,7 @@ var VelaWorkspace = class {
|
|
|
9942
10615
|
feed: this.feed,
|
|
9943
10616
|
engines: this.opts.engines ?? {},
|
|
9944
10617
|
chartDefaults: cellChartDefaults(this.opts),
|
|
10618
|
+
localization: () => this.localeState.options,
|
|
9945
10619
|
theme,
|
|
9946
10620
|
live: this.opts.live ?? false,
|
|
9947
10621
|
volume: this.opts.volume ?? true,
|
|
@@ -9994,10 +10668,11 @@ var VelaWorkspace = class {
|
|
|
9994
10668
|
chart.on("script:run", (run) => this.events.emit("script:run", { ...run, cell: cell.id }));
|
|
9995
10669
|
chart.on("priceStyle:change", ({ from, to }) => this.events.emit("cell:priceStyle", { id: cell.id, from, to }));
|
|
9996
10670
|
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 ??
|
|
10671
|
+
const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe, this.localeState), alert.indicator].filter(Boolean).join(" ");
|
|
10672
|
+
this.alerts.unshift({ cellId: cell.id, source, title: alert.title ?? this.t(TEXT22.alert), message: alert.message, time: alert.time });
|
|
9999
10673
|
if (this.alerts.length > this.alertCap) this.alerts.pop();
|
|
10000
|
-
this.
|
|
10674
|
+
const toast = alert.title ? this.t(TEXT22.alertToast, { source, title: alert.title, message: alert.message }) : this.t(TEXT22.alertToastUntitled, { source, message: alert.message });
|
|
10675
|
+
this.toastHost.show(toast, "info", 4e3);
|
|
10001
10676
|
this.topbar.setAlertCount(this.alerts.length);
|
|
10002
10677
|
});
|
|
10003
10678
|
chart.on("drawing:favorites", ({ favorites }) => {
|
|
@@ -10083,7 +10758,7 @@ var VelaWorkspace = class {
|
|
|
10083
10758
|
if (kind === "crosshair") {
|
|
10084
10759
|
for (const cell of this.cellsById.values()) cell.chart.renderer.setExternalCrosshair(null);
|
|
10085
10760
|
if (setting && ![...this.cellsById.values()].some((c) => c.chart.renderer.supportsExternalCrosshair)) {
|
|
10086
|
-
console.warn("[vela] crosshair sync: no cell renderer supports an external crosshair
|
|
10761
|
+
console.warn("[vela] crosshair sync: no cell renderer supports an external crosshair — nothing will show.");
|
|
10087
10762
|
}
|
|
10088
10763
|
if (this.topbar && this.def) this.topbar.setLayout(this.def.id);
|
|
10089
10764
|
this.markStateDirty();
|
|
@@ -10466,7 +11141,8 @@ var VelaWorkspace = class {
|
|
|
10466
11141
|
this.active.applyRange(preset);
|
|
10467
11142
|
this.bottombar?.setActiveRange(preset.id);
|
|
10468
11143
|
},
|
|
10469
|
-
onOpenChange: (open) => this.trackDialog(open)
|
|
11144
|
+
onOpenChange: (open) => this.trackDialog(open),
|
|
11145
|
+
i18n: this.localeState
|
|
10470
11146
|
}));
|
|
10471
11147
|
this.tfDrawer.open();
|
|
10472
11148
|
}
|
|
@@ -10479,7 +11155,8 @@ var VelaWorkspace = class {
|
|
|
10479
11155
|
isFavorite: (type) => this.active.chart.drawings.isFavorite(type),
|
|
10480
11156
|
onFavorite: (type, on) => this.active.chart.drawings.setFavorite(type, on),
|
|
10481
11157
|
onSelect: (type) => this.active.chart.drawings.setTool(type),
|
|
10482
|
-
onOpenChange: (open) => this.trackDialog(open)
|
|
11158
|
+
onOpenChange: (open) => this.trackDialog(open),
|
|
11159
|
+
i18n: () => this.localeState
|
|
10483
11160
|
}));
|
|
10484
11161
|
this.drawingsDrawer.open();
|
|
10485
11162
|
}
|
|
@@ -10487,16 +11164,17 @@ var VelaWorkspace = class {
|
|
|
10487
11164
|
const has = (id) => topbarHas(this.topbarComp, id);
|
|
10488
11165
|
this.moreDrawer ?? (this.moreDrawer = new MoreDrawer({
|
|
10489
11166
|
host: this.root,
|
|
11167
|
+
i18n: this.localeState,
|
|
10490
11168
|
...has("undo-redo") ? { onUndo: () => this.active.history.undo(), onRedo: () => this.active.history.redo() } : {},
|
|
10491
11169
|
...has("screenshot") ? { onScreenshot: this.screenshotOverride ? () => this.runOverride(this.screenshotOverride) : () => this.downloadScreenshot() } : {},
|
|
10492
11170
|
canUndo: () => this.active.history.canUndo,
|
|
10493
11171
|
canRedo: () => this.active.history.canRedo,
|
|
10494
|
-
priceStyles: () => priceStyleIds().map((id) => ({ id, label: priceStyleLabel(id), icon: priceStyleIcon(id) })),
|
|
11172
|
+
priceStyles: () => priceStyleIds().map((id) => ({ id, label: priceStyleLabel(id, this.localeState), icon: priceStyleIcon(id) })),
|
|
10495
11173
|
priceStyle: () => this.active.priceStyle,
|
|
10496
11174
|
onPriceStyle: (id) => this.active.setPriceStyle(id),
|
|
10497
11175
|
panels: () => has("panels") ? [...this.dock.list()] : [],
|
|
10498
11176
|
onTogglePanel: (id) => this.dock.toggle(id),
|
|
10499
|
-
...has("alerts") ? { alerts: () => this.alerts.map((a) => ({ title:
|
|
11177
|
+
...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
11178
|
// Actions placed on the bottom bar (left-aligned by default) have their stop
|
|
10501
11179
|
// there — only the menu-placed ones land in the drawer, or they would appear
|
|
10502
11180
|
// twice: left (primary) ones with the primary rows, right ones at the end.
|
|
@@ -10510,15 +11188,10 @@ var VelaWorkspace = class {
|
|
|
10510
11188
|
// single-chart mode and a composition without 'layout' omit it here too.
|
|
10511
11189
|
layout: this.monoLayout || !has("layout") ? void 0 : {
|
|
10512
11190
|
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 })),
|
|
11191
|
+
presets: () => layouts(this.localeState).filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label, checked: l.id === this.def.id })),
|
|
10514
11192
|
onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
|
|
10515
11193
|
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
|
-
],
|
|
11194
|
+
syncs: () => this.syncRows(),
|
|
10522
11195
|
onToggleSync: (id) => {
|
|
10523
11196
|
const kind = id;
|
|
10524
11197
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
@@ -10532,14 +11205,28 @@ var VelaWorkspace = class {
|
|
|
10532
11205
|
/** The contributed topbar actions the mobile menu lists, from one cluster. */
|
|
10533
11206
|
drawerActions(cluster) {
|
|
10534
11207
|
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()) }));
|
|
11208
|
+
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()) }));
|
|
11209
|
+
}
|
|
11210
|
+
/** Display text in the workspace's language. */
|
|
11211
|
+
t(text, values) {
|
|
11212
|
+
return textOf(this.localeState, text, values);
|
|
11213
|
+
}
|
|
11214
|
+
/** The layout SYNC switches (the topbar dropdown and the mobile drawer list the same). */
|
|
11215
|
+
syncRows() {
|
|
11216
|
+
return [
|
|
11217
|
+
{ id: "symbol", label: this.t(TEXT22.syncSymbol), checked: this.syncOpts.symbol === true },
|
|
11218
|
+
{ id: "timeframe", label: this.t(TEXT22.syncInterval), checked: this.syncOpts.timeframe === true },
|
|
11219
|
+
{ id: "crosshair", label: this.t(TEXT22.syncCrosshair), checked: this.syncOpts.crosshair === true },
|
|
11220
|
+
{ id: "style", label: this.t(TEXT22.syncStyle), checked: this.syncOpts.style === true }
|
|
11221
|
+
];
|
|
10536
11222
|
}
|
|
10537
11223
|
openTimezoneDrawer() {
|
|
10538
11224
|
this.timezoneDrawer ?? (this.timezoneDrawer = new TimezoneDrawer({
|
|
10539
11225
|
host: this.root,
|
|
10540
11226
|
timezone: () => this.timezone,
|
|
10541
11227
|
onTimezone: (zone) => this.setTimezone(zone),
|
|
10542
|
-
onOpenChange: (open) => this.trackDialog(open)
|
|
11228
|
+
onOpenChange: (open) => this.trackDialog(open),
|
|
11229
|
+
i18n: this.localeState
|
|
10543
11230
|
}));
|
|
10544
11231
|
this.timezoneDrawer.open();
|
|
10545
11232
|
}
|
|
@@ -10558,8 +11245,8 @@ var VelaWorkspace = class {
|
|
|
10558
11245
|
this.alertsMenu?.destroy();
|
|
10559
11246
|
const items = this.alerts.length ? this.alerts.map((a, i) => ({
|
|
10560
11247
|
id: String(i),
|
|
10561
|
-
label:
|
|
10562
|
-
})) : [{ id: "none", label:
|
|
11248
|
+
label: sliceGraphemes(this.t(TEXT22.alertRow, { source: a.source, time: eventTimeText(a.time, this.localeState), title: a.title, message: a.message }), ALERT_ROW_MAX)
|
|
11249
|
+
})) : [{ id: "none", label: this.t(TEXT22.noAlerts), disabled: true }];
|
|
10563
11250
|
this.alertsMenu = new Menu({
|
|
10564
11251
|
host: this.root,
|
|
10565
11252
|
items,
|
|
@@ -10594,29 +11281,29 @@ var VelaWorkspace = class {
|
|
|
10594
11281
|
this.keymap.register({
|
|
10595
11282
|
id: "chart.screenshot",
|
|
10596
11283
|
keys: "mod+alt+s",
|
|
10597
|
-
label: ov ? ov.label :
|
|
10598
|
-
category:
|
|
11284
|
+
label: ov ? ov.label : TEXT22.screenshot,
|
|
11285
|
+
category: TEXT22.categoryChart,
|
|
10599
11286
|
run: ov ? () => this.runOverride(ov) : () => this.downloadScreenshot()
|
|
10600
11287
|
});
|
|
10601
11288
|
}
|
|
10602
|
-
this.keymap.register({ id: "chart.reset-view", keys: "alt+r", label:
|
|
10603
|
-
this.keymap.register({ id: "chart.toggle-log", keys: "alt+l", label:
|
|
11289
|
+
this.keymap.register({ id: "chart.reset-view", keys: "alt+r", label: TEXT22.resetView, category: TEXT22.categoryChart, run: () => this.active.chart.setVisibleRangePreset("ALL") });
|
|
11290
|
+
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
11291
|
this.keymap.register({
|
|
10605
11292
|
id: "chart.toggle-percent",
|
|
10606
11293
|
keys: "alt+p",
|
|
10607
|
-
label:
|
|
10608
|
-
category:
|
|
11294
|
+
label: TEXT22.togglePercent,
|
|
11295
|
+
category: TEXT22.categoryChart,
|
|
10609
11296
|
run: () => {
|
|
10610
11297
|
const mode = this.active.chart.renderer.get("scaleMode");
|
|
10611
11298
|
this.active.chart.renderer.set("scaleMode", mode === "percent" ? "price" : "percent");
|
|
10612
11299
|
}
|
|
10613
11300
|
});
|
|
10614
|
-
this.keymap.register({ id: "drawings.trendline", keys: "alt+t", label:
|
|
11301
|
+
this.keymap.register({ id: "drawings.trendline", keys: "alt+t", label: TEXT22.trendline, category: TEXT22.categoryDrawings, run: () => this.active.chart.drawings.setTool("trendline") });
|
|
10615
11302
|
this.keymap.register({
|
|
10616
11303
|
id: "drawings.hline-cursor",
|
|
10617
11304
|
keys: "alt+h",
|
|
10618
|
-
label:
|
|
10619
|
-
category:
|
|
11305
|
+
label: TEXT22.hlineCursor,
|
|
11306
|
+
category: TEXT22.categoryDrawings,
|
|
10620
11307
|
run: () => {
|
|
10621
11308
|
const c = this.active;
|
|
10622
11309
|
if (c.lastCrossTime != null && c.lastCrossPrice != null) c.chart.drawings.add("hline", { anchors: [{ time: c.lastCrossTime, price: c.lastCrossPrice }] });
|
|
@@ -10625,32 +11312,32 @@ var VelaWorkspace = class {
|
|
|
10625
11312
|
this.keymap.register({
|
|
10626
11313
|
id: "drawings.vline-cursor",
|
|
10627
11314
|
keys: "alt+v",
|
|
10628
|
-
label:
|
|
10629
|
-
category:
|
|
11315
|
+
label: TEXT22.vlineCursor,
|
|
11316
|
+
category: TEXT22.categoryDrawings,
|
|
10630
11317
|
run: () => {
|
|
10631
11318
|
const c = this.active;
|
|
10632
11319
|
if (c.lastCrossTime != null && c.lastCrossPrice != null) c.chart.drawings.add("vline", { anchors: [{ time: c.lastCrossTime, price: c.lastCrossPrice }] });
|
|
10633
11320
|
}
|
|
10634
11321
|
});
|
|
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:
|
|
11322
|
+
this.keymap.register({ id: "history.undo", keys: ["mod+z"], label: TEXT22.undo, category: TEXT22.categoryEdit, run: () => this.active.history.undo() });
|
|
11323
|
+
this.keymap.register({ id: "history.redo", keys: ["mod+y", "mod+shift+z"], label: TEXT22.redo, category: TEXT22.categoryEdit, run: () => this.active.history.redo() });
|
|
11324
|
+
this.keymap.register({ id: "view.zoom-in", keys: "mod+arrowup", label: TEXT22.zoomIn, category: TEXT22.categoryChart, run: () => this.glider.zoom(ZOOM_IN) });
|
|
11325
|
+
this.keymap.register({ id: "view.zoom-out", keys: "mod+arrowdown", label: TEXT22.zoomOut, category: TEXT22.categoryChart, run: () => this.glider.zoom(ZOOM_OUT) });
|
|
11326
|
+
this.keymap.register({ id: "view.pan-left", keys: "mod+arrowleft", label: TEXT22.panLeft, category: TEXT22.categoryChart, run: () => this.active.chart.panBy(-PAN_FAST) });
|
|
11327
|
+
this.keymap.register({ id: "view.pan-right", keys: "mod+arrowright", label: TEXT22.panRight, category: TEXT22.categoryChart, run: () => this.active.chart.panBy(PAN_FAST) });
|
|
10641
11328
|
const indOv = this.indicatorsOverride;
|
|
10642
11329
|
if (indOv && topbarHas(this.topbarComp, "indicators")) {
|
|
10643
|
-
this.keymap.register({ id: "indicators.open", keys: "/", label: indOv.label, category:
|
|
11330
|
+
this.keymap.register({ id: "indicators.open", keys: "/", label: indOv.label, category: TEXT22.categoryIndicators, run: () => this.runOverride(indOv) });
|
|
10644
11331
|
} else if (this.indicatorPicker) {
|
|
10645
|
-
this.keymap.register({ id: "indicators.open", keys: "/", label:
|
|
11332
|
+
this.keymap.register({ id: "indicators.open", keys: "/", label: TEXT22.openIndicators, category: TEXT22.categoryIndicators, run: () => this.indicatorPicker?.open() });
|
|
10646
11333
|
}
|
|
10647
11334
|
this.keymap.register({
|
|
10648
11335
|
id: "help.shortcuts",
|
|
10649
11336
|
keys: "?",
|
|
10650
|
-
label:
|
|
10651
|
-
category:
|
|
11337
|
+
label: TEXT22.showShortcuts,
|
|
11338
|
+
category: TEXT22.categoryHelp,
|
|
10652
11339
|
run: () => {
|
|
10653
|
-
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (open) => this.trackDialog(open)));
|
|
11340
|
+
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (open) => this.trackDialog(open), () => this.localeState));
|
|
10654
11341
|
this.shortcutsHelp.open();
|
|
10655
11342
|
}
|
|
10656
11343
|
});
|
|
@@ -10659,6 +11346,7 @@ var VelaWorkspace = class {
|
|
|
10659
11346
|
routeTyping(ev) {
|
|
10660
11347
|
if (this.destroyed) return;
|
|
10661
11348
|
if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
|
|
11349
|
+
if (ev.isComposing || ev.key === "Process") return;
|
|
10662
11350
|
if (isEditableTarget(ev)) return;
|
|
10663
11351
|
const hit = resolveTyping(ev.key, { dialogs: this.openDialogs, symbolSearch: this.symbolPicker.isOpen, timeframeEntry: this.tfQuick.isOpen });
|
|
10664
11352
|
if (!hit) return;
|