@trdrs/quickcharts 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 5.2.0
6
+
7
+ The chart's right-click menu ends on Settings…, which opens the chart settings dialog, wherever the chart draws its settings door in the top bar.
8
+
9
+ The symbol search and compare dialogs open on their field with no title band, in a box rounded 20px. Every row is one 56px layout in each mode: a 36px mark, the ticker at 16px over its description at 12px with the market's kind as a small tag after it, and the source as a 24px mark at the row's end that names it in its tooltip. The pointer and the keyboard's row wear one rounded plate.
10
+
11
+ `SymbolRow.ticker` is the ticker a viewer reads a row's market by, where the feed writes one apart from its symbol; `symbolNames` and the search rows read it, and a row without it reads the symbol's own ticker.
12
+
13
+ ## 5.1.1
14
+
15
+ Range presets reconcile overlapping history pages with streamed bars and preserve a newer navigation while a page is pending.
16
+
5
17
  ## 5.1.0
6
18
 
7
19
  Charts open live subscriptions alongside history, frame the first arriving data, and retain newer streamed prices when history arrives later. A snapshot with `backfill: true` adds missing history without replacing bars already held.
package/README.md CHANGED
@@ -337,6 +337,16 @@ symbolNames({ symbol: 'NASDAQ:AAPL', name: 'Apple Inc', exchange: 'NASDAQ', type
337
337
  symbolNames('HYPERLIQUID:ETH').mark // 'ETH', before the symbol resolves
338
338
  ```
339
339
 
340
+ One exchange prefix comes off a symbol on its own. Where your feed writes a symbol that says more,
341
+ give the row the `ticker` a viewer reads it by, as `SymbolInfo.ticker` states it for the charted
342
+ symbol; the row keeps its `symbol` as the market's identity:
343
+
344
+ ```ts
345
+ import { symbolNames } from '@trdrs/quickcharts'
346
+
347
+ symbolNames({ symbol: 'EXCH:BOOK:NVDA', ticker: 'NVDA', name: 'NVIDIA Corp', exchange: 'EXCH', type: 'stock' }).mark // 'NVDA'
348
+ ```
349
+
340
350
  `@trdrs/quickcharts/symbols` carries the same rule on its own, `symbolNames` and `bareTicker`, with
341
351
  nothing that names a window, a document or a DOM type, so a view of your own with no DOM, a native
342
352
  search list among them, names a market exactly as the chart does. `bareTicker` is the ticker inside
@@ -17,7 +17,7 @@ function pairLabel(ticker, type, currencyCode) {
17
17
  }
18
18
  function factsOf(symbol) {
19
19
  if (typeof symbol === "string") return { ticker: symbol, type: "", shortName: "", longName: "", currencyCode: void 0 };
20
- if ("symbol" in symbol) return { ticker: symbol.symbol, type: symbol.type, shortName: "", longName: symbol.name.trim(), currencyCode: symbol.currencyCode };
20
+ if ("symbol" in symbol) return { ticker: symbol.ticker?.trim() || symbol.symbol, type: symbol.type, shortName: "", longName: symbol.name.trim(), currencyCode: symbol.currencyCode };
21
21
  const name = symbol.name.trim();
22
22
  const description = symbol.description.trim();
23
23
  return { ticker: symbol.ticker, type: symbol.type, shortName: name !== description ? name : "", longName: description, currencyCode: symbol.currencyCode };
@@ -38,4 +38,4 @@ export {
38
38
  bareTicker,
39
39
  symbolNames
40
40
  };
41
- //# sourceMappingURL=chunk-GFQQSY47.js.map
41
+ //# sourceMappingURL=chunk-U4W26DXU.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/symbolLabel.ts"],"sourcesContent":["// How a symbol is WRITTEN on screen: one rule, three faces, kept in one file so they cannot drift.\n// The toolbar pill wears the compact mark, the legend wears the title, and a search row wears the\n// mark beside the description. Every face drops the venue prefix, which is feed identity rather\n// than anything a viewer reads, and every surface that names a market, in this package or over it,\n// reads the same faces from `symbolNames`.\nimport type { SymbolRow } from './datafeed'\nimport type { SymbolInfo } from './symbology'\n\n/** The ticker inside a feed symbol. Only a well-formed `VENUE:TICKER` sheds its prefix; a spread\n * expression, or anything else the pattern does not admit, is its own label and passes through. */\nexport function bareTicker(symbol: string): string {\n return /^[A-Za-z][A-Za-z0-9._-]*:([A-Za-z0-9][A-Za-z0-9._!]*)$/.exec(symbol)?.[1] ?? symbol\n}\n\n/** A ticker written as a slash pair (`BTC/USD`): catalog identity that already says what it is\n * priced in. A reciprocal expression (`1/ES`) starts with a digit and is not one. */\nconst SLASH_PAIR = /^[A-Za-z][A-Za-z0-9.]*\\/[A-Za-z][A-Za-z0-9.]*$/\n\n/** Compact presentation of one symbol. Feed identity and spread expressions stay untouched. */\nexport function symbolLabel(symbol: string): string {\n const ticker = bareTicker(symbol)\n return SLASH_PAIR.test(ticker) ? ticker.replace('/', '') : ticker\n}\n\n/** The markets whose symbols ARE a pair: one thing priced in another, where the ticker alone says\n * what is being bought but not what it is priced in. Those read as `BASE / QUOTE`.\n *\n * Every other market is deliberately absent. Nearly every symbol carries a `currencyCode`, so the\n * gate cannot be \"has a currency\": a share quoted in dollars is not a pair, and `AAPL / USD` would\n * claim one that does not exist. */\nconst PAIR_MARKETS = new Set(['crypto', 'forex'])\n\n/** `BASE / QUOTE` for a pair market, or null for anything that is not one.\n *\n * The quote comes from the feed's own currency; a ticker that already carries it as a suffix\n * (`BTCUSDT`, `EURUSD`) sheds it rather than repeating it, and the base is whatever the ticker\n * states, so an inverted quotation reads the way it is written. */\nexport function pairLabel(ticker: string, type: string, currencyCode: string | undefined): string | null {\n const quote = currencyCode?.trim()\n if (!quote || !PAIR_MARKETS.has(type.toLowerCase())) return null\n const bare = bareTicker(ticker)\n const base = bare.length > quote.length && bare.toUpperCase().endsWith(quote.toUpperCase()) ? bare.slice(0, -quote.length) : bare\n return base && base.toUpperCase() !== quote.toUpperCase() ? `${base} / ${quote}` : null\n}\n\n/** The three ways one market is written, from one rule. */\nexport interface SymbolNames {\n /** The compact mark a label wears at a glance: a pair closed up (`BTCUSDC`, `EURUSD`), otherwise\n * the bare ticker (`ES1!`, `AAPL`). A spread expression is its own mark, operators and all. */\n mark: string\n /** The title a reading wears: a pair spaced (`BTC / USDC`), otherwise the feed's short name where\n * it stated one apart from the description, otherwise the bare ticker. */\n title: string\n /** The description beside a mark: a pair spaced, otherwise the feed's long description, otherwise\n * the bare ticker, so a market never repeats its own mark for want of a name. */\n description: string\n}\n\n/** The facts the rule reads, in one shape whatever the surface holds. */\ninterface Facts {\n ticker: string\n type: string\n shortName: string\n longName: string\n currencyCode: string | undefined\n}\n\nfunction factsOf(symbol: SymbolInfo | SymbolRow | string): Facts {\n if (typeof symbol === 'string') return { ticker: symbol, type: '', shortName: '', longName: '', currencyCode: undefined }\n // A search row states one name, the long one; a resolved symbol states a short name beside its\n // description, and a short name that merely repeats the description has not been given.\n if ('symbol' in symbol) return { ticker: symbol.symbol, type: symbol.type, shortName: '', longName: symbol.name.trim(), currencyCode: symbol.currencyCode }\n const name = symbol.name.trim()\n const description = symbol.description.trim()\n return { ticker: symbol.ticker, type: symbol.type, shortName: name !== description ? name : '', longName: description, currencyCode: symbol.currencyCode }\n}\n\n/** How a market is named, from a resolved `SymbolInfo`, a search row, or the feed symbol alone\n * before either has landed.\n *\n * A pair market reads as `BTC / USDC` or `EUR / USD`, both sides in the codes the market itself\n * trades in, never a spelled-out currency name. The quote comes from `currencyCode`, and a ticker\n * written as a slash pair (`BTC/USD`) needs no currency to say so. Before a symbol resolves there\n * is no pair to write, so the bare ticker stands on its own rather than guessing at a quote. A\n * market that is not a pair keeps the feed's own words: the short name as its title, the long\n * description beside its mark. */\nexport function symbolNames(symbol: SymbolInfo | SymbolRow | string): SymbolNames {\n const facts = factsOf(symbol)\n if (facts.type.toLowerCase() === 'spread') return { mark: facts.ticker, title: facts.ticker, description: facts.ticker }\n const bare = bareTicker(facts.ticker)\n const pair = pairLabel(facts.ticker, facts.type, facts.currencyCode) ?? (SLASH_PAIR.test(bare) ? bare.replace('/', ' / ') : null)\n return {\n mark: pair ? pair.replace(' / ', '') : symbolLabel(facts.ticker),\n title: pair ?? (facts.shortName || bare),\n description: pair ?? (facts.longName || bare),\n }\n}\n"],"mappings":";AAUO,SAAS,WAAW,QAAwB;AACjD,SAAO,yDAAyD,KAAK,MAAM,IAAI,CAAC,KAAK;AACvF;AAIA,IAAM,aAAa;AAGZ,SAAS,YAAY,QAAwB;AAClD,QAAM,SAAS,WAAW,MAAM;AAChC,SAAO,WAAW,KAAK,MAAM,IAAI,OAAO,QAAQ,KAAK,EAAE,IAAI;AAC7D;AAQA,IAAM,eAAe,oBAAI,IAAI,CAAC,UAAU,OAAO,CAAC;AAOzC,SAAS,UAAU,QAAgB,MAAc,cAAiD;AACvG,QAAM,QAAQ,cAAc,KAAK;AACjC,MAAI,CAAC,SAAS,CAAC,aAAa,IAAI,KAAK,YAAY,CAAC,EAAG,QAAO;AAC5D,QAAM,OAAO,WAAW,MAAM;AAC9B,QAAM,OAAO,KAAK,SAAS,MAAM,UAAU,KAAK,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,IAAI,KAAK,MAAM,GAAG,CAAC,MAAM,MAAM,IAAI;AAC7H,SAAO,QAAQ,KAAK,YAAY,MAAM,MAAM,YAAY,IAAI,GAAG,IAAI,MAAM,KAAK,KAAK;AACrF;AAwBA,SAAS,QAAQ,QAAgD;AAC/D,MAAI,OAAO,WAAW,SAAU,QAAO,EAAE,QAAQ,QAAQ,MAAM,IAAI,WAAW,IAAI,UAAU,IAAI,cAAc,OAAU;AAGxH,MAAI,YAAY,OAAQ,QAAO,EAAE,QAAQ,OAAO,QAAQ,MAAM,OAAO,MAAM,WAAW,IAAI,UAAU,OAAO,KAAK,KAAK,GAAG,cAAc,OAAO,aAAa;AAC1J,QAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,QAAM,cAAc,OAAO,YAAY,KAAK;AAC5C,SAAO,EAAE,QAAQ,OAAO,QAAQ,MAAM,OAAO,MAAM,WAAW,SAAS,cAAc,OAAO,IAAI,UAAU,aAAa,cAAc,OAAO,aAAa;AAC3J;AAWO,SAAS,YAAY,QAAsD;AAChF,QAAM,QAAQ,QAAQ,MAAM;AAC5B,MAAI,MAAM,KAAK,YAAY,MAAM,SAAU,QAAO,EAAE,MAAM,MAAM,QAAQ,OAAO,MAAM,QAAQ,aAAa,MAAM,OAAO;AACvH,QAAM,OAAO,WAAW,MAAM,MAAM;AACpC,QAAM,OAAO,UAAU,MAAM,QAAQ,MAAM,MAAM,MAAM,YAAY,MAAM,WAAW,KAAK,IAAI,IAAI,KAAK,QAAQ,KAAK,KAAK,IAAI;AAC5H,SAAO;AAAA,IACL,MAAM,OAAO,KAAK,QAAQ,OAAO,EAAE,IAAI,YAAY,MAAM,MAAM;AAAA,IAC/D,OAAO,SAAS,MAAM,aAAa;AAAA,IACnC,aAAa,SAAS,MAAM,YAAY;AAAA,EAC1C;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/symbolLabel.ts"],"sourcesContent":["// How a symbol is WRITTEN on screen: one rule, three faces, kept in one file so they cannot drift.\n// The toolbar pill wears the compact mark, the legend wears the title, and a search row wears the\n// mark beside the description. Every face drops the venue prefix, which is feed identity rather\n// than anything a viewer reads, and every surface that names a market, in this package or over it,\n// reads the same faces from `symbolNames`.\nimport type { SymbolRow } from './datafeed'\nimport type { SymbolInfo } from './symbology'\n\n/** The ticker inside a feed symbol. Only a well-formed `VENUE:TICKER` sheds its prefix; a spread\n * expression, or anything else the pattern does not admit, is its own label and passes through. */\nexport function bareTicker(symbol: string): string {\n return /^[A-Za-z][A-Za-z0-9._-]*:([A-Za-z0-9][A-Za-z0-9._!]*)$/.exec(symbol)?.[1] ?? symbol\n}\n\n/** A ticker written as a slash pair (`BTC/USD`): catalog identity that already says what it is\n * priced in. A reciprocal expression (`1/ES`) starts with a digit and is not one. */\nconst SLASH_PAIR = /^[A-Za-z][A-Za-z0-9.]*\\/[A-Za-z][A-Za-z0-9.]*$/\n\n/** Compact presentation of one symbol. Feed identity and spread expressions stay untouched. */\nexport function symbolLabel(symbol: string): string {\n const ticker = bareTicker(symbol)\n return SLASH_PAIR.test(ticker) ? ticker.replace('/', '') : ticker\n}\n\n/** The markets whose symbols ARE a pair: one thing priced in another, where the ticker alone says\n * what is being bought but not what it is priced in. Those read as `BASE / QUOTE`.\n *\n * Every other market is deliberately absent. Nearly every symbol carries a `currencyCode`, so the\n * gate cannot be \"has a currency\": a share quoted in dollars is not a pair, and `AAPL / USD` would\n * claim one that does not exist. */\nconst PAIR_MARKETS = new Set(['crypto', 'forex'])\n\n/** `BASE / QUOTE` for a pair market, or null for anything that is not one.\n *\n * The quote comes from the feed's own currency; a ticker that already carries it as a suffix\n * (`BTCUSDT`, `EURUSD`) sheds it rather than repeating it, and the base is whatever the ticker\n * states, so an inverted quotation reads the way it is written. */\nexport function pairLabel(ticker: string, type: string, currencyCode: string | undefined): string | null {\n const quote = currencyCode?.trim()\n if (!quote || !PAIR_MARKETS.has(type.toLowerCase())) return null\n const bare = bareTicker(ticker)\n const base = bare.length > quote.length && bare.toUpperCase().endsWith(quote.toUpperCase()) ? bare.slice(0, -quote.length) : bare\n return base && base.toUpperCase() !== quote.toUpperCase() ? `${base} / ${quote}` : null\n}\n\n/** The three ways one market is written, from one rule. */\nexport interface SymbolNames {\n /** The compact mark a label wears at a glance: a pair closed up (`BTCUSDC`, `EURUSD`), otherwise\n * the bare ticker (`ES1!`, `AAPL`). A spread expression is its own mark, operators and all. */\n mark: string\n /** The title a reading wears: a pair spaced (`BTC / USDC`), otherwise the feed's short name where\n * it stated one apart from the description, otherwise the bare ticker. */\n title: string\n /** The description beside a mark: a pair spaced, otherwise the feed's long description, otherwise\n * the bare ticker, so a market never repeats its own mark for want of a name. */\n description: string\n}\n\n/** The facts the rule reads, in one shape whatever the surface holds. */\ninterface Facts {\n ticker: string\n type: string\n shortName: string\n longName: string\n currencyCode: string | undefined\n}\n\nfunction factsOf(symbol: SymbolInfo | SymbolRow | string): Facts {\n if (typeof symbol === 'string') return { ticker: symbol, type: '', shortName: '', longName: '', currencyCode: undefined }\n // A search row states one name, the long one; a resolved symbol states a short name beside its\n // description, and a short name that merely repeats the description has not been given.\n if ('symbol' in symbol) return { ticker: symbol.ticker?.trim() || symbol.symbol, type: symbol.type, shortName: '', longName: symbol.name.trim(), currencyCode: symbol.currencyCode }\n const name = symbol.name.trim()\n const description = symbol.description.trim()\n return { ticker: symbol.ticker, type: symbol.type, shortName: name !== description ? name : '', longName: description, currencyCode: symbol.currencyCode }\n}\n\n/** How a market is named, from a resolved `SymbolInfo`, a search row, or the feed symbol alone\n * before either has landed.\n *\n * A pair market reads as `BTC / USDC` or `EUR / USD`, both sides in the codes the market itself\n * trades in, never a spelled-out currency name. The quote comes from `currencyCode`, and a ticker\n * written as a slash pair (`BTC/USD`) needs no currency to say so. Before a symbol resolves there\n * is no pair to write, so the bare ticker stands on its own rather than guessing at a quote. A\n * market that is not a pair keeps the feed's own words: the short name as its title, the long\n * description beside its mark. */\nexport function symbolNames(symbol: SymbolInfo | SymbolRow | string): SymbolNames {\n const facts = factsOf(symbol)\n if (facts.type.toLowerCase() === 'spread') return { mark: facts.ticker, title: facts.ticker, description: facts.ticker }\n const bare = bareTicker(facts.ticker)\n const pair = pairLabel(facts.ticker, facts.type, facts.currencyCode) ?? (SLASH_PAIR.test(bare) ? bare.replace('/', ' / ') : null)\n return {\n mark: pair ? pair.replace(' / ', '') : symbolLabel(facts.ticker),\n title: pair ?? (facts.shortName || bare),\n description: pair ?? (facts.longName || bare),\n }\n}\n"],"mappings":";AAUO,SAAS,WAAW,QAAwB;AACjD,SAAO,yDAAyD,KAAK,MAAM,IAAI,CAAC,KAAK;AACvF;AAIA,IAAM,aAAa;AAGZ,SAAS,YAAY,QAAwB;AAClD,QAAM,SAAS,WAAW,MAAM;AAChC,SAAO,WAAW,KAAK,MAAM,IAAI,OAAO,QAAQ,KAAK,EAAE,IAAI;AAC7D;AAQA,IAAM,eAAe,oBAAI,IAAI,CAAC,UAAU,OAAO,CAAC;AAOzC,SAAS,UAAU,QAAgB,MAAc,cAAiD;AACvG,QAAM,QAAQ,cAAc,KAAK;AACjC,MAAI,CAAC,SAAS,CAAC,aAAa,IAAI,KAAK,YAAY,CAAC,EAAG,QAAO;AAC5D,QAAM,OAAO,WAAW,MAAM;AAC9B,QAAM,OAAO,KAAK,SAAS,MAAM,UAAU,KAAK,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,IAAI,KAAK,MAAM,GAAG,CAAC,MAAM,MAAM,IAAI;AAC7H,SAAO,QAAQ,KAAK,YAAY,MAAM,MAAM,YAAY,IAAI,GAAG,IAAI,MAAM,KAAK,KAAK;AACrF;AAwBA,SAAS,QAAQ,QAAgD;AAC/D,MAAI,OAAO,WAAW,SAAU,QAAO,EAAE,QAAQ,QAAQ,MAAM,IAAI,WAAW,IAAI,UAAU,IAAI,cAAc,OAAU;AAGxH,MAAI,YAAY,OAAQ,QAAO,EAAE,QAAQ,OAAO,QAAQ,KAAK,KAAK,OAAO,QAAQ,MAAM,OAAO,MAAM,WAAW,IAAI,UAAU,OAAO,KAAK,KAAK,GAAG,cAAc,OAAO,aAAa;AACnL,QAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,QAAM,cAAc,OAAO,YAAY,KAAK;AAC5C,SAAO,EAAE,QAAQ,OAAO,QAAQ,MAAM,OAAO,MAAM,WAAW,SAAS,cAAc,OAAO,IAAI,UAAU,aAAa,cAAc,OAAO,aAAa;AAC3J;AAWO,SAAS,YAAY,QAAsD;AAChF,QAAM,QAAQ,QAAQ,MAAM;AAC5B,MAAI,MAAM,KAAK,YAAY,MAAM,SAAU,QAAO,EAAE,MAAM,MAAM,QAAQ,OAAO,MAAM,QAAQ,aAAa,MAAM,OAAO;AACvH,QAAM,OAAO,WAAW,MAAM,MAAM;AACpC,QAAM,OAAO,UAAU,MAAM,QAAQ,MAAM,MAAM,MAAM,YAAY,MAAM,WAAW,KAAK,IAAI,IAAI,KAAK,QAAQ,KAAK,KAAK,IAAI;AAC5H,SAAO;AAAA,IACL,MAAM,OAAO,KAAK,QAAQ,OAAO,EAAE,IAAI,YAAY,MAAM,MAAM;AAAA,IAC/D,OAAO,SAAS,MAAM,aAAa;AAAA,IACnC,aAAa,SAAS,MAAM,YAAY;AAAA,EAC1C;AACF;","names":[]}
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.1.0",
2
+ "version": "5.2.0",
3
3
  "styles": [
4
4
  {
5
5
  "id": "candles",
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ChartDatafeed, M as MarkIconId, F as FeedBar, S as SymbolRow, D as DatafeedSearchOptions, a as SearchPage, b as SearchClassNode } from './symbols-CHJGWrf-.js';
2
- export { B as BarMark, c as BarsEvent, d as DatafeedConfig, e as FeedUnavailableError, H as HistoryPage, f as MarkColor, g as MarkColorPair, h as MarkColorRole, i as MarkLine, j as MarkLineStyle, k as MarkPlacement, l as MarkShape, O as OlderPageVerdict, P as PricesHandlers, m as SubscribeHandlers, n as SymbolNames, o as SymbolPrices, T as TimescaleMark, p as olderPageVerdict, s as symbolNames } from './symbols-CHJGWrf-.js';
1
+ import { C as ChartDatafeed, M as MarkIconId, F as FeedBar, S as SymbolRow, D as DatafeedSearchOptions, a as SearchPage, b as SearchClassNode } from './symbols-CCYUK8MU.js';
2
+ export { B as BarMark, c as BarsEvent, d as DatafeedConfig, e as FeedUnavailableError, H as HistoryPage, f as MarkColor, g as MarkColorPair, h as MarkColorRole, i as MarkLine, j as MarkLineStyle, k as MarkPlacement, l as MarkShape, O as OlderPageVerdict, P as PricesHandlers, m as SubscribeHandlers, n as SymbolNames, o as SymbolPrices, T as TimescaleMark, p as olderPageVerdict, s as symbolNames } from './symbols-CCYUK8MU.js';
3
3
  import { ao as en, C as ChartMessageKey, G as GlyphSourcePort, M as MagnetMode, d as CursorMode, Q as TextEditFrame, N as TextDraft, L as LineStyle, I as IDrawing, m as DrawingCounts, D as DrawingStyle, $ as VisibilityPreset, X as ToolPreset, Y as ToolTemplate, S as SerializedDrawing, o as DrawingPreferences, l as DrawingAssetPort } from './assets-DF3nozNV.js';
4
4
  import { b as ResourceRef, C as ChartSaveLoadAdapter, q as ChartMeta, t as ChartBody, h as DrawingResourceContext, R as ResourceStore, u as DrawingsMeta, j as DrawingsBody, T as TemplateMeta, a as TemplateBody, v as LayoutMeta, w as LayoutBody, e as DrawingContextKind } from './resources-XglhzzW0.js';
5
5
  export { c as ChartLocalDrawingContext, D as DRAWING_CONTEXT_VERSION, d as DRAWING_DOCUMENT_VERSION, f as DrawingEntry, g as DrawingGroup, i as DrawingTombstone, L as LayoutSharedDrawingContext, M as MemoryResourcesOptions, x as ResourceAbortError, S as SymbolGlobalDrawingContext, y as TemplateKind, W as WriteOutcome, k as drawingContextKey, l as emptyDrawingDocument, m as liveDrawingEntries, n as liveDrawingGroups, z as memorySaveLoadAdapter, o as mergeDrawingDocuments, p as parseDrawingDocument, r as reviseDrawingDocument, s as sameDrawingContext } from './resources-XglhzzW0.js';
package/dist/index.js CHANGED
@@ -143,7 +143,7 @@ import {
143
143
  } from "./chunk-QYGSDD2Q.js";
144
144
  import {
145
145
  symbolNames
146
- } from "./chunk-GFQQSY47.js";
146
+ } from "./chunk-U4W26DXU.js";
147
147
 
148
148
  // src/datafeed.ts
149
149
  function olderPageVerdict(page, askedTo, alreadyHopped) {
@@ -19643,7 +19643,8 @@ var MENU_COMMAND = {
19643
19643
  "copy-price": "chart.price.copy",
19644
19644
  paste: "chart.drawings.paste",
19645
19645
  "remove-indicators": "chart.indicators.removeAll",
19646
- "remove-drawings": "chart.drawings.removeAll"
19646
+ "remove-drawings": "chart.drawings.removeAll",
19647
+ settings: "chart.settings.open"
19647
19648
  };
19648
19649
  var PLACE_HORIZONTAL_LINE = "chart.drawings.placeHorizontalLine";
19649
19650
  function levelAt(series2, y, step) {
@@ -19689,12 +19690,11 @@ function attachMenuPlane(deps) {
19689
19690
  {
19690
19691
  priceText,
19691
19692
  symbol: deps.symbol(),
19692
- // The context menu's paste row is live exactly when the paste command would run (a copied
19693
- // drawing on the clipboard, the verb permitted). It offers no settings row: the settings
19694
- // dialog belongs to the selected drawing's own surfaces, which the drawing plane mounts
19695
- // and the chart.drawings.* commands drive.
19693
+ // The paste row is live exactly when the paste command would run (a copied drawing on the
19694
+ // clipboard, the verb permitted), and the settings row wherever the chart offers its
19695
+ // settings door and the command would open the dialog.
19696
19696
  canPaste: deps.commands.available("chart.drawings.paste"),
19697
- canSettings: false,
19697
+ canSettings: (deps.settingsDoor ?? true) && deps.commands.available(MENU_COMMAND.settings),
19698
19698
  indicatorCount: deps.indicatorCount(),
19699
19699
  drawingCount: deps.drawingCount(),
19700
19700
  ...table ? { table } : {}
@@ -22628,6 +22628,7 @@ function createChartInstance(deps) {
22628
22628
  drawingCount: () => drawings2.handle?.count() ?? 0,
22629
22629
  extensions: () => extensions.host,
22630
22630
  shown: (id) => commandShown(deps.access, id),
22631
+ settingsDoor: deps.ui.settingsMenu,
22631
22632
  setLevel: (price) => {
22632
22633
  menuLevel = price;
22633
22634
  },
@@ -23043,17 +23044,22 @@ function createChartInstance(deps) {
23043
23044
  if (short <= 0) return;
23044
23045
  paging = true;
23045
23046
  const myEpoch = epoch;
23047
+ const myNavigation = navigationEpoch;
23046
23048
  const oldest = bars[0].t;
23047
23049
  void fetchOlder(oldest - 1, short).then(({ olderBars, end }) => {
23048
23050
  if (disposed || myEpoch !== epoch) return;
23049
23051
  if (end) noMoreHistory = true;
23050
- const older = olderBars.filter((bar) => bar.t < oldest);
23051
- if (older.length === 0) return;
23052
- bars = [...older, ...bars];
23053
- paintAll();
23054
- refreshMarks();
23055
- frameRange(chart, anchor, span, spanTf);
23052
+ const merged2 = mergeBarSnapshot(bars, olderBars.filter((bar) => bar.t < oldest), true);
23053
+ if (merged2.length > bars.length) {
23054
+ maintainTimeline(() => {
23055
+ bars = merged2;
23056
+ paintAll();
23057
+ });
23058
+ refreshMarks();
23059
+ }
23060
+ if (myNavigation === navigationEpoch) frameRange(chart, anchor, span, spanTf);
23056
23061
  }).catch(() => {
23062
+ if (!disposed && myEpoch === epoch && myNavigation === navigationEpoch) frameRange(chart, anchor, span, spanTf);
23057
23063
  }).finally(() => {
23058
23064
  if (myEpoch === epoch) paging = false;
23059
23065
  });
@@ -25952,8 +25958,9 @@ function dialogRows(input) {
25952
25958
  }
25953
25959
  return base;
25954
25960
  }
25955
- var ROW_MARK_SIZE2 = 24;
25956
- var SOURCE_MARK_SIZE = 18;
25961
+ var ROW_MARK_SIZE2 = 36;
25962
+ var SOURCE_MARK_SIZE = 24;
25963
+ var SCOPE_MARK_SIZE = 18;
25957
25964
  var picks = 0;
25958
25965
  var PREFETCH_MARGIN = 8;
25959
25966
  function buildSearchSurface(deps, box2, frame2) {
@@ -25970,7 +25977,6 @@ function buildSearchSurface(deps, box2, frame2) {
25970
25977
  let scoped = scopeSession !== null && searchScope?.on !== false;
25971
25978
  const session2 = () => scoped && scopeSession ? scopeSession : feedSession;
25972
25979
  const search2 = () => session2().controller;
25973
- if (mode === "search" || mode === "pick") box2.classList.add("qc-search-table");
25974
25980
  let query = request.changeFrom ?? "";
25975
25981
  const display = deps.display ?? {};
25976
25982
  const spreads = display.spreads !== false;
@@ -26074,7 +26080,7 @@ function buildSearchSurface(deps, box2, frame2) {
26074
26080
  }
26075
26081
  if (searchScope) {
26076
26082
  const mark = h("span", { class: "qc-search-scope-mark", "aria-hidden": "true" });
26077
- const drop = searchScope.mark?.({ host: mark, size: SOURCE_MARK_SIZE });
26083
+ const drop = searchScope.mark?.({ host: mark, size: SCOPE_MARK_SIZE });
26078
26084
  if (typeof drop === "function") {
26079
26085
  mark.dataset.qcHost = "true";
26080
26086
  scopeMarkDisposer = drop;
@@ -26087,7 +26093,7 @@ function buildSearchSurface(deps, box2, frame2) {
26087
26093
  const status2 = h("div", { class: "qc-search-status", role: "status", "aria-live": "polite" });
26088
26094
  const sentinel = h("div", { class: "qc-search-sentinel qc-muted" }, t("search.loadingMore"));
26089
26095
  sentinel.hidden = true;
26090
- append(box2, frame2.title === null ? null : dialogTitle(frame2.title, t("search.close"), frame2.done, deps.icons), field, strip, ...narrower.map((n) => n.row), list, status2);
26096
+ append(box2, field, strip, ...narrower.map((n) => n.row), list, status2);
26091
26097
  const serverQuery = () => spreads && looksLikeSpread(query) && !isSymbolPair(query) ? spreadSearchQuery(query) : query;
26092
26098
  const setQuery = (next) => {
26093
26099
  query = next;
@@ -26154,16 +26160,22 @@ function buildSearchSurface(deps, box2, frame2) {
26154
26160
  markDisposers.push(dropMark);
26155
26161
  } else mark.set(label3.ticker);
26156
26162
  option.appendChild(mark.element);
26157
- option.appendChild(h("span", { class: "qc-search-text" }, marked(label3.ticker), h("span", { class: "qc-search-name" }, label3.description)));
26163
+ option.appendChild(
26164
+ h(
26165
+ "span",
26166
+ { class: "qc-search-text" },
26167
+ marked(label3.ticker),
26168
+ h("span", { class: "qc-search-line" }, h("span", { class: "qc-search-name" }, label3.description), r.type ? h("span", { class: "qc-search-kind" }, r.type) : null)
26169
+ )
26170
+ );
26158
26171
  if (compare && entry.added) {
26159
26172
  const mark2 = h("span", { class: "qc-search-added", title: t("search.addedMark", { symbol: label3.ticker }) });
26160
26173
  mark2.appendChild(deps.icons.glyph(ICONS.check, { size: 18 }));
26161
26174
  option.appendChild(mark2);
26162
26175
  } else {
26163
26176
  const source3 = h("span", { class: "qc-search-source" });
26164
- source3.appendChild(h("span", { class: "qc-search-source-text" }, label3.source ? h("span", { class: "qc-search-source-name" }, label3.source) : null, h("span", { class: "qc-search-kind" }, r.type)));
26165
26177
  if (label3.source) {
26166
- const sourceMark = h("span", { class: "qc-search-mark", "aria-hidden": "true" });
26178
+ const sourceMark = h("span", { class: "qc-search-mark", role: "img", "aria-label": label3.source, title: label3.source });
26167
26179
  const dropSource = r.exchange ? deps.painters.venue?.({ exchange: r.exchange, host: sourceMark, size: SOURCE_MARK_SIZE }) : r.dataSource ? deps.painters.dataSource?.({ dataSource: r.dataSource, host: sourceMark, size: SOURCE_MARK_SIZE }) : void 0;
26168
26180
  if (typeof dropSource === "function") {
26169
26181
  sourceMark.dataset.qcHost = "true";
@@ -26286,7 +26298,7 @@ function openSearchDialog(deps) {
26286
26298
  deps.onClose?.(dialog);
26287
26299
  },
26288
26300
  build(box2, handle) {
26289
- surface = buildSearchSurface(deps, box2, { title, done: () => handle.close() });
26301
+ surface = buildSearchSurface(deps, box2, { done: () => handle.close() });
26290
26302
  },
26291
26303
  initialFocus: (box2) => box2.querySelector(".qc-search-input")
26292
26304
  });
@@ -30920,8 +30932,8 @@ function mountSymbolSearch(options2) {
30920
30932
  }
30921
30933
  },
30922
30934
  element,
30923
- // A card wears no title row: the page's own box is the heading. Its done is the page's close.
30924
- { title: null, done: () => options2.onClose?.() }
30935
+ // A card's done is the page's close.
30936
+ { done: () => options2.onClose?.() }
30925
30937
  );
30926
30938
  return {
30927
30939
  element,