@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.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { V as Vela, W as WidgetContext, K as SidePanelButton } from './contributions-CspPrMAJ.js';
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-CspPrMAJ.js';
3
- import { b as VelaOptions, d as MarketSession, T as ThemeName, c as VelaTheme, v as DataWindowReadout } from './options-XVpfr9HV.js';
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-CPa_hmhd.js';
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-CPa_hmhd.js';
1
+ import { V as Vela, W as WidgetContext, K as SidePanelButton } from './contributions-C7Wsm_S9.js';
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-C7Wsm_S9.js';
3
+ import { b as VelaOptions, d as MarketSession, T as ThemeName, c as VelaTheme, v as DataWindowReadout } from './options-D1AJKsn_.js';
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-xBiVsKoE.js';
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-xBiVsKoE.js';
6
6
  import { K as KeymapManager } from './keymap-CGOz5F5f.js';
7
- import { a as SymbolDescriptor } from './DataProvider-CVpN5sCq.js';
7
+ import { a as SymbolDescriptor } from './DataProvider-CmWUtDVL.js';
8
8
  import { d as SidePanel } from './side-panel-HF0IAzwf.js';
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.js';
10
10
 
package/dist/widget.js CHANGED
@@ -1,6 +1,6 @@
1
- import { VelaWorkspace } from './chunk-CSFNKXUW.js';
2
- export { Bottombar, ChartContextMenu, DataWindow, IndicatorPicker, ObjectTree, PanelDock, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeQuick, Topbar, Watermark, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, localStorageAdapter, parseTimeframe, priceStyleLabel, resolveIndicators, sanitizeState, timeframeLabel, timeframeMs } from './chunk-CSFNKXUW.js';
3
- export { TIMEZONES, normalizeTimezone, tzButtonLabel, tzMenuLabel, tzOffset } from './chunk-DUURH3RN.js';
1
+ import { VelaWorkspace } from './chunk-EMB53WNQ.js';
2
+ export { Bottombar, ChartContextMenu, DataWindow, IndicatorPicker, ObjectTree, PanelDock, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeQuick, Topbar, Watermark, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, localStorageAdapter, parseTimeframe, priceStyleLabel, resolveIndicators, sanitizeState, timeframeLabel, timeframeMs } from './chunk-EMB53WNQ.js';
3
+ export { TIMEZONES, normalizeTimezone, tzButtonLabel, tzMenuLabel, tzOffset } from './chunk-JSVCWYBF.js';
4
4
  export { DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, OVERRIDABLE_TOPBAR_IDS, SidePanel, TOPBAR_BUILTIN_IDS, TOPBAR_DEFAULT_LEFT, TOPBAR_DEFAULT_RIGHT, clampPanelWidth, pinnedTopbarActionIds, registerSidePanel, registerStatePersistence, registerSymbolRanking, registerWidgetAction, registerWidgetAttachment, resolveTopbarComposition, sidePanels, statePersistenceHandlers, symbolRanking, topbarActionOverride, topbarHas, unregisterSidePanel, unregisterStatePersistence, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments } from './chunk-BFA32GOU.js';
5
5
  import './chunk-ZADTVRUO.js';
6
6
  import './chunk-BKHSQ4YM.js';
@@ -5061,6 +5061,17 @@ function hasOwnCandlePaint(style) {
5061
5061
  for (const t of chartTypes()) if (t.id === style) return (t.basePainting ?? "candles") === "candles";
5062
5062
  return false;
5063
5063
  }
