@luxalgo/vela 0.7.3 → 0.7.5

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-BALR0qPq.d.ts → DataProvider-DLeFs1gE.d.ts} +1 -1
  2. package/dist/{DataProvider-D5uMiJYz.d.cts → DataProvider-Dut6lXGM.d.cts} +1 -1
  3. package/dist/{chunk-2EO74GIE.js → chunk-MCLI6B3S.js} +261 -68
  4. package/dist/{chunk-DH2VF5X7.js → chunk-MNG5XPLV.js} +6 -1
  5. package/dist/{contributions-BbhbZd3c.d.ts → contributions-CEkgJKTU.d.ts} +7 -3
  6. package/dist/{contributions-CcBFe-91.d.cts → contributions-DYtiRtES.d.cts} +7 -3
  7. package/dist/index.cjs +261 -68
  8. package/dist/index.d.cts +23 -6
  9. package/dist/index.d.ts +23 -6
  10. package/dist/index.js +1 -1
  11. package/dist/{options-C7b_Qssu.d.cts → options-DNBoMKkX.d.cts} +10 -1
  12. package/dist/{options-C7b_Qssu.d.ts → options-DNBoMKkX.d.ts} +10 -1
  13. package/dist/{plugin-Cnix5ZBh.d.ts → plugin-CLn--oP6.d.ts} +3 -3
  14. package/dist/{plugin-D6MlX4--.d.cts → plugin-DtkeQVAO.d.cts} +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-D8NL7o4L.d.ts → statusline-model-DypGXjtr.d.ts} +3 -3
  24. package/dist/{statusline-model-DUKt7_TQ.d.cts → statusline-model-gA__yaog.d.cts} +3 -3
  25. package/dist/ui.d.cts +1 -1
  26. package/dist/ui.d.ts +1 -1
  27. package/dist/vela.global.js +261 -68
  28. package/dist/vela.global.min.js +53 -49
  29. package/dist/widget.cjs +266 -68
  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 +266 -68
  34. package/dist/workspace.d.cts +5 -5
  35. package/dist/workspace.d.ts +5 -5
  36. package/dist/workspace.js +2 -2
  37. package/package.json +1 -1
@@ -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);
@@ -18585,6 +18602,42 @@ function presetToRange(preset, bars) {
18585
18602
  return { from: Math.max(first, from), to: last };
18586
18603
  }
18587
18604
 
18605
+ // src/core/marks/visibility.ts
18606
+ function markGroupOwnVisible(choices, groupId, groups) {
18607
+ const chosen = choices[groupId];
18608
+ if (typeof chosen === "boolean") return chosen;
18609
+ return groups.find((g) => g.id === groupId)?.visible !== false;
18610
+ }
18611
+ function markGroupVisible(choices, groupId, groups) {
18612
+ if (groupId === void 0) return true;
18613
+ const seen = /* @__PURE__ */ new Set();
18614
+ let id = groupId;
18615
+ while (id !== void 0 && !seen.has(id)) {
18616
+ seen.add(id);
18617
+ if (!markGroupOwnVisible(choices, id, groups)) return false;
18618
+ const parent = groups.find((g) => g.id === id)?.parent;
18619
+ id = parent !== void 0 && groups.some((g) => g.id === parent) ? parent : void 0;
18620
+ }
18621
+ return true;
18622
+ }
18623
+ function markGroupRows(groups) {
18624
+ const ids = new Set(groups.map((g) => g.id));
18625
+ const out = [];
18626
+ const placed = /* @__PURE__ */ new Set();
18627
+ const place = (group, depth) => {
18628
+ if (placed.has(group.id)) return;
18629
+ placed.add(group.id);
18630
+ out.push({ group, depth });
18631
+ for (const child of groups) if (child.parent === group.id && child.id !== group.id) place(child, depth + 1);
18632
+ };
18633
+ for (const g of groups) {
18634
+ const parentKnown = g.parent !== void 0 && ids.has(g.parent) && g.parent !== g.id;
18635
+ if (!parentKnown) place(g, 0);
18636
+ }
18637
+ for (const g of groups) place(g, 0);
18638
+ return out;
18639
+ }
18640
+
18588
18641
  // src/core/marks/MarksController.ts
