@tradecanvas/chart 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/Chart.d.ts +6 -0
- package/dist/Chart.d.ts.map +1 -1
- package/dist/{LayoutSession-CE8S2GCG.js → LayoutSession-CO51QKwZ.js} +10 -3
- package/dist/LayoutSession-CO51QKwZ.js.map +1 -0
- package/dist/LayoutSession-DIPjmNVe.cjs +2 -0
- package/dist/LayoutSession-DIPjmNVe.cjs.map +1 -0
- package/dist/{WidgetCommandPalette-B1jLC9oE.js → WidgetCommandPalette-CSIKnTca.js} +2 -2
- package/dist/{WidgetCommandPalette-B1jLC9oE.js.map → WidgetCommandPalette-CSIKnTca.js.map} +1 -1
- package/dist/{WidgetCommandPalette-Cx-UQ9ft.cjs → WidgetCommandPalette-CZ3GhE1O.cjs} +2 -2
- package/dist/{WidgetCommandPalette-Cx-UQ9ft.cjs.map → WidgetCommandPalette-CZ3GhE1O.cjs.map} +1 -1
- package/dist/{WidgetDrawingSettings-ezbzlGqD.js → WidgetDrawingSettings-Cc8PeqEZ.js} +2 -2
- package/dist/{WidgetDrawingSettings-ezbzlGqD.js.map → WidgetDrawingSettings-Cc8PeqEZ.js.map} +1 -1
- package/dist/{WidgetDrawingSettings-BjPR5Wyq.cjs → WidgetDrawingSettings-DutEdVyV.cjs} +2 -2
- package/dist/{WidgetDrawingSettings-BjPR5Wyq.cjs.map → WidgetDrawingSettings-DutEdVyV.cjs.map} +1 -1
- package/dist/{WidgetGoToDate-DJyCaP4H.js → WidgetGoToDate-DnQxxvf1.js} +2 -2
- package/dist/{WidgetGoToDate-DJyCaP4H.js.map → WidgetGoToDate-DnQxxvf1.js.map} +1 -1
- package/dist/{WidgetGoToDate-Vfaztduf.cjs → WidgetGoToDate-hNVxz-Zb.cjs} +2 -2
- package/dist/{WidgetGoToDate-Vfaztduf.cjs.map → WidgetGoToDate-hNVxz-Zb.cjs.map} +1 -1
- package/dist/{WidgetIndicatorPicker-Cw7pFl_l.js → WidgetIndicatorPicker-DNjcuBp6.js} +3 -3
- package/dist/{WidgetIndicatorPicker-Cw7pFl_l.js.map → WidgetIndicatorPicker-DNjcuBp6.js.map} +1 -1
- package/dist/{WidgetIndicatorPicker-Nc-1Rbm_.cjs → WidgetIndicatorPicker-GCesxFMn.cjs} +2 -2
- package/dist/{WidgetIndicatorPicker-Nc-1Rbm_.cjs.map → WidgetIndicatorPicker-GCesxFMn.cjs.map} +1 -1
- package/dist/{WidgetIndicatorSettings-Da_T6bmX.cjs → WidgetIndicatorSettings-CBHWj5z5.cjs} +2 -2
- package/dist/{WidgetIndicatorSettings-Da_T6bmX.cjs.map → WidgetIndicatorSettings-CBHWj5z5.cjs.map} +1 -1
- package/dist/{WidgetIndicatorSettings-DR8Z9RfF.js → WidgetIndicatorSettings-XhHlqyvn.js} +3 -3
- package/dist/{WidgetIndicatorSettings-DR8Z9RfF.js.map → WidgetIndicatorSettings-XhHlqyvn.js.map} +1 -1
- package/dist/{WidgetSettings-DCGCymVY.js → WidgetSettings-B1ay0W6I.js} +3 -3
- package/dist/{WidgetSettings-DCGCymVY.js.map → WidgetSettings-B1ay0W6I.js.map} +1 -1
- package/dist/{WidgetSettings-COljE3G_.cjs → WidgetSettings-CBf6hUSV.cjs} +2 -2
- package/dist/{WidgetSettings-COljE3G_.cjs.map → WidgetSettings-CBf6hUSV.cjs.map} +1 -1
- package/dist/{WidgetSymbolSearch-B8iDwVy8.js → WidgetSymbolSearch-BlQWH3Md.js} +3 -3
- package/dist/{WidgetSymbolSearch-B8iDwVy8.js.map → WidgetSymbolSearch-BlQWH3Md.js.map} +1 -1
- package/dist/{WidgetSymbolSearch-DVIRTJuT.cjs → WidgetSymbolSearch-CxeLjy63.cjs} +2 -2
- package/dist/{WidgetSymbolSearch-DVIRTJuT.cjs.map → WidgetSymbolSearch-CxeLjy63.cjs.map} +1 -1
- package/dist/i18n-BFGknfTa.cjs +2 -0
- package/dist/i18n-BFGknfTa.cjs.map +1 -0
- package/dist/{i18n-DWLKoOBG.js → i18n-BZdkj80c.js} +7 -1
- package/dist/i18n-BZdkj80c.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{indicatorInputWords-VWMPRfzM.js → indicatorInputWords-B8lf8reQ.js} +2 -2
- package/dist/{indicatorInputWords-VWMPRfzM.js.map → indicatorInputWords-B8lf8reQ.js.map} +1 -1
- package/dist/{indicatorInputWords-C4Mefhve.cjs → indicatorInputWords-_kxwKob4.cjs} +2 -2
- package/dist/{indicatorInputWords-C4Mefhve.cjs.map → indicatorInputWords-_kxwKob4.cjs.map} +1 -1
- package/dist/widget/ChartWidget.d.ts.map +1 -1
- package/dist/widget/WidgetDrawingEdit.d.ts +13 -0
- package/dist/widget/WidgetDrawingEdit.d.ts.map +1 -1
- package/dist/widget/WidgetMenus.d.ts +2 -0
- package/dist/widget/WidgetMenus.d.ts.map +1 -1
- package/dist/widget/WidgetObjectTree.d.ts.map +1 -1
- package/dist/widget/WidgetSelectionToolbar.d.ts +4 -0
- package/dist/widget/WidgetSelectionToolbar.d.ts.map +1 -1
- package/dist/widget/drawingMenu.d.ts +3 -1
- package/dist/widget/drawingMenu.d.ts.map +1 -1
- package/dist/widget/index.cjs +4 -3
- package/dist/widget/index.cjs.map +1 -1
- package/dist/widget/index.js +179 -118
- package/dist/widget/index.js.map +1 -1
- package/dist/widget/locales/ar.d.ts.map +1 -1
- package/dist/widget/locales/cs.d.ts.map +1 -1
- package/dist/widget/locales/da.d.ts.map +1 -1
- package/dist/widget/locales/de.d.ts.map +1 -1
- package/dist/widget/locales/el.d.ts.map +1 -1
- package/dist/widget/locales/en.d.ts +3 -0
- package/dist/widget/locales/en.d.ts.map +1 -1
- package/dist/widget/locales/es.d.ts.map +1 -1
- package/dist/widget/locales/et.d.ts.map +1 -1
- package/dist/widget/locales/fa.d.ts.map +1 -1
- package/dist/widget/locales/fr.d.ts.map +1 -1
- package/dist/widget/locales/he.d.ts.map +1 -1
- package/dist/widget/locales/hu.d.ts.map +1 -1
- package/dist/widget/locales/id.d.ts.map +1 -1
- package/dist/widget/locales/index.cjs +1 -1
- package/dist/widget/locales/index.cjs.map +1 -1
- package/dist/widget/locales/index.js +85 -1
- package/dist/widget/locales/index.js.map +1 -1
- package/dist/widget/locales/it.d.ts.map +1 -1
- package/dist/widget/locales/ja.d.ts.map +1 -1
- package/dist/widget/locales/ko.d.ts.map +1 -1
- package/dist/widget/locales/ms.d.ts.map +1 -1
- package/dist/widget/locales/nb.d.ts.map +1 -1
- package/dist/widget/locales/nl.d.ts.map +1 -1
- package/dist/widget/locales/pl.d.ts.map +1 -1
- package/dist/widget/locales/pt.d.ts.map +1 -1
- package/dist/widget/locales/ro.d.ts.map +1 -1
- package/dist/widget/locales/ru.d.ts.map +1 -1
- package/dist/widget/locales/sk.d.ts.map +1 -1
- package/dist/widget/locales/sv.d.ts.map +1 -1
- package/dist/widget/locales/th.d.ts.map +1 -1
- package/dist/widget/locales/tr.d.ts.map +1 -1
- package/dist/widget/locales/vi.d.ts.map +1 -1
- package/dist/widget/locales/zh.d.ts.map +1 -1
- package/dist/widget/locales/zhHant.d.ts.map +1 -1
- package/dist/widget/widgetConfig.d.ts.map +1 -1
- package/dist/widget/widgetFeatures.d.ts.map +1 -1
- package/dist/{widgetFeatures-B-el8UA3.cjs → widgetFeatures-DpBWfQ8D.cjs} +2 -2
- package/dist/widgetFeatures-DpBWfQ8D.cjs.map +1 -0
- package/dist/{widgetFeatures-DsbSG6Pq.js → widgetFeatures-sU-9JOSB.js} +2 -1
- package/dist/widgetFeatures-sU-9JOSB.js.map +1 -0
- package/dist/widgetLocales-BdRQu8wS.cjs +2 -0
- package/dist/widgetLocales-BdRQu8wS.cjs.map +1 -0
- package/dist/{widgetLocales-BX7VRdl5.js → widgetLocales-Bgm5-BB5.js} +13 -1
- package/dist/widgetLocales-Bgm5-BB5.js.map +1 -0
- package/package.json +3 -3
- package/dist/LayoutSession-C9bQsnH2.cjs +0 -2
- package/dist/LayoutSession-C9bQsnH2.cjs.map +0 -1
- package/dist/LayoutSession-CE8S2GCG.js.map +0 -1
- package/dist/i18n-DWLKoOBG.js.map +0 -1
- package/dist/i18n-fgAwciRT.cjs +0 -2
- package/dist/i18n-fgAwciRT.cjs.map +0 -1
- package/dist/widgetFeatures-B-el8UA3.cjs.map +0 -1
- package/dist/widgetFeatures-DsbSG6Pq.js.map +0 -1
- package/dist/widgetLocales-BX7VRdl5.js.map +0 -1
- package/dist/widgetLocales-C_XTkni5.cjs +0 -2
- package/dist/widgetLocales-C_XTkni5.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetSymbolSearch-DVIRTJuT.cjs","names":[],"sources":["../src/widget/symbolBadge.ts","../src/widget/symbolMatch.ts","../src/widget/symbolCell.ts","../src/widget/symbolSearchFilters.ts","../src/widget/WidgetSymbolSearch.ts"],"sourcesContent":["import type { SymbolInfo } from '@tradecanvas/commons';\nimport { logoSrc, splitVenue } from './symbolCatalog.js';\n\n/** How many kind colours there are (`--tcw-kind-1` … `--tcw-kind-5`); 0 is a symbol of no kind. */\nexport const KIND_SLOTS = 5;\n/** The size a logo is fetched for: the badge's in Print, the default look. */\nconst LOGO_PX = 20;\n/** How many logos that failed to load are remembered before the widget forgets them and tries again. */\nconst MAX_FAILED_LOGOS = 1000;\n/**\n * The logos that failed to load on this page. A row is built again on every\n * keystroke, and a browser keeps no image that failed, so without this each\n * one would be asked for again each time.\n */\nconst failedLogos = new Set<string>();\n\n/**\n * The usual kinds as feeds write them, in English, Vietnamese, Japanese,\n * Chinese (both scripts) and Korean, each with its colour: a stock, a \"cổ\n * phiếu\" and a 株式 look the same. A perpetual is crypto's own kind of\n * futures and takes crypto's colour, so on a phone, where the kind's column\n * goes, it still looks apart from an exchange's futures. Kinds with no colour\n * of their own take the one of the kind they are like: a warrant is an option\n * on a share, a fund or an ETF holds a basket as an index does, and a bond is\n * fixed income, kept with the currencies. Vietnamese puts the tone mark of\n * \"hoá\" on either vowel.\n */\nconst KNOWN_KINDS: Record<string, number> = {\n stock: 1, stocks: 1, equity: 1, share: 1, shares: 1, 'cổ phiếu': 1, 株式: 1, 株: 1, 股票: 1, 주식: 1,\n futures: 2, future: 2, derivative: 2, derivatives: 2, option: 2, options: 2, 'phái sinh': 2, 先物: 2, 期货: 2, 期貨: 2, 선물: 2,\n warrant: 2, warrants: 2, 'covered warrant': 2, 'chứng quyền': 2,\n index: 3, indices: 3, 'chỉ số': 3, 指数: 3, 指數: 3, 지수: 3,\n fund: 3, funds: 3, etf: 3, etfs: 3, 'chứng chỉ quỹ': 3,\n crypto: 4, cryptocurrency: 4, perpetual: 4, perpetuals: 4, perp: 4, perps: 4, 'tiền mã hóa': 4, 'tiền mã hoá': 4, 'tiền điện tử': 4,\n 暗号資産: 4, 仮想通貨: 4, 加密货币: 4, 加密貨幣: 4, 数字货币: 4, 數字貨幣: 4, 虚拟货币: 4, 虛擬貨幣: 4, 암호화폐: 4, 가상자산: 4, 가상화폐: 4,\n forex: 5, fx: 5, currency: 5, 'ngoại hối': 5, 為替: 5, 外国為替: 5, 外汇: 5, 外匯: 5, 외환: 5,\n bond: 5, bonds: 5, 'trái phiếu': 5,\n};\n\n/** How many kinds' and exchanges' words are kept keyed before the widget starts again. */\nconst MAX_KIND_KEYS = 1000;\nconst kindKeys = new Map<string, string>();\n\n/** What lower-casing leaves of Turkish İ (an i and a combining dot), and dotless ı, which I's lower case is not. */\nconst TURKISH_I = /i\\u0307|\\u0131/g;\n\n/**\n * A kind as the search keys it: its words trimmed, in lower case, with their\n * marks composed. Turkish i's are keyed as one, dotted or not, so a feed's\n * \"HİSSE\" and \"Hisse\" are the same kind; the words shown stay as written.\n */\nexport function kindKey(type: unknown): string {\n if (typeof type !== 'string') return '';\n // The same few words come on every row, keystroke after keystroke: each is keyed once.\n let key = kindKeys.get(type);\n if (key === undefined) {\n key = type.trim().normalize('NFC').toLowerCase().replace(TURKISH_I, 'i');\n if (kindKeys.size >= MAX_KIND_KEYS) kindKeys.clear();\n kindKeys.set(type, key);\n }\n return key;\n}\n\n/**\n * The colour of a kind of symbol, 1 to 5; 0 for none. The usual kinds have\n * their own; any other (a feed's word in another language) gets one from its\n * words, the same every time.\n */\nexport function kindSlot(type: unknown): number {\n const key = kindKey(type);\n if (!key) return 0;\n // Only the list's own words: a feed's \"constructor\" is a kind like any other, not the object's.\n return Object.prototype.hasOwnProperty.call(KNOWN_KINDS, key) ? KNOWN_KINDS[key] : 1 + (hashWords(key) % KIND_SLOTS);\n}\n\n/** FNV-1a over the code points: short words spread well, and it is the same in every browser. */\nfunction hashWords(text: string): number {\n let hash = 0x811c9dc5;\n for (const char of text) {\n hash ^= char.codePointAt(0)!;\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n return hash;\n}\n\n/** The first letter or figure of a ticker, after any venue before a colon (\"KBS:VCB\" gives V). */\nexport function symbolInitial(symbol: string): string {\n const { ticker } = splitVenue(symbol);\n const first = /[\\p{L}\\p{N}]/u.exec(ticker);\n return first ? first[0].toUpperCase() : '';\n}\n\n/**\n * A row's badge: the symbol's logo when it has one the widget takes, else its\n * initial in its kind's colour. A logo that fails to load (a blocked host, a\n * page's image policy) gives way to the initial, and later rows show the\n * initial for it without asking again. Either is decoration: the ticker\n * beside it says the same.\n */\nexport function createSymbolBadge(symbol: string, info: SymbolInfo | undefined): HTMLElement {\n const src = logoSrc(info?.logo);\n if (!src || failedLogos.has(src)) return letterBadge(symbol, info);\n const img = document.createElement('img');\n img.className = 'tcw-ss-badge';\n img.alt = '';\n img.width = LOGO_PX;\n img.height = LOGO_PX;\n img.draggable = false;\n img.setAttribute('aria-hidden', 'true');\n img.setAttribute('loading', 'lazy');\n img.setAttribute('decoding', 'async');\n // The logo's host learns nothing of the page it is shown on.\n img.setAttribute('referrerpolicy', 'no-referrer');\n img.addEventListener('error', () => {\n rememberFailedLogo(src);\n img.replaceWith(letterBadge(symbol, info));\n }, { once: true });\n img.src = src;\n return img;\n}\n\nfunction rememberFailedLogo(src: string): void {\n if (failedLogos.size >= MAX_FAILED_LOGOS) failedLogos.clear();\n failedLogos.add(src);\n}\n\nfunction letterBadge(symbol: string, info: SymbolInfo | undefined): HTMLSpanElement {\n const badge = document.createElement('span');\n badge.className = 'tcw-ss-badge';\n badge.setAttribute('aria-hidden', 'true');\n badge.dataset.kind = String(kindSlot(info?.type));\n badge.textContent = symbolInitial(symbol);\n return badge;\n}\n","import { escapeHtml } from './escapeHtml.js';\n\n/** A name that starts with what was typed, and one that holds it further in: below a ticker's own match. */\nconst NAME_START_SCORE = 180;\nconst NAME_HOLDS_SCORE = 150;\nconst NAME_DEPTH_PENALTY_MAX = 50;\n\n/** What was typed, as it is matched: in upper case for a ticker, without its marks for a name. */\nexport interface SymbolQuery {\n readonly upper: string;\n readonly folded: string;\n}\n\n/** What was typed, made ready once for every symbol it is matched with. */\nexport function symbolQuery(typed: string): SymbolQuery {\n return { upper: typed.toUpperCase(), folded: foldMarks(typed) };\n}\n\n/**\n * How well a given symbol matches what was typed, by its ticker or its name\n * (`name`, as `foldMarks` gives it), when there is no feed to search: a\n * ticker that starts with it ranks first, then one that holds it, then a\n * name, then the ticker's letters in order. 0 for no match.\n */\nexport function scoreSymbol(symbol: string, name: string, query: SymbolQuery): number {\n return Math.max(scoreMatch(symbol.toUpperCase(), query.upper), nameScore(name, query.folded));\n}\n\n/**\n * Cheap fuzzy score:\n * exact match → 1000\n * starts-with → 500 + tighter-prefix bonus\n * holds it → 200 + tighter bonus\n * subsequence match → 100 − gap penalty\n * no match → 0\n */\nexport function scoreMatch(symbol: string, query: string): number {\n if (symbol === query) return 1000;\n if (symbol.startsWith(query)) return 500 + (50 - Math.min(50, symbol.length - query.length));\n if (symbol.includes(query)) return 200 + (50 - Math.min(50, symbol.length - query.length));\n\n let qi = 0;\n let lastIdx = -1;\n let gapPenalty = 0;\n for (let i = 0; i < symbol.length && qi < query.length; i++) {\n if (symbol[i] === query[qi]) {\n if (lastIdx >= 0) gapPenalty += i - lastIdx - 1;\n lastIdx = i;\n qi++;\n }\n }\n if (qi < query.length) return 0;\n return 100 - Math.min(99, gapPenalty);\n}\n\n/** A name that holds what was typed, the nearer its start the better; both without their marks. */\nfunction nameScore(name: string, typed: string): number {\n if (!name || !typed) return 0;\n if (name.startsWith(typed)) return NAME_START_SCORE;\n const at = name.indexOf(typed);\n return at < 0 ? 0 : NAME_HOLDS_SCORE - Math.min(NAME_DEPTH_PENALTY_MAX, at);\n}\n\n/**\n * Words without their marks, in upper case: a Vietnamese name (\"Ngân hàng\")\n * is often typed without them (\"ngan hang\"). Đ has no mark to take off; it\n * reads as D.\n */\nexport function foldMarks(text: string): string {\n return text.normalize('NFD').replace(/\\p{M}+/gu, '').replace(/[đĐ]/g, 'D').toUpperCase();\n}\n\n/**\n * The symbol from `from` to `to` (all of it by default) as markup, with the\n * letters the query matched marked: the run of them where the symbol holds\n * the query whole, else each in order; nothing when the symbol doesn't hold\n * them all (a match on the name). The match is found in the whole symbol, so\n * a part of it is marked as it would be in the whole.\n */\nexport function highlight(symbol: string, query: string, from = 0, to = symbol.length): string {\n const marked = query ? matchedLetters(symbol.toUpperCase(), query) : new Set<number>();\n let out = '';\n for (let i = from; i < to; i++) {\n out += marked.has(i) ? `<mark>${escapeHtml(symbol[i])}</mark>` : escapeHtml(symbol[i]);\n }\n return out;\n}\n\nfunction matchedLetters(upper: string, query: string): Set<number> {\n const marked = new Set<number>();\n const run = upper.indexOf(query);\n if (run >= 0) {\n for (let i = run; i < run + query.length; i++) marked.add(i);\n return marked;\n }\n let qi = 0;\n for (let i = 0; i < upper.length && qi < query.length; i++) {\n if (upper[i] === query[qi]) {\n marked.add(i);\n qi++;\n }\n }\n return qi < query.length ? new Set() : marked;\n}\n","import { splitVenue } from './symbolCatalog.js';\nimport { highlight } from './symbolMatch.js';\n\n/**\n * A row's symbol as a trader says it: the ticker, without the venue before\n * it (\"HYPERLIQUID:xyz:GOLD\" reads GOLD), the whole symbol on hover; the\n * exchange column names the venue. The letters `query` (upper case) matched\n * are marked. It reads left to right in any widget (\"^GSPC\", \"BRK.B\").\n */\nexport function createSymbolCell(symbol: string, query: string): HTMLSpanElement {\n const cell = document.createElement('span');\n cell.className = 'tcw-ss-symbol';\n cell.dir = 'ltr';\n const { venue } = splitVenue(symbol);\n if (venue) cell.title = symbol;\n const ticker = document.createElement('span');\n ticker.className = 'tcw-ss-ticker';\n ticker.innerHTML = highlight(symbol, query, venue.length, symbol.length);\n cell.appendChild(ticker);\n return cell;\n}\n\n/** A venue's words, for the exchange column of a symbol whose feed names no exchange (\"BINANCE:\" is BINANCE). */\nexport function venueName(symbol: string): string {\n return splitVenue(symbol).venue.replace(/:$/, '').replace(/:/g, ' ');\n}\n","import type { SymbolInfo } from '@tradecanvas/commons';\nimport type { Translator } from './i18n.js';\nimport { kindKey } from './symbolBadge.js';\n\n/** Kinds and exchanges are keyed alike: their words trimmed, in lower case. */\nconst keyOf = kindKey;\n\n/** Exchanges must be at least this many before the search offers to keep to one. */\nconst MIN_EXCHANGES = 2;\n/** Tabs past an end by less than this (a rounded pixel) count as none. */\nconst MORE_SLACK_PX = 1;\n\n/**\n * The symbol search's filters: a tab for each kind of symbol and a list of\n * exchanges, for the dialog it is made for. What it offers only grows while\n * the dialog is open, from every row shown since it opened, so the tabs don't\n * come and go (and the list jump) as typing narrows the results; a kind or an\n * exchange picked stays picked. Kinds and exchanges that differ only in case\n * are one, named as first written.\n */\nexport class SymbolSearchFilters {\n /** The row holding both, hidden while there is nothing to pick. */\n readonly el: HTMLDivElement;\n private readonly kindsEl: HTMLDivElement;\n private readonly exchangeEl: HTMLSelectElement;\n private readonly kinds = new Map<string, string>();\n private readonly exchanges = new Map<string, string>();\n private kind: string | null = null;\n private exchange: string | null = null;\n /** Tells of the tabs' room changing (a phone turned), where the browser can. */\n private readonly resize: ResizeObserver | null;\n\n /**\n * `onChange` when the user picks a kind or an exchange; `refocus` says the\n * search may take the keyboard back. A tab is pressed once, on purpose; an\n * exchange list changes with each arrow pressed on it, so it keeps the\n * keyboard.\n */\n constructor(private readonly t: Translator, private readonly onChange: (refocus: boolean) => void) {\n this.el = document.createElement('div');\n this.el.className = 'tcw-ss-filters';\n this.el.hidden = true;\n this.kindsEl = document.createElement('div');\n this.kindsEl.className = 'tcw-ss-kinds';\n this.kindsEl.setAttribute('role', 'group');\n this.kindsEl.setAttribute('aria-label', t('symbolSearch.kinds'));\n this.kindsEl.hidden = true;\n this.kindsEl.addEventListener('scroll', () => this.markMore(), { passive: true });\n this.resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => this.markMore());\n this.resize?.observe(this.kindsEl);\n this.exchangeEl = document.createElement('select');\n this.exchangeEl.className = 'tcw-settings-select tcw-ss-exchange-filter';\n this.exchangeEl.setAttribute('aria-label', t('symbolSearch.exchange'));\n this.exchangeEl.hidden = true;\n this.exchangeEl.addEventListener('change', () => {\n this.exchange = keyOf(this.exchangeEl.value) || null;\n this.onChange(false);\n });\n this.el.append(this.kindsEl, this.exchangeEl);\n }\n\n /** Hear of the kinds and exchanges these rows come in, and show what there is to pick. */\n see(infos: readonly (SymbolInfo | undefined)[]): void {\n const kindsBefore = this.kinds.size;\n const exchangesBefore = this.exchanges.size;\n for (const info of infos) {\n remember(this.kinds, info?.type);\n remember(this.exchanges, info?.exchange);\n }\n if (this.exchanges.size !== exchangesBefore) this.renderExchanges();\n this.exchangeEl.hidden = this.exchanges.size < MIN_EXCHANGES;\n // An exchange filter shows beside the kind tabs, \"All\" at the least: alone, it stood at the end of\n // an empty row.\n if (this.kinds.size !== kindsBefore || (!this.exchangeEl.hidden && this.kindsEl.childElementCount === 0)) this.renderKinds();\n this.kindsEl.hidden = this.kinds.size === 0 && this.exchangeEl.hidden;\n this.el.hidden = this.kindsEl.hidden && this.exchangeEl.hidden;\n this.markMore();\n }\n\n /** Stop hearing of the tabs' room: the dialog has gone. */\n destroy(): void {\n this.resize?.disconnect();\n }\n\n /**\n * Where the tabs scroll in one line (a phone), say at which ends there are\n * more past the edge, `data-more` \"start\", \"end\" or both, for the fade there:\n * a tab cut at the edge then reads as more to scroll to. Right to left, the\n * row scrolls the other way, and its scrollLeft runs below zero.\n */\n private markMore(): void {\n const el = this.kindsEl;\n const past = Math.abs(el.scrollLeft);\n const room = el.scrollWidth - el.clientWidth;\n const ends = [past > MORE_SLACK_PX ? 'start' : '', past < room - MORE_SLACK_PX ? 'end' : ''].filter(Boolean);\n if (ends.length > 0) el.dataset.more = ends.join(' ');\n else delete el.dataset.more;\n }\n\n /**\n * Whether a symbol is of the kind and on the exchange picked. `lenient`\n * (the symbols given, not a feed's results) lets one through that says\n * nothing of its kind or exchange: it may be either, and hiding it would\n * hide a symbol the host listed.\n */\n passes(info: SymbolInfo | undefined, lenient: boolean): boolean {\n return matches(this.kind, keyOf(info?.type), lenient) && matches(this.exchange, keyOf(info?.exchange), lenient);\n }\n\n private renderKinds(): void {\n const chip = (value: string | null, text: string) => {\n const b = document.createElement('button');\n b.type = 'button';\n b.className = 'tcw-ss-type';\n b.textContent = text;\n // A feed's kind reads in its own direction (\"Stock.\" in a right-to-left widget); \"All\" is the widget's.\n if (value !== null) b.dir = 'auto';\n b.setAttribute('aria-pressed', String(this.kind === value));\n b.addEventListener('click', () => {\n this.kind = value;\n for (const other of this.kindsEl.children) other.setAttribute('aria-pressed', String(other === b));\n this.onChange(true);\n });\n return b;\n };\n const chips = [...this.kinds].map(([key, label]) => chip(key, label));\n this.kindsEl.replaceChildren(chip(null, this.t('symbolSearch.allKinds')), ...chips);\n }\n\n private renderExchanges(): void {\n const option = (value: string, text: string) => {\n const o = document.createElement('option');\n o.value = value;\n o.textContent = text;\n if (value) o.dir = 'auto';\n return o;\n };\n const options = [...this.exchanges.values()].map((label) => option(label, label));\n this.exchangeEl.replaceChildren(option('', this.t('symbolSearch.allExchanges')), ...options);\n this.exchangeEl.value = this.exchange === null ? '' : this.exchanges.get(this.exchange) ?? '';\n }\n}\n\nfunction remember(seen: Map<string, string>, words: string | undefined): void {\n const key = keyOf(words);\n if (key && !seen.has(key)) seen.set(key, words!.trim());\n}\n\nfunction matches(picked: string | null, key: string, lenient: boolean): boolean {\n return picked === null || key === picked || (lenient && key === '');\n}\n","import type { SymbolInfo } from '@tradecanvas/commons';\nimport { EN_TRANSLATOR, fill, type Translator } from './i18n.js';\nimport { createIcon } from './icons.js';\nimport { keepTabInside } from './focusTrap.js';\nimport { markPart } from './widgetFeatures.js';\nimport { cleanSymbolInfo, mergeSymbolInfo, readSymbolCatalog, readSymbolResults } from './symbolCatalog.js';\nimport { createSymbolBadge } from './symbolBadge.js';\nimport { createSymbolCell, venueName } from './symbolCell.js';\nimport { foldMarks, scoreSymbol, symbolQuery } from './symbolMatch.js';\nimport { SymbolSearchFilters } from './symbolSearchFilters.js';\n\nexport interface SymbolSearchCallbacks {\n onPick: (symbol: string) => void;\n onClose: () => void;\n}\n\n/** Symbols matching a query, best first; aborted when a newer query replaces it. */\nexport type SymbolSearchFn = (query: string, signal: AbortSignal) => Promise<SymbolInfo[]>;\n\n/** How long typing must pause before a search function is asked. */\nexport const SYMBOL_SEARCH_DEBOUNCE_MS = 150;\n\n/** The key code a browser gives a key an input method takes. */\nconst IME_KEY_CODE = 229;\n\n/**\n * The most rows the list draws, the best first; a line under them says how\n * many more typing would narrow. Every row is laid out in the columns all\n * the others share, so a list of thousands would be laid out again on each\n * keystroke.\n */\nexport const MAX_LISTED_ROWS = 200;\n\n/** A symbol on offer: its ticker, or what is known of it (a name, kind, exchange, logo). */\nexport type SymbolEntry = string | SymbolInfo;\n\n/** What the symbol search opens with. */\nexport interface SymbolSearchStart {\n /** Listed until something is typed; filtered, by ticker and name, when there is no `search`. */\n symbols: readonly SymbolEntry[];\n /** The chart's symbol, marked in the list. */\n current: string;\n /** What the chart knows of `current`: its name and logo, where the symbols given and the feed have none. */\n currentInfo?: SymbolInfo | null;\n /** A feed's search, asked once typing pauses. */\n search?: SymbolSearchFn;\n /** Typed already: the letter that opened it. */\n query?: string;\n /** Where a pick goes, in place of `onPick`. */\n onPick?: (symbol: string) => void;\n /** A line at the foot. */\n hint?: string;\n}\n\n/** Something compared with the chart's symbol, as the compare dialog lists it. */\nexport interface CompareEntry {\n id: string;\n symbol: string;\n /** How it is compared, in words. */\n way: string;\n color?: string;\n}\n\n/** What the compare dialog opens with. */\nexport interface CompareStart {\n symbols: readonly SymbolEntry[];\n current: string;\n search?: SymbolSearchFn;\n /** The ways to add a symbol, and the one chosen to start with. */\n ways: readonly { id: string; label: string }[];\n way: string;\n compared: readonly CompareEntry[];\n onAdd: (symbol: string, way: string) => void;\n onRemove: (id: string) => void;\n}\n\ninterface Row {\n symbol: string;\n score: number;\n info?: SymbolInfo;\n}\n\ntype Mode = { kind: 'search'; start: SymbolSearchStart } | { kind: 'compare'; start: CompareStart };\n\nlet nextSearchId = 1;\n\n/** A row's exchange: the one its feed names, else the venue before its ticker (\"BINANCE:\" is BINANCE). */\nfunction exchangeOf(row: Row): string {\n return row.info?.exchange?.trim() || venueName(row.symbol);\n}\n\n/** What the kind and exchange filters read of a row: its exchange as its column shows it. */\nfunction filterInfo(row: Row): SymbolInfo | undefined {\n const exchange = exchangeOf(row);\n return exchange ? { ...(row.info ?? { symbol: row.symbol }), exchange } : row.info;\n}\n\n/**\n * The symbol search, and the same dialog for adding a symbol to compare.\n *\n * - The symbols it is given are listed until something is typed, named\n * where they came with a SymbolInfo (or the chart knows its own).\n * - Without a search function it filters them by ticker and name with a\n * cheap match, good enough for lists in the low thousands. The list draws\n * the best `MAX_LISTED_ROWS` and says how many more typing would narrow.\n * - With one (a feed's `searchSymbols`) it asks once typing pauses, cancels a\n * query a newer one replaced, and lists names, kinds and exchanges; what it\n * hears is kept for the list it opens with next time.\n * - Whose words a row shows is one rule, wherever it is listed: the feed's,\n * then the ones given for what it left out (a logo, say), then the name\n * and logo the chart knows of its own symbol. Words that aren't text (a\n * JSON feed's numeric code, a null) say nothing.\n * - The rows can be narrowed to a kind (stocks, crypto) and, when they come\n * from two or more, to an exchange.\n * - Comparing, a pick adds the symbol the way chosen above the list and the\n * dialog stays; what is compared already is listed first, each removable.\n *\n * The keys stay in the search field: up and down move through the list, Enter picks.\n */\nexport class WidgetSymbolSearch {\n private backdrop: HTMLDivElement | null = null;\n private input: HTMLInputElement | null = null;\n private list: HTMLDivElement | null = null;\n /** What scrolls: what is compared, the list of symbols, a note when there are none. */\n private results: HTMLDivElement | null = null;\n /** Says \"Searching…\", \"Search failed\", \"No symbols match\" to a screen reader. */\n private live: HTMLDivElement | null = null;\n /** The heads of the columns, at the top of what scrolls. */\n private columns: HTMLDivElement | null = null;\n private filters: SymbolSearchFilters | null = null;\n private mode: Mode | null = null;\n /** The tickers given to it, cleaned. */\n private given: string[] = [];\n private rows: Row[] = [];\n /** Whether the rows are the symbols given (filtered or not), not a feed's results. */\n private rowsGiven = false;\n private shown: Row[] = [];\n private compared: readonly CompareEntry[] = [];\n private way = '';\n private selectedIndex = 0;\n private searchFn: SymbolSearchFn | null = null;\n private searchTimer: ReturnType<typeof setTimeout> | null = null;\n private searchAbort: AbortController | null = null;\n private status: 'idle' | 'searching' | 'failed' = 'idle';\n /** Enter came before the results for what was typed: take the top one when they land. */\n private pickOnResults = false;\n /** What the feed's searches told of each symbol, kept from one opening to the next: its words win. */\n private readonly heard = new Map<string, SymbolInfo>();\n /** What came with the symbols it opened with, over what the chart knows of its own. */\n private givenInfo = new Map<string, SymbolInfo>();\n /** Each name without its marks, made once while open: a name is matched on every keystroke. */\n private foldedNames = new Map<string, string>();\n private returnFocus: HTMLElement | null = null;\n private readonly uid = nextSearchId++;\n\n /** Where the dialog mounts (the widget's overlay layer: themed, and inside it when fullscreen). */\n constructor(\n private readonly callbacks: SymbolSearchCallbacks,\n private readonly host: () => HTMLElement = () => document.body,\n private readonly t: Translator = EN_TRANSLATOR,\n ) {}\n\n open(start: SymbolSearchStart): void {\n this.begin({ kind: 'search', start });\n }\n\n openCompare(start: CompareStart): void {\n this.compared = start.compared;\n this.way = start.ways.some((w) => w.id === start.way) ? start.way : start.ways[0]?.id ?? '';\n this.begin({ kind: 'compare', start });\n }\n\n /** What is compared now, after an add or a remove. */\n setCompared(entries: readonly CompareEntry[]): void {\n this.compared = entries;\n if (this.mode?.kind !== 'compare' || !this.results) return;\n // A remove button that had the keyboard is gone with its row: the search takes it.\n const hadFocus = this.results.contains(document.activeElement);\n this.renderList();\n if (hadFocus && !this.results.contains(document.activeElement)) this.input?.focus();\n }\n\n close(): void {\n if (!this.backdrop) return;\n this.cancelSearch();\n this.pickOnResults = false;\n this.searchFn = null;\n this.backdrop.remove();\n this.backdrop = this.input = this.list = this.results = this.live = this.columns = null;\n this.filters?.destroy();\n this.filters = null;\n this.mode = null;\n this.callbacks.onClose();\n const back = this.returnFocus;\n this.returnFocus = null;\n if (back?.isConnected) back.focus();\n }\n\n isOpen(): boolean {\n return this.backdrop !== null;\n }\n\n /** Whether it is open to add a symbol to compare. */\n isComparing(): boolean {\n return this.mode?.kind === 'compare';\n }\n\n destroy(): void {\n this.close();\n }\n\n private begin(mode: Mode): void {\n if (this.backdrop) this.close();\n this.returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.mode = mode;\n this.searchFn = mode.start.search ?? null;\n this.status = 'idle';\n this.learnGiven(mode);\n this.build(mode);\n const query = mode.kind === 'search' ? mode.start.query ?? '' : '';\n if (this.input) this.input.value = query;\n if (query) {\n this.filter();\n if (this.searchFn) this.searchNow();\n } else {\n this.listGiven();\n }\n this.input?.focus();\n }\n\n /**\n * The tickers given, and what came with them. What the chart knows of its\n * own symbol gives only a name and a logo, where none came with it: its\n * kind and exchange may be in other words (a kind in English where the\n * list's are translated, a broker for the exchange), and would add a tab or\n * an exchange that no row is in once the feed has named the symbol.\n */\n private learnGiven(mode: Mode): void {\n const catalog = readSymbolCatalog(mode.start.symbols);\n this.given = catalog.names;\n this.givenInfo = catalog.info;\n this.foldedNames = new Map();\n const current = mode.start.current;\n const own = mode.kind === 'search' ? mode.start.currentInfo : null;\n if (own && typeof own === 'object' && current) {\n const { description, logo } = cleanSymbolInfo(own, current);\n this.givenInfo.set(current, mergeSymbolInfo({ symbol: current, description, logo }, this.givenInfo.get(current))!);\n }\n }\n\n /** What a row of `symbol` shows: the feed's words, the ones given for the rest. */\n private infoOf(symbol: string): SymbolInfo | undefined {\n return mergeSymbolInfo(this.givenInfo.get(symbol), this.heard.get(symbol));\n }\n\n // --- Building ---\n\n private build(mode: Mode): void {\n const backdrop = document.createElement('div');\n backdrop.className = 'tcw-modal-backdrop';\n // A press that starts in the dialog and ends on the backdrop doesn't close it.\n let pressedBackdrop = false;\n backdrop.addEventListener('pointerdown', (e) => {\n pressedBackdrop = e.target === backdrop;\n });\n backdrop.addEventListener('click', (e) => {\n if (e.target === backdrop && pressedBackdrop) this.close();\n pressedBackdrop = false;\n });\n\n const modal = document.createElement('div');\n modal.className = `tcw-modal tcw-ss${mode.kind === 'compare' ? ' tcw-ss--compare' : ''}`;\n markPart(modal, mode.kind === 'compare' ? 'compare' : 'symbolSearch');\n modal.setAttribute('role', 'dialog');\n modal.setAttribute('aria-modal', 'true');\n modal.setAttribute('aria-labelledby', `tcw-ss-title-${this.uid}`);\n modal.tabIndex = -1;\n modal.addEventListener('keydown', (e) => this.onKeyDown(e, modal));\n\n modal.append(this.buildHeader(mode), this.buildSearch());\n if (mode.kind === 'compare') modal.appendChild(this.buildWay(mode.start));\n\n const filters = new SymbolSearchFilters(this.t, (refocus) => {\n this.afterResults(false);\n if (refocus) this.input?.focus();\n });\n\n const list = document.createElement('div');\n list.className = 'tcw-ss-list';\n list.id = `tcw-ss-list-${this.uid}`;\n list.setAttribute('role', 'listbox');\n const results = document.createElement('div');\n results.className = 'tcw-ss-results';\n const columns = this.buildColumns();\n results.append(columns, list);\n // A press on a heading or between the rows keeps the keyboard in the search (a remove button takes it).\n results.addEventListener('mousedown', (e) => {\n if (!(e.target as Element).closest('button')) e.preventDefault();\n });\n const live = document.createElement('div');\n live.className = 'tcw-ss-live tcw-visually-hidden';\n live.setAttribute('aria-live', 'polite');\n\n modal.append(filters.el, results, live);\n const hint = mode.kind === 'search' ? mode.start.hint : undefined;\n if (hint) {\n const foot = document.createElement('div');\n foot.className = 'tcw-ss-foot';\n foot.textContent = hint;\n modal.appendChild(foot);\n }\n backdrop.appendChild(modal);\n this.host().appendChild(backdrop);\n this.backdrop = backdrop;\n this.filters = filters;\n this.columns = columns;\n this.list = list;\n this.results = results;\n this.live = live;\n }\n\n /** The heads of the columns: none over the badges, then symbol, description, type, exchange. */\n private buildColumns(): HTMLDivElement {\n const columns = document.createElement('div');\n columns.className = 'tcw-ss-columns';\n columns.setAttribute('aria-hidden', 'true');\n const heads = ['', this.t('symbolSearch.symbol'), this.t('symbolSearch.description'), this.t('symbolSearch.type'), this.t('symbolSearch.exchange')];\n columns.append(...heads.map((text) => this.tag('tcw-ss-head', text)));\n return columns;\n }\n\n private buildHeader(mode: Mode): HTMLDivElement {\n const header = document.createElement('div');\n header.className = 'tcw-modal-header';\n const title = document.createElement('h3');\n title.id = `tcw-ss-title-${this.uid}`;\n title.textContent = this.t(mode.kind === 'compare' ? 'compare.title' : 'symbolSearch.title');\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.className = 'tcw-modal-close';\n closeBtn.setAttribute('aria-label', this.t('common.close'));\n closeBtn.innerHTML = createIcon('x', 16);\n closeBtn.addEventListener('click', () => this.close());\n header.append(title, closeBtn);\n return header;\n }\n\n private buildSearch(): HTMLLabelElement {\n const row = document.createElement('label');\n row.className = 'tcw-ss-search';\n row.innerHTML = createIcon('search', 16);\n const input = document.createElement('input');\n input.type = 'search';\n input.className = 'tcw-ss-input';\n input.placeholder = this.t('symbolSearch.placeholder');\n input.setAttribute('aria-label', this.t('symbolSearch.placeholder'));\n input.setAttribute('role', 'combobox');\n input.setAttribute('aria-expanded', 'true');\n input.setAttribute('aria-controls', `tcw-ss-list-${this.uid}`);\n input.setAttribute('aria-autocomplete', 'list');\n input.autocomplete = 'off';\n input.spellcheck = false;\n input.addEventListener('input', () => this.filter());\n row.appendChild(input);\n this.input = input;\n return row;\n }\n\n /** Comparing: how the next symbol is added. */\n private buildWay(start: CompareStart): HTMLLabelElement {\n const row = document.createElement('label');\n row.className = 'tcw-ss-way';\n const text = document.createElement('span');\n text.textContent = this.t('compare.addAs');\n const select = document.createElement('select');\n select.className = 'tcw-settings-select tcw-ss-way-select';\n for (const way of start.ways) {\n const option = document.createElement('option');\n option.value = way.id;\n option.textContent = way.label;\n select.appendChild(option);\n }\n select.value = this.way;\n select.addEventListener('change', () => {\n this.way = select.value;\n });\n row.append(text, select);\n return row;\n }\n\n // --- Searching ---\n\n /** The symbols it was given, named where they came with a name or a search told of one. */\n private listGiven(): void {\n this.rows = this.given.map((symbol) => ({ symbol, score: 0, info: this.infoOf(symbol) }));\n this.rowsGiven = true;\n this.afterResults(true);\n }\n\n private filter(): void {\n this.pickOnResults = false;\n const typed = this.input?.value.trim() ?? '';\n if (this.searchFn && typed) {\n this.scheduleSearch(typed);\n return;\n }\n this.cancelSearch();\n this.status = 'idle';\n if (!typed) {\n this.listGiven();\n return;\n }\n const query = symbolQuery(typed);\n this.rows = this.given\n .map((symbol) => {\n const info = this.infoOf(symbol);\n return { symbol, score: scoreSymbol(symbol, this.foldedName(info?.description), query), info };\n })\n .filter((r) => r.score > 0)\n .sort((a, b) => b.score - a.score);\n this.rowsGiven = true;\n this.afterResults(false);\n }\n\n /** A name without its marks, as matching takes it; made once for each name while open. */\n private foldedName(name: string | undefined): string {\n if (!name) return '';\n let folded = this.foldedNames.get(name);\n if (folded === undefined) {\n folded = foldMarks(name);\n this.foldedNames.set(name, folded);\n }\n return folded;\n }\n\n private scheduleSearch(query: string): void {\n if (this.searchTimer !== null) clearTimeout(this.searchTimer);\n this.searchTimer = setTimeout(() => {\n this.searchTimer = null;\n void this.runSearch(query);\n }, SYMBOL_SEARCH_DEBOUNCE_MS);\n }\n\n /** Run the search the pause would have started, now. */\n private searchNow(): void {\n if (this.searchTimer !== null) clearTimeout(this.searchTimer);\n this.searchTimer = null;\n void this.runSearch(this.input?.value.trim() ?? '');\n }\n\n private async runSearch(query: string): Promise<void> {\n const search = this.searchFn;\n if (!search) return;\n this.searchAbort?.abort();\n const controller = new AbortController();\n this.searchAbort = controller;\n // The rows on screen stay as they are while this one runs: no flicker as typing goes on, and no\n // long list drawn again. \"Searching…\" shows only where there are none.\n this.status = 'searching';\n if (this.shown.length === 0) this.renderList();\n try {\n const found = await search(query, controller.signal);\n if (controller.signal.aborted) return;\n const results = readSymbolResults(found);\n // What a later search leaves out, an earlier one may have told of.\n for (const info of results) this.heard.set(info.symbol, mergeSymbolInfo(this.heard.get(info.symbol), info)!);\n this.rows = results.map((info) => ({ symbol: info.symbol, score: 0, info: this.infoOf(info.symbol) }));\n this.status = 'idle';\n } catch {\n if (controller.signal.aborted) return;\n this.rows = [];\n this.status = 'failed';\n }\n this.rowsGiven = false;\n this.afterResults(false);\n if (this.pickOnResults) {\n this.pickOnResults = false;\n this.pick(0);\n }\n }\n\n private cancelSearch(): void {\n if (this.searchTimer !== null) clearTimeout(this.searchTimer);\n this.searchTimer = null;\n this.searchAbort?.abort();\n this.searchAbort = null;\n }\n\n /**\n * New rows: the kinds and exchanges on offer, the list, the keys on the\n * chart's symbol (`atCurrent`) or the top. The filters hear only of rows\n * that can be listed: comparing, a kind only the chart's own symbol is of\n * would be a tab that lists nothing.\n */\n private afterResults(atCurrent: boolean): void {\n const listable = this.listableRows();\n this.filters?.see(listable.map(filterInfo));\n this.shown = this.visibleRows(listable);\n const current = atCurrent ? this.shown.findIndex((r) => r.symbol === this.mode?.start.current) : -1;\n // The chart's symbol past the rows listed is not there to start on.\n this.selectedIndex = current >= 0 && current < this.listedCount() ? current : 0;\n this.renderList();\n }\n\n /** How many of the rows on show are listed. */\n private listedCount(): number {\n return Math.min(this.shown.length, MAX_LISTED_ROWS);\n }\n\n /** The rows that can be listed: comparing, all but the chart's own symbol. */\n private listableRows(): Row[] {\n if (this.mode?.kind !== 'compare') return this.rows;\n const current = this.mode.start.current;\n return this.rows.filter((r) => r.symbol !== current);\n }\n\n /**\n * The rows on show: of the kind and exchange picked. A symbol given that\n * says nothing of its kind or exchange stays: it may be of either.\n */\n private visibleRows(listable: readonly Row[]): Row[] {\n const filters = this.filters;\n return filters ? listable.filter((r) => filters.passes(filterInfo(r), this.rowsGiven)) : [...listable];\n }\n\n // --- Drawing ---\n\n /**\n * What is compared (comparing, before anything is typed) as a group under\n * its heading, then the symbols, a listbox of options only, under theirs;\n * a note when there are none, told to a screen reader too.\n */\n private renderList(): void {\n const list = this.list;\n const results = this.results;\n if (!list || !results) return;\n const typed = (this.input?.value ?? '').trim();\n const comparing = this.mode?.kind === 'compare';\n const before: HTMLElement[] = [];\n if (comparing && !typed && this.compared.length > 0) {\n const heading = this.heading(this.t('compare.onChart'), 'added');\n const group = document.createElement('div');\n group.className = 'tcw-ss-added';\n group.setAttribute('role', 'group');\n group.setAttribute('aria-labelledby', heading.id);\n group.append(...this.compared.map((entry) => this.addedRow(entry)));\n const symbols = this.heading(this.t('compare.symbols'), 'symbols');\n symbols.hidden = this.shown.length === 0;\n before.push(heading, group, symbols);\n list.setAttribute('aria-labelledby', symbols.id);\n } else {\n list.setAttribute('aria-labelledby', `tcw-ss-title-${this.uid}`);\n }\n const query = typed.toUpperCase();\n const onChart = new Set(this.compared.map((c) => c.symbol));\n const listed = this.shown.slice(0, MAX_LISTED_ROWS);\n list.replaceChildren(...listed.map((row, i) => this.row(row, i, query, comparing && onChart.has(row.symbol))));\n const note = this.shown.length === 0 ? this.emptyNote(typed, comparing) : this.moreNote(this.shown.length - listed.length);\n results.replaceChildren(...(this.columns ? [this.columns] : []), ...before, list, ...(note ? [note] : []));\n if (this.live) this.live.textContent = this.shown.length === 0 ? note?.textContent ?? '' : '';\n this.markSelection();\n }\n\n private emptyNote(typed: string, comparing: boolean): HTMLDivElement | null {\n let text: string;\n if (this.status === 'searching') text = this.t('symbolSearch.searching');\n else if (this.status === 'failed') text = this.t('symbolSearch.failed');\n else if (comparing && !typed) {\n if (this.compared.length > 0) return null;\n text = this.t('compare.empty');\n } else text = this.t('symbolSearch.empty');\n const note = document.createElement('div');\n note.className = 'tcw-ss-empty';\n note.textContent = text;\n return note;\n }\n\n /** Under a list cut short, how many more rows there are to narrow by typing; null when none. */\n private moreNote(count: number): HTMLDivElement | null {\n if (count <= 0) return null;\n const note = document.createElement('div');\n note.className = 'tcw-ss-more';\n note.textContent = fill(this.t('symbolSearch.more'), { count });\n return note;\n }\n\n private heading(text: string, name: string): HTMLDivElement {\n const el = document.createElement('div');\n el.className = 'tcw-ss-heading';\n el.id = `tcw-ss-${name}-${this.uid}`;\n el.textContent = text;\n return el;\n }\n\n private row(row: Row, index: number, query: string, onChart: boolean): HTMLDivElement {\n const el = document.createElement('div');\n el.className = 'tcw-ss-row';\n el.id = `tcw-ss-opt-${this.uid}-${index}`;\n el.dataset.symbol = row.symbol;\n el.setAttribute('role', 'option');\n const isCurrent = this.mode?.kind === 'search' && row.symbol === this.mode.start.current;\n el.classList.toggle('tcw-current', isCurrent);\n el.classList.toggle('tcw-on-chart', onChart);\n if (isCurrent) el.setAttribute('aria-current', 'true');\n\n // Every row has the same cells, empty where nothing was said, so the columns line up.\n const kind = this.tag('tcw-ss-kind', row.info?.type?.trim() ?? '');\n // The exchange the feed names, else the venue before the ticker, which the symbol cell leaves out.\n const exchange = this.tag('tcw-ss-exchange', exchangeOf(row));\n kind.dir = exchange.dir = 'auto';\n el.append(createSymbolBadge(row.symbol, row.info), createSymbolCell(row.symbol, query), this.descriptionCell(row), kind, exchange);\n if (isCurrent || onChart) {\n const check = document.createElement('span');\n check.className = 'tcw-ss-check';\n check.innerHTML = createIcon('check', 14);\n el.appendChild(check);\n }\n // The check says it to the eye; a screen reader hears it.\n if (onChart) el.appendChild(this.tag('tcw-visually-hidden', this.t('compare.onChart')));\n el.addEventListener('click', () => this.pick(index));\n el.addEventListener('mousemove', () => {\n if (this.selectedIndex === index) return;\n this.selectedIndex = index;\n this.markSelection();\n });\n return el;\n }\n\n private descriptionCell(row: Row): HTMLSpanElement {\n const cell = document.createElement('span');\n cell.className = 'tcw-ss-desc-cell';\n const desc = row.info?.description && row.info.description !== row.symbol ? row.info.description : '';\n if (desc) {\n const text = this.tag('tcw-ss-desc', desc);\n // A feed's words read in their own direction (\"Apple Inc.\" in a right-to-left widget).\n text.dir = 'auto';\n cell.appendChild(text);\n }\n return cell;\n }\n\n /** A symbol compared already: its colour where the badges are, its way across the rest, and a button to take it off. */\n private addedRow(entry: CompareEntry): HTMLDivElement {\n const el = document.createElement('div');\n el.className = 'tcw-ss-added-row';\n el.setAttribute('role', 'presentation');\n if (entry.color) {\n const swatch = document.createElement('span');\n swatch.className = 'tcw-ss-swatch';\n swatch.style.background = entry.color;\n el.appendChild(swatch);\n }\n const way = document.createElement('span');\n way.className = 'tcw-ss-way-cell';\n way.appendChild(this.tag('tcw-ss-way-label', entry.way));\n const remove = document.createElement('button');\n remove.type = 'button';\n remove.className = 'tcw-ss-remove';\n const label = fill(this.t('compare.remove'), { symbol: entry.symbol });\n remove.title = label;\n remove.setAttribute('aria-label', label);\n remove.innerHTML = createIcon('x', 12);\n remove.addEventListener('click', () => {\n if (this.mode?.kind === 'compare') this.mode.start.onRemove(entry.id);\n });\n el.append(createSymbolCell(entry.symbol, ''), way, remove);\n return el;\n }\n\n private tag(className: string, text: string): HTMLSpanElement {\n const span = document.createElement('span');\n span.className = className;\n span.textContent = text;\n return span;\n }\n\n private markSelection(): void {\n const rows = this.list?.querySelectorAll<HTMLElement>('.tcw-ss-row') ?? [];\n let active: HTMLElement | null = null;\n rows.forEach((el, i) => {\n const on = i === this.selectedIndex;\n el.setAttribute('aria-selected', String(on));\n el.classList.toggle('tcw-selected', on);\n if (on) active = el;\n });\n const row = active as HTMLElement | null;\n if (row) {\n this.input?.setAttribute('aria-activedescendant', row.id);\n row.scrollIntoView?.({ block: 'nearest' });\n } else {\n this.input?.removeAttribute('aria-activedescendant');\n }\n }\n\n // --- Picking ---\n\n private pick(index: number): void {\n const entry = this.shown[index];\n const mode = this.mode;\n if (!entry || !mode) return;\n if (mode.kind === 'compare') {\n this.selectedIndex = index;\n this.markSelection();\n mode.start.onAdd(entry.symbol, this.way);\n return;\n }\n const pick = mode.start.onPick ?? this.callbacks.onPick;\n this.close();\n pick(entry.symbol);\n }\n\n private onKeyDown(e: KeyboardEvent, modal: HTMLElement): void {\n e.stopPropagation(); // keys here must not drive chart shortcuts\n // Enter, Escape and the arrows pick and close among an input method's candidates while it composes.\n if (e.isComposing || e.keyCode === IME_KEY_CODE) return;\n if (e.key === 'Escape') {\n e.preventDefault();\n this.close();\n return;\n }\n keepTabInside(e, modal);\n if (e.target !== this.input) return;\n const count = this.listedCount();\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n if (count === 0) return;\n this.selectedIndex = (this.selectedIndex + (e.key === 'ArrowDown' ? 1 : -1) + count) % count;\n this.markSelection();\n } else if (e.key === 'Enter') {\n e.preventDefault();\n // Comparing, the dialog stays: a held Enter would add the symbol again and again.\n if (e.repeat && this.mode?.kind === 'compare') return;\n if (this.searchTimer !== null || this.status === 'searching') {\n // The list on screen is for an older query.\n this.pickOnResults = true;\n if (this.searchTimer !== null) this.searchNow();\n return;\n }\n this.pick(this.selectedIndex);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WidgetSymbolSearch-CxeLjy63.cjs","names":[],"sources":["../src/widget/symbolBadge.ts","../src/widget/symbolMatch.ts","../src/widget/symbolCell.ts","../src/widget/symbolSearchFilters.ts","../src/widget/WidgetSymbolSearch.ts"],"sourcesContent":["import type { SymbolInfo } from '@tradecanvas/commons';\nimport { logoSrc, splitVenue } from './symbolCatalog.js';\n\n/** How many kind colours there are (`--tcw-kind-1` … `--tcw-kind-5`); 0 is a symbol of no kind. */\nexport const KIND_SLOTS = 5;\n/** The size a logo is fetched for: the badge's in Print, the default look. */\nconst LOGO_PX = 20;\n/** How many logos that failed to load are remembered before the widget forgets them and tries again. */\nconst MAX_FAILED_LOGOS = 1000;\n/**\n * The logos that failed to load on this page. A row is built again on every\n * keystroke, and a browser keeps no image that failed, so without this each\n * one would be asked for again each time.\n */\nconst failedLogos = new Set<string>();\n\n/**\n * The usual kinds as feeds write them, in English, Vietnamese, Japanese,\n * Chinese (both scripts) and Korean, each with its colour: a stock, a \"cổ\n * phiếu\" and a 株式 look the same. A perpetual is crypto's own kind of\n * futures and takes crypto's colour, so on a phone, where the kind's column\n * goes, it still looks apart from an exchange's futures. Kinds with no colour\n * of their own take the one of the kind they are like: a warrant is an option\n * on a share, a fund or an ETF holds a basket as an index does, and a bond is\n * fixed income, kept with the currencies. Vietnamese puts the tone mark of\n * \"hoá\" on either vowel.\n */\nconst KNOWN_KINDS: Record<string, number> = {\n stock: 1, stocks: 1, equity: 1, share: 1, shares: 1, 'cổ phiếu': 1, 株式: 1, 株: 1, 股票: 1, 주식: 1,\n futures: 2, future: 2, derivative: 2, derivatives: 2, option: 2, options: 2, 'phái sinh': 2, 先物: 2, 期货: 2, 期貨: 2, 선물: 2,\n warrant: 2, warrants: 2, 'covered warrant': 2, 'chứng quyền': 2,\n index: 3, indices: 3, 'chỉ số': 3, 指数: 3, 指數: 3, 지수: 3,\n fund: 3, funds: 3, etf: 3, etfs: 3, 'chứng chỉ quỹ': 3,\n crypto: 4, cryptocurrency: 4, perpetual: 4, perpetuals: 4, perp: 4, perps: 4, 'tiền mã hóa': 4, 'tiền mã hoá': 4, 'tiền điện tử': 4,\n 暗号資産: 4, 仮想通貨: 4, 加密货币: 4, 加密貨幣: 4, 数字货币: 4, 數字貨幣: 4, 虚拟货币: 4, 虛擬貨幣: 4, 암호화폐: 4, 가상자산: 4, 가상화폐: 4,\n forex: 5, fx: 5, currency: 5, 'ngoại hối': 5, 為替: 5, 外国為替: 5, 外汇: 5, 外匯: 5, 외환: 5,\n bond: 5, bonds: 5, 'trái phiếu': 5,\n};\n\n/** How many kinds' and exchanges' words are kept keyed before the widget starts again. */\nconst MAX_KIND_KEYS = 1000;\nconst kindKeys = new Map<string, string>();\n\n/** What lower-casing leaves of Turkish İ (an i and a combining dot), and dotless ı, which I's lower case is not. */\nconst TURKISH_I = /i\\u0307|\\u0131/g;\n\n/**\n * A kind as the search keys it: its words trimmed, in lower case, with their\n * marks composed. Turkish i's are keyed as one, dotted or not, so a feed's\n * \"HİSSE\" and \"Hisse\" are the same kind; the words shown stay as written.\n */\nexport function kindKey(type: unknown): string {\n if (typeof type !== 'string') return '';\n // The same few words come on every row, keystroke after keystroke: each is keyed once.\n let key = kindKeys.get(type);\n if (key === undefined) {\n key = type.trim().normalize('NFC').toLowerCase().replace(TURKISH_I, 'i');\n if (kindKeys.size >= MAX_KIND_KEYS) kindKeys.clear();\n kindKeys.set(type, key);\n }\n return key;\n}\n\n/**\n * The colour of a kind of symbol, 1 to 5; 0 for none. The usual kinds have\n * their own; any other (a feed's word in another language) gets one from its\n * words, the same every time.\n */\nexport function kindSlot(type: unknown): number {\n const key = kindKey(type);\n if (!key) return 0;\n // Only the list's own words: a feed's \"constructor\" is a kind like any other, not the object's.\n return Object.prototype.hasOwnProperty.call(KNOWN_KINDS, key) ? KNOWN_KINDS[key] : 1 + (hashWords(key) % KIND_SLOTS);\n}\n\n/** FNV-1a over the code points: short words spread well, and it is the same in every browser. */\nfunction hashWords(text: string): number {\n let hash = 0x811c9dc5;\n for (const char of text) {\n hash ^= char.codePointAt(0)!;\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n return hash;\n}\n\n/** The first letter or figure of a ticker, after any venue before a colon (\"KBS:VCB\" gives V). */\nexport function symbolInitial(symbol: string): string {\n const { ticker } = splitVenue(symbol);\n const first = /[\\p{L}\\p{N}]/u.exec(ticker);\n return first ? first[0].toUpperCase() : '';\n}\n\n/**\n * A row's badge: the symbol's logo when it has one the widget takes, else its\n * initial in its kind's colour. A logo that fails to load (a blocked host, a\n * page's image policy) gives way to the initial, and later rows show the\n * initial for it without asking again. Either is decoration: the ticker\n * beside it says the same.\n */\nexport function createSymbolBadge(symbol: string, info: SymbolInfo | undefined): HTMLElement {\n const src = logoSrc(info?.logo);\n if (!src || failedLogos.has(src)) return letterBadge(symbol, info);\n const img = document.createElement('img');\n img.className = 'tcw-ss-badge';\n img.alt = '';\n img.width = LOGO_PX;\n img.height = LOGO_PX;\n img.draggable = false;\n img.setAttribute('aria-hidden', 'true');\n img.setAttribute('loading', 'lazy');\n img.setAttribute('decoding', 'async');\n // The logo's host learns nothing of the page it is shown on.\n img.setAttribute('referrerpolicy', 'no-referrer');\n img.addEventListener('error', () => {\n rememberFailedLogo(src);\n img.replaceWith(letterBadge(symbol, info));\n }, { once: true });\n img.src = src;\n return img;\n}\n\nfunction rememberFailedLogo(src: string): void {\n if (failedLogos.size >= MAX_FAILED_LOGOS) failedLogos.clear();\n failedLogos.add(src);\n}\n\nfunction letterBadge(symbol: string, info: SymbolInfo | undefined): HTMLSpanElement {\n const badge = document.createElement('span');\n badge.className = 'tcw-ss-badge';\n badge.setAttribute('aria-hidden', 'true');\n badge.dataset.kind = String(kindSlot(info?.type));\n badge.textContent = symbolInitial(symbol);\n return badge;\n}\n","import { escapeHtml } from './escapeHtml.js';\n\n/** A name that starts with what was typed, and one that holds it further in: below a ticker's own match. */\nconst NAME_START_SCORE = 180;\nconst NAME_HOLDS_SCORE = 150;\nconst NAME_DEPTH_PENALTY_MAX = 50;\n\n/** What was typed, as it is matched: in upper case for a ticker, without its marks for a name. */\nexport interface SymbolQuery {\n readonly upper: string;\n readonly folded: string;\n}\n\n/** What was typed, made ready once for every symbol it is matched with. */\nexport function symbolQuery(typed: string): SymbolQuery {\n return { upper: typed.toUpperCase(), folded: foldMarks(typed) };\n}\n\n/**\n * How well a given symbol matches what was typed, by its ticker or its name\n * (`name`, as `foldMarks` gives it), when there is no feed to search: a\n * ticker that starts with it ranks first, then one that holds it, then a\n * name, then the ticker's letters in order. 0 for no match.\n */\nexport function scoreSymbol(symbol: string, name: string, query: SymbolQuery): number {\n return Math.max(scoreMatch(symbol.toUpperCase(), query.upper), nameScore(name, query.folded));\n}\n\n/**\n * Cheap fuzzy score:\n * exact match → 1000\n * starts-with → 500 + tighter-prefix bonus\n * holds it → 200 + tighter bonus\n * subsequence match → 100 − gap penalty\n * no match → 0\n */\nexport function scoreMatch(symbol: string, query: string): number {\n if (symbol === query) return 1000;\n if (symbol.startsWith(query)) return 500 + (50 - Math.min(50, symbol.length - query.length));\n if (symbol.includes(query)) return 200 + (50 - Math.min(50, symbol.length - query.length));\n\n let qi = 0;\n let lastIdx = -1;\n let gapPenalty = 0;\n for (let i = 0; i < symbol.length && qi < query.length; i++) {\n if (symbol[i] === query[qi]) {\n if (lastIdx >= 0) gapPenalty += i - lastIdx - 1;\n lastIdx = i;\n qi++;\n }\n }\n if (qi < query.length) return 0;\n return 100 - Math.min(99, gapPenalty);\n}\n\n/** A name that holds what was typed, the nearer its start the better; both without their marks. */\nfunction nameScore(name: string, typed: string): number {\n if (!name || !typed) return 0;\n if (name.startsWith(typed)) return NAME_START_SCORE;\n const at = name.indexOf(typed);\n return at < 0 ? 0 : NAME_HOLDS_SCORE - Math.min(NAME_DEPTH_PENALTY_MAX, at);\n}\n\n/**\n * Words without their marks, in upper case: a Vietnamese name (\"Ngân hàng\")\n * is often typed without them (\"ngan hang\"). Đ has no mark to take off; it\n * reads as D.\n */\nexport function foldMarks(text: string): string {\n return text.normalize('NFD').replace(/\\p{M}+/gu, '').replace(/[đĐ]/g, 'D').toUpperCase();\n}\n\n/**\n * The symbol from `from` to `to` (all of it by default) as markup, with the\n * letters the query matched marked: the run of them where the symbol holds\n * the query whole, else each in order; nothing when the symbol doesn't hold\n * them all (a match on the name). The match is found in the whole symbol, so\n * a part of it is marked as it would be in the whole.\n */\nexport function highlight(symbol: string, query: string, from = 0, to = symbol.length): string {\n const marked = query ? matchedLetters(symbol.toUpperCase(), query) : new Set<number>();\n let out = '';\n for (let i = from; i < to; i++) {\n out += marked.has(i) ? `<mark>${escapeHtml(symbol[i])}</mark>` : escapeHtml(symbol[i]);\n }\n return out;\n}\n\nfunction matchedLetters(upper: string, query: string): Set<number> {\n const marked = new Set<number>();\n const run = upper.indexOf(query);\n if (run >= 0) {\n for (let i = run; i < run + query.length; i++) marked.add(i);\n return marked;\n }\n let qi = 0;\n for (let i = 0; i < upper.length && qi < query.length; i++) {\n if (upper[i] === query[qi]) {\n marked.add(i);\n qi++;\n }\n }\n return qi < query.length ? new Set() : marked;\n}\n","import { splitVenue } from './symbolCatalog.js';\nimport { highlight } from './symbolMatch.js';\n\n/**\n * A row's symbol as a trader says it: the ticker, without the venue before\n * it (\"HYPERLIQUID:xyz:GOLD\" reads GOLD), the whole symbol on hover; the\n * exchange column names the venue. The letters `query` (upper case) matched\n * are marked. It reads left to right in any widget (\"^GSPC\", \"BRK.B\").\n */\nexport function createSymbolCell(symbol: string, query: string): HTMLSpanElement {\n const cell = document.createElement('span');\n cell.className = 'tcw-ss-symbol';\n cell.dir = 'ltr';\n const { venue } = splitVenue(symbol);\n if (venue) cell.title = symbol;\n const ticker = document.createElement('span');\n ticker.className = 'tcw-ss-ticker';\n ticker.innerHTML = highlight(symbol, query, venue.length, symbol.length);\n cell.appendChild(ticker);\n return cell;\n}\n\n/** A venue's words, for the exchange column of a symbol whose feed names no exchange (\"BINANCE:\" is BINANCE). */\nexport function venueName(symbol: string): string {\n return splitVenue(symbol).venue.replace(/:$/, '').replace(/:/g, ' ');\n}\n","import type { SymbolInfo } from '@tradecanvas/commons';\nimport type { Translator } from './i18n.js';\nimport { kindKey } from './symbolBadge.js';\n\n/** Kinds and exchanges are keyed alike: their words trimmed, in lower case. */\nconst keyOf = kindKey;\n\n/** Exchanges must be at least this many before the search offers to keep to one. */\nconst MIN_EXCHANGES = 2;\n/** Tabs past an end by less than this (a rounded pixel) count as none. */\nconst MORE_SLACK_PX = 1;\n\n/**\n * The symbol search's filters: a tab for each kind of symbol and a list of\n * exchanges, for the dialog it is made for. What it offers only grows while\n * the dialog is open, from every row shown since it opened, so the tabs don't\n * come and go (and the list jump) as typing narrows the results; a kind or an\n * exchange picked stays picked. Kinds and exchanges that differ only in case\n * are one, named as first written.\n */\nexport class SymbolSearchFilters {\n /** The row holding both, hidden while there is nothing to pick. */\n readonly el: HTMLDivElement;\n private readonly kindsEl: HTMLDivElement;\n private readonly exchangeEl: HTMLSelectElement;\n private readonly kinds = new Map<string, string>();\n private readonly exchanges = new Map<string, string>();\n private kind: string | null = null;\n private exchange: string | null = null;\n /** Tells of the tabs' room changing (a phone turned), where the browser can. */\n private readonly resize: ResizeObserver | null;\n\n /**\n * `onChange` when the user picks a kind or an exchange; `refocus` says the\n * search may take the keyboard back. A tab is pressed once, on purpose; an\n * exchange list changes with each arrow pressed on it, so it keeps the\n * keyboard.\n */\n constructor(private readonly t: Translator, private readonly onChange: (refocus: boolean) => void) {\n this.el = document.createElement('div');\n this.el.className = 'tcw-ss-filters';\n this.el.hidden = true;\n this.kindsEl = document.createElement('div');\n this.kindsEl.className = 'tcw-ss-kinds';\n this.kindsEl.setAttribute('role', 'group');\n this.kindsEl.setAttribute('aria-label', t('symbolSearch.kinds'));\n this.kindsEl.hidden = true;\n this.kindsEl.addEventListener('scroll', () => this.markMore(), { passive: true });\n this.resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => this.markMore());\n this.resize?.observe(this.kindsEl);\n this.exchangeEl = document.createElement('select');\n this.exchangeEl.className = 'tcw-settings-select tcw-ss-exchange-filter';\n this.exchangeEl.setAttribute('aria-label', t('symbolSearch.exchange'));\n this.exchangeEl.hidden = true;\n this.exchangeEl.addEventListener('change', () => {\n this.exchange = keyOf(this.exchangeEl.value) || null;\n this.onChange(false);\n });\n this.el.append(this.kindsEl, this.exchangeEl);\n }\n\n /** Hear of the kinds and exchanges these rows come in, and show what there is to pick. */\n see(infos: readonly (SymbolInfo | undefined)[]): void {\n const kindsBefore = this.kinds.size;\n const exchangesBefore = this.exchanges.size;\n for (const info of infos) {\n remember(this.kinds, info?.type);\n remember(this.exchanges, info?.exchange);\n }\n if (this.exchanges.size !== exchangesBefore) this.renderExchanges();\n this.exchangeEl.hidden = this.exchanges.size < MIN_EXCHANGES;\n // An exchange filter shows beside the kind tabs, \"All\" at the least: alone, it stood at the end of\n // an empty row.\n if (this.kinds.size !== kindsBefore || (!this.exchangeEl.hidden && this.kindsEl.childElementCount === 0)) this.renderKinds();\n this.kindsEl.hidden = this.kinds.size === 0 && this.exchangeEl.hidden;\n this.el.hidden = this.kindsEl.hidden && this.exchangeEl.hidden;\n this.markMore();\n }\n\n /** Stop hearing of the tabs' room: the dialog has gone. */\n destroy(): void {\n this.resize?.disconnect();\n }\n\n /**\n * Where the tabs scroll in one line (a phone), say at which ends there are\n * more past the edge, `data-more` \"start\", \"end\" or both, for the fade there:\n * a tab cut at the edge then reads as more to scroll to. Right to left, the\n * row scrolls the other way, and its scrollLeft runs below zero.\n */\n private markMore(): void {\n const el = this.kindsEl;\n const past = Math.abs(el.scrollLeft);\n const room = el.scrollWidth - el.clientWidth;\n const ends = [past > MORE_SLACK_PX ? 'start' : '', past < room - MORE_SLACK_PX ? 'end' : ''].filter(Boolean);\n if (ends.length > 0) el.dataset.more = ends.join(' ');\n else delete el.dataset.more;\n }\n\n /**\n * Whether a symbol is of the kind and on the exchange picked. `lenient`\n * (the symbols given, not a feed's results) lets one through that says\n * nothing of its kind or exchange: it may be either, and hiding it would\n * hide a symbol the host listed.\n */\n passes(info: SymbolInfo | undefined, lenient: boolean): boolean {\n return matches(this.kind, keyOf(info?.type), lenient) && matches(this.exchange, keyOf(info?.exchange), lenient);\n }\n\n private renderKinds(): void {\n const chip = (value: string | null, text: string) => {\n const b = document.createElement('button');\n b.type = 'button';\n b.className = 'tcw-ss-type';\n b.textContent = text;\n // A feed's kind reads in its own direction (\"Stock.\" in a right-to-left widget); \"All\" is the widget's.\n if (value !== null) b.dir = 'auto';\n b.setAttribute('aria-pressed', String(this.kind === value));\n b.addEventListener('click', () => {\n this.kind = value;\n for (const other of this.kindsEl.children) other.setAttribute('aria-pressed', String(other === b));\n this.onChange(true);\n });\n return b;\n };\n const chips = [...this.kinds].map(([key, label]) => chip(key, label));\n this.kindsEl.replaceChildren(chip(null, this.t('symbolSearch.allKinds')), ...chips);\n }\n\n private renderExchanges(): void {\n const option = (value: string, text: string) => {\n const o = document.createElement('option');\n o.value = value;\n o.textContent = text;\n if (value) o.dir = 'auto';\n return o;\n };\n const options = [...this.exchanges.values()].map((label) => option(label, label));\n this.exchangeEl.replaceChildren(option('', this.t('symbolSearch.allExchanges')), ...options);\n this.exchangeEl.value = this.exchange === null ? '' : this.exchanges.get(this.exchange) ?? '';\n }\n}\n\nfunction remember(seen: Map<string, string>, words: string | undefined): void {\n const key = keyOf(words);\n if (key && !seen.has(key)) seen.set(key, words!.trim());\n}\n\nfunction matches(picked: string | null, key: string, lenient: boolean): boolean {\n return picked === null || key === picked || (lenient && key === '');\n}\n","import type { SymbolInfo } from '@tradecanvas/commons';\nimport { EN_TRANSLATOR, fill, type Translator } from './i18n.js';\nimport { createIcon } from './icons.js';\nimport { keepTabInside } from './focusTrap.js';\nimport { markPart } from './widgetFeatures.js';\nimport { cleanSymbolInfo, mergeSymbolInfo, readSymbolCatalog, readSymbolResults } from './symbolCatalog.js';\nimport { createSymbolBadge } from './symbolBadge.js';\nimport { createSymbolCell, venueName } from './symbolCell.js';\nimport { foldMarks, scoreSymbol, symbolQuery } from './symbolMatch.js';\nimport { SymbolSearchFilters } from './symbolSearchFilters.js';\n\nexport interface SymbolSearchCallbacks {\n onPick: (symbol: string) => void;\n onClose: () => void;\n}\n\n/** Symbols matching a query, best first; aborted when a newer query replaces it. */\nexport type SymbolSearchFn = (query: string, signal: AbortSignal) => Promise<SymbolInfo[]>;\n\n/** How long typing must pause before a search function is asked. */\nexport const SYMBOL_SEARCH_DEBOUNCE_MS = 150;\n\n/** The key code a browser gives a key an input method takes. */\nconst IME_KEY_CODE = 229;\n\n/**\n * The most rows the list draws, the best first; a line under them says how\n * many more typing would narrow. Every row is laid out in the columns all\n * the others share, so a list of thousands would be laid out again on each\n * keystroke.\n */\nexport const MAX_LISTED_ROWS = 200;\n\n/** A symbol on offer: its ticker, or what is known of it (a name, kind, exchange, logo). */\nexport type SymbolEntry = string | SymbolInfo;\n\n/** What the symbol search opens with. */\nexport interface SymbolSearchStart {\n /** Listed until something is typed; filtered, by ticker and name, when there is no `search`. */\n symbols: readonly SymbolEntry[];\n /** The chart's symbol, marked in the list. */\n current: string;\n /** What the chart knows of `current`: its name and logo, where the symbols given and the feed have none. */\n currentInfo?: SymbolInfo | null;\n /** A feed's search, asked once typing pauses. */\n search?: SymbolSearchFn;\n /** Typed already: the letter that opened it. */\n query?: string;\n /** Where a pick goes, in place of `onPick`. */\n onPick?: (symbol: string) => void;\n /** A line at the foot. */\n hint?: string;\n}\n\n/** Something compared with the chart's symbol, as the compare dialog lists it. */\nexport interface CompareEntry {\n id: string;\n symbol: string;\n /** How it is compared, in words. */\n way: string;\n color?: string;\n}\n\n/** What the compare dialog opens with. */\nexport interface CompareStart {\n symbols: readonly SymbolEntry[];\n current: string;\n search?: SymbolSearchFn;\n /** The ways to add a symbol, and the one chosen to start with. */\n ways: readonly { id: string; label: string }[];\n way: string;\n compared: readonly CompareEntry[];\n onAdd: (symbol: string, way: string) => void;\n onRemove: (id: string) => void;\n}\n\ninterface Row {\n symbol: string;\n score: number;\n info?: SymbolInfo;\n}\n\ntype Mode = { kind: 'search'; start: SymbolSearchStart } | { kind: 'compare'; start: CompareStart };\n\nlet nextSearchId = 1;\n\n/** A row's exchange: the one its feed names, else the venue before its ticker (\"BINANCE:\" is BINANCE). */\nfunction exchangeOf(row: Row): string {\n return row.info?.exchange?.trim() || venueName(row.symbol);\n}\n\n/** What the kind and exchange filters read of a row: its exchange as its column shows it. */\nfunction filterInfo(row: Row): SymbolInfo | undefined {\n const exchange = exchangeOf(row);\n return exchange ? { ...(row.info ?? { symbol: row.symbol }), exchange } : row.info;\n}\n\n/**\n * The symbol search, and the same dialog for adding a symbol to compare.\n *\n * - The symbols it is given are listed until something is typed, named\n * where they came with a SymbolInfo (or the chart knows its own).\n * - Without a search function it filters them by ticker and name with a\n * cheap match, good enough for lists in the low thousands. The list draws\n * the best `MAX_LISTED_ROWS` and says how many more typing would narrow.\n * - With one (a feed's `searchSymbols`) it asks once typing pauses, cancels a\n * query a newer one replaced, and lists names, kinds and exchanges; what it\n * hears is kept for the list it opens with next time.\n * - Whose words a row shows is one rule, wherever it is listed: the feed's,\n * then the ones given for what it left out (a logo, say), then the name\n * and logo the chart knows of its own symbol. Words that aren't text (a\n * JSON feed's numeric code, a null) say nothing.\n * - The rows can be narrowed to a kind (stocks, crypto) and, when they come\n * from two or more, to an exchange.\n * - Comparing, a pick adds the symbol the way chosen above the list and the\n * dialog stays; what is compared already is listed first, each removable.\n *\n * The keys stay in the search field: up and down move through the list, Enter picks.\n */\nexport class WidgetSymbolSearch {\n private backdrop: HTMLDivElement | null = null;\n private input: HTMLInputElement | null = null;\n private list: HTMLDivElement | null = null;\n /** What scrolls: what is compared, the list of symbols, a note when there are none. */\n private results: HTMLDivElement | null = null;\n /** Says \"Searching…\", \"Search failed\", \"No symbols match\" to a screen reader. */\n private live: HTMLDivElement | null = null;\n /** The heads of the columns, at the top of what scrolls. */\n private columns: HTMLDivElement | null = null;\n private filters: SymbolSearchFilters | null = null;\n private mode: Mode | null = null;\n /** The tickers given to it, cleaned. */\n private given: string[] = [];\n private rows: Row[] = [];\n /** Whether the rows are the symbols given (filtered or not), not a feed's results. */\n private rowsGiven = false;\n private shown: Row[] = [];\n private compared: readonly CompareEntry[] = [];\n private way = '';\n private selectedIndex = 0;\n private searchFn: SymbolSearchFn | null = null;\n private searchTimer: ReturnType<typeof setTimeout> | null = null;\n private searchAbort: AbortController | null = null;\n private status: 'idle' | 'searching' | 'failed' = 'idle';\n /** Enter came before the results for what was typed: take the top one when they land. */\n private pickOnResults = false;\n /** What the feed's searches told of each symbol, kept from one opening to the next: its words win. */\n private readonly heard = new Map<string, SymbolInfo>();\n /** What came with the symbols it opened with, over what the chart knows of its own. */\n private givenInfo = new Map<string, SymbolInfo>();\n /** Each name without its marks, made once while open: a name is matched on every keystroke. */\n private foldedNames = new Map<string, string>();\n private returnFocus: HTMLElement | null = null;\n private readonly uid = nextSearchId++;\n\n /** Where the dialog mounts (the widget's overlay layer: themed, and inside it when fullscreen). */\n constructor(\n private readonly callbacks: SymbolSearchCallbacks,\n private readonly host: () => HTMLElement = () => document.body,\n private readonly t: Translator = EN_TRANSLATOR,\n ) {}\n\n open(start: SymbolSearchStart): void {\n this.begin({ kind: 'search', start });\n }\n\n openCompare(start: CompareStart): void {\n this.compared = start.compared;\n this.way = start.ways.some((w) => w.id === start.way) ? start.way : start.ways[0]?.id ?? '';\n this.begin({ kind: 'compare', start });\n }\n\n /** What is compared now, after an add or a remove. */\n setCompared(entries: readonly CompareEntry[]): void {\n this.compared = entries;\n if (this.mode?.kind !== 'compare' || !this.results) return;\n // A remove button that had the keyboard is gone with its row: the search takes it.\n const hadFocus = this.results.contains(document.activeElement);\n this.renderList();\n if (hadFocus && !this.results.contains(document.activeElement)) this.input?.focus();\n }\n\n close(): void {\n if (!this.backdrop) return;\n this.cancelSearch();\n this.pickOnResults = false;\n this.searchFn = null;\n this.backdrop.remove();\n this.backdrop = this.input = this.list = this.results = this.live = this.columns = null;\n this.filters?.destroy();\n this.filters = null;\n this.mode = null;\n this.callbacks.onClose();\n const back = this.returnFocus;\n this.returnFocus = null;\n if (back?.isConnected) back.focus();\n }\n\n isOpen(): boolean {\n return this.backdrop !== null;\n }\n\n /** Whether it is open to add a symbol to compare. */\n isComparing(): boolean {\n return this.mode?.kind === 'compare';\n }\n\n destroy(): void {\n this.close();\n }\n\n private begin(mode: Mode): void {\n if (this.backdrop) this.close();\n this.returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n this.mode = mode;\n this.searchFn = mode.start.search ?? null;\n this.status = 'idle';\n this.learnGiven(mode);\n this.build(mode);\n const query = mode.kind === 'search' ? mode.start.query ?? '' : '';\n if (this.input) this.input.value = query;\n if (query) {\n this.filter();\n if (this.searchFn) this.searchNow();\n } else {\n this.listGiven();\n }\n this.input?.focus();\n }\n\n /**\n * The tickers given, and what came with them. What the chart knows of its\n * own symbol gives only a name and a logo, where none came with it: its\n * kind and exchange may be in other words (a kind in English where the\n * list's are translated, a broker for the exchange), and would add a tab or\n * an exchange that no row is in once the feed has named the symbol.\n */\n private learnGiven(mode: Mode): void {\n const catalog = readSymbolCatalog(mode.start.symbols);\n this.given = catalog.names;\n this.givenInfo = catalog.info;\n this.foldedNames = new Map();\n const current = mode.start.current;\n const own = mode.kind === 'search' ? mode.start.currentInfo : null;\n if (own && typeof own === 'object' && current) {\n const { description, logo } = cleanSymbolInfo(own, current);\n this.givenInfo.set(current, mergeSymbolInfo({ symbol: current, description, logo }, this.givenInfo.get(current))!);\n }\n }\n\n /** What a row of `symbol` shows: the feed's words, the ones given for the rest. */\n private infoOf(symbol: string): SymbolInfo | undefined {\n return mergeSymbolInfo(this.givenInfo.get(symbol), this.heard.get(symbol));\n }\n\n // --- Building ---\n\n private build(mode: Mode): void {\n const backdrop = document.createElement('div');\n backdrop.className = 'tcw-modal-backdrop';\n // A press that starts in the dialog and ends on the backdrop doesn't close it.\n let pressedBackdrop = false;\n backdrop.addEventListener('pointerdown', (e) => {\n pressedBackdrop = e.target === backdrop;\n });\n backdrop.addEventListener('click', (e) => {\n if (e.target === backdrop && pressedBackdrop) this.close();\n pressedBackdrop = false;\n });\n\n const modal = document.createElement('div');\n modal.className = `tcw-modal tcw-ss${mode.kind === 'compare' ? ' tcw-ss--compare' : ''}`;\n markPart(modal, mode.kind === 'compare' ? 'compare' : 'symbolSearch');\n modal.setAttribute('role', 'dialog');\n modal.setAttribute('aria-modal', 'true');\n modal.setAttribute('aria-labelledby', `tcw-ss-title-${this.uid}`);\n modal.tabIndex = -1;\n modal.addEventListener('keydown', (e) => this.onKeyDown(e, modal));\n\n modal.append(this.buildHeader(mode), this.buildSearch());\n if (mode.kind === 'compare') modal.appendChild(this.buildWay(mode.start));\n\n const filters = new SymbolSearchFilters(this.t, (refocus) => {\n this.afterResults(false);\n if (refocus) this.input?.focus();\n });\n\n const list = document.createElement('div');\n list.className = 'tcw-ss-list';\n list.id = `tcw-ss-list-${this.uid}`;\n list.setAttribute('role', 'listbox');\n const results = document.createElement('div');\n results.className = 'tcw-ss-results';\n const columns = this.buildColumns();\n results.append(columns, list);\n // A press on a heading or between the rows keeps the keyboard in the search (a remove button takes it).\n results.addEventListener('mousedown', (e) => {\n if (!(e.target as Element).closest('button')) e.preventDefault();\n });\n const live = document.createElement('div');\n live.className = 'tcw-ss-live tcw-visually-hidden';\n live.setAttribute('aria-live', 'polite');\n\n modal.append(filters.el, results, live);\n const hint = mode.kind === 'search' ? mode.start.hint : undefined;\n if (hint) {\n const foot = document.createElement('div');\n foot.className = 'tcw-ss-foot';\n foot.textContent = hint;\n modal.appendChild(foot);\n }\n backdrop.appendChild(modal);\n this.host().appendChild(backdrop);\n this.backdrop = backdrop;\n this.filters = filters;\n this.columns = columns;\n this.list = list;\n this.results = results;\n this.live = live;\n }\n\n /** The heads of the columns: none over the badges, then symbol, description, type, exchange. */\n private buildColumns(): HTMLDivElement {\n const columns = document.createElement('div');\n columns.className = 'tcw-ss-columns';\n columns.setAttribute('aria-hidden', 'true');\n const heads = ['', this.t('symbolSearch.symbol'), this.t('symbolSearch.description'), this.t('symbolSearch.type'), this.t('symbolSearch.exchange')];\n columns.append(...heads.map((text) => this.tag('tcw-ss-head', text)));\n return columns;\n }\n\n private buildHeader(mode: Mode): HTMLDivElement {\n const header = document.createElement('div');\n header.className = 'tcw-modal-header';\n const title = document.createElement('h3');\n title.id = `tcw-ss-title-${this.uid}`;\n title.textContent = this.t(mode.kind === 'compare' ? 'compare.title' : 'symbolSearch.title');\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.className = 'tcw-modal-close';\n closeBtn.setAttribute('aria-label', this.t('common.close'));\n closeBtn.innerHTML = createIcon('x', 16);\n closeBtn.addEventListener('click', () => this.close());\n header.append(title, closeBtn);\n return header;\n }\n\n private buildSearch(): HTMLLabelElement {\n const row = document.createElement('label');\n row.className = 'tcw-ss-search';\n row.innerHTML = createIcon('search', 16);\n const input = document.createElement('input');\n input.type = 'search';\n input.className = 'tcw-ss-input';\n input.placeholder = this.t('symbolSearch.placeholder');\n input.setAttribute('aria-label', this.t('symbolSearch.placeholder'));\n input.setAttribute('role', 'combobox');\n input.setAttribute('aria-expanded', 'true');\n input.setAttribute('aria-controls', `tcw-ss-list-${this.uid}`);\n input.setAttribute('aria-autocomplete', 'list');\n input.autocomplete = 'off';\n input.spellcheck = false;\n input.addEventListener('input', () => this.filter());\n row.appendChild(input);\n this.input = input;\n return row;\n }\n\n /** Comparing: how the next symbol is added. */\n private buildWay(start: CompareStart): HTMLLabelElement {\n const row = document.createElement('label');\n row.className = 'tcw-ss-way';\n const text = document.createElement('span');\n text.textContent = this.t('compare.addAs');\n const select = document.createElement('select');\n select.className = 'tcw-settings-select tcw-ss-way-select';\n for (const way of start.ways) {\n const option = document.createElement('option');\n option.value = way.id;\n option.textContent = way.label;\n select.appendChild(option);\n }\n select.value = this.way;\n select.addEventListener('change', () => {\n this.way = select.value;\n });\n row.append(text, select);\n return row;\n }\n\n // --- Searching ---\n\n /** The symbols it was given, named where they came with a name or a search told of one. */\n private listGiven(): void {\n this.rows = this.given.map((symbol) => ({ symbol, score: 0, info: this.infoOf(symbol) }));\n this.rowsGiven = true;\n this.afterResults(true);\n }\n\n private filter(): void {\n this.pickOnResults = false;\n const typed = this.input?.value.trim() ?? '';\n if (this.searchFn && typed) {\n this.scheduleSearch(typed);\n return;\n }\n this.cancelSearch();\n this.status = 'idle';\n if (!typed) {\n this.listGiven();\n return;\n }\n const query = symbolQuery(typed);\n this.rows = this.given\n .map((symbol) => {\n const info = this.infoOf(symbol);\n return { symbol, score: scoreSymbol(symbol, this.foldedName(info?.description), query), info };\n })\n .filter((r) => r.score > 0)\n .sort((a, b) => b.score - a.score);\n this.rowsGiven = true;\n this.afterResults(false);\n }\n\n /** A name without its marks, as matching takes it; made once for each name while open. */\n private foldedName(name: string | undefined): string {\n if (!name) return '';\n let folded = this.foldedNames.get(name);\n if (folded === undefined) {\n folded = foldMarks(name);\n this.foldedNames.set(name, folded);\n }\n return folded;\n }\n\n private scheduleSearch(query: string): void {\n if (this.searchTimer !== null) clearTimeout(this.searchTimer);\n this.searchTimer = setTimeout(() => {\n this.searchTimer = null;\n void this.runSearch(query);\n }, SYMBOL_SEARCH_DEBOUNCE_MS);\n }\n\n /** Run the search the pause would have started, now. */\n private searchNow(): void {\n if (this.searchTimer !== null) clearTimeout(this.searchTimer);\n this.searchTimer = null;\n void this.runSearch(this.input?.value.trim() ?? '');\n }\n\n private async runSearch(query: string): Promise<void> {\n const search = this.searchFn;\n if (!search) return;\n this.searchAbort?.abort();\n const controller = new AbortController();\n this.searchAbort = controller;\n // The rows on screen stay as they are while this one runs: no flicker as typing goes on, and no\n // long list drawn again. \"Searching…\" shows only where there are none.\n this.status = 'searching';\n if (this.shown.length === 0) this.renderList();\n try {\n const found = await search(query, controller.signal);\n if (controller.signal.aborted) return;\n const results = readSymbolResults(found);\n // What a later search leaves out, an earlier one may have told of.\n for (const info of results) this.heard.set(info.symbol, mergeSymbolInfo(this.heard.get(info.symbol), info)!);\n this.rows = results.map((info) => ({ symbol: info.symbol, score: 0, info: this.infoOf(info.symbol) }));\n this.status = 'idle';\n } catch {\n if (controller.signal.aborted) return;\n this.rows = [];\n this.status = 'failed';\n }\n this.rowsGiven = false;\n this.afterResults(false);\n if (this.pickOnResults) {\n this.pickOnResults = false;\n this.pick(0);\n }\n }\n\n private cancelSearch(): void {\n if (this.searchTimer !== null) clearTimeout(this.searchTimer);\n this.searchTimer = null;\n this.searchAbort?.abort();\n this.searchAbort = null;\n }\n\n /**\n * New rows: the kinds and exchanges on offer, the list, the keys on the\n * chart's symbol (`atCurrent`) or the top. The filters hear only of rows\n * that can be listed: comparing, a kind only the chart's own symbol is of\n * would be a tab that lists nothing.\n */\n private afterResults(atCurrent: boolean): void {\n const listable = this.listableRows();\n this.filters?.see(listable.map(filterInfo));\n this.shown = this.visibleRows(listable);\n const current = atCurrent ? this.shown.findIndex((r) => r.symbol === this.mode?.start.current) : -1;\n // The chart's symbol past the rows listed is not there to start on.\n this.selectedIndex = current >= 0 && current < this.listedCount() ? current : 0;\n this.renderList();\n }\n\n /** How many of the rows on show are listed. */\n private listedCount(): number {\n return Math.min(this.shown.length, MAX_LISTED_ROWS);\n }\n\n /** The rows that can be listed: comparing, all but the chart's own symbol. */\n private listableRows(): Row[] {\n if (this.mode?.kind !== 'compare') return this.rows;\n const current = this.mode.start.current;\n return this.rows.filter((r) => r.symbol !== current);\n }\n\n /**\n * The rows on show: of the kind and exchange picked. A symbol given that\n * says nothing of its kind or exchange stays: it may be of either.\n */\n private visibleRows(listable: readonly Row[]): Row[] {\n const filters = this.filters;\n return filters ? listable.filter((r) => filters.passes(filterInfo(r), this.rowsGiven)) : [...listable];\n }\n\n // --- Drawing ---\n\n /**\n * What is compared (comparing, before anything is typed) as a group under\n * its heading, then the symbols, a listbox of options only, under theirs;\n * a note when there are none, told to a screen reader too.\n */\n private renderList(): void {\n const list = this.list;\n const results = this.results;\n if (!list || !results) return;\n const typed = (this.input?.value ?? '').trim();\n const comparing = this.mode?.kind === 'compare';\n const before: HTMLElement[] = [];\n if (comparing && !typed && this.compared.length > 0) {\n const heading = this.heading(this.t('compare.onChart'), 'added');\n const group = document.createElement('div');\n group.className = 'tcw-ss-added';\n group.setAttribute('role', 'group');\n group.setAttribute('aria-labelledby', heading.id);\n group.append(...this.compared.map((entry) => this.addedRow(entry)));\n const symbols = this.heading(this.t('compare.symbols'), 'symbols');\n symbols.hidden = this.shown.length === 0;\n before.push(heading, group, symbols);\n list.setAttribute('aria-labelledby', symbols.id);\n } else {\n list.setAttribute('aria-labelledby', `tcw-ss-title-${this.uid}`);\n }\n const query = typed.toUpperCase();\n const onChart = new Set(this.compared.map((c) => c.symbol));\n const listed = this.shown.slice(0, MAX_LISTED_ROWS);\n list.replaceChildren(...listed.map((row, i) => this.row(row, i, query, comparing && onChart.has(row.symbol))));\n const note = this.shown.length === 0 ? this.emptyNote(typed, comparing) : this.moreNote(this.shown.length - listed.length);\n results.replaceChildren(...(this.columns ? [this.columns] : []), ...before, list, ...(note ? [note] : []));\n if (this.live) this.live.textContent = this.shown.length === 0 ? note?.textContent ?? '' : '';\n this.markSelection();\n }\n\n private emptyNote(typed: string, comparing: boolean): HTMLDivElement | null {\n let text: string;\n if (this.status === 'searching') text = this.t('symbolSearch.searching');\n else if (this.status === 'failed') text = this.t('symbolSearch.failed');\n else if (comparing && !typed) {\n if (this.compared.length > 0) return null;\n text = this.t('compare.empty');\n } else text = this.t('symbolSearch.empty');\n const note = document.createElement('div');\n note.className = 'tcw-ss-empty';\n note.textContent = text;\n return note;\n }\n\n /** Under a list cut short, how many more rows there are to narrow by typing; null when none. */\n private moreNote(count: number): HTMLDivElement | null {\n if (count <= 0) return null;\n const note = document.createElement('div');\n note.className = 'tcw-ss-more';\n note.textContent = fill(this.t('symbolSearch.more'), { count });\n return note;\n }\n\n private heading(text: string, name: string): HTMLDivElement {\n const el = document.createElement('div');\n el.className = 'tcw-ss-heading';\n el.id = `tcw-ss-${name}-${this.uid}`;\n el.textContent = text;\n return el;\n }\n\n private row(row: Row, index: number, query: string, onChart: boolean): HTMLDivElement {\n const el = document.createElement('div');\n el.className = 'tcw-ss-row';\n el.id = `tcw-ss-opt-${this.uid}-${index}`;\n el.dataset.symbol = row.symbol;\n el.setAttribute('role', 'option');\n const isCurrent = this.mode?.kind === 'search' && row.symbol === this.mode.start.current;\n el.classList.toggle('tcw-current', isCurrent);\n el.classList.toggle('tcw-on-chart', onChart);\n if (isCurrent) el.setAttribute('aria-current', 'true');\n\n // Every row has the same cells, empty where nothing was said, so the columns line up.\n const kind = this.tag('tcw-ss-kind', row.info?.type?.trim() ?? '');\n // The exchange the feed names, else the venue before the ticker, which the symbol cell leaves out.\n const exchange = this.tag('tcw-ss-exchange', exchangeOf(row));\n kind.dir = exchange.dir = 'auto';\n el.append(createSymbolBadge(row.symbol, row.info), createSymbolCell(row.symbol, query), this.descriptionCell(row), kind, exchange);\n if (isCurrent || onChart) {\n const check = document.createElement('span');\n check.className = 'tcw-ss-check';\n check.innerHTML = createIcon('check', 14);\n el.appendChild(check);\n }\n // The check says it to the eye; a screen reader hears it.\n if (onChart) el.appendChild(this.tag('tcw-visually-hidden', this.t('compare.onChart')));\n el.addEventListener('click', () => this.pick(index));\n el.addEventListener('mousemove', () => {\n if (this.selectedIndex === index) return;\n this.selectedIndex = index;\n this.markSelection();\n });\n return el;\n }\n\n private descriptionCell(row: Row): HTMLSpanElement {\n const cell = document.createElement('span');\n cell.className = 'tcw-ss-desc-cell';\n const desc = row.info?.description && row.info.description !== row.symbol ? row.info.description : '';\n if (desc) {\n const text = this.tag('tcw-ss-desc', desc);\n // A feed's words read in their own direction (\"Apple Inc.\" in a right-to-left widget).\n text.dir = 'auto';\n cell.appendChild(text);\n }\n return cell;\n }\n\n /** A symbol compared already: its colour where the badges are, its way across the rest, and a button to take it off. */\n private addedRow(entry: CompareEntry): HTMLDivElement {\n const el = document.createElement('div');\n el.className = 'tcw-ss-added-row';\n el.setAttribute('role', 'presentation');\n if (entry.color) {\n const swatch = document.createElement('span');\n swatch.className = 'tcw-ss-swatch';\n swatch.style.background = entry.color;\n el.appendChild(swatch);\n }\n const way = document.createElement('span');\n way.className = 'tcw-ss-way-cell';\n way.appendChild(this.tag('tcw-ss-way-label', entry.way));\n const remove = document.createElement('button');\n remove.type = 'button';\n remove.className = 'tcw-ss-remove';\n const label = fill(this.t('compare.remove'), { symbol: entry.symbol });\n remove.title = label;\n remove.setAttribute('aria-label', label);\n remove.innerHTML = createIcon('x', 12);\n remove.addEventListener('click', () => {\n if (this.mode?.kind === 'compare') this.mode.start.onRemove(entry.id);\n });\n el.append(createSymbolCell(entry.symbol, ''), way, remove);\n return el;\n }\n\n private tag(className: string, text: string): HTMLSpanElement {\n const span = document.createElement('span');\n span.className = className;\n span.textContent = text;\n return span;\n }\n\n private markSelection(): void {\n const rows = this.list?.querySelectorAll<HTMLElement>('.tcw-ss-row') ?? [];\n let active: HTMLElement | null = null;\n rows.forEach((el, i) => {\n const on = i === this.selectedIndex;\n el.setAttribute('aria-selected', String(on));\n el.classList.toggle('tcw-selected', on);\n if (on) active = el;\n });\n const row = active as HTMLElement | null;\n if (row) {\n this.input?.setAttribute('aria-activedescendant', row.id);\n row.scrollIntoView?.({ block: 'nearest' });\n } else {\n this.input?.removeAttribute('aria-activedescendant');\n }\n }\n\n // --- Picking ---\n\n private pick(index: number): void {\n const entry = this.shown[index];\n const mode = this.mode;\n if (!entry || !mode) return;\n if (mode.kind === 'compare') {\n this.selectedIndex = index;\n this.markSelection();\n mode.start.onAdd(entry.symbol, this.way);\n return;\n }\n const pick = mode.start.onPick ?? this.callbacks.onPick;\n this.close();\n pick(entry.symbol);\n }\n\n private onKeyDown(e: KeyboardEvent, modal: HTMLElement): void {\n e.stopPropagation(); // keys here must not drive chart shortcuts\n // Enter, Escape and the arrows pick and close among an input method's candidates while it composes.\n if (e.isComposing || e.keyCode === IME_KEY_CODE) return;\n if (e.key === 'Escape') {\n e.preventDefault();\n this.close();\n return;\n }\n keepTabInside(e, modal);\n if (e.target !== this.input) return;\n const count = this.listedCount();\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n if (count === 0) return;\n this.selectedIndex = (this.selectedIndex + (e.key === 'ArrowDown' ? 1 : -1) + count) % count;\n this.markSelection();\n } else if (e.key === 'Enter') {\n e.preventDefault();\n // Comparing, the dialog stays: a held Enter would add the symbol again and again.\n if (e.repeat && this.mode?.kind === 'compare') return;\n if (this.searchTimer !== null || this.status === 'searching') {\n // The list on screen is for an older query.\n this.pickOnResults = true;\n if (this.searchTimer !== null) this.searchNow();\n return;\n }\n this.pick(this.selectedIndex);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e={"toolbar.indicators":`Indicators`,"toolbar.more":`More`,"toolbar.indicators.all":`All`,"indicatorType.overlay":`overlay`,"indicatorType.panel":`panel`,"indicatorPicker.search":`Search`,"indicatorPicker.groups":`Groups`,"indicatorPicker.favorites":`Favourites`,"indicatorPicker.favorite":`Favourite`,"indicatorPicker.noFavorites":`Star an indicator to keep it here.`,"indicatorPicker.noMatch":`No indicator matches “{query}”.`,"indicatorPicker.noTemplates":`Save the indicators on the chart as a template to add them all at once.`,"indicatorPicker.added":`{name} added`,"indicatorPicker.onChart":`On the chart: {count}`,"indicatorCategory.movingAverages":`Moving averages`,"indicatorCategory.trend":`Trend`,"indicatorCategory.oscillators":`Oscillators`,"indicatorCategory.volatility":`Volatility`,"indicatorCategory.volume":`Volume`,"indicatorCategory.bands":`Bands & channels`,"indicatorCategory.patterns":`Price & patterns`,"indicatorCategory.other":`Other`,"toolbar.replay":`Bar replay`,"replay.selectHint":`Click a bar to start the replay`,"replay.random":`Random bar`,"replay.realtime":`Back to realtime`,"replay.play":`Play`,"replay.pause":`Pause`,"replay.stepBack":`Step back (Shift+←)`,"replay.stepForward":`Step forward (Shift+→)`,"toolbar.longBracket":`Long bracket`,"toolbar.shortBracket":`Short bracket`,"toolbar.depthLadder":`Depth ladder`,"toolbar.objects":`Objects`,"toolbar.priceAlerts":`Price alerts`,"toolbar.screenshot":`Screenshot`,"toolbar.settings":`Chart Settings`,"toolbar.toggleTheme":`Toggle theme`,"toolbar.timeframes":`Timeframes`,"toolbar.timeframes.pin":`Pin to toolbar`,"toolbar.timeframes.custom":`Custom interval, e.g. 7m, 90m, 2h, 3D`,"toolbar.timeframes.add":`Add interval`,"toolbar.timeframes.remove":`Remove interval`,"toolbar.timeframes.invalid":`Type a number and a unit: 7m, 2h, 3D, 1W, 2M`,"toolbar.timeframes.ticks":`Ticks`,"toolbar.timeframes.seconds":`Seconds`,"toolbar.timeframes.minutes":`Minutes`,"toolbar.timeframes.hours":`Hours`,"toolbar.timeframes.days":`Days`,"toolbar.timeframes.weeks":`Weeks`,"toolbar.timeframes.months":`Months`,"toolbar.trade":`Trade`,"statusBar.label":`Status bar`,"statusBar.log":`log`,"statusBar.auto":`auto`,"statusBar.marketOpen":`Open`,"statusBar.marketClosed":`Closed`,"toolbar.fullscreen":`Fullscreen`,"legend.show":`Show`,"legend.hide":`Hide`,"legend.settings":`Settings`,"legend.remove":`Remove`,"legend.collapse":`Collapse indicators`,"legend.expand":`Show indicators`,"legend.offChartType":`Not drawn on this chart type`,"indicatorSettings.title":`{name} settings`,"indicatorSettings.tab.inputs":`Inputs`,"indicatorSettings.tab.style":`Style`,"indicatorSettings.tab.levels":`Levels`,"indicatorSettings.tab.visibility":`Visibility`,"indicatorSettings.defaults":`Defaults`,"indicatorSettings.resetSettings":`Reset settings`,"indicatorSettings.saveAsDefault":`Save as default`,"indicatorSettings.defaultSaved":`New {name} indicators start like this now.`,"indicatorSettings.defaultSaveFailed":`Couldn't save the default`,"indicatorSettings.rangeFrom":`from`,"indicatorSettings.rangeTo":`to`,"indicatorSettings.offInterval":`Not shown on this interval`,"indicatorSettings.noInputs":`This indicator has no adjustable parameters.`,"indicatorInput.anchor":`Anchor`,"indicatorInput.anchor.session":`Session`,"indicatorInput.anchor.week":`Week`,"indicatorInput.anchor.month":`Month`,"indicatorInput.anchor.all":`All bars`,"indicatorSettings.source.prices":`Price`,"indicatorSettings.source.indicators":`Indicators`,"indicatorSettings.lineWidth":`Line width`,"indicatorSettings.scale":`Price scale`,"indicatorSettings.scale.right":`Right (price)`,"indicatorSettings.scale.left":`Left (own range)`,"indicatorSettings.up":`up`,"indicatorSettings.down":`down`,"indicatorSettings.addLevel":`Add level`,"indicatorSettings.removeLevel":`Remove level`,"indicatorSettings.noLevels":`No levels.`,"indicatorSettings.level":`Level`,"indicatorSettings.close":`Close`,"toolbar.exitFullscreen":`Exit fullscreen`,"range.presets":`Visible range`,"range.all":`All`,"range.goTo":`Go to date`,"range.goToSubmit":`Go to`,"range.date":`Date`,"range.time":`Time`,"range.cancel":`Cancel`,"calendar.prevMonth":`Previous month`,"calendar.nextMonth":`Next month`,"calendar.today":`Today`,"range.beforeData":`Showing the earliest loaded bar`,"goTo.title":`Go to`,"goTo.range":`Custom range`,"goTo.from":`From`,"goTo.to":`To`,"chartType.candlestick":`Candlestick`,"chartType.line":`Line`,"chartType.area":`Area`,"chartType.bar":`Bar`,"chartType.heikinAshi":`Heikin-Ashi`,"chartType.hollowCandle":`Hollow Candle`,"chartType.baseline":`Baseline`,"chartType.volumeCandles":`Volume Candles`,"chartType.equivolume":`Equivolume`,"chartType.hlcArea":`HLC Area`,"chartType.stepLine":`Step Line`,"chartType.lineWithMarkers":`Line + Markers`,"chartType.hiLo":`High-Low`,"chartType.renko":`Renko`,"chartType.lineBreak":`Line Break`,"chartType.kagi":`Kagi`,"chartType.pointAndFigure":`Point & Figure`,"chartType.rangeBars":`Range Bars`,"watchlist.title":`Watchlist`,"watchlist.lists":`Lists`,"watchlist.newList":`New list…`,"watchlist.rename":`Rename list…`,"watchlist.deleteList":`Delete list`,"watchlist.confirmDelete":`Delete “{name}”? Click again`,"watchlist.add":`Add symbol`,"watchlist.remove":`Remove {symbol}`,"watchlist.empty":`No symbols yet. Add one with +.`,"watchlist.listName":`List name`,"watchlist.columnSymbol":`Symbol`,"watchlist.columnPrice":`Last`,"watchlist.columnMove":`Chg`,"watchlist.columnChange":`Chg%`,"symbolInfo.title":`Symbol info`,"symbolInfo.open":`Market open`,"symbolInfo.closed":`Market closed`,"symbolInfo.always":`Trades around the clock`,"symbolInfo.closesIn":`closes {when}`,"symbolInfo.opensIn":`opens {when}`,"symbolInfo.prevClose":`Prev. close`,"symbolInfo.bid":`Bid`,"symbolInfo.ask":`Ask`,"symbolInfo.tick":`Tick size`,"symbolInfo.currency":`Currency`,"symbolInfo.timezone":`Time zone`,"symbolInfo.hours":`Trading hours`,"symbolInfo.news":`News`,"symbolInfo.noNews":`No news for this symbol.`,"symbolInfo.newsFailed":`News could not be loaded.`,"symbolInfo.loading":`Loading…`,"action.symbolInfo":`Symbol info`,"nav.label":`Chart navigation`,"nav.zoomIn":`Zoom in`,"nav.zoomOut":`Zoom out`,"nav.scrollLeft":`Scroll left`,"nav.scrollRight":`Scroll right`,"nav.reset":`Reset chart view`,"a11y.role":`chart`,"a11y.summary":`{what}. Last price {close}.`,"a11y.empty":`Chart, no data.`,"a11y.view":`Showing {count} bars, {from} to {to}. High {high}, low {low}.`,"a11y.bar":`{time}: open {open}, high {high}, low {low}, close {close}, volume {volume}.`,"a11y.keys":`Arrow keys scroll and zoom, Home and End go to the start and the end. Comma and period read the bars one at a time.`,"status.connecting":`Connecting...`,"status.live":`Live`,"status.delayed":`Delayed {minutes} min`,"status.connectionFailed":`Connection failed`,"status.loading":`Loading chart...`,"history.loading":`Loading history…`,"history.failed":`Could not load older bars`,"settings.title":`Chart Settings`,"settings.tab.symbol":`Symbol`,"settings.tab.statusLine":`Status line`,"settings.tab.scales":`Scales`,"settings.tab.appearance":`Appearance`,"settings.tab.layers":`Layers`,"settings.template":`Template`,"settings.section.axes":`Axes`,"settings.saveTemplate":`Save as template…`,"settings.saveTemplateTitle":`Save chart template`,"settings.templatePlaceholder":`My chart`,"settings.resetToDefaults":`Reset to defaults`,"settings.section.candleColors":`Candle Colors`,"settings.section.background":`Background`,"hotkeys.title":`Keyboard shortcuts`,"hotkeys.footer":`Press ? anytime to show this sheet`,"hotkeys.close":`Close`,"hotkeys.search":`Search shortcuts`,"hotkeys.noMatch":`No shortcuts match`,"hotkeys.group.searchNavigation":`Search & navigation`,"hotkeys.group.chartManipulation":`Chart manipulation`,"hotkeys.group.touch":`Touch (mobile / tablet)`,"hotkeys.group.keyboard":`Keyboard`,"hotkeys.group.drawing":`Drawing`,"hotkeys.group.more":`More`,"common.close":`Close`,"common.partFailed":`Couldn't open it: check the connection and try again`,"common.cancel":`Cancel`,"common.save":`Save`,"common.show":`Show`,"common.hide":`Hide`,"common.lock":`Lock`,"common.unlock":`Unlock`,"common.open":`Open`,"common.back":`Back`,"settings.upBody":`Up body`,"settings.downBody":`Down body`,"settings.upWick":`Up wick`,"settings.downWick":`Down wick`,"settings.background":`Background`,"settings.grid":`Grid`,"settings.section.lastPrice":`Last price`,"settings.horizontal":`Horizontal`,"settings.vertical":`Vertical`,"settings.scaleLines":`Lines`,"settings.priceLine":`Price line`,"settings.paneSeparator":`Pane separator`,"settings.legendText":`Legend text`,"settings.tab.trading":`Trading`,"settings.up":`Up`,"settings.down":`Down`,"settings.aboveBase":`Above the base`,"settings.belowBase":`Below the base`,"settings.areaTop":`Fill top`,"settings.areaBottom":`Fill bottom`,"settings.paneBackground":`Pane background`,"settings.paneTitles":`Pane titles`,"settings.legendLabels":`Legend labels`,"settings.watermark":`Watermark`,"settings.labelBackground":`Label background`,"settings.labelText":`Label text`,"settings.sessionBreaks":`Session breaks`,"settings.section.orders":`Orders and positions`,"settings.profit":`Profit`,"settings.loss":`Loss`,"settings.section.signals":`Signal markers`,"settings.section.tradeZones":`Trade zones`,"settings.openTrade":`Open`,"settings.handles":`Selection handles`,"settings.gridLines":`Grid lines`,"settings.volume":`Volume`,"settings.volumeProfile":`Volume profile`,"settings.marketProfile":`Market profile (TPO)`,"settings.marketProfileSplit":`Market profile per session`,"settings.marketProfileLetters":`Market profile letters`,"settings.marketProfileBuckets":`Market profile rows`,"settings.marketProfileOpacity":`Market profile opacity`,"settings.depthHeatmap":`Liquidity heatmap`,"settings.depthHeatmapOpacity":`Heatmap opacity`,"settings.swingMarkers":`Swing markers`,"settings.swingStrength":`Swing strength`,"settings.swingStructure":`Swing structure (HH/HL)`,"settings.sessionShading":`Session shading`,"settings.periodLevels":`Prior-period levels`,"settings.periodLevelsBasis":`Prior period`,"settings.priorDay":`Prior day (PDH/PDL)`,"settings.priorWeek":`Prior week (PWH/PWL)`,"settings.ohlcLegend":`OHLC legend`,"settings.indicatorValues":`Indicator values on scale`,"settings.alignAxisLabels":`No overlapping labels`,"settings.barCountdown":`Bar countdown`,"settings.crosshairMode":`Crosshair`,"settings.crosshairCursor":`Cursor`,"settings.crosshair.magnet":`Magnet`,"settings.crosshair.normal":`Normal`,"settings.crosshair.hidden":`Hidden`,"settings.timezone":`Timezone`,"settings.timezone.local":`Local`,"settings.timezone.exchange":`Exchange`,"settings.numberLocale":`Number format`,"settings.autoScale":`Auto scale`,"settings.invertScale":`Invert scale (Alt+I)`,"settings.leftScale":`Left price scale`,"settings.priceScale":`Price scale`,"settings.mainSeries":`Main series`,"settings.highLowLines":`High and low lines`,"settings.extendedHours":`Extended hours`,"settings.boxSize":`Box size`,"settings.atrPeriod":`ATR length`,"settings.lineBreakLines":`Lines to break`,"settings.reversal":`Reversal`,"settings.reversalType":`Reversal in`,"settings.reversalType.percent":`Percent`,"settings.reversalType.price":`Price`,"settings.reversalBoxes":`Reversal boxes`,"settings.barRange":`Range`,"settings.auto":`Auto`,"settings.numberAtLeast":`{min} or more`,"settings.numberWhole":`A whole number from {min} to {max}`,"settings.scale.regular":`Regular`,"settings.scale.logarithmic":`Logarithmic`,"settings.scale.percentage":`Percentage`,"settings.scale.indexedTo100":`Indexed to 100`,"alerts.title":`Price alerts`,"alerts.close":`Close alerts`,"alerts.value":`Value`,"alerts.note":`Note (optional)`,"alerts.add":`Add alert`,"alerts.empty":`No alerts yet. Add one above, or right-click the chart.`,"alerts.delete":`Delete alert`,"alerts.triggered":`triggered`,"alerts.source.price":`Price`,"alerts.condition.crossing":`Crossing`,"alerts.condition.crossingUp":`Crossing up`,"alerts.condition.crossingDown":`Crossing down`,"alerts.condition.greaterThan":`Greater than`,"alerts.condition.lessThan":`Less than`,"alerts.fired":`Alert: {text}`,"objects.title":`Objects`,"objects.close":`Close objects`,"objects.indicators":`Indicators`,"objects.drawings":`Drawings`,"objects.compare":`Compare`,"objects.addCompare":`Add a symbol to compare`,"objects.noIndicators":`No indicators`,"objects.noDrawings":`No drawings`,"objects.noComparisons":`No comparisons`,"objects.indicatorSettings":`Indicator settings`,"objects.removeIndicator":`Remove indicator`,"objects.removeDrawing":`Remove drawing`,"objects.removeComparison":`Remove comparison`,"drawing.cursor":`Cursor`,"drawing.cursors":`Cursors`,"drawing.cursorCross":`Cross`,"drawing.cursorDot":`Dot`,"drawing.cursorArrow":`Arrow`,"drawing.tools":`Drawing tools`,"drawing.style":`Drawing style & templates`,"drawing.magnet":`Magnet`,"drawing.magnetOn":`Magnet on`,"drawing.magnetOff":`Magnet off`,"drawing.eraser":`Eraser: click a drawing to remove it`,"drawing.zoomArea":`Zoom: drag a box to zoom into it`,"drawing.magnetStrong":`Magnet: strong (always snaps)`,"drawing.stay":`Stay in drawing mode`,"drawing.undo":`Undo`,"drawing.redo":`Redo`,"drawing.clearAll":`Clear all`,"clear.drawings":`Remove drawings`,"clear.indicators":`Remove indicators`,"clear.all":`Remove drawings and indicators`,"drawing.pinFavorite":`Pin to favourites`,"drawing.lockAll":`Lock all drawings`,"drawing.hideAll":`Hide all drawings`,"drawingStyle.title":`Drawing style`,"drawingStyle.templates":`Templates`,"drawingStyle.templateName":`Template name`,"drawingStyle.noTemplates":`No saved templates`,"drawingStyle.deleteTemplate":`Delete template`,"drawingSettings.tab.style":`Style`,"drawingSettings.tab.text":`Text`,"drawingSettings.tab.coordinates":`Coordinates`,"drawingSettings.tab.visibility":`Visibility`,"drawingSettings.ok":`OK`,"drawingSettings.color":`Color`,"drawingSettings.lineWidth":`Line width`,"drawingSettings.lineStyle":`Line style`,"drawingSettings.lineStyle.solid":`Solid`,"drawingSettings.lineStyle.dashed":`Dashed`,"drawingSettings.lineStyle.dotted":`Dotted`,"drawingSettings.line":`Line`,"drawingSettings.lineEnds":`Line ends`,"drawingSettings.lineEnd.normal":`Normal`,"drawingSettings.lineEnd.arrow":`Arrow`,"drawingSettings.fill":`Fill`,"drawingSettings.opacity":`Opacity (%)`,"drawingSettings.text":`Text`,"drawingSettings.fontSize":`Font size`,"drawingSettings.bold":`Bold`,"drawingSettings.italic":`Italic`,"drawingSettings.textColor":`Text color`,"drawingSettings.align":`Align`,"drawingSettings.align.left":`Left`,"drawingSettings.align.center":`Center`,"drawingSettings.align.right":`Right`,"drawingSettings.vAlign":`Vertical align`,"drawingSettings.vAlign.top":`Top`,"drawingSettings.vAlign.middle":`Middle`,"drawingSettings.vAlign.bottom":`Bottom`,"drawingSettings.background":`Background`,"drawingSettings.backgroundColor":`Background color`,"drawingSettings.border":`Border`,"drawingSettings.borderColor":`Border color`,"drawingSettings.wrap":`Wrap`,"drawingSettings.wrapWidth":`Wrap width`,"drawingSettings.saveDefault":`Save as default`,"drawingSettings.defaultSaved":`Saved as the default for {name}`,"drawingSettings.saveTemplateTitle":`Save drawing template`,"drawingSettings.levelVisible":`Show this level`,"drawingSettings.levelValue":`Level`,"drawingSettings.levelColor":`Level color`,"drawingSettings.removeLevel":`Remove level`,"drawingSettings.addLevel":`Add level`,"drawingSettings.point":`Point {n}`,"drawingSettings.date":`Date`,"drawingSettings.time":`Time`,"drawingSettings.price":`Price`,"drawingOption.extendLeft":`Extend left`,"drawingOption.extendRight":`Extend right`,"drawingOption.middleLine":`Middle line`,"drawingOption.levels":`Levels`,"drawingOption.reversed":`Reverse`,"drawingOption.showLevels":`Show levels`,"drawingOption.showPrices":`Show prices`,"drawingOption.labelPosition":`Labels`,"drawingOption.labelPosition.left":`Left`,"drawingOption.labelPosition.right":`Right`,"drawingOption.background":`Background`,"drawingOption.leftEnd":`Left end`,"drawingOption.rightEnd":`Right end`,"drawingOption.showMiddlePoint":`Show middle point`,"drawingOption.showPriceRange":`Show price range`,"drawingOption.showBarsRange":`Show bars range`,"drawingOption.showDateRange":`Show date and time range`,"drawingOption.showAngle":`Show angle`,"drawingOption.alwaysShowStats":`Always show stats`,"drawingOption.statsPosition":`Stats position`,"drawingOption.statsPosition.left":`Left`,"drawingOption.statsPosition.center":`Center`,"drawingOption.statsPosition.right":`Right`,"drawingOption.showPrice":`Show price`,"drawingOption.showTime":`Show time`,"drawingOption.trendLine":`Trend line`,"drawingOption.oneColor":`Use one color`,"objects.drawingSettings":`Drawing settings`,"objects.renameGroup":`Rename group`,"objects.renameDrawing":`Rename drawing`,"objects.cloneDrawing":`Clone`,"objects.dragToReorder":`Drag to reorder`,"drawingMenu.label":`Drawing`,"drawingMenu.settings":`Settings…`,"drawingMenu.front":`Bring to front`,"drawingMenu.forward":`Bring forward`,"drawingMenu.backward":`Send backward`,"drawingMenu.back":`Send to back`,"drawingMenu.group":`Group`,"drawingMenu.ungroup":`Ungroup`,"drawingMenu.duplicate":`Duplicate`,"drawingMenu.delete":`Delete`,"selectionToolbar.label":`Drawing toolbar`,"chartMenu.label":`Chart`,"chartMenu.addAlert":`Add alert at {price}`,"chartMenu.alertAdded":`Alert added at {price}`,"chartMenu.buyLimit":`Buy limit at {price}`,"chartMenu.buyStop":`Buy stop at {price}`,"chartMenu.sellLimit":`Sell limit at {price}`,"chartMenu.sellStop":`Sell stop at {price}`,"chartMenu.horizontalLine":`Horizontal line at {price}`,"chartMenu.resetView":`Reset chart view`,"chartMenu.resetTimeScale":`Reset time scale`,"chartMenu.hideDrawings":`Hide drawings`,"chartMenu.showDrawings":`Show drawings`,"chartMenu.removeDrawings":`Remove drawings`,"chartMenu.settings":`Settings…`,"chartMenu.invertScale":`Invert scale`,"chartMenu.goToDate":`Go to date…`,"toolbar.account":`Account: positions, orders, history`,"chartMenu.orderTicket":`New order…`,"account.title":`Account`,"account.positions":`Positions`,"account.orders":`Orders`,"account.history":`History`,"account.newOrder":`New order`,"account.close":`Close the account panel`,"account.side":`Side`,"account.long":`Long`,"account.short":`Short`,"account.quantity":`Qty`,"account.entry":`Entry`,"account.price":`Price`,"account.pnl":`P&L`,"account.stopLoss":`SL`,"account.takeProfit":`TP`,"account.actions":`Actions`,"account.time":`Time`,"account.reason":`Reason`,"account.reason.order":`Order`,"account.reason.close":`Closed`,"account.reason.reverse":`Reversed`,"account.reason.stopLoss":`Stop-loss`,"account.reason.takeProfit":`Take-profit`,"account.reverse":`Reverse position`,"account.closePosition":`Close position`,"account.cancelOrder":`Cancel order`,"account.noPositions":`No open positions`,"account.noOrders":`No working orders`,"account.noFills":`No trades yet`,"account.realised":`Realised P&L: {pnl}`,"account.openPnl":`Open P&L: {pnl}`,"ticket.title":`New order`,"ticket.buy":`Buy`,"ticket.sell":`Sell`,"ticket.type":`Type`,"ticket.type.market":`Market`,"ticket.type.limit":`Limit`,"ticket.type.stop":`Stop`,"ticket.quantity":`Quantity`,"ticket.price":`Price`,"ticket.stopLoss":`Stop-loss`,"ticket.takeProfit":`Take-profit`,"ticket.trailing":`Trailing stop`,"ticket.timeInForce":`Time in force`,"ticket.tif.gtc":`Good till cancelled`,"ticket.tif.day":`Day`,"ticket.value":`Value`,"ticket.risk":`Risk`,"ticket.reward":`Reward`,"ticket.rewardRisk":`Reward to risk`,"ticket.stepDown":`{field} down`,"ticket.stepUp":`{field} up`,"ticket.placeBuy":`Place buy order`,"ticket.placeSell":`Place sell order`,"ticket.sent":`Order sent: {side} {quantity} @ {price}`,"ticket.problem.quantity":`Enter a quantity above 0.`,"ticket.problem.price":`Enter a price above 0.`,"ticket.problem.market":`There is no market price yet.`,"ticket.problem.stopLoss":`Enter a stop-loss price above 0.`,"ticket.problem.takeProfit":`Enter a take-profit price above 0.`,"ticket.problem.stopLossSide":`The stop-loss goes below the entry for a buy, above it for a sell.`,"ticket.problem.takeProfitSide":`The take-profit goes above the entry for a buy, below it for a sell.`,"ticket.problem.limitSide":`A buy limit goes below the market and a sell limit above it.`,"ticket.problem.stopSide":`A buy stop goes above the market and a sell stop below it.`,"ticket.problem.priceStep":`Prices go in the market’s price steps.`,"ticket.problem.priceLimits":`Today’s prices run from {floor} to {ceiling}.`,"ticket.problem.lot":`Above one lot, order whole lots of {lot}; odd shares go in an order of their own.`,"toolbar.layouts":`Layout`,"layouts.unnamed":`Unnamed`,"layouts.unsavedChanges":`unsaved changes`,"layouts.save":`Save layout`,"layouts.saveAs":`Save as…`,"layouts.rename":`Rename…`,"layouts.autoSave":`Auto-save`,"layouts.open":`Open layout…`,"layouts.saved":`Layout saved: {name}`,"layouts.saveFailed":`Couldn't save the layout`,"layouts.openFailed":`Couldn't open the layout`,"layouts.deleteFailed":`Couldn't delete the layout`,"layouts.saveAsTitle":`Save layout as`,"layouts.renameTitle":`Rename layout`,"layouts.renameSubmit":`Rename`,"layouts.copyName":`{name} (copy)`,"layouts.name":`Name`,"layouts.namePlaceholder":`My layout`,"layouts.dialogTitle":`Layouts`,"layouts.search":`Search layouts`,"layouts.empty":`No saved layouts yet`,"layouts.noMatch":`No layouts match`,"layouts.current":`Open`,"layouts.updated":`saved {time}`,"layouts.renameNamed":`Rename {name}`,"layouts.deleteNamed":`Delete {name}`,"layouts.deleteConfirm":`Delete`,"grid.bar":`Charts`,"grid.layout":`Chart layout`,"grid.single":`One chart`,"grid.layoutOption":`{count} charts, {cols} across and {rows} down`,"grid.layoutCount":`{count} charts`,"grid.layoutBigLeft":`{count} charts, one large on the left`,"grid.layoutBigRight":`{count} charts, one large on the right`,"grid.layoutBigTop":`{count} charts, one large on top`,"grid.layoutBigBottom":`{count} charts, one large at the bottom`,"grid.chart":`Chart {n}`,"grid.charts":`Charts`,"grid.sync":`Sync`,"grid.sync.symbol":`Symbol`,"grid.sync.interval":`Interval`,"grid.sync.crosshair":`Crosshair`,"grid.sync.time":`Time`,"grid.sync.drawings":`Drawings`,"grid.sync.replay":`Replay`,"grid.sync.symbol.hint":`Change the symbol on every chart together`,"grid.sync.interval.hint":`Change the interval on every chart together`,"grid.sync.crosshair.hint":`Show the crosshair’s time on every chart`,"grid.sync.time.hint":`Scroll and zoom every chart to the same time`,"grid.sync.drawings.hint":`Copy drawings to the charts of the same symbol`,"grid.sync.replay.hint":`Replay every chart to the same time`,"legend.more":`More`,"legend.moveUp":`Move to pane above`,"legend.moveDown":`Move to pane below`,"legend.moveNew":`Move to new pane`,"legend.movePrice":`Move to price pane`,"pane.moveUp":`Move pane up`,"pane.moveDown":`Move pane down`,"pane.collapse":`Collapse pane`,"pane.expand":`Expand pane`,"pane.maximize":`Maximize pane`,"pane.restore":`Restore panes`,"toolbar.indicators.templates":`Templates`,"templates.save":`Save indicators as template…`,"templates.saveTitle":`Save indicator template`,"templates.namePlaceholder":`My indicators`,"templates.saved":`Template saved: {name}`,"templates.applied":`Template applied: {name}`,"templates.delete":`Delete template {name}`,"templates.nothing":`There are no indicators to save`,"templates.saveFailed":`Couldn't save the template`,"interval.title":`Change interval`,"interval.hint":`5, 15m, 1h, 1D — Enter to apply`,"interval.invalid":`Not an interval this chart can show`,"alerts.condition.movesUp":`Moves up`,"alerts.condition.movesDown":`Moves down`,"alerts.source":`Watch`,"alerts.conditionLabel":`Condition`,"alerts.compareWith":`Compared with`,"alerts.compare.value":`A value`,"alerts.percent":`Percent`,"alerts.bars":`Bars`,"alerts.moveSummary":`{percent}% within {bars} bars`,"alerts.onBarClose":`Only on bar close`,"alerts.onClose":`on bar close`,"alerts.expires":`Expires`,"alerts.expires.never":`Never expires`,"alerts.expires.hour":`Expires in 1 hour`,"alerts.expires.day":`Expires in 1 day`,"alerts.expires.week":`Expires in 1 week`,"alerts.expires.month":`Expires in 30 days`,"alerts.until":`until {time}`,"alerts.expired":`Expired`,"alerts.expiredToast":`Alert expired: {text}`,"alerts.invalid":`These alert settings don’t fit together`,"replay.step":`Step`,"replay.step.bar":`Bar`,"replay.step.auto":`Auto`,"replay.stepsFailed":`Couldn't load the finer bars: replaying bar by bar`,"replay.selectBar":`Select bar`,"replay.rate":`{rate}×`,"replay.rateHint":`1× plays one bar a second`,"replay.playingAt":`Playing at {rate}`,"replay.pausedAt":`Paused at {time}`,"replay.endedAt":`Replay ended at {time}`,"hotkeys.replayStep":`Step the replay back / forward`,"hotkeys.replayPlay":`Play or pause the replay`,"hotkeys.replayCancel":`Stop picking the replay's start bar`,"markers.neutral":`Neutral`,"markers.confidence":`confidence {pct}%`,"chartMenu.paneLog":`Logarithmic scale`,"chartMenu.paneInvert":`Invert scale`,"chartMenu.panePercent":`Percent scale`,"chartMenu.exportData":`Export data (CSV)`,"chartMenu.paste":`Paste`,"chartMenu.removeIndicators":`Remove indicators`,"chartMenu.resetPriceScale":`Reset price scale`,"chartMenu.labels":`Labels`,"chartMenu.lines":`Lines`,"chartMenu.lastPriceLine":`Last price line`,"compare.percent":`Percent change, on this scale`,"compare.ownScale":`Price, on its own scale`,"compare.ownPane":`Price, in a pane of its own`,"compare.title":`Compare symbol`,"compare.addAs":`Add as`,"compare.onChart":`On the chart`,"compare.symbols":`Symbols`,"compare.remove":`Remove {symbol}`,"compare.empty":`Nothing compared yet. Search for a symbol to add it.`,"compare.way.spread":`Spread`,"compare.way.ratio":`Ratio`,"compare.way.correlation":`Correlation`,"compare.way.correlationLog":`Correlation of returns`,"drawingSettings.addAlert":`Add alert`,"drawingSettings.alertAdded":`Alert added: price crossing {name}`,"drawingOption.rewardRatio":`Reward:risk`,"drawingOption.accountSize":`Account size`,"drawingOption.riskMode":`Risk as`,"drawingOption.riskMode.percent":`Percent of account`,"drawingOption.riskMode.amount":`Amount`,"drawingOption.risk":`Risk`,"drawingOption.qtyDecimals":`Quantity decimals`,"drawingOption.showLabels":`Show labels`,"drawingOption.showText":`Show text`,"drawingOption.direction":`Direction`,"drawingOption.direction.up":`Up`,"drawingOption.direction.down":`Down`,"drawingOption.direction.left":`Left`,"drawingOption.direction.right":`Right`,"drawingOption.size":`Size`,"drawingOption.glyph":`Icon`,"drawingOption.glyph.star":`Star`,"drawingOption.glyph.heart":`Heart`,"drawingOption.glyph.check":`Check`,"drawingOption.glyph.cross":`Cross`,"drawingOption.glyph.circle":`Circle`,"drawingOption.glyph.triangleUp":`Triangle up`,"drawingOption.glyph.triangleDown":`Triangle down`,"drawingOption.glyph.bolt":`Bolt`,"drawingOption.arrowEnd":`Arrow at the end`,"drawingOption.fullCircles":`Full circles`,"drawingOption.clockwise":`Clockwise`,"drawingOption.arcs":`Arcs`,"drawingOption.degree":`Degree`,"drawingOption.degree.primary":`Primary`,"drawingOption.degree.intermediate":`Intermediate`,"drawingOption.degree.minor":`Minor`,"drawingOption.degree.minute":`Minute`,"drawingOption.mode":`Show as`,"drawingOption.mode.bars":`Bars`,"drawingOption.mode.line":`Line`,"drawingOption.mode.highLow":`High-low`,"drawingOption.mirrored":`Mirrored`,"drawingOption.flipped":`Flipped`,"toolGroup.lines":`Lines`,"toolGroup.horizontalVertical":`Horizontal/Vertical`,"toolGroup.channels":`Channels`,"toolGroup.fibonacci":`Fibonacci`,"toolGroup.shapes":`Shapes`,"toolGroup.gannPitchforks":`Gann & Pitchforks`,"toolGroup.patterns":`Patterns`,"toolGroup.measure":`Measure`,"toolGroup.annotation":`Annotation`,"toolGroup.forecasting":`Forecasting`,"toolGroup.brushes":`Brushes`,"toolGroup.cycles":`Cycles`,"toolGroup.elliott":`Elliott Waves`,"tool.trendLine":`Trend Line`,"tool.ray":`Ray`,"tool.extendedLine":`Extended Line`,"tool.infoLine":`Info Line`,"tool.trendAngle":`Trend Angle`,"tool.horizontalLine":`Horizontal Line`,"tool.horizontalRay":`Horizontal Ray`,"tool.verticalLine":`Vertical Line`,"tool.crossLine":`Cross Line`,"tool.parallelChannel":`Parallel Channel`,"tool.regressionChannel":`Regression Channel`,"tool.disjointChannel":`Disjoint Channel`,"tool.flatTopBottom":`Flat Top/Bottom`,"tool.fibRetracement":`Fib Retracement`,"tool.fibExtension":`Fib Extension`,"tool.fibChannel":`Fib Channel`,"tool.fibTimeZones":`Fib Time Zones`,"tool.fibSpeedResistanceFan":`Fib Speed Resistance Fan`,"tool.rectangle":`Rectangle`,"tool.rotatedRectangle":`Rotated Rectangle`,"tool.circle":`Circle`,"tool.ellipse":`Ellipse`,"tool.triangle":`Triangle`,"tool.pitchfork":`Pitchfork`,"tool.schiffPitchfork":`Schiff Pitchfork`,"tool.modifiedSchiffPitchfork":`Modified Schiff Pitchfork`,"tool.gannFan":`Gann Fan`,"tool.gannBox":`Gann Box`,"tool.cyclicLines":`Cyclic Lines`,"tool.xabcdPattern":`XABCD Pattern`,"tool.abcdPattern":`ABCD Pattern`,"tool.headAndShoulders":`Head and Shoulders`,"tool.elliottWave":`Elliott Wave`,"tool.priceRange":`Price Range`,"tool.dateRange":`Date Range`,"tool.dateAndPriceRange":`Date and Price Range`,"tool.measure":`Measure`,"tool.text":`Text`,"tool.priceLabel":`Price Label`,"tool.arrow":`Arrow`,"tool.riskReward":`Long/Short Position`,"tool.anchoredVWAP":`Anchored VWAP`,"tool.volumeProfileRange":`Fixed Range Volume Profile`,"tool.note":`Note`,"tool.callout":`Callout`,"tool.flag":`Flag Mark`,"tool.arrowMark":`Arrow Mark`,"tool.icon":`Icon`,"tool.signpost":`Signpost`,"tool.balloon":`Balloon`,"tool.anchoredText":`Anchored Text`,"tool.anchoredNote":`Anchored Note`,"tool.brush":`Brush`,"tool.highlighter":`Highlighter`,"tool.path":`Path`,"tool.polyline":`Polyline`,"tool.curve":`Curve`,"tool.arc":`Arc`,"tool.fibCircles":`Fib Circles`,"tool.fibSpiral":`Fib Spiral`,"tool.fibArcs":`Fib Speed Resistance Arcs`,"tool.fibWedge":`Fib Wedge`,"tool.pitchfan":`Pitchfan`,"tool.gannSquare":`Gann Square`,"tool.elliottImpulse":`Elliott Impulse Wave`,"tool.elliottCorrection":`Elliott Correction Wave`,"tool.elliottTriangle":`Elliott Triangle Wave`,"tool.elliottDoubleCombo":`Elliott Double Combo Wave`,"tool.elliottTripleCombo":`Elliott Triple Combo Wave`,"tool.threeDrives":`Three Drives Pattern`,"tool.cypherPattern":`Cypher Pattern`,"tool.timeCycles":`Time Cycles`,"tool.sineLine":`Sine Line`,"tool.forecast":`Forecast`,"tool.projection":`Projection`,"tool.barsPattern":`Bars Pattern`,"replay.label":`Replay`,"replay.position":`Replay position`,"replay.speed":`Speed`,"symbolSearch.placeholder":`Search symbol… (e.g. BTC, ETH, AAPL)`,"symbolSearch.empty":`No symbols match`,"symbolSearch.searching":`Searching…`,"symbolSearch.failed":`Search failed`,"symbolSearch.title":`Symbol search`,"symbolSearch.symbol":`Symbol`,"symbolSearch.description":`Description`,"symbolSearch.kinds":`Kinds of symbol`,"symbolSearch.allKinds":`All`,"symbolSearch.type":`Type`,"symbolSearch.exchange":`Exchange`,"symbolSearch.allExchanges":`All exchanges`,"symbolSearch.more":`More symbols: {count}. Type to narrow the list.`,"symbolSearch.typeHint":`Type a letter on the chart to open this search`,"palette.placeholder":`Search indicators, chart types, tools…`,"palette.empty":`No results found`,"palette.group.indicator":`Indicators`,"palette.group.chartType":`Chart types`,"palette.group.drawing":`Drawing tools`,"palette.group.timeframe":`Timeframes`,"palette.group.action":`Actions`,"palette.group.recent":`Recent`,"action.screenshot":`Screenshot`,"action.copyImage":`Copy chart image`,"action.toggleTheme":`Toggle theme`,"action.settings":`Settings`,"action.shareView":`Share view (copy link)`,"action.autoFib":`Auto Fibonacci (visible swing)`,"action.dataWindow":`Toggle data window`,"action.clearDrawings":`Clear all drawings`,"ladder.title":`Depth ladder`,"ladder.bid":`Bid`,"ladder.price":`Price`,"ladder.ask":`Ask`,"ladder.empty":`No order-book data`,"ladder.sellAt":`Sell @ {price}`,"ladder.buyAt":`Buy @ {price}`,"ladder.spread":`Spread {spread}`,"dataWindow.title":`Data window`,"dock.rail":`Panels`,"dock.resize":`Resize the panel`,"dataWindow.hover":`Hover the chart`,"dataWindow.touch":`Touch the chart`,"dataWindow.open":`Open`,"dataWindow.high":`High`,"dataWindow.low":`Low`,"dataWindow.close":`Close`,"dataWindow.change":`Change`,"dataWindow.volume":`Volume`,"bracket.place":`Place`,"bracket.hint":`drag lines to adjust`,"bracket.longPlaced":`Long bracket placed · {rr}R`,"bracket.shortPlaced":`Short bracket placed · {rr}R`,"order.buyLimit":`Buy limit @ {price}`,"order.sellLimit":`Sell limit @ {price}`,"order.buyStop":`Buy stop @ {price}`,"order.sellStop":`Sell stop @ {price}`,"toast.pinned":`Pinned to favorites`,"toast.unpinned":`Unpinned`,"sidebar.unpinHint":`{tool} (right-click to unpin)`,"toast.imageCopied":`Chart image copied`,"toast.imageCopyFailed":`Copy failed — image clipboard unavailable`,"toast.linkCopied":`Share link copied`,"toast.copyFailed":`Copy failed — clipboard unavailable`,"toast.compareNeedsAdapter":`Comparison needs a live data adapter`,"toast.comparing":`Comparing {symbol}`,"toast.loadFailed":`failed to load`,"toast.compareNoBars":`{symbol}: no bars at {interval}`,"toast.autoFibAdded":`Auto Fibonacci added`,"toast.noSwing":`No clear swing in view`,"toast.fullscreenUnavailable":`Fullscreen is not available`,"toast.fileLoaded":`Loaded {count} bars from {file}`,"toast.fileSkipped":`{count} skipped`,"hotkeys.commandPalette":`Command palette`,"hotkeys.symbolSearch":`Symbol search`,"hotkeys.goToDate":`Go to date`,"hotkeys.invertScale":`Invert price scale`,"hotkeys.showSheet":`Show this sheet`,"hotkeys.saveLayout":`Save the layout`,"hotkeys.typeInterval":`Type a number to change the interval`,"hotkeys.typeSymbol":`Type a letter to search for a symbol`,"hotkeys.pan":`Pan — past the last bar into empty future space`,"hotkeys.boxSelect":`Select drawings in a box`,"hotkeys.toggleSelect":`Add / remove a drawing from the selection`,"hotkeys.measure":`Measure tool (bars × price Δ)`,"hotkeys.scalePrice":`Compress / expand vertical scale`,"hotkeys.zoomTime":`Zoom time axis`,"hotkeys.autoScale":`Re-enable auto-scale`,"hotkeys.fit":`Fit content`,"hotkeys.zoomCursor":`Zoom around cursor`,"hotkeys.touchPan":`Pan / move crosshair`,"hotkeys.pinch":`Zoom around midpoint`,"hotkeys.longPress":`Pin OHLC tooltip at bar`,"hotkeys.touchAxis":`Scale price / time axis`,"hotkeys.panBar":`Pan one bar`,"hotkeys.zoomInOut":`Zoom in / out`,"hotkeys.toStart":`Scroll to start`,"hotkeys.toEnd":`Scroll to end (live edge)`,"hotkeys.undo":`Undo`,"hotkeys.redo":`Redo`,"hotkeys.cancelDrawing":`Cancel active drawing`,"hotkeys.copy":`Copy selected drawings`,"hotkeys.paste":`Paste drawings (into any chart on the page)`,"key.drag":`Drag`,"key.click":`Click`,"key.doubleClick":`Double-click`,"key.scroll":`Scroll`,"key.priceAxis":`Price axis`,"key.timeAxis":`Time axis`,"key.oneFinger":`1 finger`,"key.twoFingers":`2 fingers`,"key.pinch":`Pinch`,"key.longPress":`Long-press`,"key.axisStrip":`Axis strip`,"hotkeys.drawingMenu":`A drawing's menu (on the drawing)`,"hotkeys.drawingSettings":`A drawing's settings (on the drawing)`,"hotkeys.group":`Group the selected drawings`,"hotkeys.ungroup":`Ungroup the selected drawings`,"hotkeys.orderStep":`Bring forward / send backward`,"hotkeys.orderEnd":`Bring to front / send to back`,"hotkeys.finishPath":`Finish a path or polyline`,"hotkeys.copyDrag":`Drag a copy of a drawing (on the drawing)`,"hotkeys.keepAngle":`Keep a line flat, upright or at 45° (drawing it, or by a handle)`,"hotkeys.flipMagnet":`Hold while drawing: turn the magnet on or off`,"hotkeys.nudge":`Move the selected drawings (Shift: 10 steps)`,"key.rightClick":`Right-click`},t={"toolbar.indicators":`Chỉ báo`,"toolbar.more":`Thêm`,"toolbar.indicators.all":`Tất cả`,"indicatorType.overlay":`phủ lên`,"indicatorType.panel":`bảng riêng`,"indicatorPicker.search":`Tìm kiếm`,"indicatorPicker.groups":`Nhóm`,"indicatorPicker.favorites":`Yêu thích`,"indicatorPicker.favorite":`Yêu thích`,"indicatorPicker.noFavorites":`Gắn sao cho chỉ báo để giữ nó ở đây.`,"indicatorPicker.noMatch":`Không có chỉ báo nào khớp với “{query}”.`,"indicatorPicker.noTemplates":`Lưu các chỉ báo trên biểu đồ thành mẫu để thêm tất cả cùng lúc.`,"indicatorPicker.added":`Đã thêm {name}`,"indicatorPicker.onChart":`Trên biểu đồ: {count}`,"indicatorCategory.movingAverages":`Đường trung bình động`,"indicatorCategory.trend":`Xu hướng`,"indicatorCategory.oscillators":`Dao động`,"indicatorCategory.volatility":`Biến động`,"indicatorCategory.volume":`Khối lượng`,"indicatorCategory.bands":`Dải & kênh`,"indicatorCategory.patterns":`Giá & mô hình`,"indicatorCategory.other":`Khác`,"toolbar.replay":`Bar replay`,"replay.selectHint":`Bấm vào một nến để bắt đầu replay`,"replay.random":`Nến ngẫu nhiên`,"replay.realtime":`Về thời gian thực`,"replay.play":`Phát`,"replay.pause":`Tạm dừng`,"replay.stepBack":`Lùi một nến (Shift+←)`,"replay.stepForward":`Tiến một nến (Shift+→)`,"toolbar.longBracket":`Lệnh Long`,"toolbar.shortBracket":`Lệnh Short`,"toolbar.depthLadder":`DOM ladder`,"toolbar.objects":`Đối tượng`,"toolbar.priceAlerts":`Cảnh báo giá`,"toolbar.screenshot":`Chụp màn hình`,"toolbar.settings":`Cài đặt biểu đồ`,"toolbar.toggleTheme":`Đổi giao diện`,"toolbar.timeframes":`Khung thời gian`,"toolbar.timeframes.pin":`Ghim lên thanh công cụ`,"toolbar.timeframes.custom":`Khung tuỳ chỉnh, ví dụ 7m, 90m, 2h, 3D`,"toolbar.timeframes.add":`Thêm khung`,"toolbar.timeframes.remove":`Xoá khung`,"toolbar.timeframes.invalid":`Nhập số và đơn vị: 7m, 2h, 3D, 1W, 2M`,"toolbar.timeframes.ticks":`Tick`,"toolbar.timeframes.seconds":`Giây`,"toolbar.timeframes.minutes":`Phút`,"toolbar.timeframes.hours":`Giờ`,"toolbar.timeframes.days":`Ngày`,"toolbar.timeframes.weeks":`Tuần`,"toolbar.timeframes.months":`Tháng`,"toolbar.trade":`Giao dịch`,"statusBar.label":`Thanh trạng thái`,"statusBar.log":`log`,"statusBar.auto":`tự động`,"statusBar.marketOpen":`Mở cửa`,"statusBar.marketClosed":`Đóng cửa`,"toolbar.fullscreen":`Toàn màn hình`,"legend.show":`Hiện`,"legend.hide":`Ẩn`,"legend.settings":`Cài đặt`,"legend.remove":`Xoá`,"legend.collapse":`Thu gọn chỉ báo`,"legend.expand":`Hiện chỉ báo`,"legend.offChartType":`Không vẽ ở loại biểu đồ này`,"indicatorSettings.title":`Cài đặt {name}`,"indicatorSettings.tab.inputs":`Thông số`,"indicatorSettings.tab.style":`Kiểu`,"indicatorSettings.tab.levels":`Mức`,"indicatorSettings.tab.visibility":`Hiển thị`,"indicatorSettings.defaults":`Mặc định`,"indicatorSettings.resetSettings":`Đặt lại cài đặt`,"indicatorSettings.saveAsDefault":`Lưu làm mặc định`,"indicatorSettings.defaultSaved":`Từ giờ, chỉ báo {name} mới sẽ bắt đầu như thế này.`,"indicatorSettings.defaultSaveFailed":`Không lưu được mặc định`,"indicatorSettings.rangeFrom":`từ`,"indicatorSettings.rangeTo":`đến`,"indicatorSettings.offInterval":`Không hiển thị ở khung thời gian này`,"indicatorSettings.noInputs":`Chỉ báo này không có thông số để chỉnh.`,"indicatorInput.anchor":`Mốc`,"indicatorInput.anchor.session":`Phiên`,"indicatorInput.anchor.week":`Tuần`,"indicatorInput.anchor.month":`Tháng`,"indicatorInput.anchor.all":`Tất cả nến`,"indicatorSettings.source.prices":`Giá`,"indicatorSettings.source.indicators":`Chỉ báo`,"indicatorSettings.lineWidth":`Độ dày nét`,"indicatorSettings.scale":`Thang giá`,"indicatorSettings.scale.right":`Phải (thang giá)`,"indicatorSettings.scale.left":`Trái (dải riêng)`,"indicatorSettings.up":`tăng`,"indicatorSettings.down":`giảm`,"indicatorSettings.addLevel":`Thêm mức`,"indicatorSettings.removeLevel":`Xoá mức`,"indicatorSettings.noLevels":`Chưa có mức nào.`,"indicatorSettings.level":`Mức`,"indicatorSettings.close":`Đóng`,"toolbar.exitFullscreen":`Thoát toàn màn hình`,"range.presets":`Khoảng hiển thị`,"range.all":`Tất cả`,"range.goTo":`Đi tới ngày`,"range.goToSubmit":`Đi tới`,"range.date":`Ngày`,"range.time":`Giờ`,"range.cancel":`Huỷ`,"calendar.prevMonth":`Tháng trước`,"calendar.nextMonth":`Tháng sau`,"calendar.today":`Hôm nay`,"range.beforeData":`Đang hiện nến sớm nhất đã tải`,"goTo.title":`Đi tới`,"goTo.range":`Khoảng tự chọn`,"goTo.from":`Từ`,"goTo.to":`Đến`,"chartType.candlestick":`Nến`,"chartType.line":`Đường`,"chartType.area":`Vùng`,"chartType.bar":`Thanh`,"chartType.heikinAshi":`Heikin-Ashi`,"chartType.hollowCandle":`Nến rỗng`,"chartType.baseline":`Đường cơ sở`,"chartType.volumeCandles":`Nến khối lượng`,"chartType.equivolume":`Equivolume`,"chartType.hlcArea":`Vùng HLC`,"chartType.stepLine":`Đường bậc thang`,"chartType.lineWithMarkers":`Đường + điểm đánh dấu`,"chartType.hiLo":`Cao-Thấp`,"chartType.renko":`Renko`,"chartType.lineBreak":`Line Break`,"chartType.kagi":`Kagi`,"chartType.pointAndFigure":`Point & Figure`,"chartType.rangeBars":`Nến theo biên độ`,"watchlist.title":`Watchlist`,"watchlist.lists":`Danh sách`,"watchlist.newList":`Danh sách mới…`,"watchlist.rename":`Đổi tên danh sách…`,"watchlist.deleteList":`Xoá danh sách`,"watchlist.confirmDelete":`Xoá “{name}”? Bấm lần nữa`,"watchlist.add":`Thêm mã`,"watchlist.remove":`Bỏ {symbol}`,"watchlist.empty":`Chưa có mã nào. Thêm bằng nút +.`,"watchlist.listName":`Tên danh sách`,"watchlist.columnSymbol":`Mã`,"watchlist.columnPrice":`Giá`,"watchlist.columnMove":`+/-`,"watchlist.columnChange":`%`,"symbolInfo.title":`Thông tin mã`,"symbolInfo.open":`Thị trường mở cửa`,"symbolInfo.closed":`Thị trường đóng cửa`,"symbolInfo.always":`Giao dịch suốt ngày đêm`,"symbolInfo.closesIn":`đóng cửa {when}`,"symbolInfo.opensIn":`mở cửa {when}`,"symbolInfo.prevClose":`Đóng cửa phiên trước`,"symbolInfo.bid":`Giá mua`,"symbolInfo.ask":`Giá bán`,"symbolInfo.tick":`Bước giá`,"symbolInfo.currency":`Tiền tệ`,"symbolInfo.timezone":`Múi giờ`,"symbolInfo.hours":`Giờ giao dịch`,"symbolInfo.news":`Tin tức`,"symbolInfo.noNews":`Chưa có tin về mã này.`,"symbolInfo.newsFailed":`Không tải được tin tức.`,"symbolInfo.loading":`Đang tải…`,"action.symbolInfo":`Thông tin mã`,"nav.label":`Điều hướng biểu đồ`,"nav.zoomIn":`Phóng to`,"nav.zoomOut":`Thu nhỏ`,"nav.scrollLeft":`Cuộn sang trái`,"nav.scrollRight":`Cuộn sang phải`,"nav.reset":`Đặt lại khung nhìn`,"a11y.role":`biểu đồ`,"a11y.summary":`{what}. Giá cuối {close}.`,"a11y.empty":`Biểu đồ, chưa có dữ liệu.`,"a11y.view":`Đang hiện {count} nến, từ {from} đến {to}. Cao nhất {high}, thấp nhất {low}.`,"a11y.bar":`{time}: mở {open}, cao {high}, thấp {low}, đóng {close}, khối lượng {volume}.`,"a11y.keys":`Phím mũi tên để cuộn và phóng to thu nhỏ, Home và End để về đầu và cuối. Dấu phẩy và dấu chấm đọc từng nến một.`,"status.connecting":`Đang kết nối...`,"status.live":`Trực tiếp`,"status.delayed":`Trễ {minutes} phút`,"status.connectionFailed":`Kết nối thất bại`,"status.loading":`Đang tải biểu đồ...`,"history.loading":`Đang tải dữ liệu cũ…`,"history.failed":`Không tải được dữ liệu cũ`,"settings.title":`Cài đặt biểu đồ`,"settings.tab.symbol":`Mã`,"settings.tab.statusLine":`Dòng trạng thái`,"settings.tab.scales":`Thang đo`,"settings.tab.appearance":`Giao diện`,"settings.tab.layers":`Lớp`,"settings.template":`Mẫu`,"settings.section.axes":`Trục`,"settings.saveTemplate":`Lưu thành mẫu…`,"settings.saveTemplateTitle":`Lưu mẫu biểu đồ`,"settings.templatePlaceholder":`Biểu đồ của tôi`,"settings.resetToDefaults":`Khôi phục mặc định`,"settings.section.candleColors":`Màu nến`,"settings.section.background":`Nền`,"hotkeys.title":`Phím tắt`,"hotkeys.footer":`Nhấn ? bất cứ lúc nào để mở bảng này`,"hotkeys.close":`Đóng`,"hotkeys.search":`Tìm phím tắt`,"hotkeys.noMatch":`Không có phím tắt nào khớp`,"hotkeys.group.searchNavigation":`Tìm kiếm & điều hướng`,"hotkeys.group.chartManipulation":`Thao tác biểu đồ`,"hotkeys.group.touch":`Cảm ứng (di động / máy tính bảng)`,"hotkeys.group.keyboard":`Bàn phím`,"hotkeys.group.drawing":`Vẽ`,"hotkeys.group.more":`Khác`,"common.close":`Đóng`,"common.partFailed":`Không mở được: hãy kiểm tra kết nối rồi thử lại`,"common.cancel":`Huỷ`,"common.save":`Lưu`,"common.show":`Hiện`,"common.hide":`Ẩn`,"common.lock":`Khoá`,"common.unlock":`Mở khoá`,"common.open":`Mở`,"common.back":`Quay lại`,"settings.upBody":`Thân nến tăng`,"settings.downBody":`Thân nến giảm`,"settings.upWick":`Râu nến tăng`,"settings.downWick":`Râu nến giảm`,"settings.background":`Nền`,"settings.grid":`Lưới`,"settings.section.lastPrice":`Giá cuối`,"settings.horizontal":`Ngang`,"settings.vertical":`Dọc`,"settings.scaleLines":`Đường kẻ`,"settings.priceLine":`Đường giá`,"settings.paneSeparator":`Vạch ngăn pane`,"settings.legendText":`Chữ chú thích`,"settings.tab.trading":`Giao dịch`,"settings.up":`Tăng`,"settings.down":`Giảm`,"settings.aboveBase":`Trên đường nền`,"settings.belowBase":`Dưới đường nền`,"settings.areaTop":`Tô phía trên`,"settings.areaBottom":`Tô phía dưới`,"settings.paneBackground":`Nền pane`,"settings.paneTitles":`Tiêu đề pane`,"settings.legendLabels":`Nhãn chú thích`,"settings.watermark":`Hình mờ`,"settings.labelBackground":`Nền nhãn`,"settings.labelText":`Chữ nhãn`,"settings.sessionBreaks":`Vạch phiên`,"settings.section.orders":`Lệnh và vị thế`,"settings.profit":`Lãi`,"settings.loss":`Lỗ`,"settings.section.signals":`Điểm tín hiệu`,"settings.section.tradeZones":`Vùng lệnh`,"settings.openTrade":`Đang mở`,"settings.handles":`Tay nắm khi chọn`,"settings.gridLines":`Đường lưới`,"settings.volume":`Khối lượng`,"settings.volumeProfile":`Hồ sơ khối lượng`,"settings.marketProfile":`Hồ sơ thị trường (TPO)`,"settings.marketProfileSplit":`Hồ sơ thị trường theo phiên`,"settings.marketProfileLetters":`Chữ cái hồ sơ thị trường`,"settings.marketProfileBuckets":`Số hàng hồ sơ thị trường`,"settings.marketProfileOpacity":`Độ mờ hồ sơ thị trường`,"settings.depthHeatmap":`Liquidity heatmap`,"settings.depthHeatmapOpacity":`Độ mờ heatmap`,"settings.swingMarkers":`Điểm đảo chiều`,"settings.swingStrength":`Độ mạnh điểm đảo chiều`,"settings.swingStructure":`Cấu trúc đỉnh đáy (HH/HL)`,"settings.sessionShading":`Tô màu phiên`,"settings.periodLevels":`Mức của kỳ trước`,"settings.periodLevelsBasis":`Kỳ trước`,"settings.priorDay":`Ngày trước (PDH/PDL)`,"settings.priorWeek":`Tuần trước (PWH/PWL)`,"settings.ohlcLegend":`Chú thích OHLC`,"settings.indicatorValues":`Giá trị chỉ báo trên trục`,"settings.alignAxisLabels":`Nhãn không chồng nhau`,"settings.barCountdown":`Đếm ngược nến`,"settings.crosshairMode":`Crosshair`,"settings.crosshairCursor":`Con trỏ`,"settings.crosshair.magnet":`Hút vào giá`,"settings.crosshair.normal":`Thường`,"settings.crosshair.hidden":`Ẩn`,"settings.timezone":`Múi giờ`,"settings.timezone.local":`Giờ máy`,"settings.timezone.exchange":`Theo sàn`,"settings.numberLocale":`Định dạng số`,"settings.autoScale":`Tự co giãn thang`,"settings.invertScale":`Đảo ngược thang (Alt+I)`,"settings.leftScale":`Thang giá bên trái`,"settings.priceScale":`Thang giá`,"settings.mainSeries":`Chuỗi giá chính`,"settings.highLowLines":`Đường cao nhất và thấp nhất`,"settings.extendedHours":`Giờ giao dịch mở rộng`,"settings.boxSize":`Box size`,"settings.atrPeriod":`Độ dài ATR`,"settings.lineBreakLines":`Số đường cần phá`,"settings.reversal":`Mức đảo chiều`,"settings.reversalType":`Đảo chiều theo`,"settings.reversalType.percent":`Phần trăm`,"settings.reversalType.price":`Giá`,"settings.reversalBoxes":`Số box đảo chiều`,"settings.barRange":`Biên độ`,"settings.auto":`Tự động`,"settings.numberAtLeast":`Từ {min} trở lên`,"settings.numberWhole":`Số nguyên từ {min} đến {max}`,"settings.scale.regular":`Thường`,"settings.scale.logarithmic":`Logarit`,"settings.scale.percentage":`Phần trăm`,"settings.scale.indexedTo100":`Quy về 100`,"alerts.title":`Cảnh báo giá`,"alerts.close":`Đóng cảnh báo`,"alerts.value":`Giá trị`,"alerts.note":`Ghi chú (không bắt buộc)`,"alerts.add":`Thêm cảnh báo`,"alerts.empty":`Chưa có cảnh báo nào. Thêm ở trên, hoặc bấm chuột phải vào biểu đồ.`,"alerts.delete":`Xoá cảnh báo`,"alerts.triggered":`đã kích hoạt`,"alerts.source.price":`Giá`,"alerts.condition.crossing":`Cắt qua`,"alerts.condition.crossingUp":`Cắt lên`,"alerts.condition.crossingDown":`Cắt xuống`,"alerts.condition.greaterThan":`Lớn hơn`,"alerts.condition.lessThan":`Nhỏ hơn`,"alerts.fired":`Cảnh báo: {text}`,"objects.title":`Đối tượng`,"objects.close":`Đóng danh sách đối tượng`,"objects.indicators":`Chỉ báo`,"objects.drawings":`Hình vẽ`,"objects.compare":`So sánh`,"objects.addCompare":`Thêm mã để so sánh`,"objects.noIndicators":`Chưa có chỉ báo`,"objects.noDrawings":`Chưa có hình vẽ`,"objects.noComparisons":`Chưa so sánh mã nào`,"objects.indicatorSettings":`Cài đặt chỉ báo`,"objects.removeIndicator":`Xoá chỉ báo`,"objects.removeDrawing":`Xoá hình vẽ`,"objects.removeComparison":`Bỏ so sánh`,"drawing.cursor":`Con trỏ`,"drawing.cursors":`Con trỏ`,"drawing.cursorCross":`Chữ thập`,"drawing.cursorDot":`Chấm`,"drawing.cursorArrow":`Mũi tên`,"drawing.tools":`Công cụ vẽ`,"drawing.style":`Kiểu vẽ & mẫu`,"drawing.magnet":`Hút vào giá`,"drawing.magnetOn":`Đang hút vào giá`,"drawing.magnetOff":`Tắt hút vào giá`,"drawing.eraser":`Tẩy: nhấp vào hình vẽ để xoá`,"drawing.zoomArea":`Phóng to: kéo một khung để phóng to vùng đó`,"drawing.magnetStrong":`Nam châm: mạnh (luôn bắt điểm)`,"drawing.stay":`Giữ công cụ vẽ`,"drawing.undo":`Hoàn tác`,"drawing.redo":`Làm lại`,"drawing.clearAll":`Xoá tất cả`,"clear.drawings":`Xoá các hình vẽ`,"clear.indicators":`Xoá các chỉ báo`,"clear.all":`Xoá hình vẽ và chỉ báo`,"drawing.pinFavorite":`Ghim vào mục yêu thích`,"drawing.lockAll":`Khoá mọi hình vẽ`,"drawing.hideAll":`Ẩn mọi hình vẽ`,"drawingStyle.title":`Kiểu vẽ`,"drawingStyle.templates":`Mẫu`,"drawingStyle.templateName":`Tên mẫu`,"drawingStyle.noTemplates":`Chưa lưu mẫu nào`,"drawingStyle.deleteTemplate":`Xoá mẫu`,"drawingSettings.tab.style":`Kiểu`,"drawingSettings.tab.text":`Chữ`,"drawingSettings.tab.coordinates":`Toạ độ`,"drawingSettings.tab.visibility":`Hiển thị`,"drawingSettings.ok":`OK`,"drawingSettings.color":`Màu`,"drawingSettings.lineWidth":`Độ dày nét`,"drawingSettings.lineStyle":`Kiểu nét`,"drawingSettings.lineStyle.solid":`Liền`,"drawingSettings.lineStyle.dashed":`Gạch`,"drawingSettings.lineStyle.dotted":`Chấm`,"drawingSettings.line":`Đường`,"drawingSettings.lineEnds":`Hai đầu đường`,"drawingSettings.lineEnd.normal":`Thường`,"drawingSettings.lineEnd.arrow":`Mũi tên`,"drawingSettings.fill":`Màu nền`,"drawingSettings.opacity":`Độ đậm (%)`,"drawingSettings.text":`Chữ`,"drawingSettings.fontSize":`Cỡ chữ`,"drawingSettings.bold":`Đậm`,"drawingSettings.italic":`Nghiêng`,"drawingSettings.textColor":`Màu chữ`,"drawingSettings.align":`Căn lề`,"drawingSettings.align.left":`Trái`,"drawingSettings.align.center":`Giữa`,"drawingSettings.align.right":`Phải`,"drawingSettings.vAlign":`Căn dọc`,"drawingSettings.vAlign.top":`Trên`,"drawingSettings.vAlign.middle":`Giữa`,"drawingSettings.vAlign.bottom":`Dưới`,"drawingSettings.background":`Nền`,"drawingSettings.backgroundColor":`Màu nền`,"drawingSettings.border":`Viền`,"drawingSettings.borderColor":`Màu viền`,"drawingSettings.wrap":`Xuống dòng`,"drawingSettings.wrapWidth":`Độ rộng xuống dòng`,"drawingSettings.saveDefault":`Lưu làm mặc định`,"drawingSettings.defaultSaved":`Đã lưu làm mặc định cho {name}`,"drawingSettings.saveTemplateTitle":`Lưu mẫu hình vẽ`,"drawingSettings.levelVisible":`Hiện mức này`,"drawingSettings.levelValue":`Mức`,"drawingSettings.levelColor":`Màu của mức`,"drawingSettings.removeLevel":`Xoá mức`,"drawingSettings.addLevel":`Thêm mức`,"drawingSettings.point":`Điểm {n}`,"drawingSettings.date":`Ngày`,"drawingSettings.time":`Giờ`,"drawingSettings.price":`Giá`,"drawingOption.extendLeft":`Kéo dài sang trái`,"drawingOption.extendRight":`Kéo dài sang phải`,"drawingOption.middleLine":`Đường giữa`,"drawingOption.levels":`Các mức`,"drawingOption.reversed":`Đảo chiều`,"drawingOption.showLevels":`Hiện mức`,"drawingOption.showPrices":`Hiện giá`,"drawingOption.labelPosition":`Vị trí nhãn`,"drawingOption.labelPosition.left":`Trái`,"drawingOption.labelPosition.right":`Phải`,"drawingOption.background":`Tô nền`,"drawingOption.leftEnd":`Đầu trái`,"drawingOption.rightEnd":`Đầu phải`,"drawingOption.showMiddlePoint":`Hiện điểm giữa`,"drawingOption.showPriceRange":`Hiện khoảng giá`,"drawingOption.showBarsRange":`Hiện số nến`,"drawingOption.showDateRange":`Hiện khoảng ngày giờ`,"drawingOption.showAngle":`Hiện góc`,"drawingOption.alwaysShowStats":`Luôn hiện thống kê`,"drawingOption.statsPosition":`Vị trí thống kê`,"drawingOption.statsPosition.left":`Trái`,"drawingOption.statsPosition.center":`Giữa`,"drawingOption.statsPosition.right":`Phải`,"drawingOption.showPrice":`Hiện giá`,"drawingOption.showTime":`Hiện thời gian`,"drawingOption.trendLine":`Trend line`,"drawingOption.oneColor":`Dùng một màu`,"objects.drawingSettings":`Cài đặt hình vẽ`,"objects.renameGroup":`Đổi tên nhóm`,"objects.renameDrawing":`Đổi tên hình vẽ`,"objects.cloneDrawing":`Nhân bản`,"objects.dragToReorder":`Kéo để sắp xếp`,"drawingMenu.label":`Hình vẽ`,"drawingMenu.settings":`Cài đặt…`,"drawingMenu.front":`Đưa lên trên cùng`,"drawingMenu.forward":`Đưa lên một lớp`,"drawingMenu.backward":`Đưa xuống một lớp`,"drawingMenu.back":`Đưa xuống dưới cùng`,"drawingMenu.group":`Nhóm lại`,"drawingMenu.ungroup":`Bỏ nhóm`,"drawingMenu.duplicate":`Nhân bản`,"drawingMenu.delete":`Xoá`,"selectionToolbar.label":`Thanh công cụ hình vẽ`,"chartMenu.label":`Biểu đồ`,"chartMenu.addAlert":`Thêm cảnh báo tại {price}`,"chartMenu.alertAdded":`Đã thêm cảnh báo tại {price}`,"chartMenu.buyLimit":`Mua giới hạn tại {price}`,"chartMenu.buyStop":`Mua stop tại {price}`,"chartMenu.sellLimit":`Bán giới hạn tại {price}`,"chartMenu.sellStop":`Bán stop tại {price}`,"chartMenu.horizontalLine":`Đường ngang tại {price}`,"chartMenu.resetView":`Đặt lại khung nhìn`,"chartMenu.resetTimeScale":`Đặt lại trục thời gian`,"chartMenu.hideDrawings":`Ẩn hình vẽ`,"chartMenu.showDrawings":`Hiện hình vẽ`,"chartMenu.removeDrawings":`Xoá hình vẽ`,"chartMenu.settings":`Cài đặt…`,"chartMenu.invertScale":`Đảo ngược thang`,"chartMenu.goToDate":`Đi tới ngày…`,"toolbar.account":`Tài khoản: vị thế, lệnh, lịch sử`,"chartMenu.orderTicket":`Lệnh mới…`,"account.title":`Tài khoản`,"account.positions":`Vị thế`,"account.orders":`Lệnh`,"account.history":`Lịch sử`,"account.newOrder":`Lệnh mới`,"account.close":`Đóng bảng tài khoản`,"account.side":`Chiều`,"account.long":`Mua`,"account.short":`Bán`,"account.quantity":`KL`,"account.entry":`Giá vào`,"account.price":`Giá`,"account.pnl":`Lãi/lỗ`,"account.stopLoss":`SL`,"account.takeProfit":`TP`,"account.actions":`Thao tác`,"account.time":`Thời gian`,"account.reason":`Lý do`,"account.reason.order":`Lệnh`,"account.reason.close":`Đóng`,"account.reason.reverse":`Đảo chiều`,"account.reason.stopLoss":`Cắt lỗ`,"account.reason.takeProfit":`Chốt lời`,"account.reverse":`Đảo chiều vị thế`,"account.closePosition":`Đóng vị thế`,"account.cancelOrder":`Huỷ lệnh`,"account.noPositions":`Không có vị thế mở`,"account.noOrders":`Không có lệnh chờ`,"account.noFills":`Chưa có giao dịch`,"account.realised":`Lãi/lỗ đã chốt: {pnl}`,"account.openPnl":`Lãi/lỗ đang mở: {pnl}`,"ticket.title":`Lệnh mới`,"ticket.buy":`Mua`,"ticket.sell":`Bán`,"ticket.type":`Loại lệnh`,"ticket.type.market":`Thị trường`,"ticket.type.limit":`Giới hạn`,"ticket.type.stop":`Stop`,"ticket.quantity":`Khối lượng`,"ticket.price":`Giá`,"ticket.stopLoss":`Cắt lỗ`,"ticket.takeProfit":`Chốt lời`,"ticket.trailing":`Trailing stop`,"ticket.timeInForce":`Hiệu lực`,"ticket.tif.gtc":`Đến khi huỷ`,"ticket.tif.day":`Trong ngày`,"ticket.value":`Giá trị`,"ticket.risk":`Rủi ro`,"ticket.reward":`Lợi nhuận`,"ticket.rewardRisk":`Lời trên lỗ`,"ticket.stepDown":`Giảm {field}`,"ticket.stepUp":`Tăng {field}`,"ticket.placeBuy":`Đặt lệnh mua`,"ticket.placeSell":`Đặt lệnh bán`,"ticket.sent":`Đã gửi lệnh: {side} {quantity} @ {price}`,"ticket.problem.quantity":`Nhập khối lượng lớn hơn 0.`,"ticket.problem.price":`Nhập giá lớn hơn 0.`,"ticket.problem.market":`Chưa có giá thị trường.`,"ticket.problem.stopLoss":`Nhập giá cắt lỗ lớn hơn 0.`,"ticket.problem.takeProfit":`Nhập giá chốt lời lớn hơn 0.`,"ticket.problem.stopLossSide":`Cắt lỗ phải dưới giá vào với lệnh mua, trên giá vào với lệnh bán.`,"ticket.problem.takeProfitSide":`Chốt lời phải trên giá vào với lệnh mua, dưới giá vào với lệnh bán.`,"ticket.problem.limitSide":`Lệnh mua giới hạn đặt dưới giá thị trường, lệnh bán giới hạn đặt trên.`,"ticket.problem.stopSide":`Lệnh mua stop đặt trên giá thị trường, lệnh bán stop đặt dưới.`,"ticket.problem.priceStep":`Giá phải đúng bước giá của thị trường.`,"ticket.problem.priceLimits":`Giá hôm nay nằm trong khoảng từ giá sàn {floor} đến giá trần {ceiling}.`,"ticket.problem.lot":`Khối lượng trên một lô phải là bội số của {lot} (lô chẵn); phần lô lẻ đặt thành lệnh riêng.`,"toolbar.layouts":`Bố cục`,"layouts.unnamed":`Chưa đặt tên`,"layouts.unsavedChanges":`có thay đổi chưa lưu`,"layouts.save":`Lưu bố cục`,"layouts.saveAs":`Lưu thành…`,"layouts.rename":`Đổi tên…`,"layouts.autoSave":`Tự động lưu`,"layouts.open":`Mở bố cục…`,"layouts.saved":`Đã lưu bố cục: {name}`,"layouts.saveFailed":`Không lưu được bố cục`,"layouts.openFailed":`Không mở được bố cục`,"layouts.deleteFailed":`Không xoá được bố cục`,"layouts.saveAsTitle":`Lưu bố cục thành`,"layouts.renameTitle":`Đổi tên bố cục`,"layouts.renameSubmit":`Đổi tên`,"layouts.copyName":`{name} (bản sao)`,"layouts.name":`Tên`,"layouts.namePlaceholder":`Bố cục của tôi`,"layouts.dialogTitle":`Các bố cục`,"layouts.search":`Tìm bố cục`,"layouts.empty":`Chưa có bố cục nào được lưu`,"layouts.noMatch":`Không có bố cục nào khớp`,"layouts.current":`Đang mở`,"layouts.updated":`lưu lúc {time}`,"layouts.renameNamed":`Đổi tên {name}`,"layouts.deleteNamed":`Xoá {name}`,"layouts.deleteConfirm":`Xoá`,"grid.bar":`Các biểu đồ`,"grid.layout":`Cách chia biểu đồ`,"grid.single":`Một biểu đồ`,"grid.layoutOption":`{count} biểu đồ, {cols} cột và {rows} hàng`,"grid.layoutCount":`{count} biểu đồ`,"grid.layoutBigLeft":`{count} biểu đồ, một biểu đồ lớn bên trái`,"grid.layoutBigRight":`{count} biểu đồ, một biểu đồ lớn bên phải`,"grid.layoutBigTop":`{count} biểu đồ, một biểu đồ lớn ở trên`,"grid.layoutBigBottom":`{count} biểu đồ, một biểu đồ lớn ở dưới`,"grid.chart":`Biểu đồ {n}`,"grid.charts":`Biểu đồ`,"grid.sync":`Đồng bộ`,"grid.sync.symbol":`Mã`,"grid.sync.interval":`Khung`,"grid.sync.crosshair":`Crosshair`,"grid.sync.time":`Thời gian`,"grid.sync.drawings":`Hình vẽ`,"grid.sync.replay":`Replay`,"grid.sync.symbol.hint":`Đổi mã trên mọi biểu đồ cùng lúc`,"grid.sync.interval.hint":`Đổi khung thời gian trên mọi biểu đồ cùng lúc`,"grid.sync.crosshair.hint":`Hiện thời điểm của crosshair trên mọi biểu đồ`,"grid.sync.time.hint":`Cuộn và phóng mọi biểu đồ tới cùng khoảng thời gian`,"grid.sync.drawings.hint":`Chép hình vẽ sang các biểu đồ cùng mã`,"grid.sync.replay.hint":`Replay mọi biểu đồ đến cùng một thời điểm`,"legend.more":`Thêm`,"legend.moveUp":`Chuyển lên pane trên`,"legend.moveDown":`Chuyển xuống pane dưới`,"legend.moveNew":`Chuyển sang pane mới`,"legend.movePrice":`Chuyển về pane giá`,"pane.moveUp":`Đưa pane lên`,"pane.moveDown":`Đưa pane xuống`,"pane.collapse":`Thu gọn pane`,"pane.expand":`Mở rộng pane`,"pane.maximize":`Phóng to pane`,"pane.restore":`Trả lại các pane`,"toolbar.indicators.templates":`Mẫu`,"templates.save":`Lưu các chỉ báo thành mẫu…`,"templates.saveTitle":`Lưu mẫu chỉ báo`,"templates.namePlaceholder":`Bộ chỉ báo của tôi`,"templates.saved":`Đã lưu mẫu: {name}`,"templates.applied":`Đã áp dụng mẫu: {name}`,"templates.delete":`Xoá mẫu {name}`,"templates.nothing":`Chưa có chỉ báo nào để lưu`,"templates.saveFailed":`Không lưu được mẫu`,"interval.title":`Đổi khung thời gian`,"interval.hint":`5, 15m, 1h, 1D — Enter để áp dụng`,"interval.invalid":`Không phải khung thời gian biểu đồ này hiển thị được`,"alerts.condition.movesUp":`Tăng`,"alerts.condition.movesDown":`Giảm`,"alerts.source":`Theo dõi`,"alerts.conditionLabel":`Điều kiện`,"alerts.compareWith":`So với`,"alerts.compare.value":`Một giá trị`,"alerts.percent":`Phần trăm`,"alerts.bars":`Số nến`,"alerts.moveSummary":`{percent}% trong {bars} nến`,"alerts.onBarClose":`Chỉ khi đóng nến`,"alerts.onClose":`khi đóng nến`,"alerts.expires":`Hết hạn`,"alerts.expires.never":`Không hết hạn`,"alerts.expires.hour":`Hết hạn sau 1 giờ`,"alerts.expires.day":`Hết hạn sau 1 ngày`,"alerts.expires.week":`Hết hạn sau 1 tuần`,"alerts.expires.month":`Hết hạn sau 30 ngày`,"alerts.until":`đến {time}`,"alerts.expired":`Đã hết hạn`,"alerts.expiredToast":`Cảnh báo đã hết hạn: {text}`,"alerts.invalid":`Các thiết lập cảnh báo này không dùng chung được`,"replay.step":`Bước`,"replay.step.bar":`Nến`,"replay.step.auto":`Tự động`,"replay.stepsFailed":`Không tải được nến khung nhỏ: replay theo từng nến`,"replay.selectBar":`Chọn nến`,"replay.rate":`{rate}×`,"replay.rateHint":`1× phát một nến mỗi giây`,"replay.playingAt":`Đang phát ở {rate}`,"replay.pausedAt":`Tạm dừng tại {time}`,"replay.endedAt":`Replay kết thúc tại {time}`,"hotkeys.replayStep":`Lùi / tiến replay một bước`,"hotkeys.replayPlay":`Phát hoặc tạm dừng replay`,"hotkeys.replayCancel":`Thôi chọn nến bắt đầu replay`,"markers.neutral":`Trung lập`,"markers.confidence":`độ tin cậy {pct}%`,"chartMenu.paneLog":`Thang logarit`,"chartMenu.paneInvert":`Đảo ngược thang`,"chartMenu.panePercent":`Thang phần trăm`,"chartMenu.exportData":`Xuất dữ liệu (CSV)`,"chartMenu.paste":`Dán`,"chartMenu.removeIndicators":`Xoá chỉ báo`,"chartMenu.resetPriceScale":`Đặt lại thang giá`,"chartMenu.labels":`Nhãn`,"chartMenu.lines":`Đường`,"chartMenu.lastPriceLine":`Đường giá cuối`,"compare.percent":`Thay đổi phần trăm, trên thang này`,"compare.ownScale":`Giá, trên thang riêng`,"compare.ownPane":`Giá, trong pane riêng`,"compare.title":`So sánh mã`,"compare.addAs":`Thêm theo kiểu`,"compare.onChart":`Trên biểu đồ`,"compare.symbols":`Các mã`,"compare.remove":`Bỏ {symbol}`,"compare.empty":`Chưa so sánh mã nào. Tìm một mã để thêm vào.`,"compare.way.spread":`Spread`,"compare.way.ratio":`Tỷ lệ`,"compare.way.correlation":`Tương quan`,"compare.way.correlationLog":`Tương quan lợi suất`,"drawingSettings.addAlert":`Thêm cảnh báo`,"drawingSettings.alertAdded":`Đã thêm cảnh báo: giá cắt {name}`,"drawingOption.rewardRatio":`Tỷ lệ lời:lỗ`,"drawingOption.accountSize":`Vốn tài khoản`,"drawingOption.riskMode":`Tính rủi ro theo`,"drawingOption.riskMode.percent":`Phần trăm vốn`,"drawingOption.riskMode.amount":`Số tiền`,"drawingOption.risk":`Rủi ro`,"drawingOption.qtyDecimals":`Số lẻ của khối lượng`,"drawingOption.showLabels":`Hiện nhãn`,"drawingOption.showText":`Hiện chữ`,"drawingOption.direction":`Hướng`,"drawingOption.direction.up":`Lên`,"drawingOption.direction.down":`Xuống`,"drawingOption.direction.left":`Trái`,"drawingOption.direction.right":`Phải`,"drawingOption.size":`Kích thước`,"drawingOption.glyph":`Biểu tượng`,"drawingOption.glyph.star":`Ngôi sao`,"drawingOption.glyph.heart":`Trái tim`,"drawingOption.glyph.check":`Dấu tích`,"drawingOption.glyph.cross":`Dấu chéo`,"drawingOption.glyph.circle":`Hình tròn`,"drawingOption.glyph.triangleUp":`Tam giác lên`,"drawingOption.glyph.triangleDown":`Tam giác xuống`,"drawingOption.glyph.bolt":`Tia sét`,"drawingOption.arrowEnd":`Mũi tên ở cuối`,"drawingOption.fullCircles":`Vẽ cả vòng tròn`,"drawingOption.clockwise":`Theo chiều kim đồng hồ`,"drawingOption.arcs":`Cung`,"drawingOption.degree":`Cấp sóng`,"drawingOption.degree.primary":`Primary`,"drawingOption.degree.intermediate":`Intermediate`,"drawingOption.degree.minor":`Minor`,"drawingOption.degree.minute":`Minute`,"drawingOption.mode":`Hiển thị dạng`,"drawingOption.mode.bars":`Nến`,"drawingOption.mode.line":`Đường`,"drawingOption.mode.highLow":`Cao-thấp`,"drawingOption.mirrored":`Đảo chiều thời gian`,"drawingOption.flipped":`Lật ngược`,"toolGroup.lines":`Đường`,"toolGroup.horizontalVertical":`Ngang/Dọc`,"toolGroup.channels":`Kênh`,"toolGroup.fibonacci":`Fibonacci`,"toolGroup.shapes":`Hình`,"toolGroup.gannPitchforks":`Gann & Pitchfork`,"toolGroup.patterns":`Mô hình`,"toolGroup.measure":`Đo`,"toolGroup.annotation":`Chú thích`,"toolGroup.forecasting":`Dự báo`,"toolGroup.brushes":`Bút vẽ`,"toolGroup.cycles":`Chu kỳ`,"toolGroup.elliott":`Sóng Elliott`,"tool.trendLine":`Trend line`,"tool.ray":`Tia`,"tool.extendedLine":`Đường kéo dài`,"tool.infoLine":`Đường thông tin`,"tool.trendAngle":`Góc xu hướng`,"tool.horizontalLine":`Đường ngang`,"tool.horizontalRay":`Tia ngang`,"tool.verticalLine":`Đường dọc`,"tool.crossLine":`Đường chữ thập`,"tool.parallelChannel":`Kênh song song`,"tool.regressionChannel":`Kênh hồi quy`,"tool.disjointChannel":`Kênh tách rời`,"tool.flatTopBottom":`Đỉnh/đáy phẳng`,"tool.fibRetracement":`Fibonacci retracement`,"tool.fibExtension":`Fibonacci mở rộng`,"tool.fibChannel":`Kênh Fibonacci`,"tool.fibTimeZones":`Vùng thời gian Fibonacci`,"tool.fibSpeedResistanceFan":`Quạt tốc độ Fibonacci`,"tool.rectangle":`Hình chữ nhật`,"tool.rotatedRectangle":`Hình chữ nhật xoay`,"tool.circle":`Hình tròn`,"tool.ellipse":`Hình elip`,"tool.triangle":`Tam giác`,"tool.pitchfork":`Pitchfork`,"tool.schiffPitchfork":`Pitchfork Schiff`,"tool.modifiedSchiffPitchfork":`Pitchfork Schiff cải tiến`,"tool.gannFan":`Quạt Gann`,"tool.gannBox":`Hộp Gann`,"tool.cyclicLines":`Đường chu kỳ`,"tool.xabcdPattern":`Mô hình XABCD`,"tool.abcdPattern":`Mô hình ABCD`,"tool.headAndShoulders":`Vai đầu vai`,"tool.elliottWave":`Sóng Elliott`,"tool.priceRange":`Khoảng giá`,"tool.dateRange":`Khoảng thời gian`,"tool.dateAndPriceRange":`Khoảng giá và thời gian`,"tool.measure":`Đo`,"tool.text":`Chữ`,"tool.priceLabel":`Nhãn giá`,"tool.arrow":`Mũi tên`,"tool.riskReward":`Vị thế Long/Short`,"tool.anchoredVWAP":`VWAP neo`,"tool.volumeProfileRange":`Hồ sơ khối lượng theo khoảng`,"tool.note":`Ghi chú`,"tool.callout":`Chú thích`,"tool.flag":`Cờ đánh dấu`,"tool.arrowMark":`Mũi tên đánh dấu`,"tool.icon":`Biểu tượng`,"tool.signpost":`Biển chỉ dẫn`,"tool.balloon":`Bóng chú thích`,"tool.anchoredText":`Chữ cố định`,"tool.anchoredNote":`Ghi chú cố định`,"tool.brush":`Bút vẽ`,"tool.highlighter":`Bút dạ quang`,"tool.path":`Đường nhiều điểm`,"tool.polyline":`Đa giác`,"tool.curve":`Đường cong`,"tool.arc":`Cung tròn`,"tool.fibCircles":`Vòng tròn Fib`,"tool.fibSpiral":`Xoắn ốc Fib`,"tool.fibArcs":`Cung kháng cự tốc độ Fib`,"tool.fibWedge":`Nêm Fib`,"tool.pitchfan":`Quạt Pitchfork`,"tool.gannSquare":`Hình vuông Gann`,"tool.elliottImpulse":`Sóng đẩy Elliott`,"tool.elliottCorrection":`Sóng điều chỉnh Elliott`,"tool.elliottTriangle":`Sóng tam giác Elliott`,"tool.elliottDoubleCombo":`Sóng kết hợp kép Elliott`,"tool.elliottTripleCombo":`Sóng kết hợp ba Elliott`,"tool.threeDrives":`Mô hình ba nhịp`,"tool.cypherPattern":`Mô hình Cypher`,"tool.timeCycles":`Chu kỳ thời gian`,"tool.sineLine":`Đường hình sin`,"tool.forecast":`Dự báo`,"tool.projection":`Phóng chiếu`,"tool.barsPattern":`Mẫu nến`,"replay.label":`Replay`,"replay.position":`Vị trí replay`,"replay.speed":`Tốc độ`,"symbolSearch.placeholder":`Tìm mã… (ví dụ BTC, ETH, AAPL)`,"symbolSearch.empty":`Không có mã nào khớp`,"symbolSearch.searching":`Đang tìm…`,"symbolSearch.failed":`Không tìm được`,"symbolSearch.title":`Tìm mã`,"symbolSearch.symbol":`Mã`,"symbolSearch.description":`Mô tả`,"symbolSearch.kinds":`Loại mã`,"symbolSearch.allKinds":`Tất cả`,"symbolSearch.type":`Loại`,"symbolSearch.exchange":`Sàn`,"symbolSearch.allExchanges":`Tất cả các sàn`,"symbolSearch.more":`Mã khác: {count}. Gõ thêm để thu hẹp danh sách.`,"symbolSearch.typeHint":`Gõ một chữ cái trên biểu đồ để mở ô tìm này`,"palette.placeholder":`Tìm chỉ báo, loại biểu đồ, công cụ…`,"palette.empty":`Không tìm thấy kết quả`,"palette.group.indicator":`Chỉ báo`,"palette.group.chartType":`Loại biểu đồ`,"palette.group.drawing":`Công cụ vẽ`,"palette.group.timeframe":`Khung thời gian`,"palette.group.action":`Thao tác`,"palette.group.recent":`Gần đây`,"action.screenshot":`Chụp màn hình`,"action.copyImage":`Sao chép ảnh biểu đồ`,"action.toggleTheme":`Đổi giao diện`,"action.settings":`Cài đặt`,"action.shareView":`Chia sẻ góc nhìn (sao chép liên kết)`,"action.autoFib":`Fibonacci tự động (sóng đang hiện)`,"action.dataWindow":`Bật/tắt cửa sổ dữ liệu`,"action.clearDrawings":`Xoá mọi hình vẽ`,"ladder.title":`DOM ladder`,"ladder.bid":`Mua`,"ladder.price":`Giá`,"ladder.ask":`Bán`,"ladder.empty":`Chưa có dữ liệu sổ lệnh`,"ladder.sellAt":`Bán @ {price}`,"ladder.buyAt":`Mua @ {price}`,"ladder.spread":`Spread {spread}`,"dataWindow.title":`Cửa sổ dữ liệu`,"dock.rail":`Các bảng`,"dock.resize":`Đổi độ rộng của bảng`,"dataWindow.hover":`Rê chuột lên biểu đồ`,"dataWindow.touch":`Chạm vào biểu đồ`,"dataWindow.open":`Mở cửa`,"dataWindow.high":`Cao`,"dataWindow.low":`Thấp`,"dataWindow.close":`Đóng cửa`,"dataWindow.change":`Thay đổi`,"dataWindow.volume":`Khối lượng`,"bracket.place":`Đặt lệnh`,"bracket.hint":`kéo các đường để chỉnh`,"bracket.longPlaced":`Đã đặt lệnh Long kèm SL/TP · {rr}R`,"bracket.shortPlaced":`Đã đặt lệnh Short kèm SL/TP · {rr}R`,"order.buyLimit":`Mua giới hạn @ {price}`,"order.sellLimit":`Bán giới hạn @ {price}`,"order.buyStop":`Mua stop @ {price}`,"order.sellStop":`Bán stop @ {price}`,"toast.pinned":`Đã ghim vào mục yêu thích`,"toast.unpinned":`Đã bỏ ghim`,"sidebar.unpinHint":`{tool} (chuột phải để bỏ ghim)`,"toast.imageCopied":`Đã sao chép ảnh biểu đồ`,"toast.imageCopyFailed":`Không sao chép được — trình duyệt không cho chép ảnh`,"toast.linkCopied":`Đã sao chép liên kết`,"toast.copyFailed":`Không sao chép được — trình duyệt không cho dùng clipboard`,"toast.compareNeedsAdapter":`So sánh cần một nguồn dữ liệu trực tiếp`,"toast.comparing":`Đang so sánh với {symbol}`,"toast.loadFailed":`không tải được`,"toast.compareNoBars":`{symbol}: không có nến ở khung {interval}`,"toast.autoFibAdded":`Đã thêm Fibonacci tự động`,"toast.noSwing":`Không thấy sóng rõ ràng trong khung nhìn`,"toast.fullscreenUnavailable":`Không mở được chế độ toàn màn hình`,"toast.fileLoaded":`Đã nạp {count} nến từ {file}`,"toast.fileSkipped":`bỏ qua {count}`,"hotkeys.commandPalette":`Bảng lệnh`,"hotkeys.symbolSearch":`Tìm mã`,"hotkeys.goToDate":`Đi tới ngày`,"hotkeys.invertScale":`Đảo ngược thang giá`,"hotkeys.showSheet":`Hiện bảng này`,"hotkeys.saveLayout":`Lưu bố cục`,"hotkeys.typeInterval":`Gõ số để đổi khung thời gian`,"hotkeys.typeSymbol":`Gõ chữ cái để tìm mã`,"hotkeys.pan":`Kéo — qua cả nến cuối, vào vùng tương lai trống`,"hotkeys.boxSelect":`Chọn các hình vẽ trong một khung`,"hotkeys.toggleSelect":`Thêm / bỏ một hình vẽ khỏi vùng chọn`,"hotkeys.measure":`Công cụ đo (số nến × chênh lệch giá)`,"hotkeys.scalePrice":`Co / giãn thang dọc`,"hotkeys.zoomTime":`Phóng to trục thời gian`,"hotkeys.autoScale":`Bật lại tự co giãn thang`,"hotkeys.fit":`Vừa khít dữ liệu`,"hotkeys.zoomCursor":`Phóng to quanh con trỏ`,"hotkeys.touchPan":`Kéo / di chuyển crosshair`,"hotkeys.pinch":`Phóng to quanh điểm giữa`,"hotkeys.longPress":`Ghim chú thích OHLC tại nến`,"hotkeys.touchAxis":`Co giãn trục giá / thời gian`,"hotkeys.panBar":`Dời một nến`,"hotkeys.zoomInOut":`Phóng to / thu nhỏ`,"hotkeys.toStart":`Về đầu dữ liệu`,"hotkeys.toEnd":`Về cuối (nến mới nhất)`,"hotkeys.undo":`Hoàn tác`,"hotkeys.redo":`Làm lại`,"hotkeys.cancelDrawing":`Huỷ hình đang vẽ`,"hotkeys.copy":`Sao chép hình vẽ đang chọn`,"hotkeys.paste":`Dán hình vẽ (vào bất kỳ biểu đồ nào trên trang)`,"key.drag":`Kéo`,"key.click":`Bấm`,"key.doubleClick":`Bấm đúp`,"key.scroll":`Cuộn`,"key.priceAxis":`Trục giá`,"key.timeAxis":`Trục thời gian`,"key.oneFinger":`1 ngón`,"key.twoFingers":`2 ngón`,"key.pinch":`Chụm`,"key.longPress":`Nhấn giữ`,"key.axisStrip":`Dải trục`,"hotkeys.drawingMenu":`Menu của hình vẽ (trên hình)`,"hotkeys.drawingSettings":`Cài đặt hình vẽ (trên hình)`,"hotkeys.group":`Nhóm các hình đang chọn`,"hotkeys.ungroup":`Bỏ nhóm các hình đang chọn`,"hotkeys.orderStep":`Đưa lên / xuống một lớp`,"hotkeys.orderEnd":`Đưa lên trên cùng / xuống dưới cùng`,"hotkeys.finishPath":`Kết thúc đường nhiều điểm hoặc đa giác`,"hotkeys.copyDrag":`Kéo ra một bản sao của hình vẽ (trên hình vẽ)`,"hotkeys.keepAngle":`Giữ đường nằm ngang, thẳng đứng hoặc 45° (khi vẽ, hoặc kéo tay nắm)`,"hotkeys.flipMagnet":`Giữ khi vẽ: bật hoặc tắt nam châm`,"hotkeys.nudge":`Di chuyển các hình vẽ đang chọn (Shift: 10 bước)`,"key.rightClick":`Chuột phải`},n=new Map([[`en`,e],[`vi`,t]]);new Proxy({},{get:(e,t)=>typeof t==`string`?n.get(t.toLowerCase()):void 0,has:(e,t)=>typeof t==`string`&&n.has(t.toLowerCase()),ownKeys:()=>[...n.keys()],getOwnPropertyDescriptor:(e,t)=>typeof t==`string`&&n.has(t.toLowerCase())?{enumerable:!0,configurable:!0,value:n.get(t.toLowerCase())}:void 0});function r(e,t){n.set(e.toLowerCase(),t)}function i(e){let t=e.toLowerCase().replace(`_`,`-`),n=[t];/^zh-(tw|hk|mo|hant)/.test(t)&&n.push(`zh-hant`);let r=t.split(`-`)[0];return r!==t&&n.push(r),(r===`no`||r===`nn`)&&n.push(`nb`),n}function a(e){for(let t of i(e)){let e=n.get(t);if(e)return e}}var o=new Set;function s(t,n){let r={...e},i=t?a(t):void 0;return i?Object.assign(r,i):t&&!n&&!o.has(t)&&(o.add(t),console.warn(`[ChartWidget] No strings for locale "${t}"; showing English. Import the language from '@tradecanvas/chart/widget/locales' and pass it as \`messages\`.`)),n&&Object.assign(r,n),r}function c(t){return n=>t[n]??e[n]??n}var l=c(e);function u(e,t){return e.replace(/\{(\w+)\}/g,(e,n)=>n in t?String(t[n]):e)}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return e}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return l}});
|
|
2
|
+
//# sourceMappingURL=i18n-BFGknfTa.cjs.map
|