@luxalgo/vela 0.5.4 → 0.6.1

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.
Files changed (37) hide show
  1. package/dist/{DataProvider-DgS2UyMc.d.cts → DataProvider-BNKtYU5V.d.cts} +22 -3
  2. package/dist/{DataProvider-Cqc_BaJ9.d.ts → DataProvider-gJjN_0eh.d.ts} +22 -3
  3. package/dist/{chunk-D6WM5IUE.js → chunk-4MQEG67Z.js} +77 -18
  4. package/dist/{chunk-JBQUY2RI.js → chunk-6LZJRO2S.js} +48 -18
  5. package/dist/{contributions-DuIxJWeH.d.ts → contributions-D64UA74H.d.cts} +9 -2
  6. package/dist/{contributions-4Nh1jbvb.d.cts → contributions-lPAgo9Cu.d.ts} +9 -2
  7. package/dist/{history-FsmvCIo4.d.ts → history-CVoFDJ2D.d.ts} +21 -3
  8. package/dist/{history-HofqYEh2.d.cts → history-fSMXVLt1.d.cts} +21 -3
  9. package/dist/index.cjs +77 -18
  10. package/dist/index.d.cts +14 -6
  11. package/dist/index.d.ts +14 -6
  12. package/dist/index.js +1 -1
  13. package/dist/{options-BlQ00Fju.d.cts → options-BqGeFHtp.d.cts} +15 -1
  14. package/dist/{options-BlQ00Fju.d.ts → options-BqGeFHtp.d.ts} +15 -1
  15. package/dist/{plugin-DJR6z3e0.d.cts → plugin-D5ni4WEJ.d.cts} +3 -3
  16. package/dist/{plugin-N_EaXN4-.d.ts → plugin-o5HJH46Z.d.ts} +3 -3
  17. package/dist/plugin.d.cts +4 -4
  18. package/dist/plugin.d.ts +4 -4
  19. package/dist/providers/binance.d.cts +2 -2
  20. package/dist/providers/binance.d.ts +2 -2
  21. package/dist/providers/coinbase.d.cts +2 -2
  22. package/dist/providers/coinbase.d.ts +2 -2
  23. package/dist/providers/hyperliquid.d.cts +2 -2
  24. package/dist/providers/hyperliquid.d.ts +2 -2
  25. package/dist/ui.d.cts +1 -1
  26. package/dist/ui.d.ts +1 -1
  27. package/dist/vela.global.js +77 -18
  28. package/dist/vela.global.min.js +29 -29
  29. package/dist/widget.cjs +175 -40
  30. package/dist/widget.d.cts +23 -15
  31. package/dist/widget.d.ts +23 -15
  32. package/dist/widget.js +56 -10
  33. package/dist/workspace.cjs +172 -36
  34. package/dist/workspace.d.cts +20 -5
  35. package/dist/workspace.d.ts +20 -5
  36. package/dist/workspace.js +52 -5
  37. package/package.json +1 -1
package/dist/widget.js CHANGED
@@ -1,7 +1,7 @@
1
- import { Glider, WidgetHistory, localStorageAdapter, decodeState, prefixedSymbol, SymbolPicker, IndicatorPicker, TimeframeQuick, Topbar, PanelDock, ObjectTree, DataWindow, ChartContextMenu, Toast, Watermark, Statusline, Bottombar, MobileBar, DrawingPill, LayoutModeController, ZOOM_IN, ZOOM_OUT, PAN_FAST, ShortcutsHelp, TimeframeDrawer, RANGE_PRESETS, DrawingsDrawer, MoreDrawer, priceStyleIcon, priceStyleLabel, TimezoneDrawer, PriceScaleDrawer, indicatorLedger, sanitizeState, legacyWidgetState, parsePersisted, statuslineInkOf, toolShortcutHints, resolveIndicators, encodeState } from './chunk-JBQUY2RI.js';
2
- export { Bottombar, ChartContextMenu, DataWindow, IndicatorPicker, ObjectTree, PanelDock, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeQuick, Topbar, Watermark, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, legacyWidgetState, loadPersisted, localStorageAdapter, parsePersisted, parseTimeframe, priceStyleLabel, resolveIndicators, sanitizeState, savePersisted, timeframeLabel, timeframeMs } from './chunk-JBQUY2RI.js';
3
- import { registerBuiltinChartTypes, resolveTheme, parseSymbol, buildToolbar, priceStyleIds, Vela, normalizeTimezone } from './chunk-D6WM5IUE.js';
4
- export { TIMEZONES, normalizeTimezone, tzButtonLabel, tzMenuLabel, tzOffset } from './chunk-D6WM5IUE.js';
1
+ import { Glider, WidgetHistory, localStorageAdapter, decodeState, prefixedSymbol, normalizeSession, SymbolPicker, IndicatorPicker, TimeframeQuick, Topbar, PanelDock, ObjectTree, DataWindow, ChartContextMenu, Toast, Watermark, Statusline, Bottombar, MobileBar, DrawingPill, LayoutModeController, ZOOM_IN, ZOOM_OUT, PAN_FAST, ShortcutsHelp, TimeframeDrawer, RANGE_PRESETS, DrawingsDrawer, MoreDrawer, priceStyleIcon, priceStyleLabel, TimezoneDrawer, PriceScaleDrawer, indicatorLedger, sanitizeState, legacyWidgetState, parsePersisted, statuslineInkOf, toolShortcutHints, resolveIndicators, encodeState } from './chunk-6LZJRO2S.js';
2
+ export { Bottombar, ChartContextMenu, DataWindow, IndicatorPicker, ObjectTree, PanelDock, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeQuick, Topbar, Watermark, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, legacyWidgetState, loadPersisted, localStorageAdapter, parsePersisted, parseTimeframe, priceStyleLabel, resolveIndicators, sanitizeState, savePersisted, timeframeLabel, timeframeMs } from './chunk-6LZJRO2S.js';
3
+ import { registerBuiltinChartTypes, resolveTheme, parseSymbol, buildToolbar, priceStyleIds, Vela, normalizeTimezone } from './chunk-4MQEG67Z.js';
4
+ export { TIMEZONES, normalizeTimezone, tzButtonLabel, tzMenuLabel, tzOffset } from './chunk-4MQEG67Z.js';
5
5
  import { widgetActions, widgetAttachments, legendActionsProviderFor, resolveEngines } from './chunk-ZNL2X6U2.js';
