@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
@@ -7116,6 +7116,42 @@ var Vela = (function (exports) {
7116
7116
  }
7117
7117
  };
7118
7118
 
7119
+ // src/core/marks/visibility.ts
7120
+ function markGroupOwnVisible(choices, groupId, groups) {
7121
+ const chosen = choices[groupId];
7122
+ if (typeof chosen === "boolean") return chosen;
7123
+ return groups.find((g) => g.id === groupId)?.visible !== false;
7124
+ }
7125
+ function markGroupVisible(choices, groupId, groups) {
7126
+ if (groupId === void 0) return true;
7127
+ const seen = /* @__PURE__ */ new Set();
7128
+ let id = groupId;
7129
+ while (id !== void 0 && !seen.has(id)) {
7130
+ seen.add(id);
7131
+ if (!markGroupOwnVisible(choices, id, groups)) return false;
7132
+ const parent = groups.find((g) => g.id === id)?.parent;
7133
+ id = parent !== void 0 && groups.some((g) => g.id === parent) ? parent : void 0;
7134
+ }
7135
+ return true;
7136
+ }
7137
+ function markGroupRows(groups) {
7138
+ const ids = new Set(groups.map((g) => g.id));
7139
+ const out = [];
7140
+ const placed = /* @__PURE__ */ new Set();
7141
+ const place = (group, depth) => {
7142
+ if (placed.has(group.id)) return;
7143
+ placed.add(group.id);
7144
+ out.push({ group, depth });
7145
+ for (const child of groups) if (child.parent === group.id && child.id !== group.id) place(child, depth + 1);
7146
+ };
7147
+ for (const g of groups) {
7148
+ const parentKnown = g.parent !== void 0 && ids.has(g.parent) && g.parent !== g.id;
7149
+ if (!parentKnown) place(g, 0);
7150
+ }
7151
+ for (const g of groups) place(g, 0);
7152
+ return out;
7153
+ }
7154
+
7119
7155
  // src/core/marks/MarksController.ts
