@tradecanvas/chart 1.79.1 → 1.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{LayoutSession-BJxMu1Df.js → LayoutSession-CX3eWUA1.js} +3 -3
- package/dist/{LayoutSession-BJxMu1Df.js.map → LayoutSession-CX3eWUA1.js.map} +1 -1
- package/dist/{LayoutSession-2o_-ji7A.cjs → LayoutSession-DFvb5ekw.cjs} +2 -2
- package/dist/{LayoutSession-2o_-ji7A.cjs.map → LayoutSession-DFvb5ekw.cjs.map} +1 -1
- package/dist/{WidgetIndicatorSettings-C4S0QgW-.js → WidgetIndicatorSettings-BiX1ZOhS.js} +2 -2
- package/dist/{WidgetIndicatorSettings-C4S0QgW-.js.map → WidgetIndicatorSettings-BiX1ZOhS.js.map} +1 -1
- package/dist/{WidgetIndicatorSettings-C9fM5_KF.cjs → WidgetIndicatorSettings-DhFQasHT.cjs} +2 -2
- package/dist/{WidgetIndicatorSettings-C9fM5_KF.cjs.map → WidgetIndicatorSettings-DhFQasHT.cjs.map} +1 -1
- package/dist/WidgetSymbolSearch-BW7HxOMk.cjs +2 -0
- package/dist/WidgetSymbolSearch-BW7HxOMk.cjs.map +1 -0
- package/dist/{WidgetSymbolSearch-BWI3iNJM.js → WidgetSymbolSearch-CYYd9zoA.js} +3 -3
- package/dist/WidgetSymbolSearch-CYYd9zoA.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +78 -78
- package/dist/{indicatorInputWords-fiLxkWio.cjs → indicatorInputWords-BfWHWL0K.cjs} +2 -2
- package/dist/{indicatorInputWords-fiLxkWio.cjs.map → indicatorInputWords-BfWHWL0K.cjs.map} +1 -1
- package/dist/{indicatorInputWords-yN99aPYU.js → indicatorInputWords-DpkxBon4.js} +2 -2
- package/dist/{indicatorInputWords-yN99aPYU.js.map → indicatorInputWords-DpkxBon4.js.map} +1 -1
- package/dist/indicatorLabel-BQ6Kdry2.cjs +2 -0
- package/dist/indicatorLabel-BQ6Kdry2.cjs.map +1 -0
- package/dist/indicatorLabel-BsidWCv1.js +18 -0
- package/dist/indicatorLabel-BsidWCv1.js.map +1 -0
- package/dist/indicatorLabel.d.ts.map +1 -1
- package/dist/symbolCatalog-CSbI2R5e.cjs +2 -0
- package/dist/symbolCatalog-CSbI2R5e.cjs.map +1 -0
- package/dist/{symbolCatalog-CA1fpz3s.js → symbolCatalog-DffhUQSJ.js} +2 -2
- package/dist/symbolCatalog-DffhUQSJ.js.map +1 -0
- package/dist/widget/ChartWidgetGrid.d.ts.map +1 -1
- package/dist/widget/WidgetCompares.d.ts.map +1 -1
- package/dist/widget/WidgetDockArea.d.ts.map +1 -1
- package/dist/widget/WidgetLayoutsDialog.d.ts.map +1 -1
- package/dist/widget/index.cjs +3 -3
- package/dist/widget/index.cjs.map +1 -1
- package/dist/widget/index.js +852 -851
- package/dist/widget/index.js.map +1 -1
- package/dist/widget/symbolCatalog.d.ts +6 -2
- package/dist/widget/symbolCatalog.d.ts.map +1 -1
- package/dist/widget/symbolSearchFilters.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/WidgetSymbolSearch-BWI3iNJM.js.map +0 -1
- package/dist/WidgetSymbolSearch-CJeMSKtO.cjs +0 -2
- package/dist/WidgetSymbolSearch-CJeMSKtO.cjs.map +0 -1
- package/dist/indicatorLabel-BNZvaOcl.cjs +0 -2
- package/dist/indicatorLabel-BNZvaOcl.cjs.map +0 -1
- package/dist/indicatorLabel-Sbegyhp2.js +0 -17
- package/dist/indicatorLabel-Sbegyhp2.js.map +0 -1
- package/dist/symbolCatalog-CA1fpz3s.js.map +0 -1
- package/dist/symbolCatalog-Dx1YnZgm.cjs +0 -2
- package/dist/symbolCatalog-Dx1YnZgm.cjs.map +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { SymbolInfo } from '@tradecanvas/commons';
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The longest symbol the widget keeps, in a watchlist or a list of symbols:
|
|
4
|
+
* room for a venue, a DEX pool's address (a Uniswap v4 pool's is 66
|
|
5
|
+
* characters) and its pair. 64 dropped such pools from the lists unseen.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MAX_SYMBOL_LENGTH = 160;
|
|
4
8
|
/** A ticker trimmed, or null when it isn't one a list can keep. */
|
|
5
9
|
export declare function cleanSymbol(v: unknown): string | null;
|
|
6
10
|
/** The symbols on offer: their tickers in order, and the info given with some. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symbolCatalog.d.ts","sourceRoot":"","sources":["../../src/widget/symbolCatalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD
|
|
1
|
+
{"version":3,"file":"symbolCatalog.d.ts","sourceRoot":"","sources":["../../src/widget/symbolCatalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAUrC,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAIrD;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAc7D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAIxE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,EAAE,CAK9D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAMzH;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAI5E;AAKD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,CAKnG;AAED,6GAA6G;AAC7G,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAE9E;AAID;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,GAAE,MAAM,GAAG,SAAsB,GAAG,MAAM,GAAG,IAAI,CAa1F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symbolSearchFilters.d.ts","sourceRoot":"","sources":["../../src/widget/symbolSearchFilters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAW5C;;;;;;;GAOG;AACH,qBAAa,mBAAmB;IAkBlB,OAAO,CAAC,QAAQ,CAAC,CAAC;IAAc,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAjBrE,mEAAmE;IACnE,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,QAAQ,CAAuB;IACvC,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAE/C;;;;;OAKG;gBAC0B,CAAC,EAAE,UAAU,EAAmB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI;IAuBjG,0FAA0F;IAC1F,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,UAAU,GAAG,SAAS,CAAC,EAAE,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"symbolSearchFilters.d.ts","sourceRoot":"","sources":["../../src/widget/symbolSearchFilters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAW5C;;;;;;;GAOG;AACH,qBAAa,mBAAmB;IAkBlB,OAAO,CAAC,QAAQ,CAAC,CAAC;IAAc,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAjBrE,mEAAmE;IACnE,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,QAAQ,CAAuB;IACvC,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAE/C;;;;;OAKG;gBAC0B,CAAC,EAAE,UAAU,EAAmB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI;IAuBjG,0FAA0F;IAC1F,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,UAAU,GAAG,SAAS,CAAC,EAAE,GAAG,IAAI;IAiBrD,2DAA2D;IAC3D,OAAO,IAAI,IAAI;IAIf;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;IAShB;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;IAI/D,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,eAAe;CAYxB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradecanvas/chart",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.80.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "High-performance canvas trading chart with built-in indicators, drawing tools, and real-time streaming. Zero external dependencies.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"README.md"
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@tradecanvas/commons": "1.
|
|
67
|
-
"@tradecanvas/core": "1.
|
|
66
|
+
"@tradecanvas/commons": "1.80.0",
|
|
67
|
+
"@tradecanvas/core": "1.80.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"jsdom": "^25.0.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetSymbolSearch-BWI3iNJM.js","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.kinds.size !== kindsBefore) this.renderKinds();\n if (this.exchanges.size !== exchangesBefore) this.renderExchanges();\n this.kindsEl.hidden = this.kinds.size === 0;\n this.exchangeEl.hidden = this.exchanges.size < MIN_EXCHANGES;\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,2 +0,0 @@
|
|
|
1
|
-
const e=require(`./widgetFeatures-BZ8OVPN2.cjs`),t=require(`./focusTrap-DrhhmG0V.cjs`),n=require(`./symbolCatalog-Dx1YnZgm.cjs`),r=require(`./i18n-hMXCt55K.cjs`);var i=20,a=1e3,o=new Set,s={stock:1,stocks:1,equity:1,share:1,shares:1,"cổ phiếu":1,株式:1,株:1,股票:1,주식:1,futures:2,future:2,derivative:2,derivatives:2,option:2,options:2,"phái sinh":2,先物:2,期货:2,期貨:2,선물:2,warrant:2,warrants:2,"covered warrant":2,"chứng quyền":2,index:3,indices:3,"chỉ số":3,指数:3,指數:3,지수:3,fund:3,funds:3,etf:3,etfs:3,"chứng chỉ quỹ":3,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,暗号資産:4,仮想通貨:4,加密货币:4,加密貨幣:4,数字货币:4,數字貨幣:4,虚拟货币:4,虛擬貨幣:4,암호화폐:4,가상자산:4,가상화폐:4,forex:5,fx:5,currency:5,"ngoại hối":5,為替:5,外国為替:5,外汇:5,外匯:5,외환:5,bond:5,bonds:5,"trái phiếu":5},c=1e3,l=new Map,u=/i\u0307|\u0131/g;function d(e){if(typeof e!=`string`)return``;let t=l.get(e);return t===void 0&&(t=e.trim().normalize(`NFC`).toLowerCase().replace(u,`i`),l.size>=c&&l.clear(),l.set(e,t)),t}function f(e){let t=d(e);return t?Object.prototype.hasOwnProperty.call(s,t)?s[t]:1+p(t)%5:0}function p(e){let t=2166136261;for(let n of e)t^=n.codePointAt(0),t=Math.imul(t,16777619)>>>0;return t}function m(e){let{ticker:t}=n.c(e),r=/[\p{L}\p{N}]/u.exec(t);return r?r[0].toUpperCase():``}function h(e,t){let r=n.i(t?.logo);if(!r||o.has(r))return _(e,t);let a=document.createElement(`img`);return a.className=`tcw-ss-badge`,a.alt=``,a.width=i,a.height=i,a.draggable=!1,a.setAttribute(`aria-hidden`,`true`),a.setAttribute(`loading`,`lazy`),a.setAttribute(`decoding`,`async`),a.setAttribute(`referrerpolicy`,`no-referrer`),a.addEventListener(`error`,()=>{g(r),a.replaceWith(_(e,t))},{once:!0}),a.src=r,a}function g(e){o.size>=a&&o.clear(),o.add(e)}function _(e,t){let n=document.createElement(`span`);return n.className=`tcw-ss-badge`,n.setAttribute(`aria-hidden`,`true`),n.dataset.kind=String(f(t?.type)),n.textContent=m(e),n}var v=180,y=150,b=50;function x(e){return{upper:e.toUpperCase(),folded:T(e)}}function S(e,t,n){return Math.max(C(e.toUpperCase(),n.upper),w(t,n.folded))}function C(e,t){if(e===t)return 1e3;if(e.startsWith(t))return 500+(50-Math.min(50,e.length-t.length));if(e.includes(t))return 200+(50-Math.min(50,e.length-t.length));let n=0,r=-1,i=0;for(let a=0;a<e.length&&n<t.length;a++)e[a]===t[n]&&(r>=0&&(i+=a-r-1),r=a,n++);return n<t.length?0:100-Math.min(99,i)}function w(e,t){if(!e||!t)return 0;if(e.startsWith(t))return v;let n=e.indexOf(t);return n<0?0:y-Math.min(b,n)}function T(e){return e.normalize(`NFD`).replace(/\p{M}+/gu,``).replace(/[đĐ]/g,`D`).toUpperCase()}function E(e,t,r=0,i=e.length){let a=t?D(e.toUpperCase(),t):new Set,o=``;for(let t=r;t<i;t++)o+=a.has(t)?`<mark>${n.u(e[t])}</mark>`:n.u(e[t]);return o}function D(e,t){let n=new Set,r=e.indexOf(t);if(r>=0){for(let e=r;e<r+t.length;e++)n.add(e);return n}let i=0;for(let r=0;r<e.length&&i<t.length;r++)e[r]===t[i]&&(n.add(r),i++);return i<t.length?new Set:n}function O(e,t){let r=document.createElement(`span`);r.className=`tcw-ss-symbol`,r.dir=`ltr`;let{venue:i}=n.c(e);i&&(r.title=e);let a=document.createElement(`span`);return a.className=`tcw-ss-ticker`,a.innerHTML=E(e,t,i.length,e.length),r.appendChild(a),r}function k(e){return n.c(e).venue.replace(/:$/,``).replace(/:/g,` `)}var A=d,j=2,M=1,N=class{el;kindsEl;exchangeEl;kinds=new Map;exchanges=new Map;kind=null;exchange=null;resize;constructor(e,t){this.t=e,this.onChange=t,this.el=document.createElement(`div`),this.el.className=`tcw-ss-filters`,this.el.hidden=!0,this.kindsEl=document.createElement(`div`),this.kindsEl.className=`tcw-ss-kinds`,this.kindsEl.setAttribute(`role`,`group`),this.kindsEl.setAttribute(`aria-label`,e(`symbolSearch.kinds`)),this.kindsEl.hidden=!0,this.kindsEl.addEventListener(`scroll`,()=>this.markMore(),{passive:!0}),this.resize=typeof ResizeObserver>`u`?null:new ResizeObserver(()=>this.markMore()),this.resize?.observe(this.kindsEl),this.exchangeEl=document.createElement(`select`),this.exchangeEl.className=`tcw-settings-select tcw-ss-exchange-filter`,this.exchangeEl.setAttribute(`aria-label`,e(`symbolSearch.exchange`)),this.exchangeEl.hidden=!0,this.exchangeEl.addEventListener(`change`,()=>{this.exchange=A(this.exchangeEl.value)||null,this.onChange(!1)}),this.el.append(this.kindsEl,this.exchangeEl)}see(e){let t=this.kinds.size,n=this.exchanges.size;for(let t of e)P(this.kinds,t?.type),P(this.exchanges,t?.exchange);this.kinds.size!==t&&this.renderKinds(),this.exchanges.size!==n&&this.renderExchanges(),this.kindsEl.hidden=this.kinds.size===0,this.exchangeEl.hidden=this.exchanges.size<j,this.el.hidden=this.kindsEl.hidden&&this.exchangeEl.hidden,this.markMore()}destroy(){this.resize?.disconnect()}markMore(){let e=this.kindsEl,t=Math.abs(e.scrollLeft),n=e.scrollWidth-e.clientWidth,r=[t>M?`start`:``,t<n-M?`end`:``].filter(Boolean);r.length>0?e.dataset.more=r.join(` `):delete e.dataset.more}passes(e,t){return F(this.kind,A(e?.type),t)&&F(this.exchange,A(e?.exchange),t)}renderKinds(){let e=(e,t)=>{let n=document.createElement(`button`);return n.type=`button`,n.className=`tcw-ss-type`,n.textContent=t,e!==null&&(n.dir=`auto`),n.setAttribute(`aria-pressed`,String(this.kind===e)),n.addEventListener(`click`,()=>{this.kind=e;for(let e of this.kindsEl.children)e.setAttribute(`aria-pressed`,String(e===n));this.onChange(!0)}),n},t=[...this.kinds].map(([t,n])=>e(t,n));this.kindsEl.replaceChildren(e(null,this.t(`symbolSearch.allKinds`)),...t)}renderExchanges(){let e=(e,t)=>{let n=document.createElement(`option`);return n.value=e,n.textContent=t,e&&(n.dir=`auto`),n},t=[...this.exchanges.values()].map(t=>e(t,t));this.exchangeEl.replaceChildren(e(``,this.t(`symbolSearch.allExchanges`)),...t),this.exchangeEl.value=this.exchange===null?``:this.exchanges.get(this.exchange)??``}};function P(e,t){let n=A(t);n&&!e.has(n)&&e.set(n,t.trim())}function F(e,t,n){return e===null||t===e||n&&t===``}var I=150,L=229,R=200,z=1;function B(e){return e.info?.exchange?.trim()||k(e.symbol)}function V(e){let t=B(e);return t?{...e.info??{symbol:e.symbol},exchange:t}:e.info}var H=class{backdrop=null;input=null;list=null;results=null;live=null;columns=null;filters=null;mode=null;given=[];rows=[];rowsGiven=!1;shown=[];compared=[];way=``;selectedIndex=0;searchFn=null;searchTimer=null;searchAbort=null;status=`idle`;pickOnResults=!1;heard=new Map;givenInfo=new Map;foldedNames=new Map;returnFocus=null;uid=z++;constructor(e,t=()=>document.body,n=r.t){this.callbacks=e,this.host=t,this.t=n}open(e){this.begin({kind:`search`,start:e})}openCompare(e){this.compared=e.compared,this.way=e.ways.some(t=>t.id===e.way)?e.way:e.ways[0]?.id??``,this.begin({kind:`compare`,start:e})}setCompared(e){if(this.compared=e,this.mode?.kind!==`compare`||!this.results)return;let t=this.results.contains(document.activeElement);this.renderList(),t&&!this.results.contains(document.activeElement)&&this.input?.focus()}close(){if(!this.backdrop)return;this.cancelSearch(),this.pickOnResults=!1,this.searchFn=null,this.backdrop.remove(),this.backdrop=this.input=this.list=this.results=this.live=this.columns=null,this.filters?.destroy(),this.filters=null,this.mode=null,this.callbacks.onClose();let e=this.returnFocus;this.returnFocus=null,e?.isConnected&&e.focus()}isOpen(){return this.backdrop!==null}isComparing(){return this.mode?.kind===`compare`}destroy(){this.close()}begin(e){this.backdrop&&this.close(),this.returnFocus=document.activeElement instanceof HTMLElement?document.activeElement:null,this.mode=e,this.searchFn=e.start.search??null,this.status=`idle`,this.learnGiven(e),this.build(e);let t=e.kind===`search`?e.start.query??``:``;this.input&&(this.input.value=t),t?(this.filter(),this.searchFn&&this.searchNow()):this.listGiven(),this.input?.focus()}learnGiven(e){let t=n.o(e.start.symbols);this.given=t.names,this.givenInfo=t.info,this.foldedNames=new Map;let r=e.start.current,i=e.kind===`search`?e.start.currentInfo:null;if(i&&typeof i==`object`&&r){let{description:e,logo:t}=n.r(i,r);this.givenInfo.set(r,n.a({symbol:r,description:e,logo:t},this.givenInfo.get(r)))}}infoOf(e){return n.a(this.givenInfo.get(e),this.heard.get(e))}build(t){let n=document.createElement(`div`);n.className=`tcw-modal-backdrop`;let r=!1;n.addEventListener(`pointerdown`,e=>{r=e.target===n}),n.addEventListener(`click`,e=>{e.target===n&&r&&this.close(),r=!1});let i=document.createElement(`div`);i.className=`tcw-modal tcw-ss${t.kind===`compare`?` tcw-ss--compare`:``}`,e.o(i,t.kind===`compare`?`compare`:`symbolSearch`),i.setAttribute(`role`,`dialog`),i.setAttribute(`aria-modal`,`true`),i.setAttribute(`aria-labelledby`,`tcw-ss-title-${this.uid}`),i.tabIndex=-1,i.addEventListener(`keydown`,e=>this.onKeyDown(e,i)),i.append(this.buildHeader(t),this.buildSearch()),t.kind===`compare`&&i.appendChild(this.buildWay(t.start));let a=new N(this.t,e=>{this.afterResults(!1),e&&this.input?.focus()}),o=document.createElement(`div`);o.className=`tcw-ss-list`,o.id=`tcw-ss-list-${this.uid}`,o.setAttribute(`role`,`listbox`);let s=document.createElement(`div`);s.className=`tcw-ss-results`;let c=this.buildColumns();s.append(c,o),s.addEventListener(`mousedown`,e=>{e.target.closest(`button`)||e.preventDefault()});let l=document.createElement(`div`);l.className=`tcw-ss-live tcw-visually-hidden`,l.setAttribute(`aria-live`,`polite`),i.append(a.el,s,l);let u=t.kind===`search`?t.start.hint:void 0;if(u){let e=document.createElement(`div`);e.className=`tcw-ss-foot`,e.textContent=u,i.appendChild(e)}n.appendChild(i),this.host().appendChild(n),this.backdrop=n,this.filters=a,this.columns=c,this.list=o,this.results=s,this.live=l}buildColumns(){let e=document.createElement(`div`);e.className=`tcw-ss-columns`,e.setAttribute(`aria-hidden`,`true`);let t=[``,this.t(`symbolSearch.symbol`),this.t(`symbolSearch.description`),this.t(`symbolSearch.type`),this.t(`symbolSearch.exchange`)];return e.append(...t.map(e=>this.tag(`tcw-ss-head`,e))),e}buildHeader(e){let n=document.createElement(`div`);n.className=`tcw-modal-header`;let r=document.createElement(`h3`);r.id=`tcw-ss-title-${this.uid}`,r.textContent=this.t(e.kind===`compare`?`compare.title`:`symbolSearch.title`);let i=document.createElement(`button`);return i.type=`button`,i.className=`tcw-modal-close`,i.setAttribute(`aria-label`,this.t(`common.close`)),i.innerHTML=t.i(`x`,16),i.addEventListener(`click`,()=>this.close()),n.append(r,i),n}buildSearch(){let e=document.createElement(`label`);e.className=`tcw-ss-search`,e.innerHTML=t.i(`search`,16);let n=document.createElement(`input`);return n.type=`search`,n.className=`tcw-ss-input`,n.placeholder=this.t(`symbolSearch.placeholder`),n.setAttribute(`aria-label`,this.t(`symbolSearch.placeholder`)),n.setAttribute(`role`,`combobox`),n.setAttribute(`aria-expanded`,`true`),n.setAttribute(`aria-controls`,`tcw-ss-list-${this.uid}`),n.setAttribute(`aria-autocomplete`,`list`),n.autocomplete=`off`,n.spellcheck=!1,n.addEventListener(`input`,()=>this.filter()),e.appendChild(n),this.input=n,e}buildWay(e){let t=document.createElement(`label`);t.className=`tcw-ss-way`;let n=document.createElement(`span`);n.textContent=this.t(`compare.addAs`);let r=document.createElement(`select`);r.className=`tcw-settings-select tcw-ss-way-select`;for(let t of e.ways){let e=document.createElement(`option`);e.value=t.id,e.textContent=t.label,r.appendChild(e)}return r.value=this.way,r.addEventListener(`change`,()=>{this.way=r.value}),t.append(n,r),t}listGiven(){this.rows=this.given.map(e=>({symbol:e,score:0,info:this.infoOf(e)})),this.rowsGiven=!0,this.afterResults(!0)}filter(){this.pickOnResults=!1;let e=this.input?.value.trim()??``;if(this.searchFn&&e){this.scheduleSearch(e);return}if(this.cancelSearch(),this.status=`idle`,!e){this.listGiven();return}let t=x(e);this.rows=this.given.map(e=>{let n=this.infoOf(e);return{symbol:e,score:S(e,this.foldedName(n?.description),t),info:n}}).filter(e=>e.score>0).sort((e,t)=>t.score-e.score),this.rowsGiven=!0,this.afterResults(!1)}foldedName(e){if(!e)return``;let t=this.foldedNames.get(e);return t===void 0&&(t=T(e),this.foldedNames.set(e,t)),t}scheduleSearch(e){this.searchTimer!==null&&clearTimeout(this.searchTimer),this.searchTimer=setTimeout(()=>{this.searchTimer=null,this.runSearch(e)},150)}searchNow(){this.searchTimer!==null&&clearTimeout(this.searchTimer),this.searchTimer=null,this.runSearch(this.input?.value.trim()??``)}async runSearch(e){let t=this.searchFn;if(!t)return;this.searchAbort?.abort();let r=new AbortController;this.searchAbort=r,this.status=`searching`,this.shown.length===0&&this.renderList();try{let i=await t(e,r.signal);if(r.signal.aborted)return;let a=n.s(i);for(let e of a)this.heard.set(e.symbol,n.a(this.heard.get(e.symbol),e));this.rows=a.map(e=>({symbol:e.symbol,score:0,info:this.infoOf(e.symbol)})),this.status=`idle`}catch{if(r.signal.aborted)return;this.rows=[],this.status=`failed`}this.rowsGiven=!1,this.afterResults(!1),this.pickOnResults&&(this.pickOnResults=!1,this.pick(0))}cancelSearch(){this.searchTimer!==null&&clearTimeout(this.searchTimer),this.searchTimer=null,this.searchAbort?.abort(),this.searchAbort=null}afterResults(e){let t=this.listableRows();this.filters?.see(t.map(V)),this.shown=this.visibleRows(t);let n=e?this.shown.findIndex(e=>e.symbol===this.mode?.start.current):-1;this.selectedIndex=n>=0&&n<this.listedCount()?n:0,this.renderList()}listedCount(){return Math.min(this.shown.length,200)}listableRows(){if(this.mode?.kind!==`compare`)return this.rows;let e=this.mode.start.current;return this.rows.filter(t=>t.symbol!==e)}visibleRows(e){let t=this.filters;return t?e.filter(e=>t.passes(V(e),this.rowsGiven)):[...e]}renderList(){let e=this.list,t=this.results;if(!e||!t)return;let n=(this.input?.value??``).trim(),r=this.mode?.kind===`compare`,i=[];if(r&&!n&&this.compared.length>0){let t=this.heading(this.t(`compare.onChart`),`added`),n=document.createElement(`div`);n.className=`tcw-ss-added`,n.setAttribute(`role`,`group`),n.setAttribute(`aria-labelledby`,t.id),n.append(...this.compared.map(e=>this.addedRow(e)));let r=this.heading(this.t(`compare.symbols`),`symbols`);r.hidden=this.shown.length===0,i.push(t,n,r),e.setAttribute(`aria-labelledby`,r.id)}else e.setAttribute(`aria-labelledby`,`tcw-ss-title-${this.uid}`);let a=n.toUpperCase(),o=new Set(this.compared.map(e=>e.symbol)),s=this.shown.slice(0,200);e.replaceChildren(...s.map((e,t)=>this.row(e,t,a,r&&o.has(e.symbol))));let c=this.shown.length===0?this.emptyNote(n,r):this.moreNote(this.shown.length-s.length);t.replaceChildren(...this.columns?[this.columns]:[],...i,e,...c?[c]:[]),this.live&&(this.live.textContent=this.shown.length===0?c?.textContent??``:``),this.markSelection()}emptyNote(e,t){let n;if(this.status===`searching`)n=this.t(`symbolSearch.searching`);else if(this.status===`failed`)n=this.t(`symbolSearch.failed`);else if(t&&!e){if(this.compared.length>0)return null;n=this.t(`compare.empty`)}else n=this.t(`symbolSearch.empty`);let r=document.createElement(`div`);return r.className=`tcw-ss-empty`,r.textContent=n,r}moreNote(e){if(e<=0)return null;let t=document.createElement(`div`);return t.className=`tcw-ss-more`,t.textContent=r.r(this.t(`symbolSearch.more`),{count:e}),t}heading(e,t){let n=document.createElement(`div`);return n.className=`tcw-ss-heading`,n.id=`tcw-ss-${t}-${this.uid}`,n.textContent=e,n}row(e,n,r,i){let a=document.createElement(`div`);a.className=`tcw-ss-row`,a.id=`tcw-ss-opt-${this.uid}-${n}`,a.dataset.symbol=e.symbol,a.setAttribute(`role`,`option`);let o=this.mode?.kind===`search`&&e.symbol===this.mode.start.current;a.classList.toggle(`tcw-current`,o),a.classList.toggle(`tcw-on-chart`,i),o&&a.setAttribute(`aria-current`,`true`);let s=this.tag(`tcw-ss-kind`,e.info?.type?.trim()??``),c=this.tag(`tcw-ss-exchange`,B(e));if(s.dir=c.dir=`auto`,a.append(h(e.symbol,e.info),O(e.symbol,r),this.descriptionCell(e),s,c),o||i){let e=document.createElement(`span`);e.className=`tcw-ss-check`,e.innerHTML=t.i(`check`,14),a.appendChild(e)}return i&&a.appendChild(this.tag(`tcw-visually-hidden`,this.t(`compare.onChart`))),a.addEventListener(`click`,()=>this.pick(n)),a.addEventListener(`mousemove`,()=>{this.selectedIndex!==n&&(this.selectedIndex=n,this.markSelection())}),a}descriptionCell(e){let t=document.createElement(`span`);t.className=`tcw-ss-desc-cell`;let n=e.info?.description&&e.info.description!==e.symbol?e.info.description:``;if(n){let e=this.tag(`tcw-ss-desc`,n);e.dir=`auto`,t.appendChild(e)}return t}addedRow(e){let n=document.createElement(`div`);if(n.className=`tcw-ss-added-row`,n.setAttribute(`role`,`presentation`),e.color){let t=document.createElement(`span`);t.className=`tcw-ss-swatch`,t.style.background=e.color,n.appendChild(t)}let i=document.createElement(`span`);i.className=`tcw-ss-way-cell`,i.appendChild(this.tag(`tcw-ss-way-label`,e.way));let a=document.createElement(`button`);a.type=`button`,a.className=`tcw-ss-remove`;let o=r.r(this.t(`compare.remove`),{symbol:e.symbol});return a.title=o,a.setAttribute(`aria-label`,o),a.innerHTML=t.i(`x`,12),a.addEventListener(`click`,()=>{this.mode?.kind===`compare`&&this.mode.start.onRemove(e.id)}),n.append(O(e.symbol,``),i,a),n}tag(e,t){let n=document.createElement(`span`);return n.className=e,n.textContent=t,n}markSelection(){let e=this.list?.querySelectorAll(`.tcw-ss-row`)??[],t=null;e.forEach((e,n)=>{let r=n===this.selectedIndex;e.setAttribute(`aria-selected`,String(r)),e.classList.toggle(`tcw-selected`,r),r&&(t=e)});let n=t;n?(this.input?.setAttribute(`aria-activedescendant`,n.id),n.scrollIntoView?.({block:`nearest`})):this.input?.removeAttribute(`aria-activedescendant`)}pick(e){let t=this.shown[e],n=this.mode;if(!t||!n)return;if(n.kind===`compare`){this.selectedIndex=e,this.markSelection(),n.start.onAdd(t.symbol,this.way);return}let r=n.start.onPick??this.callbacks.onPick;this.close(),r(t.symbol)}onKeyDown(e,n){if(e.stopPropagation(),e.isComposing||e.keyCode===L)return;if(e.key===`Escape`){e.preventDefault(),this.close();return}if(t.n(e,n),e.target!==this.input)return;let r=this.listedCount();if(e.key===`ArrowDown`||e.key===`ArrowUp`){if(e.preventDefault(),r===0)return;this.selectedIndex=(this.selectedIndex+(e.key===`ArrowDown`?1:-1)+r)%r,this.markSelection()}else if(e.key===`Enter`){if(e.preventDefault(),e.repeat&&this.mode?.kind===`compare`)return;if(this.searchTimer!==null||this.status===`searching`){this.pickOnResults=!0,this.searchTimer!==null&&this.searchNow();return}this.pick(this.selectedIndex)}}};exports.MAX_LISTED_ROWS=R,exports.SYMBOL_SEARCH_DEBOUNCE_MS=I,exports.WidgetSymbolSearch=H;
|
|
2
|
-
//# sourceMappingURL=WidgetSymbolSearch-CJeMSKtO.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetSymbolSearch-CJeMSKtO.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.kinds.size !== kindsBefore) this.renderKinds();\n if (this.exchanges.size !== exchangesBefore) this.renderExchanges();\n this.kindsEl.hidden = this.kinds.size === 0;\n this.exchangeEl.hidden = this.exchanges.size < MIN_EXCHANGES;\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,2 +0,0 @@
|
|
|
1
|
-
var e=3,t={stochrsi:4,ichimoku:4};function n(n,r,i,a){let o=Object.keys(i||r).map(e=>r[e]).filter(e=>typeof e==`number`&&Number.isFinite(e)).slice(0,t[n]??e).map(e=>String(Number(e.toFixed(4)))),s=typeof r.symbol==`string`&&r.symbol?[r.symbol]:[];return[n===`spread`&&r.mode===`ratio`?`Ratio`:a??n.toUpperCase(),...s,...o].join(` `)}Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return n}});
|
|
2
|
-
//# sourceMappingURL=indicatorLabel-BNZvaOcl.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"indicatorLabel-BNZvaOcl.cjs","names":[],"sources":["../src/indicatorLabel.ts"],"sourcesContent":["/** How many numeric parameters a label shows, so a long list stays short. */\nconst LABEL_NUMBERS = 3;\n\n/**\n * Indicators whose label shows more than that, because every one of the\n * lengths named tells two instances apart: Stochastic RSI's %D smoothing is\n * its fourth, and without it a 3/3 and a 3/5 read the same; Ichimoku's\n * displacement is its fourth.\n */\nconst LABEL_NUMBERS_BY_ID: Readonly<Record<string, number>> = { stochrsi: 4, ichimoku: 4 };\n\n/**\n * \"EMA 20\", \"BB 20 2\", \"MACD 12 26 9\": the short name (`shortName`, else the\n * id in capitals) plus up to three numeric parameters (four for Stochastic\n * RSI and Ichimoku), in the indicator's own order, so two instances of the same indicator\n * can be told apart.\n */\nexport function indicatorChipLabel(\n id: string,\n params: Record<string, unknown>,\n defaults?: Record<string, unknown>,\n shortName?: string,\n): string {\n const order = defaults ? Object.keys(defaults) : Object.keys(params);\n const numbers = order\n .map((k) => params[k])\n .filter((v): v is number => typeof v === 'number' && Number.isFinite(v))\n .slice(0, LABEL_NUMBERS_BY_ID[id] ?? LABEL_NUMBERS)\n .map((v) => String(Number(v.toFixed(4))));\n // An indicator on another symbol is named after it (\"Compare ETHUSDT\", \"Ratio ETHUSDT\").\n const symbol = typeof params.symbol === 'string' && params.symbol ? [params.symbol] : [];\n const name = id === 'spread' && params.mode === 'ratio' ? 'Ratio' : shortName ?? id.toUpperCase();\n return [name, ...symbol, ...numbers].join(' ');\n}\n"],"mappings":"AACA,IAAM,EAAgB,EAQhB,EAAwD,CAAE,SAAU,EAAG,SAAU,EAAG,CAQ1F,SAAgB,EACd,EACA,EACA,EACA,EACQ,CAER,IAAM,EADmB,OAAO,KAAlB,GAA+C,EAAO,CAEjE,IAAK,GAAM,EAAO,GAAG,CACrB,OAAQ,GAAmB,OAAO,GAAM,UAAY,OAAO,SAAS,EAAE,CAAC,CACvE,MAAM,EAAG,EAAoB,IAAO,EAAc,CAClD,IAAK,GAAM,OAAO,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAErC,EAAS,OAAO,EAAO,QAAW,UAAY,EAAO,OAAS,CAAC,EAAO,OAAO,CAAG,EAAE,CAExF,MAAO,CADM,IAAO,UAAY,EAAO,OAAS,QAAU,QAAU,GAAa,EAAG,aAAa,CACnF,GAAG,EAAQ,GAAG,EAAQ,CAAC,KAAK,IAAI"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
//#region src/indicatorLabel.ts
|
|
2
|
-
var e = 3, t = {
|
|
3
|
-
stochrsi: 4,
|
|
4
|
-
ichimoku: 4
|
|
5
|
-
};
|
|
6
|
-
function n(n, r, i, a) {
|
|
7
|
-
let o = Object.keys(i || r).map((e) => r[e]).filter((e) => typeof e == "number" && Number.isFinite(e)).slice(0, t[n] ?? e).map((e) => String(Number(e.toFixed(4)))), s = typeof r.symbol == "string" && r.symbol ? [r.symbol] : [];
|
|
8
|
-
return [
|
|
9
|
-
n === "spread" && r.mode === "ratio" ? "Ratio" : a ?? n.toUpperCase(),
|
|
10
|
-
...s,
|
|
11
|
-
...o
|
|
12
|
-
].join(" ");
|
|
13
|
-
}
|
|
14
|
-
//#endregion
|
|
15
|
-
export { n as t };
|
|
16
|
-
|
|
17
|
-
//# sourceMappingURL=indicatorLabel-Sbegyhp2.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"indicatorLabel-Sbegyhp2.js","names":[],"sources":["../src/indicatorLabel.ts"],"sourcesContent":["/** How many numeric parameters a label shows, so a long list stays short. */\nconst LABEL_NUMBERS = 3;\n\n/**\n * Indicators whose label shows more than that, because every one of the\n * lengths named tells two instances apart: Stochastic RSI's %D smoothing is\n * its fourth, and without it a 3/3 and a 3/5 read the same; Ichimoku's\n * displacement is its fourth.\n */\nconst LABEL_NUMBERS_BY_ID: Readonly<Record<string, number>> = { stochrsi: 4, ichimoku: 4 };\n\n/**\n * \"EMA 20\", \"BB 20 2\", \"MACD 12 26 9\": the short name (`shortName`, else the\n * id in capitals) plus up to three numeric parameters (four for Stochastic\n * RSI and Ichimoku), in the indicator's own order, so two instances of the same indicator\n * can be told apart.\n */\nexport function indicatorChipLabel(\n id: string,\n params: Record<string, unknown>,\n defaults?: Record<string, unknown>,\n shortName?: string,\n): string {\n const order = defaults ? Object.keys(defaults) : Object.keys(params);\n const numbers = order\n .map((k) => params[k])\n .filter((v): v is number => typeof v === 'number' && Number.isFinite(v))\n .slice(0, LABEL_NUMBERS_BY_ID[id] ?? LABEL_NUMBERS)\n .map((v) => String(Number(v.toFixed(4))));\n // An indicator on another symbol is named after it (\"Compare ETHUSDT\", \"Ratio ETHUSDT\").\n const symbol = typeof params.symbol === 'string' && params.symbol ? [params.symbol] : [];\n const name = id === 'spread' && params.mode === 'ratio' ? 'Ratio' : shortName ?? id.toUpperCase();\n return [name, ...symbol, ...numbers].join(' ');\n}\n"],"mappings":";AACA,IAAM,IAAgB,GAQhB,IAAwD;CAAE,UAAU;CAAG,UAAU;CAAG;AAQ1F,SAAgB,EACd,GACA,GACA,GACA,GACQ;CAER,IAAM,IADmB,OAAO,KAAlB,KAA+C,EAAO,CAEjE,KAAK,MAAM,EAAO,GAAG,CACrB,QAAQ,MAAmB,OAAO,KAAM,YAAY,OAAO,SAAS,EAAE,CAAC,CACvE,MAAM,GAAG,EAAoB,MAAO,EAAc,CAClD,KAAK,MAAM,OAAO,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAErC,IAAS,OAAO,EAAO,UAAW,YAAY,EAAO,SAAS,CAAC,EAAO,OAAO,GAAG,EAAE;AAExF,QAAO;EADM,MAAO,YAAY,EAAO,SAAS,UAAU,UAAU,KAAa,EAAG,aAAa;EACnF,GAAG;EAAQ,GAAG;EAAQ,CAAC,KAAK,IAAI"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"symbolCatalog-CA1fpz3s.js","names":[],"sources":["../src/widget/escapeHtml.ts","../src/widget/symbolCatalog.ts"],"sourcesContent":["/** Text made safe to put in HTML markup, attribute values included. */\nexport function escapeHtml(text: string): string {\n return text.replace(/[&<>\"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' })[c]!);\n}\n","import type { SymbolInfo } from '@tradecanvas/commons';\n\n/** The longest ticker the widget keeps, in a watchlist or a list of symbols. */\nexport const MAX_SYMBOL_LENGTH = 64;\n/** The longest logo address taken, and the longest image written in a `data:` one. */\nconst MAX_LOGO_URL = 2048;\nconst MAX_LOGO_DATA = 65_536;\n/** The images a `data:` logo may be; a page written in `data:` is none of them. */\nconst DATA_IMAGE = /^data:image\\/(png|jpeg|gif|webp|avif|svg\\+xml)[;,]/i;\nconst LOGO_PROTOCOLS = new Set(['https:', 'http:', 'blob:']);\n/** The fields a row writes as words, or loads (the logo): anything but text there says nothing. */\nconst TEXT_FIELDS: ReadonlySet<string> = new Set(['description', 'exchange', 'type', 'logo']);\n\n/** A ticker trimmed, or null when it isn't one a list can keep. */\nexport function cleanSymbol(v: unknown): string | null {\n if (typeof v !== 'string') return null;\n const symbol = v.trim();\n return symbol && symbol.length <= MAX_SYMBOL_LENGTH ? symbol : null;\n}\n\n/** The symbols on offer: their tickers in order, and the info given with some. */\nexport interface SymbolCatalog {\n names: string[];\n info: Map<string, SymbolInfo>;\n}\n\n/**\n * Symbols from untrusted input (a host's `symbols`, plain JavaScript): a\n * ticker, or a SymbolInfo with one. Tickers are cleaned as a watchlist cleans\n * them, so both key a symbol alike; a repeat keeps the first; anything else is\n * skipped, and anything but a list is no symbols.\n */\nexport function readSymbolCatalog(raw: unknown): SymbolCatalog {\n const names: string[] = [];\n const info = new Map<string, SymbolInfo>();\n if (!Array.isArray(raw)) return { names, info };\n const seen = new Set<string>();\n for (const entry of raw) {\n const isInfo = typeof entry === 'object' && entry !== null;\n const symbol = cleanSymbol(isInfo ? (entry as { symbol?: unknown }).symbol : entry);\n if (!symbol || seen.has(symbol)) continue;\n seen.add(symbol);\n names.push(symbol);\n if (isInfo) info.set(symbol, cleanSymbolInfo(entry, symbol));\n }\n return { names, info };\n}\n\n/**\n * A copy of `info` named `symbol`, its words kept only where they are text:\n * a JSON feed's numeric exchange code, a null or a blank says nothing, so it\n * neither shows nor erases what is known. A \"__proto__\" key, which JSON.parse\n * keeps as a field, is dropped: copied onto another object by assignment, it\n * would hand that object fields no one cleaned.\n */\nexport function cleanSymbolInfo(info: object, symbol: string): SymbolInfo {\n const isWords = ([key, value]: [string, unknown]) =>\n key !== '__proto__' && (!TEXT_FIELDS.has(key) || (typeof value === 'string' && value.trim() !== ''));\n return { ...Object.fromEntries(Object.entries(info).filter(isWords)), symbol } as SymbolInfo;\n}\n\n/**\n * A feed's search results, from untrusted input: those that name a symbol,\n * in order, their words cleaned as the symbols given are; anything but a list\n * is none.\n */\nexport function readSymbolResults(found: unknown): SymbolInfo[] {\n if (!Array.isArray(found)) return [];\n const named = (info: unknown): info is SymbolInfo =>\n typeof info === 'object' && info !== null && typeof (info as { symbol?: unknown }).symbol === 'string' && (info as SymbolInfo).symbol !== '';\n return found.filter(named).map((info) => cleanSymbolInfo(info, info.symbol));\n}\n\n/**\n * `over`'s fields where it has them, `base`'s for the rest: a field left\n * undefined or null (as a JSON feed writes one it has no value for) never\n * erases one. Neither is changed.\n */\nexport function mergeSymbolInfo(base: SymbolInfo | undefined, over: SymbolInfo | null | undefined): SymbolInfo | undefined {\n if (!over) return base;\n if (!base) return over;\n // Defined, not assigned: a \"__proto__\" field is copied as a field and never swaps the copy's prototype.\n const given = Object.entries(over).filter(([, value]) => value !== undefined && value !== null);\n return Object.fromEntries([...Object.entries(base), ...given]) as unknown as SymbolInfo;\n}\n\n/**\n * A symbol's venue, up to its last colon (\"BINANCE:\"), and the ticker after\n * it (\"BTCUSDT\"). No venue when there is no colon, or one at either end: the\n * ticker would be left with nothing.\n */\nexport function splitVenue(symbol: string): { venue: string; ticker: string } {\n const at = symbol.lastIndexOf(':');\n if (at <= 0 || at === symbol.length - 1) return { venue: '', ticker: symbol };\n return { venue: symbol.slice(0, at + 1), ticker: symbol.slice(at + 1) };\n}\n\n/** A ticker without its venue (\"KBS:VCB\" is VCB), in upper case. */\nconst bareTicker = (symbol: string): string => splitVenue(symbol).ticker.toUpperCase();\n\n/**\n * What the chart knows (`getSymbolInfo()`), when it is of `symbol`: in any\n * case, or with a venue before one of the two and not the other. Null when it\n * is still another symbol's, as it stays with no feed to replace it.\n */\nexport function chartInfoFor(symbol: string, info: SymbolInfo | null | undefined): SymbolInfo | null {\n if (!info || typeof info.symbol !== 'string') return null;\n if (info.symbol.toUpperCase() === symbol.toUpperCase()) return info;\n const oneHasVenue = info.symbol.includes(':') !== symbol.includes(':');\n return oneHasVenue && bareTicker(info.symbol) === bareTicker(symbol) ? info : null;\n}\n\n/** A symbol's name and exchange on a line, for a tooltip (\"Vinamilk · HOSE\"); null when neither is known. */\nexport function symbolTitle(info: SymbolInfo | null | undefined): string | null {\n return [info?.description, info?.exchange].filter(Boolean).join(' · ') || null;\n}\n\nconst pageBase = (): string | undefined => (typeof document === 'undefined' ? undefined : document.baseURI);\n\n/**\n * The address to load a logo from, or null for one it must not load. It is\n * only ever an image's `src`, so markup in it is a path that finds nothing;\n * the scheme still has to be one for images, which keeps out `javascript:`\n * and pages written in `data:`.\n */\nexport function logoSrc(raw: unknown, base: string | undefined = pageBase()): string | null {\n if (typeof raw !== 'string') return null;\n const text = raw.trim();\n // One longer than any taken is refused before it is parsed: a row's badge is made again on every keystroke.\n if (!text || text.length > MAX_LOGO_DATA) return null;\n let url: URL;\n try {\n url = new URL(text, base);\n } catch {\n return null;\n }\n if (url.protocol === 'data:') return DATA_IMAGE.test(url.href) && url.href.length <= MAX_LOGO_DATA ? url.href : null;\n return LOGO_PROTOCOLS.has(url.protocol) && url.href.length <= MAX_LOGO_URL ? url.href : null;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
function e(e){return e.replace(/[&<>"']/g,e=>({"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`})[e])}var t=2048,n=65536,r=/^data:image\/(png|jpeg|gif|webp|avif|svg\+xml)[;,]/i,i=new Set([`https:`,`http:`,`blob:`]),a=new Set([`description`,`exchange`,`type`,`logo`]);function o(e){if(typeof e!=`string`)return null;let t=e.trim();return t&&t.length<=64?t:null}function s(e){let t=[],n=new Map;if(!Array.isArray(e))return{names:t,info:n};let r=new Set;for(let i of e){let e=typeof i==`object`&&!!i,a=o(e?i.symbol:i);!a||r.has(a)||(r.add(a),t.push(a),e&&n.set(a,c(i,a)))}return{names:t,info:n}}function c(e,t){return{...Object.fromEntries(Object.entries(e).filter(([e,t])=>e!==`__proto__`&&(!a.has(e)||typeof t==`string`&&t.trim()!==``))),symbol:t}}function l(e){return Array.isArray(e)?e.filter(e=>typeof e==`object`&&!!e&&typeof e.symbol==`string`&&e.symbol!==``).map(e=>c(e,e.symbol)):[]}function u(e,t){if(!t)return e;if(!e)return t;let n=Object.entries(t).filter(([,e])=>e!=null);return Object.fromEntries([...Object.entries(e),...n])}function d(e){let t=e.lastIndexOf(`:`);return t<=0||t===e.length-1?{venue:``,ticker:e}:{venue:e.slice(0,t+1),ticker:e.slice(t+1)}}var f=e=>d(e).ticker.toUpperCase();function p(e,t){return!t||typeof t.symbol!=`string`?null:t.symbol.toUpperCase()===e.toUpperCase()||t.symbol.includes(`:`)!==e.includes(`:`)&&f(t.symbol)===f(e)?t:null}function m(e){return[e?.description,e?.exchange].filter(Boolean).join(` · `)||null}var h=()=>typeof document>`u`?void 0:document.baseURI;function g(e,a=h()){if(typeof e!=`string`)return null;let o=e.trim();if(!o||o.length>n)return null;let s;try{s=new URL(o,a)}catch{return null}return s.protocol===`data:`?r.test(s.href)&&s.href.length<=n?s.href:null:i.has(s.protocol)&&s.href.length<=t?s.href:null}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return e}});
|
|
2
|
-
//# sourceMappingURL=symbolCatalog-Dx1YnZgm.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"symbolCatalog-Dx1YnZgm.cjs","names":[],"sources":["../src/widget/escapeHtml.ts","../src/widget/symbolCatalog.ts"],"sourcesContent":["/** Text made safe to put in HTML markup, attribute values included. */\nexport function escapeHtml(text: string): string {\n return text.replace(/[&<>\"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' })[c]!);\n}\n","import type { SymbolInfo } from '@tradecanvas/commons';\n\n/** The longest ticker the widget keeps, in a watchlist or a list of symbols. */\nexport const MAX_SYMBOL_LENGTH = 64;\n/** The longest logo address taken, and the longest image written in a `data:` one. */\nconst MAX_LOGO_URL = 2048;\nconst MAX_LOGO_DATA = 65_536;\n/** The images a `data:` logo may be; a page written in `data:` is none of them. */\nconst DATA_IMAGE = /^data:image\\/(png|jpeg|gif|webp|avif|svg\\+xml)[;,]/i;\nconst LOGO_PROTOCOLS = new Set(['https:', 'http:', 'blob:']);\n/** The fields a row writes as words, or loads (the logo): anything but text there says nothing. */\nconst TEXT_FIELDS: ReadonlySet<string> = new Set(['description', 'exchange', 'type', 'logo']);\n\n/** A ticker trimmed, or null when it isn't one a list can keep. */\nexport function cleanSymbol(v: unknown): string | null {\n if (typeof v !== 'string') return null;\n const symbol = v.trim();\n return symbol && symbol.length <= MAX_SYMBOL_LENGTH ? symbol : null;\n}\n\n/** The symbols on offer: their tickers in order, and the info given with some. */\nexport interface SymbolCatalog {\n names: string[];\n info: Map<string, SymbolInfo>;\n}\n\n/**\n * Symbols from untrusted input (a host's `symbols`, plain JavaScript): a\n * ticker, or a SymbolInfo with one. Tickers are cleaned as a watchlist cleans\n * them, so both key a symbol alike; a repeat keeps the first; anything else is\n * skipped, and anything but a list is no symbols.\n */\nexport function readSymbolCatalog(raw: unknown): SymbolCatalog {\n const names: string[] = [];\n const info = new Map<string, SymbolInfo>();\n if (!Array.isArray(raw)) return { names, info };\n const seen = new Set<string>();\n for (const entry of raw) {\n const isInfo = typeof entry === 'object' && entry !== null;\n const symbol = cleanSymbol(isInfo ? (entry as { symbol?: unknown }).symbol : entry);\n if (!symbol || seen.has(symbol)) continue;\n seen.add(symbol);\n names.push(symbol);\n if (isInfo) info.set(symbol, cleanSymbolInfo(entry, symbol));\n }\n return { names, info };\n}\n\n/**\n * A copy of `info` named `symbol`, its words kept only where they are text:\n * a JSON feed's numeric exchange code, a null or a blank says nothing, so it\n * neither shows nor erases what is known. A \"__proto__\" key, which JSON.parse\n * keeps as a field, is dropped: copied onto another object by assignment, it\n * would hand that object fields no one cleaned.\n */\nexport function cleanSymbolInfo(info: object, symbol: string): SymbolInfo {\n const isWords = ([key, value]: [string, unknown]) =>\n key !== '__proto__' && (!TEXT_FIELDS.has(key) || (typeof value === 'string' && value.trim() !== ''));\n return { ...Object.fromEntries(Object.entries(info).filter(isWords)), symbol } as SymbolInfo;\n}\n\n/**\n * A feed's search results, from untrusted input: those that name a symbol,\n * in order, their words cleaned as the symbols given are; anything but a list\n * is none.\n */\nexport function readSymbolResults(found: unknown): SymbolInfo[] {\n if (!Array.isArray(found)) return [];\n const named = (info: unknown): info is SymbolInfo =>\n typeof info === 'object' && info !== null && typeof (info as { symbol?: unknown }).symbol === 'string' && (info as SymbolInfo).symbol !== '';\n return found.filter(named).map((info) => cleanSymbolInfo(info, info.symbol));\n}\n\n/**\n * `over`'s fields where it has them, `base`'s for the rest: a field left\n * undefined or null (as a JSON feed writes one it has no value for) never\n * erases one. Neither is changed.\n */\nexport function mergeSymbolInfo(base: SymbolInfo | undefined, over: SymbolInfo | null | undefined): SymbolInfo | undefined {\n if (!over) return base;\n if (!base) return over;\n // Defined, not assigned: a \"__proto__\" field is copied as a field and never swaps the copy's prototype.\n const given = Object.entries(over).filter(([, value]) => value !== undefined && value !== null);\n return Object.fromEntries([...Object.entries(base), ...given]) as unknown as SymbolInfo;\n}\n\n/**\n * A symbol's venue, up to its last colon (\"BINANCE:\"), and the ticker after\n * it (\"BTCUSDT\"). No venue when there is no colon, or one at either end: the\n * ticker would be left with nothing.\n */\nexport function splitVenue(symbol: string): { venue: string; ticker: string } {\n const at = symbol.lastIndexOf(':');\n if (at <= 0 || at === symbol.length - 1) return { venue: '', ticker: symbol };\n return { venue: symbol.slice(0, at + 1), ticker: symbol.slice(at + 1) };\n}\n\n/** A ticker without its venue (\"KBS:VCB\" is VCB), in upper case. */\nconst bareTicker = (symbol: string): string => splitVenue(symbol).ticker.toUpperCase();\n\n/**\n * What the chart knows (`getSymbolInfo()`), when it is of `symbol`: in any\n * case, or with a venue before one of the two and not the other. Null when it\n * is still another symbol's, as it stays with no feed to replace it.\n */\nexport function chartInfoFor(symbol: string, info: SymbolInfo | null | undefined): SymbolInfo | null {\n if (!info || typeof info.symbol !== 'string') return null;\n if (info.symbol.toUpperCase() === symbol.toUpperCase()) return info;\n const oneHasVenue = info.symbol.includes(':') !== symbol.includes(':');\n return oneHasVenue && bareTicker(info.symbol) === bareTicker(symbol) ? info : null;\n}\n\n/** A symbol's name and exchange on a line, for a tooltip (\"Vinamilk · HOSE\"); null when neither is known. */\nexport function symbolTitle(info: SymbolInfo | null | undefined): string | null {\n return [info?.description, info?.exchange].filter(Boolean).join(' · ') || null;\n}\n\nconst pageBase = (): string | undefined => (typeof document === 'undefined' ? undefined : document.baseURI);\n\n/**\n * The address to load a logo from, or null for one it must not load. It is\n * only ever an image's `src`, so markup in it is a path that finds nothing;\n * the scheme still has to be one for images, which keeps out `javascript:`\n * and pages written in `data:`.\n */\nexport function logoSrc(raw: unknown, base: string | undefined = pageBase()): string | null {\n if (typeof raw !== 'string') return null;\n const text = raw.trim();\n // One longer than any taken is refused before it is parsed: a row's badge is made again on every keystroke.\n if (!text || text.length > MAX_LOGO_DATA) return null;\n let url: URL;\n try {\n url = new URL(text, base);\n } catch {\n return null;\n }\n if (url.protocol === 'data:') return DATA_IMAGE.test(url.href) && url.href.length <= MAX_LOGO_DATA ? url.href : null;\n return LOGO_PROTOCOLS.has(url.protocol) && url.href.length <= MAX_LOGO_URL ? url.href : null;\n}\n"],"mappings":"AACA,SAAgB,EAAW,EAAsB,CAC/C,OAAO,EAAK,QAAQ,WAAa,IAAO,CAAE,IAAK,QAAS,IAAK,OAAQ,IAAK,OAAQ,IAAK,SAAU,IAAK,QAAS,EAAE,GAAI,CCGvH,IAAM,EAAe,KACf,EAAgB,MAEhB,EAAa,sDACb,EAAiB,IAAI,IAAI,CAAC,SAAU,QAAS,QAAQ,CAAC,CAEtD,EAAmC,IAAI,IAAI,CAAC,cAAe,WAAY,OAAQ,OAAO,CAAC,CAG7F,SAAgB,EAAY,EAA2B,CACrD,GAAI,OAAO,GAAM,SAAU,OAAO,KAClC,IAAM,EAAS,EAAE,MAAM,CACvB,OAAO,GAAU,EAAO,QAAA,GAA8B,EAAS,KAejE,SAAgB,EAAkB,EAA6B,CAC7D,IAAM,EAAkB,EAAE,CACpB,EAAO,IAAI,IACjB,GAAI,CAAC,MAAM,QAAQ,EAAI,CAAE,MAAO,CAAE,QAAO,OAAM,CAC/C,IAAM,EAAO,IAAI,IACjB,IAAK,IAAM,KAAS,EAAK,CACvB,IAAM,EAAS,OAAO,GAAU,YAAY,EACtC,EAAS,EAAY,EAAU,EAA+B,OAAS,EAAM,CAC/E,CAAC,GAAU,EAAK,IAAI,EAAO,GAC/B,EAAK,IAAI,EAAO,CAChB,EAAM,KAAK,EAAO,CACd,GAAQ,EAAK,IAAI,EAAQ,EAAgB,EAAO,EAAO,CAAC,EAE9D,MAAO,CAAE,QAAO,OAAM,CAUxB,SAAgB,EAAgB,EAAc,EAA4B,CAGxE,MAAO,CAAE,GAAG,OAAO,YAAY,OAAO,QAAQ,EAAK,CAAC,QAFnC,CAAC,EAAK,KACrB,IAAQ,cAAgB,CAAC,EAAY,IAAI,EAAI,EAAK,OAAO,GAAU,UAAY,EAAM,MAAM,GAAK,IAC/B,CAAC,CAAE,SAAQ,CAQhF,SAAgB,EAAkB,EAA8B,CAI9D,OAHK,MAAM,QAAQ,EAAM,CAGlB,EAAM,OAFE,GACb,OAAO,GAAS,YAAY,GAAiB,OAAQ,EAA8B,QAAW,UAAa,EAAoB,SAAW,GAClH,CAAC,IAAK,GAAS,EAAgB,EAAM,EAAK,OAAO,CAAC,CAH1C,EAAE,CAWtC,SAAgB,EAAgB,EAA8B,EAA6D,CACzH,GAAI,CAAC,EAAM,OAAO,EAClB,GAAI,CAAC,EAAM,OAAO,EAElB,IAAM,EAAQ,OAAO,QAAQ,EAAK,CAAC,QAAQ,EAAG,KAAW,GAAiC,KAAK,CAC/F,OAAO,OAAO,YAAY,CAAC,GAAG,OAAO,QAAQ,EAAK,CAAE,GAAG,EAAM,CAAC,CAQhE,SAAgB,EAAW,EAAmD,CAC5E,IAAM,EAAK,EAAO,YAAY,IAAI,CAElC,OADI,GAAM,GAAK,IAAO,EAAO,OAAS,EAAU,CAAE,MAAO,GAAI,OAAQ,EAAQ,CACtE,CAAE,MAAO,EAAO,MAAM,EAAG,EAAK,EAAE,CAAE,OAAQ,EAAO,MAAM,EAAK,EAAE,CAAE,CAIzE,IAAM,EAAc,GAA2B,EAAW,EAAO,CAAC,OAAO,aAAa,CAOtF,SAAgB,EAAa,EAAgB,EAAwD,CAInG,MAHI,CAAC,GAAQ,OAAO,EAAK,QAAW,SAAiB,KACjD,EAAK,OAAO,aAAa,GAAK,EAAO,aAAa,EAClC,EAAK,OAAO,SAAS,IAAI,GAAK,EAAO,SAAS,IAAI,EAChD,EAAW,EAAK,OAAO,GAAK,EAAW,EAAO,CAFL,EAEe,KAIhF,SAAgB,EAAY,EAAoD,CAC9E,MAAO,CAAC,GAAM,YAAa,GAAM,SAAS,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM,EAAI,KAG5E,IAAM,MAAsC,OAAO,SAAa,IAAc,IAAA,GAAY,SAAS,QAQnG,SAAgB,EAAQ,EAAc,EAA2B,GAAU,CAAiB,CAC1F,GAAI,OAAO,GAAQ,SAAU,OAAO,KACpC,IAAM,EAAO,EAAI,MAAM,CAEvB,GAAI,CAAC,GAAQ,EAAK,OAAS,EAAe,OAAO,KACjD,IAAI,EACJ,GAAI,CACF,EAAM,IAAI,IAAI,EAAM,EAAK,MACnB,CACN,OAAO,KAGT,OADI,EAAI,WAAa,QAAgB,EAAW,KAAK,EAAI,KAAK,EAAI,EAAI,KAAK,QAAU,EAAgB,EAAI,KAAO,KACzG,EAAe,IAAI,EAAI,SAAS,EAAI,EAAI,KAAK,QAAU,EAAe,EAAI,KAAO"}
|