@tradecanvas/chart 2.1.1 → 2.2.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 +1 -1
- package/dist/Chart.d.ts +7 -0
- package/dist/Chart.d.ts.map +1 -1
- package/dist/{LayoutSession-DHPcgkw6.js → LayoutSession-CsJe9KAx.js} +2 -2
- package/dist/LayoutSession-CsJe9KAx.js.map +1 -0
- package/dist/{LayoutSession-Ct9HkdF0.cjs → LayoutSession-CyDxiGYW.cjs} +2 -2
- package/dist/LayoutSession-CyDxiGYW.cjs.map +1 -0
- package/dist/{WidgetCommandPalette-BVctM_8J.js → WidgetCommandPalette-BflcGFjW.js} +2 -2
- package/dist/{WidgetCommandPalette-BVctM_8J.js.map → WidgetCommandPalette-BflcGFjW.js.map} +1 -1
- package/dist/{WidgetCommandPalette-JODJV4O0.cjs → WidgetCommandPalette-C09-sPec.cjs} +2 -2
- package/dist/{WidgetCommandPalette-JODJV4O0.cjs.map → WidgetCommandPalette-C09-sPec.cjs.map} +1 -1
- package/dist/{WidgetDrawingSettings-C_xRMbdJ.cjs → WidgetDrawingSettings-BkoHbGcX.cjs} +2 -2
- package/dist/{WidgetDrawingSettings-C_xRMbdJ.cjs.map → WidgetDrawingSettings-BkoHbGcX.cjs.map} +1 -1
- package/dist/{WidgetDrawingSettings-yGBoDk96.js → WidgetDrawingSettings-D7Vi1gNj.js} +2 -2
- package/dist/{WidgetDrawingSettings-yGBoDk96.js.map → WidgetDrawingSettings-D7Vi1gNj.js.map} +1 -1
- package/dist/{WidgetIndicatorPicker-Gk_Y8HzN.cjs → WidgetIndicatorPicker-BibarB3d.cjs} +2 -2
- package/dist/{WidgetIndicatorPicker-Gk_Y8HzN.cjs.map → WidgetIndicatorPicker-BibarB3d.cjs.map} +1 -1
- package/dist/{WidgetIndicatorPicker-CRT8UrQM.js → WidgetIndicatorPicker-D4P0HNoy.js} +2 -2
- package/dist/{WidgetIndicatorPicker-CRT8UrQM.js.map → WidgetIndicatorPicker-D4P0HNoy.js.map} +1 -1
- package/dist/{WidgetIndicatorSettings-DRex0IML.js → WidgetIndicatorSettings-1AfA2DUN.js} +3 -3
- package/dist/{WidgetIndicatorSettings-DRex0IML.js.map → WidgetIndicatorSettings-1AfA2DUN.js.map} +1 -1
- package/dist/{WidgetIndicatorSettings-BWcklt1J.cjs → WidgetIndicatorSettings-eiK0tyxB.cjs} +2 -2
- package/dist/{WidgetIndicatorSettings-BWcklt1J.cjs.map → WidgetIndicatorSettings-eiK0tyxB.cjs.map} +1 -1
- package/dist/{WidgetSettings-Db2q4_8R.cjs → WidgetSettings-B-shthXu.cjs} +2 -2
- package/dist/{WidgetSettings-Db2q4_8R.cjs.map → WidgetSettings-B-shthXu.cjs.map} +1 -1
- package/dist/{WidgetSettings-Dv0kLVtb.js → WidgetSettings-B7sXo5EF.js} +2 -2
- package/dist/{WidgetSettings-Dv0kLVtb.js.map → WidgetSettings-B7sXo5EF.js.map} +1 -1
- package/dist/{WidgetSymbolSearch-Bc5hW4Aa.js → WidgetSymbolSearch-SGeFCgRH.js} +2 -2
- package/dist/{WidgetSymbolSearch-Bc5hW4Aa.js.map → WidgetSymbolSearch-SGeFCgRH.js.map} +1 -1
- package/dist/{WidgetSymbolSearch-GNc3wK8z.cjs → WidgetSymbolSearch-ZBxMHQpN.cjs} +2 -2
- package/dist/{WidgetSymbolSearch-GNc3wK8z.cjs.map → WidgetSymbolSearch-ZBxMHQpN.cjs.map} +1 -1
- package/dist/{i18n-kIZPrAaD.js → i18n-BYLAXdxp.js} +3 -3
- package/dist/i18n-BYLAXdxp.js.map +1 -0
- package/dist/i18n-DODNjdbT.cjs +2 -0
- package/dist/i18n-DODNjdbT.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{indicatorInputWords-CaDGFQbW.js → indicatorInputWords-BfBQRuPS.js} +2 -2
- package/dist/{indicatorInputWords-CaDGFQbW.js.map → indicatorInputWords-BfBQRuPS.js.map} +1 -1
- package/dist/{indicatorInputWords-DeBTBHV5.cjs → indicatorInputWords-DuptaXlq.cjs} +2 -2
- package/dist/{indicatorInputWords-DeBTBHV5.cjs.map → indicatorInputWords-DuptaXlq.cjs.map} +1 -1
- package/dist/widget/index.cjs +2 -2
- package/dist/widget/index.js +9 -9
- package/dist/widget/locales/en.d.ts +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 +29 -29
- package/dist/widget/locales/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/LayoutSession-Ct9HkdF0.cjs.map +0 -1
- package/dist/LayoutSession-DHPcgkw6.js.map +0 -1
- package/dist/i18n-CMgnJ3BI.cjs +0 -2
- package/dist/i18n-CMgnJ3BI.cjs.map +0 -1
- package/dist/i18n-kIZPrAaD.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetSymbolSearch-GNc3wK8z.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-ZBxMHQpN.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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"}
|
|
@@ -413,7 +413,7 @@ var e = {
|
|
|
413
413
|
"drawingOption.extendRight": "Extend right",
|
|
414
414
|
"drawingOption.middleLine": "Middle line",
|
|
415
415
|
"drawingOption.levels": "Levels",
|
|
416
|
-
"drawingOption.
|
|
416
|
+
"drawingOption.reversed": "Reverse",
|
|
417
417
|
"drawingOption.showLevels": "Show levels",
|
|
418
418
|
"drawingOption.showPrices": "Show prices",
|
|
419
419
|
"drawingOption.labelPosition": "Labels",
|
|
@@ -1339,7 +1339,7 @@ var e = {
|
|
|
1339
1339
|
"drawingOption.extendRight": "Kéo dài sang phải",
|
|
1340
1340
|
"drawingOption.middleLine": "Đường giữa",
|
|
1341
1341
|
"drawingOption.levels": "Các mức",
|
|
1342
|
-
"drawingOption.
|
|
1342
|
+
"drawingOption.reversed": "Đảo chiều",
|
|
1343
1343
|
"drawingOption.showLevels": "Hiện mức",
|
|
1344
1344
|
"drawingOption.showPrices": "Hiện giá",
|
|
1345
1345
|
"drawingOption.labelPosition": "Vị trí nhãn",
|
|
@@ -1892,4 +1892,4 @@ function u(e, t) {
|
|
|
1892
1892
|
//#endregion
|
|
1893
1893
|
export { r as a, e as c, a as i, c as n, s as o, u as r, t as s, l as t };
|
|
1894
1894
|
|
|
1895
|
-
//# sourceMappingURL=i18n-
|
|
1895
|
+
//# sourceMappingURL=i18n-BYLAXdxp.js.map
|