@luxalgo/vela 0.6.9 → 0.6.10

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 (45) hide show
  1. package/dist/{DataProvider-8Z95Q-RJ.d.cts → DataProvider-CWmp31dA.d.ts} +49 -1
  2. package/dist/{DataProvider-DExJrfut.d.ts → DataProvider-l_eLMly_.d.cts} +49 -1
  3. package/dist/{chunk-EQCHJZOT.js → chunk-4KZVZ7QQ.js} +1 -1
  4. package/dist/{chunk-6WDDVMBJ.js → chunk-FFOP37FQ.js} +577 -321
  5. package/dist/{chunk-62SVGONC.js → chunk-G77Y7LK2.js} +2 -2
  6. package/dist/{chunk-WFSZBX3R.js → chunk-KG4YT3TI.js} +3 -1
  7. package/dist/{chunk-FCVIG7JG.js → chunk-MHY7MVXH.js} +5 -1
  8. package/dist/{chunk-STHSKXOR.js → chunk-NMTQXNT4.js} +591 -141
  9. package/dist/{contributions-D7PVZO2i.d.ts → contributions-BCz6Dr6a.d.ts} +6 -6
  10. package/dist/{contributions-C1U2Krwg.d.cts → contributions-D9vTzm5p.d.cts} +6 -6
  11. package/dist/index.cjs +580 -318
  12. package/dist/index.d.cts +31 -10
  13. package/dist/index.d.ts +31 -10
  14. package/dist/index.js +4 -4
  15. package/dist/{options-FM0peknS.d.ts → options-DSqHsQyN.d.cts} +6 -6
  16. package/dist/{options-FM0peknS.d.cts → options-DSqHsQyN.d.ts} +6 -6
  17. package/dist/{plugin-DfVqBz9p.d.cts → plugin-Bt8hLR8Z.d.cts} +3 -3
  18. package/dist/{plugin-7bkF32Rk.d.ts → plugin-CH7pfMrE.d.ts} +3 -3
  19. package/dist/plugin.cjs +3 -1
  20. package/dist/plugin.d.cts +4 -4
  21. package/dist/plugin.d.ts +4 -4
  22. package/dist/plugin.js +2 -2
  23. package/dist/providers/binance.d.cts +2 -2
  24. package/dist/providers/binance.d.ts +2 -2
  25. package/dist/providers/coinbase.d.cts +2 -2
  26. package/dist/providers/coinbase.d.ts +2 -2
  27. package/dist/providers/hyperliquid.d.cts +2 -2
  28. package/dist/providers/hyperliquid.d.ts +2 -2
  29. package/dist/{statusline-zdF4eZLr.d.ts → statusline-5K7y4Ya2.d.ts} +3 -3
  30. package/dist/{statusline-DOPiT6I6.d.cts → statusline-TYLQmoeh.d.cts} +3 -3
  31. package/dist/ui.cjs +7 -1
  32. package/dist/ui.d.cts +3 -1
  33. package/dist/ui.d.ts +3 -1
  34. package/dist/ui.js +3 -3
  35. package/dist/vela.global.js +580 -318
  36. package/dist/vela.global.min.js +50 -48
  37. package/dist/widget.cjs +1052 -340
  38. package/dist/widget.d.cts +17 -6
  39. package/dist/widget.d.ts +17 -6
  40. package/dist/widget.js +7 -7
  41. package/dist/workspace.cjs +1052 -340
  42. package/dist/workspace.d.cts +89 -5
  43. package/dist/workspace.d.ts +89 -5
  44. package/dist/workspace.js +6 -6
  45. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
- import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, LEGEND_AT_TOP_ATTR, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-6WDDVMBJ.js';
2
- import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, widgetAttachments, getDrawingType } from './chunk-EQCHJZOT.js';
3
- import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-62SVGONC.js';
4
- import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-FCVIG7JG.js';
5
- import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, SESSION_OFF, SESSION_POST, SESSION_PRE, categoricalColor } from './chunk-WFSZBX3R.js';
1
+ import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-FFOP37FQ.js';
2
+ import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, widgetAttachments, getDrawingType } from './chunk-4KZVZ7QQ.js';
3
+ import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-G77Y7LK2.js';
4
+ import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-MHY7MVXH.js';
5
+ import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, SESSION_OFF, SESSION_POST, SESSION_PRE, icon, categoricalColor } from './chunk-KG4YT3TI.js';
6
6
  import { baseOf } from './chunk-W4EJWLEO.js';
7
7
 
8
8
  // src/widget/timeframe.ts
