@luxalgo/vela 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/{DataProvider-Bu9E5Zh5.d.ts → DataProvider-BALR0qPq.d.ts} +1 -1
  2. package/dist/{DataProvider-Ck2qyS_9.d.cts → DataProvider-D5uMiJYz.d.cts} +1 -1
  3. package/dist/{chunk-A4G64KVF.js → chunk-5RQGCZTX.js} +299 -110
  4. package/dist/{chunk-IFJJPXSV.js → chunk-BFA32GOU.js} +44 -16
  5. package/dist/{chunk-CZRNTHD6.js → chunk-BKHSQ4YM.js} +129 -125
  6. package/dist/{chunk-A347YL2P.js → chunk-QGVSNAX7.js} +67 -9
  7. package/dist/{chunk-UHXG5J7C.js → chunk-ZADTVRUO.js} +1 -1
  8. package/dist/{contributions-BjQ7hyIx.d.ts → contributions-BbhbZd3c.d.ts} +31 -9
  9. package/dist/{contributions-Cv2Mutvn.d.cts → contributions-CcBFe-91.d.cts} +31 -9
  10. package/dist/index.cjs +379 -156
  11. package/dist/index.d.cts +44 -17
  12. package/dist/index.d.ts +44 -17
  13. package/dist/index.js +4 -4
  14. package/dist/{options-ex2_gtKp.d.ts → options-C7b_Qssu.d.cts} +42 -1
  15. package/dist/{options-ex2_gtKp.d.cts → options-C7b_Qssu.d.ts} +42 -1
  16. package/dist/{plugin-CiyhU7pi.d.ts → plugin-Cnix5ZBh.d.ts} +6 -4
  17. package/dist/{plugin-absNH7Yn.d.cts → plugin-D6MlX4--.d.cts} +6 -4
  18. package/dist/plugin.cjs +39 -15
  19. package/dist/plugin.d.cts +4 -4
  20. package/dist/plugin.d.ts +4 -4
  21. package/dist/plugin.js +2 -2
  22. package/dist/providers/binance.d.cts +2 -2
  23. package/dist/providers/binance.d.ts +2 -2
  24. package/dist/providers/coinbase.d.cts +2 -2
  25. package/dist/providers/coinbase.d.ts +2 -2
  26. package/dist/providers/hyperliquid.d.cts +2 -2
  27. package/dist/providers/hyperliquid.d.ts +2 -2
  28. package/dist/{statusline-model-BXoU4Kua.d.ts → statusline-model-D8NL7o4L.d.ts} +3 -3
  29. package/dist/{statusline-model-cRhPBLPO.d.cts → statusline-model-DUKt7_TQ.d.cts} +3 -3
  30. package/dist/ui.cjs +8 -4
  31. package/dist/ui.d.cts +10 -7
  32. package/dist/ui.d.ts +10 -7
  33. package/dist/ui.js +2 -2
  34. package/dist/vela.global.js +379 -156
  35. package/dist/vela.global.min.js +70 -70
  36. package/dist/widget.cjs +406 -127
  37. package/dist/widget.d.cts +6 -6
  38. package/dist/widget.d.ts +6 -6
  39. package/dist/widget.js +6 -6
  40. package/dist/workspace.cjs +406 -127
  41. package/dist/workspace.d.cts +39 -6
  42. package/dist/workspace.d.ts +39 -6
  43. package/dist/workspace.js +5 -5
  44. package/package.json +1 -1
  45. /package/dist/{chunk-FE6V2YAQ.js → chunk-JU4CWEBF.js} +0 -0
@@ -1,5 +1,5 @@
1
- import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, DEFAULT_DRAWING_COLOR, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-IFJJPXSV.js';
2
- import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-CZRNTHD6.js';
1
+ import { deserializeDrawing, chartTypes, rendererLayers, isLineLikeSeries, seriesShownOn, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, nativeInstanceChannel, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, DEFAULT_DRAWING_COLOR, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, seriesInScale, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-BFA32GOU.js';
2
+ import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-BKHSQ4YM.js';
3
3
  import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BULLISH, CATEGORICAL, SERIES_LINE, BEARISH, isDarkColor, INFO, injectStyles, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, iconMarkup, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-CAFCLMPF.js';
4
4
 
5
5
  // src/core/events/EventBus.ts
@@ -3170,11 +3170,6 @@ function resolveAnimations(animations) {
3170
3170
  };
3171
3171
  }
3172
3172
 
3173
- // src/core/model/series.ts
3174
- function isLineLikeSeries(spec) {
3175
- return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
3176
- }
3177
-
3178
3173
  // src/renderers/shared/IndicatorInputsDialog.ts
3179
3174
  var PROPS_TAB = "Properties";
3180
3175
  function bagOf(row, decl) {
@@ -5255,33 +5250,32 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
5255
5250
  center: (wickLeftDev + wickDev / 2) / dpr
5256
5251
  };
5257
5252
  }
