@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,5 +1,5 @@
1
- import { x as Millis, an as InputSchema, al as IndicatorMeta, P as PriceStyle, O as OHLCV, I as InputValue, l as IndicatorModel, k as MoveTarget, aH as PaneAxis, aW as SeriesSpec, aj as Fill, G as Background, aM as PriceLine, a6 as DrawingLine, a2 as DrawingBox, a5 as DrawingLabel, aa as DrawingPolyline, a7 as DrawingLinefill, af as DrawingTable, h as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, z as DrawingTypeKey, y as SnapMode, a8 as DrawingMode, u as MarkClickEvent, e as IChartRenderer, R as RendererCapabilities, o as LegendActionView, p as LegendCalloutView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, v as DataWindowReadout, n as SymbolPickerFn, W as WallClock, bc as PaneInfo, ai as DrawingsOption, ac as DrawingSeriesGateway, D as Drawing, s as TimelineMark, t as MarkGroup, b as VelaOptions, E as AddIndicatorOptions, aG as MarketSwitch, aF as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-XVpfr9HV.js';
2
- import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-CVpN5sCq.js';
1
+ import { x as Millis, an as InputSchema, al as IndicatorMeta, P as PriceStyle, O as OHLCV, I as InputValue, l as IndicatorModel, k as MoveTarget, aH as PaneAxis, aW as SeriesSpec, aj as Fill, G as Background, aM as PriceLine, a6 as DrawingLine, a2 as DrawingBox, a5 as DrawingLabel, aa as DrawingPolyline, a7 as DrawingLinefill, af as DrawingTable, h as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, z as DrawingTypeKey, y as SnapMode, a8 as DrawingMode, u as MarkClickEvent, e as IChartRenderer, R as RendererCapabilities, o as LegendActionView, p as LegendCalloutView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, v as DataWindowReadout, n as SymbolPickerFn, W as WallClock, bc as PaneInfo, ai as DrawingsOption, ac as DrawingSeriesGateway, D as Drawing, s as TimelineMark, t as MarkGroup, b as VelaOptions, E as AddIndicatorOptions, aG as MarketSwitch, aF as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-D1AJKsn_.js';
2
+ import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-CmWUtDVL.js';
3
3
 
4
4
  /**
5
5
  * A strategy's broker state at ONE bar — the flat summary a host reads while a script
@@ -1391,7 +1391,11 @@ declare class MarksController {
1391
1391
  * a persisted chart restores — so it warns + no-ops on a renderer without it.
1392
1392
  */
1393
1393
  setGroupVisible(id: string, visible: boolean): void;
1394
- /** A group's effective visibility: the user's (persisted) choice, else the group's declared default, else visible. */
1394
+ /**
1395
+ * A group's effective visibility: its own switch — the user's (persisted) choice, else
1396
+ * the declared default, else visible — AND every ancestor's, so a child under a
1397
+ * switched-off parent reads hidden whatever its own choice says.
1398
+ */
1395
1399
  isGroupVisible(id: string): boolean;
1396
1400
  destroy(): void;
1397
1401
  private sync;
@@ -1680,6 +1684,12 @@ interface ExternalIndicatorEntry {
1680
1684
  name: string;
1681
1685
  /** The script source (the recorded undo/redo action re-adds from it). */
1682
1686
  script: string;
1687
+ /** The indicator's id on the chart (see `AddIndicatorOptions.id`) — what a persistence
1688
+ * handler stores and hands back on restore so the indicator comes back under the id
1689
+ * its document knows. Omitted: the chart mints one. Either way undo/redo re-add the
1690
+ * indicator under the id it first ran with. An id already live on the cell's chart
1691
+ * is rejected (a warning; nothing is added). */
1692
+ id?: string;
1683
1693
  /** Engine language (default: the chart's default engine). */
1684
1694
  language?: string;
1685
1695
  /** Input-value overrides applied at add time (what a persistence handler restores). */
@@ -1,5 +1,5 @@
1
- import { x as Millis, an as InputSchema, al as IndicatorMeta, P as PriceStyle, O as OHLCV, I as InputValue, l as IndicatorModel, k as MoveTarget, aH as PaneAxis, aW as SeriesSpec, aj as Fill, G as Background, aM as PriceLine, a6 as DrawingLine, a2 as DrawingBox, a5 as DrawingLabel, aa as DrawingPolyline, a7 as DrawingLinefill, af as DrawingTable, h as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, z as DrawingTypeKey, y as SnapMode, a8 as DrawingMode, u as MarkClickEvent, e as IChartRenderer, R as RendererCapabilities, o as LegendActionView, p as LegendCalloutView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, v as DataWindowReadout, n as SymbolPickerFn, W as WallClock, bc as PaneInfo, ai as DrawingsOption, ac as DrawingSeriesGateway, D as Drawing, s as TimelineMark, t as MarkGroup, b as VelaOptions, E as AddIndicatorOptions, aG as MarketSwitch, aF as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-XVpfr9HV.cjs';
2
- import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-RF1axuy1.cjs';
1
+ import { x as Millis, an as InputSchema, al as IndicatorMeta, P as PriceStyle, O as OHLCV, I as InputValue, l as IndicatorModel, k as MoveTarget, aH as PaneAxis, aW as SeriesSpec, aj as Fill, G as Background, aM as PriceLine, a6 as DrawingLine, a2 as DrawingBox, a5 as DrawingLabel, aa as DrawingPolyline, a7 as DrawingLinefill, af as DrawingTable, h as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, z as DrawingTypeKey, y as SnapMode, a8 as DrawingMode, u as MarkClickEvent, e as IChartRenderer, R as RendererCapabilities, o as LegendActionView, p as LegendCalloutView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, v as DataWindowReadout, n as SymbolPickerFn, W as WallClock, bc as PaneInfo, ai as DrawingsOption, ac as DrawingSeriesGateway, D as Drawing, s as TimelineMark, t as MarkGroup, b as VelaOptions, E as AddIndicatorOptions, aG as MarketSwitch, aF as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-D1AJKsn_.cjs';
2
+ import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-CGa4KHRa.cjs';
3
3
 
4
4
  /**
5
5
  * A strategy's broker state at ONE bar — the flat summary a host reads while a script
@@ -1391,7 +1391,11 @@ declare class MarksController {
1391
1391
  * a persisted chart restores — so it warns + no-ops on a renderer without it.
1392
1392
  */
1393
1393
  setGroupVisible(id: string, visible: boolean): void;
1394
- /** A group's effective visibility: the user's (persisted) choice, else the group's declared default, else visible. */
1394
+ /**
1395
+ * A group's effective visibility: its own switch — the user's (persisted) choice, else
1396
+ * the declared default, else visible — AND every ancestor's, so a child under a
1397
+ * switched-off parent reads hidden whatever its own choice says.
1398
+ */
1395
1399
  isGroupVisible(id: string): boolean;
1396
1400
  destroy(): void;
1397
1401
  private sync;
@@ -1680,6 +1684,12 @@ interface ExternalIndicatorEntry {
1680
1684
  name: string;
1681
1685
  /** The script source (the recorded undo/redo action re-adds from it). */
1682
1686
  script: string;
1687
+ /** The indicator's id on the chart (see `AddIndicatorOptions.id`) — what a persistence
1688
+ * handler stores and hands back on restore so the indicator comes back under the id
1689
+ * its document knows. Omitted: the chart mints one. Either way undo/redo re-add the
1690
+ * indicator under the id it first ran with. An id already live on the cell's chart
1691
+ * is rejected (a warning; nothing is added). */
1692
+ id?: string;
1683
1693
  /** Engine language (default: the chart's default engine). */
1684
1694
  language?: string;
1685
1695
  /** Input-value overrides applied at add time (what a persistence handler restores). */
package/dist/index.cjs CHANGED
@@ -129,10 +129,18 @@ var IndicatorRegistry = class {
129
129
  this.records = /* @__PURE__ */ new Map();
130
130
  this.counter = 0;
131
131
  }
132
- /** Allocate a unique, stable per-instance id. */
133
- nextId(prefix = "ind") {
134
- this.counter += 1;
135
- return `${prefix}-${this.counter}`;
132
+ /**
133
+ * Mint a unique per-instance id. Host-supplied ids share the namespace, so a minted
134
+ * id skips anything already recorded — and anything `taken` reports live elsewhere
135
+ * (a handle the orchestrator holds outside the records, e.g. a fail-soft native).
136
+ */
137
+ nextId(prefix = "ind", taken = () => false) {
138
+ let id;
139
+ do {
140
+ this.counter += 1;
141
+ id = `${prefix}-${this.counter}`;
142
+ } while (this.records.has(id) || taken(id));
143
+ return id;
136
144
  }
137
145
  add(record) {
138
146
  this.records.set(record.id, record);
@@ -7140,6 +7148,42 @@ var DrawingController = class {
7140
7148
  }
7141
7149
  };
7142
7150
 
7151
+ // src/core/marks/visibility.ts
7152
+ function markGroupOwnVisible(choices, groupId, groups) {
7153
+ const chosen = choices[groupId];
7154
+ if (typeof chosen === "boolean") return chosen;
7155
+ return groups.find((g) => g.id === groupId)?.visible !== false;
7156
+ }
7157
+ function markGroupVisible(choices, groupId, groups) {
7158
+ if (groupId === void 0) return true;
7159
+ const seen = /* @__PURE__ */ new Set();
7160
+ let id = groupId;
7161
+ while (id !== void 0 && !seen.has(id)) {
7162
+ seen.add(id);
7163
+ if (!markGroupOwnVisible(choices, id, groups)) return false;
7164
+ const parent = groups.find((g) => g.id === id)?.parent;
7165
+ id = parent !== void 0 && groups.some((g) => g.id === parent) ? parent : void 0;
7166
+ }
7167
+ return true;
7168
+ }
7169
+ function markGroupRows(groups) {
7170
+ const ids = new Set(groups.map((g) => g.id));
7171
+ const out = [];
7172
+ const placed = /* @__PURE__ */ new Set();
7173
+ const place = (group, depth) => {
7174
+ if (placed.has(group.id)) return;
7175
+ placed.add(group.id);
7176
+ out.push({ group, depth });
7177
+ for (const child of groups) if (child.parent === group.id && child.id !== group.id) place(child, depth + 1);
7178
+ };
7179
+ for (const g of groups) {
7180
+ const parentKnown = g.parent !== void 0 && ids.has(g.parent) && g.parent !== g.id;
7181
+ if (!parentKnown) place(g, 0);
7182
+ }
7183
+ for (const g of groups) place(g, 0);
7184
+ return out;
7185
+ }
7186
+
7143
7187
  // src/core/marks/MarksController.ts
7144
7188
  var MarksController = class {
7145
7189
  constructor(renderer, events) {
@@ -7184,6 +7228,7 @@ var MarksController = class {
7184
7228
  defineGroup(group) {
7185
7229
  if (!group || typeof group.id !== "string" || group.id.length === 0) throw new Error("[vela] marks.defineGroup: `id` must be a non-empty string");
7186
7230
  if (typeof group.label !== "string") throw new Error(`[vela] marks.defineGroup: group "${group.id}" needs a string \`label\``);
7231
+ 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`);
7187
7232
  this.groups.set(group.id, { ...group });
7188
7233
  this.sync();
7189
7234
  }
@@ -7203,12 +7248,16 @@ var MarksController = class {
7203
7248
  }
7204
7249
  this.renderer.applyFeature("marks", { groups: { [id]: visible } });
7205
7250
  }
7206
- /** A group's effective visibility: the user's (persisted) choice, else the group's declared default, else visible. */
7251
+ /**
7252
+ * A group's effective visibility: its own switch — the user's (persisted) choice, else
7253
+ * the declared default, else visible — AND every ancestor's, so a child under a
7254
+ * switched-off parent reads hidden whatever its own choice says.
7255
+ */
7207
7256
  isGroupVisible(id) {
7208
7257
  const state = this.renderer.readFeature("marks");
7209
- const chosen = state?.groups?.[id];
7210
- if (typeof chosen === "boolean") return chosen;
7211
- return this.groups.get(id)?.visible !== false;
7258
+ const groups = {};
7259
+ for (const [gid, v] of Object.entries(state?.groups ?? {})) if (typeof v === "boolean") groups[gid] = v;
7260
+ return markGroupVisible(groups, id, [...this.groups.values()]);
7212
7261
  }
7213
7262
  destroy() {
7214
7263
  for (const unsub of this.subs) unsub();
@@ -8636,7 +8685,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
8636
8685
  this.engines.set(language, engine);
8637
8686
  }
8638
8687
  addIndicator(source, options = {}) {
8639
- const id = this.registry.nextId();
8688
+ const id = this.claimIndicatorId(options.id);
8640
8689
  const title = options.title ?? "Indicator";
8641
8690
  const handle = new IndicatorHandleImpl(id, title, this, source);
8642
8691
  this.handles.set(id, handle);
@@ -8658,7 +8707,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
8658
8707
  const existing = this.registry.all().find((r) => r.native?.type === type);
8659
8708
  if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
8660
8709
  }
8661
- const id = this.registry.nextId("native");
8710
+ const id = this.registry.nextId("native", (candidate) => this.handles.has(candidate));
8662
8711
  const title = descriptor?.title ?? type;
8663
8712
  const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
8664
8713
  this.handles.set(id, handle);
@@ -8936,6 +8985,24 @@ var EngineOrchestrator = class _EngineOrchestrator {
8936
8985
  this.events.clear();
8937
8986
  }
8938
8987
  // ── internals ───────────────────────────────────────────────
8988
+ /**
8989
+ * The id a new script indicator runs under: the host's when supplied, else a minted
8990
+ * one. A host id is opaque but must be a non-empty string, and it must not be live
8991
+ * on this chart — a duplicate is a programming error surfaced synchronously, never
8992
+ * renamed behind the caller's back (the whole point of supplying one is that
8993
+ * `handle.id` equals what was passed). "Live" reads both the records and the handle
8994
+ * map: a fail-soft handle never enters the registry but still owns its id.
8995
+ */
8996
+ claimIndicatorId(requested) {
8997
+ if (requested === void 0) return this.registry.nextId("ind", (candidate) => this.handles.has(candidate));
8998
+ if (typeof requested !== "string" || requested.length === 0) {
8999
+ throw new TypeError("[vela] addIndicator: `id` must be a non-empty string");
9000
+ }
9001
+ if (this.handles.has(requested) || this.registry.get(requested)) {
9002
+ throw new Error(`[vela] addIndicator: indicator id "${requested}" is already live on this chart`);
9003
+ }
9004
+ return requested;
9005
+ }
8939
9006
  async startIndicator(id, source, options, handle) {
8940
9007
  try {
8941
9008
  await this.readyPromise;
@@ -15958,6 +16025,17 @@ function hasOwnCandlePaint(style) {
15958
16025
  for (const t of chartTypes()) if (t.id === style) return (t.basePainting ?? "candles") === "candles";
15959
16026
  return false;
15960
16027
  }
16028
+ var CANDLE_OVERRIDE_KEYS = [
16029
+ "candleUpColor",
16030
+ "candleDownColor",
16031
+ "candleBodyVisible",
16032
+ "candleBorderVisible",
16033
+ "candleBorderUpColor",
16034
+ "candleBorderDownColor",
16035
+ "candleWickVisible",
16036
+ "candleWickUpColor",
16037
+ "candleWickDownColor"
16038
+ ];
15961
16039
  function candleOverrideFor(style, bags) {
15962
16040
  if (!hasOwnCandlePaint(style)) return null;
15963
16041
  const bag = bags[style] ?? {};
@@ -16029,7 +16107,7 @@ function clampLevel(v) {
16029
16107
  function clampSpacing(v) {
16030
16108
  return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
16031
16109
  }
16032
- function factoryResetConfig(factory) {
16110
+ function factoryResetConfig(factory, priceStyle = factory.series.style) {
16033
16111
  const bag = {};
16034
16112
  for (const t of chartTypes()) {
16035
16113
  const section = t.settings;
@@ -16051,10 +16129,16 @@ function factoryResetConfig(factory) {
16051
16129
  if (!section.instances) addRows(section.rows);
16052
16130
  bag[t.id] = defaults2;
16053
16131
  }
16132
+ for (const t of chartTypes()) {
16133
+ if (!hasOwnCandlePaint(t.id)) continue;
16134
+ const defaults2 = bag[t.id] ?? {};
16135
+ for (const key of CANDLE_OVERRIDE_KEYS) defaults2[key] = null;
16136
+ bag[t.id] = defaults2;
16137
+ }
16054
16138
  for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
16055
16139
  bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
16056
16140
  }
16057
- return { ...factory, chartTypes: bag };
16141
+ return { ...factory, chartTypes: bag, series: { ...factory.series, style: priceStyle } };
16058
16142
  }
16059
16143
  function mergeConfig(base, patch) {
16060
16144
  const p = asObject(patch);
@@ -18433,11 +18517,8 @@ function mergeMarksState(base, patch) {
18433
18517
  for (const [id, v] of Object.entries(g)) if (typeof v === "boolean") groups[id] = v;
18434
18518
  return { visible: typeof p.visible === "boolean" ? p.visible : base.visible, groups };
18435
18519
  }
18436
- function markGroupVisible(state, groupId, groups) {
18437
- if (groupId === void 0) return true;
18438
- const chosen = state.groups[groupId];
18439
- if (typeof chosen === "boolean") return chosen;
18440
- return groups.find((g) => g.id === groupId)?.visible !== false;
18520
+ function markGroupVisible2(state, groupId, groups) {
18521
+ return markGroupVisible(state.groups, groupId, groups);
18441
18522
  }
18442
18523
 
18443
18524
  // src/renderers/native/core/SceneGraph.ts
@@ -20613,13 +20694,29 @@ function paintMarkLane(ctx, layout, deps) {
20613
20694
  const img = deps.icons.get(mark.glyph.icon, ink, symbolPx, deps.dpr);
20614
20695
  if (img) ctx.drawImage(img, center.x - symbolPx / 2, center.y - symbolPx / 2, symbolPx, symbolPx);
20615
20696
  } else if (mark.glyph.letter) {
20697
+ const letter = mark.glyph.letter.slice(0, 2);
20616
20698
  ctx.fillStyle = ink;
20617
- ctx.font = `600 ${Math.round(size * 0.58)}px ${deps.fontFamily}`;
20618
- ctx.fillText(mark.glyph.letter.slice(0, 2), center.x, center.y + 0.5);
20699
+ let px = letterFontPx(size, letter);
20700
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
20701
+ if (letter.length > 1) {
20702
+ px = fitLetterPx(px, ctx.measureText(letter).width, symbolInnerWidth(shape, size));
20703
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
20704
+ }
20705
+ ctx.fillText(letter, center.x, center.y + 0.5);
20619
20706
  }
20620
20707
  }
20621
20708
  ctx.restore();
20622
20709
  }
20710
+ function letterFontPx(size, letter) {
20711
+ return Math.round(size * (letter.length > 1 ? 0.38 : 0.58));
20712
+ }
20713
+ function symbolInnerWidth(shape, size) {
20714
+ return (shape === "pin" ? size * 0.82 : size) - 3;
20715
+ }
20716
+ function fitLetterPx(px, width, inner) {
20717
+ if (!(width > inner) || !(width > 0)) return px;
20718
+ return Math.max(4, Math.floor(px * inner / width));
20719
+ }
20623
20720
  function traceShape(ctx, shape, x, y, size) {
20624
20721
  const r = size / 2;
20625
20722
  ctx.beginPath();
@@ -20821,7 +20918,7 @@ var ChromeRenderer = class {
20821
20918
  this.markLayout = layoutMarkLane({
20822
20919
  marks: scene.timelineMarks,
20823
20920
  groups: scene.markGroups,
20824
- hidden: (groupId) => !markGroupVisible(scene.marks, groupId, scene.markGroups),
20921
+ hidden: (groupId) => !markGroupVisible2(scene.marks, groupId, scene.markGroups),
20825
20922
  barTimes: this.barTimes(scene),
20826
20923
  intervalMs: coords.barInterval,
20827
20924
  xOf: (bar) => coords.logicalToX(bar),
@@ -21588,6 +21685,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
21588
21685
  muted and non-interactive. Applied to each row's children so it survives display:contents;
21589
21686
  !important beats the inline opacity on labels. */
21590
21687
  .vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
21688
+ /* A mark group nested under a parent group on the Events tab: indented one step per level
21689
+ (--vela-sd-depth). The indent rides the first child (the switch) as a MARGIN \u2014 padding
21690
+ would push the switch's own tick out of its box. */
21691
+ .vela-sd-nested>*:first-child{margin-left:calc(var(--vela-sd-depth,1)*24px);}
21591
21692
  /* \u2500\u2500 mobile presentation (.vela-sd-mobile on the scrim; structural sizes are inline in open()) \u2500\u2500
21592
21693
  The tab rail becomes a burger-opened overlay sidebar; the group TOC becomes a sticky
21593
21694
  row of horizontally scrollable tabs; the instance strip scrolls instead of wrapping;
@@ -21636,6 +21737,8 @@ var SettingsDialog = class {
21636
21737
  this.hostSections = [];
21637
21738
  /** The timeline-mark groups (defined + named by marks) — one checkbox each on the Events tab. */
21638
21739
  this.markGroups = [];
21740
+ /** A group's OWN switch (what its checkbox shows) — a child under an off parent keeps its own state. */
21741
+ this.markGroupOwnVisible = () => true;
21639
21742
  this.markGroupVisible = () => true;
21640
21743
  /** The Canvas → Theme row: current app theme + where a pick is raised. The row is a
21641
21744
  * host callback, NOT a config patch — the app theme stays out of the persisted
@@ -21653,6 +21756,9 @@ var SettingsDialog = class {
21653
21756
  this.activeSection = null;
21654
21757
  /** Mobile chrome: fullscreen card, burger-opened section sidebar, TOC as top tabs. */
21655
21758
  this.mobileLayout = false;
21759
+ /** Opens/closes the mobile section sidebar of the CURRENT build (the burger in the
21760
+ * shell header outlives pane rebuilds, so it reaches the rail through here). */
21761
+ this.toggleRail = null;
21656
21762
  /** The visibility policy: setting ids hidden by the host (subtree semantics). */
21657
21763
  this.hiddenSettings = /* @__PURE__ */ new Set();
21658
21764
  if (getComputedStyle(container).position === "static") container.style.position = "relative";
@@ -21662,7 +21768,8 @@ var SettingsDialog = class {
21662
21768
  this.hostSections = sections;
21663
21769
  }
21664
21770
  /** The timeline-mark groups and their current visibility — the Events tab's rows on next open. */
21665
- setMarkGroups(groups, visible) {
21771
+ setMarkGroups(groups, visible, ownVisible = visible) {
21772
+ this.markGroupOwnVisible = ownVisible;
21666
21773
  this.markGroups = groups;
21667
21774
  this.markGroupVisible = visible;
21668
21775
  }
@@ -21728,7 +21835,6 @@ var SettingsDialog = class {
21728
21835
  this.onReset = onReset ?? null;
21729
21836
  ensureControlStyles();
21730
21837
  const mobile = this.mobileLayout;
21731
- let toggleRail = null;
21732
21838
  let burger;
21733
21839
  if (mobile) {
21734
21840
  burger = document.createElement("button");
@@ -21736,8 +21842,83 @@ var SettingsDialog = class {
21736
21842
  burger.className = "vela-sd-burger";
21737
21843
  burger.innerHTML = iconAt("burger", 16);
21738
21844
  burger.title = "Sections";
21739
- burger.addEventListener("click", () => toggleRail?.());
21845
+ burger.addEventListener("click", () => this.toggleRail?.());
21846
+ }
21847
+ const ui = new Dialog({
21848
+ host: this.container,
21849
+ title: "Chart settings",
21850
+ // Non-modal: a live-edit dialog must leave the page interactive — a modal
21851
+ // machine locks pointer events on the whole body, killing the chart, the
21852
+ // legend, and the body-portaled popovers (color picker, select lists).
21853
+ modal: false,
21854
+ contained: true,
21855
+ align: "top",
21856
+ draggable: !mobile,
21857
+ flush: true,
21858
+ className: "vela-dialog--settings",
21859
+ headerStart: burger,
21860
+ closeOnBackdrop: true,
21861
+ footer: (foot) => {
21862
+ foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
21863
+ const resetBtn = document.createElement("button");
21864
+ resetBtn.type = "button";
21865
+ resetBtn.textContent = "Reset defaults";
21866
+ resetBtn.className = "vela-sd-btn";
21867
+ resetBtn.addEventListener("click", () => this.onReset?.());
21868
+ foot.appendChild(resetBtn);
21869
+ },
21870
+ // A close signal may only close ITS OWN dialog: the machine reports the exit
21871
+ // asynchronously, so a close-then-reopen in one tick would otherwise see the
21872
+ // old instance's signal tear down the freshly opened one.
21873
+ onOpenChange: (open2) => {
21874
+ if (!open2 && this.ui === ui) this.close();
21875
+ }
21876
+ });
21877
+ if (mobile) ui.positioner.classList.add("vela-sd-mobile");
21878
+ ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
21879
+ this.root = ui.positioner;
21880
+ this.ui = ui;
21881
+ const panes = this.buildContent(ui, config, section, mobile);
21882
+ ui.show();
21883
+ this.layoutPanes(panes);
21884
+ }
21885
+ /**
21886
+ * Re-seed every control of an OPEN dialog from `config`, in place: the shell stays
21887
+ * (no close/open transition), only the tab rail and panes rebuild, landing back on
21888
+ * the current tab. The reset path — the restored values must show without the
21889
+ * dialog re-entering. No-op while closed.
21890
+ */
21891
+ refresh(config) {
21892
+ const ui = this.ui;
21893
+ if (!ui) return;
21894
+ closeOpenPopovers();
21895
+ closeWidthPopover();
21896
+ for (const dispose of this.hintTips) dispose();
21897
+ this.hintTips = [];
21898
+ this.config = config;
21899
+ ui.body.replaceChildren();
21900
+ this.layoutPanes(this.buildContent(ui, config, this.activeSection ?? void 0, this.mobileLayout));
21901
+ }
21902
+ /** Structured chart-type panes (instance strip / group TOC) own their layout and
21903
+ * tag their rows hosts instead; each host gets its own field grid. Runs on a SHOWN
21904
+ * dialog — the grids measure their labels. */
21905
+ layoutPanes(panes) {
21906
+ for (const el of panes) {
21907
+ const hosts = [...el.querySelectorAll("[data-sd-rows-host]")];
21908
+ if (hosts.length === 0) {
21909
+ this.layoutSettingsGrids(el);
21910
+ continue;
21911
+ }
21912
+ for (const h of hosts) this.layoutSettingsGrids(h);
21740
21913
  }
21914
+ }
21915
+ /**
21916
+ * Build the tab rail + one pane per section into `ui.body` (the linear `body` of
21917
+ * section markers and rows is split afterwards), select `section` (the active
21918
+ * style's own tab when none is asked for), and return the pane elements for
21919
+ * {@link layoutPanes}.
21920
+ */
21921
+ buildContent(ui, config, section, mobile) {
21741
21922
  const body = document.createElement("div");
21742
21923
  body.style.cssText = "display:flex;flex-direction:column;gap:0;";
21743
21924
  const sid = (el, id) => {
@@ -21941,9 +22122,28 @@ var SettingsDialog = class {
21941
22122
  if (this.markGroups.length > 0) {
21942
22123
  body.append(sid(this.section("Events"), MARKS_SETTINGS_ID));
21943
22124
  body.append(sid(this.sectionTitle("Visible events"), MARKS_GROUPS_SETTINGS_ID));
21944
- for (const g of this.markGroups) {
21945
- body.append(sid(this.boolRow(g.label, this.markGroupVisible(g.id), (v) => this.emit({ marks: { groups: { [g.id]: v } } })), markGroupSettingsId(g.id)));
22125
+ const rowsById = /* @__PURE__ */ new Map();
22126
+ const rows = markGroupRows(this.markGroups);
22127
+ const refreshDimming = () => {
22128
+ for (const { group } of rows) {
22129
+ const el = rowsById.get(group.id);
22130
+ if (!el || group.parent === void 0) continue;
22131
+ el.classList.toggle("vela-sd-soft", !this.markGroupVisible(group.parent));
22132
+ }
22133
+ };
22134
+ for (const { group: g, depth } of rows) {
22135
+ const row = this.boolRow(g.label, this.markGroupOwnVisible(g.id), (v) => {
22136
+ this.emit({ marks: { groups: { [g.id]: v } } });
22137
+ refreshDimming();
22138
+ });
22139
+ if (depth > 0) {
22140
+ row.classList.add("vela-sd-nested");
22141
+ row.style.setProperty("--vela-sd-depth", String(depth));
22142
+ }
22143
+ rowsById.set(g.id, row);
22144
+ body.append(sid(row, markGroupSettingsId(g.id)));
21946
22145
  }
22146
+ refreshDimming();
21947
22147
  }
21948
22148
  renderChartTypeSections("end");
21949
22149
  renderHostSections("end");
@@ -21971,8 +22171,8 @@ var SettingsDialog = class {
21971
22171
  if (mobile) {
21972
22172
  railScrim = document.createElement("div");
21973
22173
  railScrim.className = "vela-sd-railscrim";
21974
- railScrim.addEventListener("click", () => toggleRail?.());
21975
- toggleRail = (open2) => {
22174
+ railScrim.addEventListener("click", () => this.toggleRail?.());
22175
+ this.toggleRail = (open2) => {
21976
22176
  const on = open2 ?? !rail.classList.contains("open");
21977
22177
  rail.classList.toggle("open", on);
21978
22178
  railScrim?.classList.toggle("open", on);
@@ -22012,35 +22212,6 @@ var SettingsDialog = class {
22012
22212
  });
22013
22213
  if (hidActive) activate(0);
22014
22214
  };
22015
- const ui = new Dialog({
22016
- host: this.container,
22017
- title: "Chart settings",
22018
- // Non-modal: a live-edit dialog must leave the page interactive — a modal
22019
- // machine locks pointer events on the whole body, killing the chart, the
22020
- // legend, and the body-portaled popovers (color picker, select lists).
22021
- modal: false,
22022
- contained: true,
22023
- align: "top",
22024
- draggable: !mobile,
22025
- flush: true,
22026
- className: "vela-dialog--settings",
22027
- headerStart: burger,
22028
- closeOnBackdrop: true,
22029
- footer: (foot) => {
22030
- foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
22031
- const resetBtn = document.createElement("button");
22032
- resetBtn.type = "button";
22033
- resetBtn.textContent = "Reset defaults";
22034
- resetBtn.className = "vela-sd-btn";
22035
- resetBtn.addEventListener("click", () => this.onReset?.());
22036
- foot.appendChild(resetBtn);
22037
- },
22038
- onOpenChange: (open2) => {
22039
- if (!open2) this.close();
22040
- }
22041
- });
22042
- if (mobile) ui.positioner.classList.add("vela-sd-mobile");
22043
- ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
22044
22215
  const activate = (idx) => {
22045
22216
  panes.forEach((p, i) => {
22046
22217
  p.el.style.display = i === idx ? "block" : "none";
@@ -22049,7 +22220,7 @@ var SettingsDialog = class {
22049
22220
  this.activeSection = panes[idx]?.title ?? null;
22050
22221
  if (mobile) {
22051
22222
  ui.titleEl.textContent = panes[idx]?.title ?? "Chart settings";
22052
- toggleRail?.(false);
22223
+ this.toggleRail?.(false);
22053
22224
  }
22054
22225
  };
22055
22226
  panes.forEach((p, i) => {
@@ -22064,17 +22235,7 @@ var SettingsDialog = class {
22064
22235
  shell.append(rail, paneHost);
22065
22236
  if (railScrim) shell.append(railScrim);
22066
22237
  ui.body.appendChild(shell);
22067
- this.root = ui.positioner;
22068
- this.ui = ui;
22069
- ui.show();
22070
- for (const p of panes) {
22071
- const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
22072
- if (hosts.length === 0) {
22073
- this.layoutSettingsGrids(p.el);
22074
- continue;
22075
- }
22076
- for (const h of hosts) this.layoutSettingsGrids(h);
22077
- }
22238
+ return panes.map((p) => p.el);
22078
22239
  }
22079
22240
  close() {
22080
22241
  closeOpenPopovers();
@@ -22082,6 +22243,7 @@ var SettingsDialog = class {
22082
22243
  const ui = this.ui;
22083
22244
  this.ui = null;
22084
22245
  this.root = null;
22246
+ this.toggleRail = null;
22085
22247
  this.tabs = [];
22086
22248
  for (const dispose of this.hintTips) dispose();
22087
22249
  this.hintTips = [];
@@ -30181,7 +30343,11 @@ var NativeRenderer = class {
30181
30343
  }
30182
30344
  this.settingsDialog.setTheme(this.theme);
30183
30345
  this.settingsDialog.setHostSections(this.hostSettingsSections);
30184
- this.settingsDialog.setMarkGroups(this.markGroupsInUse(), (id) => markGroupVisible(this.scene.marks, id, this.scene.markGroups));
30346
+ this.settingsDialog.setMarkGroups(
30347
+ this.markGroupsForEventsTab(),
30348
+ (id) => markGroupVisible2(this.scene.marks, id, this.scene.markGroups),
30349
+ (id) => markGroupOwnVisible(this.scene.marks.groups, id, this.scene.markGroups)
30350
+ );
30185
30351
  this.settingsDialog.setHiddenSettings(this.hiddenSettings);
30186
30352
  this.syncThemeControl();
30187
30353
  this.settingsDialog.toggle(
@@ -30189,13 +30355,24 @@ var NativeRenderer = class {
30189
30355
  (patch) => this.applyConfig(patch),
30190
30356
  (json) => this.applyConfig(json),
30191
30357
  () => {
30192
- if (this.factoryConfig) this.applyConfig(factoryResetConfig(this.factoryConfig));
30193
- this.settingsDialog?.close();
30194
- this.openSettingsDialog();
30358
+ if (this.factoryConfig) this.applyConfig(this.factoryResetDocument(this.factoryConfig));
30359
+ this.settingsDialog?.refresh(this.getConfig());
30195
30360
  },
30196
30361
  section
30197
30362
  );
30198
30363
  }
30364
+ /**
30365
+ * The document "Reset defaults" applies: every setting back to its first-run value,
30366
+ * with two things that are NOT settings held or resolved here — the price style
30367
+ * stays the one the user is looking at, and the timeline-mark groups (an additive
30368
+ * merge, like the type bags) are named back to their host-declared visibility.
30369
+ */
30370
+ factoryResetDocument(factory) {
30371
+ const doc = factoryResetConfig(factory, this.scene.priceStyle);
30372
+ const groups = { ...doc.marks.groups };
30373
+ for (const g of this.markGroupsInUse()) groups[g.id] = g.visible !== false;
30374
+ return { ...doc, marks: { ...doc.marks, groups } };
30375
+ }
30199
30376
  /** Close the in-chart dialogs (indicator settings + chart-settings gear). No-op when none are open. */
30200
30377
  closeDialogs() {
30201
30378
  this.inputsUI?.closeOpenDialog();
@@ -31258,6 +31435,15 @@ var NativeRenderer = class {
31258
31435
  markGroupsInUse() {
31259
31436
  return effectiveMarkGroups(this.scene.timelineMarks, this.scene.markGroups);
31260
31437
  }
31438
+ /**
31439
+ * The groups as the Events tab lists them: nested only under a DEFINED parent. The
31440
+ * painter's visibility chain resolves parents against the defined groups alone, so a
31441
+ * parent that marks merely name must not nest (and dim) a child the painter still shows.
31442
+ */
31443
+ markGroupsForEventsTab() {
31444
+ const defined = new Set(this.scene.markGroups.map((g) => g.id));
31445
+ return this.markGroupsInUse().map((g) => g.parent !== void 0 && !defined.has(g.parent) ? { ...g, parent: void 0 } : g);
31446
+ }
31261
31447
  onViewportChange(cb) {
31262
31448
  this.viewportCbs.add(cb);
31263
31449
  return () => this.viewportCbs.delete(cb);
@@ -35427,6 +35613,7 @@ function registerClassicIndicators() {
35427
35613
  }
35428
35614
 
35429
35615
  // src/Vela.ts
35616
+ var asError = (err) => err instanceof Error ? err : new Error(String(err));
35430
35617
  var Vela = class {
35431
35618
  constructor(container, options = {}, deps = {}) {
35432
35619
  registerBuiltinChartTypes();
@@ -35550,7 +35737,12 @@ var Vela = class {
35550
35737
  * fetch from — the same relationship `script:run` has to `context:changed`.
35551
35738
  */
35552
35739
  runScript(source, options) {
35553
- const handle = this.addIndicator(source, options);
35740
+ let handle;
35741
+ try {
35742
+ handle = this.addIndicator(source, options);
35743
+ } catch (err) {
35744
+ return Promise.resolve({ ok: false, run: null, error: asError(err), onUpdate: () => () => void 0, remove: () => void 0 });
35745
+ }
35554
35746
  const updates = /* @__PURE__ */ new Set();
35555
35747
  const drop = () => {
35556
35748
  try {
@@ -35594,7 +35786,12 @@ var Vela = class {
35594
35786
  });
35595
35787
  }
35596
35788
  runIndicator(source, options) {
35597
- const handle = this.addIndicator(source, options);
35789
+ let handle;
35790
+ try {
35791
+ handle = this.addIndicator(source, options);
35792
+ } catch (err) {
35793
+ return Promise.resolve({ ok: false, handle: null, error: asError(err), context: null });
35794
+ }
35598
35795
  return new Promise((resolve) => {
35599
35796
  const offReady = handle.on("ready", () => {
35600
35797
  offReady();