5064
+ var CANDLE_OVERRIDE_KEYS = [
5065
+ "candleUpColor",
5066
+ "candleDownColor",
5067
+ "candleBodyVisible",
5068
+ "candleBorderVisible",
5069
+ "candleBorderUpColor",
5070
+ "candleBorderDownColor",
5071
+ "candleWickVisible",
5072
+ "candleWickUpColor",
5073
+ "candleWickDownColor"
5074
+ ];
5064
5075
  function candleOverrideFor(style, bags) {
5065
5076
  if (!hasOwnCandlePaint(style)) return null;
5066
5077
  const bag = bags[style] ?? {};
@@ -5132,7 +5143,7 @@ function clampLevel(v) {
5132
5143
  function clampSpacing(v) {
5133
5144
  return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
5134
5145
  }
5135
- function factoryResetConfig(factory) {
5146
+ function factoryResetConfig(factory, priceStyle = factory.series.style) {
5136
5147
  const bag = {};
5137
5148
  for (const t of chartTypes()) {
5138
5149
  const section = t.settings;
@@ -5154,10 +5165,16 @@ function factoryResetConfig(factory) {
5154
5165
  if (!section.instances) addRows(section.rows);
5155
5166
  bag[t.id] = defaults2;
5156
5167
  }
5168
+ for (const t of chartTypes()) {
5169
+ if (!hasOwnCandlePaint(t.id)) continue;
5170
+ const defaults2 = bag[t.id] ?? {};
5171
+ for (const key of CANDLE_OVERRIDE_KEYS) defaults2[key] = null;
5172
+ bag[t.id] = defaults2;
5173
+ }
5157
5174
  for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
5158
5175
  bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
5159
5176
  }
5160
- return { ...factory, chartTypes: bag };
5177
+ return { ...factory, chartTypes: bag, series: { ...factory.series, style: priceStyle } };
5161
5178
  }
5162
5179
  function mergeConfig(base, patch) {
5163
5180
  const p = asObject(patch);
@@ -18375,10 +18392,18 @@ var IndicatorRegistry = class {
18375
18392
  this.records = /* @__PURE__ */ new Map();
18376
18393
  this.counter = 0;
18377
18394
  }
18378
- /** Allocate a unique, stable per-instance id. */
18379
- nextId(prefix = "ind") {
18380
- this.counter += 1;
18381
- return `${prefix}-${this.counter}`;
18395
+ /**
18396
+ * Mint a unique per-instance id. Host-supplied ids share the namespace, so a minted
18397
+ * id skips anything already recorded — and anything `taken` reports live elsewhere
18398
+ * (a handle the orchestrator holds outside the records, e.g. a fail-soft native).
18399
+ */
18400
+ nextId(prefix = "ind", taken = () => false) {
18401
+ let id;
18402
+ do {
18403
+ this.counter += 1;
18404
+ id = `${prefix}-${this.counter}`;
18405
+ } while (this.records.has(id) || taken(id));
18406
+ return id;
18382
18407
  }
18383
18408
  add(record) {
18384
18409
  this.records.set(record.id, record);
@@ -18585,6 +18610,42 @@ function presetToRange(preset, bars) {
18585
18610
  return { from: Math.max(first, from), to: last };
18586
18611
  }
18587
18612
 
18613
+ // src/core/marks/visibility.ts
18614
+ function markGroupOwnVisible(choices, groupId, groups) {
18615
+ const chosen = choices[groupId];
18616
+ if (typeof chosen === "boolean") return chosen;
18617
+ return groups.find((g) => g.id === groupId)?.visible !== false;
18618
+ }
18619
+ function markGroupVisible(choices, groupId, groups) {
18620
+ if (groupId === void 0) return true;
18621
+ const seen = /* @__PURE__ */ new Set();
18622
+ let id = groupId;
18623
+ while (id !== void 0 && !seen.has(id)) {
18624
+ seen.add(id);
18625
+ if (!markGroupOwnVisible(choices, id, groups)) return false;
18626
+ const parent = groups.find((g) => g.id === id)?.parent;
18627
+ id = parent !== void 0 && groups.some((g) => g.id === parent) ? parent : void 0;
18628
+ }
18629
+ return true;
18630
+ }
18631
+ function markGroupRows(groups) {
18632
+ const ids = new Set(groups.map((g) => g.id));
18633
+ const out = [];
18634
+ const placed = /* @__PURE__ */ new Set();
18635
+ const place = (group, depth) => {
18636
+ if (placed.has(group.id)) return;
18637
+ placed.add(group.id);
18638
+ out.push({ group, depth });
18639
+ for (const child of groups) if (child.parent === group.id && child.id !== group.id) place(child, depth + 1);
18640
+ };
18641
+ for (const g of groups) {
18642
+ const parentKnown = g.parent !== void 0 && ids.has(g.parent) && g.parent !== g.id;
18643
+ if (!parentKnown) place(g, 0);
18644
+ }
18645
+ for (const g of groups) place(g, 0);
18646
+ return out;
18647
+ }
18648
+
18588
18649
  // src/core/marks/MarksController.ts
18589
18650
  var MarksController = class {
18590
18651
  constructor(renderer, events) {
@@ -18629,6 +18690,7 @@ var MarksController = class {
18629
18690
  defineGroup(group) {
18630
18691
  if (!group || typeof group.id !== "string" || group.id.length === 0) throw new Error("[vela] marks.defineGroup: `id` must be a non-empty string");
18631
18692
  if (typeof group.label !== "string") throw new Error(`[vela] marks.defineGroup: group "${group.id}" needs a string \`label\``);
18693
+ 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`);
18632
18694
  this.groups.set(group.id, { ...group });
18633
18695
  this.sync();
18634
18696
  }
@@ -18648,12 +18710,16 @@ var MarksController = class {
18648
18710
  }
18649
18711
  this.renderer.applyFeature("marks", { groups: { [id]: visible } });
18650
18712
  }
18651
- /** A group's effective visibility: the user's (persisted) choice, else the group's declared default, else visible. */
18713
+ /**
18714
+ * A group's effective visibility: its own switch — the user's (persisted) choice, else
18715
+ * the declared default, else visible — AND every ancestor's, so a child under a
18716
+ * switched-off parent reads hidden whatever its own choice says.
18717
+ */
18652
18718
  isGroupVisible(id) {
18653
18719
  const state = this.renderer.readFeature("marks");
18654
- const chosen = state?.groups?.[id];
18655
- if (typeof chosen === "boolean") return chosen;
18656
- return this.groups.get(id)?.visible !== false;
18720
+ const groups = {};
18721
+ for (const [gid, v] of Object.entries(state?.groups ?? {})) if (typeof v === "boolean") groups[gid] = v;
18722
+ return markGroupVisible(groups, id, [...this.groups.values()]);
18657
18723
  }
18658
18724
  destroy() {
18659
18725
  for (const unsub of this.subs) unsub();
@@ -19720,7 +19786,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
19720
19786
  this.engines.set(language, engine);
19721
19787
  }
19722
19788
  addIndicator(source, options = {}) {
19723
- const id = this.registry.nextId();
19789
+ const id = this.claimIndicatorId(options.id);
19724
19790
  const title = options.title ?? "Indicator";
19725
19791
  const handle = new IndicatorHandleImpl(id, title, this, source);
19726
19792
  this.handles.set(id, handle);
@@ -19742,7 +19808,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
19742
19808
  const existing = this.registry.all().find((r) => r.native?.type === type);
19743
19809
  if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
19744
19810
  }
19745
- const id = this.registry.nextId("native");
19811
+ const id = this.registry.nextId("native", (candidate) => this.handles.has(candidate));
19746
19812
  const title = descriptor?.title ?? type;
19747
19813
  const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
19748
19814
  this.handles.set(id, handle);
@@ -20020,6 +20086,24 @@ var EngineOrchestrator = class _EngineOrchestrator {
20020
20086
  this.events.clear();
20021
20087
  }
20022
20088
  // ── internals ───────────────────────────────────────────────
20089
+ /**
20090
+ * The id a new script indicator runs under: the host's when supplied, else a minted
20091
+ * one. A host id is opaque but must be a non-empty string, and it must not be live
20092
+ * on this chart — a duplicate is a programming error surfaced synchronously, never
20093
+ * renamed behind the caller's back (the whole point of supplying one is that
20094
+ * `handle.id` equals what was passed). "Live" reads both the records and the handle
20095
+ * map: a fail-soft handle never enters the registry but still owns its id.
20096
+ */
20097
+ claimIndicatorId(requested) {
20098
+ if (requested === void 0) return this.registry.nextId("ind", (candidate) => this.handles.has(candidate));
20099
+ if (typeof requested !== "string" || requested.length === 0) {
20100
+ throw new TypeError("[vela] addIndicator: `id` must be a non-empty string");
20101
+ }
20102
+ if (this.handles.has(requested) || this.registry.get(requested)) {
20103
+ throw new Error(`[vela] addIndicator: indicator id "${requested}" is already live on this chart`);
20104
+ }
20105
+ return requested;
20106
+ }
20023
20107
  async startIndicator(id, source, options, handle) {
20024
20108
  try {
20025
20109
  await this.readyPromise;
@@ -25642,11 +25726,8 @@ function mergeMarksState(base, patch) {
25642
25726
  for (const [id, v] of Object.entries(g)) if (typeof v === "boolean") groups[id] = v;
25643
25727
  return { visible: typeof p.visible === "boolean" ? p.visible : base.visible, groups };
25644
25728
  }
25645
- function markGroupVisible(state, groupId, groups) {
25646
- if (groupId === void 0) return true;
25647
- const chosen = state.groups[groupId];
25648
- if (typeof chosen === "boolean") return chosen;
25649
- return groups.find((g) => g.id === groupId)?.visible !== false;
25729
+ function markGroupVisible2(state, groupId, groups) {
25730
+ return markGroupVisible(state.groups, groupId, groups);
25650
25731
  }
25651
25732
 
25652
25733
  // src/renderers/native/core/SceneGraph.ts
@@ -27822,13 +27903,29 @@ function paintMarkLane(ctx, layout, deps) {
27822
27903
  const img = deps.icons.get(mark.glyph.icon, ink, symbolPx, deps.dpr);
27823
27904
  if (img) ctx.drawImage(img, center.x - symbolPx / 2, center.y - symbolPx / 2, symbolPx, symbolPx);
27824
27905
  } else if (mark.glyph.letter) {
27906
+ const letter = mark.glyph.letter.slice(0, 2);
27825
27907
  ctx.fillStyle = ink;
27826
- ctx.font = `600 ${Math.round(size * 0.58)}px ${deps.fontFamily}`;
27827
- ctx.fillText(mark.glyph.letter.slice(0, 2), center.x, center.y + 0.5);
27908
+ let px = letterFontPx(size, letter);
27909
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
27910
+ if (letter.length > 1) {
27911
+ px = fitLetterPx(px, ctx.measureText(letter).width, symbolInnerWidth(shape, size));
27912
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
27913
+ }
27914
+ ctx.fillText(letter, center.x, center.y + 0.5);
27828
27915
  }
27829
27916
  }
27830
27917
  ctx.restore();
27831
27918
  }
27919
+ function letterFontPx(size, letter) {
27920
+ return Math.round(size * (letter.length > 1 ? 0.38 : 0.58));
27921
+ }
27922
+ function symbolInnerWidth(shape, size) {
27923
+ return (shape === "pin" ? size * 0.82 : size) - 3;
27924
+ }
27925
+ function fitLetterPx(px, width, inner) {
27926
+ if (!(width > inner) || !(width > 0)) return px;
27927
+ return Math.max(4, Math.floor(px * inner / width));
27928
+ }
27832
27929
  function traceShape(ctx, shape, x, y, size) {
27833
27930
  const r = size / 2;
27834
27931
  ctx.beginPath();
@@ -28030,7 +28127,7 @@ var ChromeRenderer = class {
28030
28127
  this.markLayout = layoutMarkLane({
28031
28128
  marks: scene.timelineMarks,
28032
28129
  groups: scene.markGroups,
28033
- hidden: (groupId) => !markGroupVisible(scene.marks, groupId, scene.markGroups),
28130
+ hidden: (groupId) => !markGroupVisible2(scene.marks, groupId, scene.markGroups),
28034
28131
  barTimes: this.barTimes(scene),
28035
28132
  intervalMs: coords.barInterval,
28036
28133
  xOf: (bar) => coords.logicalToX(bar),
@@ -28720,6 +28817,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28720
28817
  muted and non-interactive. Applied to each row's children so it survives display:contents;
28721
28818
  !important beats the inline opacity on labels. */
28722
28819
  .vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
28820
+ /* A mark group nested under a parent group on the Events tab: indented one step per level
28821
+ (--vela-sd-depth). The indent rides the first child (the switch) as a MARGIN \u2014 padding
28822
+ would push the switch's own tick out of its box. */
28823
+ .vela-sd-nested>*:first-child{margin-left:calc(var(--vela-sd-depth,1)*24px);}
28723
28824
  /* \u2500\u2500 mobile presentation (.vela-sd-mobile on the scrim; structural sizes are inline in open()) \u2500\u2500
28724
28825
  The tab rail becomes a burger-opened overlay sidebar; the group TOC becomes a sticky
28725
28826
  row of horizontally scrollable tabs; the instance strip scrolls instead of wrapping;
@@ -28768,6 +28869,8 @@ var SettingsDialog = class {
28768
28869
  this.hostSections = [];
28769
28870
  /** The timeline-mark groups (defined + named by marks) — one checkbox each on the Events tab. */
28770
28871
  this.markGroups = [];
28872
+ /** A group's OWN switch (what its checkbox shows) — a child under an off parent keeps its own state. */
28873
+ this.markGroupOwnVisible = () => true;
28771
28874
  this.markGroupVisible = () => true;
28772
28875
  /** The Canvas → Theme row: current app theme + where a pick is raised. The row is a
28773
28876
  * host callback, NOT a config patch — the app theme stays out of the persisted
@@ -28785,6 +28888,9 @@ var SettingsDialog = class {
28785
28888
  this.activeSection = null;
28786
28889
  /** Mobile chrome: fullscreen card, burger-opened section sidebar, TOC as top tabs. */
28787
28890
  this.mobileLayout = false;
28891
+ /** Opens/closes the mobile section sidebar of the CURRENT build (the burger in the
28892
+ * shell header outlives pane rebuilds, so it reaches the rail through here). */
28893
+ this.toggleRail = null;
28788
28894
  /** The visibility policy: setting ids hidden by the host (subtree semantics). */
28789
28895
  this.hiddenSettings = /* @__PURE__ */ new Set();
28790
28896
  if (getComputedStyle(container).position === "static") container.style.position = "relative";
@@ -28794,7 +28900,8 @@ var SettingsDialog = class {
28794
28900
  this.hostSections = sections;
28795
28901
  }
28796
28902
  /** The timeline-mark groups and their current visibility — the Events tab's rows on next open. */
28797
- setMarkGroups(groups, visible) {
28903
+ setMarkGroups(groups, visible, ownVisible = visible) {
28904
+ this.markGroupOwnVisible = ownVisible;
28798
28905
  this.markGroups = groups;
28799
28906
  this.markGroupVisible = visible;
28800
28907
  }
@@ -28860,7 +28967,6 @@ var SettingsDialog = class {
28860
28967
  this.onReset = onReset ?? null;
28861
28968
  ensureControlStyles();
28862
28969
  const mobile = this.mobileLayout;
28863
- let toggleRail = null;
28864
28970
  let burger;
28865
28971
  if (mobile) {
28866
28972
  burger = document.createElement("button");
@@ -28868,8 +28974,83 @@ var SettingsDialog = class {
28868
28974
  burger.className = "vela-sd-burger";
28869
28975
  burger.innerHTML = iconAt("burger", 16);
28870
28976
  burger.title = "Sections";
28871
- burger.addEventListener("click", () => toggleRail?.());
28977
+ burger.addEventListener("click", () => this.toggleRail?.());
28978
+ }
28979
+ const ui = new Dialog({
28980
+ host: this.container,
28981
+ title: "Chart settings",
28982
+ // Non-modal: a live-edit dialog must leave the page interactive — a modal
28983
+ // machine locks pointer events on the whole body, killing the chart, the
28984
+ // legend, and the body-portaled popovers (color picker, select lists).
28985
+ modal: false,
28986
+ contained: true,
28987
+ align: "top",
28988
+ draggable: !mobile,
28989
+ flush: true,
28990
+ className: "vela-dialog--settings",
28991
+ headerStart: burger,
28992
+ closeOnBackdrop: true,
28993
+ footer: (foot) => {
28994
+ foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
28995
+ const resetBtn = document.createElement("button");
28996
+ resetBtn.type = "button";
28997
+ resetBtn.textContent = "Reset defaults";
28998
+ resetBtn.className = "vela-sd-btn";
28999
+ resetBtn.addEventListener("click", () => this.onReset?.());
29000
+ foot.appendChild(resetBtn);
29001
+ },
29002
+ // A close signal may only close ITS OWN dialog: the machine reports the exit
29003
+ // asynchronously, so a close-then-reopen in one tick would otherwise see the
29004
+ // old instance's signal tear down the freshly opened one.
29005
+ onOpenChange: (open2) => {
29006
+ if (!open2 && this.ui === ui) this.close();
29007
+ }
29008
+ });
29009
+ if (mobile) ui.positioner.classList.add("vela-sd-mobile");
29010
+ ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
29011
+ this.root = ui.positioner;
29012
+ this.ui = ui;
29013
+ const panes = this.buildContent(ui, config, section, mobile);
29014
+ ui.show();
29015
+ this.layoutPanes(panes);
29016
+ }
29017
+ /**
29018
+ * Re-seed every control of an OPEN dialog from `config`, in place: the shell stays
29019
+ * (no close/open transition), only the tab rail and panes rebuild, landing back on
29020
+ * the current tab. The reset path — the restored values must show without the
29021
+ * dialog re-entering. No-op while closed.
29022
+ */
29023
+ refresh(config) {
29024
+ const ui = this.ui;
29025
+ if (!ui) return;
29026
+ closeOpenPopovers();
29027
+ closeWidthPopover();
29028
+ for (const dispose of this.hintTips) dispose();
29029
+ this.hintTips = [];
29030
+ this.config = config;
29031
+ ui.body.replaceChildren();
29032
+ this.layoutPanes(this.buildContent(ui, config, this.activeSection ?? void 0, this.mobileLayout));
29033
+ }
29034
+ /** Structured chart-type panes (instance strip / group TOC) own their layout and
29035
+ * tag their rows hosts instead; each host gets its own field grid. Runs on a SHOWN
29036
+ * dialog — the grids measure their labels. */
29037
+ layoutPanes(panes) {
29038
+ for (const el of panes) {
29039
+ const hosts = [...el.querySelectorAll("[data-sd-rows-host]")];
29040
+ if (hosts.length === 0) {
29041
+ this.layoutSettingsGrids(el);
29042
+ continue;
29043
+ }
29044
+ for (const h of hosts) this.layoutSettingsGrids(h);
28872
29045
  }
29046
+ }
29047
+ /**
29048
+ * Build the tab rail + one pane per section into `ui.body` (the linear `body` of
29049
+ * section markers and rows is split afterwards), select `section` (the active
29050
+ * style's own tab when none is asked for), and return the pane elements for
29051
+ * {@link layoutPanes}.
29052
+ */
29053
+ buildContent(ui, config, section, mobile) {
28873
29054
  const body = document.createElement("div");
28874
29055
  body.style.cssText = "display:flex;flex-direction:column;gap:0;";
28875
29056
  const sid = (el, id) => {
@@ -29073,9 +29254,28 @@ var SettingsDialog = class {
29073
29254
  if (this.markGroups.length > 0) {
29074
29255
  body.append(sid(this.section("Events"), MARKS_SETTINGS_ID));
29075
29256
  body.append(sid(this.sectionTitle("Visible events"), MARKS_GROUPS_SETTINGS_ID));
29076
- for (const g of this.markGroups) {
29077
- body.append(sid(this.boolRow(g.label, this.markGroupVisible(g.id), (v) => this.emit({ marks: { groups: { [g.id]: v } } })), markGroupSettingsId(g.id)));
29257
+ const rowsById = /* @__PURE__ */ new Map();
29258
+ const rows = markGroupRows(this.markGroups);
29259
+ const refreshDimming = () => {
29260
+ for (const { group } of rows) {
29261
+ const el = rowsById.get(group.id);
29262
+ if (!el || group.parent === void 0) continue;
29263
+ el.classList.toggle("vela-sd-soft", !this.markGroupVisible(group.parent));
29264
+ }
29265
+ };
29266
+ for (const { group: g, depth } of rows) {
29267
+ const row = this.boolRow(g.label, this.markGroupOwnVisible(g.id), (v) => {
29268
+ this.emit({ marks: { groups: { [g.id]: v } } });
29269
+ refreshDimming();
29270
+ });
29271
+ if (depth > 0) {
29272
+ row.classList.add("vela-sd-nested");
29273
+ row.style.setProperty("--vela-sd-depth", String(depth));
29274
+ }
29275
+ rowsById.set(g.id, row);
29276
+ body.append(sid(row, markGroupSettingsId(g.id)));
29078
29277
  }
29278
+ refreshDimming();
29079
29279
  }
29080
29280
  renderChartTypeSections("end");
29081
29281
  renderHostSections("end");
@@ -29103,8 +29303,8 @@ var SettingsDialog = class {
29103
29303
  if (mobile) {
29104
29304
  railScrim = document.createElement("div");
29105
29305
  railScrim.className = "vela-sd-railscrim";
29106
- railScrim.addEventListener("click", () => toggleRail?.());
29107
- toggleRail = (open2) => {
29306
+ railScrim.addEventListener("click", () => this.toggleRail?.());
29307
+ this.toggleRail = (open2) => {
29108
29308
  const on = open2 ?? !rail.classList.contains("open");
29109
29309
  rail.classList.toggle("open", on);
29110
29310
  railScrim?.classList.toggle("open", on);
@@ -29144,35 +29344,6 @@ var SettingsDialog = class {
29144
29344
  });
29145
29345
  if (hidActive) activate(0);
29146
29346
  };
29147
- const ui = new Dialog({
29148
- host: this.container,
29149
- title: "Chart settings",
29150
- // Non-modal: a live-edit dialog must leave the page interactive — a modal
29151
- // machine locks pointer events on the whole body, killing the chart, the
29152
- // legend, and the body-portaled popovers (color picker, select lists).
29153
- modal: false,
29154
- contained: true,
29155
- align: "top",
29156
- draggable: !mobile,
29157
- flush: true,
29158
- className: "vela-dialog--settings",
29159
- headerStart: burger,
29160
- closeOnBackdrop: true,
29161
- footer: (foot) => {
29162
- foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
29163
- const resetBtn = document.createElement("button");
29164
- resetBtn.type = "button";
29165
- resetBtn.textContent = "Reset defaults";
29166
- resetBtn.className = "vela-sd-btn";
29167
- resetBtn.addEventListener("click", () => this.onReset?.());
29168
- foot.appendChild(resetBtn);
29169
- },
29170
- onOpenChange: (open2) => {
29171
- if (!open2) this.close();
29172
- }
29173
- });
29174
- if (mobile) ui.positioner.classList.add("vela-sd-mobile");
29175
- ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
29176
29347
  const activate = (idx) => {
29177
29348
  panes.forEach((p, i) => {
29178
29349
  p.el.style.display = i === idx ? "block" : "none";
@@ -29181,7 +29352,7 @@ var SettingsDialog = class {
29181
29352
  this.activeSection = panes[idx]?.title ?? null;
29182
29353
  if (mobile) {
29183
29354
  ui.titleEl.textContent = panes[idx]?.title ?? "Chart settings";
29184
- toggleRail?.(false);
29355
+ this.toggleRail?.(false);
29185
29356
  }
29186
29357
  };
29187
29358
  panes.forEach((p, i) => {
@@ -29196,17 +29367,7 @@ var SettingsDialog = class {
29196
29367
  shell.append(rail, paneHost);
29197
29368
  if (railScrim) shell.append(railScrim);
29198
29369
  ui.body.appendChild(shell);
29199
- this.root = ui.positioner;
29200
- this.ui = ui;
29201
- ui.show();
29202
- for (const p of panes) {
29203
- const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
29204
- if (hosts.length === 0) {
29205
- this.layoutSettingsGrids(p.el);
29206
- continue;
29207
- }
29208
- for (const h of hosts) this.layoutSettingsGrids(h);
29209
- }
29370
+ return panes.map((p) => p.el);
29210
29371
  }
29211
29372
  close() {
29212
29373
  closeOpenPopovers();
@@ -29214,6 +29375,7 @@ var SettingsDialog = class {
29214
29375
  const ui = this.ui;
29215
29376
  this.ui = null;
29216
29377
  this.root = null;
29378
+ this.toggleRail = null;
29217
29379
  this.tabs = [];
29218
29380
  for (const dispose of this.hintTips) dispose();
29219
29381
  this.hintTips = [];
@@ -36532,7 +36694,11 @@ var NativeRenderer = class {
36532
36694
  }
36533
36695
  this.settingsDialog.setTheme(this.theme);
36534
36696
  this.settingsDialog.setHostSections(this.hostSettingsSections);
36535
- this.settingsDialog.setMarkGroups(this.markGroupsInUse(), (id) => markGroupVisible(this.scene.marks, id, this.scene.markGroups));
36697
+ this.settingsDialog.setMarkGroups(
36698
+ this.markGroupsForEventsTab(),
36699
+ (id) => markGroupVisible2(this.scene.marks, id, this.scene.markGroups),
36700
+ (id) => markGroupOwnVisible(this.scene.marks.groups, id, this.scene.markGroups)
36701
+ );
36536
36702
  this.settingsDialog.setHiddenSettings(this.hiddenSettings);
36537
36703
  this.syncThemeControl();
36538
36704
  this.settingsDialog.toggle(
@@ -36540,13 +36706,24 @@ var NativeRenderer = class {
36540
36706
  (patch) => this.applyConfig(patch),
36541
36707
  (json) => this.applyConfig(json),
36542
36708
  () => {
36543
- if (this.factoryConfig) this.applyConfig(factoryResetConfig(this.factoryConfig));
36544
- this.settingsDialog?.close();
36545
- this.openSettingsDialog();
36709
+ if (this.factoryConfig) this.applyConfig(this.factoryResetDocument(this.factoryConfig));
36710
+ this.settingsDialog?.refresh(this.getConfig());
36546
36711
  },
36547
36712
  section
36548
36713
  );
36549
36714
  }
36715
+ /**
36716
+ * The document "Reset defaults" applies: every setting back to its first-run value,
36717
+ * with two things that are NOT settings held or resolved here — the price style
36718
+ * stays the one the user is looking at, and the timeline-mark groups (an additive
36719
+ * merge, like the type bags) are named back to their host-declared visibility.
36720
+ */
36721
+ factoryResetDocument(factory) {
36722
+ const doc = factoryResetConfig(factory, this.scene.priceStyle);
36723
+ const groups = { ...doc.marks.groups };
36724
+ for (const g of this.markGroupsInUse()) groups[g.id] = g.visible !== false;
36725
+ return { ...doc, marks: { ...doc.marks, groups } };
36726
+ }
36550
36727
  /** Close the in-chart dialogs (indicator settings + chart-settings gear). No-op when none are open. */
36551
36728
  closeDialogs() {
36552
36729
  this.inputsUI?.closeOpenDialog();
@@ -37609,6 +37786,15 @@ var NativeRenderer = class {
37609
37786
  markGroupsInUse() {
37610
37787
  return effectiveMarkGroups(this.scene.timelineMarks, this.scene.markGroups);
37611
37788
  }
37789
+ /**
37790
+ * The groups as the Events tab lists them: nested only under a DEFINED parent. The
37791
+ * painter's visibility chain resolves parents against the defined groups alone, so a
37792
+ * parent that marks merely name must not nest (and dim) a child the painter still shows.
37793
+ */
37794
+ markGroupsForEventsTab() {
37795
+ const defined = new Set(this.scene.markGroups.map((g) => g.id));
37796
+ return this.markGroupsInUse().map((g) => g.parent !== void 0 && !defined.has(g.parent) ? { ...g, parent: void 0 } : g);
37797
+ }
37612
37798
  onViewportChange(cb) {
37613
37799
  this.viewportCbs.add(cb);
37614
37800
  return () => this.viewportCbs.delete(cb);
@@ -41749,6 +41935,7 @@ function registerClassicIndicators() {
41749
41935
  }
41750
41936
 
41751
41937
  // src/Vela.ts
41938
+ var asError = (err) => err instanceof Error ? err : new Error(String(err));
41752
41939
  var Vela = class {
41753
41940
  constructor(container, options = {}, deps = {}) {
41754
41941
  registerBuiltinChartTypes();
@@ -41872,7 +42059,12 @@ var Vela = class {
41872
42059
  * fetch from — the same relationship `script:run` has to `context:changed`.
41873
42060
  */
41874
42061
  runScript(source, options) {
41875
- const handle = this.addIndicator(source, options);
42062
+ let handle;
42063
+ try {
42064
+ handle = this.addIndicator(source, options);
42065
+ } catch (err) {
42066
+ return Promise.resolve({ ok: false, run: null, error: asError(err), onUpdate: () => () => void 0, remove: () => void 0 });
42067
+ }
41876
42068
  const updates = /* @__PURE__ */ new Set();
41877
42069
  const drop = () => {
41878
42070
  try {
@@ -41916,7 +42108,12 @@ var Vela = class {
41916
42108
  });
41917
42109
  }
41918
42110
  runIndicator(source, options) {
41919
- const handle = this.addIndicator(source, options);
42111
+ let handle;
42112
+ try {
42113
+ handle = this.addIndicator(source, options);
42114
+ } catch (err) {
42115
+ return Promise.resolve({ ok: false, handle: null, error: asError(err), context: null });
42116
+ }
41920
42117
  return new Promise((resolve) => {
41921
42118
  const offReady = handle.on("ready", () => {
41922
42119
  offReady();
@@ -43501,6 +43698,11 @@ var ChartCell = class {
43501
43698
  if (typeof zone === "string" && normalizeTimezone(zone) !== normalizeTimezone(this.deps.timezone())) {
43502
43699
  this.deps.setTimezone(normalizeTimezone(zone));
43503
43700
  }
43701
+ const style = this.priceStyle;
43702
+ if (style !== (this.state.priceStyle ?? "candles")) {
43703
+ this.state.priceStyle = style;
43704
+ this.deps.onPriceStyleChanged(this.id);
43705
+ }
43504
43706
  this.syncStatuslineColors();
43505
43707
  this.syncPlotOverlayTokens();
43506
43708
  });
@@ -44140,16 +44342,19 @@ var ChartCell = class {
44140
44342
  * a persistence handler's `restore` runs silently, a user-driven call records.
44141
44343
  */
44142
44344
  addExternalIndicator(entry) {
44345
+ const { id, inputs, props, hidden, ...script } = entry;
44143
44346
  this.addManifestInstance(
44144
- { ...entry, enabled: true },
44145
- { external: true, ...entry.inputs ? { inputs: entry.inputs } : {}, ...entry.props ? { props: entry.props } : {}, ...entry.hidden ? { hidden: true } : {} }
44347
+ { ...script, enabled: true },
44348
+ { external: true, ...id !== void 0 ? { id } : {}, ...inputs ? { inputs } : {}, ...props ? { props } : {}, ...hidden ? { hidden: true } : {} }
44146
44349
  );
44147
44350
  }
44148
44351
  /** Add ONE instance of a manifest entry (repeatable — duplicates are legitimate). */
44149
44352
  addManifestInstance(entry, opts = {}) {
44150
44353
  if (this.destroyed) return;
44151
44354
  const values = opts.inputs || opts.props ? { inputs: opts.inputs, props: opts.props } : void 0;
44152
- const it = { entry, handle: this.addToChart(entry, values), ...opts.external ? { external: true } : {}, ...values ? { values } : {} };
44355
+ const handle = this.addToChart(entry, values, opts.id);
44356
+ if (!handle) return;
44357
+ const it = { entry, handle, id: handle.id, ...opts.external ? { external: true } : {}, ...values ? { values } : {} };
44153
44358
  if (opts.hidden) it.handle?.setVisible(false);
44154
44359
  this.instances.push(it);
44155
44360
  this.deps.onIndicatorsChanged(this.id);
@@ -44158,7 +44363,7 @@ var ChartCell = class {
44158
44363
  this.history.push({
44159
44364
  undo: () => this.dropInstance(snapshot),
44160
44365
  redo: () => {
44161
- snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
44366
+ snapshot.handle = this.addToChart(snapshot.entry, snapshot.values, snapshot.id);
44162
44367
  this.instances.push(snapshot);
44163
44368
  this.deps.onIndicatorsChanged(this.id);
44164
44369
  }
@@ -44171,7 +44376,7 @@ var ChartCell = class {
44171
44376
  const snapshot = it;
44172
44377
  this.history.push({
44173
44378
  undo: () => {
44174
- snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
44379
+ snapshot.handle = this.addToChart(snapshot.entry, snapshot.values, snapshot.id);
44175
44380
  this.instances.push(snapshot);
44176
44381
  this.deps.onIndicatorsChanged(this.id);
44177
44382
  },
@@ -44261,9 +44466,10 @@ var ChartCell = class {
44261
44466
  this.deps.onIndicatorsChanged(this.id);
44262
44467
  });
44263
44468
  }
44264
- addToChart(entry, values) {
44469
+ addToChart(entry, values, id) {
44265
44470
  try {
44266
44471
  return this.inner?.addIndicator(entry.script, {
44472
+ ...id !== void 0 ? { id } : {},
44267
44473
  ...entry.language !== void 0 ? { language: entry.language } : {},
44268
44474
  ...values?.inputs ? { inputs: values.inputs } : {},
44269
44475
  ...values?.props ? { props: values.props } : {}