@luxalgo/vela 0.6.17 → 0.6.19

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-B-Jz59Rf.d.ts → DataProvider-Cb-2lC5O.d.ts} +1 -1
  2. package/dist/{DataProvider-OfR3fXg9.d.cts → DataProvider-UtWw2CgJ.d.cts} +1 -1
  3. package/dist/{chunk-YCD72KGK.js → chunk-22QJKGXX.js} +565 -151
  4. package/dist/{chunk-OSZDSH7F.js → chunk-627A5HEV.js} +252 -120
  5. package/dist/{contributions-C3b0Sx5P.d.cts → contributions-5nz3PLRe.d.ts} +9 -4
  6. package/dist/{contributions-Swzz10fG.d.ts → contributions-DEPRtkmG.d.cts} +9 -4
  7. package/dist/index.cjs +563 -149
  8. package/dist/index.d.cts +6 -6
  9. package/dist/index.d.ts +6 -6
  10. package/dist/index.js +1 -1
  11. package/dist/{options-CX1lSYWA.d.cts → options-BCRmYALw.d.cts} +12 -3
  12. package/dist/{options-CX1lSYWA.d.ts → options-BCRmYALw.d.ts} +12 -3
  13. package/dist/{plugin-mvddudqd.d.ts → plugin-CUtfLeqz.d.ts} +3 -3
  14. package/dist/{plugin-BjnEE1zA.d.cts → plugin-DVUaROPo.d.cts} +3 -3
  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-IURCya0H.d.ts → statusline-model-hY9cK4BS.d.cts} +17 -7
  24. package/dist/{statusline-model-D91cU2fF.d.cts → statusline-model-wW3m4in1.d.ts} +17 -7
  25. package/dist/ui.d.cts +1 -1
  26. package/dist/ui.d.ts +1 -1
  27. package/dist/vela.global.js +563 -149
  28. package/dist/vela.global.min.js +53 -51
  29. package/dist/widget.cjs +814 -268
  30. package/dist/widget.d.cts +39 -12
  31. package/dist/widget.d.ts +39 -12
  32. package/dist/widget.js +3 -3
  33. package/dist/workspace.cjs +814 -268
  34. package/dist/workspace.d.cts +14 -5
  35. package/dist/workspace.d.ts +14 -5
  36. package/dist/workspace.js +2 -2
  37. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-YCD72KGK.js';
1
+ import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-22QJKGXX.js';
2
2
  import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, widgetAttachments, getDrawingType, inputDeltas } from './chunk-IFJJPXSV.js';
3
3
  import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-XCBJU674.js';
4
4
  import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-T5Z5YUCF.js';
