@luxalgo/vela 0.7.4 → 0.7.6

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-RF1axuy1.d.cts → DataProvider-CGa4KHRa.d.cts} +1 -1
  2. package/dist/{DataProvider-CVpN5sCq.d.ts → DataProvider-CmWUtDVL.d.ts} +1 -1
  3. package/dist/{chunk-CSFNKXUW.js → chunk-EMB53WNQ.js} +16 -7
  4. package/dist/{chunk-DUURH3RN.js → chunk-JSVCWYBF.js} +271 -74
  5. package/dist/{contributions-CspPrMAJ.d.ts → contributions-C7Wsm_S9.d.ts} +13 -3
  6. package/dist/{contributions-DpZeu7KP.d.cts → contributions-D6p1RB38.d.cts} +13 -3
  7. package/dist/index.cjs +271 -74
  8. package/dist/index.d.cts +19 -6
  9. package/dist/index.d.ts +19 -6
  10. package/dist/index.js +1 -1
  11. package/dist/{options-XVpfr9HV.d.cts → options-D1AJKsn_.d.cts} +18 -0
  12. package/dist/{options-XVpfr9HV.d.ts → options-D1AJKsn_.d.ts} +18 -0
  13. package/dist/{plugin-D07HD2GL.d.cts → plugin-CxWfg6vK.d.cts} +3 -3
  14. package/dist/{plugin-BoLOkblF.d.ts → plugin-SodaQhjQ.d.ts} +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-Bd4qKuD6.d.cts → statusline-model-gWHrsOy3.d.cts} +3 -3
  24. package/dist/{statusline-model-CPa_hmhd.d.ts → statusline-model-xBiVsKoE.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 +271 -74
  28. package/dist/vela.global.min.js +53 -49
  29. package/dist/widget.cjs +286 -80
  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 +286 -80
  34. package/dist/workspace.d.cts +10 -5
  35. package/dist/workspace.d.ts +10 -5
  36. package/dist/workspace.js +2 -2
  37. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-XVpfr9HV.cjs';
1
+ import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-D1AJKsn_.cjs';
2
2
 
3
3
  /**
4
4
  * Symbol metadata an engine may need (e.g. Pine `syminfo.*`). Free-form beyond
@@ -1,4 +1,4 @@
1
- import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-XVpfr9HV.js';
1
+ import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-D1AJKsn_.js';
2
2
 
3
3
  /**
4
4
  * Symbol metadata an engine may need (e.g. Pine `syminfo.*`). Free-form beyond
@@ -1,4 +1,4 @@
1
- import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, SecondClock, 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-DUURH3RN.js';
1
+ import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, SecondClock, 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-JSVCWYBF.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-BFA32GOU.js';
3
3
  import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-ZADTVRUO.js';
4
4
  import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-BKHSQ4YM.js';
@@ -5644,6 +5644,11 @@ var ChartCell = class {
5644
5644
  if (typeof zone === "string" && normalizeTimezone(zone) !== normalizeTimezone(this.deps.timezone())) {
5645
5645
  this.deps.setTimezone(normalizeTimezone(zone));
5646
5646
  }
5647
+ const style = this.priceStyle;
5648
+ if (style !== (this.state.priceStyle ?? "candles")) {
5649
+ this.state.priceStyle = style;
5650
+ this.deps.onPriceStyleChanged(this.id);
5651
+ }
5647
5652
  this.syncStatuslineColors();
5648
5653
  this.syncPlotOverlayTokens();
5649
5654
  });
@@ -6283,16 +6288,19 @@ var ChartCell = class {
6283
6288
  * a persistence handler's `restore` runs silently, a user-driven call records.
6284
6289
  */
6285
6290
  addExternalIndicator(entry) {
6291
+ const { id, inputs, props, hidden, ...script } = entry;
6286
6292
  this.addManifestInstance(
6287
- { ...entry, enabled: true },
6288
- { external: true, ...entry.inputs ? { inputs: entry.inputs } : {}, ...entry.props ? { props: entry.props } : {}, ...entry.hidden ? { hidden: true } : {} }
6293
+ { ...script, enabled: true },
6294
+ { external: true, ...id !== void 0 ? { id } : {}, ...inputs ? { inputs } : {}, ...props ? { props } : {}, ...hidden ? { hidden: true } : {} }
6289
6295
  );
6290
6296
  }
6291
6297
  /** Add ONE instance of a manifest entry (repeatable — duplicates are legitimate). */
6292
6298
  addManifestInstance(entry, opts = {}) {
6293
6299
  if (this.destroyed) return;
6294
6300
  const values = opts.inputs || opts.props ? { inputs: opts.inputs, props: opts.props } : void 0;
6295
- const it = { entry, handle: this.addToChart(entry, values), ...opts.external ? { external: true } : {}, ...values ? { values } : {} };
6301
+ const handle = this.addToChart(entry, values, opts.id);
6302
+ if (!handle) return;
6303
+ const it = { entry, handle, id: handle.id, ...opts.external ? { external: true } : {}, ...values ? { values } : {} };
6296
6304
  if (opts.hidden) it.handle?.setVisible(false);
6297
6305
  this.instances.push(it);
6298
6306
  this.deps.onIndicatorsChanged(this.id);
@@ -6301,7 +6309,7 @@ var ChartCell = class {
6301
6309
  this.history.push({
6302
6310
  undo: () => this.dropInstance(snapshot),
6303
6311
  redo: () => {
6304
- snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
6312
+ snapshot.handle = this.addToChart(snapshot.entry, snapshot.values, snapshot.id);
6305
6313
  this.instances.push(snapshot);
6306
6314
  this.deps.onIndicatorsChanged(this.id);
6307
6315
  }
@@ -6314,7 +6322,7 @@ var ChartCell = class {
6314
6322
  const snapshot = it;
6315
6323
  this.history.push({
6316
6324
  undo: () => {
6317
- snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
6325
+ snapshot.handle = this.addToChart(snapshot.entry, snapshot.values, snapshot.id);
6318
6326
  this.instances.push(snapshot);
6319
6327
  this.deps.onIndicatorsChanged(this.id);
6320
6328
  },
@@ -6404,9 +6412,10 @@ var ChartCell = class {
6404
6412
  this.deps.onIndicatorsChanged(this.id);
6405
6413
  });
6406
6414
  }
6407
- addToChart(entry, values) {
6415
+ addToChart(entry, values, id) {
6408
6416
  try {
6409
6417
  return this.inner?.addIndicator(entry.script, {
6418
+ ...id !== void 0 ? { id } : {},
6410
6419
  ...entry.language !== void 0 ? { language: entry.language } : {},
6411
6420
  ...values?.inputs ? { inputs: values.inputs } : {},
6412
6421
  ...values?.props ? { props: values.props } : {}
@@ -5356,6 +5356,17 @@ function hasOwnCandlePaint(style) {
5356
5356
  for (const t of chartTypes()) if (t.id === style) return (t.basePainting ?? "candles") === "candles";
5357
5357
  return false;
5358
5358
  }
5359
+ var CANDLE_OVERRIDE_KEYS = [
5360
+ "candleUpColor",
5361
+ "candleDownColor",
5362
+ "candleBodyVisible",
5363
+ "candleBorderVisible",
5364
+ "candleBorderUpColor",
5365
+ "candleBorderDownColor",
5366
+ "candleWickVisible",
5367
+ "candleWickUpColor",
5368
+ "candleWickDownColor"
5369
+ ];
5359
5370
  function candleOverrideFor(style, bags) {
5360
5371
  if (!hasOwnCandlePaint(style)) return null;
5361
5372
  const bag = bags[style] ?? {};
@@ -5427,7 +5438,7 @@ function clampLevel(v) {
5427
5438
  function clampSpacing(v) {
5428
5439
  return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
5429
5440
  }
5430
- function factoryResetConfig(factory) {
5441
+ function factoryResetConfig(factory, priceStyle = factory.series.style) {
5431
5442
  const bag = {};
5432
5443
  for (const t of chartTypes()) {
5433
5444
  const section = t.settings;
@@ -5449,10 +5460,16 @@ function factoryResetConfig(factory) {
5449
5460
  if (!section.instances) addRows(section.rows);
5450
5461
  bag[t.id] = defaults;
5451
5462
  }
5463
+ for (const t of chartTypes()) {
5464
+ if (!hasOwnCandlePaint(t.id)) continue;
5465
+ const defaults = bag[t.id] ?? {};
5466
+ for (const key of CANDLE_OVERRIDE_KEYS) defaults[key] = null;
5467
+ bag[t.id] = defaults;
5468
+ }
5452
5469
  for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
5453
5470
  bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
5454
5471
  }
5455
- return { ...factory, chartTypes: bag };
5472
+ return { ...factory, chartTypes: bag, series: { ...factory.series, style: priceStyle } };
5456
5473
  }
5457
5474
  function mergeConfig(base, patch) {
5458
5475
  const p = asObject(patch);
@@ -7819,6 +7836,42 @@ function drawTextLines(ctx, lines, x, firstY, step, color) {
7819
7836
  for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i], x, firstY + i * step);
7820
7837
  }
7821
7838
 
7839
+ // src/core/marks/visibility.ts
7840
+ function markGroupOwnVisible(choices, groupId, groups) {
7841
+ const chosen = choices[groupId];
7842
+ if (typeof chosen === "boolean") return chosen;
7843
+ return groups.find((g) => g.id === groupId)?.visible !== false;
7844
+ }
7845
+ function markGroupVisible(choices, groupId, groups) {
7846
+ if (groupId === void 0) return true;
7847
+ const seen = /* @__PURE__ */ new Set();
7848
+ let id = groupId;
7849
+ while (id !== void 0 && !seen.has(id)) {
7850
+ seen.add(id);
7851
+ if (!markGroupOwnVisible(choices, id, groups)) return false;
7852
+ const parent = groups.find((g) => g.id === id)?.parent;
7853
+ id = parent !== void 0 && groups.some((g) => g.id === parent) ? parent : void 0;
7854
+ }
7855
+ return true;
7856
+ }
7857
+ function markGroupRows(groups) {
7858
+ const ids = new Set(groups.map((g) => g.id));
7859
+ const out = [];
7860
+ const placed = /* @__PURE__ */ new Set();
7861
+ const place = (group, depth) => {
7862
+ if (placed.has(group.id)) return;
7863
+ placed.add(group.id);
7864
+ out.push({ group, depth });
7865
+ for (const child of groups) if (child.parent === group.id && child.id !== group.id) place(child, depth + 1);
7866
+ };
7867
+ for (const g of groups) {
7868
+ const parentKnown = g.parent !== void 0 && ids.has(g.parent) && g.parent !== g.id;
7869
+ if (!parentKnown) place(g, 0);
7870
+ }
7871
+ for (const g of groups) place(g, 0);
7872
+ return out;
7873
+ }
7874
+
7822
7875
  // src/renderers/shared/marks-state.ts
7823
7876
  function defaultMarksState() {
7824
7877
  return { visible: true, groups: {} };
@@ -7831,11 +7884,8 @@ function mergeMarksState(base, patch) {
7831
7884
  for (const [id, v] of Object.entries(g)) if (typeof v === "boolean") groups[id] = v;
7832
7885
  return { visible: typeof p.visible === "boolean" ? p.visible : base.visible, groups };
7833
7886
  }
7834
- function markGroupVisible(state, groupId, groups) {
7835
- if (groupId === void 0) return true;
7836
- const chosen = state.groups[groupId];
7837
- if (typeof chosen === "boolean") return chosen;
7838
- return groups.find((g) => g.id === groupId)?.visible !== false;
7887
+ function markGroupVisible2(state, groupId, groups) {
7888
+ return markGroupVisible(state.groups, groupId, groups);
7839
7889
  }
7840
7890
 
7841
7891
  // src/renderers/native/core/SceneGraph.ts
@@ -10011,13 +10061,29 @@ function paintMarkLane(ctx, layout, deps) {
10011
10061
  const img = deps.icons.get(mark.glyph.icon, ink, symbolPx, deps.dpr);
10012
10062
  if (img) ctx.drawImage(img, center.x - symbolPx / 2, center.y - symbolPx / 2, symbolPx, symbolPx);
10013
10063
  } else if (mark.glyph.letter) {
10064
+ const letter = mark.glyph.letter.slice(0, 2);
10014
10065
  ctx.fillStyle = ink;
10015
- ctx.font = `600 ${Math.round(size * 0.58)}px ${deps.fontFamily}`;
10016
- ctx.fillText(mark.glyph.letter.slice(0, 2), center.x, center.y + 0.5);
10066
+ let px = letterFontPx(size, letter);
10067
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
10068
+ if (letter.length > 1) {
10069
+ px = fitLetterPx(px, ctx.measureText(letter).width, symbolInnerWidth(shape, size));
10070
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
10071
+ }
10072
+ ctx.fillText(letter, center.x, center.y + 0.5);
10017
10073
  }
10018
10074
  }
10019
10075
  ctx.restore();
10020
10076
  }
10077
+ function letterFontPx(size, letter) {
10078
+ return Math.round(size * (letter.length > 1 ? 0.38 : 0.58));
10079
+ }
10080
+ function symbolInnerWidth(shape, size) {
10081
+ return (shape === "pin" ? size * 0.82 : size) - 3;
10082
+ }
10083
+ function fitLetterPx(px, width, inner) {
10084
+ if (!(width > inner) || !(width > 0)) return px;
10085
+ return Math.max(4, Math.floor(px * inner / width));
10086
+ }
10021
10087
  function traceShape(ctx, shape, x, y, size) {
10022
10088
  const r = size / 2;
10023
10089
  ctx.beginPath();
@@ -10219,7 +10285,7 @@ var ChromeRenderer = class {
10219
10285
  this.markLayout = layoutMarkLane({
10220
10286
  marks: scene.timelineMarks,
10221
10287
  groups: scene.markGroups,
10222
- hidden: (groupId) => !markGroupVisible(scene.marks, groupId, scene.markGroups),
10288
+ hidden: (groupId) => !markGroupVisible2(scene.marks, groupId, scene.markGroups),
10223
10289
  barTimes: this.barTimes(scene),
10224
10290
  intervalMs: coords.barInterval,
10225
10291
  xOf: (bar) => coords.logicalToX(bar),
@@ -10909,6 +10975,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
10909
10975
  muted and non-interactive. Applied to each row's children so it survives display:contents;
10910
10976
  !important beats the inline opacity on labels. */
10911
10977
  .vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
10978
+ /* A mark group nested under a parent group on the Events tab: indented one step per level
10979
+ (--vela-sd-depth). The indent rides the first child (the switch) as a MARGIN \u2014 padding
10980
+ would push the switch's own tick out of its box. */
10981
+ .vela-sd-nested>*:first-child{margin-left:calc(var(--vela-sd-depth,1)*24px);}
10912
10982
  /* \u2500\u2500 mobile presentation (.vela-sd-mobile on the scrim; structural sizes are inline in open()) \u2500\u2500
10913
10983
  The tab rail becomes a burger-opened overlay sidebar; the group TOC becomes a sticky
10914
10984
  row of horizontally scrollable tabs; the instance strip scrolls instead of wrapping;
@@ -10957,6 +11027,8 @@ var SettingsDialog = class {
10957
11027
  this.hostSections = [];
10958
11028
  /** The timeline-mark groups (defined + named by marks) — one checkbox each on the Events tab. */
10959
11029
  this.markGroups = [];
11030
+ /** A group's OWN switch (what its checkbox shows) — a child under an off parent keeps its own state. */
11031
+ this.markGroupOwnVisible = () => true;
10960
11032
  this.markGroupVisible = () => true;
10961
11033
  /** The Canvas → Theme row: current app theme + where a pick is raised. The row is a
10962
11034
  * host callback, NOT a config patch — the app theme stays out of the persisted
@@ -10974,6 +11046,9 @@ var SettingsDialog = class {
10974
11046
  this.activeSection = null;
10975
11047
  /** Mobile chrome: fullscreen card, burger-opened section sidebar, TOC as top tabs. */
10976
11048
  this.mobileLayout = false;
11049
+ /** Opens/closes the mobile section sidebar of the CURRENT build (the burger in the
11050
+ * shell header outlives pane rebuilds, so it reaches the rail through here). */
11051
+ this.toggleRail = null;
10977
11052
  /** The visibility policy: setting ids hidden by the host (subtree semantics). */
10978
11053
  this.hiddenSettings = /* @__PURE__ */ new Set();
10979
11054
  if (getComputedStyle(container).position === "static") container.style.position = "relative";
@@ -10983,7 +11058,8 @@ var SettingsDialog = class {
10983
11058
  this.hostSections = sections;
10984
11059
  }
10985
11060
  /** The timeline-mark groups and their current visibility — the Events tab's rows on next open. */
10986
- setMarkGroups(groups, visible) {
11061
+ setMarkGroups(groups, visible, ownVisible = visible) {
11062
+ this.markGroupOwnVisible = ownVisible;
10987
11063
  this.markGroups = groups;
10988
11064
  this.markGroupVisible = visible;
10989
11065
  }
@@ -11049,7 +11125,6 @@ var SettingsDialog = class {
11049
11125
  this.onReset = onReset ?? null;
11050
11126
  ensureControlStyles();
11051
11127
  const mobile = this.mobileLayout;
11052
- let toggleRail = null;
11053
11128
  let burger;
11054
11129
  if (mobile) {
11055
11130
  burger = document.createElement("button");
@@ -11057,8 +11132,83 @@ var SettingsDialog = class {
11057
11132
  burger.className = "vela-sd-burger";
11058
11133
  burger.innerHTML = iconAt("burger", 16);
11059
11134
  burger.title = "Sections";
11060
- burger.addEventListener("click", () => toggleRail?.());
11135
+ burger.addEventListener("click", () => this.toggleRail?.());
11136
+ }
11137
+ const ui = new Dialog({
11138
+ host: this.container,
11139
+ title: "Chart settings",
11140
+ // Non-modal: a live-edit dialog must leave the page interactive — a modal
11141
+ // machine locks pointer events on the whole body, killing the chart, the
11142
+ // legend, and the body-portaled popovers (color picker, select lists).
11143
+ modal: false,
11144
+ contained: true,
11145
+ align: "top",
11146
+ draggable: !mobile,
11147
+ flush: true,
11148
+ className: "vela-dialog--settings",
11149
+ headerStart: burger,
11150
+ closeOnBackdrop: true,
11151
+ footer: (foot) => {
11152
+ foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
11153
+ const resetBtn = document.createElement("button");
11154
+ resetBtn.type = "button";
11155
+ resetBtn.textContent = "Reset defaults";
11156
+ resetBtn.className = "vela-sd-btn";
11157
+ resetBtn.addEventListener("click", () => this.onReset?.());
11158
+ foot.appendChild(resetBtn);
11159
+ },
11160
+ // A close signal may only close ITS OWN dialog: the machine reports the exit
11161
+ // asynchronously, so a close-then-reopen in one tick would otherwise see the
11162
+ // old instance's signal tear down the freshly opened one.
11163
+ onOpenChange: (open) => {
11164
+ if (!open && this.ui === ui) this.close();
11165
+ }
11166
+ });
11167
+ if (mobile) ui.positioner.classList.add("vela-sd-mobile");
11168
+ ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
11169
+ this.root = ui.positioner;
11170
+ this.ui = ui;
11171
+ const panes = this.buildContent(ui, config, section, mobile);
11172
+ ui.show();
11173
+ this.layoutPanes(panes);
11174
+ }
11175
+ /**
11176
+ * Re-seed every control of an OPEN dialog from `config`, in place: the shell stays
11177
+ * (no close/open transition), only the tab rail and panes rebuild, landing back on
11178
+ * the current tab. The reset path — the restored values must show without the
11179
+ * dialog re-entering. No-op while closed.
11180
+ */
11181
+ refresh(config) {
11182
+ const ui = this.ui;
11183
+ if (!ui) return;
11184
+ closeOpenPopovers();
11185
+ closeWidthPopover();
11186
+ for (const dispose of this.hintTips) dispose();
11187
+ this.hintTips = [];
11188
+ this.config = config;
11189
+ ui.body.replaceChildren();
11190
+ this.layoutPanes(this.buildContent(ui, config, this.activeSection ?? void 0, this.mobileLayout));
11191
+ }
11192
+ /** Structured chart-type panes (instance strip / group TOC) own their layout and
11193
+ * tag their rows hosts instead; each host gets its own field grid. Runs on a SHOWN
11194
+ * dialog — the grids measure their labels. */
11195
+ layoutPanes(panes) {
11196
+ for (const el of panes) {
11197
+ const hosts = [...el.querySelectorAll("[data-sd-rows-host]")];
11198
+ if (hosts.length === 0) {
11199
+ this.layoutSettingsGrids(el);
11200
+ continue;
11201
+ }
11202
+ for (const h of hosts) this.layoutSettingsGrids(h);
11061
11203
  }
11204
+ }
11205
+ /**
11206
+ * Build the tab rail + one pane per section into `ui.body` (the linear `body` of
11207
+ * section markers and rows is split afterwards), select `section` (the active
11208
+ * style's own tab when none is asked for), and return the pane elements for
11209
+ * {@link layoutPanes}.
11210
+ */
11211
+ buildContent(ui, config, section, mobile) {
11062
11212
  const body = document.createElement("div");
11063
11213
  body.style.cssText = "display:flex;flex-direction:column;gap:0;";
11064
11214
  const sid = (el, id) => {
@@ -11262,9 +11412,28 @@ var SettingsDialog = class {
11262
11412
  if (this.markGroups.length > 0) {
11263
11413
  body.append(sid(this.section("Events"), MARKS_SETTINGS_ID));
11264
11414
  body.append(sid(this.sectionTitle("Visible events"), MARKS_GROUPS_SETTINGS_ID));
11265
- for (const g of this.markGroups) {
11266
- body.append(sid(this.boolRow(g.label, this.markGroupVisible(g.id), (v) => this.emit({ marks: { groups: { [g.id]: v } } })), markGroupSettingsId(g.id)));
11415
+ const rowsById = /* @__PURE__ */ new Map();
11416
+ const rows = markGroupRows(this.markGroups);
11417
+ const refreshDimming = () => {
11418
+ for (const { group } of rows) {
11419
+ const el = rowsById.get(group.id);
11420
+ if (!el || group.parent === void 0) continue;
11421
+ el.classList.toggle("vela-sd-soft", !this.markGroupVisible(group.parent));
11422
+ }
11423
+ };
11424
+ for (const { group: g, depth } of rows) {
11425
+ const row = this.boolRow(g.label, this.markGroupOwnVisible(g.id), (v) => {
11426
+ this.emit({ marks: { groups: { [g.id]: v } } });
11427
+ refreshDimming();
11428
+ });
11429
+ if (depth > 0) {
11430
+ row.classList.add("vela-sd-nested");
11431
+ row.style.setProperty("--vela-sd-depth", String(depth));
11432
+ }
11433
+ rowsById.set(g.id, row);
11434
+ body.append(sid(row, markGroupSettingsId(g.id)));
11267
11435
  }
11436
+ refreshDimming();
11268
11437
  }
11269
11438
  renderChartTypeSections("end");
11270
11439
  renderHostSections("end");
@@ -11292,8 +11461,8 @@ var SettingsDialog = class {
11292
11461
  if (mobile) {
11293
11462
  railScrim = document.createElement("div");
11294
11463
  railScrim.className = "vela-sd-railscrim";
11295
- railScrim.addEventListener("click", () => toggleRail?.());
11296
- toggleRail = (open) => {
11464
+ railScrim.addEventListener("click", () => this.toggleRail?.());
11465
+ this.toggleRail = (open) => {
11297
11466
  const on = open ?? !rail.classList.contains("open");
11298
11467
  rail.classList.toggle("open", on);
11299
11468
  railScrim?.classList.toggle("open", on);
@@ -11333,35 +11502,6 @@ var SettingsDialog = class {
11333
11502
  });
11334
11503
  if (hidActive) activate(0);
11335
11504
  };
11336
- const ui = new Dialog({
11337
- host: this.container,
11338
- title: "Chart settings",
11339
- // Non-modal: a live-edit dialog must leave the page interactive — a modal
11340
- // machine locks pointer events on the whole body, killing the chart, the
11341
- // legend, and the body-portaled popovers (color picker, select lists).
11342
- modal: false,
11343
- contained: true,
11344
- align: "top",
11345
- draggable: !mobile,
11346
- flush: true,
11347
- className: "vela-dialog--settings",
11348
- headerStart: burger,
11349
- closeOnBackdrop: true,
11350
- footer: (foot) => {
11351
- foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
11352
- const resetBtn = document.createElement("button");
11353
- resetBtn.type = "button";
11354
- resetBtn.textContent = "Reset defaults";
11355
- resetBtn.className = "vela-sd-btn";
11356
- resetBtn.addEventListener("click", () => this.onReset?.());
11357
- foot.appendChild(resetBtn);
11358
- },
11359
- onOpenChange: (open) => {
11360
- if (!open) this.close();
11361
- }
11362
- });
11363
- if (mobile) ui.positioner.classList.add("vela-sd-mobile");
11364
- ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
11365
11505
  const activate = (idx) => {
11366
11506
  panes.forEach((p, i) => {
11367
11507
  p.el.style.display = i === idx ? "block" : "none";
@@ -11370,7 +11510,7 @@ var SettingsDialog = class {
11370
11510
  this.activeSection = panes[idx]?.title ?? null;
11371
11511
  if (mobile) {
11372
11512
  ui.titleEl.textContent = panes[idx]?.title ?? "Chart settings";
11373
- toggleRail?.(false);
11513
+ this.toggleRail?.(false);
11374
11514
  }
11375
11515
  };
11376
11516
  panes.forEach((p, i) => {
@@ -11385,17 +11525,7 @@ var SettingsDialog = class {
11385
11525
  shell.append(rail, paneHost);
11386
11526
  if (railScrim) shell.append(railScrim);
11387
11527
  ui.body.appendChild(shell);
11388
- this.root = ui.positioner;
11389
- this.ui = ui;
11390
- ui.show();
11391
- for (const p of panes) {
11392
- const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
11393
- if (hosts.length === 0) {
11394
- this.layoutSettingsGrids(p.el);
11395
- continue;
11396
- }
11397
- for (const h of hosts) this.layoutSettingsGrids(h);
11398
- }
11528
+ return panes.map((p) => p.el);
11399
11529
  }
11400
11530
  close() {
11401
11531
  closeOpenPopovers();
@@ -11403,6 +11533,7 @@ var SettingsDialog = class {
11403
11533
  const ui = this.ui;
11404
11534
  this.ui = null;
11405
11535
  this.root = null;
11536
+ this.toggleRail = null;
11406
11537
  this.tabs = [];
11407
11538
  for (const dispose of this.hintTips) dispose();
11408
11539
  this.hintTips = [];
@@ -18843,7 +18974,11 @@ var NativeRenderer = class {
18843
18974
  }
18844
18975
  this.settingsDialog.setTheme(this.theme);
18845
18976
  this.settingsDialog.setHostSections(this.hostSettingsSections);
18846
- this.settingsDialog.setMarkGroups(this.markGroupsInUse(), (id) => markGroupVisible(this.scene.marks, id, this.scene.markGroups));
18977
+ this.settingsDialog.setMarkGroups(
18978
+ this.markGroupsForEventsTab(),
18979
+ (id) => markGroupVisible2(this.scene.marks, id, this.scene.markGroups),
18980
+ (id) => markGroupOwnVisible(this.scene.marks.groups, id, this.scene.markGroups)
18981
+ );
18847
18982
  this.settingsDialog.setHiddenSettings(this.hiddenSettings);
18848
18983
  this.syncThemeControl();
18849
18984
  this.settingsDialog.toggle(
@@ -18851,13 +18986,24 @@ var NativeRenderer = class {
18851
18986
  (patch) => this.applyConfig(patch),
18852
18987
  (json) => this.applyConfig(json),
18853
18988
  () => {
18854
- if (this.factoryConfig) this.applyConfig(factoryResetConfig(this.factoryConfig));
18855
- this.settingsDialog?.close();
18856
- this.openSettingsDialog();
18989
+ if (this.factoryConfig) this.applyConfig(this.factoryResetDocument(this.factoryConfig));
18990
+ this.settingsDialog?.refresh(this.getConfig());
18857
18991
  },
18858
18992
  section
18859
18993
  );
18860
18994
  }
18995
+ /**
18996
+ * The document "Reset defaults" applies: every setting back to its first-run value,
18997
+ * with two things that are NOT settings held or resolved here — the price style
18998
+ * stays the one the user is looking at, and the timeline-mark groups (an additive
18999
+ * merge, like the type bags) are named back to their host-declared visibility.
19000
+ */
19001
+ factoryResetDocument(factory) {
19002
+ const doc = factoryResetConfig(factory, this.scene.priceStyle);
19003
+ const groups = { ...doc.marks.groups };
19004
+ for (const g of this.markGroupsInUse()) groups[g.id] = g.visible !== false;
19005
+ return { ...doc, marks: { ...doc.marks, groups } };
19006
+ }
18861
19007
  /** Close the in-chart dialogs (indicator settings + chart-settings gear). No-op when none are open. */
18862
19008
  closeDialogs() {
18863
19009
  this.inputsUI?.closeOpenDialog();
@@ -19920,6 +20066,15 @@ var NativeRenderer = class {
19920
20066
  markGroupsInUse() {
19921
20067
  return effectiveMarkGroups(this.scene.timelineMarks, this.scene.markGroups);
19922
20068
  }
20069
+ /**
20070
+ * The groups as the Events tab lists them: nested only under a DEFINED parent. The
20071
+ * painter's visibility chain resolves parents against the defined groups alone, so a
20072
+ * parent that marks merely name must not nest (and dim) a child the painter still shows.
20073
+ */
20074
+ markGroupsForEventsTab() {
20075
+ const defined = new Set(this.scene.markGroups.map((g) => g.id));
20076
+ return this.markGroupsInUse().map((g) => g.parent !== void 0 && !defined.has(g.parent) ? { ...g, parent: void 0 } : g);
20077
+ }
19923
20078
  onViewportChange(cb) {
19924
20079
  this.viewportCbs.add(cb);
19925
20080
  return () => this.viewportCbs.delete(cb);
@@ -21552,10 +21707,18 @@ var IndicatorRegistry = class {
21552
21707
  this.records = /* @__PURE__ */ new Map();
21553
21708
  this.counter = 0;
21554
21709
  }
21555
- /** Allocate a unique, stable per-instance id. */
21556
- nextId(prefix = "ind") {
21557
- this.counter += 1;
21558
- return `${prefix}-${this.counter}`;
21710
+ /**
21711
+ * Mint a unique per-instance id. Host-supplied ids share the namespace, so a minted
21712
+ * id skips anything already recorded — and anything `taken` reports live elsewhere
21713
+ * (a handle the orchestrator holds outside the records, e.g. a fail-soft native).
21714
+ */
21715
+ nextId(prefix = "ind", taken = () => false) {
21716
+ let id;
21717
+ do {
21718
+ this.counter += 1;
21719
+ id = `${prefix}-${this.counter}`;
21720
+ } while (this.records.has(id) || taken(id));
21721
+ return id;
21559
21722
  }
21560
21723
  add(record) {
21561
21724
  this.records.set(record.id, record);
@@ -21791,6 +21954,7 @@ var MarksController = class {
21791
21954
  defineGroup(group) {
21792
21955
  if (!group || typeof group.id !== "string" || group.id.length === 0) throw new Error("[vela] marks.defineGroup: `id` must be a non-empty string");
21793
21956
  if (typeof group.label !== "string") throw new Error(`[vela] marks.defineGroup: group "${group.id}" needs a string \`label\``);
21957
+ if (group.parent !== void 0 && (typeof group.parent !== "string" || group.parent.length === 0)) throw new Error(`[vela] marks.defineGroup: group "${group.id}" has a \`parent\` that is not a group id`);
21794
21958
  this.groups.set(group.id, { ...group });
21795
21959
  this.sync();
21796
21960
  }
@@ -21810,12 +21974,16 @@ var MarksController = class {
21810
21974
  }
21811
21975
  this.renderer.applyFeature("marks", { groups: { [id]: visible } });
21812
21976
  }
21813
- /** A group's effective visibility: the user's (persisted) choice, else the group's declared default, else visible. */
21977
+ /**
21978
+ * A group's effective visibility: its own switch — the user's (persisted) choice, else
21979
+ * the declared default, else visible — AND every ancestor's, so a child under a
21980
+ * switched-off parent reads hidden whatever its own choice says.
21981
+ */
21814
21982
  isGroupVisible(id) {
21815
21983
  const state = this.renderer.readFeature("marks");
21816
- const chosen = state?.groups?.[id];
21817
- if (typeof chosen === "boolean") return chosen;
21818
- return this.groups.get(id)?.visible !== false;
21984
+ const groups = {};
21985
+ for (const [gid, v] of Object.entries(state?.groups ?? {})) if (typeof v === "boolean") groups[gid] = v;
21986
+ return markGroupVisible(groups, id, [...this.groups.values()]);
21819
21987
  }
21820
21988
  destroy() {
21821
21989
  for (const unsub of this.subs) unsub();
@@ -22870,7 +23038,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22870
23038
  this.engines.set(language, engine);
22871
23039
  }
22872
23040
  addIndicator(source, options = {}) {
22873
- const id = this.registry.nextId();
23041
+ const id = this.claimIndicatorId(options.id);
22874
23042
  const title = options.title ?? "Indicator";
22875
23043
  const handle = new IndicatorHandleImpl(id, title, this, source);
22876
23044
  this.handles.set(id, handle);
@@ -22892,7 +23060,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22892
23060
  const existing = this.registry.all().find((r) => r.native?.type === type);
22893
23061
  if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
22894
23062
  }
22895
- const id = this.registry.nextId("native");
23063
+ const id = this.registry.nextId("native", (candidate) => this.handles.has(candidate));
22896
23064
  const title = descriptor?.title ?? type;
22897
23065
  const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
22898
23066
  this.handles.set(id, handle);
@@ -23170,6 +23338,24 @@ var EngineOrchestrator = class _EngineOrchestrator {
23170
23338
  this.events.clear();
23171
23339
  }
23172
23340
  // ── internals ───────────────────────────────────────────────
23341
+ /**
23342
+ * The id a new script indicator runs under: the host's when supplied, else a minted
23343
+ * one. A host id is opaque but must be a non-empty string, and it must not be live
23344
+ * on this chart — a duplicate is a programming error surfaced synchronously, never
23345
+ * renamed behind the caller's back (the whole point of supplying one is that
23346
+ * `handle.id` equals what was passed). "Live" reads both the records and the handle
23347
+ * map: a fail-soft handle never enters the registry but still owns its id.
23348
+ */
23349
+ claimIndicatorId(requested) {
23350
+ if (requested === void 0) return this.registry.nextId("ind", (candidate) => this.handles.has(candidate));
23351
+ if (typeof requested !== "string" || requested.length === 0) {
23352
+ throw new TypeError("[vela] addIndicator: `id` must be a non-empty string");
23353
+ }
23354
+ if (this.handles.has(requested) || this.registry.get(requested)) {
23355
+ throw new Error(`[vela] addIndicator: indicator id "${requested}" is already live on this chart`);
23356
+ }
23357
+ return requested;
23358
+ }
23173
23359
  async startIndicator(id, source, options, handle) {
23174
23360
  try {
23175
23361
  await this.readyPromise;
@@ -26375,6 +26561,7 @@ function registerClassicIndicators() {
26375
26561
  }
26376
26562
 
26377
26563
  // src/Vela.ts
26564
+ var asError = (err) => err instanceof Error ? err : new Error(String(err));
26378
26565
  var Vela = class {
26379
26566
  constructor(container, options = {}, deps = {}) {
26380
26567
  registerBuiltinChartTypes();
@@ -26498,7 +26685,12 @@ var Vela = class {
26498
26685
  * fetch from — the same relationship `script:run` has to `context:changed`.
26499
26686
  */
26500
26687
  runScript(source, options) {
26501
- const handle = this.addIndicator(source, options);
26688
+ let handle;
26689
+ try {
26690
+ handle = this.addIndicator(source, options);
26691
+ } catch (err) {
26692
+ return Promise.resolve({ ok: false, run: null, error: asError(err), onUpdate: () => () => void 0, remove: () => void 0 });
26693
+ }
26502
26694
  const updates = /* @__PURE__ */ new Set();
26503
26695
  const drop = () => {
26504
26696
  try {
@@ -26542,7 +26734,12 @@ var Vela = class {
26542
26734
  });
26543
26735
  }
26544
26736
  runIndicator(source, options) {
26545
- const handle = this.addIndicator(source, options);
26737
+ let handle;
26738
+ try {
26739
+ handle = this.addIndicator(source, options);
26740
+ } catch (err) {
26741
+ return Promise.resolve({ ok: false, handle: null, error: asError(err), context: null });
26742
+ }
26546
26743
  return new Promise((resolve) => {
26547
26744
  const offReady = handle.on("ready", () => {
26548
26745
  offReady();