18589
18642
  var MarksController = class {
18590
18643
  constructor(renderer, events) {
@@ -18629,6 +18682,7 @@ var MarksController = class {
18629
18682
  defineGroup(group) {
18630
18683
  if (!group || typeof group.id !== "string" || group.id.length === 0) throw new Error("[vela] marks.defineGroup: `id` must be a non-empty string");
18631
18684
  if (typeof group.label !== "string") throw new Error(`[vela] marks.defineGroup: group "${group.id}" needs a string \`label\``);
18685
+ 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
18686
  this.groups.set(group.id, { ...group });
18633
18687
  this.sync();
18634
18688
  }
@@ -18648,12 +18702,16 @@ var MarksController = class {
18648
18702
  }
18649
18703
  this.renderer.applyFeature("marks", { groups: { [id]: visible } });
18650
18704
  }
18651
- /** A group's effective visibility: the user's (persisted) choice, else the group's declared default, else visible. */
18705
+ /**
18706
+ * A group's effective visibility: its own switch — the user's (persisted) choice, else
18707
+ * the declared default, else visible — AND every ancestor's, so a child under a
18708
+ * switched-off parent reads hidden whatever its own choice says.
18709
+ */
18652
18710
  isGroupVisible(id) {
18653
18711
  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;
18712
+ const groups = {};
18713
+ for (const [gid, v] of Object.entries(state?.groups ?? {})) if (typeof v === "boolean") groups[gid] = v;
18714
+ return markGroupVisible(groups, id, [...this.groups.values()]);
18657
18715
  }
18658
18716
  destroy() {
18659
18717
  for (const unsub of this.subs) unsub();
@@ -25642,11 +25700,8 @@ function mergeMarksState(base, patch) {
25642
25700
  for (const [id, v] of Object.entries(g)) if (typeof v === "boolean") groups[id] = v;
25643
25701
  return { visible: typeof p.visible === "boolean" ? p.visible : base.visible, groups };
25644
25702
  }
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;
25703
+ function markGroupVisible2(state, groupId, groups) {
25704
+ return markGroupVisible(state.groups, groupId, groups);
25650
25705
  }
25651
25706
 
25652
25707
  // src/renderers/native/core/SceneGraph.ts
@@ -27658,6 +27713,32 @@ function clusterMarks(marks, barTimes, intervalMs2, hidden) {
27658
27713
  }
27659
27714
  return out;
27660
27715
  }
27716
+ function foldOverlappingClusters(clusters, xOf, bucketPx = MARK_CLUSTER_PX) {
27717
+ const byGroup = /* @__PURE__ */ new Map();
27718
+ for (const c of clusters) {
27719
+ const list = byGroup.get(c.group);
27720
+ if (list) list.push(c);
27721
+ else byGroup.set(c.group, [c]);
27722
+ }
27723
+ const out = [];
27724
+ for (const list of byGroup.values()) {
27725
+ list.sort((a, b) => a.bar - b.bar);
27726
+ const runs = [];
27727
+ let anchorX = Number.NaN;
27728
+ for (const c of list) {
27729
+ const x = xOf(c.bar);
27730
+ const run = runs[runs.length - 1];
27731
+ if (run && Number.isFinite(x) && Number.isFinite(anchorX) && x - anchorX < bucketPx) {
27732
+ run.marks.push(...c.marks);
27733
+ } else {
27734
+ runs.push({ key: c.key, bar: c.bar, group: c.group, marks: [...c.marks] });
27735
+ anchorX = x;
27736
+ }
27737
+ }
27738
+ out.push(...runs);
27739
+ }
27740
+ return out;
27741
+ }
27661
27742
  function groupRank(groups, clusters) {
27662
27743
  const rank = /* @__PURE__ */ new Map();
27663
27744
  groups.forEach((g, i) => rank.set(g.id, i));
@@ -27667,7 +27748,7 @@ function groupRank(groups, clusters) {
27667
27748
  return (group) => group === void 0 ? Number.MAX_SAFE_INTEGER : rank.get(group) ?? Number.MAX_SAFE_INTEGER - 1;
27668
27749
  }
27669
27750
  function layoutMarkLane(input) {
27670
- const clusters = clusterMarks(input.marks, input.barTimes, input.intervalMs, input.hidden);
27751
+ const clusters = foldOverlappingClusters(clusterMarks(input.marks, input.barTimes, input.intervalMs, input.hidden), input.xOf);
27671
27752
  const rankOf = groupRank(input.groups, clusters);
27672
27753
  const byBar = /* @__PURE__ */ new Map();
27673
27754
  for (const c of clusters) {
@@ -27796,13 +27877,29 @@ function paintMarkLane(ctx, layout, deps) {
27796
27877
  const img = deps.icons.get(mark.glyph.icon, ink, symbolPx, deps.dpr);
27797
27878
  if (img) ctx.drawImage(img, center.x - symbolPx / 2, center.y - symbolPx / 2, symbolPx, symbolPx);
27798
27879
  } else if (mark.glyph.letter) {
27880
+ const letter = mark.glyph.letter.slice(0, 2);
27799
27881
  ctx.fillStyle = ink;
27800
- ctx.font = `600 ${Math.round(size * 0.58)}px ${deps.fontFamily}`;
27801
- ctx.fillText(mark.glyph.letter.slice(0, 2), center.x, center.y + 0.5);
27882
+ let px = letterFontPx(size, letter);
27883
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
27884
+ if (letter.length > 1) {
27885
+ px = fitLetterPx(px, ctx.measureText(letter).width, symbolInnerWidth(shape, size));
27886
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
27887
+ }
27888
+ ctx.fillText(letter, center.x, center.y + 0.5);
27802
27889
  }
27803
27890
  }
27804
27891
  ctx.restore();
27805
27892
  }
27893
+ function letterFontPx(size, letter) {
27894
+ return Math.round(size * (letter.length > 1 ? 0.38 : 0.58));
27895
+ }
27896
+ function symbolInnerWidth(shape, size) {
27897
+ return (shape === "pin" ? size * 0.82 : size) - 3;
27898
+ }
27899
+ function fitLetterPx(px, width, inner) {
27900
+ if (!(width > inner) || !(width > 0)) return px;
27901
+ return Math.max(4, Math.floor(px * inner / width));
27902
+ }
27806
27903
  function traceShape(ctx, shape, x, y, size) {
27807
27904
  const r = size / 2;
27808
27905
  ctx.beginPath();
@@ -28004,7 +28101,7 @@ var ChromeRenderer = class {
28004
28101
  this.markLayout = layoutMarkLane({
28005
28102
  marks: scene.timelineMarks,
28006
28103
  groups: scene.markGroups,
28007
- hidden: (groupId) => !markGroupVisible(scene.marks, groupId, scene.markGroups),
28104
+ hidden: (groupId) => !markGroupVisible2(scene.marks, groupId, scene.markGroups),
28008
28105
  barTimes: this.barTimes(scene),
28009
28106
  intervalMs: coords.barInterval,
28010
28107
  xOf: (bar) => coords.logicalToX(bar),
@@ -28694,6 +28791,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28694
28791
  muted and non-interactive. Applied to each row's children so it survives display:contents;
28695
28792
  !important beats the inline opacity on labels. */
28696
28793
  .vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
28794
+ /* A mark group nested under a parent group on the Events tab: indented one step per level
28795
+ (--vela-sd-depth). The indent rides the first child (the switch) as a MARGIN \u2014 padding
28796
+ would push the switch's own tick out of its box. */
28797
+ .vela-sd-nested>*:first-child{margin-left:calc(var(--vela-sd-depth,1)*24px);}
28697
28798
  /* \u2500\u2500 mobile presentation (.vela-sd-mobile on the scrim; structural sizes are inline in open()) \u2500\u2500
28698
28799
  The tab rail becomes a burger-opened overlay sidebar; the group TOC becomes a sticky
28699
28800
  row of horizontally scrollable tabs; the instance strip scrolls instead of wrapping;
@@ -28742,6 +28843,8 @@ var SettingsDialog = class {
28742
28843
  this.hostSections = [];
28743
28844
  /** The timeline-mark groups (defined + named by marks) — one checkbox each on the Events tab. */
28744
28845
  this.markGroups = [];
28846
+ /** A group's OWN switch (what its checkbox shows) — a child under an off parent keeps its own state. */
28847
+ this.markGroupOwnVisible = () => true;
28745
28848
  this.markGroupVisible = () => true;
28746
28849
  /** The Canvas → Theme row: current app theme + where a pick is raised. The row is a
28747
28850
  * host callback, NOT a config patch — the app theme stays out of the persisted
@@ -28759,6 +28862,9 @@ var SettingsDialog = class {
28759
28862
  this.activeSection = null;
28760
28863
  /** Mobile chrome: fullscreen card, burger-opened section sidebar, TOC as top tabs. */
28761
28864
  this.mobileLayout = false;
28865
+ /** Opens/closes the mobile section sidebar of the CURRENT build (the burger in the
28866
+ * shell header outlives pane rebuilds, so it reaches the rail through here). */
28867
+ this.toggleRail = null;
28762
28868
  /** The visibility policy: setting ids hidden by the host (subtree semantics). */
28763
28869
  this.hiddenSettings = /* @__PURE__ */ new Set();
28764
28870
  if (getComputedStyle(container).position === "static") container.style.position = "relative";
@@ -28768,7 +28874,8 @@ var SettingsDialog = class {
28768
28874
  this.hostSections = sections;
28769
28875
  }
28770
28876
  /** The timeline-mark groups and their current visibility — the Events tab's rows on next open. */
28771
- setMarkGroups(groups, visible) {
28877
+ setMarkGroups(groups, visible, ownVisible = visible) {
28878
+ this.markGroupOwnVisible = ownVisible;
28772
28879
  this.markGroups = groups;
28773
28880
  this.markGroupVisible = visible;
28774
28881
  }
@@ -28834,7 +28941,6 @@ var SettingsDialog = class {
28834
28941
  this.onReset = onReset ?? null;
28835
28942
  ensureControlStyles();
28836
28943
  const mobile = this.mobileLayout;
28837
- let toggleRail = null;
28838
28944
  let burger;
28839
28945
  if (mobile) {
28840
28946
  burger = document.createElement("button");
@@ -28842,8 +28948,83 @@ var SettingsDialog = class {
28842
28948
  burger.className = "vela-sd-burger";
28843
28949
  burger.innerHTML = iconAt("burger", 16);
28844
28950
  burger.title = "Sections";
28845
- burger.addEventListener("click", () => toggleRail?.());
28951
+ burger.addEventListener("click", () => this.toggleRail?.());
28846
28952
  }
28953
+ const ui = new Dialog({
28954
+ host: this.container,
28955
+ title: "Chart settings",
28956
+ // Non-modal: a live-edit dialog must leave the page interactive — a modal
28957
+ // machine locks pointer events on the whole body, killing the chart, the
28958
+ // legend, and the body-portaled popovers (color picker, select lists).
28959
+ modal: false,
28960
+ contained: true,
28961
+ align: "top",
28962
+ draggable: !mobile,
28963
+ flush: true,
28964
+ className: "vela-dialog--settings",
28965
+ headerStart: burger,
28966
+ closeOnBackdrop: true,
28967
+ footer: (foot) => {
28968
+ foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
28969
+ const resetBtn = document.createElement("button");
28970
+ resetBtn.type = "button";
28971
+ resetBtn.textContent = "Reset defaults";
28972
+ resetBtn.className = "vela-sd-btn";
28973
+ resetBtn.addEventListener("click", () => this.onReset?.());
28974
+ foot.appendChild(resetBtn);
28975
+ },
28976
+ // A close signal may only close ITS OWN dialog: the machine reports the exit
28977
+ // asynchronously, so a close-then-reopen in one tick would otherwise see the
28978
+ // old instance's signal tear down the freshly opened one.
28979
+ onOpenChange: (open2) => {
28980
+ if (!open2 && this.ui === ui) this.close();
28981
+ }
28982
+ });
28983
+ if (mobile) ui.positioner.classList.add("vela-sd-mobile");
28984
+ ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
28985
+ this.root = ui.positioner;
28986
+ this.ui = ui;
28987
+ const panes = this.buildContent(ui, config, section, mobile);
28988
+ ui.show();
28989
+ this.layoutPanes(panes);
28990
+ }
28991
+ /**
28992
+ * Re-seed every control of an OPEN dialog from `config`, in place: the shell stays
28993
+ * (no close/open transition), only the tab rail and panes rebuild, landing back on
28994
+ * the current tab. The reset path — the restored values must show without the
28995
+ * dialog re-entering. No-op while closed.
28996
+ */
28997
+ refresh(config) {
28998
+ const ui = this.ui;
28999
+ if (!ui) return;
29000
+ closeOpenPopovers();
29001
+ closeWidthPopover();
29002
+ for (const dispose of this.hintTips) dispose();
29003
+ this.hintTips = [];
29004
+ this.config = config;
29005
+ ui.body.replaceChildren();
29006
+ this.layoutPanes(this.buildContent(ui, config, this.activeSection ?? void 0, this.mobileLayout));
29007
+ }
29008
+ /** Structured chart-type panes (instance strip / group TOC) own their layout and
29009
+ * tag their rows hosts instead; each host gets its own field grid. Runs on a SHOWN
29010
+ * dialog — the grids measure their labels. */
29011
+ layoutPanes(panes) {
29012
+ for (const el of panes) {
29013
+ const hosts = [...el.querySelectorAll("[data-sd-rows-host]")];
29014
+ if (hosts.length === 0) {
29015
+ this.layoutSettingsGrids(el);
29016
+ continue;
29017
+ }
29018
+ for (const h of hosts) this.layoutSettingsGrids(h);
29019
+ }
29020
+ }
29021
+ /**
29022
+ * Build the tab rail + one pane per section into `ui.body` (the linear `body` of
29023
+ * section markers and rows is split afterwards), select `section` (the active
29024
+ * style's own tab when none is asked for), and return the pane elements for
29025
+ * {@link layoutPanes}.
29026
+ */
29027
+ buildContent(ui, config, section, mobile) {
28847
29028
  const body = document.createElement("div");
28848
29029
  body.style.cssText = "display:flex;flex-direction:column;gap:0;";
28849
29030
  const sid = (el, id) => {
@@ -29047,9 +29228,28 @@ var SettingsDialog = class {
29047
29228
  if (this.markGroups.length > 0) {
29048
29229
  body.append(sid(this.section("Events"), MARKS_SETTINGS_ID));
29049
29230
  body.append(sid(this.sectionTitle("Visible events"), MARKS_GROUPS_SETTINGS_ID));
29050
- for (const g of this.markGroups) {
29051
- body.append(sid(this.boolRow(g.label, this.markGroupVisible(g.id), (v) => this.emit({ marks: { groups: { [g.id]: v } } })), markGroupSettingsId(g.id)));
29231
+ const rowsById = /* @__PURE__ */ new Map();
29232
+ const rows = markGroupRows(this.markGroups);
29233
+ const refreshDimming = () => {
29234
+ for (const { group } of rows) {
29235
+ const el = rowsById.get(group.id);
29236
+ if (!el || group.parent === void 0) continue;
29237
+ el.classList.toggle("vela-sd-soft", !this.markGroupVisible(group.parent));
29238
+ }
29239
+ };
29240
+ for (const { group: g, depth } of rows) {
29241
+ const row = this.boolRow(g.label, this.markGroupOwnVisible(g.id), (v) => {
29242
+ this.emit({ marks: { groups: { [g.id]: v } } });
29243
+ refreshDimming();
29244
+ });
29245
+ if (depth > 0) {
29246
+ row.classList.add("vela-sd-nested");
29247
+ row.style.setProperty("--vela-sd-depth", String(depth));
29248
+ }
29249
+ rowsById.set(g.id, row);
29250
+ body.append(sid(row, markGroupSettingsId(g.id)));
29052
29251
  }
29252
+ refreshDimming();
29053
29253
  }
29054
29254
  renderChartTypeSections("end");
29055
29255
  renderHostSections("end");
@@ -29077,8 +29277,8 @@ var SettingsDialog = class {
29077
29277
  if (mobile) {
29078
29278
  railScrim = document.createElement("div");
29079
29279
  railScrim.className = "vela-sd-railscrim";
29080
- railScrim.addEventListener("click", () => toggleRail?.());
29081
- toggleRail = (open2) => {
29280
+ railScrim.addEventListener("click", () => this.toggleRail?.());
29281
+ this.toggleRail = (open2) => {
29082
29282
  const on = open2 ?? !rail.classList.contains("open");
29083
29283
  rail.classList.toggle("open", on);
29084
29284
  railScrim?.classList.toggle("open", on);
@@ -29118,35 +29318,6 @@ var SettingsDialog = class {
29118
29318
  });
29119
29319
  if (hidActive) activate(0);
29120
29320
  };
29121
- const ui = new Dialog({
29122
- host: this.container,
29123
- title: "Chart settings",
29124
- // Non-modal: a live-edit dialog must leave the page interactive — a modal
29125
- // machine locks pointer events on the whole body, killing the chart, the
29126
- // legend, and the body-portaled popovers (color picker, select lists).
29127
- modal: false,
29128
- contained: true,
29129
- align: "top",
29130
- draggable: !mobile,
29131
- flush: true,
29132
- className: "vela-dialog--settings",
29133
- headerStart: burger,
29134
- closeOnBackdrop: true,
29135
- footer: (foot) => {
29136
- foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
29137
- const resetBtn = document.createElement("button");
29138
- resetBtn.type = "button";
29139
- resetBtn.textContent = "Reset defaults";
29140
- resetBtn.className = "vela-sd-btn";
29141
- resetBtn.addEventListener("click", () => this.onReset?.());
29142
- foot.appendChild(resetBtn);
29143
- },
29144
- onOpenChange: (open2) => {
29145
- if (!open2) this.close();
29146
- }
29147
- });
29148
- if (mobile) ui.positioner.classList.add("vela-sd-mobile");
29149
- ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
29150
29321
  const activate = (idx) => {
29151
29322
  panes.forEach((p, i) => {
29152
29323
  p.el.style.display = i === idx ? "block" : "none";
@@ -29155,7 +29326,7 @@ var SettingsDialog = class {
29155
29326
  this.activeSection = panes[idx]?.title ?? null;
29156
29327
  if (mobile) {
29157
29328
  ui.titleEl.textContent = panes[idx]?.title ?? "Chart settings";
29158
- toggleRail?.(false);
29329
+ this.toggleRail?.(false);
29159
29330
  }
29160
29331
  };
29161
29332
  panes.forEach((p, i) => {
@@ -29170,17 +29341,7 @@ var SettingsDialog = class {
29170
29341
  shell.append(rail, paneHost);
29171
29342
  if (railScrim) shell.append(railScrim);
29172
29343
  ui.body.appendChild(shell);
29173
- this.root = ui.positioner;
29174
- this.ui = ui;
29175
- ui.show();
29176
- for (const p of panes) {
29177
- const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
29178
- if (hosts.length === 0) {
29179
- this.layoutSettingsGrids(p.el);
29180
- continue;
29181
- }
29182
- for (const h of hosts) this.layoutSettingsGrids(h);
29183
- }
29344
+ return panes.map((p) => p.el);
29184
29345
  }
29185
29346
  close() {
29186
29347
  closeOpenPopovers();
@@ -29188,6 +29349,7 @@ var SettingsDialog = class {
29188
29349
  const ui = this.ui;
29189
29350
  this.ui = null;
29190
29351
  this.root = null;
29352
+ this.toggleRail = null;
29191
29353
  this.tabs = [];
29192
29354
  for (const dispose of this.hintTips) dispose();
29193
29355
  this.hintTips = [];
@@ -36506,7 +36668,11 @@ var NativeRenderer = class {
36506
36668
  }
36507
36669
  this.settingsDialog.setTheme(this.theme);
36508
36670
  this.settingsDialog.setHostSections(this.hostSettingsSections);
36509
- this.settingsDialog.setMarkGroups(this.markGroupsInUse(), (id) => markGroupVisible(this.scene.marks, id, this.scene.markGroups));
36671
+ this.settingsDialog.setMarkGroups(
36672
+ this.markGroupsForEventsTab(),
36673
+ (id) => markGroupVisible2(this.scene.marks, id, this.scene.markGroups),
36674
+ (id) => markGroupOwnVisible(this.scene.marks.groups, id, this.scene.markGroups)
36675
+ );
36510
36676
  this.settingsDialog.setHiddenSettings(this.hiddenSettings);
36511
36677
  this.syncThemeControl();
36512
36678
  this.settingsDialog.toggle(
@@ -36514,13 +36680,24 @@ var NativeRenderer = class {
36514
36680
  (patch) => this.applyConfig(patch),
36515
36681
  (json) => this.applyConfig(json),
36516
36682
  () => {
36517
- if (this.factoryConfig) this.applyConfig(factoryResetConfig(this.factoryConfig));
36518
- this.settingsDialog?.close();
36519
- this.openSettingsDialog();
36683
+ if (this.factoryConfig) this.applyConfig(this.factoryResetDocument(this.factoryConfig));
36684
+ this.settingsDialog?.refresh(this.getConfig());
36520
36685
  },
36521
36686
  section
36522
36687
  );
36523
36688
  }
36689
+ /**
36690
+ * The document "Reset defaults" applies: every setting back to its first-run value,
36691
+ * with two things that are NOT settings held or resolved here — the price style
36692
+ * stays the one the user is looking at, and the timeline-mark groups (an additive
36693
+ * merge, like the type bags) are named back to their host-declared visibility.
36694
+ */
36695
+ factoryResetDocument(factory) {
36696
+ const doc = factoryResetConfig(factory, this.scene.priceStyle);
36697
+ const groups = { ...doc.marks.groups };
36698
+ for (const g of this.markGroupsInUse()) groups[g.id] = g.visible !== false;
36699
+ return { ...doc, marks: { ...doc.marks, groups } };
36700
+ }
36524
36701
  /** Close the in-chart dialogs (indicator settings + chart-settings gear). No-op when none are open. */
36525
36702
  closeDialogs() {
36526
36703
  this.inputsUI?.closeOpenDialog();
@@ -37583,6 +37760,15 @@ var NativeRenderer = class {
37583
37760
  markGroupsInUse() {
37584
37761
  return effectiveMarkGroups(this.scene.timelineMarks, this.scene.markGroups);
37585
37762
  }
37763
+ /**
37764
+ * The groups as the Events tab lists them: nested only under a DEFINED parent. The
37765
+ * painter's visibility chain resolves parents against the defined groups alone, so a
37766
+ * parent that marks merely name must not nest (and dim) a child the painter still shows.
37767
+ */
37768
+ markGroupsForEventsTab() {
37769
+ const defined = new Set(this.scene.markGroups.map((g) => g.id));
37770
+ return this.markGroupsInUse().map((g) => g.parent !== void 0 && !defined.has(g.parent) ? { ...g, parent: void 0 } : g);
37771
+ }
37586
37772
  onViewportChange(cb) {
37587
37773
  this.viewportCbs.add(cb);
37588
37774
  return () => this.viewportCbs.delete(cb);
@@ -38541,7 +38727,7 @@ var NativeRenderer = class {
38541
38727
  const or = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
38542
38728
  if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
38543
38729
  }
38544
- const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels));
38730
+ const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
38545
38731
  pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
38546
38732
  pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
38547
38733
  pane.axisFormat = void 0;
@@ -38791,6 +38977,13 @@ var NativeRenderer = class {
38791
38977
  priceLayersAnchoredToBars(masterModels) {
38792
38978
  return masterModels.some((m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type));
38793
38979
  }
38980
+ /** True when the pane's master content contributes SOMETHING to its autoscale besides
38981
+ * the candles: a series painted on the pane (force_overlay ones scale elsewhere), a
38982
+ * price line, or a measured drawings range. Mirrors what `computePaneScale` considers. */
38983
+ paneHasMeasurableContent(masterModels, drawings) {
38984
+ if (drawings) return true;
38985
+ return masterModels.some((m) => m.priceLines.length > 0 || m.series.some((s) => s.overlay !== true));
38986
+ }
38794
38987
  /** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
38795
38988
  * band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
38796
38989
  * Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
@@ -43468,6 +43661,11 @@ var ChartCell = class {
43468
43661
  if (typeof zone === "string" && normalizeTimezone(zone) !== normalizeTimezone(this.deps.timezone())) {
43469
43662
  this.deps.setTimezone(normalizeTimezone(zone));
43470
43663
  }
43664
+ const style = this.priceStyle;
43665
+ if (style !== (this.state.priceStyle ?? "candles")) {
43666
+ this.state.priceStyle = style;
43667
+ this.deps.onPriceStyleChanged(this.id);
43668
+ }
43471
43669
  this.syncStatuslineColors();
43472
43670
  this.syncPlotOverlayTokens();
43473
43671
  });
@@ -1,9 +1,9 @@
1
- import { O as OHLCV, V as VisibleRangePreset, a as VisibleRange, b as VelaOptions, I as InputValue, c as VelaTheme, N as NativeBackend, d as MarketSession } from './options-C7b_Qssu.cjs';
2
- import { S as SyncSetting, V as VelaStorage, C as CellState, W as WidgetHistory, R as ResolvedIndicator, a as StatuslinePart, b as RangePreset, T as TrackSizes, c as VelaShellOptions, d as SyncOptions, e as SyncKind, f as WorkspaceState } from './statusline-model-DUKt7_TQ.cjs';
3
- export { g as ChartState, P as PanelsState, h as decodeState, i as encodeState, s as sanitizeState } from './statusline-model-DUKt7_TQ.cjs';
1
+ import { O as OHLCV, V as VisibleRangePreset, a as VisibleRange, b as VelaOptions, I as InputValue, c as VelaTheme, N as NativeBackend, d as MarketSession } from './options-DNBoMKkX.cjs';
2
+ import { S as SyncSetting, V as VelaStorage, C as CellState, W as WidgetHistory, R as ResolvedIndicator, a as StatuslinePart, b as RangePreset, T as TrackSizes, c as VelaShellOptions, d as SyncOptions, e as SyncKind, f as WorkspaceState } from './statusline-model-gA__yaog.cjs';
3
+ export { g as ChartState, P as PanelsState, h as decodeState, i as encodeState, s as sanitizeState } from './statusline-model-gA__yaog.cjs';
4
4
  import { K as KeymapManager } from './keymap-CGOz5F5f.cjs';
5
- import { I as IndicatorHandle, S as ScriptingEngine, W as WidgetContext, V as Vela, E as ExternalIndicatorEntry, a as ScriptRun } from './contributions-CcBFe-91.cjs';
6
- import { M as MarketDataFeed } from './DataProvider-D5uMiJYz.cjs';
5
+ import { I as IndicatorHandle, S as ScriptingEngine, W as WidgetContext, V as Vela, E as ExternalIndicatorEntry, a as ScriptRun } from './contributions-DYtiRtES.cjs';
6
+ import { M as MarketDataFeed } from './DataProvider-Dut6lXGM.cjs';
7
7
 
8
8
  /** The cells that follow `originId` under `setting` — PURE (never includes the origin). */
9
9
  declare function syncTargets(originId: string, setting: SyncSetting | undefined, cellIds: readonly string[]): string[];
@@ -1,9 +1,9 @@
1
- import { O as OHLCV, V as VisibleRangePreset, a as VisibleRange, b as VelaOptions, I as InputValue, c as VelaTheme, N as NativeBackend, d as MarketSession } from './options-C7b_Qssu.js';
2
- import { S as SyncSetting, V as VelaStorage, C as CellState, W as WidgetHistory, R as ResolvedIndicator, a as StatuslinePart, b as RangePreset, T as TrackSizes, c as VelaShellOptions, d as SyncOptions, e as SyncKind, f as WorkspaceState } from './statusline-model-D8NL7o4L.js';
3
- export { g as ChartState, P as PanelsState, h as decodeState, i as encodeState, s as sanitizeState } from './statusline-model-D8NL7o4L.js';
1
+ import { O as OHLCV, V as VisibleRangePreset, a as VisibleRange, b as VelaOptions, I as InputValue, c as VelaTheme, N as NativeBackend, d as MarketSession } from './options-DNBoMKkX.js';
2
+ import { S as SyncSetting, V as VelaStorage, C as CellState, W as WidgetHistory, R as ResolvedIndicator, a as StatuslinePart, b as RangePreset, T as TrackSizes, c as VelaShellOptions, d as SyncOptions, e as SyncKind, f as WorkspaceState } from './statusline-model-DypGXjtr.js';
3
+ export { g as ChartState, P as PanelsState, h as decodeState, i as encodeState, s as sanitizeState } from './statusline-model-DypGXjtr.js';
4
4
  import { K as KeymapManager } from './keymap-CGOz5F5f.js';
5
- import { I as IndicatorHandle, S as ScriptingEngine, W as WidgetContext, V as Vela, E as ExternalIndicatorEntry, a as ScriptRun } from './contributions-BbhbZd3c.js';
6
- import { M as MarketDataFeed } from './DataProvider-BALR0qPq.js';
5
+ import { I as IndicatorHandle, S as ScriptingEngine, W as WidgetContext, V as Vela, E as ExternalIndicatorEntry, a as ScriptRun } from './contributions-CEkgJKTU.js';
6
+ import { M as MarketDataFeed } from './DataProvider-DLeFs1gE.js';
7
7
 
8
8
  /** The cells that follow `originId` under `setting` — PURE (never includes the origin). */
9
9
  declare function syncTargets(originId: string, setting: SyncSetting | undefined, cellIds: readonly string[]): string[];
package/dist/workspace.js CHANGED
@@ -1,5 +1,5 @@
1
- export { ChartCell, GRID_PICKER_MAX, VelaWorkspace, activeAfterLayout, decodeState, encodeState, ensureLayout, evenTracks, gridStyles, layoutDefinition, layoutForGrid, layoutShape, layouts, memoryStorageAdapter, rangesWithin, registerBuiltinLayouts, registerLayout, resizeTracks, sanitizeState, syncTargets, trackOffsets, unregisterLayout } from './chunk-DH2VF5X7.js';
2
- import './chunk-2EO74GIE.js';
1
+ export { ChartCell, GRID_PICKER_MAX, VelaWorkspace, activeAfterLayout, decodeState, encodeState, ensureLayout, evenTracks, gridStyles, layoutDefinition, layoutForGrid, layoutShape, layouts, memoryStorageAdapter, rangesWithin, registerBuiltinLayouts, registerLayout, resizeTracks, sanitizeState, syncTargets, trackOffsets, unregisterLayout } from './chunk-MNG5XPLV.js';
2
+ import './chunk-MCLI6B3S.js';
3
3
  import './chunk-BFA32GOU.js';
4
4
  import './chunk-ZADTVRUO.js';
5
5
  import './chunk-BKHSQ4YM.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luxalgo/vela",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "Open-source charting library with a native high-performance renderer, drawing tools, pluggable chart types and pluggable scripting engines.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://luxalgo.com/vela",