@@ -1263,6 +1263,9 @@ function paneLabel(pane, fallbackTitle) {
1263
1263
  function indicatorLabel(i, fallbackTitle) {
1264
1264
  return i.title || fallbackTitle(i.id) || i.id;
1265
1265
  }
1266
+ function indicatorRowLabel(i, fallbackTitle) {
1267
+ return i.shorttitle || indicatorLabel(i, fallbackTitle);
1268
+ }
1266
1269
  function paneRows(pane) {
1267
1270
  return pane.items.flatMap((it) => it.kind === "row" ? [it.row] : []);
1268
1271
  }
@@ -1394,7 +1397,7 @@ function paneItems(snap, pane, units) {
1394
1397
  const indicatorRow = (i) => ({
1395
1398
  kind: "indicator",
1396
1399
  id: i.id,
1397
- label: indicatorLabel(i, snap.handleTitle),
1400
+ label: indicatorRowLabel(i, snap.handleTitle),
1398
1401
  visible: snap.indicatorVisible(i.id),
1399
1402
  ownScale: i.ownScale
1400
1403
  });
@@ -1524,9 +1527,13 @@ var CSS4 = `
1524
1527
  .vela-ot-empty { padding: 20px 10px; text-align: center; color: var(--vela-fg-muted); font-size: 12px; }
1525
1528
 
1526
1529
  /* \u2500\u2500 drawing groups \u2500\u2500 */
1527
- /* One top-level entry in a pane's drawing list: a lone drawing, or a whole group block. The
1528
- transparent border reserves the outline used when a drawing is dropped into the group. */
1529
- .vela-ot-unit { border: 1px solid transparent; border-radius: 6px; }
1530
+ /* One top-level entry in a pane's drawing list: a lone drawing, or a whole group block. No
1531
+ border of its own \u2014 units stack flush, so consecutive highlighted rows read as one block; the
1532
+ drop-into-group outline is drawn inside the box instead (see the drag-and-drop rules). */
1533
+ .vela-ot-unit { border-radius: 6px; }
1534
+ /* Adjacent picked rows merge into one contiguous highlight: the shared edge loses its rounding. */
1535
+ .vela-ot-unit:has(> .vela-ot-row[data-picked]) + .vela-ot-unit > .vela-ot-row[data-picked] { border-top-left-radius: 0; border-top-right-radius: 0; }
1536
+ .vela-ot-unit:has(+ .vela-ot-unit > .vela-ot-row[data-picked]) > .vela-ot-row[data-picked] { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
1530
1537
  .vela-ot-row[data-row-kind='group'] > .vela-icon { width: 12px; font-size: 11px; }
1531
1538
  /* A member sits indented under its group's header. */
1532
1539
  .vela-ot-subrow { padding-left: 26px; }
@@ -1583,8 +1590,10 @@ var CSS4 = `
1583
1590
  .vela-ot-gap[data-drop] { height: 4px; background: var(--vela-fg-bright); }
1584
1591
  .vela-ot-gap[data-drop]::before { display: none; }
1585
1592
  /* A whole container accepts the drop: merging into a pane, or a pane with no drawings yet.
1586
- The pane block's transparent border reserves the room for this outline. */
1593
+ The pane block's transparent border reserves the room for this outline; a group unit has no
1594
+ border to color, so it draws the same line as an inset outline (no layout footprint). */
1587
1595
  .vela-ot [data-drop='target'] { border-color: var(--vela-fg-bright); background: var(--vela-hover); }
1596
+ .vela-ot-unit[data-drop='target'] { outline: 1px solid var(--vela-fg-bright); outline-offset: -1px; }
1588
1597
  /* Where a reorder would insert. */
1589
1598
  .vela-ot [data-drop='before'] { box-shadow: inset 0 2px 0 var(--vela-fg-bright); }
1590
1599
  .vela-ot [data-drop='after'] { box-shadow: inset 0 -2px 0 var(--vela-fg-bright); }
@@ -1647,7 +1656,8 @@ var ObjectTree = class extends SidePanel {
1647
1656
  super(host, "Object tree", "vela-ot");
1648
1657
  this.iconFor = iconFor;
1649
1658
  this.chart = null;
1650
- this.selectedDrawing = null;
1659
+ /** What the CHART has selected — every member of a multi-selection, mirrored as rows. */
1660
+ this.selectedDrawings = /* @__PURE__ */ new Set();
1651
1661
  this.symbolName = "";
1652
1662
  /** The raw (possibly venue-prefixed) symbol — what icon resolution routes on. */
1653
1663
  this.symbolRaw = "";
@@ -1698,12 +1708,9 @@ var ObjectTree = class extends SidePanel {
1698
1708
  this.unsubs.push(chart.on(ev, () => this.refresh()));
1699
1709
  }
1700
1710
  this.unsubs.push(
1701
- chart.on("drawing:selected", ({ id }) => {
1702
- this.selectedDrawing = id;
1703
- if (!this.syncingSelection) {
1704
- this.picked.clear();
1705
- if (id) this.picked.add(id);
1706
- }
1711
+ chart.on("drawing:selected", ({ ids }) => {
1712
+ this.selectedDrawings = new Set(ids);
1713
+ if (!this.syncingSelection) this.picked = new Set(ids);
1707
1714
  this.refresh();
1708
1715
  })
1709
1716
  );
@@ -2051,7 +2058,7 @@ var ObjectTree = class extends SidePanel {
2051
2058
  el.dataset.drag = "1";
2052
2059
  el.title = "Drag to restack, or onto another pane to move it there";
2053
2060
  if (inGroup) el.classList.add("vela-ot-subrow");
2054
- if (d.id === this.selectedDrawing) el.dataset.selected = "1";
2061
+ if (this.selectedDrawings.has(d.id)) el.dataset.selected = "1";
2055
2062
  if (this.picked.has(d.id)) el.dataset.picked = "1";
2056
2063
  el.addEventListener("click", (e) => this.onDrawClick(e, d.id));
2057
2064
  return el;
@@ -3228,6 +3235,20 @@ var SymbolPicker = class {
3228
3235
  this.seed = initialQuery;
3229
3236
  this.dialog.show();
3230
3237
  }
3238
+ get isOpen() {
3239
+ return this.dialog.open;
3240
+ }
3241
+ /** Text typed at the picker that never reached its field: closed, it opens seeded
3242
+ * with the text; open (the field takes focus a tick after the dialog does, and a
3243
+ * fast typist's next key lands in that tick), it extends the query. */
3244
+ type(text) {
3245
+ if (!this.isOpen) {
3246
+ this.open(text);
3247
+ return;
3248
+ }
3249
+ this.input.value = (this.input.value + text).toUpperCase();
3250
+ this.refresh();
3251
+ }
3231
3252
  close() {
3232
3253
  this.dialog.hide();
3233
3254
  }
@@ -3641,6 +3662,20 @@ var TimeframeQuick = class {
3641
3662
  this.input.setSelectionRange(this.input.value.length, this.input.value.length);
3642
3663
  }, 0);
3643
3664
  }
3665
+ get isOpen() {
3666
+ return this.dialog.open;
3667
+ }
3668
+ /** Text typed at the entry that never reached its field: closed, it opens seeded
3669
+ * with the text; open (the field takes focus a tick after the dialog does, and a
3670
+ * fast typist's next key lands in that tick), it extends the entry. */
3671
+ type(text) {
3672
+ if (!this.isOpen) {
3673
+ this.open(text);
3674
+ return;
3675
+ }
3676
+ this.input.value += text;
3677
+ this.renderHint();
3678
+ }
3644
3679
  close() {
3645
3680
  this.dialog.hide();
3646
3681
  }
@@ -3800,9 +3835,10 @@ async function resolveIndicators(config, fetchImpl = fetch) {
3800
3835
  return out;
3801
3836
  }
3802
3837
  var ledgerEntryName = (e) => typeof e === "string" ? e : e.name;
3838
+ var ledgerNativeType = (e) => typeof e === "string" ? e : e.type;
3803
3839
  function indicatorLedger(i) {
3804
3840
  const natives = [...i.present];
3805
- if (i.volumePending && !natives.includes("volume")) natives.push("volume");
3841
+ if (i.volumePending && !natives.some((e) => ledgerNativeType(e) === "volume")) natives.push("volume");
3806
3842
  return {
3807
3843
  manifest: i.manifestSettled ? [...i.instanceEntries] : [...i.pendingManifest ?? i.instanceEntries],
3808
3844
  natives
@@ -3886,18 +3922,28 @@ function sanitizeCell(raw) {
3886
3922
  if (c.drawings != null && typeof c.drawings === "object") out.drawings = c.drawings;
3887
3923
  const ind = c.indicators;
3888
3924
  if (ind != null && typeof ind === "object") {
3925
+ const bag = (v) => v != null && typeof v === "object" && !Array.isArray(v) ? v : void 0;
3889
3926
  const manifest = Array.isArray(ind.manifest) ? ind.manifest.flatMap((n) => {
3890
3927
  if (typeof n === "string") return [n];
3891
3928
  if (n != null && typeof n === "object" && typeof n.name === "string") {
3892
3929
  const e = n;
3893
- const bag = (v) => v != null && typeof v === "object" && !Array.isArray(v) ? v : void 0;
3894
3930
  const inputs = bag(e.inputs);
3895
3931
  const props = bag(e.props);
3896
- return [inputs || props ? { name: e.name, ...inputs ? { inputs } : {}, ...props ? { props } : {} } : e.name];
3932
+ const hidden = e.hidden === true;
3933
+ return [inputs || props || hidden ? { name: e.name, ...inputs ? { inputs } : {}, ...props ? { props } : {}, ...hidden ? { hidden } : {} } : e.name];
3934
+ }
3935
+ return [];
3936
+ }) : [];
3937
+ const natives = Array.isArray(ind.natives) ? ind.natives.flatMap((n) => {
3938
+ if (typeof n === "string") return [n];
3939
+ if (n != null && typeof n === "object" && typeof n.type === "string") {
3940
+ const e = n;
3941
+ const inputs = bag(e.inputs);
3942
+ const hidden = e.hidden === true;
3943
+ return [inputs || hidden ? { type: e.type, ...inputs ? { inputs } : {}, ...hidden ? { hidden } : {} } : e.type];
3897
3944
  }
3898
3945
  return [];
3899
3946
  }) : [];
3900
- const natives = Array.isArray(ind.natives) ? ind.natives.filter((n) => typeof n === "string") : [];
3901
3947
  out.indicators = { manifest, natives };
3902
3948
  }
3903
3949
  const ext = sanitizeExt(c.ext);
@@ -4033,11 +4079,34 @@ function fmtChange(open, close) {
4033
4079
  const sign = diff >= 0 ? "+" : "";
4034
4080
  return `${sign}${fmtPrice(diff, decimalsFor(close))} (${sign}${pct.toFixed(2)}%)`;
4035
4081
  }
4082
+ function fmtChangePct(open, close) {
4083
+ if (open == null || close == null || !Number.isFinite(open) || !Number.isFinite(close) || open === 0) return "";
4084
+ const pct = (close - open) / open * 100;
4085
+ return `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%`;
4086
+ }
4036
4087
 
4037
4088
  // src/widget/statusline-model.ts
4038
4089
  function segmentVisibility(parts, chartHidden = false) {
4039
4090
  return { avatar: parts.logo, symbol: parts.name, meta: parts.name, market: parts.market, ohlc: parts.ohlc && !chartHidden, change: parts.change && !chartHidden, eye: chartHidden };
4040
4091
  }
4092
+ var STATUSLINE_LADDER = [
4093
+ { stacked: false, level: "full" },
4094
+ { stacked: true, level: "full" },
4095
+ { stacked: true, level: "compact" },
4096
+ { stacked: true, level: "minimal" }
4097
+ ];
4098
+ function readoutCells(level, readout) {
4099
+ if (readout === "value") return [{ key: "close", label: "" }];
4100
+ if (level === "full") {
4101
+ return [
4102
+ { key: "open", label: "O" },
4103
+ { key: "high", label: "H" },
4104
+ { key: "low", label: "L" },
4105
+ { key: "close", label: "C" }
4106
+ ];
4107
+ }
4108
+ return [{ key: "close", label: level === "compact" ? "C" : "" }];
4109
+ }
4041
4110
  function statuslineMenuItems(parts, chartVisible) {
4042
4111
  return [
4043
4112
  { id: "part:logo", label: "Symbol logo", checked: parts.logo },
@@ -4064,6 +4133,11 @@ var CSS10 = `
4064
4133
  display: flex;
4065
4134
  align-items: baseline;
4066
4135
  gap: var(--vela-space-2);
4136
+ /* The chip never grows past the plot: fit() measures overflow against this width to
4137
+ * walk the layout ladder (stack, then shed values), so overflow:hidden only guards
4138
+ * the transient between a resize and the next measure. */
4139
+ max-width: calc(100% - var(--vela-toolbar-gutter, 0px) - var(--vela-scale-gutter, 0px) - 24px);
4140
+ overflow: hidden;
4067
4141
  color: var(--vela-fg);
4068
4142
  font-size: var(--vela-font-size-md);
4069
4143
  /* Same chip treatment as the indicator legend rows (InputsUI): a translucent wash of
@@ -4085,6 +4159,31 @@ var CSS10 = `
4085
4159
  /* Chart hidden (the price series' eye \u2014 renderer 'candleVisible'): the line dims to
4086
4160
  * the same 0.5 wash a hidden indicator's legend row wears. */
4087
4161
  .vela-statusline.vela-sl-chart-hidden { opacity: 0.5; }
4162
+ /* Two rows inside the chip \u2014 the identity (logo / symbol / meta / market badge) and the
4163
+ * value readout (O/H/L/C + change, or the show-chart eye). In the widest layout they sit
4164
+ * side by side on one line; the STACKED layouts (fit() decides, see the ladder in
4165
+ * statusline-model) turn the chip into a column so the values drop under the symbol. */
4166
+ .vela-statusline .vela-sl-row {
4167
+ display: flex;
4168
+ align-items: baseline;
4169
+ gap: var(--vela-space-2);
4170
+ white-space: nowrap;
4171
+ }
4172
+ .vela-statusline.vela-sl-stacked {
4173
+ flex-direction: column;
4174
+ align-items: flex-start;
4175
+ /* The same air between the two rows as between the values row and the legend row
4176
+ * that follows the chip (see the stacked legend shift below). */
4177
+ row-gap: var(--vela-space-1);
4178
+ }
4179
+ /* Stacked, the identity row may shrink (the meta carries the ellipsis) \u2014 the values row
4180
+ * never does, so its overflow is what fit() measures to keep descending the ladder. */
4181
+ .vela-statusline.vela-sl-stacked .vela-sl-identity { max-width: 100%; }
4182
+ .vela-statusline.vela-sl-stacked .vela-sl-meta {
4183
+ min-width: 0;
4184
+ overflow: hidden;
4185
+ text-overflow: ellipsis;
4186
+ }
4088
4187
  .vela-statusline .vela-sl-avatar {
4089
4188
  width: 18px;
4090
4189
  height: 18px;
@@ -4136,59 +4235,13 @@ var CSS10 = `
4136
4235
  * stylesheet is document-global, so a bare attribute selector here would shift every
4137
4236
  * chart on the page, including statusline-less ones. */
4138
4237
  .vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(26px); }
4139
- /* Mobile: two-line chip \u2014 logo / symbol / meta / market status on one aligned row, the
4140
- * bar change on the next. Full O/H/L/C stays hidden (too dense on a phone-width plot).
4141
- * GRID, not a wrapping flexbox: an absolutely positioned wrapping flex container sizes
4142
- * to the one-line sum of ALL segments (max-content), so its background used to stretch
4143
- * far past the market badge; a grid hugs the widest actual row. The meta column may
4144
- * shrink (it carries the ellipsis), the others wrap their content. */
4145
- [data-layout='mobile'] .vela-statusline {
4146
- display: grid;
4147
- grid-template-columns: auto auto minmax(0, auto) auto;
4148
- justify-content: start;
4149
- align-items: center;
4150
- row-gap: 1px;
4151
- max-width: calc(100% - var(--vela-toolbar-gutter, 0px) - var(--vela-scale-gutter, 0px) - 24px);
4152
- font-size: var(--vela-font-size-sm);
4153
- }
4154
- [data-layout='mobile'] .vela-statusline .vela-sl-symbol {
4155
- font-size: var(--vela-font-size-md);
4156
- line-height: 1;
4157
- }
4158
- [data-layout='mobile'] .vela-statusline .vela-sl-meta {
4159
- min-width: 0;
4160
- overflow: hidden;
4161
- text-overflow: ellipsis;
4162
- white-space: nowrap;
4163
- line-height: 1;
4164
- }
4165
- [data-layout='mobile'] .vela-statusline .vela-sl-avatar,
4166
- [data-layout='mobile'] .vela-statusline .vela-sl-market { align-self: center; }
4167
- [data-layout='mobile'] .vela-statusline .vela-sl-ohlc { display: none !important; }
4168
- [data-layout='mobile'] .vela-statusline .vela-sl-change {
4169
- /* Second row, under the text column \u2014 the avatar keeps the first column. */
4170
- grid-column: 2 / -1;
4171
- font-size: var(--vela-font-size-sm);
4172
- line-height: 1.2;
4173
- }
4174
- [data-layout='mobile'] .vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(40px); }
4175
- /* FIT mode (multi-chart cells \u2014 see setFitMode): the line never wraps; segments that
4176
- * don't fit are HIDDEN by fit() (change first, then meta, then the market badge), so
4177
- * overflow:hidden only guards the transient between a resize and the next measure.
4178
- * Placed after the mobile block on purpose: same specificity, later wins. */
4179
- .vela-statusline.vela-sl-fit {
4180
- display: flex; /* undo the mobile grid \u2014 fit mode is one flex row again */
4181
- flex-wrap: nowrap;
4182
- align-items: center;
4183
- max-width: calc(100% - var(--vela-toolbar-gutter, 0px) - var(--vela-scale-gutter, 0px) - 24px);
4184
- overflow: hidden;
4185
- }
4186
- .vela-statusline.vela-sl-fit .vela-sl-change {
4187
- flex-basis: auto;
4188
- padding-left: 0;
4189
- }
4190
- /* One row again \u2014 the mobile two-line shift doesn't apply in fit mode. */
4191
- [data-layout='mobile'] .vela-sl-fit-host.vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(26px); }
4238
+ /* A stacked chip is two lines tall \u2014 the legend shifts one more line. fit() flags the
4239
+ * host whenever the ladder lands on a stacked layout. */
4240
+ .vela-has-statusline.vela-sl-stacked-host [${LEGEND_AT_TOP_ATTR}] { transform: translateY(40px); }
4241
+ /* Mobile: the same ladder at the phone's type scale \u2014 the width alone decides how much
4242
+ * of the readout fits, exactly as on a narrow desktop chart. */
4243
+ [data-layout='mobile'] .vela-statusline { font-size: var(--vela-font-size-sm); }
4244
+ [data-layout='mobile'] .vela-statusline .vela-sl-symbol { font-size: var(--vela-font-size-md); }
4192
4245
  `;
4193
4246
  function baseOfTicker(ticker) {
4194
4247
  return ticker.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || ticker;
@@ -4259,6 +4312,8 @@ var Statusline = class {
4259
4312
  /** Fit mode (multi-chart cells): one row, overflowing segments hidden — see {@link setFitMode}. */
4260
4313
  this.fitMode = false;
4261
4314
  this.fitRO = null;
4315
+ /** The ladder rung currently applied — see {@link fit}. */
4316
+ this.layout = { stacked: false, level: "full" };
4262
4317
  this.onContextMenu = (e) => {
4263
4318
  if (!this.menu || !this.menuHooks) return;
4264
4319
  e.preventDefault();
@@ -4305,8 +4360,18 @@ var Statusline = class {
4305
4360
  this.menuHooks?.setChartVisible(true);
4306
4361
  this.setChartHidden(false);
4307
4362
  });
4308
- this.el.append(this.avatarEl, this.symbolEl, this.metaEl, this.marketEl, this.ohlcEl, this.changeEl, this.eyeEl);
4363
+ this.identityRow = doc.createElement("span");
4364
+ this.identityRow.className = "vela-sl-row vela-sl-identity";
4365
+ this.identityRow.append(this.avatarEl, this.symbolEl, this.metaEl, this.marketEl);
4366
+ this.valuesRow = doc.createElement("span");
4367
+ this.valuesRow.className = "vela-sl-row vela-sl-values";
4368
+ this.valuesRow.append(this.ohlcEl, this.changeEl, this.eyeEl);
4369
+ this.el.append(this.identityRow, this.valuesRow);
4309
4370
  host.appendChild(this.el);
4371
+ if (typeof ResizeObserver !== "undefined") {
4372
+ this.fitRO = new ResizeObserver(() => this.fit());
4373
+ this.fitRO.observe(host);
4374
+ }
4310
4375
  this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
4311
4376
  this.eyeTip = new Tooltip(this.eyeEl, { content: "Show chart", placement: "bottom" });
4312
4377
  this.setMarketStatus("open");
@@ -4322,27 +4387,15 @@ var Statusline = class {
4322
4387
  this.fit();
4323
4388
  }
4324
4389
  /**
4325
- * Multi-chart cells: keep the line on ONE row whatever the cell width — never wrap.
4326
- * Segments that don't fit are hidden outright rather than clipped mid-glyph, least
4327
- * important first: OHLC, then the bar change, the venue/timeframe meta, and the
4328
- * market badge; the logo + ticker always stay. Re-fits live on host resizes.
4390
+ * Multi-chart cells: the same width ladder as a single chart, plus a last resort for
4391
+ * cells too narrow for even its bottom rung — whatever still overflows is hidden
4392
+ * outright rather than clipped mid-glyph, least important first: the values, the
4393
+ * venue/timeframe meta, then the market badge; the logo + ticker always stay.
4329
4394
  */
4330
4395
  setFitMode(on) {
4331
4396
  if (on === this.fitMode) return;
4332
4397
  this.fitMode = on;
4333
- this.el.classList.toggle("vela-sl-fit", on);
4334
- this.host.classList.toggle("vela-sl-fit-host", on);
4335
- if (on) {
4336
- if (typeof ResizeObserver !== "undefined" && !this.fitRO) {
4337
- this.fitRO = new ResizeObserver(() => this.fit());
4338
- this.fitRO.observe(this.host);
4339
- }
4340
- this.fit();
4341
- } else {
4342
- this.fitRO?.disconnect();
4343
- this.fitRO = null;
4344
- this.syncParts();
4345
- }
4398
+ this.fit();
4346
4399
  }
4347
4400
  /** Project the parts config onto the segments (the baseline fit() prunes from). */
4348
4401
  syncParts() {
@@ -4354,22 +4407,42 @@ var Statusline = class {
4354
4407
  this.ohlcEl.style.display = seg.ohlc ? "" : "none";
4355
4408
  this.changeEl.style.display = seg.change ? "" : "none";
4356
4409
  this.eyeEl.style.display = seg.eye ? "inline-flex" : "none";
4410
+ this.valuesRow.style.display = seg.ohlc || seg.change || seg.eye ? "" : "none";
4357
4411
  }
4358
- /** Hide overflowing segments until the row fits its max-width (fit mode only). */
4412
+ /** Apply one ladder rung: the stacked/inline shape and the readout's level. */
4413
+ applyLayout(layout) {
4414
+ this.layout = layout;
4415
+ this.el.classList.toggle("vela-sl-stacked", layout.stacked);
4416
+ this.host.classList.toggle("vela-sl-stacked-host", layout.stacked);
4417
+ this.renderValues();
4418
+ }
4419
+ /**
4420
+ * Walk the width ladder (see {@link STATUSLINE_LADDER}) until the chip fits its
4421
+ * max-width: the full one-line readout, then the values stacked under the symbol
4422
+ * line, then O/H/L shed, then the absolute change. In fit mode, when the last rung
4423
+ * still overflows, whole segments hide, least important first. Without layout
4424
+ * (detached host, node tests) nothing overflows, so the widest rung stays.
4425
+ */
4359
4426
  fit() {
4360
- if (!this.fitMode) return;
4361
4427
  this.syncParts();
4362
4428
  const seg = segmentVisibility(this.parts, this.chartHidden);
4429
+ const overflows = () => this.el.scrollWidth > this.el.clientWidth;
4430
+ const hasValues = seg.ohlc || seg.change;
4431
+ for (const rung of STATUSLINE_LADDER) {
4432
+ this.applyLayout(rung);
4433
+ if (!hasValues || !overflows()) break;
4434
+ }
4435
+ if (!this.fitMode) return;
4363
4436
  const order = [
4364
- [this.ohlcEl, seg.ohlc],
4365
- [this.changeEl, seg.change],
4437
+ [this.valuesRow, hasValues],
4366
4438
  [this.metaEl, seg.meta],
4367
4439
  [this.marketEl, seg.market]
4368
4440
  ];
4369
4441
  for (const [el, shown] of order) {
4370
- if (this.el.scrollWidth <= this.el.clientWidth) break;
4442
+ if (!overflows()) break;
4371
4443
  if (shown) el.style.display = "none";
4372
4444
  }
4445
+ if (this.valuesRow.style.display === "none") this.applyLayout({ ...this.layout, stacked: false });
4373
4446
  }
4374
4447
  /** Shape + color the value readout after the active price style: its own up/down
4375
4448
  * colors (candle bodies, bar ticks, the line color, …), the direction rule that
@@ -4482,7 +4555,7 @@ var Statusline = class {
4482
4555
  this.marketTip.destroy();
4483
4556
  this.eyeTip.destroy();
4484
4557
  this.marketBubble.destroy();
4485
- this.host.classList.remove("vela-has-statusline", "vela-sl-fit-host");
4558
+ this.host.classList.remove("vela-has-statusline", "vela-sl-stacked-host");
4486
4559
  this.el.remove();
4487
4560
  }
4488
4561
  detach() {
@@ -4491,6 +4564,10 @@ var Statusline = class {
4491
4564
  }
4492
4565
  render() {
4493
4566
  if (this.menuHooks) this.setChartHidden(!this.menuHooks.chartVisible());
4567
+ this.fit();
4568
+ }
4569
+ /** Write the value readout for the current bar at the current ladder level. */
4570
+ renderValues() {
4494
4571
  const bar = this.hoverBar ?? this.lastBar;
4495
4572
  if (!bar) {
4496
4573
  this.ohlcEl.replaceChildren();
@@ -4510,12 +4587,11 @@ var Statusline = class {
4510
4587
  s.appendChild(b);
4511
4588
  return s;
4512
4589
  };
4513
- if (this.readout === "value") this.ohlcEl.replaceChildren(cell("", bar.close));
4514
- else this.ohlcEl.replaceChildren(cell("O", bar.open), cell("H", bar.high), cell("L", bar.low), cell("C", bar.close));
4515
- this.changeEl.textContent = fmtChange(bar.open, bar.close);
4590
+ const { level } = this.layout;
4591
+ this.ohlcEl.replaceChildren(...readoutCells(level, this.readout).map((c) => cell(c.label, bar[c.key])));
4592
+ this.changeEl.textContent = level === "minimal" ? fmtChangePct(bar.open, bar.close) : fmtChange(bar.open, bar.close);
4516
4593
  this.changeEl.dataset.dir = up ? "up" : "down";
4517
4594
  this.changeEl.style.color = ink;
4518
- this.fit();
4519
4595
  }
4520
4596
  };
4521
4597
 
@@ -5442,6 +5518,11 @@ function cellDrawings(opt) {
5442
5518
  if (opt === true || opt == null) return { toolbar: false };
5443
5519
  return { ...opt, toolbar: false };
5444
5520
  }
5521
+ function nativeLedgerEntry(handle) {
5522
+ const inputs = inputDeltas(handle.inputs, handle.inputValues());
5523
+ const hidden = !handle.visible;
5524
+ return inputs || hidden ? { type: handle.nativeType, ...inputs ? { inputs } : {}, ...hidden ? { hidden: true } : {} } : handle.nativeType;
5525
+ }
5445
5526
  function instanceDeltas(handle) {
5446
5527
  if (!handle) return void 0;
5447
5528
  const inputs = inputDeltas(handle.inputs, handle.inputValues());
@@ -5564,10 +5645,13 @@ var ChartCell = class {
5564
5645
  if (seed.rendererConfig != null) this.inner.renderer.applyConfig(seed.rendererConfig);
5565
5646
  if (seed.drawings != null) this.inner.drawings.fromJSON(seed.drawings);
5566
5647
  if (seed.indicators) {
5567
- for (const type of seed.indicators.natives) this.inner.addNativeIndicator(type);
5648
+ for (const entry of seed.indicators.natives) {
5649
+ const handle = this.inner.addNativeIndicator(ledgerNativeType(entry));
5650
+ this.applyNativeLedgerEntry(handle, entry);
5651
+ }
5568
5652
  this.pendingManifestNames = [...seed.indicators.manifest];
5569
5653
  }
5570
- this.volumeIntent = seed.indicators ? seed.indicators.natives.includes("volume") : deps.volume;
5654
+ this.volumeIntent = seed.indicators ? seed.indicators.natives.some((e) => ledgerNativeType(e) === "volume") : deps.volume;
5571
5655
  this.extState = { ...seed.ext ?? {} };
5572
5656
  this.inner.on("load:end", () => {
5573
5657
  this.volumeMayBePending = false;
@@ -5639,6 +5723,7 @@ var ChartCell = class {
5639
5723
  this.refreshNativeCatalog();
5640
5724
  });
5641
5725
  this.inner.on("indicator:inputs", () => this.deps.onStateDirty());
5726
+ this.inner.on("indicator:visibility", () => this.deps.onStateDirty());
5642
5727
  this.inner.on("indicator:removed", ({ id: id2 }) => {
5643
5728
  if (this.destroyed) return;
5644
5729
  const idx = this.instances.findIndex((it) => it.handle?.id === id2);
@@ -6083,7 +6168,7 @@ var ChartCell = class {
6083
6168
  if (list.length === 0) return;
6084
6169
  for (const led of this.pendingManifestNames) {
6085
6170
  const entry = list.find((e) => e.name === ledgerEntryName(led));
6086
- if (entry) this.addManifestInstance(entry, { record: false, ...typeof led === "object" ? { inputs: led.inputs, props: led.props } : {} });
6171
+ if (entry) this.addManifestInstance(entry, { record: false, ...typeof led === "object" ? { inputs: led.inputs, props: led.props, hidden: led.hidden } : {} });
6087
6172
  }
6088
6173
  this.pendingManifestNames = null;
6089
6174
  return;
@@ -6102,23 +6187,35 @@ var ChartCell = class {
6102
6187
  applyIndicatorLedger(led) {
6103
6188
  const chart = this.inner;
6104
6189
  if (!chart) return;
6105
- this.volumeIntent = led.natives.includes("volume");
6190
+ this.volumeIntent = led.natives.some((e) => ledgerNativeType(e) === "volume");
6106
6191
  this.history.silently(() => {
6107
6192
  const owed = /* @__PURE__ */ new Map();
6108
- for (const type of led.natives) owed.set(type, (owed.get(type) ?? 0) + 1);
6193
+ for (const e of led.natives) {
6194
+ const type = ledgerNativeType(e);
6195
+ const queue = owed.get(type);
6196
+ if (queue) queue.push(e);
6197
+ else owed.set(type, [e]);
6198
+ }
6109
6199
  for (const h of this.nativeHandles()) {
6110
- const type = h.nativeType;
6111
- const n = owed.get(type) ?? 0;
6112
- if (n > 0) owed.set(type, n - 1);
6200
+ const entry = owed.get(h.nativeType)?.shift();
6201
+ if (entry !== void 0) this.applyNativeLedgerEntry(h, entry);
6113
6202
  else h.remove();
6114
6203
  }
6115
- for (const [type, n] of owed) for (let i = 0; i < n; i++) chart.addNativeIndicator(type);
6204
+ for (const queue of owed.values()) {
6205
+ for (const e of queue) {
6206
+ const h = chart.addNativeIndicator(ledgerNativeType(e));
6207
+ this.applyNativeLedgerEntry(h, e);
6208
+ }
6209
+ }
6116
6210
  for (const it of [...this.instances]) this.dropInstance(it);
6117
6211
  if (this.manifest.length > 0) {
6118
6212
  for (const item of led.manifest) {
6119
6213
  const entry = this.manifest.find((e) => e.name === ledgerEntryName(item));
6120
6214
  if (entry)
6121
- this.addManifestInstance(entry, { record: false, ...typeof item === "object" ? { inputs: item.inputs, props: item.props } : {} });
6215
+ this.addManifestInstance(entry, {
6216
+ record: false,
6217
+ ...typeof item === "object" ? { inputs: item.inputs, props: item.props, hidden: item.hidden } : {}
6218
+ });
6122
6219
  }
6123
6220
  this.pendingManifestNames = null;
6124
6221
  } else if (!this.deps.manifestSettled()) {
@@ -6185,6 +6282,7 @@ var ChartCell = class {
6185
6282
  if (this.destroyed) return;
6186
6283
  const values = opts.inputs || opts.props ? { inputs: opts.inputs, props: opts.props } : void 0;
6187
6284
  const it = { entry, handle: this.addToChart(entry, values), ...opts.external ? { external: true } : {}, ...values ? { values } : {} };
6285
+ if (opts.hidden) it.handle?.setVisible(false);
6188
6286
  this.instances.push(it);
6189
6287
  this.deps.onIndicatorsChanged(this.id);
6190
6288
  if (opts.record === false) return;
@@ -6228,6 +6326,29 @@ var ChartCell = class {
6228
6326
  /** Add a native indicator. A multi-instance type gets a fresh instance every time; a
6229
6327
  * single-instance type already on the chart hands back its existing one — nothing
6230
6328
  * changed, so nothing enters the undo timeline. */
6329
+ /**
6330
+ * Converge one native handle to a restored ledger entry: every schema key gets the
6331
+ * entry's value or its declaration default (the DOCUMENT is the truth — a live
6332
+ * tweak must not survive a restore that says otherwise), and visibility follows
6333
+ * the `hidden` flag. Skips the input write when nothing differs — `setInputs`
6334
+ * re-runs the indicator, and a no-op restore must not flicker every native.
6335
+ */
6336
+ applyNativeLedgerEntry(handle, entry) {
6337
+ const wanted = typeof entry === "string" ? void 0 : entry.inputs;
6338
+ if (handle.inputs.length > 0) {
6339
+ const current = handle.inputValues();
6340
+ const next = {};
6341
+ for (const schema of handle.inputs) {
6342
+ const target = wanted?.[schema.key] ?? schema.defval;
6343
+ if (JSON.stringify(current[schema.key]) !== JSON.stringify(target)) next[schema.key] = target;
6344
+ }
6345
+ if (Object.keys(next).length > 0) handle.setInputs(next);
6346
+ } else if (wanted) {
6347
+ handle.setInputs(wanted);
6348
+ }
6349
+ const visible = !(typeof entry === "object" && entry.hidden);
6350
+ if (handle.visible !== visible) handle.setVisible(visible);
6351
+ }
6231
6352
  addNative(type) {
6232
6353
  const chart = this.inner;
6233
6354
  if (!chart) return;
@@ -6391,10 +6512,13 @@ var ChartCell = class {
6391
6512
  // (`ctx.addIndicator`) stay out: their names would never resolve against the
6392
6513
  // manifest — their plugin persists them via the `ext` seam instead.
6393
6514
  indicators: indicatorLedger({
6394
- present: this.inner ? this.inner.presentNativeIndicators() : [],
6515
+ // Live handles carry what the registry read cannot: the input DELTAS and
6516
+ // the hidden flag, so native settings/visibility survive the round-trip.
6517
+ present: this.inner ? this.nativeHandles().map((h) => nativeLedgerEntry(h)) : [],
6395
6518
  instanceEntries: this.instances.filter((it) => !it.external).map((it) => {
6396
6519
  const d = it.handle ? instanceDeltas(it.handle) : it.values;
6397
- return d ? { name: it.entry.name, ...d } : it.entry.name;
6520
+ const hidden = it.handle ? !it.handle.visible : false;
6521
+ return d || hidden ? { name: it.entry.name, ...d ?? {}, ...hidden ? { hidden: true } : {} } : it.entry.name;
6398
6522
  }),
6399
6523
  pendingManifest: this.pendingManifestNames,
6400
6524
  manifestSettled: this.deps.manifestSettled(),
@@ -8053,6 +8177,17 @@ function nextAutoCellId(taken) {
8053
8177
  if (!taken.has(`c${i}`)) return `c${i}`;
8054
8178
  }
8055
8179
  }
8180
+ function resolveTyping(key, open) {
8181
+ if (open.dialogs > 0) {
8182
+ if (key.length !== 1) return null;
8183
+ if (open.symbolSearch) return { target: "symbol", text: key.toUpperCase() };
8184
+ if (open.timeframeEntry) return { target: "timeframe", text: key };
8185
+ return null;
8186
+ }
8187
+ if (/^[a-zA-Z]$/.test(key)) return { target: "symbol", text: key.toUpperCase() };
8188
+ if (/^[0-9]$/.test(key)) return { target: "timeframe", text: key };
8189
+ return null;
8190
+ }
8056
8191
  var VelaWorkspace = class {
8057
8192
  constructor(container, opts = {}) {
8058
8193
  this.events = new TypedEventBus();
@@ -9686,17 +9821,14 @@ var VelaWorkspace = class {
9686
9821
  }
9687
9822
  /** Bare-typing router: letters → symbol search (seeded), digits → timeframe entry. */
9688
9823
  routeTyping(ev) {
9689
- if (this.destroyed || this.openDialogs > 0) return;
9824
+ if (this.destroyed) return;
9690
9825
  if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
9691
9826
  if (isEditableTarget(ev)) return;
9692
- const key = ev.key;
9693
- if (/^[a-zA-Z]$/.test(key)) {
9694
- ev.preventDefault();
9695
- this.symbolPicker.open(key.toUpperCase());
9696
- } else if (/^[0-9]$/.test(key)) {
9697
- ev.preventDefault();
9698
- this.tfQuick.open(key);
9699
- }
9827
+ const hit = resolveTyping(ev.key, { dialogs: this.openDialogs, symbolSearch: this.symbolPicker.isOpen, timeframeEntry: this.tfQuick.isOpen });
9828
+ if (!hit) return;
9829
+ ev.preventDefault();
9830
+ if (hit.target === "symbol") this.symbolPicker.type(hit.text);
9831
+ else this.tfQuick.type(hit.text);
9700
9832
  }
9701
9833
  trackDialog(open) {
9702
9834
  this.openDialogs = Math.max(0, this.openDialogs + (open ? 1 : -1));