@luxalgo/vela 0.7.0 → 0.7.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-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-IFJJPXSV.js → chunk-BFA32GOU.js} +44 -16
  4. package/dist/{chunk-CZRNTHD6.js → chunk-BKHSQ4YM.js} +129 -125
  5. package/dist/{chunk-A4G64KVF.js → chunk-PTNO3YK2.js} +311 -111
  6. package/dist/{chunk-A347YL2P.js → chunk-QBWLINWL.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 +391 -157
  11. package/dist/index.d.cts +52 -17
  12. package/dist/index.d.ts +52 -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 +391 -157
  35. package/dist/vela.global.min.js +70 -70
  36. package/dist/widget.cjs +418 -128
  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 +418 -128
  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();
@@ -20826,7 +20852,7 @@ var NativeRenderer = class {
20826
20852
  const or = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
20827
20853
  if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
20828
20854
  }
20829
- const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
20855
+ const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels));
20830
20856
  pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
20831
20857
  pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
20832
20858
  pane.axisFormat = void 0;
@@ -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);
@@ -21016,6 +21083,17 @@ var NativeRenderer = class {
21016
21083
  (m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
21017
21084
  );
21018
21085
  }
21086
+ /**
21087
+ * True when SDK-layer content on the price pane paints at BAR PRICES without
21088
+ * contributing a series: the active chart type's own layer, or an overlay layer native
21089
+ * (a series-less model whose type names a mounted layer). Hiding the candles must then
21090
+ * keep the bars in the price scale — with nothing else on the pane the scale would fall
21091
+ * to the {0,1} placeholder and every such layer would paint off-screen and vanish.
21092
+ */
21093
+ priceLayersAnchoredToBars(masterModels) {
21094
+ if (this.extLayers.some((l) => l.owner === null && l.def.id === this.scene.priceStyle)) return true;
21095
+ return masterModels.some((m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type));
21096
+ }
21019
21097
  /** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
21020
21098
  * band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
21021
21099
  * Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
@@ -21512,6 +21590,9 @@ var IndicatorHandleImpl = class {
21512
21590
  setProps(values) {
21513
21591
  this.controller.applyProps(this.id, values);
21514
21592
  }
21593
+ updateCode(source) {
21594
+ this.controller.updateCode(this.id, source);
21595
+ }
21515
21596
  setVisible(visible) {
21516
21597
  this.controller.setVisible(this.id, visible);
21517
21598
  }
@@ -21534,6 +21615,10 @@ var IndicatorHandleImpl = class {
21534
21615
  setPropsSchema(schema) {
21535
21616
  this.propsSchema = schema;
21536
21617
  }
21618
+ /** Sync the public `source` once a code update has been prepared and taken over. */
21619
+ setSource(source) {
21620
+ this.source = source;
21621
+ }
21537
21622
  /** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
21538
21623
  setVisibleState(visible) {
21539
21624
  this.visibleState = visible;
@@ -22342,6 +22427,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22342
22427
  if (identityChanged) {
22343
22428
  this.beginLoad(false);
22344
22429
  this.setBarSeries([], { clearing: true });
22430
+ this.blankIndicatorVisuals();
22345
22431
  if (this.activeEngineStyle) {
22346
22432
  this.typeEngines.get(this.activeEngineStyle)?.suspend();
22347
22433
  this.renderer.setNativeData?.(this.activeEngineStyle, void 0);
@@ -22384,7 +22470,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22384
22470
  restartNativeIndicators() {
22385
22471
  for (const record of this.registry.all()) {
22386
22472
  if (!record.native) continue;
22387
- record.native.instance.stop();
22473
+ if (record.native.started) record.native.instance.stop();
22388
22474
  record.native.instance = record.native.descriptor.create();
22389
22475
  record.native.started = false;
22390
22476
  record.pendingStructural = true;
@@ -22592,6 +22678,23 @@ var EngineOrchestrator = class _EngineOrchestrator {
22592
22678
  this.renderer.setBars(this.bars, { preserveView: true });
22593
22679
  this.reexecuteIndicators();
22594
22680
  }
22681
+ /**
22682
+ * Blank every mounted indicator's painted output — series data, fills, backgrounds,
22683
+ * price lines, drawings, bar colors, trades — while keeping the mount (legend row,
22684
+ * pane, settings) intact, via the same idempotent-by-id remount an input edit uses.
22685
+ * Called on a market identity switch: the fresh models arrive only after the new
22686
+ * bars load, so nothing blanked here is ever restored — each consumer's re-run
22687
+ * remounts a full model over it.
22688
+ */
22689
+ blankIndicatorVisuals() {
22690
+ for (const record of this.registry.all()) {
22691
+ if (record.hidden || !record.renderHandle || !record.model) continue;
22692
+ record.model = blankedModel(record.model);
22693
+ record.renderHandle = this.renderer.mountIndicator(record.model);
22694
+ record.pendingStructural = true;
22695
+ this.setLoading(record, true);
22696
+ }
22697
+ }
22595
22698
  /** Stop + re-run every visible Pine indicator (its input bars changed wholesale). */
