@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
@@ -1,4 +1,4 @@
1
- import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-BlQ00Fju.cjs';
1
+ import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-BqGeFHtp.cjs';
2
2
 
3
3
  /**
4
4
  * Symbol metadata an engine may need (e.g. Pine `syminfo.*`). Free-form beyond
@@ -26,6 +26,12 @@ interface BarRange {
26
26
  to?: number;
27
27
  /** Max bars. */
28
28
  limit?: number;
29
+ /**
30
+ * Trading session to serve (`'regular'` | `'extended'`) — rides every request on
31
+ * markets that have sessions; absent = the provider's default. A provider without
32
+ * a session concept ignores it.
33
+ */
34
+ session?: string;
29
35
  }
30
36
  interface MarketDataFeed {
31
37
  /** Load the initial history (from a provider or the offline `data` array). */
@@ -64,6 +70,15 @@ interface SymbolDescriptor {
64
70
  description?: string;
65
71
  /** Instrument class, free-form (e.g. `crypto`, `futures`, `stock`). */
66
72
  type?: string;
73
+ /**
74
+ * The instrument's LISTING-venue prefix (`NASDAQ`, `NYSE`, `AMEX`) — a property of the
75
+ * SYMBOL, not of the provider: AAPL is Nasdaq-listed and IBM NYSE-listed even when one
76
+ * provider supplies both tapes. When declared, it is what pickers/legends display, what
77
+ * `PREFIX:TICKER` strings resolve against (TradingView parity: `NYSE:AAPL` is NOT
78
+ * found), and what the canonical committed/persisted form carries. Absent on venues
79
+ * where the provider IS the identity (crypto, fx) — the provider name prefixes those.
80
+ */
81
+ prefix?: string;
67
82
  /** Owning provider name — annotated by the registry aggregation (badges in pickers). */
68
83
  provider?: string;
69
84
  }
@@ -119,9 +134,13 @@ interface DataProvider {
119
134
  /**
120
135
  * Open a true live stream for `ticker`/`timeframe`. Each call to `onBar` delivers
121
136
  * the forming candle (or a freshly-closed one). Returns an unsubscribe fn. Absent
122
- * ⇒ the feed polls `getBars` for ticks instead.
137
+ * ⇒ the feed polls `getBars` for ticks instead. `opts.session` names the trading
138
+ * session the chart is showing (see {@link BarRange.session}) — a provider whose
139
+ * live source cannot filter by session may fall back to polling internally.
123
140
  */
124
- subscribe?(ticker: string, timeframe: string, onBar: (bar: OHLCV) => void): Unsubscribe;
141
+ subscribe?(ticker: string, timeframe: string, onBar: (bar: OHLCV) => void, opts?: {
142
+ session?: string;
143
+ }): Unsubscribe;
125
144
  /** Apply runtime config (e.g. API keys). Absent ⇒ no configuration needed. */
126
145
  configure?(config: unknown): void;
127
146
  /**
@@ -1,4 +1,4 @@
1
- import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-BlQ00Fju.js';
1
+ import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-BqGeFHtp.js';
2
2
 
3
3
  /**
4
4
  * Symbol metadata an engine may need (e.g. Pine `syminfo.*`). Free-form beyond
@@ -26,6 +26,12 @@ interface BarRange {
26
26
  to?: number;
27
27
  /** Max bars. */
28
28
  limit?: number;
29
+ /**
30
+ * Trading session to serve (`'regular'` | `'extended'`) — rides every request on
31
+ * markets that have sessions; absent = the provider's default. A provider without
32
+ * a session concept ignores it.
33
+ */
34
+ session?: string;
29
35
  }
30
36
  interface MarketDataFeed {
31
37
  /** Load the initial history (from a provider or the offline `data` array). */
@@ -64,6 +70,15 @@ interface SymbolDescriptor {
64
70
  description?: string;
65
71
  /** Instrument class, free-form (e.g. `crypto`, `futures`, `stock`). */
66
72
  type?: string;
73
+ /**
74
+ * The instrument's LISTING-venue prefix (`NASDAQ`, `NYSE`, `AMEX`) — a property of the
75
+ * SYMBOL, not of the provider: AAPL is Nasdaq-listed and IBM NYSE-listed even when one
76
+ * provider supplies both tapes. When declared, it is what pickers/legends display, what
77
+ * `PREFIX:TICKER` strings resolve against (TradingView parity: `NYSE:AAPL` is NOT
78
+ * found), and what the canonical committed/persisted form carries. Absent on venues
79
+ * where the provider IS the identity (crypto, fx) — the provider name prefixes those.
80
+ */
81
+ prefix?: string;
67
82
  /** Owning provider name — annotated by the registry aggregation (badges in pickers). */
68
83
  provider?: string;
69
84
  }
@@ -119,9 +134,13 @@ interface DataProvider {
119
134
  /**
120
135
  * Open a true live stream for `ticker`/`timeframe`. Each call to `onBar` delivers
121
136
  * the forming candle (or a freshly-closed one). Returns an unsubscribe fn. Absent
122
- * ⇒ the feed polls `getBars` for ticks instead.
137
+ * ⇒ the feed polls `getBars` for ticks instead. `opts.session` names the trading
138
+ * session the chart is showing (see {@link BarRange.session}) — a provider whose
139
+ * live source cannot filter by session may fall back to polling internally.
123
140
  */
124
- subscribe?(ticker: string, timeframe: string, onBar: (bar: OHLCV) => void): Unsubscribe;
141
+ subscribe?(ticker: string, timeframe: string, onBar: (bar: OHLCV) => void, opts?: {
142
+ session?: string;
143
+ }): Unsubscribe;
125
144
  /** Apply runtime config (e.g. API keys). Absent ⇒ no configuration needed. */
126
145
  configure?(config: unknown): void;
127
146
  /**
@@ -575,8 +575,8 @@ var RendererControl = class {
575
575
  };
576
576
 
577
577
  // src/data/BarStore.ts
578
- function seriesKey(provider, symbol, timeframe) {
579
- return `${provider}|${symbol}|${timeframe}`;
578
+ function seriesKey(provider, symbol, timeframe, session) {
579
+ return `${provider}|${symbol}|${timeframe}${session && session !== "regular" ? `|${session}` : ""}`;
580
580
  }
581
581
  function symbolOf(key) {
582
582
  return key.split("|")[1] ?? "";
@@ -685,7 +685,7 @@ var ProviderRegistry = class {
685
685
  /** Register (or replace) a provider; returns a promise that settles when its index is built. */
686
686
  register(rawName, provider) {
687
687
  const name = normName(rawName);
688
- const entry = { name, provider, index: null, descriptors: [], settled: false, settle: Promise.resolve() };
688
+ const entry = { name, provider, index: null, prefixIndex: null, byTicker: null, descriptors: [], settled: false, settle: Promise.resolve() };
689
689
  this.entries.set(name, entry);
690
690
  entry.settle = this.buildIndex(entry);
691
691
  queueMicrotask(() => this.notify(false));
@@ -698,6 +698,9 @@ var ProviderRegistry = class {
698
698
  const symbols = await entry.provider.listSymbols();
699
699
  entry.descriptors = symbols;
700
700
  entry.index = new Set(symbols.map((s) => normTicker(s.ticker)));
701
+ entry.byTicker = new Map(symbols.map((s) => [normTicker(s.ticker), s]));
702
+ entry.prefixIndex = /* @__PURE__ */ new Map();
703
+ for (const s of symbols) if (s.prefix) entry.prefixIndex.set(`${normName(s.prefix)}:${normTicker(s.ticker)}`, s);
701
704
  }
702
705
  } catch {
703
706
  entry.index = null;
@@ -733,14 +736,25 @@ var ProviderRegistry = class {
733
736
  }
734
737
  /**
735
738
  * Resolve a symbol to `{ provider, ticker }`, or null when nothing can serve it
736
- * yet. Explicit prefix → that provider (must be registered). Bare first
737
- * indexed provider (registration order, `opts.default` first) that contains the
738
- * ticker; `opts.lenient` additionally allows a sole registered provider before
739
- * its index exists.
739
+ * yet. Explicit prefix → that provider (must be registered), else a provider whose
740
+ * index declares that LISTING prefix for that ticker (`NASDAQ:AAPL` routes to the
741
+ * provider serving Nasdaq-listed AAPL TradingView-strict, so `NYSE:AAPL` resolves
742
+ * to nothing rather than auto-correcting). Bare → first indexed provider
743
+ * (registration order, `opts.default` first) that contains the ticker;
744
+ * `opts.lenient` additionally allows a sole registered provider before its index
745
+ * exists.
740
746
  */
741
747
  resolve(raw, opts = {}) {
742
748
  const { provider, ticker } = this.parse(raw);
743
- if (provider) return this.entries.has(provider) ? { provider, ticker } : null;
749
+ if (provider) {
750
+ if (this.entries.has(provider)) return { provider, ticker };
751
+ const key = `${provider}:${normTicker(ticker)}`;
752
+ for (const name of this.candidateOrder(opts.default)) {
753
+ const d = this.entries.get(name).prefixIndex?.get(key);
754
+ if (d) return { provider: name, ticker: d.ticker };
755
+ }
756
+ return null;
757
+ }
744
758
  const norm = normTicker(ticker);
745
759
  for (const name of this.candidateOrder(opts.default)) {
746
760
  if (this.entries.get(name).index?.has(norm)) return { provider: name, ticker };
@@ -806,6 +820,22 @@ var ProviderRegistry = class {
806
820
  }
807
821
  return names;
808
822
  }
823
+ /**
824
+ * The DISPLAY prefix for a resolved symbol — the descriptor's LISTING prefix when the
825
+ * data declares one, else the provider name. This is the single seam every label
826
+ * (legend chip, committed/persisted `PREFIX:TICKER` strings) derives from, which is
827
+ * what makes a non-canonical spelling impossible to display: the form always comes
828
+ * from the data, never from what was typed.
829
+ */
830
+ displayPrefixOf(resolved) {
831
+ const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
832
+ return d?.prefix ?? resolved.provider;
833
+ }
834
+ /** The canonical `PREFIX:TICKER` string for a resolved symbol (descriptor spellings). */
835
+ canonicalSymbol(resolved) {
836
+ const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
837
+ return `${d?.prefix ?? resolved.provider}:${d?.ticker ?? resolved.ticker}`;
838
+ }
809
839
  /** Indexed symbols for one provider (or all, concatenated) — for autocomplete. */
810
840
  symbolsOf(rawName) {
811
841
  if (rawName != null) return this.entries.get(normName(rawName))?.descriptors ?? [];
@@ -829,9 +859,9 @@ var ProviderRegistry = class {
829
859
  };
830
860
 
831
861
  // src/data/CachingDataFeed.ts
832
- function cacheKey(symbol, timeframe) {
862
+ function cacheKey(symbol, timeframe, session) {
833
863
  const { provider, ticker } = parseSymbol(symbol);
834
- return seriesKey(provider ?? "", ticker, timeframe);
864
+ return seriesKey(provider ?? "", ticker, timeframe, session);
835
865
  }
836
866
  var PAGE_BARS = 1e4;
837
867
  var SINGLE_FETCH_BARS = PAGE_BARS + 1;
@@ -843,7 +873,7 @@ var CachingDataFeed = class {
843
873
  async load(cfg) {
844
874
  if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
845
875
  const symbol = cfg.symbol ?? "TEST";
846
- const key = cacheKey(symbol, cfg.timeframe ?? "60");
876
+ const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
847
877
  this.store.retainSymbol(symbol);
848
878
  const cached = this.store.get(key);
849
879
  const n = cfg.bars ?? 500;
@@ -872,7 +902,7 @@ var CachingDataFeed = class {
872
902
  */
873
903
  async loadRange(cfg, range) {
874
904
  if (!this.inner.loadRange) return this.inner.load(cfg);
875
- const key = cacheKey(cfg.symbol ?? "TEST", cfg.timeframe ?? "60");
905
+ const key = cacheKey(cfg.symbol ?? "TEST", cfg.timeframe ?? "60", cfg.session);
876
906
  let cached = this.store.get(key);
877
907
  let newest = cached?.[cached.length - 1];
878
908
  if (range.to != null && newest && range.to < newest.time) {
@@ -1030,6 +1060,20 @@ var MultiProviderFeed = class {
1030
1060
  resolveSymbol(raw) {
1031
1061
  return this.registry.resolve(raw, { default: this.primaryProvider });
1032
1062
  }
1063
+ /**
1064
+ * The DISPLAY prefix for `raw` — the descriptor's LISTING prefix when the data
1065
+ * declares one (`NASDAQ` for AAPL), else the resolved provider name. Null while
1066
+ * nothing resolves the symbol.
1067
+ */
1068
+ displayPrefix(raw) {
1069
+ const resolved = this.resolveSymbol(raw);
1070
+ return resolved ? this.registry.displayPrefixOf(resolved) : null;
1071
+ }
1072
+ /** The canonical `PREFIX:TICKER` form of `raw`, or null while unresolvable. */
1073
+ canonicalSymbol(raw) {
1074
+ const resolved = this.resolveSymbol(raw);
1075
+ return resolved ? this.registry.canonicalSymbol(resolved) : null;
1076
+ }
1033
1077
  /** The registered provider INSTANCE under `name` (undefined if unknown). */
1034
1078
  providerInstance(name) {
1035
1079
  return this.registry.get(name);
@@ -1165,13 +1209,13 @@ var RegistryFetchFeed = class {
1165
1209
  const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
1166
1210
  const provider = this.registry.get(name ?? "");
1167
1211
  if (!provider) return Promise.resolve([]);
1168
- return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500 });
1212
+ return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
1169
1213
  }
1170
1214
  loadRange(cfg, range) {
1171
1215
  const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
1172
1216
  const provider = this.registry.get(name ?? "");
1173
1217
  if (!provider) return Promise.resolve([]);
1174
- return safeBars(provider, ticker, cfg.timeframe ?? "60", range);
1218
+ return safeBars(provider, ticker, cfg.timeframe ?? "60", { ...range, session: cfg.session });
1175
1219
  }
1176
1220
  subscribe(cfg, onBar) {
1177
1221
  const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
@@ -1179,13 +1223,13 @@ var RegistryFetchFeed = class {
1179
1223
  if (!provider) return () => {
1180
1224
  };
1181
1225
  const tf = cfg.timeframe ?? "60";
1182
- if (provider.subscribe) return provider.subscribe(ticker, tf, onBar);
1226
+ if (provider.subscribe) return provider.subscribe(ticker, tf, onBar, cfg.session ? { session: cfg.session } : void 0);
1183
1227
  let stopped = false;
1184
1228
  let timer = null;
1185
1229
  const poll = async () => {
1186
1230
  if (stopped) return;
1187
1231
  try {
1188
- const bars = await provider.getBars(ticker, tf, { limit: 2 });
1232
+ const bars = await provider.getBars(ticker, tf, { limit: 2, session: cfg.session });
1189
1233
  if (stopped) return;
1190
1234
  for (const b of bars) onBar(b);
1191
1235
  } catch {
@@ -1235,6 +1279,17 @@ var DataControl = class {
1235
1279
  resolve(symbol) {
1236
1280
  return this.registry?.resolveSymbol(symbol) ?? null;
1237
1281
  }
1282
+ /**
1283
+ * The DISPLAY prefix for `symbol` — the listing venue its descriptor declares
1284
+ * (`NASDAQ` for AAPL) or the resolved provider name. Null while unresolvable.
1285
+ */
1286
+ displayPrefix(symbol) {
1287
+ return this.registry?.displayPrefix(symbol) ?? null;
1288
+ }
1289
+ /** The canonical `PREFIX:TICKER` form of `symbol`, or null while unresolvable. */
1290
+ canonicalSymbol(symbol) {
1291
+ return this.registry?.canonicalSymbol(symbol) ?? null;
1292
+ }
1238
1293
  /**
1239
1294
  * The registered provider INSTANCE under `name` — the seam for EXTENDED provider
1240
1295
  * surfaces: a provider may implement interfaces beyond the `DataProvider` port
@@ -18903,7 +18958,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
18903
18958
  * an in-flight `setMarket` immediately (the config mutates before the load). */
18904
18959
  marketSnapshot() {
18905
18960
  const m = this.config.market;
18906
- return { symbol: m.symbol, provider: parseSymbol(m.symbol ?? "").provider ?? void 0, timeframe: m.timeframe, bars: m.bars, offline: m.data !== void 0 };
18961
+ 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 };
18907
18962
  }
18908
18963
  /**
18909
18964
  * Switch the chart's market IN PLACE — no destroy/recreate. The renderer stays
@@ -18922,7 +18977,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
18922
18977
  */
18923
18978
  async setMarket(next) {
18924
18979
  const m = this.config.market;
18925
- const identityChanged = next.symbol !== void 0 && next.symbol !== m.symbol || next.timeframe !== void 0 && next.timeframe !== m.timeframe || next.data !== void 0;
18980
+ 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,
18981
+ // exactly like a timeframe change; the cache keys the sessions apart.
18982
+ next.session !== void 0 && next.session !== m.session || next.data !== void 0;
18926
18983
  const depthChanged = next.bars !== void 0 && next.bars !== m.bars;
18927
18984
  const depthOnly = depthChanged && !identityChanged && this.rawBars.length > 0 && !m.data?.length && ((next.bars ?? 0) <= this.rawBars.length || typeof this.feed.loadRange === "function");
18928
18985
  if (!identityChanged && !depthChanged) {
@@ -18944,6 +19001,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
18944
19001
  if (next.symbol !== void 0) m.symbol = next.symbol;
18945
19002
  if (next.timeframe !== void 0) m.timeframe = next.timeframe;
18946
19003
  if (next.bars !== void 0) m.bars = next.bars;
19004
+ if (next.session !== void 0) m.session = next.session;
18947
19005
  if (next.data !== void 0) m.data = next.data;
18948
19006
  else if (next.symbol !== void 0) delete m.data;
18949
19007
  m.visibleRange = next.visibleRange;
@@ -20314,6 +20372,7 @@ var Vela = class {
20314
20372
  symbol: options.symbol,
20315
20373
  timeframe: options.timeframe,
20316
20374
  bars: options.bars,
20375
+ session: options.session,
20317
20376
  visibleRange: options.visibleRange,
20318
20377
  data: options.data
20319
20378
  },
@@ -1,4 +1,4 @@
1
- import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel } from './chunk-D6WM5IUE.js';
1
+ import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel } from './chunk-4MQEG67Z.js';
2
2
  import { chartType, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, getDrawingType } from './chunk-ZNL2X6U2.js';
3
3
  import { Tooltip, Menu, Dialog, Drawer } from './chunk-QWIEKZRE.js';
4
4
  import { iconMarkup, registerIcon, injectStyles, iconEl, SESSION_PRE, SESSION_POST, SESSION_OFF, icon, categoricalColor } from './chunk-EMS6PO2T.js';
@@ -1468,10 +1468,12 @@ var CSS5 = `
1468
1468
  color: var(--vela-fg-muted);
1469
1469
  font-size: 11px;
1470
1470
  font-weight: 600;
1471
- cursor: not-allowed;
1472
- opacity: 0.55;
1471
+ cursor: pointer;
1473
1472
  }
1474
- .vela-bb-session-btn.is-active { color: var(--vela-fg); background: var(--vela-surface-elev); opacity: 0.8; }
1473
+ .vela-bb-session-btn:disabled { cursor: not-allowed; opacity: 0.55; }
1474
+ .vela-bb-session-btn:not(:disabled):hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
1475
+ .vela-bb-session-btn.is-active { color: var(--vela-fg); background: var(--vela-surface-elev); }
1476
+ .vela-bb-session-btn.is-active:disabled { opacity: 0.8; }
1475
1477
  .vela-bb-settings {
1476
1478
  all: unset;
1477
1479
  display: inline-flex;
@@ -1491,6 +1493,8 @@ var Bottombar = class {
1491
1493
  constructor(host, opts) {
1492
1494
  this.settingsTip = null;
1493
1495
  this.rangeButtons = /* @__PURE__ */ new Map();
1496
+ this.sessionButtons = /* @__PURE__ */ new Map();
1497
+ this.sessionEl = null;
1494
1498
  this.timer = null;
1495
1499
  this.timezone = opts.timezone;
1496
1500
  const doc = host.ownerDocument;
@@ -1520,12 +1524,19 @@ var Bottombar = class {
1520
1524
  this.tzButton.append(this.clockEl, this.tzLabelEl);
1521
1525
  const session = doc.createElement("span");
1522
1526
  session.className = "vela-bb-session";
1527
+ this.sessionEl = session;
1523
1528
  session.title = "Session \u2014 stocks & ETFs only";
1524
- for (const [label, active] of [["RTH", true], ["ETH", false]]) {
1529
+ for (const [key, label] of [["regular", "RTH"], ["extended", "ETH"]]) {
1525
1530
  const b = doc.createElement("button");
1526
- b.className = "vela-bb-session-btn" + (active ? " is-active" : "");
1531
+ b.className = "vela-bb-session-btn" + (key === "regular" ? " is-active" : "");
1527
1532
  b.textContent = label;
1528
1533
  b.disabled = true;
1534
+ b.addEventListener("click", () => {
1535
+ if (b.disabled) return;
1536
+ this.setSession({ session: key, enabled: true });
1537
+ opts.onSession?.(key);
1538
+ });
1539
+ this.sessionButtons.set(key, b);
1529
1540
  session.appendChild(b);
1530
1541
  }
1531
1542
  const settingsBtn = doc.createElement("button");
@@ -1563,6 +1574,19 @@ var Bottombar = class {
1563
1574
  else delete b.dataset.active;
1564
1575
  }
1565
1576
  }
1577
+ /**
1578
+ * Reflect the ACTIVE chart's session posture. `enabled: false` (a continuous
1579
+ * market, or metadata not landed yet) shows the disabled RTH-active stub — the
1580
+ * pre-session-model look, byte-for-byte. Enabled, the chips become clickable and
1581
+ * the active one tracks the chart's current session.
1582
+ */
1583
+ setSession(state) {
1584
+ if (this.sessionEl) this.sessionEl.title = state.enabled ? "Session \u2014 regular (RTH) vs extended (ETH) hours" : "Session \u2014 stocks & ETFs only";
1585
+ for (const [key, b] of this.sessionButtons) {
1586
+ b.disabled = !state.enabled;
1587
+ b.classList.toggle("is-active", state.enabled ? key === state.session : key === "regular");
1588
+ }
1589
+ }
1566
1590
  destroy() {
1567
1591
  if (this.timer !== null) clearInterval(this.timer);
1568
1592
  this.tzMenu.destroy();
@@ -1605,10 +1629,11 @@ function parseQuery(raw, venues) {
1605
1629
  function onlyOne(matches) {
1606
1630
  return matches.length === 1 ? matches[0] : null;
1607
1631
  }
1632
+ var venueOf = (s) => s.prefix ?? s.provider;
1608
1633
  function filterSymbols(list, query, limit = 100) {
1609
- const venues = [...new Set(list.map((s) => s.provider?.toLowerCase()).filter((p) => !!p))];
1634
+ const venues = [...new Set(list.flatMap((s) => [venueOf(s)?.toLowerCase(), s.provider?.toLowerCase()]).filter((p) => !!p))];
1610
1635
  const { scope, term } = parseQuery(query, venues);
1611
- const pool = scope ? list.filter((s) => s.provider?.toLowerCase() === scope) : list;
1636
+ const pool = scope ? list.filter((s) => venueOf(s)?.toLowerCase() === scope || s.provider?.toLowerCase() === scope) : list;
1612
1637
  const q = term.toUpperCase();
1613
1638
  if (!q) {
1614
1639
  if (scope) return [...pool].sort((a, b) => a.ticker.localeCompare(b.ticker)).slice(0, limit);
@@ -1627,7 +1652,7 @@ function filterSymbols(list, query, limit = 100) {
1627
1652
  if (t.startsWith(q)) prefix.push(s);
1628
1653
  else if (t.includes(q)) substr.push(s);
1629
1654
  else if ((s.description ?? "").toUpperCase().includes(q)) desc.push(s);
1630
- else if (!scope && s.provider?.toLowerCase().includes(qLower)) venue.push(s);
1655
+ else if (!scope && (venueOf(s)?.toLowerCase().includes(qLower) || s.provider?.toLowerCase().includes(qLower))) venue.push(s);
1631
1656
  if (prefix.length >= limit) break;
1632
1657
  }
1633
1658
  return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
@@ -1779,14 +1804,14 @@ var SymbolPicker = class {
1779
1804
  else if (e.key === "ArrowUp") this.moveHighlight(-1);
1780
1805
  else if (e.key === "Enter") {
1781
1806
  const pick = this.rows[this.highlighted];
1782
- if (pick) this.select(pick.ticker, pick.provider, opts.onSelect);
1807
+ if (pick) this.select(pick.ticker, pick.prefix ?? pick.provider, opts.onSelect);
1783
1808
  return;
1784
1809
  } else return;
1785
1810
  e.preventDefault();
1786
1811
  });
1787
1812
  this.list.addEventListener("click", (e) => {
1788
1813
  const row = e.target.closest(".vela-sp-row");
1789
- if (row?.dataset.ticker) this.select(row.dataset.ticker, row.dataset.provider, opts.onSelect);
1814
+ if (row?.dataset.ticker) this.select(row.dataset.ticker, row.dataset.venue, opts.onSelect);
1790
1815
  });
1791
1816
  }
1792
1817
  /** Wire where symbols come from (re-called on every widget rebuild). */
@@ -1803,9 +1828,9 @@ var SymbolPicker = class {
1803
1828
  destroy() {
1804
1829
  this.dialog.destroy();
1805
1830
  }
1806
- select(ticker, provider, onSelect) {
1831
+ select(ticker, venue, onSelect) {
1807
1832
  this.close();
1808
- onSelect(provider ? `${provider.toLowerCase()}:${ticker}` : ticker);
1833
+ onSelect(venue ? `${venue}:${ticker}` : ticker);
1809
1834
  }
1810
1835
  moveHighlight(delta) {
1811
1836
  if (!this.rows.length) return;
@@ -1838,7 +1863,8 @@ var SymbolPicker = class {
1838
1863
  const row = doc.createElement("div");
1839
1864
  row.className = "vela-sp-row";
1840
1865
  row.dataset.ticker = s.ticker;
1841
- if (s.provider) row.dataset.provider = s.provider;
1866
+ const venue = s.prefix ?? s.provider;
1867
+ if (venue) row.dataset.venue = venue;
1842
1868
  const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
1843
1869
  const main = doc.createElement("span");
1844
1870
  main.className = "vela-sp-main";
@@ -1850,11 +1876,11 @@ var SymbolPicker = class {
1850
1876
  d.textContent = s.description ?? (s.type ?? "");
1851
1877
  main.append(t, d);
1852
1878
  row.append(av, main);
1853
- if (s.provider) {
1879
+ if (venue) {
1854
1880
  const badge = doc.createElement("span");
1855
1881
  badge.className = "vela-sp-badge";
1856
- badge.dataset.p = s.provider;
1857
- badge.textContent = s.provider;
1882
+ badge.dataset.p = s.provider ?? venue;
1883
+ badge.textContent = venue;
1858
1884
  row.appendChild(badge);
1859
1885
  }
1860
1886
  this.list.appendChild(row);
@@ -4264,6 +4290,7 @@ function sanitizeCell(raw) {
4264
4290
  if (typeof c.timeframe === "string") out.timeframe = c.timeframe;
4265
4291
  if (typeof c.priceStyle === "string") out.priceStyle = c.priceStyle;
4266
4292
  if (typeof c.bars === "number" && Number.isFinite(c.bars) && c.bars > 0) out.bars = c.bars;
4293
+ if (c.session === "regular" || c.session === "extended") out.session = c.session;
4267
4294
  if (typeof c.watermark === "boolean") out.watermark = c.watermark;
4268
4295
  if (typeof c.indicatorTitles === "boolean") out.indicatorTitles = c.indicatorTitles;
4269
4296
  if (typeof c.indicatorValues === "boolean") out.indicatorValues = c.indicatorValues;
@@ -4373,6 +4400,9 @@ function indicatorLedger(i) {
4373
4400
  };
4374
4401
  }
4375
4402
 
4403
+ // src/core/options.ts
4404
+ var normalizeSession = (v) => v === "regular" || v === "extended" ? v : void 0;
4405
+
4376
4406
  // src/widget/layout-mode.ts
4377
4407
  var MOBILE_BREAKPOINT_PX = 640;
4378
4408
  var COARSE_BREAKPOINT_PX = 920;
@@ -5739,4 +5769,4 @@ var Toast = class {
5739
5769
  }
5740
5770
  };
5741
5771
 
5742
- export { Bottombar, ChartContextMenu, DataWindow, DrawingPill, DrawingsDrawer, Glider, IndicatorPicker, LayoutModeController, MobileBar, MoreDrawer, ObjectTree, PAN_FAST, PanelDock, PriceScaleDrawer, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeDrawer, TimeframeQuick, TimezoneDrawer, Toast, Topbar, Watermark, WidgetHistory, ZOOM_IN, ZOOM_OUT, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, indicatorLedger, legacyWidgetState, loadPersisted, localStorageAdapter, parsePersisted, parseTimeframe, prefixedSymbol, priceStyleIcon, priceStyleLabel, resolveIndicators, sanitizeState, savePersisted, statuslineInkOf, timeframeLabel, timeframeMs, toolShortcutHints };
5772
+ export { Bottombar, ChartContextMenu, DataWindow, DrawingPill, DrawingsDrawer, Glider, IndicatorPicker, LayoutModeController, MobileBar, MoreDrawer, ObjectTree, PAN_FAST, PanelDock, PriceScaleDrawer, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeDrawer, TimeframeQuick, TimezoneDrawer, Toast, Topbar, Watermark, WidgetHistory, ZOOM_IN, ZOOM_OUT, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, indicatorLedger, legacyWidgetState, loadPersisted, localStorageAdapter, normalizeSession, parsePersisted, parseTimeframe, prefixedSymbol, priceStyleIcon, priceStyleLabel, resolveIndicators, sanitizeState, savePersisted, statuslineInkOf, timeframeLabel, timeframeMs, toolShortcutHints };
@@ -1,5 +1,5 @@
1
- import { s as Millis, ac as InputSchema, ab as IndicatorMeta, P as PriceStyle, O as OHLCV, l as InputValue, j as IndicatorModel, i as MoveTarget, ay as SeriesSpec, a9 as Fill, B as Background, aq as PriceLine, $ as DrawingLine, X as DrawingBox, _ as DrawingLabel, a3 as DrawingPolyline, a0 as DrawingLinefill, a5 as DrawingTable, f as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, u as DrawingTypeKey, t as SnapMode, a1 as DrawingMode, I as IChartRenderer, R as RendererCapabilities, n as LegendActionView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, q as DataWindowReadout, m as SymbolPickerFn, aK as PaneInfo, a8 as DrawingsOption, D as Drawing, b as VelaOptions, w as AddIndicatorOptions, am as MarketSwitch, al as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-BlQ00Fju.js';
2
- import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-Cqc_BaJ9.js';
1
+ import { t as Millis, ad as InputSchema, ac as IndicatorMeta, P as PriceStyle, O as OHLCV, m as InputValue, k as IndicatorModel, j as MoveTarget, az as SeriesSpec, aa as Fill, B as Background, ar as PriceLine, a0 as DrawingLine, Y as DrawingBox, $ as DrawingLabel, a4 as DrawingPolyline, a1 as DrawingLinefill, a6 as DrawingTable, g as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, v as DrawingTypeKey, u as SnapMode, a2 as DrawingMode, I as IChartRenderer, R as RendererCapabilities, o as LegendActionView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, r as DataWindowReadout, n as SymbolPickerFn, aL as PaneInfo, a9 as DrawingsOption, D as Drawing, b as VelaOptions, x as AddIndicatorOptions, an as MarketSwitch, am as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-BqGeFHtp.cjs';
2
+ import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-BNKtYU5V.cjs';
3
3
 
4
4
  /**
5
5
  * A strategy's broker state at ONE bar — the flat summary a host reads while a script
@@ -344,6 +344,13 @@ declare class DataControl {
344
344
  providers(): ProviderInfo[];
345
345
  /** Resolve a symbol string to `{ provider, ticker }`, or null if nothing serves it. */
346
346
  resolve(symbol: string): Resolved | null;
347
+ /**
348
+ * The DISPLAY prefix for `symbol` — the listing venue its descriptor declares
349
+ * (`NASDAQ` for AAPL) or the resolved provider name. Null while unresolvable.
350
+ */
351
+ displayPrefix(symbol: string): string | null;
352
+ /** The canonical `PREFIX:TICKER` form of `symbol`, or null while unresolvable. */
353
+ canonicalSymbol(symbol: string): string | null;
347
354
  /**
348
355
  * The registered provider INSTANCE under `name` — the seam for EXTENDED provider
349
356
  * surfaces: a provider may implement interfaces beyond the `DataProvider` port
@@ -1,5 +1,5 @@
1
- import { s as Millis, ac as InputSchema, ab as IndicatorMeta, P as PriceStyle, O as OHLCV, l as InputValue, j as IndicatorModel, i as MoveTarget, ay as SeriesSpec, a9 as Fill, B as Background, aq as PriceLine, $ as DrawingLine, X as DrawingBox, _ as DrawingLabel, a3 as DrawingPolyline, a0 as DrawingLinefill, a5 as DrawingTable, f as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, u as DrawingTypeKey, t as SnapMode, a1 as DrawingMode, I as IChartRenderer, R as RendererCapabilities, n as LegendActionView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, q as DataWindowReadout, m as SymbolPickerFn, aK as PaneInfo, a8 as DrawingsOption, D as Drawing, b as VelaOptions, w as AddIndicatorOptions, am as MarketSwitch, al as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-BlQ00Fju.cjs';
2
- import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-DgS2UyMc.cjs';
1
+ import { t as Millis, ad as InputSchema, ac as IndicatorMeta, P as PriceStyle, O as OHLCV, m as InputValue, k as IndicatorModel, j as MoveTarget, az as SeriesSpec, aa as Fill, B as Background, ar as PriceLine, a0 as DrawingLine, Y as DrawingBox, $ as DrawingLabel, a4 as DrawingPolyline, a1 as DrawingLinefill, a6 as DrawingTable, g as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, v as DrawingTypeKey, u as SnapMode, a2 as DrawingMode, I as IChartRenderer, R as RendererCapabilities, o as LegendActionView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, r as DataWindowReadout, n as SymbolPickerFn, aL as PaneInfo, a9 as DrawingsOption, D as Drawing, b as VelaOptions, x as AddIndicatorOptions, an as MarketSwitch, am as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-BqGeFHtp.js';
2
+ import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-gJjN_0eh.js';
3
3
 
4
4
  /**
5
5
  * A strategy's broker state at ONE bar — the flat summary a host reads while a script
@@ -344,6 +344,13 @@ declare class DataControl {
344
344
  providers(): ProviderInfo[];
345
345
  /** Resolve a symbol string to `{ provider, ticker }`, or null if nothing serves it. */
346
346
  resolve(symbol: string): Resolved | null;
347
+ /**
348
+ * The DISPLAY prefix for `symbol` — the listing venue its descriptor declares
349
+ * (`NASDAQ` for AAPL) or the resolved provider name. Null while unresolvable.
350
+ */
351
+ displayPrefix(symbol: string): string | null;
352
+ /** The canonical `PREFIX:TICKER` form of `symbol`, or null while unresolvable. */
353
+ canonicalSymbol(symbol: string): string | null;
347
354
  /**
348
355
  * The registered provider INSTANCE under `name` — the seam for EXTENDED provider
349
356
  * surfaces: a provider may implement interfaces beyond the `DataProvider` port
@@ -1,6 +1,6 @@
1
- import { V as VisibleRangePreset } from './options-BlQ00Fju.js';
2
- import { D as DataProvider } from './DataProvider-Cqc_BaJ9.js';
3
- import { S as ScriptingEngine, V as Vela } from './contributions-DuIxJWeH.js';
1
+ import { V as VisibleRangePreset } from './options-BqGeFHtp.js';
2
+ import { D as DataProvider } from './DataProvider-gJjN_0eh.js';
3
+ import { S as ScriptingEngine, V as Vela } from './contributions-lPAgo9Cu.js';
4
4
 
5
5
  interface RangePreset {
6
6
  /** Button label. */
@@ -27,6 +27,8 @@ interface BottombarOptions {
27
27
  timezone: string;
28
28
  onRange: (preset: RangePreset) => void;
29
29
  onTimezone: (zone: string) => void;
30
+ /** RTH/ETH toggled by the user. Fires only while the toggle is ENABLED (see {@link Bottombar.setSession}). */
31
+ onSession?: (session: 'regular' | 'extended') => void;
30
32
  onSettingsClick?: () => void;
31
33
  }
32
34
  declare class Bottombar {
@@ -37,12 +39,24 @@ declare class Bottombar {
37
39
  private readonly tzMenu;
38
40
  private readonly settingsTip;
39
41
  private readonly rangeButtons;
42
+ private readonly sessionButtons;
43
+ private sessionEl;
40
44
  private timezone;
41
45
  private timer;
42
46
  constructor(host: HTMLElement, opts: BottombarOptions);
43
47
  setTimezone(zone: string): void;
44
48
  /** Highlight (or clear with null) the active range chip — cleared on manual tf changes. */
45
49
  setActiveRange(id: string | null): void;
50
+ /**
51
+ * Reflect the ACTIVE chart's session posture. `enabled: false` (a continuous
52
+ * market, or metadata not landed yet) shows the disabled RTH-active stub — the
53
+ * pre-session-model look, byte-for-byte. Enabled, the chips become clickable and
54
+ * the active one tracks the chart's current session.
55
+ */
56
+ setSession(state: {
57
+ session: 'regular' | 'extended';
58
+ enabled: boolean;
59
+ }): void;
46
60
  destroy(): void;
47
61
  private tzItems;
48
62
  private tick;
@@ -130,6 +144,8 @@ interface CellState {
130
144
  timeframe?: string;
131
145
  priceStyle?: string;
132
146
  bars?: number;
147
+ /** Trading session shown (`'extended'` persisted; absent = regular, the default). */
148
+ session?: string;
133
149
  /** Symbol watermark visibility — a per-chart display pref. */
134
150
  watermark?: boolean;
135
151
  /** Indicator titles (the in-chart legend rows) visibility — a per-chart display pref. */
@@ -212,6 +228,8 @@ interface PersistedState {
212
228
  priceStyle?: string;
213
229
  timezone?: string;
214
230
  bars?: string;
231
+ /** Trading session (`regular` | `extended`) — URL param `session`. */
232
+ session?: string;
215
233
  watermark?: string;
216
234
  /** Comma-joined favorite drawing-tool types. */
217
235
  favorites?: string;