@luxalgo/vela 0.5.1 → 0.5.2

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-DGesjOJT.d.ts → DataProvider-DTOEXKTI.d.ts} +1 -1
  2. package/dist/{DataProvider-BBQ3Hr21.d.cts → DataProvider-gnzv-dgc.d.cts} +1 -1
  3. package/dist/{chunk-ULIX6AE2.js → chunk-ATPU5CI6.js} +1 -1
  4. package/dist/{chunk-S6F6SQ2L.js → chunk-B5TIKQJ5.js} +60 -7
  5. package/dist/{chunk-EP6NDJYK.js → chunk-LJLZR44Y.js} +4 -2
  6. package/dist/{chunk-AIW3EJUE.js → chunk-P556H6EA.js} +329 -10
  7. package/dist/{chunk-FNOMKHEZ.js → chunk-RRFGWZNJ.js} +1321 -363
  8. package/dist/{chunk-5SE4PL3H.js → chunk-Y7TK4ZWK.js} +1363 -48
  9. package/dist/{contributions-BlV3kb0O.d.cts → contributions-Ci_i3IN2.d.cts} +33 -2
  10. package/dist/{contributions-6N0rbrPC.d.ts → contributions-b6AVrqqG.d.ts} +33 -2
  11. package/dist/{history-DX3Zne3a.d.cts → history-Cf1lVsap.d.cts} +15 -3
  12. package/dist/{history-BTd52YMA.d.ts → history-Ci3M0xLV.d.ts} +15 -3
  13. package/dist/index.cjs +1374 -366
  14. package/dist/index.d.cts +52 -19
  15. package/dist/index.d.ts +52 -19
  16. package/dist/index.js +4 -4
  17. package/dist/{options-DbQholFV.d.cts → options-XTBpbx0s.d.cts} +47 -1
  18. package/dist/{options-DbQholFV.d.ts → options-XTBpbx0s.d.ts} +47 -1
  19. package/dist/{plugin-TN3n-9YN.d.ts → plugin-CnNd7cuF.d.ts} +172 -25
  20. package/dist/{plugin-O34MbXf-.d.cts → plugin-DfH4Y-Om.d.cts} +172 -25
  21. package/dist/plugin.cjs +53 -7
  22. package/dist/plugin.d.cts +4 -4
  23. package/dist/plugin.d.ts +4 -4
  24. package/dist/plugin.js +2 -2
  25. package/dist/providers/binance.d.cts +2 -2
  26. package/dist/providers/binance.d.ts +2 -2
  27. package/dist/providers/coinbase.d.cts +2 -2
  28. package/dist/providers/coinbase.d.ts +2 -2
  29. package/dist/providers/hyperliquid.d.cts +2 -2
  30. package/dist/providers/hyperliquid.d.ts +2 -2
  31. package/dist/ui.cjs +332 -9
  32. package/dist/ui.d.cts +64 -2
  33. package/dist/ui.d.ts +64 -2
  34. package/dist/ui.js +3 -3
  35. package/dist/vela.global.js +1374 -366
  36. package/dist/vela.global.min.js +84 -34
  37. package/dist/widget.cjs +3348 -550
  38. package/dist/widget.d.cts +65 -7
  39. package/dist/widget.d.ts +65 -7
  40. package/dist/widget.js +169 -18
  41. package/dist/workspace.cjs +4251 -1400
  42. package/dist/workspace.d.cts +45 -9
  43. package/dist/workspace.d.ts +45 -9
  44. package/dist/workspace.js +226 -22
  45. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
- import { O as OHLCV, V as VisibleRangePreset, a as VisibleRange, b as VelaOptions, c as VelaTheme, N as NativeBackend } from './options-DbQholFV.cjs';
2
- import { S as SyncSetting, V as VelaStorage, C as CellState, W as WidgetHistory, R as ResolvedIndicator, a as RangePreset, T as TrackSizes, b as VelaShellOptions, c as SyncOptions, d as SyncKind, e as WorkspaceState } from './history-DX3Zne3a.cjs';
3
- export { f as ChartState, P as PanelsState, g as decodeState, h as encodeState, s as sanitizeState } from './history-DX3Zne3a.cjs';
1
+ import { O as OHLCV, V as VisibleRangePreset, a as VisibleRange, b as VelaOptions, c as VelaTheme, N as NativeBackend } from './options-XTBpbx0s.cjs';
2
+ import { S as SyncSetting, V as VelaStorage, C as CellState, W as WidgetHistory, R as ResolvedIndicator, a as RangePreset, T as TrackSizes, b as VelaShellOptions, c as SyncOptions, d as SyncKind, e as WorkspaceState } from './history-Cf1lVsap.cjs';
3
+ export { f as ChartState, P as PanelsState, g as decodeState, h as encodeState, s as sanitizeState } from './history-Cf1lVsap.cjs';
4
4
  import { K as KeymapManager } from './keymap-CGOz5F5f.cjs';
5
- import { I as IndicatorHandle, S as ScriptingEngine, W as WidgetContext, V as Vela, a as ScriptRun } from './contributions-BlV3kb0O.cjs';
6
- import { M as MarketDataFeed } from './DataProvider-BBQ3Hr21.cjs';
5
+ import { I as IndicatorHandle, S as ScriptingEngine, W as WidgetContext, V as Vela, a as ScriptRun } from './contributions-Ci_i3IN2.cjs';
6
+ import { M as MarketDataFeed } from './DataProvider-gnzv-dgc.cjs';
7
7
 
8
8
  /** The cells that follow `originId` under `setting` — PURE (never includes the origin). */
9
9
  declare function syncTargets(originId: string, setting: SyncSetting | undefined, cellIds: readonly string[]): string[];