7120
7156
  var MarksController = class {
7121
7157
  constructor(renderer, events) {
@@ -7160,6 +7196,7 @@ var Vela = (function (exports) {
7160
7196
  defineGroup(group) {
7161
7197
  if (!group || typeof group.id !== "string" || group.id.length === 0) throw new Error("[vela] marks.defineGroup: `id` must be a non-empty string");
7162
7198
  if (typeof group.label !== "string") throw new Error(`[vela] marks.defineGroup: group "${group.id}" needs a string \`label\``);
7199
+ if (group.parent !== void 0 && (typeof group.parent !== "string" || group.parent.length === 0)) throw new Error(`[vela] marks.defineGroup: group "${group.id}" has a \`parent\` that is not a group id`);
7163
7200
  this.groups.set(group.id, { ...group });
7164
7201
  this.sync();
7165
7202
  }
@@ -7179,12 +7216,16 @@ var Vela = (function (exports) {
7179
7216
  }
7180
7217
  this.renderer.applyFeature("marks", { groups: { [id]: visible } });
7181
7218
  }
7182
- /** A group's effective visibility: the user's (persisted) choice, else the group's declared default, else visible. */
7219
+ /**
7220
+ * A group's effective visibility: its own switch — the user's (persisted) choice, else
7221
+ * the declared default, else visible — AND every ancestor's, so a child under a
7222
+ * switched-off parent reads hidden whatever its own choice says.
7223
+ */
7183
7224
  isGroupVisible(id) {
7184
7225
  const state = this.renderer.readFeature("marks");
7185
- const chosen = state?.groups?.[id];
7186
- if (typeof chosen === "boolean") return chosen;
7187
- return this.groups.get(id)?.visible !== false;
7226
+ const groups = {};
7227
+ for (const [gid, v] of Object.entries(state?.groups ?? {})) if (typeof v === "boolean") groups[gid] = v;
7228
+ return markGroupVisible(groups, id, [...this.groups.values()]);
7188
7229
  }
7189
7230
  destroy() {
7190
7231
  for (const unsub of this.subs) unsub();
@@ -23374,6 +23415,17 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23374
23415
  for (const t of chartTypes()) if (t.id === style) return (t.basePainting ?? "candles") === "candles";
23375
23416
  return false;
23376
23417
  }
23418
+ var CANDLE_OVERRIDE_KEYS = [
23419
+ "candleUpColor",
23420
+ "candleDownColor",
23421
+ "candleBodyVisible",
23422
+ "candleBorderVisible",
23423
+ "candleBorderUpColor",
23424
+ "candleBorderDownColor",
23425
+ "candleWickVisible",
23426
+ "candleWickUpColor",
23427
+ "candleWickDownColor"
23428
+ ];
23377
23429
  function candleOverrideFor(style, bags) {
23378
23430
  if (!hasOwnCandlePaint(style)) return null;
23379
23431
  const bag = bags[style] ?? {};
@@ -23445,7 +23497,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23445
23497
  function clampSpacing(v) {
23446
23498
  return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
23447
23499
  }
23448
- function factoryResetConfig(factory) {
23500
+ function factoryResetConfig(factory, priceStyle = factory.series.style) {
23449
23501
  const bag = {};
23450
23502
  for (const t of chartTypes()) {
23451
23503
  const section = t.settings;
@@ -23467,10 +23519,16 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23467
23519
  if (!section.instances) addRows(section.rows);
23468
23520
  bag[t.id] = defaults2;
23469
23521
  }
23522
+ for (const t of chartTypes()) {
23523
+ if (!hasOwnCandlePaint(t.id)) continue;
23524
+ const defaults2 = bag[t.id] ?? {};
23525
+ for (const key of CANDLE_OVERRIDE_KEYS) defaults2[key] = null;
23526
+ bag[t.id] = defaults2;
23527
+ }
23470
23528
  for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
23471
23529
  bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
23472
23530
  }
23473
- return { ...factory, chartTypes: bag };
23531
+ return { ...factory, chartTypes: bag, series: { ...factory.series, style: priceStyle } };
23474
23532
  }
23475
23533
  function mergeConfig(base, patch) {
23476
23534
  const p = asObject(patch);
@@ -25849,11 +25907,8 @@ void main() {
25849
25907
  for (const [id, v] of Object.entries(g)) if (typeof v === "boolean") groups[id] = v;
25850
25908
  return { visible: typeof p.visible === "boolean" ? p.visible : base.visible, groups };
25851
25909
  }
25852
- function markGroupVisible(state, groupId, groups) {
25853
- if (groupId === void 0) return true;
25854
- const chosen = state.groups[groupId];
25855
- if (typeof chosen === "boolean") return chosen;
25856
- return groups.find((g) => g.id === groupId)?.visible !== false;
25910
+ function markGroupVisible2(state, groupId, groups) {
25911
+ return markGroupVisible(state.groups, groupId, groups);
25857
25912
  }
25858
25913
 
25859
25914
  // src/renderers/native/core/SceneGraph.ts
@@ -27865,6 +27920,32 @@ void main() {
27865
27920
  }
27866
27921
  return out;
27867
27922
  }
27923
+ function foldOverlappingClusters(clusters, xOf, bucketPx = MARK_CLUSTER_PX) {
27924
+ const byGroup = /* @__PURE__ */ new Map();
27925
+ for (const c of clusters) {
27926
+ const list = byGroup.get(c.group);
27927
+ if (list) list.push(c);
27928
+ else byGroup.set(c.group, [c]);
27929
+ }
27930
+ const out = [];
27931
+ for (const list of byGroup.values()) {
27932
+ list.sort((a, b) => a.bar - b.bar);
27933
+ const runs = [];
27934
+ let anchorX = Number.NaN;
27935
+ for (const c of list) {
27936
+ const x = xOf(c.bar);
27937
+ const run = runs[runs.length - 1];
27938
+ if (run && Number.isFinite(x) && Number.isFinite(anchorX) && x - anchorX < bucketPx) {
27939
+ run.marks.push(...c.marks);
27940
+ } else {
27941
+ runs.push({ key: c.key, bar: c.bar, group: c.group, marks: [...c.marks] });
27942
+ anchorX = x;
27943
+ }
27944
+ }
27945
+ out.push(...runs);
27946
+ }
27947
+ return out;
27948
+ }
27868
27949
  function groupRank(groups, clusters) {
27869
27950
  const rank = /* @__PURE__ */ new Map();
27870
27951
  groups.forEach((g, i) => rank.set(g.id, i));
@@ -27874,7 +27955,7 @@ void main() {
27874
27955
  return (group) => group === void 0 ? Number.MAX_SAFE_INTEGER : rank.get(group) ?? Number.MAX_SAFE_INTEGER - 1;
27875
27956
  }
27876
27957
  function layoutMarkLane(input) {
27877
- const clusters = clusterMarks(input.marks, input.barTimes, input.intervalMs, input.hidden);
27958
+ const clusters = foldOverlappingClusters(clusterMarks(input.marks, input.barTimes, input.intervalMs, input.hidden), input.xOf);
27878
27959
  const rankOf = groupRank(input.groups, clusters);
27879
27960
  const byBar = /* @__PURE__ */ new Map();
27880
27961
  for (const c of clusters) {
@@ -28003,13 +28084,29 @@ void main() {
28003
28084
  const img = deps.icons.get(mark.glyph.icon, ink, symbolPx, deps.dpr);
28004
28085
  if (img) ctx.drawImage(img, center.x - symbolPx / 2, center.y - symbolPx / 2, symbolPx, symbolPx);
28005
28086
  } else if (mark.glyph.letter) {
28087
+ const letter = mark.glyph.letter.slice(0, 2);
28006
28088
  ctx.fillStyle = ink;
28007
- ctx.font = `600 ${Math.round(size3 * 0.58)}px ${deps.fontFamily}`;
28008
- ctx.fillText(mark.glyph.letter.slice(0, 2), center.x, center.y + 0.5);
28089
+ let px = letterFontPx(size3, letter);
28090
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
28091
+ if (letter.length > 1) {
28092
+ px = fitLetterPx(px, ctx.measureText(letter).width, symbolInnerWidth(shape, size3));
28093
+ ctx.font = `600 ${px}px ${deps.fontFamily}`;
28094
+ }
28095
+ ctx.fillText(letter, center.x, center.y + 0.5);
28009
28096
  }
28010
28097
  }
28011
28098
  ctx.restore();
28012
28099
  }
28100
+ function letterFontPx(size3, letter) {
28101
+ return Math.round(size3 * (letter.length > 1 ? 0.38 : 0.58));
28102
+ }
28103
+ function symbolInnerWidth(shape, size3) {
28104
+ return (shape === "pin" ? size3 * 0.82 : size3) - 3;
28105
+ }
28106
+ function fitLetterPx(px, width, inner) {
28107
+ if (!(width > inner) || !(width > 0)) return px;
28108
+ return Math.max(4, Math.floor(px * inner / width));
28109
+ }
28013
28110
  function traceShape(ctx, shape, x, y, size3) {
28014
28111
  const r = size3 / 2;
28015
28112
  ctx.beginPath();
@@ -28211,7 +28308,7 @@ void main() {
28211
28308
  this.markLayout = layoutMarkLane({
28212
28309
  marks: scene.timelineMarks,
28213
28310
  groups: scene.markGroups,
28214
- hidden: (groupId) => !markGroupVisible(scene.marks, groupId, scene.markGroups),
28311
+ hidden: (groupId) => !markGroupVisible2(scene.marks, groupId, scene.markGroups),
28215
28312
  barTimes: this.barTimes(scene),
28216
28313
  intervalMs: coords.barInterval,
28217
28314
  xOf: (bar) => coords.logicalToX(bar),
@@ -28978,6 +29075,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
28978
29075
  muted and non-interactive. Applied to each row's children so it survives display:contents;
28979
29076
  !important beats the inline opacity on labels. */
28980
29077
  .vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
29078
+ /* A mark group nested under a parent group on the Events tab: indented one step per level
29079
+ (--vela-sd-depth). The indent rides the first child (the switch) as a MARGIN \u2014 padding
29080
+ would push the switch's own tick out of its box. */
29081
+ .vela-sd-nested>*:first-child{margin-left:calc(var(--vela-sd-depth,1)*24px);}
28981
29082
  /* \u2500\u2500 mobile presentation (.vela-sd-mobile on the scrim; structural sizes are inline in open()) \u2500\u2500
28982
29083
  The tab rail becomes a burger-opened overlay sidebar; the group TOC becomes a sticky
28983
29084
  row of horizontally scrollable tabs; the instance strip scrolls instead of wrapping;
@@ -29026,6 +29127,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29026
29127
  this.hostSections = [];
29027
29128
  /** The timeline-mark groups (defined + named by marks) — one checkbox each on the Events tab. */
29028
29129
  this.markGroups = [];
29130
+ /** A group's OWN switch (what its checkbox shows) — a child under an off parent keeps its own state. */
29131
+ this.markGroupOwnVisible = () => true;
29029
29132
  this.markGroupVisible = () => true;
29030
29133
  /** The Canvas → Theme row: current app theme + where a pick is raised. The row is a
29031
29134
  * host callback, NOT a config patch — the app theme stays out of the persisted
@@ -29043,6 +29146,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29043
29146
  this.activeSection = null;
29044
29147
  /** Mobile chrome: fullscreen card, burger-opened section sidebar, TOC as top tabs. */
29045
29148
  this.mobileLayout = false;
29149
+ /** Opens/closes the mobile section sidebar of the CURRENT build (the burger in the
29150
+ * shell header outlives pane rebuilds, so it reaches the rail through here). */
29151
+ this.toggleRail = null;
29046
29152
  /** The visibility policy: setting ids hidden by the host (subtree semantics). */
29047
29153
  this.hiddenSettings = /* @__PURE__ */ new Set();
29048
29154
  if (getComputedStyle(container).position === "static") container.style.position = "relative";
@@ -29052,7 +29158,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29052
29158
  this.hostSections = sections;
29053
29159
  }
29054
29160
  /** The timeline-mark groups and their current visibility — the Events tab's rows on next open. */
29055
- setMarkGroups(groups, visible) {
29161
+ setMarkGroups(groups, visible, ownVisible = visible) {
29162
+ this.markGroupOwnVisible = ownVisible;
29056
29163
  this.markGroups = groups;
29057
29164
  this.markGroupVisible = visible;
29058
29165
  }
@@ -29118,7 +29225,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29118
29225
  this.onReset = onReset ?? null;
29119
29226
  ensureControlStyles();
29120
29227
  const mobile = this.mobileLayout;
29121
- let toggleRail = null;
29122
29228
  let burger;
29123
29229
  if (mobile) {
29124
29230
  burger = document.createElement("button");
@@ -29126,8 +29232,83 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29126
29232
  burger.className = "vela-sd-burger";
29127
29233
  burger.innerHTML = iconAt("burger", 16);
29128
29234
  burger.title = "Sections";
29129
- burger.addEventListener("click", () => toggleRail?.());
29235
+ burger.addEventListener("click", () => this.toggleRail?.());
29236
+ }
29237
+ const ui = new Dialog({
29238
+ host: this.container,
29239
+ title: "Chart settings",
29240
+ // Non-modal: a live-edit dialog must leave the page interactive — a modal
29241
+ // machine locks pointer events on the whole body, killing the chart, the
29242
+ // legend, and the body-portaled popovers (color picker, select lists).
29243
+ modal: false,
29244
+ contained: true,
29245
+ align: "top",
29246
+ draggable: !mobile,
29247
+ flush: true,
29248
+ className: "vela-dialog--settings",
29249
+ headerStart: burger,
29250
+ closeOnBackdrop: true,
29251
+ footer: (foot) => {
29252
+ foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
29253
+ const resetBtn = document.createElement("button");
29254
+ resetBtn.type = "button";
29255
+ resetBtn.textContent = "Reset defaults";
29256
+ resetBtn.className = "vela-sd-btn";
29257
+ resetBtn.addEventListener("click", () => this.onReset?.());
29258
+ foot.appendChild(resetBtn);
29259
+ },
29260
+ // A close signal may only close ITS OWN dialog: the machine reports the exit
29261
+ // asynchronously, so a close-then-reopen in one tick would otherwise see the
29262
+ // old instance's signal tear down the freshly opened one.
29263
+ onOpenChange: (open2) => {
29264
+ if (!open2 && this.ui === ui) this.close();
29265
+ }
29266
+ });
29267
+ if (mobile) ui.positioner.classList.add("vela-sd-mobile");
29268
+ ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
29269
+ this.root = ui.positioner;
29270
+ this.ui = ui;
29271
+ const panes = this.buildContent(ui, config, section, mobile);
29272
+ ui.show();
29273
+ this.layoutPanes(panes);
29274
+ }
29275
+ /**
29276
+ * Re-seed every control of an OPEN dialog from `config`, in place: the shell stays
29277
+ * (no close/open transition), only the tab rail and panes rebuild, landing back on
29278
+ * the current tab. The reset path — the restored values must show without the
29279
+ * dialog re-entering. No-op while closed.
29280
+ */
29281
+ refresh(config) {
29282
+ const ui = this.ui;
29283
+ if (!ui) return;
29284
+ closeOpenPopovers();
29285
+ closeWidthPopover();
29286
+ for (const dispose of this.hintTips) dispose();
29287
+ this.hintTips = [];
29288
+ this.config = config;
29289
+ ui.body.replaceChildren();
29290
+ this.layoutPanes(this.buildContent(ui, config, this.activeSection ?? void 0, this.mobileLayout));
29291
+ }
29292
+ /** Structured chart-type panes (instance strip / group TOC) own their layout and
29293
+ * tag their rows hosts instead; each host gets its own field grid. Runs on a SHOWN
29294
+ * dialog — the grids measure their labels. */
29295
+ layoutPanes(panes) {
29296
+ for (const el of panes) {
29297
+ const hosts = [...el.querySelectorAll("[data-sd-rows-host]")];
29298
+ if (hosts.length === 0) {
29299
+ this.layoutSettingsGrids(el);
29300
+ continue;
29301
+ }
29302
+ for (const h of hosts) this.layoutSettingsGrids(h);
29130
29303
  }
29304
+ }
29305
+ /**
29306
+ * Build the tab rail + one pane per section into `ui.body` (the linear `body` of
29307
+ * section markers and rows is split afterwards), select `section` (the active
29308
+ * style's own tab when none is asked for), and return the pane elements for
29309
+ * {@link layoutPanes}.
29310
+ */
29311
+ buildContent(ui, config, section, mobile) {
29131
29312
  const body = document.createElement("div");
29132
29313
  body.style.cssText = "display:flex;flex-direction:column;gap:0;";
29133
29314
  const sid = (el, id) => {
@@ -29331,9 +29512,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29331
29512
  if (this.markGroups.length > 0) {
29332
29513
  body.append(sid(this.section("Events"), MARKS_SETTINGS_ID));
29333
29514
  body.append(sid(this.sectionTitle("Visible events"), MARKS_GROUPS_SETTINGS_ID));
29334
- for (const g of this.markGroups) {
29335
- body.append(sid(this.boolRow(g.label, this.markGroupVisible(g.id), (v) => this.emit({ marks: { groups: { [g.id]: v } } })), markGroupSettingsId(g.id)));
29515
+ const rowsById = /* @__PURE__ */ new Map();
29516
+ const rows = markGroupRows(this.markGroups);
29517
+ const refreshDimming = () => {
29518
+ for (const { group } of rows) {
29519
+ const el = rowsById.get(group.id);
29520
+ if (!el || group.parent === void 0) continue;
29521
+ el.classList.toggle("vela-sd-soft", !this.markGroupVisible(group.parent));
29522
+ }
29523
+ };
29524
+ for (const { group: g, depth } of rows) {
29525
+ const row = this.boolRow(g.label, this.markGroupOwnVisible(g.id), (v) => {
29526
+ this.emit({ marks: { groups: { [g.id]: v } } });
29527
+ refreshDimming();
29528
+ });
29529
+ if (depth > 0) {
29530
+ row.classList.add("vela-sd-nested");
29531
+ row.style.setProperty("--vela-sd-depth", String(depth));
29532
+ }
29533
+ rowsById.set(g.id, row);
29534
+ body.append(sid(row, markGroupSettingsId(g.id)));
29336
29535
  }
29536
+ refreshDimming();
29337
29537
  }
29338
29538
  renderChartTypeSections("end");
29339
29539
  renderHostSections("end");
@@ -29361,8 +29561,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29361
29561
  if (mobile) {
29362
29562
  railScrim = document.createElement("div");
29363
29563
  railScrim.className = "vela-sd-railscrim";
29364
- railScrim.addEventListener("click", () => toggleRail?.());
29365
- toggleRail = (open2) => {
29564
+ railScrim.addEventListener("click", () => this.toggleRail?.());
29565
+ this.toggleRail = (open2) => {
29366
29566
  const on = open2 ?? !rail.classList.contains("open");
29367
29567
  rail.classList.toggle("open", on);
29368
29568
  railScrim?.classList.toggle("open", on);
@@ -29402,35 +29602,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29402
29602
  });
29403
29603
  if (hidActive) activate(0);
29404
29604
  };
29405
- const ui = new Dialog({
29406
- host: this.container,
29407
- title: "Chart settings",
29408
- // Non-modal: a live-edit dialog must leave the page interactive — a modal
29409
- // machine locks pointer events on the whole body, killing the chart, the
29410
- // legend, and the body-portaled popovers (color picker, select lists).
29411
- modal: false,
29412
- contained: true,
29413
- align: "top",
29414
- draggable: !mobile,
29415
- flush: true,
29416
- className: "vela-dialog--settings",
29417
- headerStart: burger,
29418
- closeOnBackdrop: true,
29419
- footer: (foot) => {
29420
- foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
29421
- const resetBtn = document.createElement("button");
29422
- resetBtn.type = "button";
29423
- resetBtn.textContent = "Reset defaults";
29424
- resetBtn.className = "vela-sd-btn";
29425
- resetBtn.addEventListener("click", () => this.onReset?.());
29426
- foot.appendChild(resetBtn);
29427
- },
29428
- onOpenChange: (open2) => {
29429
- if (!open2) this.close();
29430
- }
29431
- });
29432
- if (mobile) ui.positioner.classList.add("vela-sd-mobile");
29433
- ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
29434
29605
  const activate = (idx) => {
29435
29606
  panes.forEach((p, i) => {
29436
29607
  p.el.style.display = i === idx ? "block" : "none";
@@ -29439,7 +29610,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29439
29610
  this.activeSection = panes[idx]?.title ?? null;
29440
29611
  if (mobile) {
29441
29612
  ui.titleEl.textContent = panes[idx]?.title ?? "Chart settings";
29442
- toggleRail?.(false);
29613
+ this.toggleRail?.(false);
29443
29614
  }
29444
29615
  };
29445
29616
  panes.forEach((p, i) => {
@@ -29454,17 +29625,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29454
29625
  shell.append(rail, paneHost);
29455
29626
  if (railScrim) shell.append(railScrim);
29456
29627
  ui.body.appendChild(shell);
29457
- this.root = ui.positioner;
29458
- this.ui = ui;
29459
- ui.show();
29460
- for (const p of panes) {
29461
- const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
29462
- if (hosts.length === 0) {
29463
- this.layoutSettingsGrids(p.el);
29464
- continue;
29465
- }
29466
- for (const h of hosts) this.layoutSettingsGrids(h);
29467
- }
29628
+ return panes.map((p) => p.el);
29468
29629
  }
29469
29630
  close() {
29470
29631
  closeOpenPopovers();
@@ -29472,6 +29633,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29472
29633
  const ui = this.ui;
29473
29634
  this.ui = null;
29474
29635
  this.root = null;
29636
+ this.toggleRail = null;
29475
29637
  this.tabs = [];
29476
29638
  for (const dispose of this.hintTips) dispose();
29477
29639
  this.hintTips = [];
@@ -37571,7 +37733,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37571
37733
  }
37572
37734
  this.settingsDialog.setTheme(this.theme);
37573
37735
  this.settingsDialog.setHostSections(this.hostSettingsSections);
37574
- this.settingsDialog.setMarkGroups(this.markGroupsInUse(), (id) => markGroupVisible(this.scene.marks, id, this.scene.markGroups));
37736
+ this.settingsDialog.setMarkGroups(
37737
+ this.markGroupsForEventsTab(),
37738
+ (id) => markGroupVisible2(this.scene.marks, id, this.scene.markGroups),
37739
+ (id) => markGroupOwnVisible(this.scene.marks.groups, id, this.scene.markGroups)
37740
+ );
37575
37741
  this.settingsDialog.setHiddenSettings(this.hiddenSettings);
37576
37742
  this.syncThemeControl();
37577
37743
  this.settingsDialog.toggle(
@@ -37579,13 +37745,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37579
37745
  (patch) => this.applyConfig(patch),
37580
37746
  (json) => this.applyConfig(json),
37581
37747
  () => {
37582
- if (this.factoryConfig) this.applyConfig(factoryResetConfig(this.factoryConfig));
37583
- this.settingsDialog?.close();
37584
- this.openSettingsDialog();
37748
+ if (this.factoryConfig) this.applyConfig(this.factoryResetDocument(this.factoryConfig));
37749
+ this.settingsDialog?.refresh(this.getConfig());
37585
37750
  },
37586
37751
  section
37587
37752
  );
37588
37753
  }
37754
+ /**
37755
+ * The document "Reset defaults" applies: every setting back to its first-run value,
37756
+ * with two things that are NOT settings held or resolved here — the price style
37757
+ * stays the one the user is looking at, and the timeline-mark groups (an additive
37758
+ * merge, like the type bags) are named back to their host-declared visibility.
37759
+ */
37760
+ factoryResetDocument(factory) {
37761
+ const doc = factoryResetConfig(factory, this.scene.priceStyle);
37762
+ const groups = { ...doc.marks.groups };
37763
+ for (const g of this.markGroupsInUse()) groups[g.id] = g.visible !== false;
37764
+ return { ...doc, marks: { ...doc.marks, groups } };
37765
+ }
37589
37766
  /** Close the in-chart dialogs (indicator settings + chart-settings gear). No-op when none are open. */
37590
37767
  closeDialogs() {
37591
37768
  this.inputsUI?.closeOpenDialog();
@@ -38648,6 +38825,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38648
38825
  markGroupsInUse() {
38649
38826
  return effectiveMarkGroups(this.scene.timelineMarks, this.scene.markGroups);
38650
38827
  }
38828
+ /**
38829
+ * The groups as the Events tab lists them: nested only under a DEFINED parent. The
38830
+ * painter's visibility chain resolves parents against the defined groups alone, so a
38831
+ * parent that marks merely name must not nest (and dim) a child the painter still shows.
38832
+ */
38833
+ markGroupsForEventsTab() {
38834
+ const defined = new Set(this.scene.markGroups.map((g) => g.id));
38835
+ return this.markGroupsInUse().map((g) => g.parent !== void 0 && !defined.has(g.parent) ? { ...g, parent: void 0 } : g);
38836
+ }
38651
38837
  onViewportChange(cb) {
38652
38838
  this.viewportCbs.add(cb);
38653
38839
  return () => this.viewportCbs.delete(cb);
@@ -39606,7 +39792,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39606
39792
  const or2 = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
39607
39793
  if (or2) dr = dr ? { min: Math.min(dr.min, or2.min), max: Math.max(dr.max, or2.max) } : or2;
39608
39794
  }
39609
- const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels));
39795
+ const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
39610
39796
  pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
39611
39797
  pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
39612
39798
  pane.axisFormat = void 0;
@@ -39856,6 +40042,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39856
40042
  priceLayersAnchoredToBars(masterModels) {
39857
40043
  return masterModels.some((m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type));
39858
40044
  }
40045
+ /** True when the pane's master content contributes SOMETHING to its autoscale besides
40046
+ * the candles: a series painted on the pane (force_overlay ones scale elsewhere), a
40047
+ * price line, or a measured drawings range. Mirrors what `computePaneScale` considers. */
40048
+ paneHasMeasurableContent(masterModels, drawings) {
40049
+ if (drawings) return true;
40050
+ return masterModels.some((m) => m.priceLines.length > 0 || m.series.some((s) => s.overlay !== true));
40051
+ }
39859
40052
  /** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
39860
40053
  * band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
39861
40054
  * Top-to-bottom order. Every pane is independent — the price pane from the scene setting,