@luxalgo/vela 0.8.4 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/{DataProvider-Byo8pEv0.d.ts → DataProvider-BUdlkx9I.d.ts} +1 -1
  2. package/dist/{DataProvider-Bw58CjYy.d.cts → DataProvider-DdqMxKdl.d.cts} +1 -1
  3. package/dist/{chunk-MOJ4T26N.js → chunk-BN4DNS3O.js} +27 -7
  4. package/dist/{chunk-UNUVPQFW.js → chunk-QYIO37K7.js} +482 -94
  5. package/dist/{contributions-DOm40g5-.d.cts → contributions-D8xuYc9U.d.cts} +37 -5
  6. package/dist/{contributions-BY3zSYiZ.d.ts → contributions-P9fBBuY1.d.ts} +37 -5
  7. package/dist/index.cjs +482 -94
  8. package/dist/index.d.cts +23 -7
  9. package/dist/index.d.ts +23 -7
  10. package/dist/index.js +1 -1
  11. package/dist/{options-CSXbjMM5.d.cts → options-BI5Tp32b.d.cts} +36 -1
  12. package/dist/{options-CSXbjMM5.d.ts → options-BI5Tp32b.d.ts} +36 -1
  13. package/dist/{plugin-CiQVyETg.d.cts → plugin-B8F1nor8.d.cts} +14 -8
  14. package/dist/{plugin-BDxUhGO3.d.ts → plugin-D5Z-Q6TX.d.ts} +14 -8
  15. package/dist/plugin.d.cts +4 -4
  16. package/dist/plugin.d.ts +4 -4
  17. package/dist/providers/binance.d.cts +2 -2
  18. package/dist/providers/binance.d.ts +2 -2
  19. package/dist/providers/coinbase.d.cts +2 -2
  20. package/dist/providers/coinbase.d.ts +2 -2
  21. package/dist/providers/hyperliquid.d.cts +2 -2
  22. package/dist/providers/hyperliquid.d.ts +2 -2
  23. package/dist/{statusline-model-DUBX6Fib.d.cts → statusline-model-CTwtDIca.d.cts} +3 -3
  24. package/dist/{statusline-model-C9tYXuux.d.ts → statusline-model-D9kWC9X5.d.ts} +3 -3
  25. package/dist/ui.d.cts +1 -1
  26. package/dist/ui.d.ts +1 -1
  27. package/dist/vela.global.js +482 -94
  28. package/dist/vela.global.min.js +53 -53
  29. package/dist/widget.cjs +508 -100
  30. package/dist/widget.d.cts +6 -6
  31. package/dist/widget.d.ts +6 -6
  32. package/dist/widget.js +3 -3
  33. package/dist/workspace.cjs +508 -100
  34. package/dist/workspace.d.cts +5 -5
  35. package/dist/workspace.d.ts +5 -5
  36. package/dist/workspace.js +2 -2
  37. package/package.json +1 -1
@@ -4168,6 +4168,8 @@ var LEGEND_TITLE_STATUS_GAP_PX = 8;
4168
4168
  var LEGEND_ACTION_GAP_PX = 6;
4169
4169
  var LEGEND_CTL_CSS = `cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border:none;line-height:0;padding:0;width:${LEGEND_CTL_PX}px;height:${LEGEND_CTL_PX}px;border-radius:3px;box-sizing:border-box;flex:none;`;
4170
4170
  var LEGEND_ROW_MIN_H = LEGEND_CTL_PX + LEGEND_ROW_PAD_Y * 2;
4171
+ var STACK_RAISE = "stack:raise";
4172
+ var STACK_LOWER = "stack:lower";
4171
4173
  var EYE_SVG = iconAt("eye", LEGEND_ICON_PX2);
4172
4174
  var EYE_OFF_SVG = iconAt("eye-off", LEGEND_ICON_PX2);
4173
4175
  var GEAR_SVG = iconAt("gear", LEGEND_ICON_PX2);