@@ -654,7 +654,7 @@ var Topbar = class {
654
654
  this.leftActionsHost.className = "vela-widget-actions-left";
655
655
  this.panelsHost = doc.createElement("span");
656
656
  this.panelsHost.className = "vela-widget-panels";
657
- const tool = (cls, icon, tip, onClick) => this.toolButton(cls, icon, tip, onClick, this.tooltips);
657
+ const tool = (cls, icon2, tip, onClick) => this.toolButton(cls, icon2, tip, onClick, this.tooltips);
658
658
  if (vis("undo-redo")) {
659
659
  this.undoBtn = tool("vela-widget-undo", "undo", "Undo", opts.onUndoClick);
660
660
  this.redoBtn = tool("vela-widget-redo", "redo", "Redo", opts.onRedoClick);
@@ -852,8 +852,8 @@ var Topbar = class {
852
852
  }
853
853
  renderStyleButton(doc) {
854
854
  this.styleButton.replaceChildren();
855
- const icon = priceStyleIcon(this.priceStyle);
856
- if (icon) this.styleButton.appendChild(iconEl(icon, doc));
855
+ const icon2 = priceStyleIcon(this.priceStyle);
856
+ if (icon2) this.styleButton.appendChild(iconEl(icon2, doc));
857
857
  else this.styleButton.appendChild(doc.createTextNode(priceStyleLabel(this.priceStyle)));
858
858
  this.styleButton.setAttribute("aria-label", `Chart style \u2014 ${priceStyleLabel(this.priceStyle)}`);
859
859
  }
@@ -945,11 +945,11 @@ var Topbar = class {
945
945
  this.el.remove();
946
946
  }
947
947
  /** One icon-only tool button with its kit tooltip, parked in `sink` for disposal. */
948
- toolButton(cls, icon, tip, onClick, sink) {
948
+ toolButton(cls, icon2, tip, onClick, sink) {
949
949
  const doc = this.el.ownerDocument;
950
950
  const b = doc.createElement("button");
951
951
  b.className = `vela-widget-tool ${cls}`;
952
- b.appendChild(iconEl(icon, doc));
952
+ b.appendChild(iconEl(icon2, doc));
953
953
  b.setAttribute("aria-label", tip);
954
954
  sink.push(new Tooltip(b, { content: tip, triggerId: `vela-tool-${cls}`, host: this.host }));
955
955
  if (onClick) b.addEventListener("click", onClick);
@@ -1629,13 +1629,13 @@ var MenuBuilder = class {
1629
1629
  this.actions = /* @__PURE__ */ new Map();
1630
1630
  this.seq = 0;
1631
1631
  }
1632
- entry(label, icon, run, separatorBefore = false) {
1632
+ entry(label, icon2, run, separatorBefore = false) {
1633
1633
  const id = this.next();
1634
1634
  this.actions.set(id, run);
1635
- return { id, label, icon, separatorBefore };
1635
+ return { id, label, icon: icon2, separatorBefore };
1636
1636
  }
1637
- submenu(label, icon, submenu, separatorBefore = false) {
1638
- return { id: this.next(), label, icon, submenu, separatorBefore };
1637
+ submenu(label, icon2, submenu, separatorBefore = false) {
1638
+ return { id: this.next(), label, icon: icon2, submenu, separatorBefore };
1639
1639
  }
1640
1640
  next() {
1641
1641
  this.seq += 1;
@@ -1872,8 +1872,8 @@ var ObjectTree = class extends SidePanel {
1872
1872
  const acts = doc.createElement("span");
1873
1873
  acts.className = "vela-ot-acts";
1874
1874
  head.appendChild(acts);
1875
- const op = (icon, title, run) => {
1876
- acts.appendChild(this.btn({ icon, title, run }));
1875
+ const op = (icon2, title, run) => {
1876
+ acts.appendChild(this.btn({ icon: icon2, title, run }));
1877
1877
  };
1878
1878
  if (pane.kind !== "price") {
1879
1879
  if (pane.order > 1) op("arrow-up", "Move pane up", () => chart.panes.move(pane.id, "up"));
@@ -1888,8 +1888,8 @@ var ObjectTree = class extends SidePanel {
1888
1888
  const { chart } = pass;
1889
1889
  if (row.kind === "price") {
1890
1890
  const base = this.symbolName.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || this.symbolName;
1891
- const icon = tickerIconEl(doc, base || "P", this.symbolName || "Price", "vela-ot-avatar", this.symbolRaw ? this.iconFor?.(this.symbolRaw) : void 0);
1892
- const el2 = this.row(icon, row.label, row.visible, [
1891
+ const icon2 = tickerIconEl(doc, base || "P", this.symbolName || "Price", "vela-ot-avatar", this.symbolRaw ? this.iconFor?.(this.symbolRaw) : void 0);
1892
+ const el2 = this.row(icon2, row.label, row.visible, [
1893
1893
  {
1894
1894
  icon: row.visible ? "eye" : "eye-off",
1895
1895
  title: row.visible ? "Hide" : "Show",
@@ -2008,16 +2008,16 @@ var ObjectTree = class extends SidePanel {
2008
2008
  const doc = this.el.ownerDocument;
2009
2009
  const { chart } = pass;
2010
2010
  const markup = drawingMeta(d.type).icon;
2011
- let icon;
2011
+ let icon2;
2012
2012
  if (markup) {
2013
- icon = doc.createElement("span");
2014
- icon.className = "vela-icon";
2015
- icon.setAttribute("aria-hidden", "true");
2016
- icon.innerHTML = markup;
2013
+ icon2 = doc.createElement("span");
2014
+ icon2.className = "vela-icon";
2015
+ icon2.setAttribute("aria-hidden", "true");
2016
+ icon2.innerHTML = markup;
2017
2017
  } else {
2018
- icon = iconEl("pen", doc);
2018
+ icon2 = iconEl("pen", doc);
2019
2019
  }
2020
- const el = this.row(icon, drawingLabel(d), d.visible, [
2020
+ const el = this.row(icon2, drawingLabel(d), d.visible, [
2021
2021
  {
2022
2022
  icon: d.locked ? "lock" : "unlock",
2023
2023
  title: d.locked ? "Unlock" : "Lock",
@@ -2083,7 +2083,7 @@ var ObjectTree = class extends SidePanel {
2083
2083
  }
2084
2084
  }
2085
2085
  /** The shared row shell: icon, name, optional tag, then the action buttons. */
2086
- row(icon, label, visible, actions, tag) {
2086
+ row(icon2, label, visible, actions, tag) {
2087
2087
  const doc = this.el.ownerDocument;
2088
2088
  const el = doc.createElement("div");
2089
2089
  el.className = "vela-ot-row";
@@ -2092,7 +2092,7 @@ var ObjectTree = class extends SidePanel {
2092
2092
  name.className = "vela-ot-name";
2093
2093
  name.textContent = label;
2094
2094
  name.title = label;
2095
- el.append(icon, name);
2095
+ el.append(icon2, name);
2096
2096
  if (tag) {
2097
2097
  const t = doc.createElement("span");
2098
2098
  t.className = "vela-ot-tag";
@@ -2931,6 +2931,17 @@ function dedupeSymbols(list) {
2931
2931
  }
2932
2932
  return out;
2933
2933
  }
2934
+ function foldGroups(list) {
2935
+ const groupsAbove = /* @__PURE__ */ new Set();
2936
+ const out = [];
2937
+ for (const s of list) {
2938
+ if (isGroupRow(s)) {
2939
+ groupsAbove.add(groupKeyOf(s));
2940
+ out.push(s);
2941
+ } else if (s.group == null || !groupsAbove.has(groupKeyOf(s))) out.push(s);
2942
+ }
2943
+ return out;
2944
+ }
2934
2945
  var TOP_TICKERS = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "LINKUSDT"];
2935
2946
  function parseQuery(raw, venues) {
2936
2947
  const m = raw.match(/^\s*([^\s:]+)\s*[:\s]\s*(.*)$/);
@@ -3062,6 +3073,21 @@ var CSS6 = `
3062
3073
  .vela-sp-badge[data-p='binance'] { color: #f0b90b; } /* palette-exempt: venue brand mark */
3063
3074
  .vela-sp-badge[data-p='hyperliquid'] { color: #50d2c1; } /* palette-exempt: venue brand mark */
3064
3075
  .vela-sp-empty { padding: var(--vela-space-3); color: var(--vela-fg-muted); text-align: center; }
3076
+ /* Grouped listings (futures roots): the chevron unfolds members inline, indented. */
3077
+ .vela-sp-expander {
3078
+ all: unset;
3079
+ flex: none;
3080
+ display: inline-flex;
3081
+ align-items: center;
3082
+ justify-content: center;
3083
+ width: 22px;
3084
+ height: 22px;
3085
+ border-radius: 5px;
3086
+ cursor: pointer;
3087
+ color: var(--vela-fg-muted);
3088
+ }
3089
+ .vela-sp-expander:hover { background: var(--vela-surface-elev); color: var(--vela-fg); }
3090
+ .vela-sp-row[data-member] { padding-left: 34px; }
3065
3091
  `;
3066
3092
  var PAGE = 100;
3067
3093
  var SymbolPicker = class {
@@ -3073,6 +3099,11 @@ var SymbolPicker = class {
3073
3099
  this.seed = "";
3074
3100
  this.activeTab = "All";
3075
3101
  this.visible = PAGE;
3102
+ /** The last filter pass returned fewer raw rows than asked — the pool is drained
3103
+ * (checked BEFORE folding: folding shortens pages without meaning exhaustion). */
3104
+ this.exhausted = false;
3105
+ /** Group rows currently expanded (venue-scoped keys) — members shown inline. */
3106
+ this.expanded = /* @__PURE__ */ new Set();
3076
3107
  /** The ranked pool cache — `key` fingerprints the raw pool the ranking ran on. */
3077
3108
  this.ranked = null;
3078
3109
  this.ranking = false;
@@ -3087,7 +3118,7 @@ var SymbolPicker = class {
3087
3118
  searchRow.append(iconEl("search", doc), this.input);
3088
3119
  this.tabs = doc.createElement("div");
3089
3120
  this.tabs.className = "vela-sp-tabs";
3090
- for (const t of ["All", "Stocks", "ETFs", "Crypto", "Forex", "Commodities"]) {
3121
+ for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
3091
3122
  const b = doc.createElement("button");
3092
3123
  b.className = "vela-sp-tab";
3093
3124
  b.textContent = t;
@@ -3103,7 +3134,7 @@ var SymbolPicker = class {
3103
3134
  this.list = doc.createElement("div");
3104
3135
  this.list.className = "vela-sp-list";
3105
3136
  this.list.addEventListener("scroll", () => {
3106
- if (this.rows.length < this.visible) return;
3137
+ if (this.exhausted) return;
3107
3138
  if (this.list.scrollTop + this.list.clientHeight < this.list.scrollHeight - 200) return;
3108
3139
  this.visible += PAGE;
3109
3140
  this.grow();
@@ -3132,14 +3163,19 @@ var SymbolPicker = class {
3132
3163
  else if (e.key === "ArrowUp") this.moveHighlight(-1);
3133
3164
  else if (e.key === "Enter") {
3134
3165
  const pick = this.rows[this.highlighted];
3135
- if (pick) this.select(pick.ticker, pick.prefix ?? pick.provider, opts.onSelect);
3166
+ if (pick) this.pick(pick);
3136
3167
  return;
3137
3168
  } else return;
3138
3169
  e.preventDefault();
3139
3170
  });
3140
3171
  this.list.addEventListener("click", (e) => {
3141
- const row = e.target.closest(".vela-sp-row");
3142
- if (row?.dataset.ticker) this.select(row.dataset.ticker, row.dataset.venue, opts.onSelect);
3172
+ const target = e.target;
3173
+ const row = target.closest(".vela-sp-row");
3174
+ if (!row) return;
3175
+ const s = this.rows[Number(row.dataset.i)];
3176
+ if (!s) return;
3177
+ if (target.closest(".vela-sp-expander")) this.toggleExpand(s);
3178
+ else this.pick(s);
3143
3179
  });
3144
3180
  }
3145
3181
  /** Wire where symbols come from (re-called on every widget rebuild). */
@@ -3156,6 +3192,27 @@ var SymbolPicker = class {
3156
3192
  destroy() {
3157
3193
  this.dialog.destroy();
3158
3194
  }
3195
+ /** Route a row activation: a GROUP row loads its default member (the root itself is
3196
+ * listed, never loadable), any other row loads itself. */
3197
+ pick(s) {
3198
+ const target = isGroupRow(s) ? defaultMemberOf(this.pool(), s) ?? s : s;
3199
+ this.select(target.ticker, target.prefix ?? target.provider, this.opts.onSelect);
3200
+ }
3201
+ /** Expand/collapse a group row IN PLACE — same query, same page, same scroll; only
3202
+ * the member rows under the group appear or go. */
3203
+ toggleExpand(s) {
3204
+ const key = groupKeyOf(s);
3205
+ if (!this.expanded.delete(key)) this.expanded.add(key);
3206
+ const scrollTop = this.list.scrollTop;
3207
+ const focus = this.rows[this.highlighted];
3208
+ this.rows = this.computeRows();
3209
+ this.list.replaceChildren();
3210
+ this.rows.forEach((r, i) => this.list.appendChild(this.rowEl(r, i)));
3211
+ const at = focus ? this.rows.indexOf(focus) : -1;
3212
+ this.highlighted = at >= 0 ? at : Math.min(this.highlighted, Math.max(0, this.rows.length - 1));
3213
+ this.renderHighlight();
3214
+ this.list.scrollTop = scrollTop;
3215
+ }
3159
3216
  select(ticker, venue, onSelect) {
3160
3217
  this.close();
3161
3218
  onSelect(venue ? `${venue}:${ticker}` : ticker);
@@ -3197,10 +3254,26 @@ var SymbolPicker = class {
3197
3254
  return this.ranked?.result ?? raw;
3198
3255
  }
3199
3256
  computeRows() {
3200
- const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Forex: ["forex"], Commodities: ["commodity"] };
3257
+ const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Futures: ["futures", "root"], Forex: ["forex"], Commodities: ["commodity"] };
3201
3258
  const all = this.pool();
3202
3259
  const pool = this.activeTab === "All" ? all : all.filter((s) => TAB_TYPES[this.activeTab]?.includes((s.type ?? "").toLowerCase()) || this.activeTab === "Crypto" && (s.type ?? "").toLowerCase() === "futures");
3203
- return filterSymbols(pool, this.input.value, this.visible, symbolRanking() ? false : TOP_TICKERS);
3260
+ const filtered = filterSymbols(pool, this.input.value, this.visible, symbolRanking() ? false : TOP_TICKERS);
3261
+ this.exhausted = filtered.length < this.visible;
3262
+ const folded = foldGroups(filtered);
3263
+ if (!this.expanded.size) return folded;
3264
+ const keyOf = (s) => `${(s.prefix ?? s.provider ?? "").toLowerCase()}:${s.ticker.toUpperCase()}`;
3265
+ const present = new Set(folded.map(keyOf));
3266
+ const out = [];
3267
+ for (const s of folded) {
3268
+ out.push(s);
3269
+ if (!isGroupRow(s) || !this.expanded.has(groupKeyOf(s))) continue;
3270
+ for (const m of groupMembers(all, s)) {
3271
+ if (present.has(keyOf(m))) continue;
3272
+ present.add(keyOf(m));
3273
+ out.push(m);
3274
+ }
3275
+ }
3276
+ return out;
3204
3277
  }
3205
3278
  refresh() {
3206
3279
  const doc = this.list.ownerDocument;
@@ -3215,19 +3288,20 @@ var SymbolPicker = class {
3215
3288
  this.list.appendChild(empty);
3216
3289
  return;
3217
3290
  }
3218
- for (const s of this.rows) this.list.appendChild(this.rowEl(s));
3291
+ this.rows.forEach((s, i) => this.list.appendChild(this.rowEl(s, i)));
3219
3292
  this.renderHighlight();
3220
3293
  }
3221
3294
  /** Append the page the grown `visible` just uncovered — rows already on screen stay put. */
3222
3295
  grow() {
3223
3296
  const already = this.rows.length;
3224
3297
  this.rows = this.computeRows();
3225
- for (const s of this.rows.slice(already)) this.list.appendChild(this.rowEl(s));
3298
+ this.rows.slice(already).forEach((s, j) => this.list.appendChild(this.rowEl(s, already + j)));
3226
3299
  }
3227
- rowEl(s) {
3300
+ rowEl(s, i) {
3228
3301
  const doc = this.list.ownerDocument;
3229
3302
  const row = doc.createElement("div");
3230
3303
  row.className = "vela-sp-row";
3304
+ row.dataset.i = String(i);
3231
3305
  row.dataset.ticker = s.ticker;
3232
3306
  const venue = s.prefix ?? s.provider;
3233
3307
  if (venue) row.dataset.venue = venue;
@@ -3242,6 +3316,14 @@ var SymbolPicker = class {
3242
3316
  d.textContent = s.description ?? (s.type ?? "");
3243
3317
  main.append(t, d);
3244
3318
  row.append(av, main);
3319
+ if (isGroupRow(s)) {
3320
+ row.dataset.group = "1";
3321
+ const expander = doc.createElement("button");
3322
+ expander.className = "vela-sp-expander";
3323
+ expander.setAttribute("aria-label", "Show contracts");
3324
+ expander.appendChild(iconEl(this.expanded.has(groupKeyOf(s)) ? "chevron-down" : "chevron-right", doc));
3325
+ row.appendChild(expander);
3326
+ } else if (s.group != null && this.expanded.has(groupKeyOf(s))) row.dataset.member = "1";
3245
3327
  if (venue) {
3246
3328
  const badge = doc.createElement("span");
3247
3329
  badge.className = "vela-sp-badge";
@@ -4817,6 +4899,229 @@ var SessionShadingTracker = class {
4817
4899
  }
4818
4900
  };
4819
4901
 
4902
+ // src/widget/glide.ts
4903
+ var MIN_SPAN_MS = 6e4;
4904
+ var FOLLOW = 0.25;
4905
+ var ZOOM_IN = 0.8;
4906
+ var ZOOM_OUT = 1.25;
4907
+ var PAN_FAST = 0.2;
4908
+ function zoomTarget(base, factor) {
4909
+ const newSpan = Math.max(MIN_SPAN_MS, (base.to - base.from) * factor);
4910
+ return { from: base.to - newSpan, to: base.to };
4911
+ }
4912
+ function followStep(cur, target, follow = FOLLOW, epsFrac = 15e-4) {
4913
+ const eps = Math.max(MIN_SPAN_MS, (target.to - target.from) * epsFrac);
4914
+ const nf = cur.from + (target.from - cur.from) * follow;
4915
+ const nt = cur.to + (target.to - cur.to) * follow;
4916
+ const done = Math.abs(target.from - nf) <= eps && Math.abs(target.to - nt) <= eps;
4917
+ return { cur: done ? { ...target } : { from: nf, to: nt }, done };
4918
+ }
4919
+ var Glider = class {
4920
+ constructor(chart) {
4921
+ this.chart = chart;
4922
+ // The glide eases ITS OWN range, never the chart's read-back: getVisibleRange()
4923
+ // is data-bounded (whole bars, `to` capped at the newest bar) and the renderer
4924
+ // clamps what gets applied — chasing that read-back can never converge on a
4925
+ // clamped step, leaving the rAF loop re-applying a stale target forever (and
4926
+ // grinding the span down when panning past the newest bar). The internal range
4927
+ // converges geometrically, so the glide always terminates; the renderer is free
4928
+ // to clamp each applied frame.
4929
+ this.cur = null;
4930
+ this.target = null;
4931
+ this.raf = 0;
4932
+ }
4933
+ zoom(factor) {
4934
+ this.to((base) => zoomTarget(base, factor));
4935
+ }
4936
+ stop() {
4937
+ if (this.raf) cancelAnimationFrame(this.raf);
4938
+ this.raf = 0;
4939
+ this.cur = null;
4940
+ this.target = null;
4941
+ }
4942
+ to(make) {
4943
+ const chart = this.chart();
4944
+ const base = this.target ?? chart?.getVisibleRange();
4945
+ if (!chart || !base) return;
4946
+ this.cur ?? (this.cur = { ...base });
4947
+ this.target = make(base);
4948
+ if (!this.raf) this.tick();
4949
+ }
4950
+ tick() {
4951
+ this.raf = requestAnimationFrame(() => {
4952
+ const chart = this.chart();
4953
+ if (!chart || !this.cur || !this.target) {
4954
+ this.stop();
4955
+ return;
4956
+ }
4957
+ const { cur: next, done } = followStep(this.cur, this.target);
4958
+ this.cur = next;
4959
+ chart.setVisibleRange(next);
4960
+ if (done) this.stop();
4961
+ else this.tick();
4962
+ });
4963
+ }
4964
+ };
4965
+
4966
+ // src/widget/cell-controls.ts
4967
+ var CELL_CONTROLS_PROXIMITY_PX = 120;
4968
+ var TIME_AXIS_H2 = 22;
4969
+ var CONTROLS_BOTTOM_PX = TIME_AXIS_H2 + 12;
4970
+ var CLUSTER_H = 24;
4971
+ var CLUSTER_PILL = "rgba(0,0,0,0.65)";
4972
+ var STYLE_ID12 = "vela-cell-controls";
4973
+ var CSS12 = `
4974
+ .vela-cc-btn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;border-radius:var(--vela-radius-sm);background:transparent;line-height:0;font-size:12px;color:var(--vela-fg-muted);cursor:pointer;}
4975
+ .vela-cc-btn svg{display:block;}
4976
+ .vela-cc-btn:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
4977
+ .vela-cc-on,.vela-cc-on:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
4978
+ .vela-cc-grip{cursor:grab;touch-action:none;}
4979
+ .vela-cc-grip:active{cursor:grabbing;}
4980
+ `;
4981
+ function nearBottomCenter(x, y, width, height, proximityPx = CELL_CONTROLS_PROXIMITY_PX) {
4982
+ const cx = width / 2;
4983
+ const cy = height - CONTROLS_BOTTOM_PX - CLUSTER_H / 2;
4984
+ return Math.hypot(x - cx, y - cy) <= proximityPx;
4985
+ }
4986
+ var CellControls = class {
4987
+ constructor(host, deps) {
4988
+ this.host = host;
4989
+ this.deps = deps;
4990
+ this.near = false;
4991
+ /** A grip drag is underway — the proximity reveal must not hide the cluster
4992
+ * while captured pointer moves sweep across the whole grid. */
4993
+ this.dragging = false;
4994
+ /** Mobile: the proximity reveal is meaningless without a cursor — the mobile
4995
+ * bar's maximize stop replaces the cluster. */
4996
+ this.suspended = false;
4997
+ this.onHostMove = (e) => {
4998
+ if (this.suspended) return;
4999
+ if (this.dragging) return;
5000
+ const rect = this.host.getBoundingClientRect();
5001
+ this.setNear(nearBottomCenter(e.clientX - rect.left, e.clientY - rect.top, rect.width, rect.height));
5002
+ };
5003
+ this.onHostLeave = () => {
5004
+ if (this.dragging) return;
5005
+ this.setNear(false);
5006
+ };
5007
+ injectStyles(STYLE_ID12, CSS12, host.ownerDocument);
5008
+ this.glider = new Glider(deps.chart);
5009
+ this.root = host.ownerDocument.createElement("div");
5010
+ Object.assign(this.root.style, {
5011
+ position: "absolute",
5012
+ left: "50%",
5013
+ bottom: `${CONTROLS_BOTTOM_PX}px`,
5014
+ transform: "translateX(-50%)",
5015
+ zIndex: "6",
5016
+ display: "none",
5017
+ // revealed by cursor proximity (onHostMove)
5018
+ gap: "2px",
5019
+ padding: "2px",
5020
+ borderRadius: "var(--vela-radius-md)",
5021
+ background: CLUSTER_PILL,
5022
+ pointerEvents: "auto"
5023
+ });
5024
+ this.host.addEventListener("pointermove", this.onHostMove);
5025
+ this.host.addEventListener("pointerleave", this.onHostLeave);
5026
+ this.host.appendChild(this.root);
5027
+ this.refresh();
5028
+ }
5029
+ /** Rebuild the buttons (the multi-cell gate or the maximized state changed). */
5030
+ refresh() {
5031
+ this.root.textContent = "";
5032
+ const multi = this.deps.multiCell();
5033
+ const maximized = multi && this.deps.isMaximized();
5034
+ if (multi && !maximized) this.root.appendChild(this.makeGrip());
5035
+ this.root.appendChild(this.button("minus", "Zoom out", () => this.glider.zoom(ZOOM_OUT)));
5036
+ this.root.appendChild(this.button("plus", "Zoom in", () => this.glider.zoom(ZOOM_IN)));
5037
+ if (multi) {
5038
+ this.root.appendChild(
5039
+ this.button(maximized ? "restore" : "maximize", maximized ? "Restore layout" : "Maximize chart", () => this.deps.toggleMaximize(), {
5040
+ // The maximized state reads as an inverse chip (white-on-dark, dark-on-light),
5041
+ // the same active-state affordance as a collapsed pane's expand button.
5042
+ selected: maximized
5043
+ })
5044
+ );
5045
+ }
5046
+ this.root.appendChild(
5047
+ this.button("reset", "Reset chart", () => {
5048
+ this.glider.stop();
5049
+ this.deps.reset();
5050
+ })
5051
+ );
5052
+ }
5053
+ button(iconId, title, onClick, opts = {}) {
5054
+ const b = this.host.ownerDocument.createElement("button");
5055
+ b.type = "button";
5056
+ b.title = title;
5057
+ b.setAttribute("aria-label", title);
5058
+ b.className = opts.selected === true ? "vela-cc-btn vela-cc-on" : "vela-cc-btn";
5059
+ b.innerHTML = icon(iconId);
5060
+ b.addEventListener("click", (e) => {
5061
+ e.stopPropagation();
5062
+ onClick();
5063
+ });
5064
+ return b;
5065
+ }
5066
+ /** The drag handle (2×3 dot grip): press and drag onto another cell to trade
5067
+ * slots with it. The preview highlight follows the pointer; releasing outside
5068
+ * any other cell cancels. */
5069
+ makeGrip() {
5070
+ const b = this.host.ownerDocument.createElement("button");
5071
+ b.type = "button";
5072
+ b.title = "Drag to move chart";
5073
+ b.setAttribute("aria-label", "Drag to move chart");
5074
+ b.className = "vela-cc-btn vela-cc-grip";
5075
+ b.innerHTML = icon("grip");
5076
+ b.addEventListener("pointerdown", (e) => this.onGripDown(b, e));
5077
+ return b;
5078
+ }
5079
+ onGripDown(btn, e) {
5080
+ if (e.button !== 0 && e.pointerType === "mouse") return;
5081
+ e.preventDefault();
5082
+ e.stopPropagation();
5083
+ try {
5084
+ btn.setPointerCapture(e.pointerId);
5085
+ } catch {
5086
+ }
5087
+ this.dragging = true;
5088
+ let target = null;
5089
+ const move = (ev) => {
5090
+ target = this.deps.dragTargetAt(ev.clientX, ev.clientY);
5091
+ this.deps.previewDrop(target);
5092
+ };
5093
+ const finish = (commit) => () => {
5094
+ this.dragging = false;
5095
+ this.deps.previewDrop(null);
5096
+ btn.removeEventListener("pointermove", move);
5097
+ btn.removeEventListener("pointerup", onUp);
5098
+ btn.removeEventListener("pointercancel", onCancel);
5099
+ if (commit && target != null) this.deps.dropOn(target);
5100
+ };
5101
+ const onUp = finish(true);
5102
+ const onCancel = finish(false);
5103
+ btn.addEventListener("pointermove", move);
5104
+ btn.addEventListener("pointerup", onUp);
5105
+ btn.addEventListener("pointercancel", onCancel);
5106
+ }
5107
+ /** Mobile flips the cluster off entirely (and hides it if currently revealed). */
5108
+ setSuspended(on) {
5109
+ this.suspended = on;
5110
+ if (on) this.setNear(false);
5111
+ }
5112
+ setNear(near) {
5113
+ if (near === this.near) return;
5114
+ this.near = near;
5115
+ this.root.style.display = near ? "flex" : "none";
5116
+ }
5117
+ destroy() {
5118
+ this.glider.stop();
5119
+ this.host.removeEventListener("pointermove", this.onHostMove);
5120
+ this.host.removeEventListener("pointerleave", this.onHostLeave);
5121
+ this.root.remove();
5122
+ }
5123
+ };
5124
+
4820
5125
  // src/widget/history.ts
4821
5126
  var WidgetHistory = class {
4822
5127
  /** `getChart` late-resolves the CURRENT chart: drawing steps recorded before a chart
@@ -5066,11 +5371,18 @@ var ChartCell = class {
5066
5371
  if (this.inner && this.state.symbol) this.marketStatus?.track(this.inner.data, this.state.symbol);
5067
5372
  });
5068
5373
  this.syncStatuslineColors();
5374
+ this.cellControls = new CellControls(this.host, {
5375
+ chart: () => this.inner,
5376
+ reset: () => this.resetView(),
5377
+ multiCell: () => deps.multiCell(),
5378
+ isMaximized: () => deps.isMaximized(id),
5379
+ toggleMaximize: () => deps.toggleMaximize(id),
5380
+ dragTargetAt: (x, y) => deps.cellDragTarget(id, x, y),
5381
+ previewDrop: (target) => deps.previewDropTarget(target),
5382
+ dropOn: (target) => deps.dropCell(id, target)
5383
+ });
5069
5384
  this.contextMenu = new ChartContextMenu(this.host, {
5070
- resetView: () => {
5071
- this.inner?.renderer.set("autoScale", true);
5072
- this.inner?.setVisibleRangePreset("ALL");
5073
- },
5385
+ resetView: () => this.resetView(),
5074
5386
  timezone: () => this.deps.timezone(),
5075
5387
  setTimezone: (zone) => this.deps.setTimezone(zone),
5076
5388
  // Right-clicking activates the cell first (capture-phase pointerdown), so the
@@ -5444,6 +5756,20 @@ var ChartCell = class {
5444
5756
  this.inner.setVisibleRangePreset(preset.preset);
5445
5757
  }
5446
5758
  }
5759
+ /** Reset this cell's view: re-enable auto scale and frame the full history —
5760
+ * the same action the chart context menu offers. */
5761
+ resetView() {
5762
+ this.inner?.renderer.set("autoScale", true);
5763
+ this.inner?.setVisibleRangePreset("ALL");
5764
+ }
5765
+ /** Rebuild the view-controls cluster (the maximize gate or state changed). */
5766
+ refreshControls() {
5767
+ this.cellControls.refresh();
5768
+ }
5769
+ /** Mobile flips the per-cell cluster off (the shell's mobile bar replaces it). */
5770
+ setControlsSuspended(on) {
5771
+ this.cellControls.setSuspended(on);
5772
+ }
5447
5773
  /** Make this cell the active one and put keyboard focus on its chart surface. */
5448
5774
  focus() {
5449
5775
  this.deps.activate(this.id);
@@ -5757,6 +6083,7 @@ var ChartCell = class {
5757
6083
  destroy() {
5758
6084
  this.destroyed = true;
5759
6085
  this.offMarket();
6086
+ this.cellControls.destroy();
5760
6087
  this.contextMenu.destroy();
5761
6088
  this.history.destroy();
5762
6089
  this.marketStatus?.stop();
@@ -6015,8 +6342,8 @@ var SplitterLayer = class {
6015
6342
  };
6016
6343
 
6017
6344
  // src/widget/toast.ts
6018
- var STYLE_ID12 = "vela-widget-toast";
6019
- var CSS12 = `
6345
+ var STYLE_ID13 = "vela-widget-toast";
6346
+ var CSS13 = `
6020
6347
  .vela-toast {
6021
6348
  position: absolute;
6022
6349
  left: 50%;
@@ -6046,7 +6373,7 @@ var CSS12 = `
6046
6373
  var Toast = class {
6047
6374
  constructor(host) {
6048
6375
  this.timer = null;
6049
- injectStyles(STYLE_ID12, CSS12, host.ownerDocument);
6376
+ injectStyles(STYLE_ID13, CSS13, host.ownerDocument);
6050
6377
  this.el = host.ownerDocument.createElement("div");
6051
6378
  this.el.className = "vela-toast";
6052
6379
  host.appendChild(this.el);
@@ -6070,70 +6397,6 @@ var Toast = class {
6070
6397
  }
6071
6398
  };
6072
6399
 
6073
- // src/widget/glide.ts
6074
- var MIN_SPAN_MS = 6e4;
6075
- var FOLLOW = 0.25;
6076
- var ZOOM_IN = 0.8;
6077
- var ZOOM_OUT = 1.25;
6078
- var PAN_FAST = 0.2;
6079
- function zoomTarget(base, factor) {
6080
- const newSpan = Math.max(MIN_SPAN_MS, (base.to - base.from) * factor);
6081
- return { from: base.to - newSpan, to: base.to };
6082
- }
6083
- function followStep(cur, target, follow = FOLLOW, epsFrac = 15e-4) {
6084
- const eps = Math.max(MIN_SPAN_MS, (target.to - target.from) * epsFrac);
6085
- const nf = cur.from + (target.from - cur.from) * follow;
6086
- const nt = cur.to + (target.to - cur.to) * follow;
6087
- const done = Math.abs(target.from - nf) <= eps && Math.abs(target.to - nt) <= eps;
6088
- return { cur: done ? { ...target } : { from: nf, to: nt }, done };
6089
- }
6090
- var Glider = class {
6091
- constructor(chart) {
6092
- this.chart = chart;
6093
- // The glide eases ITS OWN range, never the chart's read-back: getVisibleRange()
6094
- // is data-bounded (whole bars, `to` capped at the newest bar) and the renderer
6095
- // clamps what gets applied — chasing that read-back can never converge on a
6096
- // clamped step, leaving the rAF loop re-applying a stale target forever (and
6097
- // grinding the span down when panning past the newest bar). The internal range
6098
- // converges geometrically, so the glide always terminates; the renderer is free
6099
- // to clamp each applied frame.
6100
- this.cur = null;
6101
- this.target = null;
6102
- this.raf = 0;
6103
- }
6104
- zoom(factor) {
6105
- this.to((base) => zoomTarget(base, factor));
6106
- }
6107
- stop() {
6108
- if (this.raf) cancelAnimationFrame(this.raf);
6109
- this.raf = 0;
6110
- this.cur = null;
6111
- this.target = null;
6112
- }
6113
- to(make) {
6114
- const chart = this.chart();
6115
- const base = this.target ?? chart?.getVisibleRange();
6116
- if (!chart || !base) return;
6117
- this.cur ?? (this.cur = { ...base });
6118
- this.target = make(base);
6119
- if (!this.raf) this.tick();
6120
- }
6121
- tick() {
6122
- this.raf = requestAnimationFrame(() => {
6123
- const chart = this.chart();
6124
- if (!chart || !this.cur || !this.target) {
6125
- this.stop();
6126
- return;
6127
- }
6128
- const { cur: next, done } = followStep(this.cur, this.target);
6129
- this.cur = next;
6130
- chart.setVisibleRange(next);
6131
- if (done) this.stop();
6132
- else this.tick();
6133
- });
6134
- }
6135
- };
6136
-
6137
6400
  // src/widget/tool-shortcuts.ts
6138
6401
  var TOOL_BINDINGS = [
6139
6402
  ["trendline", "drawings.trendline"],
@@ -6205,8 +6468,8 @@ var LayoutModeController = class {
6205
6468
  };
6206
6469
 
6207
6470
  // src/widget/mobile-bar.ts
6208
- var STYLE_ID13 = "vela-widget-mobilebar";
6209
- var CSS13 = `
6471
+ var STYLE_ID14 = "vela-widget-mobilebar";
6472
+ var CSS14 = `
6210
6473
  .vela-mobilebar {
6211
6474
  display: none;
6212
6475
  align-items: stretch;
@@ -6237,6 +6500,9 @@ var CSS13 = `
6237
6500
  }
6238
6501
  .vela-mb-item:active { background: var(--vela-hover); }
6239
6502
  .vela-mb-item .vela-icon { font-size: 18px; width: 18px; height: 18px; }
6503
+ /* A lit stop (the maximize toggle while something is isolated): the inverse
6504
+ "selected" chip \u2014 white on the dark theme, dark on the light one. */
6505
+ .vela-mb-item.vela-mb-on, .vela-mb-item.vela-mb-on:active { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
6240
6506
  /* Left-aligned contributed actions get their own stops (the built-in indicators
6241
6507
  slot) \u2014 the wrapper is layout-transparent so each stop flexes like a sibling. */
6242
6508
  .vela-mb-actions { display: contents; }
@@ -6256,14 +6522,14 @@ var MobileBar = class {
6256
6522
  constructor(host, opts) {
6257
6523
  this.opts = opts;
6258
6524
  const doc = host.ownerDocument;
6259
- injectStyles(STYLE_ID13, CSS13, doc);
6525
+ injectStyles(STYLE_ID14, CSS14, doc);
6260
6526
  this.el = doc.createElement("div");
6261
6527
  this.el.className = "vela-mobilebar";
6262
- const item = (cls, label, onClick, icon) => {
6528
+ const item = (cls, label, onClick, icon2) => {
6263
6529
  const b = doc.createElement("button");
6264
6530
  b.className = `vela-mb-item ${cls}`;
6265
6531
  b.setAttribute("aria-label", label);
6266
- if (icon) b.appendChild(iconEl(icon, doc));
6532
+ if (icon2) b.appendChild(iconEl(icon2, doc));
6267
6533
  b.addEventListener("click", onClick);
6268
6534
  return b;
6269
6535
  };
@@ -6277,9 +6543,11 @@ var MobileBar = class {
6277
6543
  this.actionsHost.className = "vela-mb-actions";
6278
6544
  const onDrawings = opts.onDrawingsClick;
6279
6545
  const drawings = onDrawings ? item("vela-mb-drawings", "Drawings", onDrawings, "pen") : null;
6546
+ const onMaximize = opts.onMaximizeClick;
6547
+ this.maxEl = onMaximize ? item("vela-mb-maximize", "Maximize chart", onMaximize, "maximize") : null;
6280
6548
  const more = item("vela-mb-more", "More", opts.onMoreClick, "kebab");
6281
6549
  const settings = item("vela-mb-settings", "Chart settings", opts.onSettingsClick, "gear");
6282
- this.el.append(this.symbolEl, this.tfEl, ...indicators ? [indicators] : [], this.actionsHost, ...drawings ? [drawings] : [], more, settings);
6550
+ this.el.append(this.symbolEl, this.tfEl, ...indicators ? [indicators] : [], this.actionsHost, ...drawings ? [drawings] : [], ...this.maxEl ? [this.maxEl] : [], more, settings);
6283
6551
  host.appendChild(this.el);
6284
6552
  this.renderActions();
6285
6553
  }
@@ -6311,14 +6579,22 @@ var MobileBar = class {
6311
6579
  setTimeframe(tf) {
6312
6580
  this.tfEl.textContent = timeframeLabel(tf);
6313
6581
  }
6582
+ /** Light the maximize stop while something is isolated (a chart over the grid,
6583
+ * or a maximized pane inside the active chart) — inverse chip + restore glyph. */
6584
+ setMaximizeActive(on) {
6585
+ if (!this.maxEl) return;
6586
+ this.maxEl.classList.toggle("vela-mb-on", on);
6587
+ this.maxEl.setAttribute("aria-label", on ? "Restore layout" : "Maximize chart");
6588
+ this.maxEl.replaceChildren(iconEl(on ? "restore" : "maximize", this.el.ownerDocument));
6589
+ }
6314
6590
  destroy() {
6315
6591
  this.el.remove();
6316
6592
  }
6317
6593
  };
6318
6594
 
6319
6595
  // src/widget/timeframe-drawer.ts
6320
- var STYLE_ID14 = "vela-widget-tf-drawer";
6321
- var CSS14 = `
6596
+ var STYLE_ID15 = "vela-widget-tf-drawer";
6597
+ var CSS15 = `
6322
6598
  .vela-tfd-heading {
6323
6599
  padding: 4px 2px 8px;
6324
6600
  font-size: 12px;
@@ -6374,7 +6650,7 @@ var TimeframeDrawer = class {
6374
6650
  this.rangeChips = /* @__PURE__ */ new Map();
6375
6651
  this.tfChips = /* @__PURE__ */ new Map();
6376
6652
  const doc = opts.host.ownerDocument;
6377
- injectStyles(STYLE_ID14, CSS14, doc);
6653
+ injectStyles(STYLE_ID15, CSS15, doc);
6378
6654
  this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
6379
6655
  const rangeHeading = doc.createElement("div");
6380
6656
  rangeHeading.className = "vela-tfd-heading";
@@ -6432,8 +6708,8 @@ var TimeframeDrawer = class {
6432
6708
  };
6433
6709
 
6434
6710
  // src/widget/drawings-drawer.ts
6435
- var STYLE_ID15 = "vela-widget-drawings-drawer";
6436
- var CSS15 = `
6711
+ var STYLE_ID16 = "vela-widget-drawings-drawer";
6712
+ var CSS16 = `
6437
6713
  /* Search + tabs stay pinned while the tool list scrolls underneath. */
6438
6714
  .vela-dd-sticky {
6439
6715
  position: sticky;
@@ -6535,7 +6811,7 @@ var DrawingsDrawer = class {
6535
6811
  this.definition = { groups: [] };
6536
6812
  this.activeGroup = "";
6537
6813
  const doc = opts.host.ownerDocument;
6538
- injectStyles(STYLE_ID15, CSS15, doc);
6814
+ injectStyles(STYLE_ID16, CSS16, doc);
6539
6815
  this.drawer = new Drawer({
6540
6816
  host: opts.host,
6541
6817
  title: "Drawings",
@@ -6677,8 +6953,8 @@ var DrawingsDrawer = class {
6677
6953
  };
6678
6954
 
6679
6955
  // src/widget/more-drawer.ts
6680
- var STYLE_ID16 = "vela-widget-more-drawer";
6681
- var CSS16 = `
6956
+ var STYLE_ID17 = "vela-widget-more-drawer";
6957
+ var CSS17 = `
6682
6958
  .vela-md-actions {
6683
6959
  display: flex;
6684
6960
  gap: 6px;
@@ -6753,7 +7029,7 @@ var MoreDrawer = class {
6753
7029
  constructor(opts) {
6754
7030
  this.opts = opts;
6755
7031
  this.view = "main";
6756
- injectStyles(STYLE_ID16, CSS16, opts.host.ownerDocument);
7032
+ injectStyles(STYLE_ID17, CSS17, opts.host.ownerDocument);
6757
7033
  this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
6758
7034
  }
6759
7035
  open() {
@@ -6809,11 +7085,11 @@ var MoreDrawer = class {
6809
7085
  renderMain(doc) {
6810
7086
  const actions = doc.createElement("div");
6811
7087
  actions.className = "vela-md-actions";
6812
- const action = (icon, label, enabled, run) => {
7088
+ const action = (icon2, label, enabled, run) => {
6813
7089
  const b = doc.createElement("button");
6814
7090
  b.className = "vela-md-action";
6815
7091
  b.disabled = !enabled;
6816
- b.appendChild(iconEl(icon, doc));
7092
+ b.appendChild(iconEl(icon2, doc));
6817
7093
  b.appendChild(doc.createTextNode(label));
6818
7094
  b.addEventListener("click", () => {
6819
7095
  run();
@@ -6947,8 +7223,8 @@ var MoreDrawer = class {
6947
7223
  };
6948
7224
 
6949
7225
  // src/widget/timezone-drawer.ts
6950
- var STYLE_ID17 = "vela-widget-timezone-drawer";
6951
- var CSS17 = `
7226
+ var STYLE_ID18 = "vela-widget-timezone-drawer";
7227
+ var CSS18 = `
6952
7228
  .vela-tzd-list { padding: 2px 0 4px; }
6953
7229
  .vela-tzd-row {
6954
7230
  display: flex;
@@ -6967,7 +7243,7 @@ var CSS17 = `
6967
7243
  var TimezoneDrawer = class {
6968
7244
  constructor(opts) {
6969
7245
  this.opts = opts;
6970
- injectStyles(STYLE_ID17, CSS17, opts.host.ownerDocument);
7246
+ injectStyles(STYLE_ID18, CSS18, opts.host.ownerDocument);
6971
7247
  this.drawer = new Drawer({ host: opts.host, title: "Time zone", onOpenChange: opts.onOpenChange });
6972
7248
  }
6973
7249
  open() {
@@ -7005,8 +7281,8 @@ var TimezoneDrawer = class {
7005
7281
  };
7006
7282
 
7007
7283
  // src/widget/price-scale-drawer.ts
7008
- var STYLE_ID18 = "vela-widget-price-scale-drawer";
7009
- var CSS18 = `
7284
+ var STYLE_ID19 = "vela-widget-price-scale-drawer";
7285
+ var CSS19 = `
7010
7286
  .vela-psd-list { padding: 2px 0 4px; }
7011
7287
  .vela-psd-row {
7012
7288
  display: flex;
@@ -7035,7 +7311,7 @@ var PriceScaleDrawer = class {
7035
7311
  constructor(opts) {
7036
7312
  this.opts = opts;
7037
7313
  this.pane = null;
7038
- injectStyles(STYLE_ID18, CSS18, opts.host.ownerDocument);
7314
+ injectStyles(STYLE_ID19, CSS19, opts.host.ownerDocument);
7039
7315
  this.drawer = new Drawer({ host: opts.host, title: "Price scale", onOpenChange: opts.onOpenChange });
7040
7316
  }
7041
7317
  open() {
@@ -7171,8 +7447,8 @@ var PriceScaleDrawer = class {
7171
7447
  };
7172
7448
 
7173
7449
  // src/widget/drawing-pill.ts
7174
- var STYLE_ID19 = "vela-widget-drawing-pill";
7175
- var CSS19 = `
7450
+ var STYLE_ID20 = "vela-widget-drawing-pill";
7451
+ var CSS20 = `
7176
7452
  .vela-drawpill { display: none; }
7177
7453
  [data-layout='mobile'] .vela-drawpill {
7178
7454
  position: absolute;
@@ -7232,17 +7508,17 @@ var DrawingPill = class {
7232
7508
  this.chart = null;
7233
7509
  this.chartSubs = [];
7234
7510
  const doc = host.ownerDocument;
7235
- injectStyles(STYLE_ID19, CSS19, doc);
7511
+ injectStyles(STYLE_ID20, CSS20, doc);
7236
7512
  this.el = doc.createElement("div");
7237
7513
  this.el.className = "vela-drawpill";
7238
7514
  this.el.hidden = true;
7239
7515
  this.toolGlyph = doc.createElement("span");
7240
7516
  this.toolGlyph.className = "vela-drawpill-tool";
7241
- const btn = (icon, label, onClick) => {
7517
+ const btn = (icon2, label, onClick) => {
7242
7518
  const b = doc.createElement("button");
7243
7519
  b.className = "vela-drawpill-btn";
7244
7520
  b.setAttribute("aria-label", label);
7245
- b.appendChild(iconEl(icon, doc));
7521
+ b.appendChild(iconEl(icon2, doc));
7246
7522
  b.addEventListener("click", onClick);
7247
7523
  return b;
7248
7524
  };
@@ -7345,10 +7621,10 @@ function buildContext(host) {
7345
7621
  var DEFAULT_TIMEFRAMES = ["1", "5", "15", "60", "240", "D", "W"];
7346
7622
  var GAP_PX = 2;
7347
7623
  var POOL_CAP = 16;
7348
- var TIME_AXIS_H2 = 22;
7624
+ var TIME_AXIS_H3 = 22;
7349
7625
  var ALERT_CAP = 50;
7350
- var STYLE_ID20 = "vela-workspace";
7351
- var CSS20 = `
7626
+ var STYLE_ID21 = "vela-workspace";
7627
+ var CSS21 = `
7352
7628
  .vela-workspace { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; background: var(--vela-bg); }
7353
7629
  .vela-ws-main { position: relative; display: flex; flex-direction: row; flex: 1 1 auto; min-height: 0; }
7354
7630
  .vela-ws-toolbar { position: relative; flex: none; }
@@ -7375,6 +7651,21 @@ var CSS20 = `
7375
7651
  /* Mobile: the docked drawing-toolbar column would eat a phone-width grid \u2014 the shell's
7376
7652
  drawings drawer + on-chart pill replace it (same policy as the widget's in-chart bar). */
7377
7653
  [data-layout='mobile'] .vela-ws-toolbar { display: none; }
7654
+ /* A maximized cell owns the whole grid: the splitter strips have no seams to grab and
7655
+ the active ring would just outline the only visible chart \u2014 both are noise here. */
7656
+ .vela-ws-grid[data-maximized='1'] .vela-ws-splitter { display: none; }
7657
+ .vela-ws-grid[data-maximized='1'] .vela-cell[data-active='1']::after { display: none; }
7658
+ /* Drop-target preview while a cell's drag handle is held: a dashed ring + the same
7659
+ soft wash the splitter hover uses, over the chart, inert to the pointer. */
7660
+ .vela-cell[data-drop-target='1']::before {
7661
+ content: '';
7662
+ position: absolute;
7663
+ inset: 0;
7664
+ border: 2px dashed var(--vela-fg-bright);
7665
+ background: var(--vela-separator-hover-band);
7666
+ pointer-events: none;
7667
+ z-index: 11;
7668
+ }
7378
7669
  `;
7379
7670
  registerIcon("layout", svg16('<rect x="1.5" y="1.5" width="13" height="13" rx="1.5"/><path d="M8 1.5v13M1.5 8h13"/>'));
7380
7671
  function declaredOrder(cells) {
@@ -7404,6 +7695,9 @@ var VelaWorkspace = class {
7404
7695
  * slots beyond the list get auto identities. Grows, never reorders. */
7405
7696
  this.order = [];
7406
7697
  this.activeId = null;
7698
+ /** The cell maximized over the whole grid (null = normal grid). TRANSIENT view
7699
+ * state — never persisted; any structural change (layout, applyState) restores. */
7700
+ this.maximizedId = null;
7407
7701
  this.cellBackend = "auto";
7408
7702
  this.destroyed = false;
7409
7703
  this.shortcutsHelp = null;
@@ -7508,7 +7802,7 @@ var VelaWorkspace = class {
7508
7802
  this.order = boot?.charts ? boot.charts.map((c) => c.id) : declaredOrder(opts.cells);
7509
7803
  const bootActive = boot?.activeCellId ?? null;
7510
7804
  const doc = hostEl.ownerDocument;
7511
- injectStyles(STYLE_ID20, CSS20, doc);
7805
+ injectStyles(STYLE_ID21, CSS21, doc);
7512
7806
  this.root = doc.createElement("div");
7513
7807
  this.root.className = "vela-workspace";
7514
7808
  ensureUIHost(this.root, resolveTheme(opts.theme));
@@ -7618,7 +7912,11 @@ var VelaWorkspace = class {
7618
7912
  if (attribution !== false) {
7619
7913
  const background = resolveTheme(opts.theme).background;
7620
7914
  const mark = typeof attribution === "string" && attribution.trim() ? createCustomMark(doc, attribution, background) : createAttributionMark(doc, background);
7621
- Object.assign(mark.style, { left: "12px", bottom: `${TIME_AXIS_H2 + 10}px`, zIndex: "11" });
7915
+ Object.assign(mark.style, {
7916
+ left: "calc(var(--vela-toolbar-gutter, 0px) + 12px)",
7917
+ bottom: `calc(var(--vela-bottom-gutter, ${TIME_AXIS_H3}px) + 10px)`,
7918
+ zIndex: "11"
7919
+ });
7622
7920
  this.gridEl.appendChild(mark);
7623
7921
  this.attributionMark = mark;
7624
7922
  }
@@ -7684,6 +7982,9 @@ var VelaWorkspace = class {
7684
7982
  ...topbarHas(this.topbarComp, "indicators") && (picker || this.indicatorsOverride) ? { onIndicatorsClick: this.indicatorsOverride ? () => this.runOverride(this.indicatorsOverride) : () => picker.open() } : {},
7685
7983
  getContext: () => this.context(),
7686
7984
  ...this.drawingsEnabled ? { onDrawingsClick: () => this.openDrawingsDrawer() } : {},
7985
+ // Multi-chart only: the stop that isolates the ACTIVE chart (the
7986
+ // per-cell hover cluster has no cursor to reveal it on mobile).
7987
+ ...this.monoLayout ? {} : { onMaximizeClick: () => this.toggleMobileMaximize() },
7687
7988
  onMoreClick: () => this.openMoreDrawer(),
7688
7989
  onSettingsClick: () => this.active.chart.renderer.openSettings()
7689
7990
  }) : null;
@@ -7890,7 +8191,9 @@ var VelaWorkspace = class {
7890
8191
  this.pool.clear();
7891
8192
  for (const { id, ...cs } of st.charts.slice(liveCount)) this.pool.set(id, cs);
7892
8193
  this.order = st.charts.map((c) => c.id);
8194
+ this.clearMaximized();
7893
8195
  this.applyGrid();
8196
+ this.refreshCellControls();
7894
8197
  const nextActive2 = st.activeCellId && this.cellsById.has(st.activeCellId) ? st.activeCellId : this.order[0] ?? null;
7895
8198
  if (nextActive2 === this.activeId) this.projectActiveCell();
7896
8199
  else this.setActiveCell(nextActive2);
@@ -7916,6 +8219,7 @@ var VelaWorkspace = class {
7916
8219
  const def = this.monoLayout ? null : ensureLayout(st.layout);
7917
8220
  if (def) this.def = def;
7918
8221
  this.cellBackend = this.backendFor(this.def);
8222
+ this.clearMaximized();
7919
8223
  this.applyGrid();
7920
8224
  this.buildCells();
7921
8225
  this.syncCellPresentation();
@@ -7979,6 +8283,7 @@ var VelaWorkspace = class {
7979
8283
  setLayout(layout) {
7980
8284
  if (this.destroyed) return;
7981
8285
  if (this.monoLayout) return;
8286
+ this.clearMaximized();
7982
8287
  const next = this.resolveLayout(layout);
7983
8288
  const nextBackend = this.backendFor(next);
7984
8289
  const rebuildAll = nextBackend !== this.cellBackend;
@@ -7999,6 +8304,7 @@ var VelaWorkspace = class {
7999
8304
  this.buildCells();
8000
8305
  this.alignNewCellStyles(preexisting);
8001
8306
  this.syncCellPresentation();
8307
+ this.refreshCellControls();
8002
8308
  this.topbar.setLayout(next.id);
8003
8309
  const nextActive = activeAfterLayout(this.activeId, this.order.slice(0, next.cells.length));
8004
8310
  if (nextActive === this.activeId) this.projectActiveCell();
@@ -8007,6 +8313,67 @@ var VelaWorkspace = class {
8007
8313
  this.events.emit("layout:changed", { layout: next.id });
8008
8314
  this.markStateDirty();
8009
8315
  }
8316
+ /** The identity of the cell maximized over the whole grid, or null. */
8317
+ get maximizedCell() {
8318
+ return this.maximizedId;
8319
+ }
8320
+ /**
8321
+ * Maximize one cell over the whole grid, or restore the layout with `null`. Pure
8322
+ * presentation: the other cells stay alive underneath — charts, subscriptions and
8323
+ * state untouched — so restoring is instant. The maximized cell becomes the active
8324
+ * one. Transient view state (also reachable from each cell's bottom-center view
8325
+ * cluster): switching layouts or applying a state document restores the grid.
8326
+ */
8327
+ maximizeCell(id) {
8328
+ if (this.destroyed) return;
8329
+ if (id != null && (!this.cellsById.has(id) || this.def.cells.length <= 1)) return;
8330
+ if (id === this.maximizedId) return;
8331
+ this.maximizedId = id;
8332
+ if (id) this.setActiveCell(id);
8333
+ this.applyGrid();
8334
+ this.refreshCellControls();
8335
+ this.syncMobileMaximize();
8336
+ this.events.emit("cell:maximized", { id });
8337
+ }
8338
+ /** The mobile bar's maximize stop: one press isolates the ACTIVE chart over the
8339
+ * grid; while something is already isolated — the chart, or a pane inside it
8340
+ * (mobile's double-tap) — the press restores that instead. Every branch re-syncs
8341
+ * the stop on its own (`maximizeCell` directly, `panes.maximize` via its
8342
+ * synchronous `pane:changed`). */
8343
+ toggleMobileMaximize() {
8344
+ const cell = this.activeId ? this.cellsById.get(this.activeId) : void 0;
8345
+ if (!cell) return;
8346
+ if (this.maximizedId) this.maximizeCell(null);
8347
+ else if (cell.chart.panes.list().some((p) => p.maximized)) cell.chart.panes.maximize(null);
8348
+ else this.maximizeCell(cell.id);
8349
+ }
8350
+ /** Keep the mobile bar's maximize stop truthful: lit (inverse chip, restore
8351
+ * glyph) while the active chart covers the grid OR one of its panes is
8352
+ * maximized — the state a double-tap toggles is otherwise invisible on mobile. */
8353
+ syncMobileMaximize() {
8354
+ if (!this.mobileBar) return;
8355
+ const cell = this.activeId ? this.cellsById.get(this.activeId) : void 0;
8356
+ const paneMax = cell ? cell.chart.panes.list().some((p) => p.maximized) : false;
8357
+ this.mobileBar.setMaximizeActive(this.maximizedId != null || paneMax);
8358
+ }
8359
+ /**
8360
+ * Trade the SLOTS of two live cells — the grid arrangement changes, the cells
8361
+ * themselves (charts, indicators, drawings, the active flag) stay untouched.
8362
+ * What each cell's drag handle commits; also callable directly by hosts.
8363
+ */
8364
+ swapCells(a, b) {
8365
+ if (this.destroyed || a === b) return;
8366
+ const i = this.order.indexOf(a);
8367
+ const j = this.order.indexOf(b);
8368
+ if (i < 0 || j < 0 || !this.cellsById.has(a) || !this.cellsById.has(b)) return;
8369
+ [this.order[i], this.order[j]] = [this.order[j], this.order[i]];
8370
+ for (const [k] of this.def.cells.entries()) {
8371
+ const host = this.cellsById.get(this.order[k] ?? "")?.host;
8372
+ if (host) this.gridEl.appendChild(host);
8373
+ }
8374
+ this.applyGrid();
8375
+ this.markStateDirty();
8376
+ }
8010
8377
  resize() {
8011
8378
  this.splitters.layout();
8012
8379
  }
@@ -8076,6 +8443,7 @@ var VelaWorkspace = class {
8076
8443
  this.mobileBar?.renderActions();
8077
8444
  this.mobileBar?.setSymbol(cell.symbol);
8078
8445
  this.mobileBar?.setTimeframe(cell.timeframe);
8446
+ this.syncMobileMaximize();
8079
8447
  this.drawingPill?.onChart(cell.chart);
8080
8448
  const pushHistory = () => this.topbar.setHistoryState(cell.history.canUndo, cell.history.canRedo);
8081
8449
  this.historyUnsub?.();
@@ -8165,8 +8533,78 @@ var VelaWorkspace = class {
8165
8533
  const host = this.cellsById.get(this.order[i] ?? "")?.host;
8166
8534
  if (host) host.style.gridArea = perCell[slot.id]?.gridArea ?? "";
8167
8535
  }
8536
+ this.applyMaximizePresentation();
8537
+ this.mountAttributionMark();
8168
8538
  this.splitters.layout();
8169
8539
  }
8540
+ /** Overlay the maximize presentation on the freshly applied grid: EVERY cell spans
8541
+ * the full track grid — the maximized one on top, the siblings invisible beneath
8542
+ * it (their charts stay alive — restoring is instant). The siblings must span too:
8543
+ * left in their slots they would auto-flow into implicit zero-height rows, whose
8544
+ * gaps steal height from the maximized cell and collapse their renderers to 0.
8545
+ * The splitter strips and the active ring hide via the `data-maximized` rules. */
8546
+ applyMaximizePresentation() {
8547
+ const maxId = this.maximizedId;
8548
+ if (maxId) this.gridEl.dataset.maximized = "1";
8549
+ else delete this.gridEl.dataset.maximized;
8550
+ for (const [id, cell] of this.cellsById) {
8551
+ const style = cell.host.style;
8552
+ if (maxId) style.gridArea = "1 / 1 / -1 / -1";
8553
+ style.zIndex = maxId && id === maxId ? "5" : "";
8554
+ style.visibility = maxId && id !== maxId ? "hidden" : "";
8555
+ }
8556
+ }
8557
+ /** Rebuild every cell's view cluster (the maximize gate or state changed). */
8558
+ refreshCellControls() {
8559
+ for (const cell of this.cellsById.values()) cell.refreshControls();
8560
+ }
8561
+ /** Drop the transient maximize on a structural change (layout switch, state
8562
+ * document) — WITH the event, so hosts tracking `cell:maximized` never drift
8563
+ * from `maximizedCell`. The caller's own grid re-apply paints the restore. */
8564
+ clearMaximized() {
8565
+ if (this.maximizedId == null) return;
8566
+ this.maximizedId = null;
8567
+ this.events.emit("cell:maximized", { id: null });
8568
+ }
8569
+ /** The live cell under a viewport point, excluding `excludeId` and any host a
8570
+ * maximize has hidden — the drag handle's hit-test. */
8571
+ cellAtPoint(x, y, excludeId) {
8572
+ for (const [id, cell] of this.cellsById) {
8573
+ if (id === excludeId || cell.host.style.visibility === "hidden") continue;
8574
+ const r = cell.host.getBoundingClientRect();
8575
+ if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return id;
8576
+ }
8577
+ return null;
8578
+ }
8579
+ /** Mark one cell as the live drop target of a grip drag (null clears all) —
8580
+ * the `data-drop-target` stylesheet rule paints the dashed preview ring. */
8581
+ setDropTarget(id) {
8582
+ for (const [cid, cell] of this.cellsById) {
8583
+ if (cid === id) cell.host.dataset.dropTarget = "1";
8584
+ else delete cell.host.dataset.dropTarget;
8585
+ }
8586
+ }
8587
+ /** The cell whose bottom-left corner the grid's attribution mark floats in — the
8588
+ * maximized cell while one covers the grid, else the bottom-left slot's cell. */
8589
+ bottomLeftCell() {
8590
+ if (this.maximizedId) return this.cellsById.get(this.maximizedId);
8591
+ const grid = occupancyGrid(this.def);
8592
+ const slot = grid[grid.length - 1]?.[0];
8593
+ const idx = this.def.cells.findIndex((c) => (c.area ?? c.id) === slot);
8594
+ return this.cellsById.get(this.order[idx >= 0 ? idx : 0] ?? "");
8595
+ }
8596
+ /** Keep the shared attribution mark inside the BOTTOM-LEFT visible cell: its
8597
+ * offsets ride that cell's renderer-published `--vela-bottom-gutter` /
8598
+ * `--vela-toolbar-gutter`, so collapsed pane strips push the mark up without any
8599
+ * bookkeeping here. Re-run after anything that changes which host that is
8600
+ * (layout switch, maximize, cell rebuild); a destroyed host drops the mark from
8601
+ * the DOM, and this re-mount brings it back. */
8602
+ mountAttributionMark() {
8603
+ const mark = this.attributionMark;
8604
+ if (!mark) return;
8605
+ const host = this.bottomLeftCell()?.host ?? this.gridEl;
8606
+ if (mark.parentElement !== host) host.appendChild(mark);
8607
+ }
8170
8608
  /** Create the cells the current layout wants but don't exist yet (pool-first).
8171
8609
  * A slot's CELL IDENTITY is `order[i]` (declaration order — never the slot's own
8172
8610
  * positional id); slots past the declared list mint an auto identity once. */
@@ -8198,6 +8636,12 @@ var VelaWorkspace = class {
8198
8636
  setTimezone: (zone) => this.setTimezone(zone),
8199
8637
  context: () => this.context(),
8200
8638
  activate: (id2) => this.setActiveCell(id2),
8639
+ multiCell: () => !this.monoLayout && this.def.cells.length > 1,
8640
+ isMaximized: (id2) => this.maximizedId === id2,
8641
+ toggleMaximize: (id2) => this.maximizeCell(this.maximizedId === id2 ? null : id2),
8642
+ cellDragTarget: (id2, x, y) => this.cellAtPoint(x, y, id2),
8643
+ previewDropTarget: (target) => this.setDropTarget(target),
8644
+ dropCell: (id2, target) => this.swapCells(id2, target),
8201
8645
  onMarketChanged: (id2) => this.onCellMarketChanged(id2),
8202
8646
  onPriceStyleChanged: (id2) => this.onCellPriceStyleChanged(id2),
8203
8647
  onIndicatorsChanged: (id2) => this.onCellIndicatorsChanged(id2),
@@ -8211,6 +8655,7 @@ var VelaWorkspace = class {
8211
8655
  if (id === this.activeId) cell.host.dataset.active = "1";
8212
8656
  this.wireCell(cell);
8213
8657
  cell.chart.renderer.setLayoutMode(this.layoutCtl.current);
8658
+ cell.setControlsSuspended(this.layoutCtl.current === "mobile");
8214
8659
  if (this.favs.length > 0) cell.chart.drawings.setFavorites(this.favs);
8215
8660
  cell.setManifest(this.manifest, pooled?.indicators == null);
8216
8661
  cell.restorePersistedExt();
@@ -8220,6 +8665,7 @@ var VelaWorkspace = class {
8220
8665
  const host = this.cellsById.get(this.order[i] ?? "")?.host;
8221
8666
  if (host) this.gridEl.appendChild(host);
8222
8667
  }
8668
+ this.mountAttributionMark();
8223
8669
  }
8224
8670
  /** Per-cell chart subscriptions (trigger ② — the chart instance is stable for the
8225
8671
  * cell's whole life, so these live and die with the cell). */
@@ -8279,6 +8725,9 @@ var VelaWorkspace = class {
8279
8725
  chart.on("viewport:changed", (range) => this.propagateViewport(cell.id, range));
8280
8726
  chart.renderer.onConfigChanged(() => this.propagateStylePrefs(cell.id));
8281
8727
  chart.on("theme:changed", (t) => this.setTheme(t));
8728
+ chart.on("pane:changed", () => {
8729
+ if (cell.id === this.activeId) this.syncMobileMaximize();
8730
+ });
8282
8731
  chart.renderer.onAxisLongPress((e) => {
8283
8732
  if (this.layoutCtl.current !== "mobile") return;
8284
8733
  if (e.axis === "time") this.openTimezoneDrawer();
@@ -8599,6 +9048,7 @@ var VelaWorkspace = class {
8599
9048
  for (const cell of this.cellsById.values()) {
8600
9049
  cell.chart.renderer.closeDialogs();
8601
9050
  cell.chart.renderer.setLayoutMode(mode);
9051
+ cell.setControlsSuspended(mode === "mobile");
8602
9052
  }
8603
9053
  this.syncCellPresentation();
8604
9054
  }