@luxalgo/vela 0.6.4 → 0.6.5

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.
@@ -13407,6 +13407,7 @@ var CSS8 = `
13407
13407
  .vela-sp-badge[data-p='hyperliquid'] { color: #50d2c1; } /* palette-exempt: venue brand mark */
13408
13408
  .vela-sp-empty { padding: var(--vela-space-3); color: var(--vela-fg-muted); text-align: center; }
13409
13409
  `;
13410
+ var PAGE = 100;
13410
13411
  var SymbolPicker = class {
13411
13412
  constructor(opts) {
13412
13413
  this.source = () => [];
@@ -13414,6 +13415,7 @@ var SymbolPicker = class {
13414
13415
  this.highlighted = 0;
13415
13416
  this.seed = "";
13416
13417
  this.activeTab = "All";
13418
+ this.visible = PAGE;
13417
13419
  const doc = (opts.host ?? document.body).ownerDocument;
13418
13420
  injectStyles(STYLE_ID7, CSS8, doc);
13419
13421
  this.input = doc.createElement("input");
@@ -13440,6 +13442,12 @@ var SymbolPicker = class {
13440
13442
  }
13441
13443
  this.list = doc.createElement("div");
13442
13444
  this.list.className = "vela-sp-list";
13445
+ this.list.addEventListener("scroll", () => {
13446
+ if (this.rows.length < this.visible) return;
13447
+ if (this.list.scrollTop + this.list.clientHeight < this.list.scrollHeight - 200) return;
13448
+ this.visible += PAGE;
13449
+ this.grow();
13450
+ });
13443
13451
  this.dialog = new Dialog({
13444
13452
  title: "Symbol Search",
13445
13453
  host: opts.host,
@@ -13505,11 +13513,15 @@ var SymbolPicker = class {
13505
13513
  } else delete el.dataset.highlighted;
13506
13514
  });
13507
13515
  }
13508
- refresh() {
13509
- const doc = this.list.ownerDocument;
13516
+ computeRows() {
13510
13517
  const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Forex: ["forex"], Commodities: ["commodity"] };
13511
13518
  const pool = this.activeTab === "All" ? this.source() : this.source().filter((s) => TAB_TYPES[this.activeTab]?.includes((s.type ?? "").toLowerCase()) || this.activeTab === "Crypto" && (s.type ?? "").toLowerCase() === "futures");
13512
- this.rows = filterSymbols(pool, this.input.value);
13519
+ return filterSymbols(pool, this.input.value, this.visible);
13520
+ }
13521
+ refresh() {
13522
+ const doc = this.list.ownerDocument;
13523
+ this.visible = PAGE;
13524
+ this.rows = this.computeRows();
13513
13525
  this.highlighted = 0;
13514
13526
  this.list.replaceChildren();
13515
13527
  if (!this.rows.length) {
@@ -13519,34 +13531,42 @@ var SymbolPicker = class {
13519
13531
  this.list.appendChild(empty);
13520
13532
  return;
13521
13533
  }
13522
- for (const s of this.rows) {
13523
- const row = doc.createElement("div");
13524
- row.className = "vela-sp-row";
13525
- row.dataset.ticker = s.ticker;
13526
- const venue = s.prefix ?? s.provider;
13527
- if (venue) row.dataset.venue = venue;
13528
- const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
13529
- const main = doc.createElement("span");
13530
- main.className = "vela-sp-main";
13531
- const t = doc.createElement("span");
13532
- t.className = "vela-sp-ticker";
13533
- t.textContent = s.ticker;
13534
- const d = doc.createElement("span");
13535
- d.className = "vela-sp-desc";
13536
- d.textContent = s.description ?? (s.type ?? "");
13537
- main.append(t, d);
13538
- row.append(av, main);
13539
- if (venue) {
13540
- const badge = doc.createElement("span");
13541
- badge.className = "vela-sp-badge";
13542
- badge.dataset.p = s.provider ?? venue;
13543
- badge.textContent = venue;
13544
- row.appendChild(badge);
13545
- }
13546
- this.list.appendChild(row);
13547
- }
13534
+ for (const s of this.rows) this.list.appendChild(this.rowEl(s));
13548
13535
  this.renderHighlight();
13549
13536
  }
13537
+ /** Append the page the grown `visible` just uncovered — rows already on screen stay put. */
13538
+ grow() {
13539
+ const already = this.rows.length;
13540
+ this.rows = this.computeRows();
13541
+ for (const s of this.rows.slice(already)) this.list.appendChild(this.rowEl(s));
13542
+ }
13543
+ rowEl(s) {
13544
+ const doc = this.list.ownerDocument;
13545
+ const row = doc.createElement("div");
13546
+ row.className = "vela-sp-row";
13547
+ row.dataset.ticker = s.ticker;
13548
+ const venue = s.prefix ?? s.provider;
13549
+ if (venue) row.dataset.venue = venue;
13550
+ const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
13551
+ const main = doc.createElement("span");
13552
+ main.className = "vela-sp-main";
13553
+ const t = doc.createElement("span");
13554
+ t.className = "vela-sp-ticker";
13555
+ t.textContent = s.ticker;
13556
+ const d = doc.createElement("span");
13557
+ d.className = "vela-sp-desc";
13558
+ d.textContent = s.description ?? (s.type ?? "");
13559
+ main.append(t, d);
13560
+ row.append(av, main);
13561
+ if (venue) {
13562
+ const badge = doc.createElement("span");
13563
+ badge.className = "vela-sp-badge";
13564
+ badge.dataset.p = s.provider ?? venue;
13565
+ badge.textContent = venue;
13566
+ row.appendChild(badge);
13567
+ }
13568
+ return row;
13569
+ }
13550
13570
  };
13551
13571
 
13552
13572
  // src/widget/indicator-picker.ts
@@ -21997,6 +22017,12 @@ var SceneGraph = class {
21997
22017
  assignIndicatorZ(id) {
21998
22018
  if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.bottomZ() - 1);
21999
22019
  }
22020
+ /** Mount-time default for a LAYER-BACKED native (it paints on a canvas stacked above the
22021
+ * data canvas by default): top of the stack, so the recorded order tells the truth from
22022
+ * the first frame. Keeps an existing key, so a restored stack survives the remount. */
22023
+ assignIndicatorZTop(id) {
22024
+ if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.topZ() + 1);
22025
+ }
22000
22026
  forgetIndicatorZ(id) {
22001
22027
  this.seriesZ.delete(id);
22002
22028
  }
@@ -31035,6 +31061,19 @@ function rendererLayers() {
31035
31061
  return [...registry4.values()];
31036
31062
  }
31037
31063
 
31064
+ // src/renderers/native/core/layerStacking.ts
31065
+ function stackLayers(entries, candleZ) {
31066
+ const keyed = entries.map((e) => ({
31067
+ id: e.id,
31068
+ key: e.ownerZ ?? (e.placement === "below-data" ? -Infinity : candleZ)
31069
+ }));
31070
+ keyed.sort((a, b) => a.key - b.key);
31071
+ const below = [];
31072
+ const above = [];
31073
+ for (const k of keyed) (k.key < candleZ ? below : above).push(k.id);
31074
+ return { below, above };
31075
+ }
31076
+
31038
31077
  // src/renderers/shared/dom-raster.ts
31039
31078
  function hasInk(color) {
31040
31079
  if (!color || color === "transparent") return false;
@@ -31407,6 +31446,8 @@ var NativeRenderer = class {
31407
31446
  this.volumeRenderer = new VolumeRenderer();
31408
31447
  /** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
31409
31448
  this.extLayers = [];
31449
+ /** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
31450
+ this.layerOrderSig = "";
31410
31451
  // The attribution mark (see chrome/AttributionMark + the NOTICE file): default-on;
31411
31452
  // disabling requires an equivalent visible attribution elsewhere in the host UI.
31412
31453
  this.attributionEl = null;
@@ -32337,9 +32378,7 @@ var NativeRenderer = class {
32337
32378
  if (el.getAttribute("data-vela-screenshot") === "under") rasterizeOverlay(ctx, el, frame);
32338
32379
  }
32339
32380
  }
32340
- const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
32341
- const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
32342
- for (const canvas of [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas]) {
32381
+ for (const canvas of [...this.canvasPile(), this.chromeCanvas, this.drawingsCanvas]) {
32343
32382
  if (canvas && canvas.width > 0 && canvas.height > 0) ctx.drawImage(canvas, 0, 0);
32344
32383
  }
32345
32384
  if (frame) {
@@ -32473,6 +32512,7 @@ var NativeRenderer = class {
32473
32512
  const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
32474
32513
  const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
32475
32514
  this.plot.append(...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
32515
+ this.layerOrderSig = "";
32476
32516
  this.wrapper.appendChild(this.plot);
32477
32517
  this.factoryConfig = this.getConfig();
32478
32518
  this.attributionEl = this.buildAttributionEl();
@@ -33001,7 +33041,8 @@ var NativeRenderer = class {
33001
33041
  mountIndicator(model) {
33002
33042
  this.scene.indicators.set(model.id, model);
33003
33043
  this.refreshAnchorOffset(model);
33004
- this.scene.assignIndicatorZ(model.id);
33044
+ if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
33045
+ else this.scene.assignIndicatorZ(model.id);
33005
33046
  this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
33006
33047
  native: !!model.native,
33007
33048
  ...model.shorttitle ? { settingsTitle: model.title } : {}
@@ -33820,10 +33861,17 @@ var NativeRenderer = class {
33820
33861
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
33821
33862
  for (const l of this.extLayers) {
33822
33863
  if (!l.def.repaintOnCursor) continue;
33823
- l.instance.render(this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs));
33864
+ const lp = this.layerPane(l.def.id) ?? pane;
33865
+ if (lp.collapsed) continue;
33866
+ l.instance.render(this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs));
33824
33867
  if (this.animZoom && l.instance.animating?.()) this.animator.start();
33825
33868
  }
33826
33869
  }
33870
+ /** Blank one SDK layer canvas (a collapsed host pane suppresses the layer's painting). */
33871
+ clearLayerCanvas(canvas) {
33872
+ if (canvas.width === 0 || canvas.height === 0) return;
33873
+ canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
33874
+ }
33827
33875
  /** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
33828
33876
  extLayerArgs(id, scale, bounds, nowMs) {
33829
33877
  return {
@@ -33842,6 +33890,7 @@ var NativeRenderer = class {
33842
33890
  }
33843
33891
  /** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
33844
33892
  paintData() {
33893
+ this.syncLayerCanvasOrder();
33845
33894
  this.stampScaleInvert();
33846
33895
  this.backend.modelAlpha = this.modelAlpha;
33847
33896
  this.backend.candleBodyAlpha = this.candleBodyAlpha;
@@ -33872,9 +33921,14 @@ var NativeRenderer = class {
33872
33921
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
33873
33922
  let folded = null;
33874
33923
  for (const l of this.extLayers) {
33875
- const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
33924
+ const lp = this.layerPane(l.def.id) ?? pane;
33925
+ if (lp.collapsed) {
33926
+ this.clearLayerCanvas(l.canvas);
33927
+ continue;
33928
+ }
33929
+ const args = this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs);
33876
33930
  l.instance.render(args);
33877
- folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
33931
+ if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
33878
33932
  if (this.animZoom && l.instance.animating?.()) this.animator.start();
33879
33933
  }
33880
33934
  if (folded) {
@@ -34036,6 +34090,9 @@ var NativeRenderer = class {
34036
34090
  pane.scaleTarget = { min: 0, max: maxVol / VOLUME_PANE_FILL_FRAC };
34037
34091
  pane.axisFormat = "volume";
34038
34092
  }
34093
+ } else if (this.layerNativesOwnPane(pane, masterModels)) {
34094
+ pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
34095
+ pane.percentBaseline = this.bars[i0]?.close ?? 0;
34039
34096
  }
34040
34097
  if (pane === pricePane && this.scene.tradeMarkers.visible && pane.bounds.height > 0) {
34041
34098
  const th = this.tradesScaleHints(vr);
@@ -34122,6 +34179,59 @@ var NativeRenderer = class {
34122
34179
  }
34123
34180
  return null;
34124
34181
  }
34182
+ /** The mounted native indicator that OWNS an SDK layer — the one whose type equals the
34183
+ * layer id (the id doubles as the data channel, so the pairing is the SDK's own
34184
+ * contract). Null for chart-type channels and while the owner is hidden (a hidden
34185
+ * indicator leaves the scene; its cleared data channel paints nothing anyway). */
34186
+ layerOwner(layerId) {
34187
+ for (const m of this.scene.indicators.values()) {
34188
+ if (m.native?.type === layerId) return m;
34189
+ }
34190
+ return null;
34191
+ }
34192
+ /** The pane an SDK layer paints on: its owner's pane, else the price pane. */
34193
+ layerPane(layerId) {
34194
+ const owner = this.layerOwner(layerId);
34195
+ const paneId = owner ? owner.paneId ?? PRICE_PANE_ID2 : PRICE_PANE_ID2;
34196
+ return this.scene.panes.get(paneId) ?? null;
34197
+ }
34198
+ /** The SDK layer canvases split around the data canvas, each side back-to-front:
34199
+ * owned layers by their owner's z key against the candles' (an indicator restacked
34200
+ * below the candles takes its layer canvas along), unowned by declared placement. */
34201
+ orderedLayerCanvases() {
34202
+ const byId = new Map(this.extLayers.map((l) => [l.def.id, l.canvas]));
34203
+ const { below, above } = stackLayers(
34204
+ this.extLayers.map((l) => {
34205
+ const owner = this.layerOwner(l.def.id);
34206
+ return {
34207
+ id: l.def.id,
34208
+ placement: l.def.placement === "below-data" ? "below-data" : "above-data",
34209
+ ownerZ: owner ? this.scene.zOf(owner.id) : null
34210
+ };
34211
+ }),
34212
+ this.scene.candleZ
34213
+ );
34214
+ return { below: below.map((id) => byId.get(id)), above: above.map((id) => byId.get(id)) };
34215
+ }
34216
+ /** The full canvas pile in paint order (layers + data/volume/vpvr) — what the DOM
34217
+ * stacking and the screenshot compositor must both follow. */
34218
+ canvasPile() {
34219
+ const { below, above } = this.orderedLayerCanvases();
34220
+ return [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above];
34221
+ }
34222
+ /** Re-slot the SDK layer canvases in the plot when the computed order changed (a z
34223
+ * write, a restored config, an indicator mount/remove/restack). Runs at the top of
34224
+ * every data frame; a no-op when the signature is unchanged. Re-inserting an
34225
+ * absolutely-positioned, pointer-transparent canvas repaints nothing by itself. */
34226
+ syncLayerCanvasOrder() {
34227
+ if (this.extLayers.length === 0 || !this.plot) return;
34228
+ const { below, above } = this.orderedLayerCanvases();
34229
+ const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).def.id), "|", ...above.map((c) => this.extLayers.find((l) => l.canvas === c).def.id)].join(",");
34230
+ if (sig === this.layerOrderSig) return;
34231
+ this.layerOrderSig = sig;
34232
+ for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
34233
+ for (const c of above) this.plot.insertBefore(c, this.chromeCanvas);
34234
+ }
34125
34235
  /** True when an active volume layer is this study pane's ONLY content — so its scale should
34126
34236
  * come from volume, not the empty {0,1} placeholder. (In the price pane, or alongside a real
34127
34237
  * series, volume stays a bottom overlay and the master scale wins.) */
