@tradecanvas/chart 1.80.0 → 1.81.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-CX3eWUA1.js → LayoutSession-C4Lp1X6t.js} +2 -2
- package/dist/{LayoutSession-CX3eWUA1.js.map → LayoutSession-C4Lp1X6t.js.map} +1 -1
- package/dist/{LayoutSession-DFvb5ekw.cjs → LayoutSession-CeayqT91.cjs} +2 -2
- package/dist/{LayoutSession-DFvb5ekw.cjs.map → LayoutSession-CeayqT91.cjs.map} +1 -1
- package/dist/{WidgetSymbolSearch-BW7HxOMk.cjs → WidgetSymbolSearch-DeBxGj4a.cjs} +2 -2
- package/dist/{WidgetSymbolSearch-BW7HxOMk.cjs.map → WidgetSymbolSearch-DeBxGj4a.cjs.map} +1 -1
- package/dist/{WidgetSymbolSearch-CYYd9zoA.js → WidgetSymbolSearch-WOskcBJN.js} +24 -24
- package/dist/{WidgetSymbolSearch-CYYd9zoA.js.map → WidgetSymbolSearch-WOskcBJN.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{symbolCatalog-DffhUQSJ.js → symbolCatalog-B_9kDAjE.js} +30 -27
- package/dist/symbolCatalog-B_9kDAjE.js.map +1 -0
- package/dist/symbolCatalog-CXqRu23x.cjs +2 -0
- package/dist/symbolCatalog-CXqRu23x.cjs.map +1 -0
- package/dist/widget/WidgetLoadingOverlay.d.ts.map +1 -1
- package/dist/widget/index.cjs +21 -61
- package/dist/widget/index.cjs.map +1 -1
- package/dist/widget/index.js +1023 -1027
- package/dist/widget/index.js.map +1 -1
- package/dist/widget/symbolCatalog.d.ts +2 -7
- package/dist/widget/symbolCatalog.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/symbolCatalog-CSbI2R5e.cjs +0 -2
- package/dist/symbolCatalog-CSbI2R5e.cjs.map +0 -1
- package/dist/symbolCatalog-DffhUQSJ.js.map +0 -1
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import { SymbolInfo } from '@tradecanvas/commons';
|
|
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;
|
|
1
|
+
import { MAX_SYMBOL_LENGTH, SymbolInfo } from '@tradecanvas/commons';
|
|
2
|
+
export { MAX_SYMBOL_LENGTH };
|
|
8
3
|
/** A ticker trimmed, or null when it isn't one a list can keep. */
|
|
9
4
|
export declare function cleanSymbol(v: unknown): string | null;
|
|
10
5
|
/** 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,
|
|
1
|
+
{"version":3,"file":"symbolCatalog.d.ts","sourceRoot":"","sources":["../../src/widget/symbolCatalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAE1E,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAU7B,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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradecanvas/chart",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.81.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.81.0",
|
|
67
|
+
"@tradecanvas/core": "1.81.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"jsdom": "^25.0.1",
|
|
@@ -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<=160?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-CSbI2R5e.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"symbolCatalog-CSbI2R5e.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/**\n * The longest symbol the widget keeps, in a watchlist or a list of symbols:\n * room for a venue, a DEX pool's address (a Uniswap v4 pool's is 66\n * characters) and its pair. 64 dropped such pools from the lists unseen.\n */\nexport const MAX_SYMBOL_LENGTH = 160;\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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"symbolCatalog-DffhUQSJ.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/**\n * The longest symbol the widget keeps, in a watchlist or a list of symbols:\n * room for a venue, a DEX pool's address (a Uniswap v4 pool's is 66\n * characters) and its pair. 64 dropped such pools from the lists unseen.\n */\nexport const MAX_SYMBOL_LENGTH = 160;\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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"}
|