6
6
  export { DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, SidePanel, clampPanelWidth, registerSidePanel, registerWidgetAction, registerWidgetAttachment, sidePanels, unregisterSidePanel, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments } from './chunk-ZNL2X6U2.js';
7
7
  import { KeymapManager, Menu, isEditableTarget } from './chunk-QWIEKZRE.js';
@@ -14,7 +14,8 @@ var PARAMS = [
14
14
  ["timeframe", "interval"],
15
15
  ["priceStyle", "style"],
16
16
  ["timezone", "tz"],
17
- ["bars", "bars"]
17
+ ["bars", "bars"],
18
+ ["session", "session"]
18
19
  ];
19
20
  function readUrlState(search) {
20
21
  const out = {};
@@ -146,6 +147,7 @@ var VelaWidget = class {
146
147
  this.priceStyle = fromUrl.priceStyle ?? bootCell?.priceStyle ?? opts.priceStyle ?? "candles";
147
148
  this.timezone = fromUrl.timezone ?? boot?.timezone ?? opts.timezone ?? "Etc/UTC";
148
149
  this.bars = Number(fromUrl.bars ?? bootCell?.bars ?? opts.bars ?? 1e3);
150
+ this.session = normalizeSession(fromUrl.session ?? bootCell?.session ?? opts.session);
149
151
  this.watermarkOn = bootCell?.watermark !== void 0 ? bootCell.watermark : opts.watermark !== false;
150
152
  this.indicatorTitlesOn = bootCell?.indicatorTitles ?? true;
151
153
  this.indicatorValuesOn = bootCell?.indicatorValues ?? true;
@@ -252,6 +254,7 @@ var VelaWidget = class {
252
254
  timezone: this.timezone,
253
255
  onRange: (preset) => this.applyRange(preset),
254
256
  onTimezone: (zone) => this.setTimezone(zone),
257
+ onSession: (session) => this.setSession(session),
255
258
  onSettingsClick: () => this.inner?.renderer.openSettings()
256
259
  }) : null;
257
260
  this.mobileBar = opts.bottombar !== false ? new MobileBar(this.root, {
@@ -481,13 +484,15 @@ var VelaWidget = class {
481
484
  this.statusline?.setMeta(tf, this.providerLabel());
482
485
  }
483
486
  /**
484
- * The provider name to show: the one that RESOLVED the symbol, not the one configured.
485
- * They differ whenever the `provider` option is just a default (or names something that
486
- * isn't registered) the status line must not claim a venue that served nothing.
487
+ * The venue label to show: the LISTING prefix the resolved symbol's data declares
488
+ * (`NASDAQ` for AAPL), else the provider that RESOLVED the symbol never the one
489
+ * configured or typed. They differ whenever the `provider` option is just a default,
490
+ * the prefix was a listing venue, or the spelling isn't registered — the status line
491
+ * must not claim a venue that served nothing.
487
492
  */
488
493
  providerLabel() {
489
- const resolved = this.inner?.data.resolve(this.symbol)?.provider;
490
- return resolved ?? parseSymbol(this.symbol).provider ?? "";
494
+ const display = this.inner?.data.displayPrefix(this.symbol);
495
+ return display ?? parseSymbol(this.symbol).provider ?? "";
491
496
  }
492
497
  setTimeframe(tf) {
493
498
  if (tf === this.timeframe || this.destroyed) return;
@@ -498,6 +503,30 @@ var VelaWidget = class {
498
503
  this.markStateDirty();
499
504
  void this.inner?.setMarket({ timeframe: tf, bars: this.bars });
500
505
  }
506
+ /**
507
+ * Switch the shown trading session (RTH/ETH) in place — a full reload, like a
508
+ * timeframe change (the two sessions are different bar series). No-op on a chart
509
+ * already showing that session; meaningless on continuous markets (the toggle is
510
+ * disabled there, but the API tolerates the call — the provider ignores the flag).
511
+ */
512
+ setSession(session) {
513
+ if (session === this.session || session === "regular" && this.session === void 0 || this.destroyed) return;
514
+ this.session = session;
515
+ this.refreshSessionToggle();
516
+ this.markStateDirty();
517
+ void this.inner?.setMarket({ session });
518
+ }
519
+ /** Re-derive the RTH/ETH toggle's posture from the ACTIVE symbol's metadata: enabled
520
+ * iff the resolved symbol declares a real session vocabulary (not `24x7`). */
521
+ refreshSessionToggle() {
522
+ const chart = this.inner;
523
+ if (!chart || !this.bottombar) return;
524
+ void chart.data.symbolInfo(this.symbol).then((si) => {
525
+ if (this.destroyed || this.inner !== chart) return;
526
+ const enabled = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
527
+ this.bottombar?.setSession({ session: this.session ?? "regular", enabled });
528
+ });
529
+ }
501
530
  /** Star/unstar a timeframe — the topbar chips and dropdown stars follow, and the
502
531
  * set persists with the rest of the shell state. */
503
532
  setTimeframeFavorite(tf, on) {
@@ -544,6 +573,8 @@ var VelaWidget = class {
544
573
  cell.timeframe = live?.timeframe ?? this.timeframe;
545
574
  cell.priceStyle = this.priceStyle;
546
575
  if (this.bars > 0) cell.bars = this.bars;
576
+ const session = normalizeSession(live?.session ?? this.session);
577
+ if (session === "extended") cell.session = session;
547
578
  cell.watermark = this.watermarkOn;
548
579
  cell.indicatorTitles = this.indicatorTitlesOn;
549
580
  cell.indicatorValues = this.indicatorValuesOn;
@@ -610,9 +641,15 @@ var VelaWidget = class {
610
641
  const symbol = fromUrl.symbol ?? prefixedSymbol(cell);
611
642
  const timeframe = fromUrl.timeframe ?? cell.timeframe;
612
643
  const bars = Number(fromUrl.bars ?? cell.bars ?? 0);
644
+ const session = normalizeSession(fromUrl.session ?? cell.session);
613
645
  const next = {};
614
646
  if (symbol && symbol !== this.symbol) next.symbol = symbol;
615
647
  if (timeframe && timeframe !== this.timeframe) next.timeframe = timeframe;
648
+ if (session && session !== (this.session ?? "regular")) {
649
+ this.session = session;
650
+ next.session = session;
651
+ this.refreshSessionToggle();
652
+ }
616
653
  if (bars > 0 && bars !== this.bars) {
617
654
  this.bars = bars;
618
655
  next.bars = Math.max(bars, this.rangeBars);
@@ -839,6 +876,7 @@ var VelaWidget = class {
839
876
  symbol: this.symbol,
840
877
  timeframe: this.timeframe,
841
878
  bars: Math.max(this.bars, this.rangeBars),
879
+ session: this.session,
842
880
  volume: this.ledgerVolume ?? chartOpts.volume,
843
881
  // A pending range chip frames the FIRST paint (no preview flash, no re-frame).
844
882
  ...this.pendingRange ? { visibleRange: this.pendingRange.preset } : {}
@@ -847,6 +885,11 @@ var VelaWidget = class {
847
885
  for (const [language, make] of Object.entries(resolveEngines(engines))) chart.registerEngine(language, make());
848
886
  this.inner = chart;
849
887
  this.symbolPicker.setSource(() => chart.data.symbols());
888
+ void chart.data.ready().then(() => {
889
+ if (this.inner !== chart) return;
890
+ this.statusline?.setMeta(this.timeframe, this.providerLabel());
891
+ this.refreshSessionToggle();
892
+ });
850
893
  if (this.layoutCtl !== void 0) chart.renderer.setLayoutMode(this.layoutCtl.current);
851
894
  this.objectTree.setSymbol(this.symbol);
852
895
  chart.renderer.setLegendActions(legendActionsProviderFor(chart, () => this.context()));
@@ -922,6 +965,7 @@ var VelaWidget = class {
922
965
  this.statusline?.setSymbol(symbol);
923
966
  this.objectTree.setSymbol(symbol);
924
967
  this.watermark?.update(symbol, this.timeframe);
968
+ this.refreshSessionToggle();
925
969
  }
926
970
  if (timeframe !== this.timeframe) this.syncTimeframeChrome(timeframe);
927
971
  });
@@ -1219,6 +1263,8 @@ var VelaWidget = class {
1219
1263
  priceStyle: this.priceStyle,
1220
1264
  timezone: this.timezone,
1221
1265
  bars: String(this.bars),
1266
+ // Empty clears the param — the default (regular) keeps links lean.
1267
+ session: this.session === "extended" ? "extended" : "",
1222
1268
  watermark: this.watermarkOn ? "1" : "0",
1223
1269
  favorites: this.favs.join(",")
1224
1270
  });
@@ -152,7 +152,7 @@ var ProviderRegistry = class {
152
152
  /** Register (or replace) a provider; returns a promise that settles when its index is built. */
153
153
  register(rawName, provider) {
154
154
  const name = normName(rawName);
155
- const entry = { name, provider, index: null, descriptors: [], settled: false, settle: Promise.resolve() };
155
+ const entry = { name, provider, index: null, prefixIndex: null, byTicker: null, descriptors: [], settled: false, settle: Promise.resolve() };
156
156
  this.entries.set(name, entry);
157
157
  entry.settle = this.buildIndex(entry);
158
158
  queueMicrotask(() => this.notify(false));
@@ -165,6 +165,9 @@ var ProviderRegistry = class {
165
165
  const symbols = await entry.provider.listSymbols();
166
166
  entry.descriptors = symbols;
167
167
  entry.index = new Set(symbols.map((s) => normTicker(s.ticker)));
168
+ entry.byTicker = new Map(symbols.map((s) => [normTicker(s.ticker), s]));
169
+ entry.prefixIndex = /* @__PURE__ */ new Map();
170
+ for (const s of symbols) if (s.prefix) entry.prefixIndex.set(`${normName(s.prefix)}:${normTicker(s.ticker)}`, s);
168
171
  }
169
172
  } catch {
170
173
  entry.index = null;
@@ -200,14 +203,25 @@ var ProviderRegistry = class {
200
203
  }
201
204
  /**
202
205
  * Resolve a symbol to `{ provider, ticker }`, or null when nothing can serve it
203
- * yet. Explicit prefix → that provider (must be registered). Bare first
204
- * indexed provider (registration order, `opts.default` first) that contains the
205
- * ticker; `opts.lenient` additionally allows a sole registered provider before
206
- * its index exists.
206
+ * yet. Explicit prefix → that provider (must be registered), else a provider whose
207
+ * index declares that LISTING prefix for that ticker (`NASDAQ:AAPL` routes to the
208
+ * provider serving Nasdaq-listed AAPL TradingView-strict, so `NYSE:AAPL` resolves
209
+ * to nothing rather than auto-correcting). Bare → first indexed provider
210
+ * (registration order, `opts.default` first) that contains the ticker;
211
+ * `opts.lenient` additionally allows a sole registered provider before its index
212
+ * exists.
207
213
  */
208
214
  resolve(raw, opts = {}) {
209
215
  const { provider, ticker } = this.parse(raw);
210
- if (provider) return this.entries.has(provider) ? { provider, ticker } : null;
216
+ if (provider) {
217
+ if (this.entries.has(provider)) return { provider, ticker };
218
+ const key = `${provider}:${normTicker(ticker)}`;
219
+ for (const name of this.candidateOrder(opts.default)) {
220
+ const d = this.entries.get(name).prefixIndex?.get(key);
221
+ if (d) return { provider: name, ticker: d.ticker };
222
+ }
223
+ return null;
224
+ }
211
225
  const norm = normTicker(ticker);
212
226
  for (const name of this.candidateOrder(opts.default)) {
213
227
  if (this.entries.get(name).index?.has(norm)) return { provider: name, ticker };
@@ -273,6 +287,22 @@ var ProviderRegistry = class {
273
287
  }
274
288
  return names;
275
289
  }
290
+ /**
291
+ * The DISPLAY prefix for a resolved symbol — the descriptor's LISTING prefix when the
292
+ * data declares one, else the provider name. This is the single seam every label
293
+ * (legend chip, committed/persisted `PREFIX:TICKER` strings) derives from, which is
294
+ * what makes a non-canonical spelling impossible to display: the form always comes
295
+ * from the data, never from what was typed.
296
+ */
297
+ displayPrefixOf(resolved) {
298
+ const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
299
+ return d?.prefix ?? resolved.provider;
300
+ }
301
+ /** The canonical `PREFIX:TICKER` string for a resolved symbol (descriptor spellings). */
302
+ canonicalSymbol(resolved) {
303
+ const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
304
+ return `${d?.prefix ?? resolved.provider}:${d?.ticker ?? resolved.ticker}`;
305
+ }
276
306
  /** Indexed symbols for one provider (or all, concatenated) — for autocomplete. */
277
307
  symbolsOf(rawName) {
278
308
  if (rawName != null) return this.entries.get(normName(rawName))?.descriptors ?? [];
@@ -296,8 +326,8 @@ var ProviderRegistry = class {
296
326
  };
297
327
 
298
328
  // src/data/BarStore.ts
299
- function seriesKey(provider, symbol, timeframe) {
300
- return `${provider}|${symbol}|${timeframe}`;
329
+ function seriesKey(provider, symbol, timeframe, session) {
330
+ return `${provider}|${symbol}|${timeframe}${session && session !== "regular" ? `|${session}` : ""}`;
301
331
  }
302
332
  function symbolOf(key) {
303
333
  return key.split("|")[1] ?? "";
@@ -395,9 +425,9 @@ function timeframeToMs(timeframe) {
395
425
  }
396
426
 
397
427
  // src/data/CachingDataFeed.ts
398
- function cacheKey(symbol, timeframe) {
428
+ function cacheKey(symbol, timeframe, session) {
399
429
  const { provider, ticker } = parseSymbol(symbol);
400
- return seriesKey(provider ?? "", ticker, timeframe);
430
+ return seriesKey(provider ?? "", ticker, timeframe, session);
401
431
  }
402
432
  var PAGE_BARS = 1e4;
403
433
  var SINGLE_FETCH_BARS = PAGE_BARS + 1;
@@ -409,7 +439,7 @@ var CachingDataFeed = class {
409
439
  async load(cfg) {
410
440
  if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
411
441
  const symbol = cfg.symbol ?? "TEST";
412
- const key = cacheKey(symbol, cfg.timeframe ?? "60");
442
+ const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
413
443
  this.store.retainSymbol(symbol);
414
444
  const cached = this.store.get(key);
415
445
  const n = cfg.bars ?? 500;
@@ -438,7 +468,7 @@ var CachingDataFeed = class {
438
468
  */
439
469
  async loadRange(cfg, range) {
440
470
  if (!this.inner.loadRange) return this.inner.load(cfg);
441
- const key = cacheKey(cfg.symbol ?? "TEST", cfg.timeframe ?? "60");
471
+ const key = cacheKey(cfg.symbol ?? "TEST", cfg.timeframe ?? "60", cfg.session);
442
472
  let cached = this.store.get(key);
443
473
  let newest = cached?.[cached.length - 1];
444
474
  if (range.to != null && newest && range.to < newest.time) {
@@ -596,6 +626,20 @@ var MultiProviderFeed = class {
596
626
  resolveSymbol(raw) {
597
627
  return this.registry.resolve(raw, { default: this.primaryProvider });
598
628
  }
629
+ /**
630
+ * The DISPLAY prefix for `raw` — the descriptor's LISTING prefix when the data
631
+ * declares one (`NASDAQ` for AAPL), else the resolved provider name. Null while
632
+ * nothing resolves the symbol.
633
+ */
634
+ displayPrefix(raw) {
635
+ const resolved = this.resolveSymbol(raw);
636
+ return resolved ? this.registry.displayPrefixOf(resolved) : null;
637
+ }
638
+ /** The canonical `PREFIX:TICKER` form of `raw`, or null while unresolvable. */
639
+ canonicalSymbol(raw) {
640
+ const resolved = this.resolveSymbol(raw);
641
+ return resolved ? this.registry.canonicalSymbol(resolved) : null;
642
+ }
599
643
  /** The registered provider INSTANCE under `name` (undefined if unknown). */
600
644
  providerInstance(name) {
601
645
  return this.registry.get(name);
@@ -731,13 +775,13 @@ var RegistryFetchFeed = class {
731
775
  const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
732
776
  const provider = this.registry.get(name ?? "");
733
777
  if (!provider) return Promise.resolve([]);
734
- return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500 });
778
+ return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
735
779
  }
736
780
  loadRange(cfg, range) {
737
781
  const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
738
782
  const provider = this.registry.get(name ?? "");
739
783
  if (!provider) return Promise.resolve([]);
740
- return safeBars(provider, ticker, cfg.timeframe ?? "60", range);
784
+ return safeBars(provider, ticker, cfg.timeframe ?? "60", { ...range, session: cfg.session });
741
785
  }
742
786
  subscribe(cfg, onBar) {
743
787
  const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
@@ -745,13 +789,13 @@ var RegistryFetchFeed = class {
745
789
  if (!provider) return () => {
746
790
  };
747
791
  const tf = cfg.timeframe ?? "60";
748
- if (provider.subscribe) return provider.subscribe(ticker, tf, onBar);
792
+ if (provider.subscribe) return provider.subscribe(ticker, tf, onBar, cfg.session ? { session: cfg.session } : void 0);
749
793
  let stopped = false;
750
794
  let timer = null;
751
795
  const poll = async () => {
752
796
  if (stopped) return;
753
797
  try {
754
- const bars = await provider.getBars(ticker, tf, { limit: 2 });
798
+ const bars = await provider.getBars(ticker, tf, { limit: 2, session: cfg.session });
755
799
  if (stopped) return;
756
800
  for (const b of bars) onBar(b);
757
801
  } catch {
@@ -4730,10 +4774,12 @@ var CSS4 = `
4730
4774
  color: var(--vela-fg-muted);
4731
4775
  font-size: 11px;
4732
4776
  font-weight: 600;
4733
- cursor: not-allowed;
4734
- opacity: 0.55;
4777
+ cursor: pointer;
4735
4778
  }
4736
- .vela-bb-session-btn.is-active { color: var(--vela-fg); background: var(--vela-surface-elev); opacity: 0.8; }
4779
+ .vela-bb-session-btn:disabled { cursor: not-allowed; opacity: 0.55; }
4780
+ .vela-bb-session-btn:not(:disabled):hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
4781
+ .vela-bb-session-btn.is-active { color: var(--vela-fg); background: var(--vela-surface-elev); }
4782
+ .vela-bb-session-btn.is-active:disabled { opacity: 0.8; }
4737
4783
  .vela-bb-settings {
4738
4784
  all: unset;
4739
4785
  display: inline-flex;
@@ -4753,6 +4799,8 @@ var Bottombar = class {
4753
4799
  constructor(host, opts) {
4754
4800
  this.settingsTip = null;
4755
4801
  this.rangeButtons = /* @__PURE__ */ new Map();
4802
+ this.sessionButtons = /* @__PURE__ */ new Map();
4803
+ this.sessionEl = null;
4756
4804
  this.timer = null;
4757
4805
  this.timezone = opts.timezone;
4758
4806
  const doc = host.ownerDocument;
@@ -4782,12 +4830,19 @@ var Bottombar = class {
4782
4830
  this.tzButton.append(this.clockEl, this.tzLabelEl);
4783
4831
  const session = doc.createElement("span");
4784
4832
  session.className = "vela-bb-session";
4833
+ this.sessionEl = session;
4785
4834
  session.title = "Session \u2014 stocks & ETFs only";
4786
- for (const [label, active] of [["RTH", true], ["ETH", false]]) {
4835
+ for (const [key, label] of [["regular", "RTH"], ["extended", "ETH"]]) {
4787
4836
  const b = doc.createElement("button");
4788
- b.className = "vela-bb-session-btn" + (active ? " is-active" : "");
4837
+ b.className = "vela-bb-session-btn" + (key === "regular" ? " is-active" : "");
4789
4838
  b.textContent = label;
4790
4839
  b.disabled = true;
4840
+ b.addEventListener("click", () => {
4841
+ if (b.disabled) return;
4842
+ this.setSession({ session: key, enabled: true });
4843
+ opts.onSession?.(key);
4844
+ });
4845
+ this.sessionButtons.set(key, b);
4791
4846
  session.appendChild(b);
4792
4847
  }
4793
4848
  const settingsBtn = doc.createElement("button");
@@ -4825,6 +4880,19 @@ var Bottombar = class {
4825
4880
  else delete b.dataset.active;
4826
4881
  }
4827
4882
  }
4883
+ /**
4884
+ * Reflect the ACTIVE chart's session posture. `enabled: false` (a continuous
4885
+ * market, or metadata not landed yet) shows the disabled RTH-active stub — the
4886
+ * pre-session-model look, byte-for-byte. Enabled, the chips become clickable and
4887
+ * the active one tracks the chart's current session.
4888
+ */
4889
+ setSession(state) {
4890
+ if (this.sessionEl) this.sessionEl.title = state.enabled ? "Session \u2014 regular (RTH) vs extended (ETH) hours" : "Session \u2014 stocks & ETFs only";
4891
+ for (const [key, b] of this.sessionButtons) {
4892
+ b.disabled = !state.enabled;
4893
+ b.classList.toggle("is-active", state.enabled ? key === state.session : key === "regular");
4894
+ }
4895
+ }
4828
4896
  destroy() {
4829
4897
  if (this.timer !== null) clearInterval(this.timer);
4830
4898
  this.tzMenu.destroy();
@@ -12423,10 +12491,11 @@ function parseQuery(raw, venues) {
12423
12491
  function onlyOne(matches) {
12424
12492
  return matches.length === 1 ? matches[0] : null;
12425
12493
  }
12494
+ var venueOf = (s) => s.prefix ?? s.provider;
12426
12495
  function filterSymbols(list, query, limit = 100) {
12427
- const venues = [...new Set(list.map((s) => s.provider?.toLowerCase()).filter((p) => !!p))];
12496
+ const venues = [...new Set(list.flatMap((s) => [venueOf(s)?.toLowerCase(), s.provider?.toLowerCase()]).filter((p) => !!p))];
12428
12497
  const { scope, term } = parseQuery(query, venues);
12429
- const pool = scope ? list.filter((s) => s.provider?.toLowerCase() === scope) : list;
12498
+ const pool = scope ? list.filter((s) => venueOf(s)?.toLowerCase() === scope || s.provider?.toLowerCase() === scope) : list;
12430
12499
  const q = term.toUpperCase();
12431
12500
  if (!q) {
12432
12501
  if (scope) return [...pool].sort((a, b) => a.ticker.localeCompare(b.ticker)).slice(0, limit);
@@ -12445,7 +12514,7 @@ function filterSymbols(list, query, limit = 100) {
12445
12514
  if (t.startsWith(q)) prefix.push(s);
12446
12515
  else if (t.includes(q)) substr.push(s);
12447
12516
  else if ((s.description ?? "").toUpperCase().includes(q)) desc.push(s);
12448
- else if (!scope && s.provider?.toLowerCase().includes(qLower)) venue.push(s);
12517
+ else if (!scope && (venueOf(s)?.toLowerCase().includes(qLower) || s.provider?.toLowerCase().includes(qLower))) venue.push(s);
12449
12518
  if (prefix.length >= limit) break;
12450
12519
  }
12451
12520
  return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
@@ -12597,14 +12666,14 @@ var SymbolPicker = class {
12597
12666
  else if (e.key === "ArrowUp") this.moveHighlight(-1);
12598
12667
  else if (e.key === "Enter") {
12599
12668
  const pick = this.rows[this.highlighted];
12600
- if (pick) this.select(pick.ticker, pick.provider, opts.onSelect);
12669
+ if (pick) this.select(pick.ticker, pick.prefix ?? pick.provider, opts.onSelect);
12601
12670
  return;
12602
12671
  } else return;
12603
12672
  e.preventDefault();
12604
12673
  });
12605
12674
  this.list.addEventListener("click", (e) => {
12606
12675
  const row = e.target.closest(".vela-sp-row");
12607
- if (row?.dataset.ticker) this.select(row.dataset.ticker, row.dataset.provider, opts.onSelect);
12676
+ if (row?.dataset.ticker) this.select(row.dataset.ticker, row.dataset.venue, opts.onSelect);
12608
12677
  });
12609
12678
  }
12610
12679
  /** Wire where symbols come from (re-called on every widget rebuild). */
@@ -12621,9 +12690,9 @@ var SymbolPicker = class {
12621
12690
  destroy() {
12622
12691
  this.dialog.destroy();
12623
12692
  }
12624
- select(ticker, provider, onSelect) {
12693
+ select(ticker, venue, onSelect) {
12625
12694
  this.close();
12626
- onSelect(provider ? `${provider.toLowerCase()}:${ticker}` : ticker);
12695
+ onSelect(venue ? `${venue}:${ticker}` : ticker);
12627
12696
  }
12628
12697
  moveHighlight(delta) {
12629
12698
  if (!this.rows.length) return;
@@ -12656,7 +12725,8 @@ var SymbolPicker = class {
12656
12725
  const row = doc.createElement("div");
12657
12726
  row.className = "vela-sp-row";
12658
12727
  row.dataset.ticker = s.ticker;
12659
- if (s.provider) row.dataset.provider = s.provider;
12728
+ const venue = s.prefix ?? s.provider;
12729
+ if (venue) row.dataset.venue = venue;
12660
12730
  const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
12661
12731
  const main = doc.createElement("span");
12662
12732
  main.className = "vela-sp-main";
@@ -12668,11 +12738,11 @@ var SymbolPicker = class {
12668
12738
  d.textContent = s.description ?? (s.type ?? "");
12669
12739
  main.append(t, d);
12670
12740
  row.append(av, main);
12671
- if (s.provider) {
12741
+ if (venue) {
12672
12742
  const badge = doc.createElement("span");
12673
12743
  badge.className = "vela-sp-badge";
12674
- badge.dataset.p = s.provider;
12675
- badge.textContent = s.provider;
12744
+ badge.dataset.p = s.provider ?? venue;
12745
+ badge.textContent = venue;
12676
12746
  row.appendChild(badge);
12677
12747
  }
12678
12748
  this.list.appendChild(row);
@@ -16212,6 +16282,7 @@ function sanitizeCell(raw) {
16212
16282
  if (typeof c.timeframe === "string") out.timeframe = c.timeframe;
16213
16283
  if (typeof c.priceStyle === "string") out.priceStyle = c.priceStyle;
16214
16284
  if (typeof c.bars === "number" && Number.isFinite(c.bars) && c.bars > 0) out.bars = c.bars;
16285
+ if (c.session === "regular" || c.session === "extended") out.session = c.session;
16215
16286
  if (typeof c.watermark === "boolean") out.watermark = c.watermark;
16216
16287
  if (typeof c.indicatorTitles === "boolean") out.indicatorTitles = c.indicatorTitles;
16217
16288
  if (typeof c.indicatorValues === "boolean") out.indicatorValues = c.indicatorValues;
@@ -16845,7 +16916,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
16845
16916
  * an in-flight `setMarket` immediately (the config mutates before the load). */
16846
16917
  marketSnapshot() {
16847
16918
  const m = this.config.market;
16848
- return { symbol: m.symbol, provider: parseSymbol(m.symbol ?? "").provider ?? void 0, timeframe: m.timeframe, bars: m.bars, offline: m.data !== void 0 };
16919
+ return { symbol: m.symbol, provider: parseSymbol(m.symbol ?? "").provider ?? void 0, timeframe: m.timeframe, bars: m.bars, session: m.session, offline: m.data !== void 0 };
16849
16920
  }
16850
16921
  /**
16851
16922
  * Switch the chart's market IN PLACE — no destroy/recreate. The renderer stays
@@ -16864,7 +16935,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
16864
16935
  */
16865
16936
  async setMarket(next) {
16866
16937
  const m = this.config.market;
16867
- const identityChanged = next.symbol !== void 0 && next.symbol !== m.symbol || next.timeframe !== void 0 && next.timeframe !== m.timeframe || next.data !== void 0;
16938
+ const identityChanged = next.symbol !== void 0 && next.symbol !== m.symbol || next.timeframe !== void 0 && next.timeframe !== m.timeframe || // A session switch changes WHICH bars exist (RTH vs ETH) — a full reload,
16939
+ // exactly like a timeframe change; the cache keys the sessions apart.
16940
+ next.session !== void 0 && next.session !== m.session || next.data !== void 0;
16868
16941
  const depthChanged = next.bars !== void 0 && next.bars !== m.bars;
16869
16942
  const depthOnly = depthChanged && !identityChanged && this.rawBars.length > 0 && !m.data?.length && ((next.bars ?? 0) <= this.rawBars.length || typeof this.feed.loadRange === "function");
16870
16943
  if (!identityChanged && !depthChanged) {
@@ -16886,6 +16959,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
16886
16959
  if (next.symbol !== void 0) m.symbol = next.symbol;
16887
16960
  if (next.timeframe !== void 0) m.timeframe = next.timeframe;
16888
16961
  if (next.bars !== void 0) m.bars = next.bars;
16962
+ if (next.session !== void 0) m.session = next.session;
16889
16963
  if (next.data !== void 0) m.data = next.data;
16890
16964
  else if (next.symbol !== void 0) delete m.data;
16891
16965
  m.visibleRange = next.visibleRange;
@@ -18267,6 +18341,17 @@ var DataControl = class {
18267
18341
  resolve(symbol) {
18268
18342
  return this.registry?.resolveSymbol(symbol) ?? null;
18269
18343
  }
18344
+ /**
18345
+ * The DISPLAY prefix for `symbol` — the listing venue its descriptor declares
18346
+ * (`NASDAQ` for AAPL) or the resolved provider name. Null while unresolvable.
18347
+ */
18348
+ displayPrefix(symbol) {
18349
+ return this.registry?.displayPrefix(symbol) ?? null;
18350
+ }
18351
+ /** The canonical `PREFIX:TICKER` form of `symbol`, or null while unresolvable. */
18352
+ canonicalSymbol(symbol) {
18353
+ return this.registry?.canonicalSymbol(symbol) ?? null;
18354
+ }
18270
18355
  /**
18271
18356
  * The registered provider INSTANCE under `name` — the seam for EXTENDED provider
18272
18357
  * surfaces: a provider may implement interfaces beyond the `DataProvider` port
@@ -33946,6 +34031,7 @@ var Vela = class {
33946
34031
  symbol: options.symbol,
33947
34032
  timeframe: options.timeframe,
33948
34033
  bars: options.bars,
34034
+ session: options.session,
33949
34035
  visibleRange: options.visibleRange,
33950
34036
  data: options.data
33951
34037
  },
@@ -34236,6 +34322,9 @@ function resolveElement(container) {
34236
34322
  return element;
34237
34323
  }
34238
34324
 
34325
+ // src/core/options.ts
34326
+ var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
34327
+
34239
34328
  // src/widget/format.ts
34240
34329
  function decimalsFor(ref) {
34241
34330
  const a = Math.abs(ref);
@@ -34914,6 +35003,7 @@ function seedDefaults(opts) {
34914
35003
  timeframe: opts.timeframe,
34915
35004
  bars: opts.bars,
34916
35005
  priceStyle: opts.priceStyle,
35006
+ session: opts.session,
34917
35007
  data: opts.data,
34918
35008
  visibleRange: opts.visibleRange
34919
35009
  };
@@ -34942,6 +35032,8 @@ var ChartCell = class {
34942
35032
  this.lastCrossPrice = null;
34943
35033
  /** The bottombar range chip this cell is framed on (null = none). */
34944
35034
  this.activeRangeId = null;
35035
+ /** Latched verdict of {@link sessionAvailable} (async metadata, sticky per symbol). */
35036
+ this.sessionAvailableFlag = false;
34945
35037
  this.manifest = [];
34946
35038
  /** A restored ledger's manifest entry NAMES, waiting for the manifest to resolve
34947
35039
  * (a pool/persisted cell can be built before the shared manifest has loaded). */
@@ -34961,7 +35053,7 @@ var ChartCell = class {
34961
35053
  this.destroyed = false;
34962
35054
  this.appTheme = deps.theme;
34963
35055
  const symbol = prefixedSymbol(seed);
34964
- this.state = { symbol, provider: parseSymbol(symbol ?? "").provider ?? void 0, timeframe: seed.timeframe, priceStyle: seed.priceStyle, bars: seed.bars };
35056
+ this.state = { symbol, provider: parseSymbol(symbol ?? "").provider ?? void 0, timeframe: seed.timeframe, priceStyle: seed.priceStyle, bars: seed.bars, session: normalizeSession(seed.session) };
34965
35057
  const doc = gridHost.ownerDocument;
34966
35058
  this.host = doc.createElement("div");
34967
35059
  this.host.className = "vela-cell";
@@ -34978,6 +35070,7 @@ var ChartCell = class {
34978
35070
  timeframe: seed.timeframe,
34979
35071
  bars: seed.bars,
34980
35072
  priceStyle: seed.priceStyle,
35073
+ session: normalizeSession(seed.session),
34981
35074
  data: seed.data,
34982
35075
  visibleRange: seed.visibleRange,
34983
35076
  theme: deps.theme,
@@ -35036,6 +35129,10 @@ var ChartCell = class {
35036
35129
  this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "") : null;
35037
35130
  this.statusline?.setMeta(seed.timeframe ?? "60", this.state.provider ?? "");
35038
35131
  this.statusline?.onChart(this.inner);
35132
+ void this.inner.data.ready().then(() => {
35133
+ if (this.inner && this.state.symbol) this.statusline?.setMeta(this.state.timeframe ?? "60", this.inner.data.displayPrefix(this.state.symbol) ?? this.state.provider ?? "");
35134
+ this.refreshSessionAvailable();
35135
+ });
35039
35136
  this.syncStatuslineColors();
35040
35137
  this.contextMenu = new ChartContextMenu(this.host, {
35041
35138
  resetView: () => {
@@ -35156,14 +35253,48 @@ var ChartCell = class {
35156
35253
  this.state.symbol = symbol2;
35157
35254
  this.state.provider = parseSymbol(symbol2).provider ?? void 0;
35158
35255
  this.state.timeframe = timeframe;
35256
+ this.state.session = normalizeSession(this.inner?.market.session);
35159
35257
  this.watermark?.update(symbol2, timeframe);
35160
35258
  this.statusline?.setSymbol(symbol2);
35161
- this.statusline?.setMeta(timeframe, this.inner?.data.resolve(symbol2)?.provider ?? this.state.provider ?? "");
35259
+ this.statusline?.setMeta(timeframe, this.inner?.data.displayPrefix(symbol2) ?? this.state.provider ?? "");
35162
35260
  if (this.inner) this.statusline?.onChart(this.inner);
35163
35261
  this.refreshNativeCatalog();
35262
+ this.refreshSessionAvailable();
35164
35263
  this.deps.onMarketChanged(this.id);
35165
35264
  });
35166
35265
  }
35266
+ /**
35267
+ * Does this cell's market HAVE sessions (RTH/ETH meaningful)? Derived from the
35268
+ * symbol's own metadata (`syminfo.session !== '24x7'`), asynchronously — the
35269
+ * workspace re-projects the shared bottombar when the verdict lands or changes.
35270
+ */
35271
+ get sessionAvailable() {
35272
+ return this.sessionAvailableFlag;
35273
+ }
35274
+ /** This cell's shown session (`regular` when unset — the provider default). */
35275
+ get session() {
35276
+ return normalizeSession(this.state.session) ?? "regular";
35277
+ }
35278
+ /** Switch this cell's shown session in place (a reload — RTH and ETH are different bars). */
35279
+ setSession(session) {
35280
+ if (session === this.session) return;
35281
+ this.state.session = session;
35282
+ this.deps.onStateDirty();
35283
+ void this.inner?.setMarket({ session });
35284
+ }
35285
+ refreshSessionAvailable() {
35286
+ const chart = this.inner;
35287
+ const symbol = this.state.symbol;
35288
+ if (!chart || !symbol) return;
35289
+ void chart.data.symbolInfo(symbol).then((si) => {
35290
+ if (this.inner !== chart) return;
35291
+ const available = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
35292
+ if (available !== this.sessionAvailableFlag) {
35293
+ this.sessionAvailableFlag = available;
35294
+ this.deps.onMarketChanged(this.id);
35295
+ }
35296
+ });
35297
+ }
35167
35298
  /** Show/hide this cell's symbol watermark (persisted per cell). */
35168
35299
  setWatermarkVisible(visible) {
35169
35300
  this.watermarkOn = visible;
@@ -36021,6 +36152,9 @@ var VelaWorkspace = class {
36021
36152
  this.bottombar?.setActiveRange(preset.id);
36022
36153
  },
36023
36154
  onTimezone: (zone) => this.setTimezone(zone),
36155
+ // RTH/ETH acts on the ACTIVE cell (like the range chips): sessions
36156
+ // are a per-chart market dimension, not a shell preference.
36157
+ onSession: (session) => this.active.setSession(session),
36024
36158
  onSettingsClick: () => this.active.chart.renderer.openSettings()
36025
36159
  }) : null;
36026
36160
  this.mobileBar = opts.bottombar !== false ? new MobileBar(this.root, {
@@ -36353,6 +36487,7 @@ var VelaWorkspace = class {
36353
36487
  this.objectTree.setSymbol(cell.symbol);
36354
36488
  this.dock.onChart(cell.chart);
36355
36489
  this.bottombar?.setActiveRange(cell.activeRangeId);
36490
+ this.bottombar?.setSession({ session: cell.session, enabled: cell.sessionAvailable });
36356
36491
  this.indicatorPicker?.sync();
36357
36492
  this.glider.stop();
36358
36493
  const d = cell.chart.drawings;
@@ -36754,6 +36889,7 @@ var VelaWorkspace = class {
36754
36889
  this.mobileBar?.setSymbol(cell.symbol);
36755
36890
  this.mobileBar?.setTimeframe(cell.timeframe);
36756
36891
  this.objectTree.setSymbol(cell.symbol);
36892
+ this.bottombar?.setSession({ session: cell.session, enabled: cell.sessionAvailable });
36757
36893
  }
36758
36894
  /** Trigger ② — a cell's indicator ledger changed: count + picker only if active. */
36759
36895
  onCellIndicatorsChanged(id) {