@@ -34131,6 +34241,15 @@ var NativeRenderer = class {
34131
34241
  if (this.nativeLayerPane("volume") !== pane) return false;
34132
34242
  return masterModels.every((m) => m.native?.type === "volume");
34133
34243
  }
34244
+ /** True when this study pane's master content is only SDK-layer natives (series-less
34245
+ * models whose type names a mounted layer) — its scale then follows the visible bars
34246
+ * (see the call site). Any real master series takes over the scale as usual. */
34247
+ layerNativesOwnPane(pane, masterModels) {
34248
+ if (pane.kind === "price" || masterModels.length === 0) return false;
34249
+ return masterModels.every(
34250
+ (m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
34251
+ );
34252
+ }
34134
34253
  /** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
34135
34254
  * band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
34136
34255
  * Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
package/dist/workspace.js CHANGED
@@ -1,6 +1,6 @@
1
- import { WidgetHistory, SessionShadingTracker, prefixedSymbol, normalizeSession, Watermark, Statusline, MarketStatusTracker, ChartContextMenu, timeframeMs, 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-3QX6QOCX.js';
2
- export { decodeState, encodeState, sanitizeState } from './chunk-3QX6QOCX.js';
3
- import { parseSymbol, Vela, normalizeTimezone, TypedEventBus, MultiProviderFeed, resolveTheme, createCustomMark, createAttributionMark, DrawingToolbar, defaultToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs, priceStyleIds } from './chunk-6FA4TDUF.js';
1
+ import { WidgetHistory, SessionShadingTracker, prefixedSymbol, normalizeSession, Watermark, Statusline, MarketStatusTracker, ChartContextMenu, timeframeMs, 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-N6MBJHMV.js';
2
+ export { decodeState, encodeState, sanitizeState } from './chunk-N6MBJHMV.js';
3
+ import { parseSymbol, Vela, normalizeTimezone, TypedEventBus, MultiProviderFeed, resolveTheme, createCustomMark, createAttributionMark, DrawingToolbar, defaultToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs, priceStyleIds } from './chunk-FQBT4NFM.js';
4
4
  import { resolveEngines, legendActionsProviderFor, rendererDefaults, widgetActions, widgetAttachments } from './chunk-BEJ57HP4.js';
5
5
  import { KeymapManager, isEditableTarget } from './chunk-2R7BANEM.js';
6
6
  import { applyPlotOverlayTokens, ensureUIHost, Menu } from './chunk-OICPLNU7.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luxalgo/vela",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "Open-source charting library with a native high-performance renderer, drawing tools, pluggable chart types and pluggable scripting engines.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://luxalgo.com/vela",