@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
package/dist/widget.cjs CHANGED
@@ -5115,6 +5115,17 @@ function hasOwnCandlePaint(style) {
5115
5115
  for (const t of chartTypes()) if (t.id === style) return (t.basePainting ?? "candles") === "candles";
5116
5116
  return false;
5117
5117
  }
5118
+ var CANDLE_OVERRIDE_KEYS = [
5119
+ "candleUpColor",
5120
+ "candleDownColor",
5121
+ "candleBodyVisible",
5122
+ "candleBorderVisible",
5123
+ "candleBorderUpColor",
5124
+ "candleBorderDownColor",
5125
+ "candleWickVisible",
5126
+ "candleWickUpColor",
5127
+ "candleWickDownColor"
5128
+ ];
5118
5129
  function candleOverrideFor(style, bags) {
5119
5130
  if (!hasOwnCandlePaint(style)) return null;
5120
5131
  const bag = bags[style] ?? {};
@@ -5186,7 +5197,7 @@ function clampLevel(v) {
5186
5197
  function clampSpacing(v) {
5187
5198
  return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
5188
5199
  }
5189
- function factoryResetConfig(factory) {
5200
+ function factoryResetConfig(factory, priceStyle = factory.series.style) {
5190
5201
  const bag = {};
5191
5202
  for (const t of chartTypes()) {
5192
5203
  const section = t.settings;
@@ -5208,10 +5219,16 @@ function factoryResetConfig(factory) {
5208
5219
  if (!section.instances) addRows(section.rows);
5209
5220
  bag[t.id] = defaults2;
5210
5221
  }
5222
+ for (const t of chartTypes()) {
5223
+ if (!hasOwnCandlePaint(t.id)) continue;
5224
+ const defaults2 = bag[t.id] ?? {};
5225
+ for (const key of CANDLE_OVERRIDE_KEYS) defaults2[key] = null;
5226
+ bag[t.id] = defaults2;
5227
+ }
5211
5228
  for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
5212
5229
  bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
5213
5230
  }
5214
- return { ...factory, chartTypes: bag };
5231
+ return { ...factory, chartTypes: bag, series: { ...factory.series, style: priceStyle } };
5215
5232
  }
5216
5233
  function mergeConfig(base, patch) {
5217
5234
  const p = asObject(patch);
@@ -18443,10 +18460,18 @@ var IndicatorRegistry = class {
18443
18460
  this.records = /* @__PURE__ */ new Map();
18444
18461
  this.counter = 0;
18445
18462
  }
18446
- /** Allocate a unique, stable per-instance id. */
18447
- nextId(prefix = "ind") {
18448
- this.counter += 1;
18449
- return `${prefix}-${this.counter}`;
18463
+ /**
18464
+ * Mint a unique per-instance id. Host-supplied ids share the namespace, so a minted
18465
+ * id skips anything already recorded — and anything `taken` reports live elsewhere
18466
+ * (a handle the orchestrator holds outside the records, e.g. a fail-soft native).
18467
+ */
18468
+ nextId(prefix = "ind", taken = () => false) {
18469
+ let id;
18470
+ do {
18471
+ this.counter += 1;
18472
+ id = `${prefix}-${this.counter}`;
18473
+ } while (this.records.has(id) || taken(id));
18474
+ return id;
18450
18475
  }
18451
18476
  add(record) {
18452
18477
  this.records.set(record.id, record);
@@ -18653,6 +18678,42 @@ function presetToRange(preset, bars) {
18653
18678
  return { from: Math.max(first, from), to: last };
18654
18679
  }
18655
18680
 
18681
+ // src/core/marks/visibility.ts
18682
+ function markGroupOwnVisible(choices, groupId, groups) {
18683
+ const chosen = choices[groupId];
18684
+ if (typeof chosen === "boolean") return chosen;
18685
+ return groups.find((g) => g.id === groupId)?.visible !== false;
18686
+ }
18687
+ function markGroupVisible(choices, groupId, groups) {
18688
+ if (groupId === void 0) return true;
18689
+ const seen = /* @__PURE__ */ new Set();
18690
+ let id = groupId;
18691
+ while (id !== void 0 && !seen.has(id)) {
18692
+ seen.add(id);
18693
+ if (!markGroupOwnVisible(choices, id, groups)) return false;
18694
+ const parent = groups.find((g) => g.id === id)?.parent;
18695
+ id = parent !== void 0 && groups.some((g) => g.id === parent) ? parent : void 0;
18696
+ }
18697
+ return true;
18698
+ }
18699
+ function markGroupRows(groups) {
18700
+ const ids = new Set(groups.map((g) => g.id));
18701
+ const out = [];
18702
+ const placed = /* @__PURE__ */ new Set();
18703
+ const place = (group, depth) => {
18704
+ if (placed.has(group.id)) return;
18705
+ placed.add(group.id);
18706
+ out.push({ group, depth });
18707
+ for (const child of groups) if (child.parent === group.id && child.id !== group.id) place(child, depth + 1);
18708
+ };
18709
+ for (const g of groups) {
18710
+ const parentKnown = g.parent !== void 0 && ids.has(g.parent) && g.parent !== g.id;
18711
+ if (!parentKnown) place(g, 0);
18712
+ }
18713
+ for (const g of groups) place(g, 0);
18714
+ return out;
18715
+ }
18716
+
18656
18717
  // src/core/marks/MarksController.ts
18657
18718
  var MarksController = class {
18658
18719
  constructor(renderer, events) {
@@ -18697,6 +18758,7 @@ var MarksController = class {
18697
18758
  defineGroup(group) {
18698
18759
  if (!group || typeof group.id !== "string" || group.id.length === 0) throw new Error("[vela] marks.defineGroup: `id` must be a non-empty string");
18699
18760
  if (typeof group.label !== "string") throw new Error(`[vela] marks.defineGroup: group "${group.id}" needs a string \`label\``);
18761
+ 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`);
18700
18762
  this.groups.set(group.id, { ...group });
18701
18763
  this.sync();
18702
18764
  }
@@ -18716,12 +18778,16 @@ var MarksController = class {
18716
18778
  }
18717
18779
  this.renderer.applyFeature("marks", { groups: { [id]: visible } });
18718
18780
  }
18719
- /** A group's effective visibility: the user's (persisted) choice, else the group's declared default, else visible. */
18781
+ /**
18782
+ * A group's effective visibility: its own switch — the user's (persisted) choice, else
18783
+ * the declared default, else visible — AND every ancestor's, so a child under a
18784
+ * switched-off parent reads hidden whatever its own choice says.
18785
+ */
18720
18786
  isGroupVisible(id) {
18721
18787
  const state = this.renderer.readFeature("marks");
18722
- const chosen = state?.groups?.[id];
18723
- if (typeof chosen === "boolean") return chosen;
18724
- return this.groups.get(id)?.visible !== false;
18788
+ const groups = {};
18789
+ for (const [gid, v] of Object.entries(state?.groups ?? {})) if (typeof v === "boolean") groups[gid] = v;
18790
+ return markGroupVisible(groups, id, [...this.groups.values()]);
18725
18791
  }
18726
18792
  destroy() {
18727
18793
  for (const unsub of this.subs) unsub();
@@ -19788,7 +19854,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
19788
19854
  this.engines.set(language, engine);
19789
19855
  }
19790
19856
  addIndicator(source, options = {}) {
19791
- const id = this.registry.nextId();
19857
+ const id = this.claimIndicatorId(options.id);
19792
19858
  const title = options.title ?? "Indicator";
19793
19859
  const handle = new IndicatorHandleImpl(id, title, this, source);
19794
19860
  this.handles.set(id, handle);
@@ -19810,7 +19876,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
19810
19876
  const existing = this.registry.all().find((r) => r.native?.type === type);
19811
19877
  if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
19812
19878
  }
19813
- const id = this.registry.nextId("native");
19879
+ const id = this.registry.nextId("native", (candidate) => this.handles.has(candidate));
19814
19880
  const title = descriptor?.title ?? type;
19815
19881
  const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
19816
19882
  this.handles.set(id, handle);
@@ -20088,6 +20154,24 @@ var EngineOrchestrator = class _EngineOrchestrator {
20088
20154
  this.events.clear();
20089
20155
  }
20090
20156
  // ── internals ───────────────────────────────────────────────
20157
+ /**
20158
+ * The id a new script indicator runs under: the host's when supplied, else a minted
20159
+ * one. A host id is opaque but must be a non-empty string, and it must not be live
20160
+ * on this chart — a duplicate is a programming error surfaced synchronously, never
20161
+ * renamed behind the caller's back (the whole point of supplying one is that
20162
+ * `handle.id` equals what was passed). "Live" reads both the records and the handle
20163
+ * map: a fail-soft handle never enters the registry but still owns its id.
20164
+ */
20165
+ claimIndicatorId(requested) {
20166
+ if (requested === void 0) return this.registry.nextId("ind", (candidate) => this.handles.has(candidate));
20167
+ if (typeof requested !== "string" || requested.length === 0) {
20168
+ throw new TypeError("[vela] addIndicator: `id` must be a non-empty string");
20169
+ }
20170
+ if (this.handles.has(requested) || this.registry.get(requested)) {
20171
+ throw new Error(`[vela] addIndicator: indicator id "${requested}" is already live on this chart`);
20172
+ }
20173
+ return requested;
20174
+ }
20091
20175
  async startIndicator(id, source, options, handle) {
20092
20176
  try {
20093
20177
  await this.readyPromise;
@@ -25710,11 +25794,8 @@ function mergeMarksState(base, patch) {
25710
25794
  for (const [id, v] of Object.entries(g)) if (typeof v === "boolean") groups[id] = v;
25711
25795
  return { visible: typeof p.visible === "boolean" ? p.visible : base.visible, groups };
25712
25796
  }
25713
- function markGroupVisible(state, groupId, groups) {
25714
- if (groupId === void 0) return true;
25715
- const chosen = state.groups[groupId];
25716
- if (typeof chosen === "boolean") return chosen;
25717
- return groups.find((g) => g.id === groupId)?.visible !== false;
25797
+ function markGroupVisible2(state, groupId, groups) {
25798
+ return markGroupVisible(state.groups, groupId, groups);
25718
25799
  }
25719
25800
 
25720
25801
  // src/renderers/native/core/SceneGraph.ts
@@ -27890,13 +27971,29 @@ function paintMarkLane(ctx, layout, deps) {
27890
27971
  const img = deps.icons.get(mark.glyph.icon, ink, symbolPx, deps.dpr);
27891
27972
  if (img) ctx.drawImage(img, center.x - symbolPx / 2, center.y - symbolPx / 2, symbolPx, symbolPx);
27892
27973
  } else if (mark.glyph.letter) {
27974
+ const letter = mark.glyph.letter.slice(0, 2);
27893
27975
  ctx.fillStyle = ink;
27894
- ctx.font = `600 ${Math.round(size * 0.58)}px ${deps.fontFamily}`;
27895
- ctx.fillText(mark.glyph.letter.slice(0, 2), center.x, center.y + 0.5);
27976
+ let px = letterFontPx(size, letter);
27977
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
27978
+ if (letter.length > 1) {
27979
+ px = fitLetterPx(px, ctx.measureText(letter).width, symbolInnerWidth(shape, size));
27980
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
27981
+ }
27982
+ ctx.fillText(letter, center.x, center.y + 0.5);
27896
27983
  }
27897
27984
  }
27898
27985
  ctx.restore();
27899
27986
  }
27987
+ function letterFontPx(size, letter) {
27988
+ return Math.round(size * (letter.length > 1 ? 0.38 : 0.58));
27989
+ }
27990
+ function symbolInnerWidth(shape, size) {
27991
+ return (shape === "pin" ? size * 0.82 : size) - 3;
27992
+ }
27993
+ function fitLetterPx(px, width, inner) {
27994
+ if (!(width > inner) || !(width > 0)) return px;
27995
+ return Math.max(4, Math.floor(px * inner / width));
27996
+ }
27900
27997
  function traceShape(ctx, shape, x, y, size) {
27901
27998
  const r = size / 2;
27902
27999
  ctx.beginPath();
@@ -28098,7 +28195,7 @@ var ChromeRenderer = class {
28098
28195
  this.markLayout = layoutMarkLane({
28099
28196
  marks: scene.timelineMarks,
28100
28197
  groups: scene.markGroups,
28101
- hidden: (groupId) => !markGroupVisible(scene.marks, groupId, scene.markGroups),
28198
+ hidden: (groupId) => !markGroupVisible2(scene.marks, groupId, scene.markGroups),
28102
28199
  barTimes: this.barTimes(scene),
28103
28200
  intervalMs: coords.barInterval,
28104
28201
  xOf: (bar) => coords.logicalToX(bar),
@@ -28788,6 +28885,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28788
28885
  muted and non-interactive. Applied to each row's children so it survives display:contents;
28789
28886
  !important beats the inline opacity on labels. */
28790
28887
  .vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
28888
+ /* A mark group nested under a parent group on the Events tab: indented one step per level
28889
+ (--vela-sd-depth). The indent rides the first child (the switch) as a MARGIN \u2014 padding
28890
+ would push the switch's own tick out of its box. */
28891
+ .vela-sd-nested>*:first-child{margin-left:calc(var(--vela-sd-depth,1)*24px);}
28791
28892
  /* \u2500\u2500 mobile presentation (.vela-sd-mobile on the scrim; structural sizes are inline in open()) \u2500\u2500
28792
28893
  The tab rail becomes a burger-opened overlay sidebar; the group TOC becomes a sticky
28793
28894
  row of horizontally scrollable tabs; the instance strip scrolls instead of wrapping;
@@ -28836,6 +28937,8 @@ var SettingsDialog = class {
28836
28937
  this.hostSections = [];
28837
28938
  /** The timeline-mark groups (defined + named by marks) — one checkbox each on the Events tab. */
28838
28939
  this.markGroups = [];
28940
+ /** A group's OWN switch (what its checkbox shows) — a child under an off parent keeps its own state. */
28941
+ this.markGroupOwnVisible = () => true;
28839
28942
  this.markGroupVisible = () => true;
28840
28943
  /** The Canvas → Theme row: current app theme + where a pick is raised. The row is a
28841
28944
  * host callback, NOT a config patch — the app theme stays out of the persisted
@@ -28853,6 +28956,9 @@ var SettingsDialog = class {
28853
28956
  this.activeSection = null;
28854
28957
  /** Mobile chrome: fullscreen card, burger-opened section sidebar, TOC as top tabs. */
28855
28958
  this.mobileLayout = false;
28959
+ /** Opens/closes the mobile section sidebar of the CURRENT build (the burger in the
28960
+ * shell header outlives pane rebuilds, so it reaches the rail through here). */
28961
+ this.toggleRail = null;
28856
28962
  /** The visibility policy: setting ids hidden by the host (subtree semantics). */
28857
28963
  this.hiddenSettings = /* @__PURE__ */ new Set();
28858
28964
  if (getComputedStyle(container).position === "static") container.style.position = "relative";
@@ -28862,7 +28968,8 @@ var SettingsDialog = class {
28862
28968
  this.hostSections = sections;
28863
28969
  }
28864
28970
  /** The timeline-mark groups and their current visibility — the Events tab's rows on next open. */
28865
- setMarkGroups(groups, visible) {
28971
+ setMarkGroups(groups, visible, ownVisible = visible) {
28972
+ this.markGroupOwnVisible = ownVisible;
28866
28973
  this.markGroups = groups;
28867
28974
  this.markGroupVisible = visible;
28868
28975
  }
@@ -28928,7 +29035,6 @@ var SettingsDialog = class {
28928
29035
  this.onReset = onReset ?? null;
28929
29036
  ensureControlStyles();
28930
29037
  const mobile = this.mobileLayout;
28931
- let toggleRail = null;
28932
29038
  let burger;
28933
29039
  if (mobile) {
28934
29040
  burger = document.createElement("button");
@@ -28936,8 +29042,83 @@ var SettingsDialog = class {
28936
29042
  burger.className = "vela-sd-burger";
28937
29043
  burger.innerHTML = iconAt("burger", 16);
28938
29044
  burger.title = "Sections";
28939
- burger.addEventListener("click", () => toggleRail?.());
29045
+ burger.addEventListener("click", () => this.toggleRail?.());
29046
+ }
29047
+ const ui = new Dialog({
29048
+ host: this.container,
29049
+ title: "Chart settings",
29050
+ // Non-modal: a live-edit dialog must leave the page interactive — a modal
29051
+ // machine locks pointer events on the whole body, killing the chart, the
29052
+ // legend, and the body-portaled popovers (color picker, select lists).
29053
+ modal: false,
29054
+ contained: true,
29055
+ align: "top",
29056
+ draggable: !mobile,
29057
+ flush: true,
29058
+ className: "vela-dialog--settings",
29059
+ headerStart: burger,
29060
+ closeOnBackdrop: true,
29061
+ footer: (foot) => {
29062
+ foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
29063
+ const resetBtn = document.createElement("button");
29064
+ resetBtn.type = "button";
29065
+ resetBtn.textContent = "Reset defaults";
29066
+ resetBtn.className = "vela-sd-btn";
29067
+ resetBtn.addEventListener("click", () => this.onReset?.());
29068
+ foot.appendChild(resetBtn);
29069
+ },
29070
+ // A close signal may only close ITS OWN dialog: the machine reports the exit
29071
+ // asynchronously, so a close-then-reopen in one tick would otherwise see the
29072
+ // old instance's signal tear down the freshly opened one.
29073
+ onOpenChange: (open2) => {
29074
+ if (!open2 && this.ui === ui) this.close();
29075
+ }
29076
+ });
29077
+ if (mobile) ui.positioner.classList.add("vela-sd-mobile");
29078
+ ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
29079
+ this.root = ui.positioner;
29080
+ this.ui = ui;
29081
+ const panes = this.buildContent(ui, config, section, mobile);
29082
+ ui.show();
29083
+ this.layoutPanes(panes);
29084
+ }
29085
+ /**
29086
+ * Re-seed every control of an OPEN dialog from `config`, in place: the shell stays
29087
+ * (no close/open transition), only the tab rail and panes rebuild, landing back on
29088
+ * the current tab. The reset path — the restored values must show without the
29089
+ * dialog re-entering. No-op while closed.
29090
+ */
29091
+ refresh(config) {
29092
+ const ui = this.ui;
29093
+ if (!ui) return;
29094
+ closeOpenPopovers();
29095
+ closeWidthPopover();
29096
+ for (const dispose of this.hintTips) dispose();
29097
+ this.hintTips = [];
29098
+ this.config = config;
29099
+ ui.body.replaceChildren();
29100
+ this.layoutPanes(this.buildContent(ui, config, this.activeSection ?? void 0, this.mobileLayout));
29101
+ }
29102
+ /** Structured chart-type panes (instance strip / group TOC) own their layout and
29103
+ * tag their rows hosts instead; each host gets its own field grid. Runs on a SHOWN
29104
+ * dialog — the grids measure their labels. */
29105
+ layoutPanes(panes) {
29106
+ for (const el of panes) {
29107
+ const hosts = [...el.querySelectorAll("[data-sd-rows-host]")];
29108
+ if (hosts.length === 0) {
29109
+ this.layoutSettingsGrids(el);
29110
+ continue;
29111
+ }
29112
+ for (const h of hosts) this.layoutSettingsGrids(h);
28940
29113
  }
29114
+ }
29115
+ /**
29116
+ * Build the tab rail + one pane per section into `ui.body` (the linear `body` of
29117
+ * section markers and rows is split afterwards), select `section` (the active
29118
+ * style's own tab when none is asked for), and return the pane elements for
29119
+ * {@link layoutPanes}.
29120
+ */
29121
+ buildContent(ui, config, section, mobile) {
28941
29122
  const body = document.createElement("div");
28942
29123
  body.style.cssText = "display:flex;flex-direction:column;gap:0;";
28943
29124
  const sid = (el, id) => {
@@ -29141,9 +29322,28 @@ var SettingsDialog = class {
29141
29322
  if (this.markGroups.length > 0) {
29142
29323
  body.append(sid(this.section("Events"), MARKS_SETTINGS_ID));
29143
29324
  body.append(sid(this.sectionTitle("Visible events"), MARKS_GROUPS_SETTINGS_ID));
29144
- for (const g of this.markGroups) {
29145
- body.append(sid(this.boolRow(g.label, this.markGroupVisible(g.id), (v) => this.emit({ marks: { groups: { [g.id]: v } } })), markGroupSettingsId(g.id)));
29325
+ const rowsById = /* @__PURE__ */ new Map();
29326
+ const rows = markGroupRows(this.markGroups);
29327
+ const refreshDimming = () => {
29328
+ for (const { group } of rows) {
29329
+ const el = rowsById.get(group.id);
29330
+ if (!el || group.parent === void 0) continue;
29331
+ el.classList.toggle("vela-sd-soft", !this.markGroupVisible(group.parent));
29332
+ }
29333
+ };
29334
+ for (const { group: g, depth } of rows) {
29335
+ const row = this.boolRow(g.label, this.markGroupOwnVisible(g.id), (v) => {
29336
+ this.emit({ marks: { groups: { [g.id]: v } } });
29337
+ refreshDimming();
29338
+ });
29339
+ if (depth > 0) {
29340
+ row.classList.add("vela-sd-nested");
29341
+ row.style.setProperty("--vela-sd-depth", String(depth));
29342
+ }
29343
+ rowsById.set(g.id, row);
29344
+ body.append(sid(row, markGroupSettingsId(g.id)));
29146
29345
  }
29346
+ refreshDimming();
29147
29347
  }
29148
29348
  renderChartTypeSections("end");
29149
29349
  renderHostSections("end");
@@ -29171,8 +29371,8 @@ var SettingsDialog = class {
29171
29371
  if (mobile) {
29172
29372
  railScrim = document.createElement("div");
29173
29373
  railScrim.className = "vela-sd-railscrim";
29174
- railScrim.addEventListener("click", () => toggleRail?.());
29175
- toggleRail = (open2) => {
29374
+ railScrim.addEventListener("click", () => this.toggleRail?.());
29375
+ this.toggleRail = (open2) => {
29176
29376
  const on = open2 ?? !rail.classList.contains("open");
29177
29377
  rail.classList.toggle("open", on);
29178
29378
  railScrim?.classList.toggle("open", on);
@@ -29212,35 +29412,6 @@ var SettingsDialog = class {
29212
29412
  });
29213
29413
  if (hidActive) activate(0);
29214
29414
  };
29215
- const ui = new Dialog({
29216
- host: this.container,
29217
- title: "Chart settings",
29218
- // Non-modal: a live-edit dialog must leave the page interactive — a modal
29219
- // machine locks pointer events on the whole body, killing the chart, the
29220
- // legend, and the body-portaled popovers (color picker, select lists).
29221
- modal: false,
29222
- contained: true,
29223
- align: "top",
29224
- draggable: !mobile,
29225
- flush: true,
29226
- className: "vela-dialog--settings",
29227
- headerStart: burger,
29228
- closeOnBackdrop: true,
29229
- footer: (foot) => {
29230
- foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
29231
- const resetBtn = document.createElement("button");
29232
- resetBtn.type = "button";
29233
- resetBtn.textContent = "Reset defaults";
29234
- resetBtn.className = "vela-sd-btn";
29235
- resetBtn.addEventListener("click", () => this.onReset?.());
29236
- foot.appendChild(resetBtn);
29237
- },
29238
- onOpenChange: (open2) => {
29239
- if (!open2) this.close();
29240
- }
29241
- });
29242
- if (mobile) ui.positioner.classList.add("vela-sd-mobile");
29243
- ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
29244
29415
  const activate = (idx) => {
29245
29416
  panes.forEach((p, i) => {
29246
29417
  p.el.style.display = i === idx ? "block" : "none";
@@ -29249,7 +29420,7 @@ var SettingsDialog = class {
29249
29420
  this.activeSection = panes[idx]?.title ?? null;
29250
29421
  if (mobile) {
29251
29422
  ui.titleEl.textContent = panes[idx]?.title ?? "Chart settings";
29252
- toggleRail?.(false);
29423
+ this.toggleRail?.(false);
29253
29424
  }
29254
29425
  };
29255
29426
  panes.forEach((p, i) => {
@@ -29264,17 +29435,7 @@ var SettingsDialog = class {
29264
29435
  shell.append(rail, paneHost);
29265
29436
  if (railScrim) shell.append(railScrim);
29266
29437
  ui.body.appendChild(shell);
29267
- this.root = ui.positioner;
29268
- this.ui = ui;
29269
- ui.show();
29270
- for (const p of panes) {
29271
- const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
29272
- if (hosts.length === 0) {
29273
- this.layoutSettingsGrids(p.el);
29274
- continue;
29275
- }
29276
- for (const h of hosts) this.layoutSettingsGrids(h);
29277
- }
29438
+ return panes.map((p) => p.el);
29278
29439
  }
29279
29440
  close() {
29280
29441
  closeOpenPopovers();
@@ -29282,6 +29443,7 @@ var SettingsDialog = class {
29282
29443
  const ui = this.ui;
29283
29444
  this.ui = null;
29284
29445
  this.root = null;
29446
+ this.toggleRail = null;
29285
29447
  this.tabs = [];
29286
29448
  for (const dispose of this.hintTips) dispose();
29287
29449
  this.hintTips = [];
@@ -36600,7 +36762,11 @@ var NativeRenderer = class {
36600
36762
  }
36601
36763
  this.settingsDialog.setTheme(this.theme);
36602
36764
  this.settingsDialog.setHostSections(this.hostSettingsSections);
36603
- this.settingsDialog.setMarkGroups(this.markGroupsInUse(), (id) => markGroupVisible(this.scene.marks, id, this.scene.markGroups));
36765
+ this.settingsDialog.setMarkGroups(
36766
+ this.markGroupsForEventsTab(),
36767
+ (id) => markGroupVisible2(this.scene.marks, id, this.scene.markGroups),
36768
+ (id) => markGroupOwnVisible(this.scene.marks.groups, id, this.scene.markGroups)
36769
+ );
36604
36770
  this.settingsDialog.setHiddenSettings(this.hiddenSettings);
36605
36771
  this.syncThemeControl();
36606
36772
  this.settingsDialog.toggle(
@@ -36608,13 +36774,24 @@ var NativeRenderer = class {
36608
36774
  (patch) => this.applyConfig(patch),
36609
36775
  (json) => this.applyConfig(json),
36610
36776
  () => {
36611
- if (this.factoryConfig) this.applyConfig(factoryResetConfig(this.factoryConfig));
36612
- this.settingsDialog?.close();
36613
- this.openSettingsDialog();
36777
+ if (this.factoryConfig) this.applyConfig(this.factoryResetDocument(this.factoryConfig));
36778
+ this.settingsDialog?.refresh(this.getConfig());
36614
36779
  },
36615
36780
  section
36616
36781
  );
36617
36782
  }
36783
+ /**
36784
+ * The document "Reset defaults" applies: every setting back to its first-run value,
36785
+ * with two things that are NOT settings held or resolved here — the price style
36786
+ * stays the one the user is looking at, and the timeline-mark groups (an additive
36787
+ * merge, like the type bags) are named back to their host-declared visibility.
36788
+ */
36789
+ factoryResetDocument(factory) {
36790
+ const doc = factoryResetConfig(factory, this.scene.priceStyle);
36791
+ const groups = { ...doc.marks.groups };
36792
+ for (const g of this.markGroupsInUse()) groups[g.id] = g.visible !== false;
36793
+ return { ...doc, marks: { ...doc.marks, groups } };
36794
+ }
36618
36795
  /** Close the in-chart dialogs (indicator settings + chart-settings gear). No-op when none are open. */
36619
36796
  closeDialogs() {
36620
36797
  this.inputsUI?.closeOpenDialog();
@@ -37677,6 +37854,15 @@ var NativeRenderer = class {
37677
37854
  markGroupsInUse() {
37678
37855
  return effectiveMarkGroups(this.scene.timelineMarks, this.scene.markGroups);
37679
37856
  }
37857
+ /**
37858
+ * The groups as the Events tab lists them: nested only under a DEFINED parent. The
37859
+ * painter's visibility chain resolves parents against the defined groups alone, so a
37860
+ * parent that marks merely name must not nest (and dim) a child the painter still shows.
37861
+ */
37862
+ markGroupsForEventsTab() {
37863
+ const defined = new Set(this.scene.markGroups.map((g) => g.id));
37864
+ return this.markGroupsInUse().map((g) => g.parent !== void 0 && !defined.has(g.parent) ? { ...g, parent: void 0 } : g);
37865
+ }
37680
37866
  onViewportChange(cb) {
37681
37867
  this.viewportCbs.add(cb);
37682
37868
  return () => this.viewportCbs.delete(cb);
@@ -41817,6 +42003,7 @@ function registerClassicIndicators() {
41817
42003
  }
41818
42004
 
41819
42005
  // src/Vela.ts
42006
+ var asError = (err) => err instanceof Error ? err : new Error(String(err));
41820
42007
  var Vela = class {
41821
42008
  constructor(container, options = {}, deps = {}) {
41822
42009
  registerBuiltinChartTypes();
@@ -41940,7 +42127,12 @@ var Vela = class {
41940
42127
  * fetch from — the same relationship `script:run` has to `context:changed`.
41941
42128
  */
41942
42129
  runScript(source, options) {
41943
- const handle = this.addIndicator(source, options);
42130
+ let handle;
42131
+ try {
42132
+ handle = this.addIndicator(source, options);
42133
+ } catch (err) {
42134
+ return Promise.resolve({ ok: false, run: null, error: asError(err), onUpdate: () => () => void 0, remove: () => void 0 });
42135
+ }
41944
42136
  const updates = /* @__PURE__ */ new Set();
41945
42137
  const drop = () => {
41946
42138
  try {
@@ -41984,7 +42176,12 @@ var Vela = class {
41984
42176
  });
41985
42177
  }
41986
42178
  runIndicator(source, options) {
41987
- const handle = this.addIndicator(source, options);
42179
+ let handle;
42180
+ try {
42181
+ handle = this.addIndicator(source, options);
42182
+ } catch (err) {
42183
+ return Promise.resolve({ ok: false, handle: null, error: asError(err), context: null });
42184
+ }
41988
42185
  return new Promise((resolve) => {
41989
42186
  const offReady = handle.on("ready", () => {
41990
42187
  offReady();
@@ -43569,6 +43766,11 @@ var ChartCell = class {
43569
43766
  if (typeof zone === "string" && normalizeTimezone(zone) !== normalizeTimezone(this.deps.timezone())) {
43570
43767
  this.deps.setTimezone(normalizeTimezone(zone));
43571
43768
  }
43769
+ const style = this.priceStyle;
43770
+ if (style !== (this.state.priceStyle ?? "candles")) {
43771
+ this.state.priceStyle = style;
43772
+ this.deps.onPriceStyleChanged(this.id);
43773
+ }
43572
43774
  this.syncStatuslineColors();
43573
43775
  this.syncPlotOverlayTokens();
43574
43776
  });
@@ -44208,16 +44410,19 @@ var ChartCell = class {
44208
44410
  * a persistence handler's `restore` runs silently, a user-driven call records.
44209
44411
  */
44210
44412
  addExternalIndicator(entry) {
44413
+ const { id, inputs, props, hidden, ...script } = entry;
44211
44414
  this.addManifestInstance(
44212
- { ...entry, enabled: true },
44213
- { external: true, ...entry.inputs ? { inputs: entry.inputs } : {}, ...entry.props ? { props: entry.props } : {}, ...entry.hidden ? { hidden: true } : {} }
44415
+ { ...script, enabled: true },
44416
+ { external: true, ...id !== void 0 ? { id } : {}, ...inputs ? { inputs } : {}, ...props ? { props } : {}, ...hidden ? { hidden: true } : {} }
44214
44417
  );
44215
44418
  }
44216
44419
  /** Add ONE instance of a manifest entry (repeatable — duplicates are legitimate). */
44217
44420
  addManifestInstance(entry, opts = {}) {
44218
44421
  if (this.destroyed) return;
44219
44422
  const values = opts.inputs || opts.props ? { inputs: opts.inputs, props: opts.props } : void 0;
44220
- const it = { entry, handle: this.addToChart(entry, values), ...opts.external ? { external: true } : {}, ...values ? { values } : {} };
44423
+ const handle = this.addToChart(entry, values, opts.id);
44424
+ if (!handle) return;
44425
+ const it = { entry, handle, id: handle.id, ...opts.external ? { external: true } : {}, ...values ? { values } : {} };
44221
44426
  if (opts.hidden) it.handle?.setVisible(false);
44222
44427
  this.instances.push(it);
44223
44428
  this.deps.onIndicatorsChanged(this.id);
@@ -44226,7 +44431,7 @@ var ChartCell = class {
44226
44431
  this.history.push({
44227
44432
  undo: () => this.dropInstance(snapshot),
44228
44433
  redo: () => {
44229
- snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
44434
+ snapshot.handle = this.addToChart(snapshot.entry, snapshot.values, snapshot.id);
44230
44435
  this.instances.push(snapshot);
44231
44436
  this.deps.onIndicatorsChanged(this.id);
44232
44437
  }
@@ -44239,7 +44444,7 @@ var ChartCell = class {
44239
44444
  const snapshot = it;
44240
44445
  this.history.push({
44241
44446
  undo: () => {
44242
- snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
44447
+ snapshot.handle = this.addToChart(snapshot.entry, snapshot.values, snapshot.id);
44243
44448
  this.instances.push(snapshot);
44244
44449
  this.deps.onIndicatorsChanged(this.id);
44245
44450
  },
@@ -44329,9 +44534,10 @@ var ChartCell = class {
44329
44534
  this.deps.onIndicatorsChanged(this.id);
44330
44535
  });
44331
44536
  }
44332
- addToChart(entry, values) {
44537
+ addToChart(entry, values, id) {
44333
44538
  try {
44334
44539
  return this.inner?.addIndicator(entry.script, {
44540
+ ...id !== void 0 ? { id } : {},
44335
44541
  ...entry.language !== void 0 ? { language: entry.language } : {},
44336
44542
  ...values?.inputs ? { inputs: values.inputs } : {},
44337
44543
  ...values?.props ? { props: values.props } : {}
package/dist/widget.d.cts CHANGED
@@ -1,10 +1,10 @@
1
- import { V as Vela, W as WidgetContext, K as SidePanelButton } from './contributions-DpZeu7KP.cjs';
2
- export { C as CellStateContext, aA as DEFAULT_PANEL_ORDER, E as ExternalIndicatorEntry, O as OVERRIDABLE_TOPBAR_IDS, Q as SidePanelDescriptor, T as SidePanelHandle, X as StatePersistenceHandler, $ as SymbolRankingHook, a4 as WidgetActionDescriptor, a5 as WidgetActionTarget, a6 as WidgetAttachment, ag as registerSidePanel, ah as registerStatePersistence, ai as registerSymbolRanking, aj as registerWidgetAction, ak as registerWidgetAttachment, am as sidePanels, an as statePersistenceHandlers, ao as symbolRanking, ap as topbarActionOverride, au as unregisterSidePanel, av as unregisterStatePersistence, aw as unregisterWidgetAction, ax as unregisterWidgetAttachment, ay as widgetActions, az as widgetAttachments } from './contributions-DpZeu7KP.cjs';
3
- import { b as VelaOptions, d as MarketSession, T as ThemeName, c as VelaTheme, v as DataWindowReadout } from './options-XVpfr9HV.cjs';
4
- import { c as VelaShellOptions, W as WidgetHistory, b as RangePreset, f as WorkspaceState, j as TopbarComposition, a as StatuslinePart, P as PanelsState } from './statusline-model-Bd4qKuD6.cjs';
5
- export { B as Bottombar, k as BottombarOptions, C as CellState, g as ChartState, I as IndicatorLoader, l as IndicatorManifest, m as IndicatorManifestEntry, n as RANGE_PRESETS, R as ResolvedIndicator, o as ResolvedTopbarComposition, p as TOPBAR_BUILTIN_IDS, q as TOPBAR_DEFAULT_LEFT, r as TOPBAR_DEFAULT_RIGHT, V as VelaStorage, t as WidgetStorage, h as decodeState, i as encodeState, u as localStorageAdapter, v as pinnedTopbarActionIds, w as resolveIndicators, x as resolveTopbarComposition, s as sanitizeState, y as topbarHas } from './statusline-model-Bd4qKuD6.cjs';
1
+ import { V as Vela, W as WidgetContext, K as SidePanelButton } from './contributions-D6p1RB38.cjs';
2
+ export { C as CellStateContext, aA as DEFAULT_PANEL_ORDER, E as ExternalIndicatorEntry, O as OVERRIDABLE_TOPBAR_IDS, Q as SidePanelDescriptor, T as SidePanelHandle, X as StatePersistenceHandler, $ as SymbolRankingHook, a4 as WidgetActionDescriptor, a5 as WidgetActionTarget, a6 as WidgetAttachment, ag as registerSidePanel, ah as registerStatePersistence, ai as registerSymbolRanking, aj as registerWidgetAction, ak as registerWidgetAttachment, am as sidePanels, an as statePersistenceHandlers, ao as symbolRanking, ap as topbarActionOverride, au as unregisterSidePanel, av as unregisterStatePersistence, aw as unregisterWidgetAction, ax as unregisterWidgetAttachment, ay as widgetActions, az as widgetAttachments } from './contributions-D6p1RB38.cjs';
3
+ import { b as VelaOptions, d as MarketSession, T as ThemeName, c as VelaTheme, v as DataWindowReadout } from './options-D1AJKsn_.cjs';
4
+ import { c as VelaShellOptions, W as WidgetHistory, b as RangePreset, f as WorkspaceState, j as TopbarComposition, a as StatuslinePart, P as PanelsState } from './statusline-model-gWHrsOy3.cjs';
5
+ export { B as Bottombar, k as BottombarOptions, C as CellState, g as ChartState, I as IndicatorLoader, l as IndicatorManifest, m as IndicatorManifestEntry, n as RANGE_PRESETS, R as ResolvedIndicator, o as ResolvedTopbarComposition, p as TOPBAR_BUILTIN_IDS, q as TOPBAR_DEFAULT_LEFT, r as TOPBAR_DEFAULT_RIGHT, V as VelaStorage, t as WidgetStorage, h as decodeState, i as encodeState, u as localStorageAdapter, v as pinnedTopbarActionIds, w as resolveIndicators, x as resolveTopbarComposition, s as sanitizeState, y as topbarHas } from './statusline-model-gWHrsOy3.cjs';
6
6
  import { K as KeymapManager } from './keymap-CGOz5F5f.cjs';
7
- import { a as SymbolDescriptor } from './DataProvider-RF1axuy1.cjs';
7
+ import { a as SymbolDescriptor } from './DataProvider-CGa4KHRa.cjs';
8
8
  import { d as SidePanel } from './side-panel-HF0IAzwf.cjs';
9
9
  export { D as DEFAULT_PANEL_MAX_WIDTH, a as DEFAULT_PANEL_MIN_WIDTH, b as DEFAULT_PANEL_WIDTH, S as SidePanelOptions, c as clampPanelWidth } from './side-panel-HF0IAzwf.cjs';
10
10