@@ -146,12 +146,16 @@ declare class ChartCell {
146
146
  private watermarkOn;
147
147
  /** Indicator titles (this cell's in-chart legend rows) shown. */
148
148
  private indicatorTitlesOn;
149
+ /** Plot values beside this cell's legend titles shown. */
150
+ private indicatorValuesOn;
149
151
  private destroyed;
150
152
  constructor(id: string, gridHost: HTMLElement, seed: CellBoot, deps: CellDeps);
151
153
  /** Show/hide this cell's symbol watermark (persisted per cell). */
152
154
  setWatermarkVisible(visible: boolean): void;
153
155
  /** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
154
156
  setIndicatorTitlesVisible(visible: boolean): void;
157
+ /** Show/hide the plot values beside this cell's legend titles (persisted per cell). */
158
+ setIndicatorValuesVisible(visible: boolean): void;
155
159
  /** The LIVE chart of this cell — never cache it across a layout change (the cell's
156
160
  * identity is what endures; the chart dies with the cell). */
157
161
  get chart(): Vela;
@@ -169,6 +173,9 @@ declare class ChartCell {
169
173
  * colors and direction rule (candle bodies by close-vs-open, baseline by position
170
174
  * against the live baseline price, …) instead of the fixed theme tokens. */
171
175
  private syncStatuslineColors;
176
+ /** Multi-cell grids keep the status line on one row and hide what doesn't fit —
177
+ * the workspace flips this with the layout (see Statusline.setFitMode). */
178
+ setStatuslineFit(on: boolean): void;
172
179
  /** The workspace shell keeps the app theme; the cell host's tokens re-derive from
173
180
  * the LIVE plot surface (see {@link applyPlotOverlayTokens}). */
174
181
  private syncPlotOverlayTokens;
@@ -411,6 +418,7 @@ declare class VelaWorkspace {
411
418
  /** The side-panel column, shared by the whole grid. */
412
419
  private readonly dock;
413
420
  private readonly symbolPicker;
421
+ /** Null when the host disabled it (`indicatorPicker: false`). */
414
422
  private readonly indicatorPicker;
415
423
  private readonly tfQuick;
416
424
  private shortcutsHelp;
@@ -453,6 +461,17 @@ declare class VelaWorkspace {
453
461
  private openDialogs;
454
462
  private alerts;
455
463
  private alertsMenu;
464
+ /** Writes `data-layout` on the root and pushes mode flips into every cell's renderer. */
465
+ private layoutCtl;
466
+ private readonly mobileBar;
467
+ private readonly drawingPill;
468
+ private tfDrawer;
469
+ private drawingsDrawer;
470
+ private moreDrawer;
471
+ private timezoneDrawer;
472
+ private priceScaleDrawer;
473
+ /** Plot-local y of the last price-axis long-press (targets the pane under the finger). */
474
+ private priceScalePressY;
456
475
  private readonly attachmentDisposers;
457
476
  /** The single grid-wide attribution mark — re-inked on a live theme swap. */
458
477
  private attributionMark;
@@ -551,10 +570,13 @@ declare class VelaWorkspace {
551
570
  private applySyncSetting;
552
571
  /**
553
572
  * Mirror an origin cell's pointer time onto its same-group followers as GHOST
554
- * crosshairs (`renderer.setExternalCrosshair`). Leaving the origin propagates
555
- * `null` (the event already carries it) and clears every ghost. No busy guard
556
- * needed: an external crosshair never re-emits `onCrosshairMove` the flow is
557
- * one-way by port contract, so no echo loop can exist.
573
+ * crosshairs (`renderer.setExternalCrosshair`). The horizontal price level rides
574
+ * along ONLY to followers showing the same ticker price scales are comparable
575
+ * there, while an origin's price painted on another market's pane would be noise.
576
+ * Leaving the origin propagates `null` (the event already carries it) and clears
577
+ * every ghost. No busy guard needed: an external crosshair never re-emits
578
+ * `onCrosshairMove` — the flow is one-way by port contract, so no echo loop can
579
+ * exist.
558
580
  */
559
581
  private propagateCrosshair;
560
582
  /**
@@ -599,6 +621,20 @@ declare class VelaWorkspace {
599
621
  private onCellIndicatorsChanged;
600
622
  /** Timeframe changes routed from the topbar menu / quick entry (chip state follows). */
601
623
  private setActiveTimeframe;
624
+ /** The mode flipped (container resized across the breakpoint, or a coarse-pointer
625
+ * change). Close the open surfaces — a desktop card must not linger over the
626
+ * mobile chrome (and vice versa) — and re-present every cell's own chrome. */
627
+ private onLayoutModeChange;
628
+ /** Push the layout-shape-dependent chrome onto every cell: multi-cell grids keep
629
+ * the status line on one row (segments that don't fit hide), and on MOBILE their
630
+ * legends' fold chip routes to the object tree instead of unfolding in place —
631
+ * per-indicator controls live there (the legend rows have no room in a grid cell). */
632
+ private syncCellPresentation;
633
+ private openTimeframeDrawer;
634
+ private openDrawingsDrawer;
635
+ private openMoreDrawer;
636
+ private openTimezoneDrawer;
637
+ private openPriceScaleDrawer;
602
638
  /** The aggregated alerts bell — entries carry their cell; selecting one activates it. */
603
639
  private openAlertsMenu;
604
640
  /** Mount registered attachments not yet mounted on this workspace (idempotent per id). */
@@ -1,9 +1,9 @@
1
- import { O as OHLCV, V as VisibleRangePreset, a as VisibleRange, b as VelaOptions, c as VelaTheme, N as NativeBackend } from './options-DbQholFV.js';
2
- import { S as SyncSetting, V as VelaStorage, C as CellState, W as WidgetHistory, R as ResolvedIndicator, a as RangePreset, T as TrackSizes, b as VelaShellOptions, c as SyncOptions, d as SyncKind, e as WorkspaceState } from './history-BTd52YMA.js';
3
- export { f as ChartState, P as PanelsState, g as decodeState, h as encodeState, s as sanitizeState } from './history-BTd52YMA.js';
1
+ import { O as OHLCV, V as VisibleRangePreset, a as VisibleRange, b as VelaOptions, c as VelaTheme, N as NativeBackend } from './options-XTBpbx0s.js';
2
+ import { S as SyncSetting, V as VelaStorage, C as CellState, W as WidgetHistory, R as ResolvedIndicator, a as RangePreset, T as TrackSizes, b as VelaShellOptions, c as SyncOptions, d as SyncKind, e as WorkspaceState } from './history-Ci3M0xLV.js';
3
+ export { f as ChartState, P as PanelsState, g as decodeState, h as encodeState, s as sanitizeState } from './history-Ci3M0xLV.js';
4
4
  import { K as KeymapManager } from './keymap-CGOz5F5f.js';
5
- import { I as IndicatorHandle, S as ScriptingEngine, W as WidgetContext, V as Vela, a as ScriptRun } from './contributions-6N0rbrPC.js';
6
- import { M as MarketDataFeed } from './DataProvider-DGesjOJT.js';
5
+ import { I as IndicatorHandle, S as ScriptingEngine, W as WidgetContext, V as Vela, a as ScriptRun } from './contributions-b6AVrqqG.js';
6
+ import { M as MarketDataFeed } from './DataProvider-DTOEXKTI.js';
7
7
 
8
8
  /** The cells that follow `originId` under `setting` — PURE (never includes the origin). */
9
9
  declare function syncTargets(originId: string, setting: SyncSetting | undefined, cellIds: readonly string[]): string[];
@@ -146,12 +146,16 @@ declare class ChartCell {
146
146
  private watermarkOn;
147
147
  /** Indicator titles (this cell's in-chart legend rows) shown. */
148
148
  private indicatorTitlesOn;
149
+ /** Plot values beside this cell's legend titles shown. */
150
+ private indicatorValuesOn;
149
151
  private destroyed;
150
152
  constructor(id: string, gridHost: HTMLElement, seed: CellBoot, deps: CellDeps);
151
153
  /** Show/hide this cell's symbol watermark (persisted per cell). */
152
154
  setWatermarkVisible(visible: boolean): void;
153
155
  /** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
154
156
  setIndicatorTitlesVisible(visible: boolean): void;
157
+ /** Show/hide the plot values beside this cell's legend titles (persisted per cell). */
158
+ setIndicatorValuesVisible(visible: boolean): void;
155
159
  /** The LIVE chart of this cell — never cache it across a layout change (the cell's
156
160
  * identity is what endures; the chart dies with the cell). */
157
161
  get chart(): Vela;
@@ -169,6 +173,9 @@ declare class ChartCell {
169
173
  * colors and direction rule (candle bodies by close-vs-open, baseline by position
170
174
  * against the live baseline price, …) instead of the fixed theme tokens. */
171
175
  private syncStatuslineColors;
176
+ /** Multi-cell grids keep the status line on one row and hide what doesn't fit —
177
+ * the workspace flips this with the layout (see Statusline.setFitMode). */
178
+ setStatuslineFit(on: boolean): void;
172
179
  /** The workspace shell keeps the app theme; the cell host's tokens re-derive from
173
180
  * the LIVE plot surface (see {@link applyPlotOverlayTokens}). */
174
181
  private syncPlotOverlayTokens;
@@ -411,6 +418,7 @@ declare class VelaWorkspace {
411
418
  /** The side-panel column, shared by the whole grid. */
412
419
  private readonly dock;
413
420
  private readonly symbolPicker;
421
+ /** Null when the host disabled it (`indicatorPicker: false`). */
414
422
  private readonly indicatorPicker;
415
423
  private readonly tfQuick;
416
424
  private shortcutsHelp;
@@ -453,6 +461,17 @@ declare class VelaWorkspace {
453
461
  private openDialogs;
454
462
  private alerts;
455
463
  private alertsMenu;
464
+ /** Writes `data-layout` on the root and pushes mode flips into every cell's renderer. */
465
+ private layoutCtl;
466
+ private readonly mobileBar;
467
+ private readonly drawingPill;
468
+ private tfDrawer;
469
+ private drawingsDrawer;
470
+ private moreDrawer;
471
+ private timezoneDrawer;
472
+ private priceScaleDrawer;
473
+ /** Plot-local y of the last price-axis long-press (targets the pane under the finger). */
474
+ private priceScalePressY;
456
475
  private readonly attachmentDisposers;
457
476
  /** The single grid-wide attribution mark — re-inked on a live theme swap. */
458
477
  private attributionMark;
@@ -551,10 +570,13 @@ declare class VelaWorkspace {
551
570
  private applySyncSetting;
552
571
  /**
553
572
  * Mirror an origin cell's pointer time onto its same-group followers as GHOST
554
- * crosshairs (`renderer.setExternalCrosshair`). Leaving the origin propagates
555
- * `null` (the event already carries it) and clears every ghost. No busy guard
556
- * needed: an external crosshair never re-emits `onCrosshairMove` the flow is
557
- * one-way by port contract, so no echo loop can exist.
573
+ * crosshairs (`renderer.setExternalCrosshair`). The horizontal price level rides
574
+ * along ONLY to followers showing the same ticker price scales are comparable
575
+ * there, while an origin's price painted on another market's pane would be noise.
576
+ * Leaving the origin propagates `null` (the event already carries it) and clears
577
+ * every ghost. No busy guard needed: an external crosshair never re-emits
578
+ * `onCrosshairMove` — the flow is one-way by port contract, so no echo loop can
579
+ * exist.
558
580
  */
559
581
  private propagateCrosshair;
560
582
  /**
@@ -599,6 +621,20 @@ declare class VelaWorkspace {
599
621
  private onCellIndicatorsChanged;
600
622
  /** Timeframe changes routed from the topbar menu / quick entry (chip state follows). */
601
623
  private setActiveTimeframe;
624
+ /** The mode flipped (container resized across the breakpoint, or a coarse-pointer
625
+ * change). Close the open surfaces — a desktop card must not linger over the
626
+ * mobile chrome (and vice versa) — and re-present every cell's own chrome. */
627
+ private onLayoutModeChange;
628
+ /** Push the layout-shape-dependent chrome onto every cell: multi-cell grids keep
629
+ * the status line on one row (segments that don't fit hide), and on MOBILE their
630
+ * legends' fold chip routes to the object tree instead of unfolding in place —
631
+ * per-indicator controls live there (the legend rows have no room in a grid cell). */
632
+ private syncCellPresentation;
633
+ private openTimeframeDrawer;
634
+ private openDrawingsDrawer;
635
+ private openMoreDrawer;
636
+ private openTimezoneDrawer;
637
+ private openPriceScaleDrawer;
602
638
  /** The aggregated alerts bell — entries carry their cell; selecting one activates it. */
603
639
  private openAlertsMenu;
604
640
  /** Mount registered attachments not yet mounted on this workspace (idempotent per id). */
package/dist/workspace.js CHANGED
@@ -1,10 +1,10 @@
1
- import { WidgetHistory, prefixedSymbol, Watermark, Statusline, ChartContextMenu, statuslineInkOf, indicatorLedger, Glider, localStorageAdapter, decodeState, SymbolPicker, IndicatorPicker, TimeframeQuick, Topbar, PanelDock, ObjectTree, DataWindow, Toast, Bottombar, toolShortcutHints, resolveIndicators, sanitizeState, encodeState, ZOOM_IN, ZOOM_OUT, PAN_FAST, ShortcutsHelp } from './chunk-5SE4PL3H.js';
2
- export { decodeState, encodeState, sanitizeState } from './chunk-5SE4PL3H.js';
3
- import { parseSymbol, Vela, normalizeTimezone, TypedEventBus, MultiProviderFeed, resolveTheme, createCustomMark, createAttributionMark, DrawingToolbar, defaultToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-FNOMKHEZ.js';
4
- import { resolveEngines, legendActionsProviderFor, rendererDefaults, widgetAttachments } from './chunk-S6F6SQ2L.js';
5
- import { applyPlotOverlayTokens, ensureUIHost, KeymapManager, Menu, isEditableTarget } from './chunk-AIW3EJUE.js';
6
- import './chunk-ULIX6AE2.js';
7
- import { registerIcon, svg16, injectStyles } from './chunk-EP6NDJYK.js';
1
+ import { WidgetHistory, prefixedSymbol, Watermark, Statusline, ChartContextMenu, statuslineInkOf, indicatorLedger, Glider, localStorageAdapter, decodeState, SymbolPicker, IndicatorPicker, TimeframeQuick, Topbar, PanelDock, ObjectTree, DataWindow, Toast, Bottombar, MobileBar, DrawingPill, LayoutModeController, toolShortcutHints, resolveIndicators, sanitizeState, encodeState, TimeframeDrawer, RANGE_PRESETS, DrawingsDrawer, MoreDrawer, priceStyleIcon, priceStyleLabel, TimezoneDrawer, PriceScaleDrawer, ZOOM_IN, ZOOM_OUT, PAN_FAST, ShortcutsHelp } from './chunk-Y7TK4ZWK.js';
2
+ export { decodeState, encodeState, sanitizeState } from './chunk-Y7TK4ZWK.js';
3
+ import { parseSymbol, Vela, normalizeTimezone, TypedEventBus, MultiProviderFeed, resolveTheme, createCustomMark, createAttributionMark, DrawingToolbar, defaultToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs, priceStyleIds } from './chunk-RRFGWZNJ.js';
4
+ import { resolveEngines, legendActionsProviderFor, rendererDefaults, widgetActions, widgetAttachments } from './chunk-B5TIKQJ5.js';
5
+ import { applyPlotOverlayTokens, ensureUIHost, KeymapManager, Menu, isEditableTarget } from './chunk-P556H6EA.js';
6
+ import './chunk-ATPU5CI6.js';
7
+ import { registerIcon, svg16, injectStyles } from './chunk-LJLZR44Y.js';
8
8
 
9
9
  // src/workspace/sync.ts
10
10
  function syncTargets(originId, setting, cellIds) {
@@ -81,6 +81,8 @@ var ChartCell = class {
81
81
  this.pendingRange = null;
82
82
  /** Indicator titles (this cell's in-chart legend rows) shown. */
83
83
  this.indicatorTitlesOn = true;
84
+ /** Plot values beside this cell's legend titles shown. */
85
+ this.indicatorValuesOn = true;
84
86
  this.destroyed = false;
85
87
  this.appTheme = deps.theme;
86
88
  const symbol = prefixedSymbol(seed);
@@ -145,10 +147,14 @@ var ChartCell = class {
145
147
  this.inner.on("load:end", () => {
146
148
  this.volumeMayBePending = false;
147
149
  });
150
+ this.inner.on("load:start", () => this.watermark?.setLoading(true));
151
+ this.inner.on("load:end", () => this.watermark?.setLoading(false));
148
152
  const tz = deps.timezone();
149
153
  if (tz !== "Etc/UTC") this.inner.renderer.set("timezone", tz);
150
154
  this.indicatorTitlesOn = seed.indicatorTitles ?? true;
151
155
  if (!this.indicatorTitlesOn) this.inner.renderer.set("indicatorTitles", false);
156
+ this.indicatorValuesOn = seed.indicatorValues ?? true;
157
+ if (!this.indicatorValuesOn) this.inner.renderer.set("indicatorValues", false);
152
158
  this.watermarkOn = seed.watermark ?? deps.watermark;
153
159
  this.watermark = deps.watermark ? new Watermark(this.host, symbol ?? "", seed.timeframe ?? "60") : null;
154
160
  if (!this.watermarkOn) this.watermark?.setVisible(false);
@@ -256,6 +262,12 @@ var ChartCell = class {
256
262
  label: "Titles",
257
263
  get: () => this.indicatorTitlesOn,
258
264
  set: (v) => this.setIndicatorTitlesVisible(v)
265
+ },
266
+ {
267
+ kind: "toggle",
268
+ label: "Values",
269
+ get: () => this.indicatorValuesOn,
270
+ set: (v) => this.setIndicatorValuesVisible(v)
259
271
  }
260
272
  ]
261
273
  },
@@ -289,6 +301,12 @@ var ChartCell = class {
289
301
  this.inner?.renderer.set("indicatorTitles", visible);
290
302
  this.deps.onStateDirty();
291
303
  }
304
+ /** Show/hide the plot values beside this cell's legend titles (persisted per cell). */
305
+ setIndicatorValuesVisible(visible) {
306
+ this.indicatorValuesOn = visible;
307
+ this.inner?.renderer.set("indicatorValues", visible);
308
+ this.deps.onStateDirty();
309
+ }
292
310
  /** The LIVE chart of this cell — never cache it across a layout change (the cell's
293
311
  * identity is what endures; the chart dies with the cell). */
294
312
  get chart() {
@@ -333,6 +351,11 @@ var ChartCell = class {
333
351
  if (!this.statusline || !this.inner) return;
334
352
  this.statusline.setDirectionColors(...statuslineInkOf(this.inner.renderer, this.priceStyle));
335
353
  }
354
+ /** Multi-cell grids keep the status line on one row and hide what doesn't fit —
355
+ * the workspace flips this with the layout (see Statusline.setFitMode). */
356
+ setStatuslineFit(on) {
357
+ this.statusline?.setFitMode(on);
358
+ }
336
359
  /** The workspace shell keeps the app theme; the cell host's tokens re-derive from
337
360
  * the LIVE plot surface (see {@link applyPlotOverlayTokens}). */
338
361
  syncPlotOverlayTokens() {
@@ -521,6 +544,7 @@ var ChartCell = class {
521
544
  priceStyle: this.priceStyle,
522
545
  watermark: this.watermarkOn,
523
546
  indicatorTitles: this.indicatorTitlesOn,
547
+ indicatorValues: this.indicatorValuesOn,
524
548
  rendererConfig: this.inner?.renderer.getConfig() ?? void 0,
525
549
  drawings: this.inner ? this.inner.drawings.toJSON() : void 0,
526
550
  // Natives from the chart's SYNC registry read — an async catalog mirror here
@@ -850,6 +874,9 @@ var CSS = `
850
874
  .vela-ws-splitter:hover::after { content: ''; position: absolute; background: var(--vela-separator-hover-line); }
851
875
  .vela-ws-splitter[data-axis='cols']:hover::after { left: calc(50% - 1px); top: 0; width: 2px; height: 100%; }
852
876
  .vela-ws-splitter[data-axis='rows']:hover::after { top: calc(50% - 1px); left: 0; height: 2px; width: 100%; }
877
+ /* Mobile: the docked drawing-toolbar column would eat a phone-width grid \u2014 the shell's
878
+ drawings drawer + on-chart pill replace it (same policy as the widget's in-chart bar). */
879
+ [data-layout='mobile'] .vela-ws-toolbar { display: none; }
853
880
  `;
854
881
  registerIcon("layout", svg16('<rect x="1.5" y="1.5" width="13" height="13" rx="1.5"/><path d="M8 1.5v13M1.5 8h13"/>'));
855
882
  function declaredOrder(cells) {
@@ -916,6 +943,13 @@ var VelaWorkspace = class {
916
943
  this.openDialogs = 0;
917
944
  this.alerts = [];
918
945
  this.alertsMenu = null;
946
+ this.tfDrawer = null;
947
+ this.drawingsDrawer = null;
948
+ this.moreDrawer = null;
949
+ this.timezoneDrawer = null;
950
+ this.priceScaleDrawer = null;
951
+ /** Plot-local y of the last price-axis long-press (targets the pane under the finger). */
952
+ this.priceScalePressY = 0;
919
953
  this.attachmentDisposers = /* @__PURE__ */ new Map();
920
954
  /** The single grid-wide attribution mark — re-inked on a live theme swap. */
921
955
  this.attributionMark = null;
@@ -976,14 +1010,18 @@ var VelaWorkspace = class {
976
1010
  }
977
1011
  });
978
1012
  this.symbolPicker.setSource(() => this.feed.symbols());
979
- this.indicatorPicker = new IndicatorPicker({
1013
+ this.indicatorPicker = opts.indicatorPicker !== false ? new IndicatorPicker({
980
1014
  host: this.root,
981
1015
  library: () => this.active.libraryRows(),
982
1016
  onChart: () => this.active.onChartRows(),
983
1017
  onAdd: (i) => this.active.addFromLibrary(i),
984
1018
  onRemove: (i) => this.active.removeFromChart(i),
985
- onOpenChange: (open) => this.trackDialog(open)
986
- });
1019
+ onOpenChange: (open) => {
1020
+ if (open) for (const cell of this.cells()) cell.chart.renderer.closeDialogs();
1021
+ this.trackDialog(open);
1022
+ }
1023
+ }) : null;
1024
+ const picker = this.indicatorPicker;
987
1025
  this.tfQuick = new TimeframeQuick({
988
1026
  host: this.root,
989
1027
  onApply: (tf) => this.setActiveTimeframe(tf),
@@ -992,7 +1030,7 @@ var VelaWorkspace = class {
992
1030
  this.topbar = new Topbar(this.root, {
993
1031
  symbol: "",
994
1032
  onSymbolClick: () => this.symbolPicker.open(),
995
- onIndicatorsClick: () => this.indicatorPicker.open(),
1033
+ ...picker ? { onIndicatorsClick: () => picker.open() } : {},
996
1034
  onUndoClick: () => this.active.history.undo(),
997
1035
  onRedoClick: () => this.active.history.redo(),
998
1036
  onScreenshotClick: () => this.active.downloadScreenshot(),
@@ -1105,7 +1143,21 @@ var VelaWorkspace = class {
1105
1143
  onTimezone: (zone) => this.setTimezone(zone),
1106
1144
  onSettingsClick: () => this.active.chart.renderer.openSettings()
1107
1145
  }) : null;
1146
+ this.mobileBar = opts.bottombar !== false ? new MobileBar(this.root, {
1147
+ symbol: "",
1148
+ timeframe: "60",
1149
+ onSymbolClick: () => this.symbolPicker.open(),
1150
+ onTimeframeClick: () => this.openTimeframeDrawer(),
1151
+ ...picker ? { onIndicatorsClick: () => picker.open() } : {},
1152
+ getContext: () => this.context(),
1153
+ onDrawingsClick: () => this.openDrawingsDrawer(),
1154
+ onMoreClick: () => this.openMoreDrawer(),
1155
+ onSettingsClick: () => this.active.chart.renderer.openSettings()
1156
+ }) : null;
1157
+ this.drawingPill = new DrawingPill(this.gridEl);
1108
1158
  hostEl.appendChild(this.root);
1159
+ this.layoutCtl = new LayoutModeController(this.root, opts.layoutMode ?? "auto");
1160
+ this.layoutCtl.onChange((mode) => this.onLayoutModeChange(mode));
1109
1161
  this.splitters = new SplitterLayer(this.gridEl, {
1110
1162
  tracks: () => this.currentTracks(),
1111
1163
  grid: () => occupancyGrid(this.def),
@@ -1126,6 +1178,7 @@ var VelaWorkspace = class {
1126
1178
  this.cellBackend = this.backendFor(this.def);
1127
1179
  this.applyGrid();
1128
1180
  this.buildCells();
1181
+ this.syncCellPresentation();
1129
1182
  this.setActiveCell(bootActive != null && this.cellsById.has(bootActive) ? bootActive : this.order[0] ?? null);
1130
1183
  if (opts.autofocus) this.refocusActive();
1131
1184
  if (opts.indicators !== void 0) {
@@ -1208,6 +1261,7 @@ var VelaWorkspace = class {
1208
1261
  refreshActions() {
1209
1262
  this.mountAttachments();
1210
1263
  this.topbar.renderActions();
1264
+ this.mobileBar?.renderActions();
1211
1265
  this.dock.refresh();
1212
1266
  for (const cell of this.cells()) cell.chart.renderer.setLegendActions(legendActionsProviderFor(cell.chart, () => this.context()));
1213
1267
  }
@@ -1273,6 +1327,7 @@ var VelaWorkspace = class {
1273
1327
  this.cellBackend = this.backendFor(this.def);
1274
1328
  this.applyGrid();
1275
1329
  this.buildCells();
1330
+ this.syncCellPresentation();
1276
1331
  this.topbar.setLayout(this.def.id);
1277
1332
  const nextActive = st.activeCellId && this.cellsById.has(st.activeCellId) ? st.activeCellId : this.order[0] ?? null;
1278
1333
  if (nextActive === this.activeId) this.projectActiveCell();
@@ -1333,6 +1388,7 @@ var VelaWorkspace = class {
1333
1388
  this.cellBackend = nextBackend;
1334
1389
  this.applyGrid();
1335
1390
  this.buildCells();
1391
+ this.syncCellPresentation();
1336
1392
  this.topbar.setLayout(next.id);
1337
1393
  const nextActive = activeAfterLayout(this.activeId, this.order.slice(0, next.cells.length));
1338
1394
  if (nextActive === this.activeId) this.projectActiveCell();
@@ -1368,11 +1424,19 @@ var VelaWorkspace = class {
1368
1424
  this.drawToolbar?.destroy();
1369
1425
  this.topbar.destroy();
1370
1426
  this.bottombar?.destroy();
1427
+ this.mobileBar?.destroy();
1428
+ this.drawingPill.destroy();
1429
+ this.tfDrawer?.destroy();
1430
+ this.drawingsDrawer?.destroy();
1431
+ this.moreDrawer?.destroy();
1432
+ this.timezoneDrawer?.destroy();
1433
+ this.priceScaleDrawer?.destroy();
1434
+ this.layoutCtl.destroy();
1371
1435
  this.dock.destroy();
1372
1436
  this.objectTree.destroy();
1373
1437
  this.dataWindow.destroy();
1374
1438
  this.symbolPicker.destroy();
1375
- this.indicatorPicker.destroy();
1439
+ this.indicatorPicker?.destroy();
1376
1440
  this.tfQuick.destroy();
1377
1441
  this.shortcutsHelp?.destroy();
1378
1442
  this.toast.destroy();
@@ -1393,6 +1457,10 @@ var VelaWorkspace = class {
1393
1457
  this.topbar.setPriceStyle(cell.priceStyle);
1394
1458
  this.topbar.setIndicatorCount(cell.indicatorCount);
1395
1459
  this.topbar.renderActions();
1460
+ this.mobileBar?.renderActions();
1461
+ this.mobileBar?.setSymbol(cell.symbol);
1462
+ this.mobileBar?.setTimeframe(cell.timeframe);
1463
+ this.drawingPill.onChart(cell.chart);
1396
1464
  const pushHistory = () => this.topbar.setHistoryState(cell.history.canUndo, cell.history.canRedo);
1397
1465
  this.historyUnsub?.();
1398
1466
  this.historyUnsub = cell.history.onChange(pushHistory);
@@ -1400,7 +1468,7 @@ var VelaWorkspace = class {
1400
1468
  this.objectTree.setSymbol(cell.symbol);
1401
1469
  this.dock.onChart(cell.chart);
1402
1470
  this.bottombar?.setActiveRange(cell.activeRangeId);
1403
- this.indicatorPicker.sync();
1471
+ this.indicatorPicker?.sync();
1404
1472
  this.glider.stop();
1405
1473
  const d = cell.chart.drawings;
1406
1474
  if (d.getTool() !== this.globalTool) d.setTool(this.globalTool);
@@ -1522,6 +1590,7 @@ var VelaWorkspace = class {
1522
1590
  this.cellsById.set(id, cell);
1523
1591
  if (id === this.activeId) cell.host.dataset.active = "1";
1524
1592
  this.wireCell(cell);
1593
+ cell.chart.renderer.setLayoutMode(this.layoutCtl.current);
1525
1594
  if (this.favs.length > 0) cell.chart.drawings.setFavorites(this.favs);
1526
1595
  cell.setManifest(this.manifest, pooled?.indicators == null);
1527
1596
  this.events.emit("cell:created", { id });
@@ -1551,7 +1620,7 @@ var VelaWorkspace = class {
1551
1620
  this.drawToolbar?.setFavorites(favorites);
1552
1621
  this.markStateDirty();
1553
1622
  });
1554
- chart.renderer.onCrosshairMove((e) => this.propagateCrosshair(cell.id, e.time));
1623
+ chart.renderer.onCrosshairMove((e) => this.propagateCrosshair(cell.id, e.time, e.paneKind === "price" ? e.price : null));
1555
1624
  chart.on("drawing:created", ({ id }) => {
1556
1625
  this.propagateDrawing(cell.id, id);
1557
1626
  this.markStateDirty();
@@ -1587,6 +1656,11 @@ var VelaWorkspace = class {
1587
1656
  });
1588
1657
  chart.on("viewport:changed", (range) => this.propagateViewport(cell.id, range));
1589
1658
  chart.on("theme:changed", (t) => this.setTheme(t));
1659
+ chart.renderer.onAxisLongPress((e) => {
1660
+ if (this.layoutCtl.current !== "mobile") return;
1661
+ if (e.axis === "time") this.openTimezoneDrawer();
1662
+ else this.openPriceScaleDrawer(e.y);
1663
+ });
1590
1664
  }
1591
1665
  // ── sync links ──────────────────────────────────────────────
1592
1666
  applySyncSetting(kind, setting, align = false) {
@@ -1620,17 +1694,24 @@ var VelaWorkspace = class {
1620
1694
  }
1621
1695
  /**
1622
1696
  * Mirror an origin cell's pointer time onto its same-group followers as GHOST
1623
- * crosshairs (`renderer.setExternalCrosshair`). Leaving the origin propagates
1624
- * `null` (the event already carries it) and clears every ghost. No busy guard
1625
- * needed: an external crosshair never re-emits `onCrosshairMove` the flow is
1626
- * one-way by port contract, so no echo loop can exist.
1697
+ * crosshairs (`renderer.setExternalCrosshair`). The horizontal price level rides
1698
+ * along ONLY to followers showing the same ticker price scales are comparable
1699
+ * there, while an origin's price painted on another market's pane would be noise.
1700
+ * Leaving the origin propagates `null` (the event already carries it) and clears
1701
+ * every ghost. No busy guard needed: an external crosshair never re-emits
1702
+ * `onCrosshairMove` — the flow is one-way by port contract, so no echo loop can
1703
+ * exist.
1627
1704
  */
1628
- propagateCrosshair(originId, time) {
1705
+ propagateCrosshair(originId, time, price = null) {
1629
1706
  if (this.destroyed) return;
1630
1707
  const setting = this.syncOpts.crosshair;
1631
1708
  if (!setting) return;
1709
+ const originTicker = parseSymbol(this.cellsById.get(originId)?.symbol ?? "").ticker;
1632
1710
  for (const id of syncTargets(originId, setting, [...this.cellsById.keys()])) {
1633
- this.cellsById.get(id)?.chart.renderer.setExternalCrosshair(time);
1711
+ const follower = this.cellsById.get(id);
1712
+ if (!follower) continue;
1713
+ const comparable = originTicker !== "" && parseSymbol(follower.symbol).ticker === originTicker;
1714
+ follower.chart.renderer.setExternalCrosshair(time, comparable ? price : null);
1634
1715
  }
1635
1716
  }
1636
1717
  /**
@@ -1785,6 +1866,8 @@ var VelaWorkspace = class {
1785
1866
  if (!cell) return;
1786
1867
  this.topbar.setSymbol(cell.symbol);
1787
1868
  this.topbar.setTimeframe(cell.timeframe);
1869
+ this.mobileBar?.setSymbol(cell.symbol);
1870
+ this.mobileBar?.setTimeframe(cell.timeframe);
1788
1871
  this.objectTree.setSymbol(cell.symbol);
1789
1872
  }
1790
1873
  /** Trigger ② — a cell's indicator ledger changed: count + picker only if active. */
@@ -1794,13 +1877,132 @@ var VelaWorkspace = class {
1794
1877
  const cell = this.cellsById.get(id);
1795
1878
  if (!cell) return;
1796
1879
  this.topbar.setIndicatorCount(cell.indicatorCount);
1797
- this.indicatorPicker.sync();
1880
+ this.indicatorPicker?.sync();
1798
1881
  }
1799
1882
  /** Timeframe changes routed from the topbar menu / quick entry (chip state follows). */
1800
1883
  setActiveTimeframe(tf) {
1801
1884
  this.bottombar?.setActiveRange(null);
1802
1885
  this.active.setTimeframe(tf);
1803
1886
  }
1887
+ /** The mode flipped (container resized across the breakpoint, or a coarse-pointer
1888
+ * change). Close the open surfaces — a desktop card must not linger over the
1889
+ * mobile chrome (and vice versa) — and re-present every cell's own chrome. */
1890
+ onLayoutModeChange(mode) {
1891
+ this.symbolPicker.close();
1892
+ this.indicatorPicker?.close();
1893
+ this.tfDrawer?.close();
1894
+ this.drawingsDrawer?.close();
1895
+ this.moreDrawer?.close();
1896
+ this.timezoneDrawer?.close();
1897
+ this.priceScaleDrawer?.close();
1898
+ this.alertsMenu?.destroy();
1899
+ this.alertsMenu = null;
1900
+ for (const cell of this.cellsById.values()) {
1901
+ cell.chart.renderer.closeDialogs();
1902
+ cell.chart.renderer.setLayoutMode(mode);
1903
+ }
1904
+ this.syncCellPresentation();
1905
+ }
1906
+ /** Push the layout-shape-dependent chrome onto every cell: multi-cell grids keep
1907
+ * the status line on one row (segments that don't fit hide), and on MOBILE their
1908
+ * legends' fold chip routes to the object tree instead of unfolding in place —
1909
+ * per-indicator controls live there (the legend rows have no room in a grid cell). */
1910
+ syncCellPresentation() {
1911
+ const multi = this.def.cells.length > 1;
1912
+ const overview = multi && this.layoutCtl.current === "mobile" ? () => this.dock.toggle("objects", true) : null;
1913
+ for (const cell of this.cellsById.values()) {
1914
+ cell.setStatuslineFit(multi);
1915
+ cell.chart.renderer.setLegendOverviewAction(overview);
1916
+ }
1917
+ }
1918
+ // ── mobile drawers (built on first open; every read is live and hits the ACTIVE cell) ──
1919
+ openTimeframeDrawer() {
1920
+ this.tfDrawer ?? (this.tfDrawer = new TimeframeDrawer({
1921
+ host: this.root,
1922
+ timeframes: this.opts.timeframes ?? DEFAULT_TIMEFRAMES,
1923
+ ranges: RANGE_PRESETS,
1924
+ currentTimeframe: () => this.active.timeframe,
1925
+ activeRange: () => this.active.activeRangeId,
1926
+ onTimeframe: (tf) => this.setActiveTimeframe(tf),
1927
+ onRange: (preset) => {
1928
+ this.active.applyRange(preset);
1929
+ this.bottombar?.setActiveRange(preset.id);
1930
+ },
1931
+ onOpenChange: (open) => this.trackDialog(open)
1932
+ }));
1933
+ this.tfDrawer.open();
1934
+ }
1935
+ openDrawingsDrawer() {
1936
+ this.drawingsDrawer ?? (this.drawingsDrawer = new DrawingsDrawer({
1937
+ host: this.root,
1938
+ toolbar: () => defaultToolbar(),
1939
+ // the shared static toolbar's definition (see constructor)
1940
+ currentTool: () => this.active.chart.drawings.getTool(),
1941
+ isFavorite: (type) => this.active.chart.drawings.isFavorite(type),
1942
+ onFavorite: (type, on) => this.active.chart.drawings.setFavorite(type, on),
1943
+ onSelect: (type) => this.active.chart.drawings.setTool(type),
1944
+ onOpenChange: (open) => this.trackDialog(open)
1945
+ }));
1946
+ this.drawingsDrawer.open();
1947
+ }
1948
+ openMoreDrawer() {
1949
+ this.moreDrawer ?? (this.moreDrawer = new MoreDrawer({
1950
+ host: this.root,
1951
+ onUndo: () => this.active.history.undo(),
1952
+ onRedo: () => this.active.history.redo(),
1953
+ onScreenshot: () => this.active.downloadScreenshot(),
1954
+ canUndo: () => this.active.history.canUndo,
1955
+ canRedo: () => this.active.history.canRedo,
1956
+ priceStyles: () => priceStyleIds().map((id) => ({ id, label: priceStyleLabel(id), icon: priceStyleIcon(id) })),
1957
+ priceStyle: () => this.active.priceStyle,
1958
+ onPriceStyle: (id) => this.active.setPriceStyle(id),
1959
+ panels: () => [...this.dock.list()],
1960
+ onTogglePanel: (id) => this.dock.toggle(id),
1961
+ alerts: () => this.alerts.map((a) => ({ title: `[${a.cellId} \xB7 ${a.symbol}] ${a.title}`, message: a.message, time: a.time })),
1962
+ // Left-aligned actions have their own bottom-bar stop — only the rest
1963
+ // lands in the drawer, or every left action would appear twice.
1964
+ actions: () => widgetActions("topbar", this.context()).filter((a) => a.align !== "left").map((a) => ({ label: a.label, icon: a.icon, run: () => a.run(this.context()) })),
1965
+ // The desktop layout dropdown's whole surface — the grid canvas, the
1966
+ // non-canvas presets and the sync switches — relocated into the kebab
1967
+ // drawer (the topbar is hidden on mobile). Same reads as the topbar block.
1968
+ layout: {
1969
+ shape: () => layoutShape(this.def),
1970
+ presets: () => layouts().filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label, checked: l.id === this.def.id })),
1971
+ onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
1972
+ onSelectPreset: (id) => this.setLayout(id),
1973
+ syncs: () => [
1974
+ { id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
1975
+ { id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
1976
+ { id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true }
1977
+ ],
1978
+ onToggleSync: (id) => {
1979
+ const kind = id;
1980
+ this.sync.set(kind, this.syncOpts[kind] ? false : true);
1981
+ }
1982
+ },
1983
+ onOpenChange: (open) => this.trackDialog(open)
1984
+ }));
1985
+ this.moreDrawer.open();
1986
+ }
1987
+ openTimezoneDrawer() {
1988
+ this.timezoneDrawer ?? (this.timezoneDrawer = new TimezoneDrawer({
1989
+ host: this.root,
1990
+ timezone: () => this.timezone,
1991
+ onTimezone: (zone) => this.setTimezone(zone),
1992
+ onOpenChange: (open) => this.trackDialog(open)
1993
+ }));
1994
+ this.timezoneDrawer.open();
1995
+ }
1996
+ openPriceScaleDrawer(y) {
1997
+ this.priceScalePressY = y;
1998
+ this.priceScaleDrawer ?? (this.priceScaleDrawer = new PriceScaleDrawer({
1999
+ host: this.root,
2000
+ chart: () => this.activeId ? this.cellsById.get(this.activeId)?.chart ?? null : null,
2001
+ pressY: () => this.priceScalePressY,
2002
+ onOpenChange: (open) => this.trackDialog(open)
2003
+ }));
2004
+ this.priceScaleDrawer.open();
2005
+ }
1804
2006
  /** The aggregated alerts bell — entries carry their cell; selecting one activates it. */
1805
2007
  openAlertsMenu(anchor) {
1806
2008
  this.alertsMenu?.destroy();
@@ -1872,7 +2074,9 @@ var VelaWorkspace = class {
1872
2074
  this.keymap.register({ id: "view.zoom-out", keys: "mod+arrowdown", label: "Zoom out", category: "Chart", run: () => this.glider.zoom(ZOOM_OUT) });
1873
2075
  this.keymap.register({ id: "view.pan-left", keys: "mod+arrowleft", label: "Pan toward history", category: "Chart", run: () => this.active.chart.panBy(-PAN_FAST) });
1874
2076
  this.keymap.register({ id: "view.pan-right", keys: "mod+arrowright", label: "Pan toward now", category: "Chart", run: () => this.active.chart.panBy(PAN_FAST) });
1875
- this.keymap.register({ id: "indicators.open", keys: "/", label: "Open the indicator picker", category: "Indicators", run: () => this.indicatorPicker.open() });
2077
+ if (this.indicatorPicker) {
2078
+ this.keymap.register({ id: "indicators.open", keys: "/", label: "Open the indicator picker", category: "Indicators", run: () => this.indicatorPicker?.open() });
2079
+ }
1876
2080
  this.keymap.register({
1877
2081
  id: "help.shortcuts",
1878
2082
  keys: "?",