@@ -4197,6 +4199,10 @@ var InputsUI = class {
4197
4199
  this.mobileLayout = false;
4198
4200
  /** Pane move/merge hook — when set, rows get a "Move to" menu + become drag-to-pane sources. */
4199
4201
  this.moveApi = null;
4202
+ /** Overlay restacking hook — when set, a row's menus offer to step it forward or back. */
4203
+ this.stackApi = null;
4204
+ /** What {@link orderRows} last ordered the rows by (skips unchanged frames). */
4205
+ this.rowOrderKey = "";
4200
4206
  /** Host-contributed legend actions, resolved PER ROW at render time (see setLegendActions). */
4201
4207
  this.legendActions = null;
4202
4208
  /** Host-contributed callout bubbles, resolved PER ROW at render time (see setLegendCallouts). */
@@ -4335,6 +4341,10 @@ var InputsUI = class {
4335
4341
  setMoveApi(api) {
4336
4342
  this.moveApi = api;
4337
4343
  }
4344
+ /** Enable the context menu's "Bring in front of …" entry for overlay indicators. Null disables it. */
4345
+ setStackApi(api) {
4346
+ this.stackApi = api;
4347
+ }
4338
4348
  /**
4339
4349
  * Wire the host-contributed row actions. Re-calling replaces the provider and
4340
4350
  * re-projects the rows already on screen (a late `registerLegendAction` appears after
@@ -4480,16 +4490,20 @@ var InputsUI = class {
4480
4490
  }
4481
4491
  this.moveTargets = items.map((it) => it.target);
4482
4492
  this.moveMenuId = id;
4483
- const descriptors = items.map((it, i) => ({ id: String(i), label: it.label }));
4493
+ const stack = this.stackItems(id);
4494
+ const moves = items.map((it, i) => ({ id: String(i), label: it.label }));
4495
+ if (moves[0] && stack.length > 0) moves[0].separatorBefore = true;
4496
+ const descriptors = [...stack, ...moves];
4484
4497
  if (!this.moveMenu) {
4485
4498
  this.moveMenu = new Menu({
4486
4499
  host: this.container,
4487
4500
  items: descriptors,
4488
4501
  placement: "bottom-start",
4489
4502
  onSelect: (itemId) => {
4490
- const target = this.moveTargets[Number(itemId)];
4491
4503
  const rowId = this.moveMenuId;
4492
- if (target && rowId) api.move(rowId, target);
4504
+ if (!rowId || this.runStackItem(itemId, rowId)) return;
4505
+ const target = this.moveTargets[Number(itemId)];
4506
+ if (target) api.move(rowId, target);
4493
4507
  }
4494
4508
  });
4495
4509
  } else {
@@ -4502,18 +4516,24 @@ var InputsUI = class {
4502
4516
  this.moveMenu?.close();
4503
4517
  }
4504
4518
  // ── legend row context menu (right-click) ───────────────────────────────
4505
- /** Open the row's action menu at the pointer. One entry today: the values toggle. */
4519
+ /** Open the row's action menu at the pointer: an overlay indicator's restacking steps
4520
+ * first (where a layer is there to pass), then the values toggle. */
4506
4521
  openRowMenu(id, x, y) {
4507
4522
  this.closeRowMenu();
4508
4523
  this.closeMoveMenu();
4509
4524
  const row2 = this.rows.get(id);
4510
4525
  if (!row2) return;
4511
4526
  this.rowMenuId = id;
4512
- const items = [{
4513
- id: "values",
4514
- label: "Indicator values",
4515
- checked: this.rowValuesShown(row2)
4516
- }];
4527
+ const stack = this.stackItems(id);
4528
+ const items = [
4529
+ ...stack,
4530
+ {
4531
+ id: "values",
4532
+ label: "Indicator values",
4533
+ checked: this.rowValuesShown(row2),
4534
+ separatorBefore: stack.length > 0
4535
+ }
4536
+ ];
4517
4537
  if (!this.rowMenu) {
4518
4538
  this.rowMenu = new Menu({
4519
4539
  host: this.container,
@@ -4522,8 +4542,10 @@ var InputsUI = class {
4522
4542
  // Right-click action menu — checked state reads as a leading ✓ (see the kit Menu).
4523
4543
  checkmarks: true,
4524
4544
  onSelect: (itemId) => {
4525
- if (itemId !== "values" || !this.rowMenuId) return;
4526
- const target = this.rows.get(this.rowMenuId);
4545
+ const rowId = this.rowMenuId;
4546
+ if (!rowId || this.runStackItem(itemId, rowId)) return;
4547
+ if (itemId !== "values") return;
4548
+ const target = this.rows.get(rowId);
4527
4549
  if (!target) return;
4528
4550
  target.showValues = !this.rowValuesShown(target);
4529
4551
  this.renderRowValues(target);
@@ -4537,6 +4559,55 @@ var InputsUI = class {
4537
4559
  closeRowMenu() {
4538
4560
  this.rowMenu?.close();
4539
4561
  }
4562
+ /** A row's one-step restacking entries (forward, then back), for whichever neighbor exists. */
4563
+ stackItems(id) {
4564
+ const api = this.stackApi;
4565
+ if (!api) return [];
4566
+ const items = [];
4567
+ const forward = this.stackLabel("Bring in front of", api.above(id), "the indicator in front");
4568
+ if (forward) items.push({ id: STACK_RAISE, label: forward });
4569
+ const back = this.stackLabel("Send behind", api.below(id), "the indicator behind");
4570
+ if (back) items.push({ id: STACK_LOWER, label: back });
4571
+ return items;
4572
+ }
4573
+ stackLabel(verb, neighbor, fallback) {
4574
+ if (neighbor === null) return null;
4575
+ if (neighbor === "price") return `${verb} price`;
4576
+ return `${verb} ${this.rows.get(neighbor.indicator)?.title ?? fallback}`;
4577
+ }
4578
+ /** Run a restacking entry; false when `itemId` is not one. */
4579
+ runStackItem(itemId, rowId) {
4580
+ if (itemId === STACK_RAISE) this.stackApi?.raise(rowId);
4581
+ else if (itemId === STACK_LOWER) this.stackApi?.lower(rowId);
4582
+ else return false;
4583
+ return true;
4584
+ }
4585
+ /**
4586
+ * List each pane's rows front-most first, the way the indicators stack (the object
4587
+ * tree's order). `backToFront` is the paint order; rows it doesn't name keep their
4588
+ * place after the others. Cheap to call every frame: no-op while nothing changed.
4589
+ */
4590
+ orderRows(backToFront) {
4591
+ const key = `${backToFront.join(",")}|${[...this.rows.values()].map((r) => `${r.id}@${r.paneId}`).join(",")}`;
4592
+ if (key === this.rowOrderKey) return;
4593
+ this.rowOrderKey = key;
4594
+ const rank = new Map(backToFront.map((id, i) => [id, i]));
4595
+ for (const lg of this.legends.values()) {
4596
+ const els = [...this.rows.values()].filter((r) => r.el.parentElement === lg).map((r) => r.el);
4597
+ const dom = [...lg.children];
4598
+ const ofEl = new Map([...this.rows.values()].map((r) => [r.el, r.id]));
4599
+ const at = (el) => rank.get(ofEl.get(el)) ?? -1;
4600
+ els.sort((a, b) => {
4601
+ const ra = at(a);
4602
+ const rb = at(b);
4603
+ if (ra >= 0 && rb >= 0) return rb - ra;
4604
+ if (ra >= 0 || rb >= 0) return ra >= 0 ? -1 : 1;
4605
+ return dom.indexOf(a) - dom.indexOf(b);
4606
+ });
4607
+ const anchor = this.foldToggle?.parentElement === lg ? this.foldToggle : null;
4608
+ for (const el of els) lg.insertBefore(el, anchor);
4609
+ }
4610
+ }
4540
4611
  /** Move a legend row to another pane (indicator merged/moved), tidying an emptied container. */
4541
4612
  setPane(id, paneId) {
4542
4613
  const row2 = this.rows.get(id);
@@ -6227,6 +6298,7 @@ var WebGL2Backend = class {
6227
6298
  const i0 = Math.max(0, Math.floor(vr.from));
6228
6299
  const i1 = Math.min(n - 1, Math.ceil(vr.to));
6229
6300
  if (i1 < i0) return;
6301
+ const s1 = Math.max(i1, Math.ceil(vr.to));
6230
6302
  gl.enable(gl.BLEND);
6231
6303
  gl.enable(gl.SCISSOR_TEST);
6232
6304
  const barColorMap = mergeBarColors(scene.indicators);
@@ -6285,8 +6357,8 @@ var WebGL2Backend = class {
6285
6357
  b.alpha = this.modelAlpha;
6286
6358
  const off = scene.offsetOf(m.id);
6287
6359
  const mp = effPane(m);
6288
- for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, i1, off);
6289
- for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
6360
+ for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, s1, off);
6361
+ for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, s1, theme, off);
6290
6362
  }
6291
6363
  if (drawCandles && !candleDrawn) {
6292
6364
  drawSlicesUpTo(scene.candleZ);
@@ -6297,11 +6369,11 @@ var WebGL2Backend = class {
6297
6369
  if (isPrice) {
6298
6370
  for (const m of scene.indicators.values()) {
6299
6371
  const off = scene.offsetOf(m.id);
6300
- for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, off);
6372
+ for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, s1, off);
6301
6373
  }
6302
6374
  for (const m of scene.indicators.values()) {
6303
6375
  const off = scene.offsetOf(m.id);
6304
- for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
6376
+ for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, s1, theme, off);
6305
6377
  }
6306
6378
  }
6307
6379
  drawSlicesUpTo(Infinity);
@@ -6323,7 +6395,7 @@ var WebGL2Backend = class {
6323
6395
  if (this.glow > 0 && !this.screenProgram && !this.ensureScreenPipeline()) this.glow = 0;
6324
6396
  if (this.glow > 0 && this.screenProgram) {
6325
6397
  this.glowBatch.alpha = this.modelAlpha;
6326
- this.renderGlow(gl, scene, coords, i0, i1);
6398
+ this.renderGlow(gl, scene, coords, i0, s1);
6327
6399
  }
6328
6400
  }
6329
6401
  /**
@@ -6880,7 +6952,7 @@ var WebGL2Backend = class {
6880
6952
  const tickW = Math.max(1, Math.round(spacing * 0.35));
6881
6953
  for (let i = i0; i <= i1; i += 1) {
6882
6954
  const bar = s.bars[i - off];
6883
- if (!bar) continue;
6955
+ if (!bar || !Number.isFinite(bar.open + bar.high + bar.low + bar.close)) continue;
6884
6956
  const bc = s.barColors?.[i - off];
6885
6957
  const body = parseColor(bc?.color ?? (bar.close >= bar.open ? up : down));
6886
6958
  const x = Math.round(coords.logicalToX(i));
@@ -7533,6 +7605,8 @@ var InputController = class {
7533
7605
  const tapRelease = this.dragging && !this.moved && (!wasTouch || Math.hypot(x - this.startX, y - this.startY) <= TOUCH_TAP_SLOP);
7534
7606
  if (this.dragging && this.region === "drawing") {
7535
7607
  this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
7608
+ } else if (this.dragging && this.region === "separator") {
7609
+ this.deps.endPaneResize?.();
7536
7610
  } else if (tapRelease && this.region === "data") {
7537
7611
  this.deps.onClick(x, y);
7538
7612
  } else if (this.dragging && this.region === "data" && wasTouch) {
@@ -7554,6 +7628,7 @@ var InputController = class {
7554
7628
  this.cancelLongPress();
7555
7629
  if (!this.dragging) return;
7556
7630
  if (this.region === "drawing" && !Number.isNaN(this.cursorX)) this.deps.drawingsPointerUp?.(this.cursorX, this.cursorY, this.snapMode(e));
7631
+ if (this.region === "separator") this.deps.endPaneResize?.();
7557
7632
  if (this.region === "crosshair" || e.pointerType === "touch") this.deps.onPointerMove(null, null);
7558
7633
  this.endGesture(e);
7559
7634
  };
@@ -8266,9 +8341,12 @@ var SceneGraph = class {
8266
8341
  setIndicatorZ(id, z) {
8267
8342
  this.seriesZ.set(id, z);
8268
8343
  }
8269
- /** Snapshot of the current ordering for a UI/read API: `{ id, z }` sorted by z. */
8344
+ /** Snapshot of the current ordering for a UI/read API: `{ id, z }` back to front, in the
8345
+ * order the indicators paint — on a shared key, mount order (see `orderedIndicatorsForPane`). */
8270
8346
  indicatorZOrder() {
8271
- return [...this.seriesZ.entries()].map(([id, z]) => ({ id, z })).sort((a, b) => a.z - b.z);
8347
+ const mounted = new Map([...this.indicators.keys()].map((id, i) => [id, i]));
8348
+ const at = (id) => mounted.get(id) ?? Number.MAX_SAFE_INTEGER;
8349
+ return [...this.seriesZ.entries()].map(([id, z]) => ({ id, z })).sort((a, b) => a.z - b.z || at(a.id) - at(b.id));
8272
8350
  }
8273
8351
  /** The pane's series z keys (each indicator, plus the candles on the price pane), sorted
8274
8352
  * ascending and de-duplicated — the boundaries a user drawing's z is slotted against. */
@@ -8296,16 +8374,19 @@ var SceneGraph = class {
8296
8374
  for (const z of this.seriesZ.values()) if (z < min) min = z;
8297
8375
  return min;
8298
8376
  }
8377
+ /** Create the pane, or update an existing one in place. Without `heightWeight` an existing
8378
+ * pane keeps its weight (a user resize survives other indicators mounting onto it) and a
8379
+ * new one gets the default: 3 for the price pane, 1 for a study pane. */
8299
8380
  ensurePane(id, kind, order, heightWeight) {
8300
8381
  this.orderedCache = null;
8301
8382
  const existing = this.panes.get(id);
8302
8383
  if (existing) {
8303
8384
  existing.order = order;
8304
- existing.heightWeight = heightWeight;
8385
+ if (heightWeight !== void 0) existing.heightWeight = heightWeight;
8305
8386
  existing.kind = kind;
8306
8387
  return existing;
8307
8388
  }
8308
- const pane = { id, kind, order, heightWeight, bounds: { top: 0, height: 0 }, scale: { min: 0, max: 1 }, scaleTarget: { min: 0, max: 1 }, initialized: false, manualScale: null, collapsed: false, percentBaseline: 0 };
8389
+ const pane = { id, kind, order, heightWeight: heightWeight ?? (kind === "price" ? 3 : 1), bounds: { top: 0, height: 0 }, scale: { min: 0, max: 1 }, scaleTarget: { min: 0, max: 1 }, initialized: false, manualScale: null, collapsed: false, percentBaseline: 0 };
8309
8390
  this.panes.set(id, pane);
8310
8391
  return pane;
8311
8392
  }
@@ -8362,6 +8443,7 @@ var Canvas2dBackend = class {
8362
8443
  const i0 = Math.max(0, Math.floor(vr.from));
8363
8444
  const i1 = Math.min(n - 1, Math.ceil(vr.to));
8364
8445
  if (i1 < i0) return;
8446
+ const s1 = Math.max(i1, Math.ceil(vr.to));
8365
8447
  const barColorMap = mergeBarColors2(scene.indicators);
8366
8448
  const panes = scene.orderedPanes();
8367
8449
  for (const pane of panes) {
@@ -8404,8 +8486,8 @@ var Canvas2dBackend = class {
8404
8486
  ctx.globalAlpha = this.modelAlpha;
8405
8487
  const off = scene.offsetOf(m.id);
8406
8488
  const mp = effPane(m);
8407
- for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, i1, off);
8408
- for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
8489
+ for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, s1, off);
8490
+ for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, s1, theme, off);
8409
8491
  }
8410
8492
  if (drawCandles && !candleDrawn) {
8411
8493
  drawSlicesUpTo(scene.candleZ);
@@ -8416,11 +8498,11 @@ var Canvas2dBackend = class {
8416
8498
  ctx.globalAlpha = this.modelAlpha;
8417
8499
  for (const m of scene.indicators.values()) {
8418
8500
  const off = scene.offsetOf(m.id);
8419
- for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, off);
8501
+ for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, s1, off);
8420
8502
  }
8421
8503
  for (const m of scene.indicators.values()) {
8422
8504
  const off = scene.offsetOf(m.id);
8423
- for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
8505
+ for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, s1, theme, off);
8424
8506
  }
8425
8507
  }
8426
8508
  drawSlicesUpTo(Infinity);
@@ -8872,7 +8954,7 @@ var Canvas2dBackend = class {
8872
8954
  const tickW = Math.max(1, Math.round(spacing * 0.35));
8873
8955
  for (let i = i0; i <= i1; i += 1) {
8874
8956
  const b = s.bars[i - off];
8875
- if (!b) continue;
8957
+ if (!b || !Number.isFinite(b.open + b.high + b.low + b.close)) continue;
8876
8958
  const bc = s.barColors?.[i - off];
8877
8959
  const body = bc?.color ?? (b.close >= b.open ? up : down);
8878
8960
  const x = Math.round(coords.logicalToX(i)) + 0.5;
@@ -18994,6 +19076,58 @@ function stackLayers(entries, candleZ) {
18994
19076
  return { below, above };
18995
19077
  }
18996
19078
 
19079
+ // src/renderers/native/core/overlayStack.ts
19080
+ function priceStack(indicators, candleZ) {
19081
+ const out = [...indicators];
19082
+ const at = out.findIndex((e) => e.z >= candleZ);
19083
+ out.splice(at < 0 ? out.length : at, 0, { id: null, z: candleZ });
19084
+ return out;
19085
+ }
19086
+ function untie(stack) {
19087
+ const out = [];
19088
+ for (let i = 0; i < stack.length; ) {
19089
+ let j = i + 1;
19090
+ while (j < stack.length && stack[j].z === stack[i].z) j += 1;
19091
+ const run = j - i;
19092
+ if (run > 1) {
19093
+ const base = stack[i].z;
19094
+ const step = ((stack[j]?.z ?? base + 1) - base) / run;
19095
+ for (let k = 1; k < run; k += 1) {
19096
+ const id = stack[i + k].id;
19097
+ if (id !== null) out.push({ id, z: base + k * step });
19098
+ }
19099
+ }
19100
+ i = j;
19101
+ }
19102
+ return out;
19103
+ }
19104
+ function layerAbove(stack, id) {
19105
+ const i = stack.findIndex((e) => e.id === id);
19106
+ if (i < 0 || i === stack.length - 1) return null;
19107
+ const above = stack[i + 1];
19108
+ return above.id === null ? "price" : { indicator: above.id };
19109
+ }
19110
+ function layerBelow(stack, id) {
19111
+ const i = stack.findIndex((e) => e.id === id);
19112
+ if (i <= 0) return null;
19113
+ const below = stack[i - 1];
19114
+ return below.id === null ? "price" : { indicator: below.id };
19115
+ }
19116
+ function raisedZ(stack, id) {
19117
+ const i = stack.findIndex((e) => e.id === id);
19118
+ if (i < 0 || i === stack.length - 1) return null;
19119
+ const above = stack[i + 1];
19120
+ const next = stack[i + 2];
19121
+ return next ? (above.z + next.z) / 2 : above.z + 1;
19122
+ }
19123
+ function loweredZ(stack, id) {
19124
+ const i = stack.findIndex((e) => e.id === id);
19125
+ if (i <= 0) return null;
19126
+ const below = stack[i - 1];
19127
+ const prev2 = stack[i - 2];
19128
+ return prev2 ? (prev2.z + below.z) / 2 : below.z - 1;
19129
+ }
19130
+
18997
19131
  // src/renderers/native/chrome/vela-logos.ts
18998
19132
  var VELA_MARK_SVG = '<svg viewBox="0 -142 161.66 148" aria-hidden="true"><g fill="currentColor"><path d="M0 -140L32.91 -140L77.52 -62.73L61.06 -34.24Z"/><path d="M161.66 -140L128.75 -140L84.14 -62.73L100.59 -34.24Z"/><path d="M80.83 -52.04L95.85 -26.02L80.83 0L65.81 -26.02Z"/></g></svg>';
18999
19133
  var VELA_LETTERS_SVG = '<svg viewBox="161.66 -142 310.57 148" aria-hidden="true"><g fill="currentColor"><path d="M226.3 1.17Q200.81 1.17 186.12 -12.99Q171.42 -27.15 171.42 -51.17Q171.42 -75.2 185.87 -89.6Q200.33 -104 224.25 -104Q247.4 -104 261.7 -90.58Q276.01 -77.15 276.3 -54.59Q276.3 -49.9 275.42 -45.02H200.81V-43.55Q201.2 -32.62 207.75 -26.42Q214.29 -20.21 225.03 -20.21Q233.72 -20.21 239.34 -23.93Q244.95 -27.64 246.81 -34.77H274.64Q272.1 -19.14 259.16 -8.98Q246.22 1.17 226.3 1.17ZM201.5 -63.38H247.88Q246.42 -72.75 240.27 -77.88Q234.12 -83.01 224.45 -83.01Q215.17 -83.01 208.92 -77.73Q202.67 -72.46 201.5 -63.38ZM287.54 0V-140.04H315.76V0ZM429.34 -23.83H432.56V0H418.11Q407.76 0 403.36 -4.44Q398.97 -8.89 399.16 -16.8Q387.83 1.17 365.57 1.17Q348.48 1.17 337.88 -6.74Q327.29 -14.65 327.29 -29Q327.29 -45.41 339.45 -54.3Q351.6 -63.18 374.55 -63.18H394.96V-68.16Q394.96 -75.2 390.08 -79.3Q385.2 -83.4 376.51 -83.4Q368.3 -83.4 363.03 -79.98Q357.76 -76.56 356.88 -70.8H329.73Q331.19 -85.84 344.08 -94.92Q356.97 -104 377.29 -104Q399.06 -104 410.88 -94.43Q422.7 -84.86 422.7 -66.6V-30.18Q422.7 -26.37 424.26 -25.1Q425.82 -23.83 429.34 -23.83ZM394.96 -42.38V-44.43H374.36Q365.47 -44.43 360.59 -40.92Q355.7 -37.4 355.7 -30.96Q355.7 -25.59 359.81 -22.41Q363.91 -19.24 370.94 -19.24Q381.97 -19.24 388.37 -25.54Q394.77 -31.84 394.96 -42.38Z"/><path fill-opacity="0.55" d="M437.44 -124.28H441.96V-113.41H445.34V-124.28H449.87V-127.4H437.44ZM451.43 -113.41H454.7V-123.29L458.97 -113.41H461.62L465.99 -123.29V-113.41H469.21V-127.4H464.53L460.42 -117.68L456.16 -127.4H451.43Z"/></g></svg>';
@@ -19644,6 +19778,8 @@ var NativeRenderer = class {
19644
19778
  // pane just below it
19645
19779
  this.resizeSplitStart = null;
19646
19780
  // the two panes' shared span when the drag began
19781
+ this.resizeStartAboveWeight = 0;
19782
+ // the upper pane's weight when the drag began
19647
19783
  /** The user drawings take part in pointer input (the runtime `drawingsInteractive` feature). */
19648
19784
  this.drawingsInteractive = true;
19649
19785
  this.hoverSeparatorY = null;
@@ -20286,6 +20422,7 @@ var NativeRenderer = class {
20286
20422
  };
20287
20423
  this.scene.candleZ = next.stacking.candles;
20288
20424
  for (const [id, z] of Object.entries(next.stacking.series)) this.scene.setIndicatorZ(id, z);
20425
+ this.syncLegendOrder();
20289
20426
  s.bars = { upColor: next.bars.upColor, downColor: next.bars.downColor };
20290
20427
  s.line = { color: next.line.color, width: next.line.width };
20291
20428
  s.area = { lineColor: next.area.lineColor, width: next.area.width, topColor: next.area.topColor, bottomColor: next.area.bottomColor };
@@ -20331,6 +20468,7 @@ var NativeRenderer = class {
20331
20468
  if (typeof v.z === "number") this.scene.setIndicatorZ(v.id, v.z);
20332
20469
  else if (v.to === "front") this.scene.bringIndicatorToFront(v.id);
20333
20470
  else if (v.to === "back") this.scene.sendIndicatorToBack(v.id);
20471
+ this.syncLegendOrder();
20334
20472
  }
20335
20473
  /** Toggle the in-chart settings gear + dialog (the `settings` feature). The gear
20336
20474
  * sits above the time axis, clear of the right price-axis strip. */
@@ -20718,7 +20856,7 @@ var NativeRenderer = class {
20718
20856
  this.chrome.mount(this.chromeCanvas);
20719
20857
  this.crosshairLayer.mount(this.cursorCanvas);
20720
20858
  this.coords.setViewport(defaultViewport());
20721
- this.scene.ensurePane(PRICE_PANE_ID, "price", 0, 3);
20859
+ this.scene.ensurePane(PRICE_PANE_ID, "price", 0);
20722
20860
  this.scheduler = new Scheduler((level) => this.renderFrame(level));
20723
20861
  this.animator = new Animator((dt) => this.animTick(dt));
20724
20862
  this.targetBarSpacing = this.coords.getViewport().barSpacing;
@@ -20731,7 +20869,7 @@ var NativeRenderer = class {
20731
20869
  onClick: (x, y) => {
20732
20870
  if (this.handleMarkClick(x, y)) return;
20733
20871
  this.userDrawings?.deselect();
20734
- this.handleClick(x);
20872
+ this.handleClick(x, y);
20735
20873
  },
20736
20874
  onAxisLongPress: (axis, x, y) => {
20737
20875
  for (const cb of this.axisLongPressCbs) cb({ axis, x, y });
@@ -20745,6 +20883,7 @@ var NativeRenderer = class {
20745
20883
  paneSeparatorAt: (y) => this.paneSeparatorAt(y),
20746
20884
  beginPaneResize: (y) => this.beginPaneResize(y),
20747
20885
  paneResizeBy: (dy) => this.paneResizeBy(dy),
20886
+ endPaneResize: () => this.endPaneResize(),
20748
20887
  resetPaneSize: (y) => this.resetPaneSize(y),
20749
20888
  resetView: () => this.resetView(),
20750
20889
  // User drawings claim a gesture before pan when armed / over a drawing.
@@ -20870,6 +21009,12 @@ var NativeRenderer = class {
20870
21009
  for (const cb of this.moveIndicatorCbs) cb(id, target);
20871
21010
  }
20872
21011
  });
21012
+ this.inputsUI.setStackApi({
21013
+ above: (id) => layerAbove(this.overlayStack(), id),
21014
+ below: (id) => layerBelow(this.overlayStack(), id),
21015
+ raise: (id) => this.restackOverlay(id, raisedZ),
21016
+ lower: (id) => this.restackOverlay(id, loweredZ)
21017
+ });
20873
21018
  this.paneControls = new PaneControls(this.plot, theme, {
20874
21019
  panes: () => this.paneControlViews(),
20875
21020
  rightAxis: () => this.rightAxisW,
@@ -21187,7 +21332,7 @@ var NativeRenderer = class {
21187
21332
  }
21188
21333
  // ── panes ──
21189
21334
  ensurePane(pane) {
21190
- this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight ?? (pane.kind === "price" ? 3 : 1));
21335
+ this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight);
21191
21336
  this.layoutPanes();
21192
21337
  this.paneControls?.refresh();
21193
21338
  this.scheduler.invalidate(4 /* Full */);
@@ -21209,6 +21354,7 @@ var NativeRenderer = class {
21209
21354
  model.ownScale = opts?.ownScale === true;
21210
21355
  if (!model.ownScale) this.scene.dropIndicatorScale(handle.id);
21211
21356
  this.inputsUI.setPane(handle.id, paneId);
21357
+ this.syncLegendOrder();
21212
21358
  this.refreshAnchorOffset(model);
21213
21359
  this.refreshAxisWidth();
21214
21360
  this.layoutPanes();
@@ -21236,6 +21382,15 @@ var NativeRenderer = class {
21236
21382
  this.paneControls?.refresh();
21237
21383
  this.scheduler.invalidate(4 /* Full */);
21238
21384
  }
21385
+ setPaneHeightWeight(paneId, weight) {
21386
+ const pane = this.scene.panes.get(paneId);
21387
+ if (!pane || !(weight > 0) || pane.heightWeight === weight) return;
21388
+ pane.heightWeight = weight;
21389
+ this.afterPaneLayoutChange();
21390
+ }
21391
+ paneHeightWeight(paneId) {
21392
+ return this.scene.panes.get(paneId)?.heightWeight ?? null;
21393
+ }
21239
21394
  onPaneAction(cb) {
21240
21395
  this.paneActionCbs.add(cb);
21241
21396
  return () => this.paneActionCbs.delete(cb);
@@ -21330,6 +21485,7 @@ var NativeRenderer = class {
21330
21485
  this.vpvrHidden = false;
21331
21486
  }
21332
21487
  if (model.ownScale) this.refreshAxisWidth();
21488
+ this.syncLegendOrder();
21333
21489
  this.paneControls?.refresh();
21334
21490
  this.scheduler.invalidate(4 /* Full */);
21335
21491
  return { id: model.id };
@@ -21737,7 +21893,7 @@ var NativeRenderer = class {
21737
21893
  this.setMarkHover(null);
21738
21894
  this.scheduler.invalidate(1 /* Cursor */);
21739
21895
  this.hoverLogical = null;
21740
- const empty = { time: null, price: null, paneKind: null, values: /* @__PURE__ */ new Map(), ohlc: null };
21896
+ const empty = { time: null, price: null, paneKind: null, paneId: null, values: /* @__PURE__ */ new Map(), ohlc: null };
21741
21897
  for (const cb of this.crosshairCbs) cb(empty);
21742
21898
  return;
21743
21899
  }
@@ -21768,14 +21924,23 @@ var NativeRenderer = class {
21768
21924
  const bar = onBar ? this.bars[logical] : void 0;
21769
21925
  const ohlc = bar ? { time: bar.time, open: bar.open, high: bar.high, low: bar.low, close: bar.close, volume: bar.volume } : null;
21770
21926
  this.hoverLogical = onBar ? logical : null;
21771
- const event = { time, price, paneKind: inData && pane ? pane.kind : null, values, ohlc };
21927
+ const onPane = inData && pane ? pane : null;
21928
+ const event = { time, price, paneKind: onPane?.kind ?? null, paneId: onPane?.id ?? null, values, ohlc };
21772
21929
  for (const cb of this.crosshairCbs) cb(event);
21773
21930
  }
21774
- handleClick(x) {
21931
+ handleClick(x, y) {
21775
21932
  if (this.coords.barCount === 0) return;
21776
21933
  const logical = Math.round(this.coords.xToLogical(x));
21777
21934
  const onBar = logical >= 0 && logical < this.coords.barCount;
21778
- for (const cb of this.clickCbs) cb({ time: onBar ? this.coords.logicalToTime(logical) : null, price: null });
21935
+ const inData = x >= 0 && y >= 0 && x <= this.coords.width && y <= this.coords.height;
21936
+ const pane = inData ? this.paneNodeAtY(y) : null;
21937
+ const event = {
21938
+ time: onBar ? this.coords.logicalToTime(logical) : null,
21939
+ price: pane ? this.coords.yToPrice(y, pane.scale, pane.bounds) : null,
21940
+ paneKind: pane?.kind ?? null,
21941
+ paneId: pane?.id ?? null
21942
+ };
21943
+ for (const cb of this.clickCbs) cb(event);
21779
21944
  }
21780
21945
  /**
21781
21946
  * Fan out (or collapse, with null) a multi-group mark stack; repaints the chrome tier when
@@ -22020,6 +22185,7 @@ var NativeRenderer = class {
22020
22185
  const below = panes[i];
22021
22186
  this.resizeAbove = above;
22022
22187
  this.resizeBelow = below;
22188
+ this.resizeStartAboveWeight = above.heightWeight;
22023
22189
  this.resizeSplitStart = {
22024
22190
  combinedTop: above.bounds.top,
22025
22191
  combinedHeight: above.bounds.height + below.bounds.height,
@@ -22040,6 +22206,16 @@ var NativeRenderer = class {
22040
22206
  this.layoutPanes();
22041
22207
  this.scheduler.invalidate(4 /* Full */);
22042
22208
  }
22209
+ /** Release the separator: report the resize once, at the end of the drag, if it changed anything. */
22210
+ endPaneResize() {
22211
+ const above = this.resizeAbove;
22212
+ const below = this.resizeBelow;
22213
+ const startAbove = this.resizeStartAboveWeight;
22214
+ this.resizeAbove = null;
22215
+ this.resizeBelow = null;
22216
+ this.resizeSplitStart = null;
22217
+ if (above && below && above.heightWeight !== startAbove) this.emitPaneAction({ type: "resize", paneIds: [above.id, below.id] });
22218
+ }
22043
22219
  /** Double-click a separator → split the two adjacent panes evenly (each gets half of
22044
22220
  * their combined weight). A simple, predictable reset that leaves siblings untouched. */
22045
22221
  resetPaneSize(y) {
@@ -22053,6 +22229,7 @@ var NativeRenderer = class {
22053
22229
  below.heightWeight = half;
22054
22230
  this.layoutPanes();
22055
22231
  this.scheduler.invalidate(4 /* Full */);
22232
+ this.emitPaneAction({ type: "resize", paneIds: [above.id, below.id] });
22056
22233
  }
22057
22234
  // ── keyboard navigation / accessibility (item 11) ──
22058
22235
  /** Enable/disable keyboard control: focusability + ARIA + key listeners + live region. */
@@ -22307,6 +22484,7 @@ var NativeRenderer = class {
22307
22484
  /** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
22308
22485
  paintData() {
22309
22486
  this.syncLayerCanvasOrder();
22487
+ this.syncLegendOrder();
22310
22488
  this.stampScaleInvert();
22311
22489
  this.backend.modelAlpha = this.modelAlpha;
22312
22490
  this.backend.candleBodyAlpha = this.candleBodyAlpha;
@@ -22488,6 +22666,7 @@ var NativeRenderer = class {
22488
22666
  const i0 = Math.max(0, Math.floor(vr.from));
22489
22667
  const i1 = Math.min(n - 1, Math.ceil(vr.to));
22490
22668
  if (i1 < i0) return;
22669
+ const s1 = Math.max(i1, Math.ceil(vr.to));
22491
22670
  this.scene.percentBaseline = this.bars[i0]?.close ?? 0;
22492
22671
  const panes = this.scene.orderedPanes();
22493
22672
  const pricePane = panes.find((p) => p.kind === "price") ?? null;
@@ -22505,11 +22684,11 @@ var NativeRenderer = class {
22505
22684
  const masterModels = models.filter((m) => m.ownScale !== true);
22506
22685
  let dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
22507
22686
  if (pane === pricePane) {
22508
- const or = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
22687
+ const or = overlaySeriesRange(this.scene.indicators.values(), i0, s1, (id) => this.scene.offsetOf(id));
22509
22688
  if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
22510
22689
  }
22511
22690
  const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
22512
- pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
22691
+ pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, s1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
22513
22692
  pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
22514
22693
  pane.axisFormat = void 0;
22515
22694
  pane.axisBands = void 0;
@@ -22549,7 +22728,7 @@ var NativeRenderer = class {
22549
22728
  continue;
22550
22729
  }
22551
22730
  const mdr = this.chrome.paneDrawingsRange([model], this.scene, false, vr);
22552
- sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id), margins);
22731
+ sl.scaleTarget = computePaneScale([model], this.bars, false, i0, s1, mdr, false, (id) => this.scene.offsetOf(id), margins);
22553
22732
  if (!animating || !sl.initialized) {
22554
22733
  sl.scale = { ...sl.scaleTarget };
22555
22734
  sl.initialized = true;
@@ -22620,6 +22799,30 @@ var NativeRenderer = class {
22620
22799
  const master = models.find((m) => m.ownScale !== true) ?? models[0];
22621
22800
  return master?.title || null;
22622
22801
  }
22802
+ /**
22803
+ * The price pane's layers back to front, as the legend restacks them: every indicator
22804
+ * with a legend row, in paint order, plus the candles. The same layers and order the
22805
+ * object tree lists (see `indicatorZOrder`).
22806
+ */
22807
+ overlayStack() {
22808
+ const painted = this.scene.orderedIndicatorsForPane(PRICE_PANE_ID).filter((m) => m.legend !== false).map((m) => ({ id: m.id, z: this.scene.zOf(m.id) }));
22809
+ return priceStack(painted, this.scene.candleZ);
22810
+ }
22811
+ /** List the legend rows the way the indicators stack, front-most first (no-op when unchanged). */
22812
+ syncLegendOrder() {
22813
+ this.inputsUI?.orderRows(this.scene.indicatorZOrder().map((e) => e.id));
22814
+ }
22815
+ /** Apply a legend restacking step: shared keys are spread apart first (the order stays),
22816
+ * so the step lands exactly one layer over. No-op when there is nothing to step over. */
22817
+ restackOverlay(id, step) {
22818
+ for (const fix of untie(this.overlayStack())) this.scene.setIndicatorZ(fix.id, fix.z);
22819
+ const z = step(this.overlayStack(), id);
22820
+ if (z === null) return;
22821
+ this.scene.setIndicatorZ(id, z);
22822
+ this.syncLegendOrder();
22823
+ this.scheduler.invalidate(4 /* Full */);
22824
+ for (const cb of this.configChangedCbs) cb();
22825
+ }
22623
22826
  /** The pane a bespoke-layer native indicator (volume/vpvr) currently lives in (null = not mounted). */
22624
22827
  nativeLayerPane(type) {
22625
22828
  for (const m of this.scene.indicators.values()) {
@@ -23074,17 +23277,35 @@ function leadingPointsBefore(model, headTime) {
23074
23277
  for (const s of model.series) {
23075
23278
  const times = s.kind === "candle" || s.kind === "bar" ? s.bars : isLineLikeSeries(s) ? s.points : null;
23076
23279
  if (!times || times.length === 0) continue;
23077
- let lo = 0;
23078
- let hi = times.length;
23079
- while (lo < hi) {
23080
- const mid = lo + hi >> 1;
23081
- if (times[mid].time < headTime) lo = mid + 1;
23082
- else hi = mid;
23083
- }
23084
- return lo;
23280
+ const count = slotsBefore(times, headTime);
23281
+ if (count !== null) return count;
23085
23282
  }
23086
23283
  return null;
23087
23284
  }
23285
+ function slotsBefore(times, headTime) {
23286
+ let lo = 0;
23287
+ let hi = times.length;
23288
+ while (lo < hi) {
23289
+ const mid = lo + hi >> 1;
23290
+ const t = times[mid]?.time;
23291
+ if (t === void 0) return slotsBeforeScan(times, headTime);
23292
+ if (t < headTime) lo = mid + 1;
23293
+ else hi = mid;
23294
+ }
23295
+ return lo;
23296
+ }
23297
+ function slotsBeforeScan(times, headTime) {
23298
+ let lastBefore = -1;
23299
+ let any = false;
23300
+ for (let i = 0; i < times.length; i += 1) {
23301
+ const t = times[i]?.time;
23302
+ if (t === void 0) continue;
23303
+ any = true;
23304
+ if (t >= headTime) return t === headTime ? i : lastBefore + 1;
23305
+ lastBefore = i;
23306
+ }
23307
+ return any ? times.length : null;
23308
+ }
23088
23309
  function applyPatch(model, patch) {
23089
23310
  if (patch.kind === "value") {
23090
23311
  for (const delta of patch.series) {
@@ -23697,6 +23918,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
23697
23918
  this.barTransform = null;
23698
23919
  // ── chart-type data engines (SDK): one lazily-created engine per style id ──
23699
23920
  this.typeEngines = /* @__PURE__ */ new Map();
23921
+ /** Engines suspended by the chart — their host pushes are dropped until they resume. */
23922
+ this.suspendedEngines = /* @__PURE__ */ new WeakSet();
23700
23923
  /** Last-seen chart-type settings per type id (dialog edits + persisted restores) —
23701
23924
  * replayed into a data engine whenever one is (re)created. */
23702
23925
  this.typeSettings = /* @__PURE__ */ new Map();
@@ -23971,12 +24194,14 @@ var EngineOrchestrator = class _EngineOrchestrator {
23971
24194
  this.mergeHistoryIntoReplay(bars);
23972
24195
  return;
23973
24196
  }
23974
- this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
24197
+ const repaint = painted;
24198
+ this.setBarSeries(bars, repaint ? { preserveView: true } : void 0);
23975
24199
  if (!painted && bars.length > 0) {
23976
24200
  painted = true;
23977
24201
  if (opts.firstLoad) this.activateBarLayers();
23978
24202
  if (!final) this.historyState = "backfill";
23979
24203
  }
24204
+ if (repaint && !final) this.notifySessionsBars("backfill");
23980
24205
  };
23981
24206
  const abort = new AbortController();
23982
24207
  this.progressiveAbort = abort;
@@ -24179,10 +24404,15 @@ var EngineOrchestrator = class _EngineOrchestrator {
24179
24404
  this.setBarSeries([], { clearing: true });
24180
24405
  this.blankIndicatorVisuals();
24181
24406
  if (this.activeEngineStyle) {
24182
- this.typeEngines.get(this.activeEngineStyle)?.suspend();
24407
+ this.suspendTypeEngine(this.typeEngines.get(this.activeEngineStyle));
24183
24408
  this.renderer.setNativeData?.(this.activeEngineStyle, void 0);
24184
24409
  this.renderer.setNativeData?.(`${this.activeEngineStyle}-pending`, []);
24185
24410
  }
24411
+ for (const record of this.registry.all()) {
24412
+ if (!record.native?.started) continue;
24413
+ this.stopNativeInstance(record.native);
24414
+ this.renderer.setNativeData?.(this.nativeChannel(record), void 0);
24415
+ }
24186
24416
  }
24187
24417
  if (depthOnly) {
24188
24418
  this.extendDepth(gen, m.bars ?? 500);
@@ -24209,6 +24439,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
24209
24439
  this.restartChartTypeEngine();
24210
24440
  this.reexecuteIndicators();
24211
24441
  }
24442
+ if (depthOnly) this.releaseHeldNatives();
24212
24443
  if (!replayBack) this.startLive();
24213
24444
  if (identityChanged) {
24214
24445
  this.events.emit("market:changed", { symbol: m.symbol ?? "TEST", timeframe: m.timeframe ?? "60", prev: prev2 });
@@ -24225,9 +24456,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
24225
24456
  restartNativeIndicators() {
24226
24457
  for (const record of this.registry.all()) {
24227
24458
  if (!record.native) continue;
24228
- if (record.native.started) record.native.instance.stop();
24459
+ this.stopNativeInstance(record.native);
24229
24460
  record.native.instance = record.native.descriptor.create();
24230
- record.native.started = false;
24231
24461
  record.pendingStructural = true;
24232
24462
  if (record.hidden) {
24233
24463
  record.native.stale = true;
@@ -24244,6 +24474,21 @@ var EngineOrchestrator = class _EngineOrchestrator {
24244
24474
  this.typeEngines.clear();
24245
24475
  if (this.activeEngineStyle) void this.startChartTypeEngine(this.activeEngineStyle);
24246
24476
  }
24477
+ /** Suspend a chart-type engine. Its host drops every push until the engine resumes:
24478
+ * work it left in flight lands in its own state, never on the scene. */
24479
+ suspendTypeEngine(engine) {
24480
+ if (!engine) return;
24481
+ engine.suspend();
24482
+ this.suspendedEngines.add(engine);
24483
+ }
24484
+ /** Stop a running native instance, then revoke its context: the teardown inside `stop()`
24485
+ * still reaches its layer, but work it left in flight (a fetch resolving later) does not. */
24486
+ stopNativeInstance(native) {
24487
+ if (native.started) native.instance.stop();
24488
+ native.started = false;
24489
+ native.revokeContext?.();
24490
+ native.revokeContext = void 0;
24491
+ }
24247
24492
  /**
24248
24493
  * The FIRST candles just painted (the quick preview, or the only load): activate the
24249
24494
  * layers that need bars but NOT full history. The volume layer reads the chart's bars
@@ -24776,7 +25021,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
24776
25021
  const def = chartType(id);
24777
25022
  const want = def?.dataEngine ? id : null;
24778
25023
  if (want === this.activeEngineStyle) return;
24779
- if (this.activeEngineStyle) this.typeEngines.get(this.activeEngineStyle)?.suspend();
25024
+ if (this.activeEngineStyle) this.suspendTypeEngine(this.typeEngines.get(this.activeEngineStyle));
24780
25025
  this.activeEngineStyle = want;
24781
25026
  if (want) void this.startChartTypeEngine(want);
24782
25027
  }
@@ -24787,6 +25032,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
24787
25032
  if (this.activeEngineStyle !== id) return;
24788
25033
  const existing = this.typeEngines.get(id);
24789
25034
  if (existing) {
25035
+ this.suspendedEngines.delete(existing);
24790
25036
  existing.resume();
24791
25037
  return;
24792
25038
  }
@@ -24796,6 +25042,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
24796
25042
  this.typeEngines.set(id, engine);
24797
25043
  const stored = this.typeSettings.get(id);
24798
25044
  if (stored) engine.onSettings?.(stored);
25045
+ const reachesScene = () => this.typeEngines.get(id) === engine && !this.suspendedEngines.has(engine);
24799
25046
  engine.start({
24800
25047
  symbol: this.config.market.symbol ?? "TEST",
24801
25048
  timeframe: this.config.market.timeframe ?? "60",
@@ -24803,8 +25050,12 @@ var EngineOrchestrator = class _EngineOrchestrator {
24803
25050
  session: this.config.market.session,
24804
25051
  bars: () => this.bars,
24805
25052
  data: this.dataControl,
24806
- pushData: (data) => this.renderer.setNativeData?.(id, data),
24807
- pushPending: (ranges) => this.renderer.setNativeData?.(`${id}-pending`, ranges)
25053
+ pushData: (data) => {
25054
+ if (reachesScene()) this.renderer.setNativeData?.(id, data);
25055
+ },
25056
+ pushPending: (ranges) => {
25057
+ if (reachesScene()) this.renderer.setNativeData?.(`${id}-pending`, ranges);
25058
+ }
24808
25059
  });
24809
25060
  }
24810
25061
  /**
@@ -24812,8 +25063,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
24812
25063
  * a pre-load style change is picked up by init's `setBarSeries`. With bars loaded, the
24813
25064
  * view is rebuilt in place (viewport preserved) and every Pine indicator re-executes:
24814
25065
  * its input data changed wholesale, which a streaming session's incremental context
24815
- * cannot absorb. Native indicators need nothing — their layers read the renderer's
24816
- * bars per frame.
25066
+ * cannot absorb. Natives computed from `ctx.bars()` re-run over the new candles (a
25067
+ * synthetic price style is their source too); layer natives that read the renderer's
25068
+ * bars per frame ignore the poke.
24817
25069
  */
24818
25070
  syncBarTransform(style) {
24819
25071
  const transform = barTransformFor(style);
@@ -24823,6 +25075,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
24823
25075
  this.bars = transform ? transform.full(this.rawBars) : this.rawBars;
24824
25076
  this.renderer.setBars(this.bars, { preserveView: true });
24825
25077
  this.reexecuteIndicators();
25078
+ if (this.switchingMarket) return;
25079
+ for (const record of this.registry.all()) {
25080
+ if (record.native?.started && record.renderHandle && !record.hidden) this.pokeNative(record);
25081
+ }
24826
25082
  }
24827
25083
  /**
24828
25084
  * Blank every mounted indicator's painted output — series data, fills, backgrounds,
@@ -24923,10 +25179,39 @@ var EngineOrchestrator = class _EngineOrchestrator {
24923
25179
  if (record.session) {
24924
25180
  record.pendingCause = cause;
24925
25181
  record.session.notifyBars(reason);
24926
- } else if (record.native) record.native.instance.onBars();
25182
+ } else if (record.native) this.pokeNative(record);
24927
25183
  }
24928
25184
  if (this.activeEngineStyle) this.typeEngines.get(this.activeEngineStyle)?.onBars?.();
24929
25185
  }
25186
+ /**
25187
+ * Tell a native its bars changed. A `waitsForHistory` native ignores every change while
25188
+ * the history is still loading — a backfill chunk, a deeper snapshot, a tick arriving
25189
+ * meanwhile — and its output stays on its bars through the anchor its emit carried.
25190
+ * Once the history is complete, a held one starts and a running one re-runs.
25191
+ */
25192
+ pokeNative(record) {
25193
+ const native = record.native;
25194
+ if (!native.descriptor.waitsForHistory) {
25195
+ native.instance.onBars();
25196
+ return;
25197
+ }
25198
+ if (this.historyState === "backfill") return;
25199
+ if (native.heldForHistory) {
25200
+ native.heldForHistory = false;
25201
+ const handle = this.handles.get(record.id);
25202
+ if (handle) void this.startNativeIndicator(record.id, handle);
25203
+ return;
25204
+ }
25205
+ if (native.started) native.instance.onBars();
25206
+ }
25207
+ /** Start the natives still held for a history that has completed without poking them
25208
+ * (a depth-only switch finishes its trim while the switch gates every poke). */
25209
+ releaseHeldNatives() {
25210
+ if (this.historyState === "backfill") return;
25211
+ for (const record of this.registry.all()) {
25212
+ if (record.native?.heldForHistory && !record.hidden) this.pokeNative(record);
25213
+ }
25214
+ }
24930
25215
  /** Gap healing needs a ranged feed and a provider-backed series (offline `data` has no source). */
24931
25216
  canHeal() {
24932
25217
  return !!this.feed.loadRange && !this.config.market.data?.length;
@@ -25218,7 +25503,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
25218
25503
  if (record?.native?.type === "volume") this.volumeOptedOut = true;
25219
25504
  if (record) this.settleCode(record, { ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator was removed.`) });
25220
25505
  record?.session?.stop();
25221
- if (record?.native?.started) record.native.instance.stop();
25506
+ if (record?.native) this.stopNativeInstance(record.native);
25222
25507
  this.handles.delete(id);
25223
25508
  if (record?.renderHandle) this.renderer.removeIndicator(record.renderHandle);
25224
25509
  const paneId = record?.model?.paneId;
@@ -25339,7 +25624,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
25339
25624
  for (const record of this.registry.all()) {
25340
25625
  this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
25341
25626
  record.session?.stop();
25342
- if (record.native?.started) record.native.instance.stop();
25627
+ if (record.native) this.stopNativeInstance(record.native);
25343
25628
  }
25344
25629
  for (const engine of this.typeEngines.values()) engine.stop();
25345
25630
  this.typeEngines.clear();
@@ -25466,7 +25751,19 @@ var EngineOrchestrator = class _EngineOrchestrator {
25466
25751
  await this.readyPromise;
25467
25752
  const record = this.registry.get(id);
25468
25753
  if (!record?.native || record.hidden) return;
25754
+ const native = record.native;
25755
+ if (native.started) return;
25756
+ if (native.descriptor.waitsForHistory && this.historyState === "backfill") {
25757
+ native.heldForHistory = true;
25758
+ this.mountNativePlaceholder(id, record);
25759
+ return;
25760
+ }
25761
+ native.heldForHistory = false;
25469
25762
  const channel = this.nativeChannel(record);
25763
+ let alive = true;
25764
+ native.revokeContext = () => {
25765
+ alive = false;
25766
+ };
25470
25767
  const ctx = {
25471
25768
  id,
25472
25769
  symbol: this.config.market.symbol ?? "TEST",
@@ -25475,14 +25772,18 @@ var EngineOrchestrator = class _EngineOrchestrator {
25475
25772
  session: this.config.market.session,
25476
25773
  bars: () => this.bars,
25477
25774
  data: this.dataControl,
25478
- emit: (out) => this.applyModel(id, this.buildNativeModel(record, out)),
25479
- pushData: (data) => this.renderer.setNativeData?.(channel, data),
25775
+ emit: (out) => {
25776
+ if (alive) this.applyModel(id, this.buildNativeModel(record, out, this.bars[0]?.time));
25777
+ },
25778
+ pushData: (data) => {
25779
+ if (alive) this.renderer.setNativeData?.(channel, data);
25780
+ },
25480
25781
  setStatus: (status) => {
25481
- if (record.renderHandle && !record.hidden) this.renderer.setIndicatorStatus?.(record.renderHandle, status);
25782
+ if (alive && record.renderHandle && !record.hidden) this.renderer.setIndicatorStatus?.(record.renderHandle, status);
25482
25783
  }
25483
25784
  };
25484
- record.native.instance.start(ctx, record.inputValues);
25485
- record.native.started = true;
25785
+ native.instance.start(ctx, record.inputValues);
25786
+ native.started = true;
25486
25787
  } catch (err) {
25487
25788
  this.fail(id, handle, err);
25488
25789
  }
@@ -25495,8 +25796,14 @@ var EngineOrchestrator = class _EngineOrchestrator {
25495
25796
  const { type, descriptor } = record.native;
25496
25797
  return descriptor.multiInstance ? nativeInstanceChannel(type, record.id) : type;
25497
25798
  }
25498
- /** Wrap a native indicator's emitted visuals into a full IndicatorModel, tagged `native`. */
25499
- buildNativeModel(record, out) {
25799
+ /**
25800
+ * Wrap a native indicator's emitted visuals into a full IndicatorModel, tagged `native`.
25801
+ * `anchorTime` is the first bar the output is index-aligned to — the chart's first bar
25802
+ * when it emitted. Without it the renderer pins the output's first slot to whatever the
25803
+ * first bar is NOW, so history landing in front of a computed output (or the oldest
25804
+ * bars trimmed under it) would slide the whole plot by that many bars.
25805
+ */
25806
+ buildNativeModel(record, out, anchorTime) {
25500
25807
  const d = record.native.descriptor;
25501
25808
  const type = record.native.type;
25502
25809
  const channel = this.nativeChannel(record);
@@ -25507,6 +25814,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
25507
25814
  overlay: d.overlay,
25508
25815
  paneHint: d.paneHint,
25509
25816
  native: { type, ...channel !== type ? { channel } : {} },
25817
+ ...anchorTime != null ? { anchorTime } : {},
25510
25818
  ...d.legend === false ? { legend: false } : {},
25511
25819
  ...out.paneAxis != null ? { paneAxis: out.paneAxis } : {},
25512
25820
  series: out.series ?? [],
@@ -25561,8 +25869,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
25561
25869
  const paneId = this.routePane(id, model, record.options ?? {});
25562
25870
  this.placeModel(model, id, paneId);
25563
25871
  record.model = model;
25564
- this.ensurePaneFor(paneId);
25872
+ const paneCreated = this.ensurePaneFor(paneId);
25565
25873
  record.renderHandle = this.renderer.mountIndicator(model);
25874
+ if (paneCreated) this.events.emit("pane:changed", void 0);
25566
25875
  record.pendingStructural = true;
25567
25876
  if (record.hidden) {
25568
25877
  this.renderer.setIndicatorVisible?.(record.renderHandle, false);
@@ -25586,24 +25895,49 @@ var EngineOrchestrator = class _EngineOrchestrator {
25586
25895
  const paneId = this.routePane(id, model, record.options ?? {});
25587
25896
  this.placeModel(model, id, paneId);
25588
25897
  record.model = model;
25589
- this.ensurePaneFor(paneId);
25898
+ const paneCreated = this.ensurePaneFor(paneId);
25590
25899
  record.renderHandle = this.renderer.mountIndicator(model);
25900
+ if (paneCreated) this.events.emit("pane:changed", void 0);
25591
25901
  record.pendingStructural = true;
25592
25902
  this.renderer.setIndicatorVisible?.(record.renderHandle, false);
25593
25903
  this.announce(record, this.handles.get(id));
25594
25904
  }
25905
+ /**
25906
+ * The legend row of a native HELD for the history (see `waitsForHistory`): an empty
25907
+ * model with the title and inputs schema, spinning, not announced — exactly a script
25908
+ * indicator's loading placeholder, so `ready`/`indicator:added` and `inspect()` still
25909
+ * mean "it produced output". A record that already has a row (restarted over a new
25910
+ * market, or shown again) keeps it and only spins. The first emit remounts over it.
25911
+ */
25912
+ mountNativePlaceholder(id, record) {
25913
+ record.pendingStructural = true;
25914
+ if (!record.renderHandle) {
25915
+ const model = this.buildNativeModel(record, {});
25916
+ const paneId = this.routePane(id, model, record.options ?? {});
25917
+ this.placeModel(model, id, paneId);
25918
+ record.model = model;
25919
+ const paneCreated = this.ensurePaneFor(paneId);
25920
+ record.renderHandle = this.renderer.mountIndicator(model);
25921
+ if (paneCreated) this.events.emit("pane:changed", void 0);
25922
+ }
25923
+ this.setLoading(record, true);
25924
+ }
25595
25925
  /** Flip the record's loading state and reflect it in the legend row (spinner on/off). */
25596
25926
  setLoading(record, loading) {
25597
25927
  record.loading = loading;
25598
25928
  if (record.renderHandle && !record.hidden) this.renderer.setIndicatorStatus?.(record.renderHandle, loading ? "loading" : "idle");
25599
25929
  }
25930
+ /** Make sure the renderer has the pane; true when it is new to the layout (the caller
25931
+ * emits `pane:changed` once the indicator's model sits on it, so `list()` shows it). */
25600
25932
  ensurePaneFor(paneId) {
25601
- if (!this.paneOrder.includes(paneId)) this.paneOrder.push(paneId);
25933
+ const created = !this.paneOrder.includes(paneId);
25934
+ if (created) this.paneOrder.push(paneId);
25602
25935
  this.renderer.ensurePane({
25603
25936
  id: paneId,
25604
25937
  kind: paneId === "price" ? "price" : "study",
25605
25938
  order: this.paneOrder.indexOf(paneId)
25606
25939
  });
25940
+ return created;
25607
25941
  }
25608
25942
  /** True when any (still-registered) indicator sits on the given pane. */
25609
25943
  paneStillUsed(paneId) {
@@ -25639,12 +25973,14 @@ var EngineOrchestrator = class _EngineOrchestrator {
25639
25973
  const panes = [];
25640
25974
  for (const paneId of this.paneOrder) {
25641
25975
  if (paneId !== "price" && !byPane.has(paneId)) continue;
25976
+ const heightWeight = this.renderer.paneHeightWeight?.(paneId) ?? null;
25642
25977
  panes.push({
25643
25978
  id: paneId,
25644
25979
  kind: paneId === "price" ? "price" : "study",
25645
25980
  order: panes.length,
25646
25981
  collapsed: this.collapsedPanes.has(paneId),
25647
25982
  maximized: this.maximizedPaneId === paneId,
25983
+ ...heightWeight !== null ? { heightWeight } : {},
25648
25984
  indicators: byPane.get(paneId) ?? []
25649
25985
  });
25650
25986
  }
@@ -25676,6 +26012,22 @@ var EngineOrchestrator = class _EngineOrchestrator {
25676
26012
  this.renderer.setPaneMaximized?.(paneId);
25677
26013
  this.events.emit("pane:changed", void 0);
25678
26014
  }
26015
+ setPaneHeightWeight(paneId, weight) {
26016
+ if (!this.renderer.setPaneHeightWeight) {
26017
+ console.warn("[vela] the active renderer does not support pane sizing \u2014 chart.panes.setHeightWeight ignored.");
26018
+ return;
26019
+ }
26020
+ if (typeof weight !== "number" || !Number.isFinite(weight) || weight <= 0) {
26021
+ console.warn(`[vela] chart.panes.setHeightWeight: weight must be a positive number (got ${String(weight)}).`);
26022
+ return;
26023
+ }
26024
+ if (!this.existingPaneIds().includes(paneId)) {
26025
+ console.warn(`[vela] chart.panes.setHeightWeight: no pane "${paneId}".`);
26026
+ return;
26027
+ }
26028
+ this.renderer.setPaneHeightWeight(paneId, weight);
26029
+ this.events.emit("pane:changed", void 0);
26030
+ }
25679
26031
  /** IndicatorController/PaneController: move (or merge) an indicator to another pane. */
25680
26032
  moveIndicator(id, target) {
25681
26033
  if (!this.paneManagementSupported() || !this.renderer.setIndicatorPane) {
@@ -25773,6 +26125,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
25773
26125
  this.maximizedPaneId = a.maximized ? a.paneId : null;
25774
26126
  this.events.emit("pane:changed", void 0);
25775
26127
  break;
26128
+ case "resize":
26129
+ this.events.emit("pane:changed", void 0);
26130
+ break;
25776
26131
  }
25777
26132
  }
25778
26133
  /** Fire `indicator:added` + the handle's `ready` once, on the first computed model. */
@@ -25803,14 +26158,16 @@ var EngineOrchestrator = class _EngineOrchestrator {
25803
26158
  const paneId2 = this.routePane(id, model, record.options ?? {});
25804
26159
  this.placeModel(model, id, paneId2);
25805
26160
  record.model = model;
25806
- this.ensurePaneFor(paneId2);
26161
+ const paneCreated = this.ensurePaneFor(paneId2);
25807
26162
  record.renderHandle = this.renderer.mountIndicator(model);
25808
26163
  record.pendingStructural = false;
26164
+ if (paneCreated) this.events.emit("pane:changed", void 0);
25809
26165
  this.announce(record, handle);
25810
26166
  return true;
25811
26167
  }
25812
26168
  let paneId = record.model?.paneId ?? "price";
25813
26169
  const prevOwnScale = record.model?.ownScale === true;
26170
+ let rerouted = false;
25814
26171
  if (record.loading && !record.paneLocked) {
25815
26172
  const routed = this.routePane(id, model, record.options ?? {});
25816
26173
  if (routed !== paneId) {
@@ -25821,6 +26178,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
25821
26178
  this.ensurePaneFor(routed);
25822
26179
  record.pendingStructural = true;
25823
26180
  paneId = routed;
26181
+ rerouted = true;
25824
26182
  }
25825
26183
  }
25826
26184
  this.placeModel(model, id, paneId);
@@ -25833,6 +26191,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
25833
26191
  } else {
25834
26192
  this.renderer.updateIndicator(record.renderHandle, modelToValuePatch(model));
25835
26193
  }
26194
+ if (rerouted) this.events.emit("pane:changed", void 0);
25836
26195
  if (record.loading) this.setLoading(record, false);
25837
26196
  this.announce(record, handle);
25838
26197
  return true;
@@ -25847,13 +26206,16 @@ var EngineOrchestrator = class _EngineOrchestrator {
25847
26206
  const overlay = options.overlay ?? model.overlay;
25848
26207
  return overlay ? "price" : `pane-${id}`;
25849
26208
  }
25850
- /** Each named value series' last plotted value — the model is the one source every
25851
- * engine fills, so this works without an execution context. */
25852
- static plotsAtLastBar(model) {
26209
+ /** Each named value series' value on the chart's last bar — the model is the one source
26210
+ * every engine fills, so this works without an execution context. Points past the last
26211
+ * bar (a series shifted forward) are skipped by time. */
26212
+ static plotsAtLastBar(model, lastBarTime) {
25853
26213
  const out = {};
25854
26214
  for (const spec of model.series) {
25855
26215
  if (!isLineLikeSeries(spec)) continue;
25856
- out[spec.title] = spec.points[spec.points.length - 1]?.value ?? null;
26216
+ let i = spec.points.length - 1;
26217
+ if (lastBarTime !== void 0) while (i >= 0 && spec.points[i].time > lastBarTime) i -= 1;
26218
+ out[spec.title] = spec.points[i]?.value ?? null;
25857
26219
  }
25858
26220
  return out;
25859
26221
  }
@@ -25909,7 +26271,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
25909
26271
  // whatever produced this run.
25910
26272
  forming: this.config.live && this.bars.length > 0,
25911
26273
  complete: this.historyState !== "backfill",
25912
- plots: _EngineOrchestrator.plotsAtLastBar(model),
26274
+ plots: _EngineOrchestrator.plotsAtLastBar(model, lastBar?.time),
25913
26275
  vars: snapshot?.variables ?? {},
25914
26276
  ...snapshot?.strategy ? { strategy: snapshot.strategy } : {},
25915
26277
  warnings: snapshot?.warnings ?? [],
@@ -25978,8 +26340,9 @@ function sameInputValue(a, b) {
25978
26340
  return typeof a === "object" && a !== null && typeof b === "object" && b !== null && JSON.stringify(a) === JSON.stringify(b);
25979
26341
  }
25980
26342
  function blankedModel(model) {
26343
+ const { anchorTime: _anchor, ...rest } = model;
25981
26344
  return {
25982
- ...model,
26345
+ ...rest,
25983
26346
  series: model.series.map((s) => s.kind === "candle" || s.kind === "bar" ? { ...s, bars: [] } : s.kind === "markers" ? { ...s, markers: [] } : { ...s, points: [] }),
25984
26347
  fills: [],
25985
26348
  backgrounds: [],
@@ -26002,12 +26365,14 @@ function modelToValuePatch(model) {
26002
26365
  if (s.kind === "candle" || s.kind === "bar") {
26003
26366
  series.push({ seriesId: s.id, kind: "bars", bars: s.bars, barColors: s.barColors ?? [] });
26004
26367
  for (const b of s.bars) {
26368
+ if (!b) continue;
26005
26369
  if (b.time < from) from = b.time;
26006
26370
  if (b.time > to) to = b.time;
26007
26371
  }
26008
26372
  } else if (isLineLikeSeries(s)) {
26009
26373
  series.push({ seriesId: s.id, kind: "points", points: s.points });
26010
26374
  for (const p of s.points) {
26375
+ if (!p) continue;
26011
26376
  if (p.time < from) from = p.time;
26012
26377
  if (p.time > to) to = p.time;
26013
26378
  }
@@ -26064,6 +26429,17 @@ var PanesControl = class {
26064
26429
  if (this.guard()) this.controller.maximizePane(paneId);
26065
26430
  return this;
26066
26431
  }
26432
+ /**
26433
+ * Set a pane's height weight: the panes share the plot height in proportion to their
26434
+ * weights, so `setHeightWeight('price', 6)` next to two study panes at 1 gives the price
26435
+ * pane three quarters of the height. The weight sticks until the user drags a separator
26436
+ * or it is set again; a collapsed pane applies it when expanded. `weight` must be a
26437
+ * positive number.
26438
+ */
26439
+ setHeightWeight(paneId, weight) {
26440
+ if (this.guard()) this.controller.setPaneHeightWeight(paneId, weight);
26441
+ return this;
26442
+ }
26067
26443
  /** Move/merge an indicator to a pane — sugar for `handle.moveTo(...)` by id. */
26068
26444
  moveIndicator(id, target) {
26069
26445
  if (this.guard()) this.controller.moveIndicator(id, target);
@@ -26388,6 +26764,9 @@ function classicDescriptor(spec) {
26388
26764
  overlay: spec.overlay,
26389
26765
  // Studies stack (a 20 and a 200 moving average side by side) — unlike the layer-backed natives.
26390
26766
  multiInstance: true,
26767
+ // A study's values depend on every bar before them (an EMA's seed, an RSI's
26768
+ // smoothing), so it computes once over the whole history, as script indicators do.
26769
+ waitsForHistory: true,
26391
26770
  inputsSchema: () => spec.inputs,
26392
26771
  defaultInputs: () => Object.fromEntries(spec.inputs.map((i) => [i.key, i.defval])),
26393
26772
  create: () => new ClassicIndicator(spec)
@@ -27223,41 +27602,50 @@ var maEnvelope = {
27223
27602
  return directionalChannel(bars, basis, map(basis, (x) => x * (1 + k)), map(basis, (x) => x * (1 - k)), ink, "envelope");
27224
27603
  }
27225
27604
  };
27226
- var LINREG_CURVE = "Curve";
27227
- var LINREG_SLOPE = "Slope";
27228
- var linearRegression = {
27229
- type: "linear-regression",
27230
- title: "Linear Regression Curve",
27231
- shortTitle: "LinReg",
27232
- // Slope lives in its own pane; Curve force-overlays itself onto the price candles.
27605
+ var leastSquaresMa = {
27606
+ type: "lsma",
27607
+ title: "Least Squares Moving Average",
27608
+ shortTitle: "LSMA",
27609
+ overlay: true,
27610
+ inputs: [
27611
+ sourceInput("Close", "source", "Source", "Price series the regression line is fitted to."),
27612
+ { ...lengthInput(25, "length", "LSMA Length", 5e3, "Number of bars N in each least-squares fit. Every bar fits a straight line to its last N values and plots where that line ends, so the average leads turns that a same-length SMA trails, at the cost of overshooting after sharp moves."), min: 2 },
27613
+ intInput("offset", "Regression Offset", 0, 0, 500, "Bars to step back along each bar's fitted line. 0 plots the line's endpoint at the current bar; larger values read the fit further back, trading lead for smoothness."),
27614
+ ...directionInputs()
27615
+ ],
27616
+ compute: (bars, inputs) => {
27617
+ const src = sourceValues(bars, str3(inputs, "source", "Close"));
27618
+ const values = linreg(src, num3(inputs, "length", 25), Math.trunc(num3(inputs, "offset", 0)));
27619
+ return { plots: [slopeColoredPlot("lsma", "LSMA", values, inputs)] };
27620
+ }
27621
+ };
27622
+ var linearRegressionSlope = {
27623
+ type: "linear-regression-slope",
27624
+ title: "Linear Regression Slope",
27625
+ shortTitle: "LRS",
27233
27626
  overlay: false,
27234
27627
  inputs: [
27235
- optionInput("mode", "Mode", LINREG_CURVE, [LINREG_CURVE, LINREG_SLOPE], "Curve plots the regression value on the price chart; Slope plots the per-bar slope of the fit in the pane."),
27236
27628
  { ...lengthInput(100, "length", "Length", 5e3, "Number of bars used to fit the linear regression."), min: 2 },
27237
27629
  sourceInput("Close", "source", "Source", "Price series the regression is fitted to."),
27238
- colorInput(BULLISH, "Rising", "bullColor", "Color of the regression curve and slope line while the fit slopes upward."),
27239
- colorInput(BEARISH, "Falling", "bearColor", "Color of the regression curve and slope line while the fit slopes downward."),
27240
- colorInput(NEUTRAL, "Regression Line", "lineColor", "Color of the regression curve and slope line while the fit is flat.")
27630
+ colorInput(BULLISH, "Rising", "bullColor", "Color of the slope line while the fit slopes upward."),
27631
+ colorInput(BEARISH, "Falling", "bearColor", "Color of the slope line while the fit slopes downward."),
27632
+ colorInput(NEUTRAL, "Slope Line", "lineColor", "Color of the slope line while the fit is flat.")
27241
27633
  ],
27242
27634
  compute: (bars, inputs) => {
27243
27635
  const len = num3(inputs, "length", 100);
27244
27636
  const src = sourceValues(bars, str3(inputs, "source", "Close"));
27245
- const curve = linreg(src, len, 0);
27246
- const slope = zip(curve, linreg(src, len, 1), (a, b) => a - b);
27637
+ const slope = zip(linreg(src, len, 0), linreg(src, len, 1), (a, b) => a - b);
27247
27638
  const bull = str3(inputs, "bullColor", BULLISH);
27248
27639
  const bear = str3(inputs, "bearColor", BEARISH);
27249
27640
  const flat = str3(inputs, "lineColor", NEUTRAL);
27250
27641
  const colors = slope.map((s) => Number.isFinite(s) ? s > 0 ? bull : s < 0 ? bear : flat : null);
27251
- if (str3(inputs, "mode", LINREG_CURVE) === LINREG_SLOPE) {
27252
- return {
27253
- plots: [{ key: "slope", title: "Slope", values: slope, color: flat, width: 2, colors }],
27254
- levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed" }]
27255
- };
27256
- }
27257
- return { plots: [{ key: "curve", title: "Curve", values: curve, color: flat, width: 2, colors, overlay: true }] };
27642
+ return {
27643
+ plots: [{ key: "slope", title: "Slope", values: slope, color: flat, width: 2, colors }],
27644
+ levels: [{ key: "zero", price: 0, color: transp(NEUTRAL, 50), lineStyle: "dashed" }]
27645
+ };
27258
27646
  }
27259
27647
  };
27260
- var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, linearRegression];
27648
+ var averageSpecs = [simpleMa, exponentialMa, movingAverage, smoothedMa, zlema, vidya, maEnvelope, leastSquaresMa, linearRegressionSlope];
27261
27649
 
27262
27650
  // src/core/native-indicators/classics/bands.ts
27263
27651
  var JAW_COLOR = "#ab47bc";