@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
@@ -105,10 +105,18 @@ var Vela = (function (exports) {
105
105
  this.records = /* @__PURE__ */ new Map();
106
106
  this.counter = 0;
107
107
  }
108
- /** Allocate a unique, stable per-instance id. */
109
- nextId(prefix = "ind") {
110
- this.counter += 1;
111
- return `${prefix}-${this.counter}`;
108
+ /**
109
+ * Mint a unique per-instance id. Host-supplied ids share the namespace, so a minted
110
+ * id skips anything already recorded — and anything `taken` reports live elsewhere
111
+ * (a handle the orchestrator holds outside the records, e.g. a fail-soft native).
112
+ */
113
+ nextId(prefix = "ind", taken = () => false) {
114
+ let id;
115
+ do {
116
+ this.counter += 1;
117
+ id = `${prefix}-${this.counter}`;
118
+ } while (this.records.has(id) || taken(id));
119
+ return id;
112
120
  }
113
121
  add(record) {
114
122
  this.records.set(record.id, record);
@@ -7116,6 +7124,42 @@ var Vela = (function (exports) {
7116
7124
  }
7117
7125
  };
7118
7126
 
7127
+ // src/core/marks/visibility.ts
7128
+ function markGroupOwnVisible(choices, groupId, groups) {
7129
+ const chosen = choices[groupId];
7130
+ if (typeof chosen === "boolean") return chosen;
7131
+ return groups.find((g) => g.id === groupId)?.visible !== false;
7132
+ }
7133
+ function markGroupVisible(choices, groupId, groups) {
7134
+ if (groupId === void 0) return true;
7135
+ const seen = /* @__PURE__ */ new Set();
7136
+ let id = groupId;
7137
+ while (id !== void 0 && !seen.has(id)) {
7138
+ seen.add(id);
7139
+ if (!markGroupOwnVisible(choices, id, groups)) return false;
7140
+ const parent = groups.find((g) => g.id === id)?.parent;
7141
+ id = parent !== void 0 && groups.some((g) => g.id === parent) ? parent : void 0;
7142
+ }
7143
+ return true;
7144
+ }
7145
+ function markGroupRows(groups) {
7146
+ const ids = new Set(groups.map((g) => g.id));
7147
+ const out = [];
7148
+ const placed = /* @__PURE__ */ new Set();
7149
+ const place = (group, depth) => {
7150
+ if (placed.has(group.id)) return;
7151
+ placed.add(group.id);
7152
+ out.push({ group, depth });
7153
+ for (const child of groups) if (child.parent === group.id && child.id !== group.id) place(child, depth + 1);
7154
+ };
7155
+ for (const g of groups) {
7156
+ const parentKnown = g.parent !== void 0 && ids.has(g.parent) && g.parent !== g.id;
7157
+ if (!parentKnown) place(g, 0);
7158
+ }
7159
+ for (const g of groups) place(g, 0);
7160
+ return out;
7161
+ }
7162
+
7119
7163
  // src/core/marks/MarksController.ts
7120
7164
  var MarksController = class {
7121
7165
  constructor(renderer, events) {
@@ -7160,6 +7204,7 @@ var Vela = (function (exports) {
7160
7204
  defineGroup(group) {
7161
7205
  if (!group || typeof group.id !== "string" || group.id.length === 0) throw new Error("[vela] marks.defineGroup: `id` must be a non-empty string");
7162
7206
  if (typeof group.label !== "string") throw new Error(`[vela] marks.defineGroup: group "${group.id}" needs a string \`label\``);
7207
+ 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`);
7163
7208
  this.groups.set(group.id, { ...group });
7164
7209
  this.sync();
7165
7210
  }
@@ -7179,12 +7224,16 @@ var Vela = (function (exports) {
7179
7224
  }
7180
7225
  this.renderer.applyFeature("marks", { groups: { [id]: visible } });
7181
7226
  }
7182
- /** A group's effective visibility: the user's (persisted) choice, else the group's declared default, else visible. */
7227
+ /**
7228
+ * A group's effective visibility: its own switch — the user's (persisted) choice, else
7229
+ * the declared default, else visible — AND every ancestor's, so a child under a
7230
+ * switched-off parent reads hidden whatever its own choice says.
7231
+ */
7183
7232
  isGroupVisible(id) {
7184
7233
  const state = this.renderer.readFeature("marks");
7185
- const chosen = state?.groups?.[id];
7186
- if (typeof chosen === "boolean") return chosen;
7187
- return this.groups.get(id)?.visible !== false;
7234
+ const groups = {};
7235
+ for (const [gid, v] of Object.entries(state?.groups ?? {})) if (typeof v === "boolean") groups[gid] = v;
7236
+ return markGroupVisible(groups, id, [...this.groups.values()]);
7188
7237
  }
7189
7238
  destroy() {
7190
7239
  for (const unsub of this.subs) unsub();
@@ -8612,7 +8661,7 @@ var Vela = (function (exports) {
8612
8661
  this.engines.set(language, engine);
8613
8662
  }
8614
8663
  addIndicator(source, options = {}) {
8615
- const id = this.registry.nextId();
8664
+ const id = this.claimIndicatorId(options.id);
8616
8665
  const title = options.title ?? "Indicator";
8617
8666
  const handle = new IndicatorHandleImpl(id, title, this, source);
8618
8667
  this.handles.set(id, handle);
@@ -8634,7 +8683,7 @@ var Vela = (function (exports) {
8634
8683
  const existing = this.registry.all().find((r) => r.native?.type === type);
8635
8684
  if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
8636
8685
  }
8637
- const id = this.registry.nextId("native");
8686
+ const id = this.registry.nextId("native", (candidate) => this.handles.has(candidate));
8638
8687
  const title = descriptor?.title ?? type;
8639
8688
  const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
8640
8689
  this.handles.set(id, handle);
@@ -8912,6 +8961,24 @@ var Vela = (function (exports) {
8912
8961
  this.events.clear();
8913
8962
  }
8914
8963
  // ── internals ───────────────────────────────────────────────
8964
+ /**
8965
+ * The id a new script indicator runs under: the host's when supplied, else a minted
8966
+ * one. A host id is opaque but must be a non-empty string, and it must not be live
8967
+ * on this chart — a duplicate is a programming error surfaced synchronously, never
8968
+ * renamed behind the caller's back (the whole point of supplying one is that
8969
+ * `handle.id` equals what was passed). "Live" reads both the records and the handle
8970
+ * map: a fail-soft handle never enters the registry but still owns its id.
8971
+ */
8972
+ claimIndicatorId(requested) {
8973
+ if (requested === void 0) return this.registry.nextId("ind", (candidate) => this.handles.has(candidate));
8974
+ if (typeof requested !== "string" || requested.length === 0) {
8975
+ throw new TypeError("[vela] addIndicator: `id` must be a non-empty string");
8976
+ }
8977
+ if (this.handles.has(requested) || this.registry.get(requested)) {
8978
+ throw new Error(`[vela] addIndicator: indicator id "${requested}" is already live on this chart`);
8979
+ }
8980
+ return requested;
8981
+ }
8915
8982
  async startIndicator(id, source, options, handle) {
8916
8983
  try {
8917
8984
  await this.readyPromise;
@@ -23374,6 +23441,17 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23374
23441
  for (const t of chartTypes()) if (t.id === style) return (t.basePainting ?? "candles") === "candles";
23375
23442
  return false;
23376
23443
  }
23444
+ var CANDLE_OVERRIDE_KEYS = [
23445
+ "candleUpColor",
23446
+ "candleDownColor",
23447
+ "candleBodyVisible",
23448
+ "candleBorderVisible",
23449
+ "candleBorderUpColor",
23450
+ "candleBorderDownColor",
23451
+ "candleWickVisible",
23452
+ "candleWickUpColor",
23453
+ "candleWickDownColor"
23454
+ ];
23377
23455
  function candleOverrideFor(style, bags) {
23378
23456
  if (!hasOwnCandlePaint(style)) return null;
23379
23457
  const bag = bags[style] ?? {};
@@ -23445,7 +23523,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23445
23523
  function clampSpacing(v) {
23446
23524
  return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
23447
23525
  }
23448
- function factoryResetConfig(factory) {
23526
+ function factoryResetConfig(factory, priceStyle = factory.series.style) {
23449
23527
  const bag = {};
23450
23528
  for (const t of chartTypes()) {
23451
23529
  const section = t.settings;
@@ -23467,10 +23545,16 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23467
23545
  if (!section.instances) addRows(section.rows);
23468
23546
  bag[t.id] = defaults2;
23469
23547
  }
23548
+ for (const t of chartTypes()) {
23549
+ if (!hasOwnCandlePaint(t.id)) continue;
23550
+ const defaults2 = bag[t.id] ?? {};
23551
+ for (const key of CANDLE_OVERRIDE_KEYS) defaults2[key] = null;
23552
+ bag[t.id] = defaults2;
23553
+ }
23470
23554
  for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
23471
23555
  bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
23472
23556
  }
23473
- return { ...factory, chartTypes: bag };
23557
+ return { ...factory, chartTypes: bag, series: { ...factory.series, style: priceStyle } };
23474
23558
  }
23475
23559
  function mergeConfig(base, patch) {
23476
23560
  const p = asObject(patch);
@@ -25849,11 +25933,8 @@ void main() {
25849
25933
  for (const [id, v] of Object.entries(g)) if (typeof v === "boolean") groups[id] = v;
25850
25934
  return { visible: typeof p.visible === "boolean" ? p.visible : base.visible, groups };
25851
25935
  }
25852
- function markGroupVisible(state, groupId, groups) {
25853
- if (groupId === void 0) return true;
25854
- const chosen = state.groups[groupId];
25855
- if (typeof chosen === "boolean") return chosen;
25856
- return groups.find((g) => g.id === groupId)?.visible !== false;
25936
+ function markGroupVisible2(state, groupId, groups) {
25937
+ return markGroupVisible(state.groups, groupId, groups);
25857
25938
  }
25858
25939
 
25859
25940
  // src/renderers/native/core/SceneGraph.ts
@@ -28029,13 +28110,29 @@ void main() {
28029
28110
  const img = deps.icons.get(mark.glyph.icon, ink, symbolPx, deps.dpr);
28030
28111
  if (img) ctx.drawImage(img, center.x - symbolPx / 2, center.y - symbolPx / 2, symbolPx, symbolPx);
28031
28112
  } else if (mark.glyph.letter) {
28113
+ const letter = mark.glyph.letter.slice(0, 2);
28032
28114
  ctx.fillStyle = ink;
28033
- ctx.font = `600 ${Math.round(size3 * 0.58)}px ${deps.fontFamily}`;
28034
- ctx.fillText(mark.glyph.letter.slice(0, 2), center.x, center.y + 0.5);
28115
+ let px = letterFontPx(size3, letter);
28116
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
28117
+ if (letter.length > 1) {
28118
+ px = fitLetterPx(px, ctx.measureText(letter).width, symbolInnerWidth(shape, size3));
28119
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
28120
+ }
28121
+ ctx.fillText(letter, center.x, center.y + 0.5);
28035
28122
  }
28036
28123
  }
28037
28124
  ctx.restore();
28038
28125
  }
28126
+ function letterFontPx(size3, letter) {
28127
+ return Math.round(size3 * (letter.length > 1 ? 0.38 : 0.58));
28128
+ }
28129
+ function symbolInnerWidth(shape, size3) {
28130
+ return (shape === "pin" ? size3 * 0.82 : size3) - 3;
28131
+ }
28132
+ function fitLetterPx(px, width, inner) {
28133
+ if (!(width > inner) || !(width > 0)) return px;
28134
+ return Math.max(4, Math.floor(px * inner / width));
28135
+ }
28039
28136
  function traceShape(ctx, shape, x, y, size3) {
28040
28137
  const r = size3 / 2;
28041
28138
  ctx.beginPath();
@@ -28237,7 +28334,7 @@ void main() {
28237
28334
  this.markLayout = layoutMarkLane({
28238
28335
  marks: scene.timelineMarks,
28239
28336
  groups: scene.markGroups,
28240
- hidden: (groupId) => !markGroupVisible(scene.marks, groupId, scene.markGroups),
28337
+ hidden: (groupId) => !markGroupVisible2(scene.marks, groupId, scene.markGroups),
28241
28338
  barTimes: this.barTimes(scene),
28242
28339
  intervalMs: coords.barInterval,
28243
28340
  xOf: (bar) => coords.logicalToX(bar),
@@ -29004,6 +29101,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29004
29101
  muted and non-interactive. Applied to each row's children so it survives display:contents;
29005
29102
  !important beats the inline opacity on labels. */
29006
29103
  .vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
29104
+ /* A mark group nested under a parent group on the Events tab: indented one step per level
29105
+ (--vela-sd-depth). The indent rides the first child (the switch) as a MARGIN \u2014 padding
29106
+ would push the switch's own tick out of its box. */
29107
+ .vela-sd-nested>*:first-child{margin-left:calc(var(--vela-sd-depth,1)*24px);}
29007
29108
  /* \u2500\u2500 mobile presentation (.vela-sd-mobile on the scrim; structural sizes are inline in open()) \u2500\u2500
29008
29109
  The tab rail becomes a burger-opened overlay sidebar; the group TOC becomes a sticky
29009
29110
  row of horizontally scrollable tabs; the instance strip scrolls instead of wrapping;
@@ -29052,6 +29153,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29052
29153
  this.hostSections = [];
29053
29154
  /** The timeline-mark groups (defined + named by marks) — one checkbox each on the Events tab. */
29054
29155
  this.markGroups = [];
29156
+ /** A group's OWN switch (what its checkbox shows) — a child under an off parent keeps its own state. */
29157
+ this.markGroupOwnVisible = () => true;
29055
29158
  this.markGroupVisible = () => true;
29056
29159
  /** The Canvas → Theme row: current app theme + where a pick is raised. The row is a
29057
29160
  * host callback, NOT a config patch — the app theme stays out of the persisted
@@ -29069,6 +29172,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29069
29172
  this.activeSection = null;
29070
29173
  /** Mobile chrome: fullscreen card, burger-opened section sidebar, TOC as top tabs. */
29071
29174
  this.mobileLayout = false;
29175
+ /** Opens/closes the mobile section sidebar of the CURRENT build (the burger in the
29176
+ * shell header outlives pane rebuilds, so it reaches the rail through here). */
29177
+ this.toggleRail = null;
29072
29178
  /** The visibility policy: setting ids hidden by the host (subtree semantics). */
29073
29179
  this.hiddenSettings = /* @__PURE__ */ new Set();
29074
29180
  if (getComputedStyle(container).position === "static") container.style.position = "relative";
@@ -29078,7 +29184,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29078
29184
  this.hostSections = sections;
29079
29185
  }
29080
29186
  /** The timeline-mark groups and their current visibility — the Events tab's rows on next open. */
29081
- setMarkGroups(groups, visible) {
29187
+ setMarkGroups(groups, visible, ownVisible = visible) {
29188
+ this.markGroupOwnVisible = ownVisible;
29082
29189
  this.markGroups = groups;
29083
29190
  this.markGroupVisible = visible;
29084
29191
  }
@@ -29144,7 +29251,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29144
29251
  this.onReset = onReset ?? null;
29145
29252
  ensureControlStyles();
29146
29253
  const mobile = this.mobileLayout;
29147
- let toggleRail = null;
29148
29254
  let burger;
29149
29255
  if (mobile) {
29150
29256
  burger = document.createElement("button");
@@ -29152,8 +29258,83 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29152
29258
  burger.className = "vela-sd-burger";
29153
29259
  burger.innerHTML = iconAt("burger", 16);
29154
29260
  burger.title = "Sections";
29155
- burger.addEventListener("click", () => toggleRail?.());
29261
+ burger.addEventListener("click", () => this.toggleRail?.());
29156
29262
  }
29263
+ const ui = new Dialog({
29264
+ host: this.container,
29265
+ title: "Chart settings",
29266
+ // Non-modal: a live-edit dialog must leave the page interactive — a modal
29267
+ // machine locks pointer events on the whole body, killing the chart, the
29268
+ // legend, and the body-portaled popovers (color picker, select lists).
29269
+ modal: false,
29270
+ contained: true,
29271
+ align: "top",
29272
+ draggable: !mobile,
29273
+ flush: true,
29274
+ className: "vela-dialog--settings",
29275
+ headerStart: burger,
29276
+ closeOnBackdrop: true,
29277
+ footer: (foot) => {
29278
+ foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
29279
+ const resetBtn = document.createElement("button");
29280
+ resetBtn.type = "button";
29281
+ resetBtn.textContent = "Reset defaults";
29282
+ resetBtn.className = "vela-sd-btn";
29283
+ resetBtn.addEventListener("click", () => this.onReset?.());
29284
+ foot.appendChild(resetBtn);
29285
+ },
29286
+ // A close signal may only close ITS OWN dialog: the machine reports the exit
29287
+ // asynchronously, so a close-then-reopen in one tick would otherwise see the
29288
+ // old instance's signal tear down the freshly opened one.
29289
+ onOpenChange: (open2) => {
29290
+ if (!open2 && this.ui === ui) this.close();
29291
+ }
29292
+ });
29293
+ if (mobile) ui.positioner.classList.add("vela-sd-mobile");
29294
+ ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
29295
+ this.root = ui.positioner;
29296
+ this.ui = ui;
29297
+ const panes = this.buildContent(ui, config, section, mobile);
29298
+ ui.show();
29299
+ this.layoutPanes(panes);
29300
+ }
29301
+ /**
29302
+ * Re-seed every control of an OPEN dialog from `config`, in place: the shell stays
29303
+ * (no close/open transition), only the tab rail and panes rebuild, landing back on
29304
+ * the current tab. The reset path — the restored values must show without the
29305
+ * dialog re-entering. No-op while closed.
29306
+ */
29307
+ refresh(config) {
29308
+ const ui = this.ui;
29309
+ if (!ui) return;
29310
+ closeOpenPopovers();
29311
+ closeWidthPopover();
29312
+ for (const dispose of this.hintTips) dispose();
29313
+ this.hintTips = [];
29314
+ this.config = config;
29315
+ ui.body.replaceChildren();
29316
+ this.layoutPanes(this.buildContent(ui, config, this.activeSection ?? void 0, this.mobileLayout));
29317
+ }
29318
+ /** Structured chart-type panes (instance strip / group TOC) own their layout and
29319
+ * tag their rows hosts instead; each host gets its own field grid. Runs on a SHOWN
29320
+ * dialog — the grids measure their labels. */
29321
+ layoutPanes(panes) {
29322
+ for (const el of panes) {
29323
+ const hosts = [...el.querySelectorAll("[data-sd-rows-host]")];
29324
+ if (hosts.length === 0) {
29325
+ this.layoutSettingsGrids(el);
29326
+ continue;
29327
+ }
29328
+ for (const h of hosts) this.layoutSettingsGrids(h);
29329
+ }
29330
+ }
29331
+ /**
29332
+ * Build the tab rail + one pane per section into `ui.body` (the linear `body` of
29333
+ * section markers and rows is split afterwards), select `section` (the active
29334
+ * style's own tab when none is asked for), and return the pane elements for
29335
+ * {@link layoutPanes}.
29336
+ */
29337
+ buildContent(ui, config, section, mobile) {
29157
29338
  const body = document.createElement("div");
29158
29339
  body.style.cssText = "display:flex;flex-direction:column;gap:0;";
29159
29340
  const sid = (el, id) => {
@@ -29357,9 +29538,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29357
29538
  if (this.markGroups.length > 0) {
29358
29539
  body.append(sid(this.section("Events"), MARKS_SETTINGS_ID));
29359
29540
  body.append(sid(this.sectionTitle("Visible events"), MARKS_GROUPS_SETTINGS_ID));
29360
- for (const g of this.markGroups) {
29361
- body.append(sid(this.boolRow(g.label, this.markGroupVisible(g.id), (v) => this.emit({ marks: { groups: { [g.id]: v } } })), markGroupSettingsId(g.id)));
29541
+ const rowsById = /* @__PURE__ */ new Map();
29542
+ const rows = markGroupRows(this.markGroups);
29543
+ const refreshDimming = () => {
29544
+ for (const { group } of rows) {
29545
+ const el = rowsById.get(group.id);
29546
+ if (!el || group.parent === void 0) continue;
29547
+ el.classList.toggle("vela-sd-soft", !this.markGroupVisible(group.parent));
29548
+ }
29549
+ };
29550
+ for (const { group: g, depth } of rows) {
29551
+ const row = this.boolRow(g.label, this.markGroupOwnVisible(g.id), (v) => {
29552
+ this.emit({ marks: { groups: { [g.id]: v } } });
29553
+ refreshDimming();
29554
+ });
29555
+ if (depth > 0) {
29556
+ row.classList.add("vela-sd-nested");
29557
+ row.style.setProperty("--vela-sd-depth", String(depth));
29558
+ }
29559
+ rowsById.set(g.id, row);
29560
+ body.append(sid(row, markGroupSettingsId(g.id)));
29362
29561
  }
29562
+ refreshDimming();
29363
29563
  }
29364
29564
  renderChartTypeSections("end");
29365
29565
  renderHostSections("end");
@@ -29387,8 +29587,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29387
29587
  if (mobile) {
29388
29588
  railScrim = document.createElement("div");
29389
29589
  railScrim.className = "vela-sd-railscrim";
29390
- railScrim.addEventListener("click", () => toggleRail?.());
29391
- toggleRail = (open2) => {
29590
+ railScrim.addEventListener("click", () => this.toggleRail?.());
29591
+ this.toggleRail = (open2) => {
29392
29592
  const on = open2 ?? !rail.classList.contains("open");
29393
29593
  rail.classList.toggle("open", on);
29394
29594
  railScrim?.classList.toggle("open", on);
@@ -29428,35 +29628,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29428
29628
  });
29429
29629
  if (hidActive) activate(0);
29430
29630
  };
29431
- const ui = new Dialog({
29432
- host: this.container,
29433
- title: "Chart settings",
29434
- // Non-modal: a live-edit dialog must leave the page interactive — a modal
29435
- // machine locks pointer events on the whole body, killing the chart, the
29436
- // legend, and the body-portaled popovers (color picker, select lists).
29437
- modal: false,
29438
- contained: true,
29439
- align: "top",
29440
- draggable: !mobile,
29441
- flush: true,
29442
- className: "vela-dialog--settings",
29443
- headerStart: burger,
29444
- closeOnBackdrop: true,
29445
- footer: (foot) => {
29446
- foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
29447
- const resetBtn = document.createElement("button");
29448
- resetBtn.type = "button";
29449
- resetBtn.textContent = "Reset defaults";
29450
- resetBtn.className = "vela-sd-btn";
29451
- resetBtn.addEventListener("click", () => this.onReset?.());
29452
- foot.appendChild(resetBtn);
29453
- },
29454
- onOpenChange: (open2) => {
29455
- if (!open2) this.close();
29456
- }
29457
- });
29458
- if (mobile) ui.positioner.classList.add("vela-sd-mobile");
29459
- ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
29460
29631
  const activate = (idx) => {
29461
29632
  panes.forEach((p, i) => {
29462
29633
  p.el.style.display = i === idx ? "block" : "none";
@@ -29465,7 +29636,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29465
29636
  this.activeSection = panes[idx]?.title ?? null;
29466
29637
  if (mobile) {
29467
29638
  ui.titleEl.textContent = panes[idx]?.title ?? "Chart settings";
29468
- toggleRail?.(false);
29639
+ this.toggleRail?.(false);
29469
29640
  }
29470
29641
  };
29471
29642
  panes.forEach((p, i) => {
@@ -29480,17 +29651,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29480
29651
  shell.append(rail, paneHost);
29481
29652
  if (railScrim) shell.append(railScrim);
29482
29653
  ui.body.appendChild(shell);
29483
- this.root = ui.positioner;
29484
- this.ui = ui;
29485
- ui.show();
29486
- for (const p of panes) {
29487
- const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
29488
- if (hosts.length === 0) {
29489
- this.layoutSettingsGrids(p.el);
29490
- continue;
29491
- }
29492
- for (const h of hosts) this.layoutSettingsGrids(h);
29493
- }
29654
+ return panes.map((p) => p.el);
29494
29655
  }
29495
29656
  close() {
29496
29657
  closeOpenPopovers();
@@ -29498,6 +29659,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29498
29659
  const ui = this.ui;
29499
29660
  this.ui = null;
29500
29661
  this.root = null;
29662
+ this.toggleRail = null;
29501
29663
  this.tabs = [];
29502
29664
  for (const dispose of this.hintTips) dispose();
29503
29665
  this.hintTips = [];
@@ -37597,7 +37759,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37597
37759
  }
37598
37760
  this.settingsDialog.setTheme(this.theme);
37599
37761
  this.settingsDialog.setHostSections(this.hostSettingsSections);
37600
- this.settingsDialog.setMarkGroups(this.markGroupsInUse(), (id) => markGroupVisible(this.scene.marks, id, this.scene.markGroups));
37762
+ this.settingsDialog.setMarkGroups(
37763
+ this.markGroupsForEventsTab(),
37764
+ (id) => markGroupVisible2(this.scene.marks, id, this.scene.markGroups),
37765
+ (id) => markGroupOwnVisible(this.scene.marks.groups, id, this.scene.markGroups)
37766
+ );
37601
37767
  this.settingsDialog.setHiddenSettings(this.hiddenSettings);
37602
37768
  this.syncThemeControl();
37603
37769
  this.settingsDialog.toggle(
@@ -37605,13 +37771,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37605
37771
  (patch) => this.applyConfig(patch),
37606
37772
  (json) => this.applyConfig(json),
37607
37773
  () => {
37608
- if (this.factoryConfig) this.applyConfig(factoryResetConfig(this.factoryConfig));
37609
- this.settingsDialog?.close();
37610
- this.openSettingsDialog();
37774
+ if (this.factoryConfig) this.applyConfig(this.factoryResetDocument(this.factoryConfig));
37775
+ this.settingsDialog?.refresh(this.getConfig());
37611
37776
  },
37612
37777
  section
37613
37778
  );
37614
37779
  }
37780
+ /**
37781
+ * The document "Reset defaults" applies: every setting back to its first-run value,
37782
+ * with two things that are NOT settings held or resolved here — the price style
37783
+ * stays the one the user is looking at, and the timeline-mark groups (an additive
37784
+ * merge, like the type bags) are named back to their host-declared visibility.
37785
+ */
37786
+ factoryResetDocument(factory) {
37787
+ const doc = factoryResetConfig(factory, this.scene.priceStyle);
37788
+ const groups = { ...doc.marks.groups };
37789
+ for (const g of this.markGroupsInUse()) groups[g.id] = g.visible !== false;
37790
+ return { ...doc, marks: { ...doc.marks, groups } };
37791
+ }
37615
37792
  /** Close the in-chart dialogs (indicator settings + chart-settings gear). No-op when none are open. */
37616
37793
  closeDialogs() {
37617
37794
  this.inputsUI?.closeOpenDialog();
@@ -38674,6 +38851,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38674
38851
  markGroupsInUse() {
38675
38852
  return effectiveMarkGroups(this.scene.timelineMarks, this.scene.markGroups);
38676
38853
  }
38854
+ /**
38855
+ * The groups as the Events tab lists them: nested only under a DEFINED parent. The
38856
+ * painter's visibility chain resolves parents against the defined groups alone, so a
38857
+ * parent that marks merely name must not nest (and dim) a child the painter still shows.
38858
+ */
38859
+ markGroupsForEventsTab() {
38860
+ const defined = new Set(this.scene.markGroups.map((g) => g.id));
38861
+ return this.markGroupsInUse().map((g) => g.parent !== void 0 && !defined.has(g.parent) ? { ...g, parent: void 0 } : g);
38862
+ }
38677
38863
  onViewportChange(cb) {
38678
38864
  this.viewportCbs.add(cb);
38679
38865
  return () => this.viewportCbs.delete(cb);
@@ -42843,6 +43029,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
42843
43029
  }
42844
43030
 
42845
43031
  // src/Vela.ts
43032
+ var asError = (err) => err instanceof Error ? err : new Error(String(err));
42846
43033
  var Vela = class {
42847
43034
  constructor(container, options = {}, deps = {}) {
42848
43035
  registerBuiltinChartTypes();
@@ -42966,7 +43153,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
42966
43153
  * fetch from — the same relationship `script:run` has to `context:changed`.
42967
43154
  */
42968
43155
  runScript(source, options) {
42969
- const handle = this.addIndicator(source, options);
43156
+ let handle;
43157
+ try {
43158
+ handle = this.addIndicator(source, options);
43159
+ } catch (err) {
43160
+ return Promise.resolve({ ok: false, run: null, error: asError(err), onUpdate: () => () => void 0, remove: () => void 0 });
43161
+ }
42970
43162
  const updates = /* @__PURE__ */ new Set();
42971
43163
  const drop = () => {
42972
43164
  try {
@@ -43010,7 +43202,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
43010
43202
  });
43011
43203
  }
43012
43204
  runIndicator(source, options) {
43013
- const handle = this.addIndicator(source, options);
43205
+ let handle;
43206
+ try {
43207
+ handle = this.addIndicator(source, options);
43208
+ } catch (err) {
43209
+ return Promise.resolve({ ok: false, handle: null, error: asError(err), context: null });
43210
+ }
43014
43211
  return new Promise((resolve) => {
43015
43212
  const offReady = handle.on("ready", () => {
43016
43213
  offReady();