5258
- function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
5253
+ function aggregateCandleColumns(bars, i0, i1, xOf, yOf, colorOf2) {
5259
5254
  const out = [];
5260
5255
  let col = NaN;
5261
- let intervals = [];
5256
+ let runs = [];
5262
5257
  const flush = () => {
5263
- if (intervals.length === 0) return;
5264
- const merged = [intervals[0]];
5265
- for (let k = 1; k < intervals.length; k += 1) {
5266
- const prev2 = merged[merged.length - 1];
5267
- const next = intervals[k];
5268
- if (Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
5269
- prev2.hi = next.hi;
5270
- if (next.headIdx < prev2.headIdx) {
5271
- prev2.headIdx = next.headIdx;
5272
- prev2.headTime = next.headTime;
5273
- prev2.open = next.open;
5274
- }
5275
- if (next.lastIdx > prev2.lastIdx) {
5276
- prev2.lastIdx = next.lastIdx;
5277
- prev2.close = next.close;
5258
+ if (runs.length === 0) return;
5259
+ runs.sort((a, b) => a.lo - b.lo);
5260
+ const merged = [];
5261
+ for (const next of runs) {
5262
+ let prev2;
5263
+ for (let k = merged.length - 1; k >= 0; k -= 1) {
5264
+ if (merged[k].color === next.color) {
5265
+ prev2 = merged[k];
5266
+ break;
5278
5267
  }
5268
+ }
5269
+ if (prev2 && Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
5270
+ prev2.hi = next.hi;
5271
+ if (next.lastIdx > prev2.lastIdx) prev2.lastIdx = next.lastIdx;
5279
5272
  } else {
5280
5273
  merged.push(next);
5281
5274
  }
5282
5275
  }
5283
- for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
5284
- intervals = [];
5276
+ merged.sort((a, b) => a.lastIdx - b.lastIdx);
5277
+ for (const r of merged) out.push({ x: col, hi: r.hi, lo: r.lo, color: r.color });
5278
+ runs = [];
5285
5279
  };
5286
5280
  for (let i = i0; i <= i1; i += 1) {
5287
5281
  const b = bars[i];
@@ -5291,26 +5285,17 @@ function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
5291
5285
  flush();
5292
5286
  col = x;
5293
5287
  }
5288
+ const color = colorOf2(b);
5294
5289
  let lo = b.low;
5295
5290
  let hi = b.high;
5296
- let headIdx = i;
5297
- let headTime = b.time;
5298
- let open = b.open;
5299
- for (let k = intervals.length - 1; k >= 0; k -= 1) {
5300
- const iv = intervals[k];
5301
- if (iv.lo > hi || iv.hi < lo) continue;
5302
- if (iv.lo < lo) lo = iv.lo;
5303
- if (iv.hi > hi) hi = iv.hi;
5304
- if (iv.headIdx < headIdx) {
5305
- headIdx = iv.headIdx;
5306
- headTime = iv.headTime;
5307
- open = iv.open;
5308
- }
5309
- intervals.splice(k, 1);
5310
- }
5311
- let insertAt = 0;
5312
- while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
5313
- intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open, lastIdx: i, close: b.close });
5291
+ for (let k = runs.length - 1; k >= 0; k -= 1) {
5292
+ const r = runs[k];
5293
+ if (r.color !== color || r.lo > hi || r.hi < lo) continue;
5294
+ if (r.lo < lo) lo = r.lo;
5295
+ if (r.hi > hi) hi = r.hi;
5296
+ runs.splice(k, 1);
5297
+ }
5298
+ runs.push({ lo, hi, color, lastIdx: i });
5314
5299
  }
5315
5300
  flush();
5316
5301
  return out;
@@ -6285,7 +6270,7 @@ var WebGL2Backend = class {
6285
6270
  * Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
6286
6271
  emitGlowSources(b, scene, pane, coords, i0, i1) {
6287
6272
  const emitOne = (s, off) => {
6288
- if (!isLineLikeSeries(s) || s.visible === false) return;
6273
+ if (!isLineLikeSeries(s) || !seriesShownOn(s, "pane")) return;
6289
6274
  if (s.kind === "histogram" || s.kind === "columns") return;
6290
6275
  if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
6291
6276
  else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
@@ -6396,7 +6381,7 @@ var WebGL2Backend = class {
6396
6381
  const spacing = coords.bodySpacing();
6397
6382
  const tier = candleTier(spacing);
6398
6383
  if (tier === "aggregate") {
6399
- this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors);
6384
+ this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => barColors.get(bar.time) ?? (bar.close >= bar.open ? up : down));
6400
6385
  return;
6401
6386
  }
6402
6387
  const tickW = Math.max(1, Math.round(spacing * 0.35));
@@ -6537,14 +6522,17 @@ var WebGL2Backend = class {
6537
6522
  const spacing = coords.bodySpacing();
6538
6523
  const tier = candleTier(spacing);
6539
6524
  const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
6525
+ const up = paint.up;
6526
+ const down = paint.down;
6527
+ const cs = paint.candle;
6540
6528
  if (tier === "aggregate") {
6541
- this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, paint.up, paint.down, barColors);
6529
+ this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => {
6530
+ const isUp = bar.close >= bar.open;
6531
+ return (isUp ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(bar.time) ?? (isUp ? up : down);
6532
+ });
6542
6533
  return;
6543
6534
  }
6544
6535
  const drawBody = tier === "full";
6545
- const up = paint.up;
6546
- const down = paint.down;
6547
- const cs = paint.candle;
6548
6536
  const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
6549
6537
  for (let i = i0; i <= i1; i += 1) {
6550
6538
  const bar = bars[i];
@@ -6593,24 +6581,26 @@ var WebGL2Backend = class {
6593
6581
  /**
6594
6582
  * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
6595
6583
  * rounded pixel column collapse into high-low sticks — one per contiguous
6596
- * coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
6597
- * column stays a void. Draw cost stays bounded by screen width, not bar count;
6598
- * stick color follows its own first-open→last-close.
6584
+ * SAME-COLOR coverage run (see {@link aggregateCandleColumns}), so a price gap
6585
+ * inside the column stays a void and every bar's extent keeps its own color. Draw
6586
+ * cost stays bounded by screen width, not bar count. `colorOf` is the calling
6587
+ * style's per-bar paint rule (candles and OHLC bars resolve theirs differently).
6599
6588
  */
6600
- emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
6589
+ emitCandlesAggregated(b, bars, i0, i1, coords, pane, colorOf2) {
6601
6590
  const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
6602
- for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
6603
- const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
6591
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
6592
+ const c = parseColor(s.color);
6604
6593
  const hY = yOf(s.hi);
6605
6594
  b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
6606
6595
  }
6607
6596
  }
6608
6597
  emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
6598
+ if (!seriesShownOn(spec, "pane")) return;
6609
6599
  if (spec.kind === "candle" || spec.kind === "bar") {
6610
6600
  this.emitPlotCandles(b, spec, pane, coords, i0, i1, theme, off);
6611
6601
  return;
6612
6602
  }
6613
- if (!isLineLikeSeries(spec) || spec.visible === false) return;
6603
+ if (!isLineLikeSeries(spec)) return;
6614
6604
  switch (spec.kind) {
6615
6605
  case "histogram":
6616
6606
  case "columns":
@@ -8274,7 +8264,7 @@ var Canvas2dBackend = class {
8274
8264
  const spacing = coords.bodySpacing();
8275
8265
  const tier = candleTier(spacing);
8276
8266
  if (tier === "aggregate") {
8277
- this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors);
8267
+ this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => barColors.get(b.time) ?? (b.close >= b.open ? up : down));
8278
8268
  return;
8279
8269
  }
8280
8270
  const tickW = Math.max(1, Math.round(spacing * 0.35));
@@ -8450,12 +8440,15 @@ var Canvas2dBackend = class {
8450
8440
  const spacing = coords.bodySpacing();
8451
8441
  const tier = candleTier(spacing);
8452
8442
  const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
8443
+ const cs = paint.candle;
8453
8444
  if (tier === "aggregate") {
8454
- this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, paint.up, paint.down, barColors);
8445
+ this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => {
8446
+ const up = b.close >= b.open;
8447
+ return (up ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(b.time) ?? (up ? paint.up : paint.down);
8448
+ });
8455
8449
  return;
8456
8450
  }
8457
8451
  const drawBody = tier === "full";
8458
- const cs = paint.candle;
8459
8452
  const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
8460
8453
  for (let i = i0; i <= i1; i += 1) {
8461
8454
  const b = bars[i];
@@ -8512,17 +8505,18 @@ var Canvas2dBackend = class {
8512
8505
  }
8513
8506
  /**
8514
8507
  * Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
8515
- * sticks (one per contiguous coverage run — see {@link aggregateCandleColumns}),
8516
- * so draw cost is bounded by screen width (not bar count) when zoomed far out
8517
- * and a price gap inside the column stays a void. Each stick's color follows
8518
- * its first-open→last-close direction (a barcolor() on the head bar still wins).
8508
+ * sticks (one per contiguous SAME-COLOR coverage run — see
8509
+ * {@link aggregateCandleColumns}), so draw cost is bounded by screen width (not
8510
+ * bar count) when zoomed far out, a price gap inside the column stays a void, and
8511
+ * every bar's extent keeps its own color. `colorOf` is the calling style's per-bar
8512
+ * paint rule (candles and OHLC bars resolve theirs differently).
8519
8513
  */
8520
- drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
8514
+ drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, colorOf2) {
8521
8515
  const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
8522
8516
  ctx.lineWidth = 1;
8523
- for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
8517
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
8524
8518
  const x = s.x + 0.5;
8525
- ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
8519
+ ctx.strokeStyle = s.color;
8526
8520
  ctx.beginPath();
8527
8521
  ctx.moveTo(x, yOf(s.hi));
8528
8522
  ctx.lineTo(x, yOf(s.lo));
@@ -8530,11 +8524,12 @@ var Canvas2dBackend = class {
8530
8524
  }
8531
8525
  }
8532
8526
  drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
8527
+ if (!seriesShownOn(spec, "pane")) return;
8533
8528
  if (spec.kind === "candle" || spec.kind === "bar") {
8534
8529
  this.drawPlotCandles(ctx, spec, pane, coords, i0, i1, theme, off);
8535
8530
  return;
8536
8531
  }
8537
- if (!isLineLikeSeries(spec) || spec.visible === false) return;
8532
+ if (!isLineLikeSeries(spec)) return;
8538
8533
  switch (spec.kind) {
8539
8534
  case "histogram":
8540
8535
  case "columns":
@@ -16772,6 +16767,7 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
16772
16767
  return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
16773
16768
  }
16774
16769
  function considerSeries(s, i0, i1, off, consider) {
16770
+ if (!seriesInScale(s)) return;
16775
16771
  if (s.kind === "candle" || s.kind === "bar") {
16776
16772
  for (let i = i0; i <= i1; i += 1) {
16777
16773
  const b = s.bars[i - off];
@@ -16991,6 +16987,22 @@ var MarkPopover = class {
16991
16987
  this.openKey = null;
16992
16988
  /** Bumped per open/close — a lazy content resolving after its popup went away is dropped. */
16993
16989
  this.generation = 0;
16990
+ /**
16991
+ * The cluster whose popup was open when the current pointer press began. The anchor is
16992
+ * hit-transparent (the glyph is canvas pixels), so the shell's outside-dismiss closes the
16993
+ * popup on pointerdown; the click the renderer derives from the same pointerup must then
16994
+ * read as "close", not reopen that cluster. Captured on the document ahead of the shell's
16995
+ * (deferred) listener, cleared once the release has dispatched.
16996
+ */
16997
+ this.pressKey = null;
16998
+ this.onPress = () => {
16999
+ if (this.openKey === null) return;
17000
+ this.pressKey = this.openKey;
17001
+ const doc = this.deps.plot.ownerDocument;
17002
+ const clear = () => void setTimeout(() => this.pressKey = null, 0);
17003
+ doc.addEventListener("pointerup", clear, { capture: true, once: true });
17004
+ doc.addEventListener("pointercancel", clear, { capture: true, once: true });
17005
+ };
16994
17006
  const doc = deps.plot.ownerDocument;
16995
17007
  injectStyles(CALLOUT_STYLE_ID, CALLOUT_CSS, doc);
16996
17008
  injectStyles(MARKS_STYLE_ID, MARKS_CSS, doc);
@@ -16998,12 +17010,14 @@ var MarkPopover = class {
16998
17010
  this.anchor.className = "vela-marks-anchor";
16999
17011
  Object.assign(this.anchor.style, { position: "absolute", pointerEvents: "none", left: "0", top: "0", width: "0", height: "0" });
17000
17012
  deps.plot.appendChild(this.anchor);
17013
+ doc.addEventListener("pointerdown", this.onPress, true);
17001
17014
  }
17002
17015
  /** The cluster key the open popup belongs to, or null. */
17003
17016
  get key() {
17004
17017
  return this.openKey;
17005
17018
  }
17006
17019
  open(cluster, rect) {
17020
+ if (this.pressKey === cluster.key) return;
17007
17021
  this.close();
17008
17022
  this.place(rect);
17009
17023
  const gen = ++this.generation;
@@ -17051,6 +17065,7 @@ var MarkPopover = class {
17051
17065
  }
17052
17066
  destroy() {
17053
17067
  this.close();
17068
+ this.deps.plot.ownerDocument.removeEventListener("pointerdown", this.onPress, true);
17054
17069
  this.anchor.remove();
17055
17070
  }
17056
17071
  place(rect) {
@@ -17921,6 +17936,13 @@ var NativeRenderer = class {
17921
17936
  this.backdropRenderer = new BackdropRenderer();
17922
17937
  this.volumeRenderer = new VolumeRenderer();
17923
17938
  /** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
17939
+ /**
17940
+ * The mounted SDK layers. One BASE entry per registered definition (channel = the layer
17941
+ * id, no owner of its own), plus one DEDICATED entry per mounted native indicator whose
17942
+ * model names its own channel (`native.channel` — an instance of a multi-instance type):
17943
+ * same definition, its own canvas, reading that instance's channel and following that
17944
+ * instance's pane and z key. `channel` is unique across the array.
17945
+ */
17924
17946
  this.extLayers = [];
17925
17947
  /** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
17926
17948
  this.layerOrderSig = "";
@@ -19066,11 +19088,7 @@ var NativeRenderer = class {
19066
19088
  Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
19067
19089
  this.plot = document.createElement("div");
19068
19090
  Object.assign(this.plot.style, { position: "absolute", top: "0", right: "0", bottom: "0", left: "0" });
19069
- this.extLayers = rendererLayers().map((def) => {
19070
- const canvas = document.createElement("canvas");
19071
- Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
19072
- return { def, instance: def.create(), canvas };
19073
- });
19091
+ this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
19074
19092
  const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
19075
19093
  const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
19076
19094
  this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
@@ -19087,6 +19105,7 @@ var NativeRenderer = class {
19087
19105
  this.volumeRenderer.mount(this.volumeCanvas);
19088
19106
  this.vpvrRenderer.mount(this.vpvrCanvas);
19089
19107
  for (const l of this.extLayers) l.instance.mount(l.canvas);
19108
+ for (const m of this.scene.indicators.values()) this.ensureInstanceLayer(m);
19090
19109
  this.chrome.mount(this.chromeCanvas);
19091
19110
  this.crosshairLayer.mount(this.cursorCanvas);
19092
19111
  this.coords.setViewport(defaultViewport());
@@ -19679,6 +19698,7 @@ var NativeRenderer = class {
19679
19698
  this.refreshAnchorOffset(model);
19680
19699
  if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
19681
19700
  else this.scene.assignIndicatorZ(model.id);
19701
+ this.ensureInstanceLayer(model);
19682
19702
  if (model.legend !== false) {
19683
19703
  this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
19684
19704
  native: !!model.native,
@@ -19722,6 +19742,7 @@ var NativeRenderer = class {
19722
19742
  this.scene.vpvrLayer = null;
19723
19743
  }
19724
19744
  this.scene.indicators.delete(handle.id);
19745
+ this.dropInstanceLayer(handle.id);
19725
19746
  this.scene.forgetIndicatorZ(handle.id);
19726
19747
  this.scene.forgetAnchorOffset(handle.id);
19727
19748
  this.scene.dropIndicatorScale(handle.id);
@@ -20515,29 +20536,31 @@ var NativeRenderer = class {
20515
20536
  up: bar.close >= bar.open
20516
20537
  };
20517
20538
  }
20518
- /** One group per indicator (name = indicator title), each with a row per drawable plot. */
20539
+ /** One group per indicator (name = indicator title), each with a row per plot shown in the data window. */
20519
20540
  dataWindowGroups(idx, pricePane) {
20520
20541
  const groups = [];
20521
20542
  for (const model of this.scene.indicators.values()) {
20522
- const rows = this.dataWindowRowsFor(model, idx, pricePane);
20543
+ const rows = this.readoutRowsFor(model, idx, pricePane, "dataWindow");
20523
20544
  if (rows.length) groups.push({ name: model.title, rows });
20524
20545
  }
20525
20546
  return groups;
20526
20547
  }
20527
- /** One indicator's readout at bar `idx`: a row per drawable plot, formatted on its pane's scale. */
20528
- dataWindowRowsFor(model, idx, pricePane) {
20548
+ /** One indicator's readout at bar `idx` for one value surface (the legend or the data window):
20549
+ * a row per plot shown on that surface, formatted on its pane's scale. A plot's `display`
20550
+ * decides per surface — a data-window-only plot has a row here and none in the legend. */
20551
+ readoutRowsFor(model, idx, pricePane, surface) {
20529
20552
  if (model.native?.type === "volume") return this.volumeReadoutRows(model, idx);
20530
20553
  const pane = (model.paneId ? this.scene.panes.get(model.paneId) : null) ?? pricePane;
20531
20554
  const off = this.scene.offsetOf(model.id);
20532
20555
  const rows = [];
20533
20556
  for (const s of model.series) {
20557
+ if (!seriesShownOn(s, surface)) continue;
20534
20558
  let value;
20535
20559
  let color;
20536
20560
  if (s.kind === "candle" || s.kind === "bar") {
20537
20561
  value = s.bars[idx - off]?.close;
20538
20562
  color = s.style?.up ?? this.theme.upColor;
20539
20563
  } else if (isLineLikeSeries(s)) {
20540
- if (s.visible === false) continue;
20541
20564
  value = s.points[idx - off]?.value;
20542
20565
  color = s.points[idx - off]?.color ?? s.style.color;
20543
20566
  } else {
@@ -20560,9 +20583,10 @@ var NativeRenderer = class {
20560
20583
  const color = bar.close >= bar.open ? cfg?.upColor ?? this.theme.upColor : cfg?.downColor ?? this.theme.downColor;
20561
20584
  return [{ label: model.title, value: formatVolume(vol), color }];
20562
20585
  }
20563
- /** Refresh the plot values beside every legend title — the same readout the data window
20564
- * shows (crosshair bar, else the latest bar), pushed per paint. A hidden indicator has
20565
- * no scene model, so its row is absent from the map and its readout clears. */
20586
+ /** Refresh the plot values beside every legend title — the same bar the data window reads
20587
+ * (crosshair bar, else the latest bar), pushed per paint, filtered to the plots whose
20588
+ * `display` includes the legend. A hidden indicator has no scene model, so its row is
20589
+ * absent from the map and its readout clears. */
20566
20590
  updateLegendValues() {
20567
20591
  if (!this.inputsUI) return;
20568
20592
  const n = this.bars.length;
@@ -20571,7 +20595,7 @@ var NativeRenderer = class {
20571
20595
  const idx = this.hoverLogical != null ? this.hoverLogical : n - 1;
20572
20596
  const pricePane = this.dataWindowPricePane();
20573
20597
  for (const model of this.scene.indicators.values()) {
20574
- values.set(model.id, this.dataWindowRowsFor(model, idx, pricePane).map((r) => ({ value: r.value, color: r.color })));
20598
+ values.set(model.id, this.readoutRowsFor(model, idx, pricePane, "legend").map((r) => ({ value: r.value, color: r.color })));
20575
20599
  }
20576
20600
  }
20577
20601
  this.inputsUI.setPlotValues(values);
@@ -20597,9 +20621,9 @@ var NativeRenderer = class {
20597
20621
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
20598
20622
  for (const l of this.extLayers) {
20599
20623
  if (!l.def.repaintOnCursor) continue;
20600
- const lp = this.layerPane(l.def.id) ?? pane;
20624
+ const lp = this.layerPane(l) ?? pane;
20601
20625
  if (lp.collapsed) continue;
20602
- l.instance.render(this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs));
20626
+ l.instance.render(this.extLayerArgs(l, lp.scale, lp.bounds, nowMs));
20603
20627
  if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
20604
20628
  }
20605
20629
  }
@@ -20608,13 +20632,15 @@ var NativeRenderer = class {
20608
20632
  if (canvas.width === 0 || canvas.height === 0) return;
20609
20633
  canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
20610
20634
  }
20611
- /** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
20612
- extLayerArgs(id, scale, bounds, nowMs) {
20635
+ /** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
20636
+ * Data + pending come from the entry's channel (per instance for a dedicated entry);
20637
+ * the settings bag is the TYPE's — a chart type's dialog values, one bag per type. */
20638
+ extLayerArgs(l, scale, bounds, nowMs) {
20613
20639
  return {
20614
20640
  bars: this.scene.bars,
20615
- data: this.scene.nativeData.get(id),
20616
- settings: this.scene.nativeData.get(`${id}-settings`) ?? {},
20617
- pending: this.scene.nativePending.get(id) ?? [],
20641
+ data: this.scene.nativeData.get(l.channel),
20642
+ settings: this.scene.nativeData.get(`${l.def.id}-settings`) ?? {},
20643
+ pending: this.scene.nativePending.get(l.channel) ?? [],
20618
20644
  coords: this.coords,
20619
20645
  scale,
20620
20646
  bounds,
@@ -20657,12 +20683,12 @@ var NativeRenderer = class {
20657
20683
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
20658
20684
  let folded = null;
20659
20685
  for (const l of this.extLayers) {
20660
- const lp = this.layerPane(l.def.id) ?? pane;
20686
+ const lp = this.layerPane(l) ?? pane;
20661
20687
  if (lp.collapsed) {
20662
20688
  this.clearLayerCanvas(l.canvas);
20663
20689
  continue;
20664
20690
  }
20665
- const args = this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs);
20691
+ const args = this.extLayerArgs(l, lp.scale, lp.bounds, nowMs);
20666
20692
  l.instance.render(args);
20667
20693
  if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
20668
20694
  if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
@@ -20945,19 +20971,60 @@ var NativeRenderer = class {
20945
20971
  }
20946
20972
  return null;
20947
20973
  }
20948
- /** The mounted native indicator that OWNS an SDK layer the one whose type equals the
20949
- * layer id (the id doubles as the data channel, so the pairing is the SDK's own
20950
- * contract). Null for chart-type channels and while the owner is hidden (a hidden
20951
- * indicator leaves the scene; its cleared data channel paints nothing anyway). */
20952
- layerOwner(layerId) {
20974
+ /** One transparent, pointer-transparent SDK layer canvas covering the plot. */
20975
+ createLayerCanvas() {
20976
+ const canvas = document.createElement("canvas");
20977
+ Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
20978
+ return canvas;
20979
+ }
20980
+ /**
20981
+ * Mount the DEDICATED layer of a native indicator that names its own channel (an
20982
+ * instance of a multi-instance type painting through a registered layer): a fresh
20983
+ * instance of the layer's definition on its own canvas, reading that channel. Idempotent
20984
+ * — a show after hide re-mounts the model, and the layer (kept across the hide, like the
20985
+ * z key) is simply reused. Skipped while the renderer itself is unmounted (`mount()`
20986
+ * catches up from the scene).
20987
+ */
20988
+ ensureInstanceLayer(model) {
20989
+ const channel = model.native?.channel;
20990
+ if (!channel || !this.plot) return;
20991
+ if (this.extLayers.some((l) => l.owner === model.id)) return;
20992
+ const base = this.extLayers.find((l) => l.owner === null && l.def.id === model.native.type);
20993
+ if (!base) return;
20994
+ const canvas = this.createLayerCanvas();
20995
+ canvas.width = this.dataCanvas.width;
20996
+ canvas.height = this.dataCanvas.height;
20997
+ canvas.style.width = this.dataCanvas.style.width || "100%";
20998
+ canvas.style.height = this.dataCanvas.style.height || "100%";
20999
+ const entry = { def: base.def, instance: base.def.create(), canvas, channel, owner: model.id };
21000
+ this.extLayers.push(entry);
21001
+ this.plot.insertBefore(canvas, this.chromeCanvas);
21002
+ entry.instance.mount(canvas);
21003
+ }
21004
+ /** Tear down the dedicated layer of a removed indicator (a hide keeps it). */
21005
+ dropInstanceLayer(indicatorId) {
21006
+ const i = this.extLayers.findIndex((l2) => l2.owner === indicatorId);
21007
+ if (i < 0) return;
21008
+ const [l] = this.extLayers.splice(i, 1);
21009
+ l.instance.destroy?.();
21010
+ l.canvas.remove();
21011
+ }
21012
+ /** The mounted native indicator that OWNS an SDK layer entry. A dedicated entry is
21013
+ * owned by the instance it was mounted for; the base entry by the mounted indicator
21014
+ * whose type equals the layer id and that has no channel of its own (the id doubles
21015
+ * as the data channel, so the pairing is the SDK's own contract). Null for chart-type
21016
+ * channels and while the owner is hidden (a hidden indicator leaves the scene; its
21017
+ * cleared data channel paints nothing anyway). */
21018
+ layerOwner(l) {
21019
+ if (l.owner !== null) return this.scene.indicators.get(l.owner) ?? null;
20953
21020
  for (const m of this.scene.indicators.values()) {
20954
- if (m.native?.type === layerId) return m;
21021
+ if (m.native?.type === l.def.id && !m.native.channel) return m;
20955
21022
  }
20956
21023
  return null;
20957
21024
  }
20958
21025
  /** The pane an SDK layer paints on: its owner's pane, else the price pane. */
20959
- layerPane(layerId) {
20960
- const owner = this.layerOwner(layerId);
21026
+ layerPane(l) {
21027
+ const owner = this.layerOwner(l);
20961
21028
  const paneId = owner ? owner.paneId ?? PRICE_PANE_ID : PRICE_PANE_ID;
20962
21029
  return this.scene.panes.get(paneId) ?? null;
20963
21030
  }
@@ -20965,12 +21032,12 @@ var NativeRenderer = class {
20965
21032
  * owned layers by their owner's z key against the candles' (an indicator restacked
20966
21033
  * below the candles takes its layer canvas along), unowned by declared placement. */
20967
21034
  orderedLayerCanvases() {
20968
- const byId = new Map(this.extLayers.map((l) => [l.def.id, l.canvas]));
21035
+ const byId = new Map(this.extLayers.map((l) => [l.channel, l.canvas]));
20969
21036
  const { below, above } = stackLayers(
20970
21037
  this.extLayers.map((l) => {
20971
- const owner = this.layerOwner(l.def.id);
21038
+ const owner = this.layerOwner(l);
20972
21039
  return {
20973
- id: l.def.id,
21040
+ id: l.channel,
20974
21041
  placement: l.def.placement === "below-data" ? "below-data" : "above-data",
20975
21042
  ownerZ: owner ? this.scene.zOf(owner.id) : null
20976
21043
  };
@@ -20992,7 +21059,7 @@ var NativeRenderer = class {
20992
21059
  syncLayerCanvasOrder() {
20993
21060
  if (this.extLayers.length === 0 || !this.plot) return;
20994
21061
  const { below, above } = this.orderedLayerCanvases();
20995
- 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(",");
21062
+ const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).channel), "|", ...above.map((c) => this.extLayers.find((l) => l.canvas === c).channel)].join(",");
20996
21063
  if (sig === this.layerOrderSig) return;
20997
21064
  this.layerOrderSig = sig;
20998
21065
  for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
@@ -21512,6 +21579,9 @@ var IndicatorHandleImpl = class {
21512
21579
  setProps(values) {
21513
21580
  this.controller.applyProps(this.id, values);
21514
21581
  }
21582
+ updateCode(source) {
21583
+ this.controller.updateCode(this.id, source);
21584
+ }
21515
21585
  setVisible(visible) {
21516
21586
  this.controller.setVisible(this.id, visible);
21517
21587
  }
@@ -21534,6 +21604,10 @@ var IndicatorHandleImpl = class {
21534
21604
  setPropsSchema(schema) {
21535
21605
  this.propsSchema = schema;
21536
21606
  }
21607
+ /** Sync the public `source` once a code update has been prepared and taken over. */
21608
+ setSource(source) {
21609
+ this.source = source;
21610
+ }
21537
21611
  /** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
21538
21612
  setVisibleState(visible) {
21539
21613
  this.visibleState = visible;
@@ -22342,6 +22416,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22342
22416
  if (identityChanged) {
22343
22417
  this.beginLoad(false);
22344
22418
  this.setBarSeries([], { clearing: true });
22419
+ this.blankIndicatorVisuals();
22345
22420
  if (this.activeEngineStyle) {
22346
22421
  this.typeEngines.get(this.activeEngineStyle)?.suspend();
22347
22422
  this.renderer.setNativeData?.(this.activeEngineStyle, void 0);
@@ -22384,7 +22459,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22384
22459
  restartNativeIndicators() {
22385
22460
  for (const record of this.registry.all()) {
22386
22461
  if (!record.native) continue;
22387
- record.native.instance.stop();
22462
+ if (record.native.started) record.native.instance.stop();
22388
22463
  record.native.instance = record.native.descriptor.create();
22389
22464
  record.native.started = false;
22390
22465
  record.pendingStructural = true;
@@ -22592,6 +22667,23 @@ var EngineOrchestrator = class _EngineOrchestrator {
22592
22667
  this.renderer.setBars(this.bars, { preserveView: true });
22593
22668
  this.reexecuteIndicators();
22594
22669
  }
22670
+ /**
22671
+ * Blank every mounted indicator's painted output — series data, fills, backgrounds,
22672
+ * price lines, drawings, bar colors, trades — while keeping the mount (legend row,
22673
+ * pane, settings) intact, via the same idempotent-by-id remount an input edit uses.
22674
+ * Called on a market identity switch: the fresh models arrive only after the new
22675
+ * bars load, so nothing blanked here is ever restored — each consumer's re-run
22676
+ * remounts a full model over it.
22677
+ */
22678
+ blankIndicatorVisuals() {
22679
+ for (const record of this.registry.all()) {
22680
+ if (record.hidden || !record.renderHandle || !record.model) continue;
22681
+ record.model = blankedModel(record.model);
22682
+ record.renderHandle = this.renderer.mountIndicator(record.model);
22683
+ record.pendingStructural = true;
22684
+ this.setLoading(record, true);
22685
+ }
22686
+ }
22595
22687
  /** Stop + re-run every visible Pine indicator (its input bars changed wholesale). */
22596
22688
  reexecuteIndicators() {
22597
22689
  for (const record of this.registry.all()) {
@@ -22836,6 +22928,61 @@ var EngineOrchestrator = class _EngineOrchestrator {
22836
22928
  record.session.update(record.inputValues, record.propValues);
22837
22929
  this.events.emit("indicator:inputs", { id });
22838
22930
  }
22931
+ /**
22932
+ * IndicatorController: replace a SCRIPT indicator's source in place (see
22933
+ * {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
22934
+ * computing until the new source has compiled, so a broken edit costs the user
22935
+ * nothing but an `error` event. Natives have no script — warn and leave them alone.
22936
+ */
22937
+ updateCode(id, source) {
22938
+ const record = this.registry.get(id);
22939
+ const handle = this.handles.get(id);
22940
+ if (!record || !handle) return;
22941
+ if (record.native) {
22942
+ console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
22943
+ return;
22944
+ }
22945
+ if (source === record.source && record.pendingSource === void 0) return;
22946
+ record.pendingSource = source;
22947
+ void this.swapSource(id, source, handle);
22948
+ }
22949
+ async swapSource(id, source, handle) {
22950
+ const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
22951
+ let prepared;
22952
+ try {
22953
+ prepared = await engine.prepare(source, id);
22954
+ } catch (err) {
22955
+ const record2 = this.registry.get(id);
22956
+ if (record2?.pendingSource === source) {
22957
+ record2.pendingSource = void 0;
22958
+ this.fail(id, handle, err);
22959
+ }
22960
+ return;
22961
+ }
22962
+ const record = this.registry.get(id);
22963
+ if (!record || record.pendingSource !== source) return;
22964
+ record.pendingSource = void 0;
22965
+ record.source = source;
22966
+ record.engine = engine;
22967
+ record.prepared = prepared;
22968
+ handle.setSource(source);
22969
+ record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
22970
+ record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
22971
+ handle.setSchema(prepared.inputs);
22972
+ handle.setPropsSchema(prepared.props ?? []);
22973
+ record.session?.stop();
22974
+ record.session = void 0;
22975
+ record.pendingStructural = true;
22976
+ record.pendingCause = "code";
22977
+ if (record.hidden) return;
22978
+ if (record.renderHandle) {
22979
+ this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
22980
+ this.setLoading(record, true);
22981
+ } else {
22982
+ this.mountLoadingPlaceholder(id, record);
22983
+ }
22984
+ this.executeIndicator(id, handle);
22985
+ }
22839
22986
  /** IndicatorController: tear down an indicator and (if now empty) its pane. */
22840
22987
  /** Live handles of every indicator on the chart (script + native), insertion order. */
22841
22988
  listIndicators() {
@@ -22850,7 +22997,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22850
22997
  const record = this.registry.remove(id);
22851
22998
  if (record?.native?.type === "volume") this.volumeOptedOut = true;
22852
22999
  record?.session?.stop();
22853
- record?.native?.instance.stop();
23000
+ if (record?.native?.started) record.native.instance.stop();
22854
23001
  this.handles.delete(id);
22855
23002
  if (record?.renderHandle) this.renderer.removeIndicator(record.renderHandle);
22856
23003
  const paneId = record?.model?.paneId;
@@ -22876,7 +23023,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22876
23023
  if (!visible) {
22877
23024
  record.session?.stop();
22878
23025
  record.session = void 0;
22879
- record.native?.instance.suspend();
23026
+ if (record.native?.started) record.native.instance.suspend();
22880
23027
  if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
22881
23028
  else if (record.native) this.mountHiddenNativeRow(id, record);
22882
23029
  } else {
@@ -22957,7 +23104,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22957
23104
  this.paneActionUnsub?.();
22958
23105
  for (const record of this.registry.all()) {
22959
23106
  record.session?.stop();
22960
- record.native?.instance.stop();
23107
+ if (record.native?.started) record.native.instance.stop();
22961
23108
  }
22962
23109
  for (const engine of this.typeEngines.values()) engine.stop();
22963
23110
  this.typeEngines.clear();
@@ -22981,6 +23128,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22981
23128
  const engine = this.engineFor(options.language);
22982
23129
  record.engine = engine;
22983
23130
  const prepared = await engine.prepare(source, id);
23131
+ if (record.source !== source) return;
22984
23132
  record.prepared = prepared;
22985
23133
  const defaults = {};
22986
23134
  for (const input of prepared.inputs) defaults[input.key] = input.defval;
@@ -23051,7 +23199,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
23051
23199
  await this.readyPromise;
23052
23200
  const record = this.registry.get(id);
23053
23201
  if (!record?.native || record.hidden) return;
23202
+ const channel = this.nativeChannel(record);
23054
23203
  const ctx = {
23204
+ id,
23055
23205
  symbol: this.config.market.symbol ?? "TEST",
23056
23206
  timeframe: this.config.market.timeframe ?? "60",
23057
23207
  live: this.config.live,
@@ -23059,7 +23209,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
23059
23209
  bars: () => this.bars,
23060
23210
  data: this.dataControl,
23061
23211
  emit: (out) => this.applyModel(id, this.buildNativeModel(record, out)),
23062
- pushData: (data) => this.renderer.setNativeData?.(record.native.type, data),
23212
+ pushData: (data) => this.renderer.setNativeData?.(channel, data),
23063
23213
  setStatus: (status) => {
23064
23214
  if (record.renderHandle && !record.hidden) this.renderer.setIndicatorStatus?.(record.renderHandle, status);
23065
23215
  }
@@ -23070,16 +23220,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
23070
23220
  this.fail(id, handle, err);
23071
23221
  }
23072
23222
  }
23223
+ /** The renderer-layer channel a native instance's `pushData` lands on: the type itself
23224
+ * for a single-instance type (the layer id doubles as the channel), a per-instance
23225
+ * channel for a `multiInstance` type — otherwise every instance would overwrite the
23226
+ * one layer. Stamped on the model so the renderer mounts a dedicated layer for it. */
23227
+ nativeChannel(record) {
23228
+ const { type, descriptor } = record.native;
23229
+ return descriptor.multiInstance ? nativeInstanceChannel(type, record.id) : type;
23230
+ }
23073
23231
  /** Wrap a native indicator's emitted visuals into a full IndicatorModel, tagged `native`. */
23074
23232
  buildNativeModel(record, out) {
23075
23233
  const d = record.native.descriptor;
23234
+ const type = record.native.type;
23235
+ const channel = this.nativeChannel(record);
23076
23236
  return {
23077
23237
  id: record.id,
23078
23238
  title: record.title,
23079
23239
  ...d.shortTitle ? { shorttitle: d.shortTitle } : {},
23080
23240
  overlay: d.overlay,
23081
23241
  paneHint: d.paneHint,
23082
- native: { type: record.native.type },
23242
+ native: { type, ...channel !== type ? { channel } : {} },
23083
23243
  ...d.legend === false ? { legend: false } : {},
23084
23244
  ...out.paneAxis != null ? { paneAxis: out.paneAxis } : {},
23085
23245
  series: out.series ?? [],
@@ -23369,6 +23529,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
23369
23529
  applyModel(id, model) {
23370
23530
  const record = this.registry.get(id);
23371
23531
  if (!record || record.hidden) return true;
23532
+ if (this.switchingMarket) return false;
23372
23533
  if (record.loading && this.bars.length === 0 && !_EngineOrchestrator.modelHasOutput(model)) return false;
23373
23534
  const handle = this.handles.get(id);
23374
23535
  if (!record.renderHandle) {
@@ -23386,7 +23547,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
23386
23547
  if (record.loading && !record.paneLocked) {
23387
23548
  const routed = this.routePane(id, model, record.options ?? {});
23388
23549
  if (routed !== paneId) {
23389
- if (paneId !== "price") {
23550
+ if (paneId !== "price" && !this.registry.all().some((r) => r.id !== id && r.model?.paneId === paneId)) {
23390
23551
  this.renderer.removePane(paneId);
23391
23552
  this.forgetPane(paneId);
23392
23553
  }
@@ -23515,6 +23676,34 @@ function yieldToPaint() {
23515
23676
  }
23516
23677
  return new Promise((resolve) => setTimeout(resolve, 0));
23517
23678
  }
23679
+ function valuesOnSchema(schema, previous) {
23680
+ const out = {};
23681
+ const declared = /* @__PURE__ */ new Set();
23682
+ for (const s of schema) {
23683
+ out[s.key] = s.defval;
23684
+ declared.add(s.key);
23685
+ declared.add(s.title);
23686
+ }
23687
+ for (const [k, v] of Object.entries(previous)) if (declared.has(k)) out[k] = v;
23688
+ return out;
23689
+ }
23690
+ function blankedModel(model) {
23691
+ return {
23692
+ ...model,
23693
+ series: model.series.map((s) => s.kind === "candle" || s.kind === "bar" ? { ...s, bars: [] } : s.kind === "markers" ? { ...s, markers: [] } : { ...s, points: [] }),
23694
+ fills: [],
23695
+ backgrounds: [],
23696
+ priceLines: [],
23697
+ lines: [],
23698
+ boxes: [],
23699
+ labels: [],
23700
+ polylines: [],
23701
+ linefills: [],
23702
+ tables: [],
23703
+ barColors: [],
23704
+ trades: []
23705
+ };
23706
+ }
23518
23707
  function modelToValuePatch(model) {
23519
23708
  const series = [];
23520
23709
  let from = Number.POSITIVE_INFINITY;