22596
22699
  reexecuteIndicators() {
22597
22700
  for (const record of this.registry.all()) {
@@ -22836,6 +22939,61 @@ var EngineOrchestrator = class _EngineOrchestrator {
22836
22939
  record.session.update(record.inputValues, record.propValues);
22837
22940
  this.events.emit("indicator:inputs", { id });
22838
22941
  }
22942
+ /**
22943
+ * IndicatorController: replace a SCRIPT indicator's source in place (see
22944
+ * {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
22945
+ * computing until the new source has compiled, so a broken edit costs the user
22946
+ * nothing but an `error` event. Natives have no script — warn and leave them alone.
22947
+ */
22948
+ updateCode(id, source) {
22949
+ const record = this.registry.get(id);
22950
+ const handle = this.handles.get(id);
22951
+ if (!record || !handle) return;
22952
+ if (record.native) {
22953
+ console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
22954
+ return;
22955
+ }
22956
+ if (source === record.source && record.pendingSource === void 0) return;
22957
+ record.pendingSource = source;
22958
+ void this.swapSource(id, source, handle);
22959
+ }
22960
+ async swapSource(id, source, handle) {
22961
+ const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
22962
+ let prepared;
22963
+ try {
22964
+ prepared = await engine.prepare(source, id);
22965
+ } catch (err) {
22966
+ const record2 = this.registry.get(id);
22967
+ if (record2?.pendingSource === source) {
22968
+ record2.pendingSource = void 0;
22969
+ this.fail(id, handle, err);
22970
+ }
22971
+ return;
22972
+ }
22973
+ const record = this.registry.get(id);
22974
+ if (!record || record.pendingSource !== source) return;
22975
+ record.pendingSource = void 0;
22976
+ record.source = source;
22977
+ record.engine = engine;
22978
+ record.prepared = prepared;
22979
+ handle.setSource(source);
22980
+ record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
22981
+ record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
22982
+ handle.setSchema(prepared.inputs);
22983
+ handle.setPropsSchema(prepared.props ?? []);
22984
+ record.session?.stop();
22985
+ record.session = void 0;
22986
+ record.pendingStructural = true;
22987
+ record.pendingCause = "code";
22988
+ if (record.hidden) return;
22989
+ if (record.renderHandle) {
22990
+ this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
22991
+ this.setLoading(record, true);
22992
+ } else {
22993
+ this.mountLoadingPlaceholder(id, record);
22994
+ }
22995
+ this.executeIndicator(id, handle);
22996
+ }
22839
22997
  /** IndicatorController: tear down an indicator and (if now empty) its pane. */
22840
22998
  /** Live handles of every indicator on the chart (script + native), insertion order. */
22841
22999
  listIndicators() {
@@ -22850,7 +23008,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22850
23008
  const record = this.registry.remove(id);
22851
23009
  if (record?.native?.type === "volume") this.volumeOptedOut = true;
22852
23010
  record?.session?.stop();
22853
- record?.native?.instance.stop();
23011
+ if (record?.native?.started) record.native.instance.stop();
22854
23012
  this.handles.delete(id);
22855
23013
  if (record?.renderHandle) this.renderer.removeIndicator(record.renderHandle);
22856
23014
  const paneId = record?.model?.paneId;
@@ -22876,7 +23034,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22876
23034
  if (!visible) {
22877
23035
  record.session?.stop();
22878
23036
  record.session = void 0;
22879
- record.native?.instance.suspend();
23037
+ if (record.native?.started) record.native.instance.suspend();
22880
23038
  if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
22881
23039
  else if (record.native) this.mountHiddenNativeRow(id, record);
22882
23040
  } else {
@@ -22957,7 +23115,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22957
23115
  this.paneActionUnsub?.();
22958
23116
  for (const record of this.registry.all()) {
22959
23117
  record.session?.stop();
22960
- record.native?.instance.stop();
23118
+ if (record.native?.started) record.native.instance.stop();
22961
23119
  }
22962
23120
  for (const engine of this.typeEngines.values()) engine.stop();
22963
23121
  this.typeEngines.clear();
@@ -22981,6 +23139,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22981
23139
  const engine = this.engineFor(options.language);
22982
23140
  record.engine = engine;
22983
23141
  const prepared = await engine.prepare(source, id);
23142
+ if (record.source !== source) return;
22984
23143
  record.prepared = prepared;
22985
23144
  const defaults = {};
22986
23145
  for (const input of prepared.inputs) defaults[input.key] = input.defval;
@@ -23051,7 +23210,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
23051
23210
  await this.readyPromise;
23052
23211
  const record = this.registry.get(id);
23053
23212
  if (!record?.native || record.hidden) return;
23213
+ const channel = this.nativeChannel(record);
23054
23214
  const ctx = {
23215
+ id,
23055
23216
  symbol: this.config.market.symbol ?? "TEST",
23056
23217
  timeframe: this.config.market.timeframe ?? "60",
23057
23218
  live: this.config.live,
@@ -23059,7 +23220,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
23059
23220
  bars: () => this.bars,
23060
23221
  data: this.dataControl,
23061
23222
  emit: (out) => this.applyModel(id, this.buildNativeModel(record, out)),
23062
- pushData: (data) => this.renderer.setNativeData?.(record.native.type, data),
23223
+ pushData: (data) => this.renderer.setNativeData?.(channel, data),
23063
23224
  setStatus: (status) => {
23064
23225
  if (record.renderHandle && !record.hidden) this.renderer.setIndicatorStatus?.(record.renderHandle, status);
23065
23226
  }
@@ -23070,16 +23231,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
23070
23231
  this.fail(id, handle, err);
23071
23232
  }
23072
23233
  }
23234
+ /** The renderer-layer channel a native instance's `pushData` lands on: the type itself
23235
+ * for a single-instance type (the layer id doubles as the channel), a per-instance
23236
+ * channel for a `multiInstance` type — otherwise every instance would overwrite the
23237
+ * one layer. Stamped on the model so the renderer mounts a dedicated layer for it. */
23238
+ nativeChannel(record) {
23239
+ const { type, descriptor } = record.native;
23240
+ return descriptor.multiInstance ? nativeInstanceChannel(type, record.id) : type;
23241
+ }
23073
23242
  /** Wrap a native indicator's emitted visuals into a full IndicatorModel, tagged `native`. */
23074
23243
  buildNativeModel(record, out) {
23075
23244
  const d = record.native.descriptor;
23245
+ const type = record.native.type;
23246
+ const channel = this.nativeChannel(record);
23076
23247
  return {
23077
23248
  id: record.id,
23078
23249
  title: record.title,
23079
23250
  ...d.shortTitle ? { shorttitle: d.shortTitle } : {},
23080
23251
  overlay: d.overlay,
23081
23252
  paneHint: d.paneHint,
23082
- native: { type: record.native.type },
23253
+ native: { type, ...channel !== type ? { channel } : {} },
23083
23254
  ...d.legend === false ? { legend: false } : {},
23084
23255
  ...out.paneAxis != null ? { paneAxis: out.paneAxis } : {},
23085
23256
  series: out.series ?? [],
@@ -23369,6 +23540,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
23369
23540
  applyModel(id, model) {
23370
23541
  const record = this.registry.get(id);
23371
23542
  if (!record || record.hidden) return true;
23543
+ if (this.switchingMarket) return false;
23372
23544
  if (record.loading && this.bars.length === 0 && !_EngineOrchestrator.modelHasOutput(model)) return false;
23373
23545
  const handle = this.handles.get(id);
23374
23546
  if (!record.renderHandle) {
@@ -23386,7 +23558,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
23386
23558
  if (record.loading && !record.paneLocked) {
23387
23559
  const routed = this.routePane(id, model, record.options ?? {});
23388
23560
  if (routed !== paneId) {
23389
- if (paneId !== "price") {
23561
+ if (paneId !== "price" && !this.registry.all().some((r) => r.id !== id && r.model?.paneId === paneId)) {
23390
23562
  this.renderer.removePane(paneId);
23391
23563
  this.forgetPane(paneId);
23392
23564
  }
@@ -23515,6 +23687,34 @@ function yieldToPaint() {
23515
23687
  }
23516
23688
  return new Promise((resolve) => setTimeout(resolve, 0));
23517
23689
  }
23690
+ function valuesOnSchema(schema, previous) {
23691
+ const out = {};
23692
+ const declared = /* @__PURE__ */ new Set();
23693
+ for (const s of schema) {
23694
+ out[s.key] = s.defval;
23695
+ declared.add(s.key);
23696
+ declared.add(s.title);
23697
+ }
23698
+ for (const [k, v] of Object.entries(previous)) if (declared.has(k)) out[k] = v;
23699
+ return out;
23700
+ }
23701
+ function blankedModel(model) {
23702
+ return {
23703
+ ...model,
23704
+ series: model.series.map((s) => s.kind === "candle" || s.kind === "bar" ? { ...s, bars: [] } : s.kind === "markers" ? { ...s, markers: [] } : { ...s, points: [] }),
23705
+ fills: [],
23706
+ backgrounds: [],
23707
+ priceLines: [],
23708
+ lines: [],
23709
+ boxes: [],
23710
+ labels: [],
23711
+ polylines: [],
23712
+ linefills: [],
23713
+ tables: [],
23714
+ barColors: [],
23715
+ trades: []
23716
+ };
23717
+ }
23518
23718
  function modelToValuePatch(model) {
23519
23719
  const series = [];
23520
23720
  let from = Number.POSITIVE_INFINITY;