@luxalgo/vela 0.8.2 → 0.8.4

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 (48) hide show
  1. package/dist/{DataProvider-COGDs9Xg.d.cts → DataProvider-Bw58CjYy.d.cts} +1 -1
  2. package/dist/{DataProvider-DpOWZD8F.d.ts → DataProvider-Byo8pEv0.d.ts} +1 -1
  3. package/dist/{chunk-DPAA3SRT.js → chunk-ALTGHKIM.js} +836 -32
  4. package/dist/{chunk-27UTYE7T.js → chunk-AUOB7A5Y.js} +49 -7
  5. package/dist/{chunk-HKNO24GD.js → chunk-I2U4NZD2.js} +56 -1
  6. package/dist/{chunk-R2JMNIHY.js → chunk-MOJ4T26N.js} +341 -169
  7. package/dist/{chunk-T3DZIR5Y.js → chunk-UNUVPQFW.js} +2214 -886
  8. package/dist/{chunk-NLDTVFOI.js → chunk-WY5H4S4I.js} +129 -10
  9. package/dist/{contributions-Cgm9RJYP.d.cts → contributions-BY3zSYiZ.d.ts} +98 -7
  10. package/dist/{contributions-bWgwyiVi.d.ts → contributions-DOm40g5-.d.cts} +98 -7
  11. package/dist/index.cjs +3066 -893
  12. package/dist/index.d.cts +36 -22
  13. package/dist/index.d.ts +36 -22
  14. package/dist/index.js +5 -5
  15. package/dist/{options-NAUHiQEC.d.ts → options-CSXbjMM5.d.cts} +129 -4
  16. package/dist/{options-NAUHiQEC.d.cts → options-CSXbjMM5.d.ts} +129 -4
  17. package/dist/{plugin-CqxWoDKS.d.ts → plugin-BDxUhGO3.d.ts} +4 -4
  18. package/dist/{plugin-CjjzZX2F.d.cts → plugin-CiQVyETg.d.cts} +4 -4
  19. package/dist/plugin.cjs +573 -17
  20. package/dist/plugin.d.cts +5 -5
  21. package/dist/plugin.d.ts +5 -5
  22. package/dist/plugin.js +3 -3
  23. package/dist/providers/binance.d.cts +2 -2
  24. package/dist/providers/binance.d.ts +2 -2
  25. package/dist/providers/coinbase.d.cts +2 -2
  26. package/dist/providers/coinbase.d.ts +2 -2
  27. package/dist/providers/hyperliquid.d.cts +2 -2
  28. package/dist/providers/hyperliquid.d.ts +2 -2
  29. package/dist/{side-panel-wMNfgyZb.d.cts → side-panel-_2UK1gSm.d.cts} +45 -3
  30. package/dist/{side-panel-wMNfgyZb.d.ts → side-panel-_2UK1gSm.d.ts} +45 -3
  31. package/dist/{statusline-model-Dih6FGWP.d.ts → statusline-model-C9tYXuux.d.ts} +19 -4
  32. package/dist/{statusline-model-Dpfpq54A.d.cts → statusline-model-DUBX6Fib.d.cts} +19 -4
  33. package/dist/ui.cjs +224 -14
  34. package/dist/ui.d.cts +119 -87
  35. package/dist/ui.d.ts +119 -87
  36. package/dist/ui.js +3 -3
  37. package/dist/vela.global.js +3073 -900
  38. package/dist/vela.global.min.js +158 -54
  39. package/dist/widget.cjs +3640 -1119
  40. package/dist/widget.d.cts +24 -10
  41. package/dist/widget.d.ts +24 -10
  42. package/dist/widget.js +7 -7
  43. package/dist/workspace.cjs +3639 -1119
  44. package/dist/workspace.d.cts +61 -20
  45. package/dist/workspace.d.ts +61 -20
  46. package/dist/workspace.js +6 -6
  47. package/package.json +1 -1
  48. /package/dist/{chunk-JU4CWEBF.js → chunk-W5YPYZ2P.js} +0 -0
@@ -1,8 +1,8 @@
1
- import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, SecondClock, resolveTimezone, timezoneMenuRows, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, normalizeTimezone, isExchangeTimezone, TypedEventBus, timeframeToMs, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore } from './chunk-T3DZIR5Y.js';
2
- import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, mobilePlacement, widgetAttachments, getDrawingType, inputDeltas } from './chunk-DPAA3SRT.js';
3
- import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-27UTYE7T.js';
4
- import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-NLDTVFOI.js';
5
- import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, announceSurface, icon, categoricalColor } from './chunk-HKNO24GD.js';
1
+ import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, SecondClock, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, TypedEventBus, timeframeToMs, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, CANDLE_OVERRIDE_KEYS } from './chunk-UNUVPQFW.js';
2
+ import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, tzButtonLabel, resolveTimezone, timezoneMenuRows, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, paneScaleAt, composeMenu, priceAxisItems, scaleChoiceOf, timeAxisItems, bodyItems, scaleWrites, invertWrite, settingsSectionOf, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, normalizeTimezone, isExchangeTimezone, statePersistenceHandlers, topbarHas, rendererDefaults, mobilePlacement, widgetAttachments, getDrawingType, inputDeltas } from './chunk-ALTGHKIM.js';
3
+ import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-AUOB7A5Y.js';
4
+ import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-WY5H4S4I.js';
5
+ import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, announceSurface, holdForExit, icon, categoricalColor } from './chunk-I2U4NZD2.js';
6
6
  import { baseOf } from './chunk-W4EJWLEO.js';
7
7
 
8
8
  // src/widget/timeframe.ts
@@ -108,11 +108,11 @@ function timeframeLabel(value) {
108
108
  }
109
109
 
110
110
  // src/widget/layout-picker.ts
111
- var STYLE_ID = "vela-widget-layout-picker-v14";
111
+ var STYLE_ID = "vela-widget-layout-picker-v15";
112
112
  var CSS = `
113
113
  .vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
114
114
  .vela-lp {
115
- background: var(--vela-surface-elev);
115
+ background: var(--vela-surface);
116
116
  color: var(--vela-fg);
117
117
  border: 1px solid var(--vela-border-strong);
118
118
  border-radius: 8px;
@@ -241,6 +241,8 @@ var LayoutPicker = class {
241
241
  constructor(opts) {
242
242
  this.squares = [];
243
243
  this.isOpen = false;
244
+ /** The card's exit animation after a close; a reopen cancels it. */
245
+ this.exit = null;
244
246
  /** Hover preview (1-based rows/cols), null = show current shape. */
245
247
  this.hover = null;
246
248
  this.onDocPointerDown = (e) => {
@@ -335,6 +337,8 @@ var LayoutPicker = class {
335
337
  open() {
336
338
  if (this.isOpen) return;
337
339
  this.isOpen = true;
340
+ this.exit?.cancel();
341
+ this.exit = null;
338
342
  this.hover = null;
339
343
  this.refresh();
340
344
  this.layer.style.display = "";
@@ -349,7 +353,10 @@ var LayoutPicker = class {
349
353
  if (!this.isOpen) return;
350
354
  this.isOpen = false;
351
355
  announceSurface(this.panel, false, "popover", this.opts.trigger);
352
- this.layer.style.display = "none";
356
+ this.exit = holdForExit(this.panel, () => {
357
+ this.exit = null;
358
+ if (!this.isOpen) this.layer.style.display = "none";
359
+ });
353
360
  this.opts.trigger.setAttribute("aria-expanded", "false");
354
361
  this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
355
362
  this.doc.removeEventListener("keydown", this.onDocKeydown, true);
@@ -364,6 +371,7 @@ var LayoutPicker = class {
364
371
  }
365
372
  destroy() {
366
373
  this.close();
374
+ this.exit?.finish();
367
375
  this.infoTip.destroy();
368
376
  this.layer.remove();
369
377
  }
@@ -430,7 +438,7 @@ var LayoutPicker = class {
430
438
  const hostRect = this.opts.host.getBoundingClientRect();
431
439
  const trigRect = this.opts.trigger.getBoundingClientRect();
432
440
  let left = trigRect.left - hostRect.left;
433
- const top = trigRect.bottom - hostRect.top + 4;
441
+ const top = trigRect.bottom - hostRect.top + 8;
434
442
  const width = this.layer.offsetWidth;
435
443
  if (left + width > hostRect.width - 8) left = Math.max(8, hostRect.width - 8 - width);
436
444
  this.layer.style.left = `${left}px`;
@@ -783,7 +791,8 @@ var Topbar = class {
783
791
  triggerId: "vela-topbar-style",
784
792
  host,
785
793
  items: this.styleItems(),
786
- onSelect: (id) => opts.onPriceStyle(id)
794
+ onSelect: (id) => opts.onPriceStyle(id),
795
+ iconBadges: true
787
796
  });
788
797
  }
789
798
  setSymbol(symbol) {
@@ -849,8 +858,14 @@ var Topbar = class {
849
858
  renderLayoutButton(doc) {
850
859
  if (!this.layoutButton) return;
851
860
  this.layoutButton.replaceChildren();
852
- if (iconMarkup("layout")) this.layoutButton.appendChild(iconEl("layout", doc));
853
- else this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
861
+ const glyph = this.opts.layout?.glyph?.();
862
+ if (glyph || iconMarkup("layout")) {
863
+ const icon2 = iconEl("layout", doc);
864
+ if (glyph) icon2.innerHTML = glyph;
865
+ this.layoutButton.appendChild(icon2);
866
+ } else {
867
+ this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
868
+ }
854
869
  this.layoutButton.setAttribute("aria-label", `Layout \u2014 ${this.layoutId ?? ""}`);
855
870
  }
856
871
  renderStyleButton(doc) {
@@ -1704,8 +1719,8 @@ var ObjectTree = class extends SidePanel {
1704
1719
  set groups(next) {
1705
1720
  if (this.chart) this.groupsPerChart.set(this.chart, next);
1706
1721
  }
1707
- toggle(open = this.el.hidden) {
1708
- super.toggle(open);
1722
+ toggle(open = !this.open, instant = false) {
1723
+ super.toggle(open, instant);
1709
1724
  if (open) this.refresh();
1710
1725
  }
1711
1726
  setSymbol(symbol) {
@@ -2739,8 +2754,8 @@ var DataWindow = class extends SidePanel {
2739
2754
  this.unsubs = [];
2740
2755
  injectStyles(STYLE_ID5, CSS5, host.ownerDocument);
2741
2756
  }
2742
- toggle(open = this.el.hidden) {
2743
- super.toggle(open);
2757
+ toggle(open = !this.open, instant = false) {
2758
+ super.toggle(open, instant);
2744
2759
  if (open) this.refresh();
2745
2760
  }
2746
2761
  /** (Re)bind to a chart instance — called after every widget rebuild. */
@@ -2910,7 +2925,7 @@ var PanelDock = class {
2910
2925
  }
2911
2926
  this.pinned = new Set(state.pinned ?? []);
2912
2927
  for (const entry of this.entries) entry.panel.setOverlay(!this.pinned.has(entry.id));
2913
- for (const entry of this.entries) entry.panel.toggle(entry.id === state.open);
2928
+ for (const entry of this.entries) entry.panel.toggle(entry.id === state.open, true);
2914
2929
  this.pendingOpen = state.open && !this.entries.some((e) => e.id === state.open) ? state.open : null;
2915
2930
  }
2916
2931
  /** Drop the contributed panels (the shell destroys its own). */
@@ -2926,7 +2941,7 @@ var PanelDock = class {
2926
2941
  if (this.pinned.has(entry.id)) entry.panel.setOverlay(false);
2927
2942
  entry.panel.onOpenChange = (open) => {
2928
2943
  if (open) {
2929
- for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
2944
+ for (const other of this.entries) if (other !== entry) other.panel.toggle(false, true);
2930
2945
  this.pendingOpen = null;
2931
2946
  }
2932
2947
  this.deps.chrome.setPanelActive(entry.id, open);
@@ -3916,6 +3931,8 @@ function sanitizeState(doc) {
3916
3931
  const favs = d.timeframeFavorites.filter((f) => typeof f === "string");
3917
3932
  if (favs.length > 0) out.timeframeFavorites = favs;
3918
3933
  }
3934
+ const tools = sanitizeDrawingTools(d.drawingTools);
3935
+ if (tools) out.drawingTools = tools;
3919
3936
  const sync = sanitizeSync(d.sync);
3920
3937
  if (sync) out.sync = sync;
3921
3938
  const tracks = sanitizeTrackSizes(d.trackSizes);
@@ -3934,6 +3951,28 @@ function sanitizeExt(raw) {
3934
3951
  }
3935
3952
  return Object.keys(out).length > 0 ? out : null;
3936
3953
  }
3954
+ function sanitizeDrawingTools(raw) {
3955
+ if (raw == null || typeof raw !== "object" || Array.isArray(raw)) return null;
3956
+ const r = raw;
3957
+ const out = {};
3958
+ if (r.defaults != null && typeof r.defaults === "object" && !Array.isArray(r.defaults)) {
3959
+ const defaults = {};
3960
+ for (const [type, value] of Object.entries(r.defaults)) {
3961
+ if (type.length > 0 && value != null && typeof value === "object" && !Array.isArray(value)) defaults[type] = value;
3962
+ }
3963
+ if (Object.keys(defaults).length > 0) out.defaults = defaults;
3964
+ }
3965
+ if (r.templates != null && typeof r.templates === "object" && !Array.isArray(r.templates)) {
3966
+ const templates = {};
3967
+ for (const [type, list] of Object.entries(r.templates)) {
3968
+ if (type.length > 0 && Array.isArray(list) && list.length > 0) templates[type] = list;
3969
+ }
3970
+ if (Object.keys(templates).length > 0) out.templates = templates;
3971
+ }
3972
+ if (r.magnet === "off" || r.magnet === "weak" || r.magnet === "strong") out.magnet = r.magnet;
3973
+ if (typeof r.stay === "boolean") out.stay = r.stay;
3974
+ return Object.keys(out).length > 0 ? out : null;
3975
+ }
3937
3976
  function sanitizeCell(raw) {
3938
3977
  if (raw == null || typeof raw !== "object") return null;
3939
3978
  const c = raw;
@@ -4042,15 +4081,48 @@ function syncTargets(originId, setting, cellIds) {
4042
4081
  function rangesWithin(a, b, epsMs) {
4043
4082
  return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
4044
4083
  }
4045
- var STYLE_SYNC_CONFIG_KEYS = ["layout", "panes", "grid", "priceScale", "crosshair"];
4046
- function styleConfigSlice(config) {
4047
- if (config == null || typeof config !== "object") return null;
4048
- const doc = config;
4049
- const out = {};
4084
+ var STYLE_SYNC_CONFIG_KEYS = [
4085
+ "layout",
4086
+ "panes",
4087
+ "grid",
4088
+ "margins",
4089
+ "priceScale",
4090
+ "crosshair",
4091
+ "animations",
4092
+ "candles",
4093
+ "bars",
4094
+ "line",
4095
+ "area",
4096
+ "baseline",
4097
+ "sessions"
4098
+ ];
4099
+ function styleConfigPatch(origin, follower) {
4100
+ const from = asRecord(origin);
4101
+ const to = asRecord(follower);
4102
+ if (!from || !to) return null;
4103
+ const patch = {};
4050
4104
  for (const key of STYLE_SYNC_CONFIG_KEYS) {
4051
- if (doc[key] != null && typeof doc[key] === "object") out[key] = doc[key];
4105
+ const block = asRecord(from[key]);
4106
+ if (block && JSON.stringify(block) !== JSON.stringify(to[key])) patch[key] = block;
4052
4107
  }
4053
- return Object.keys(out).length > 0 ? out : null;
4108
+ const spacing = asRecord(from.series)?.spacing;
4109
+ if (typeof spacing === "number" && spacing !== asRecord(to.series)?.spacing) patch.series = { spacing };
4110
+ const fromTypes = asRecord(from.chartTypes) ?? {};
4111
+ const toTypes = asRecord(to.chartTypes) ?? {};
4112
+ const types = {};
4113
+ for (const typeId of /* @__PURE__ */ new Set([...Object.keys(fromTypes), ...Object.keys(toTypes)])) {
4114
+ const src = asRecord(fromTypes[typeId]) ?? {};
4115
+ const dst = asRecord(toTypes[typeId]) ?? {};
4116
+ for (const key of CANDLE_OVERRIDE_KEYS) {
4117
+ const value = src[key] ?? null;
4118
+ if (value !== (dst[key] ?? null)) (types[typeId] ?? (types[typeId] = {}))[key] = value;
4119
+ }
4120
+ }
4121
+ if (Object.keys(types).length > 0) patch.chartTypes = types;
4122
+ return Object.keys(patch).length > 0 ? patch : null;
4123
+ }
4124
+ function asRecord(v) {
4125
+ return v != null && typeof v === "object" ? v : null;
4054
4126
  }
4055
4127
 
4056
4128
  // src/workspace/persist.ts
@@ -4812,113 +4884,28 @@ var Watermark = class {
4812
4884
  }
4813
4885
  };
4814
4886
 
4815
- // src/widget/context-menu-model.ts
4816
- var SETTINGS_SECTION = {
4817
- body: "Canvas",
4818
- "price-axis": "Scales and lines",
4819
- "time-axis": "Scales and lines"
4820
- };
4821
- function settingsItem(zone, label) {
4822
- return { id: `settings:${SETTINGS_SECTION[zone]}`, label, separatorBefore: true };
4823
- }
4824
- function settingsSectionOf(id) {
4825
- return id.slice("settings:".length) || void 0;
4826
- }
4827
- var SCALE_CHOICES = [
4828
- ["regular", "Regular"],
4829
- ["percent", "Percent"],
4830
- ["indexed", "Indexed to 100"],
4831
- ["log", "Logarithmic"]
4832
- ];
4833
- function paneScaleAt(panes, y) {
4834
- return panes.find((p) => y >= p.top && y < p.top + p.height) ?? panes[0] ?? null;
4835
- }
4836
- function scaleChoiceOf(scale) {
4837
- if (scale.log) return "log";
4838
- if (scale.mode === "percent") return "percent";
4839
- if (scale.mode === "indexed") return "indexed";
4840
- return "regular";
4841
- }
4842
- function mainScale(pane) {
4843
- return pane === null || pane.kind === "price";
4844
- }
4845
- function scaleWrites(choice, pane) {
4846
- const mode = choice === "percent" ? "percent" : choice === "indexed" ? "indexed" : "price";
4847
- const log = choice === "log";
4848
- if (mainScale(pane)) {
4849
- return [
4850
- ["scaleMode", mode],
4851
- ["logScale", log]
4852
- ];
4853
- }
4854
- return [
4855
- ["scaleMode", { pane: pane.id, mode }],
4856
- ["logScale", { pane: pane.id, value: log }]
4857
- ];
4858
- }
4859
- function invertWrite(next, pane) {
4860
- return mainScale(pane) ? ["invertScale", next] : ["invertScale", { pane: pane.id, value: next }];
4861
- }
4862
- function priceAxisItems(s) {
4863
- return [
4864
- { id: "auto", label: "Auto (fits data to screen)", checked: s.auto },
4865
- { id: "invert", label: "Invert scale", checked: s.invert },
4866
- ...SCALE_CHOICES.map(([choice, label], i) => ({
4867
- id: `scale:${choice}`,
4868
- label,
4869
- checked: s.choice === choice,
4870
- separatorBefore: i === 0
4871
- })),
4872
- {
4873
- id: "labels",
4874
- label: "Labels",
4875
- separatorBefore: true,
4876
- submenu: [
4877
- { id: "toggle:axisLabels", label: "Price axis labels", checked: s.axisLabels },
4878
- { id: "toggle:priceLabel", label: "Last price label", checked: s.priceLabel },
4879
- { id: "toggle:countdown", label: "Countdown to bar close", checked: s.countdown }
4880
- ]
4881
- },
4882
- {
4883
- id: "levels",
4884
- label: "Levels",
4885
- submenu: [{ id: "toggle:currentPriceLine", label: "Last Price Line", checked: s.priceLine }]
4886
- },
4887
- settingsItem("price-axis", "More settings\u2026")
4888
- ];
4889
- }
4890
- function timeAxisItems(timezone) {
4891
- return [
4892
- {
4893
- id: "timezone",
4894
- label: "Time zone",
4895
- submenu: timezoneMenuRows(timezone).map((r) => ({ id: `tz:${r.value}`, label: r.label, checked: r.checked }))
4896
- },
4897
- settingsItem("time-axis", "More settings\u2026")
4898
- ];
4899
- }
4900
- function bodyItems(counts) {
4901
- return [
4902
- { id: "reset-view", label: "Reset chart view" },
4903
- { id: "remove-drawings", label: "Remove drawings", disabled: counts.drawings === 0, separatorBefore: true },
4904
- { id: "remove-indicators", label: "Remove indicators", disabled: counts.indicators === 0 },
4905
- settingsItem("body", "Settings\u2026")
4906
- ];
4907
- }
4908
-
4909
4887
  // src/widget/context-menu.ts
4910
4888
  var PRICE_AXIS_W = 60;
4911
4889
  var TIME_AXIS_H = 26;
4890
+ var OFF_PLOT = { price: null, time: null, paneKind: null };
4912
4891
  var ChartContextMenu = class {
4913
4892
  constructor(host, cbs) {
4914
4893
  this.cbs = cbs;
4915
4894
  this.chart = null;
4895
+ this.offCrosshair = null;
4896
+ /** The bound chart's latest crosshair report — a right-click always follows a pointer
4897
+ * move to its spot, so this is where it landed. */
4898
+ this.crosshair = OFF_PLOT;
4899
+ /** {@link crosshair} as it stood when the menu opened: the pointer moves on through the
4900
+ * menu before a row is picked, and `run` must see the right-clicked spot. */
4901
+ this.pointer = OFF_PLOT;
4916
4902
  this.lastZone = "body";
4917
4903
  /** The pane whose scale the open price-axis menu targets (null ⇒ the main scale). */
4918
4904
  this.lastPane = null;
4919
4905
  this.onContextMenu = (e) => {
4920
4906
  e.preventDefault();
4921
4907
  if (!this.chart) return;
4908
+ this.pointer = this.crosshair;
4922
4909
  this.lastZone = this.zoneOf(e);
4923
4910
  this.lastPane = this.lastZone === "price-axis" ? this.paneAt(e) : null;
4924
4911
  this.menu.setItems(this.itemsFor(this.lastZone));
@@ -4932,15 +4919,23 @@ var ChartContextMenu = class {
4932
4919
  // Pointer-anchored action menu: checked state reads as a leading ✓, not a
4933
4920
  // washed row (which would read as hover in a menu with no trigger button).
4934
4921
  checkmarks: true,
4922
+ iconBadges: true,
4935
4923
  onSelect: (id) => this.run(id)
4936
4924
  });
4937
4925
  host.addEventListener("contextmenu", this.onContextMenu);
4938
4926
  }
4939
4927
  /** (Re)bind to a chart instance — called after every widget rebuild. */
4940
4928
  onChart(chart) {
4929
+ this.offCrosshair?.();
4941
4930
  this.chart = chart;
4931
+ this.crosshair = OFF_PLOT;
4932
+ this.offCrosshair = chart.renderer.onCrosshairMove((e) => {
4933
+ this.crosshair = { price: e.price, time: e.time, paneKind: e.paneKind ?? null };
4934
+ });
4942
4935
  }
4943
4936
  destroy() {
4937
+ this.offCrosshair?.();
4938
+ this.offCrosshair = null;
4944
4939
  this.host.removeEventListener("contextmenu", this.onContextMenu);
4945
4940
  this.menu.destroy();
4946
4941
  }
@@ -4959,49 +4954,52 @@ var ChartContextMenu = class {
4959
4954
  flag(feature) {
4960
4955
  return Boolean(this.chart?.renderer.get(feature));
4961
4956
  }
4962
- contributed(zone) {
4957
+ /** A fresh widget context carrying the pointer captured at open. Copied by property
4958
+ * descriptor: the context's getters must stay LIVE, and a spread would freeze them. */
4959
+ context() {
4963
4960
  const ctx = this.cbs.getContext?.();
4964
- return widgetActions(`context:${zone}`, ctx).map((a, i) => ({
4965
- id: `action:${a.id}`,
4966
- label: a.label,
4967
- icon: a.icon,
4968
- separatorBefore: i === 0
4961
+ if (!ctx) return void 0;
4962
+ return Object.create(Object.getPrototypeOf(ctx), {
4963
+ ...Object.getOwnPropertyDescriptors(ctx),
4964
+ pointer: { value: this.pointer, enumerable: true }
4965
+ });
4966
+ }
4967
+ contributed(zone) {
4968
+ return widgetActions(`context:${zone}`, this.context()).map((a) => ({
4969
+ item: { id: `action:${a.id}`, label: a.label, icon: a.icon },
4970
+ order: a.order
4969
4971
  }));
4970
4972
  }
4971
4973
  itemsFor(zone) {
4974
+ return composeMenu(zone, this.builtinItems(zone), this.contributed(zone));
4975
+ }
4976
+ builtinItems(zone) {
4972
4977
  if (zone === "price-axis") {
4973
4978
  const pane = this.lastPane;
4974
- return [
4975
- ...priceAxisItems({
4976
- auto: this.chart?.renderer.get("autoScale") !== false,
4977
- invert: pane ? pane.invert : this.flag("invertScale"),
4978
- choice: scaleChoiceOf(pane ?? { mode: String(this.chart?.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
4979
- axisLabels: this.flag("axisLabels"),
4980
- priceLabel: this.flag("priceLabel"),
4981
- countdown: this.flag("countdown"),
4982
- priceLine: this.flag("currentPriceLine")
4983
- }),
4984
- ...this.contributed(zone)
4985
- ];
4979
+ return priceAxisItems({
4980
+ auto: this.chart?.renderer.get("autoScale") !== false,
4981
+ invert: pane ? pane.invert : this.flag("invertScale"),
4982
+ choice: scaleChoiceOf(pane ?? { mode: String(this.chart?.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
4983
+ axisLabels: this.flag("axisLabels"),
4984
+ priceLabel: this.flag("priceLabel"),
4985
+ countdown: this.flag("countdown"),
4986
+ priceLine: this.flag("currentPriceLine")
4987
+ });
4986
4988
  }
4987
4989
  if (zone === "time-axis") {
4988
- const tz = this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC");
4989
- return [...timeAxisItems(tz), ...this.contributed("time-axis")];
4990
+ return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"));
4990
4991
  }
4991
4992
  const chart = this.chart;
4992
- return [
4993
- ...bodyItems({
4994
- drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
4995
- indicators: chart?.indicators().length ?? 0
4996
- }),
4997
- ...this.contributed("body")
4998
- ];
4993
+ return bodyItems({
4994
+ drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
4995
+ indicators: chart?.indicators().length ?? 0
4996
+ });
4999
4997
  }
5000
4998
  run(id) {
5001
4999
  const chart = this.chart;
5002
5000
  if (!chart) return;
5003
5001
  if (id.startsWith("action:")) {
5004
- const ctx = this.cbs.getContext?.();
5002
+ const ctx = this.context();
5005
5003
  if (ctx) widgetActions(`context:${this.lastZone}`, ctx).find((a) => a.id === id.slice("action:".length))?.run(ctx);
5006
5004
  return;
5007
5005
  }
@@ -5651,8 +5649,8 @@ function seedDefaults(opts) {
5651
5649
  };
5652
5650
  }
5653
5651
  function cellChartDefaults(opts) {
5654
- const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings } = opts;
5655
- return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings };
5652
+ const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis } = opts;
5653
+ return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis };
5656
5654
  }
5657
5655
  function cellDrawings(opt) {
5658
5656
  if (opt === false) return false;
@@ -6222,12 +6220,14 @@ var ChartCell = class {
6222
6220
  this.watermarkOn = visible;
6223
6221
  this.watermark?.setVisible(visible);
6224
6222
  this.deps.onStateDirty();
6223
+ this.deps.onStatusPrefsChanged(this.id);
6225
6224
  }
6226
6225
  /** Show/hide the "Replay" line under this cell's watermark while it replays (persisted per cell). */
6227
6226
  setReplayWatermarkVisible(visible) {
6228
6227
  this.replayWatermarkOn = visible;
6229
6228
  this.watermark?.setReplayVisible(visible);
6230
6229
  this.deps.onStateDirty();
6230
+ this.deps.onStatusPrefsChanged(this.id);
6231
6231
  }
6232
6232
  /** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
6233
6233
  setIndicatorTitlesVisible(visible) {
@@ -6248,18 +6248,20 @@ var ChartCell = class {
6248
6248
  this.statusline?.setPartVisible(part, visible);
6249
6249
  this.deps.onStatusPrefsChanged(this.id);
6250
6250
  }
6251
- /** This cell's Status line tab prefs as one bundle (see {@link CellStatusPrefs}). */
6251
+ /** This cell's Status line tab and watermark prefs as one bundle (see {@link CellStatusPrefs}). */
6252
6252
  statusPrefs() {
6253
6253
  const sl = this.statusline;
6254
6254
  return {
6255
6255
  parts: sl ? { logo: sl.partVisible("logo"), name: sl.partVisible("name"), market: sl.partVisible("market"), ohlc: sl.partVisible("ohlc"), change: sl.partVisible("change") } : null,
6256
6256
  indicatorTitles: this.indicatorTitlesOn,
6257
- indicatorValues: this.indicatorValuesOn
6257
+ indicatorValues: this.indicatorValuesOn,
6258
+ watermark: this.watermarkOn,
6259
+ replayWatermark: this.replayWatermarkOn
6258
6260
  };
6259
6261
  }
6260
- /** Converge this cell's Status line tab prefs to `prefs` — the follower half of
6261
- * the workspace's style link. Idempotent: matching values change nothing, so a
6262
- * propagated echo dies on its own. */
6262
+ /** Converge this cell's Status line tab and watermark prefs to `prefs` — the
6263
+ * follower half of the workspace's style link. Idempotent: matching values change
6264
+ * nothing, so a propagated echo dies on its own. */
6263
6265
  applyStatusPrefs(prefs) {
6264
6266
  if (prefs.parts && this.statusline) {
6265
6267
  for (const part of Object.keys(prefs.parts)) {
@@ -6268,6 +6270,8 @@ var ChartCell = class {
6268
6270
  }
6269
6271
  if (prefs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(prefs.indicatorTitles);
6270
6272
  if (prefs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(prefs.indicatorValues);
6273
+ if (prefs.watermark !== this.watermarkOn) this.setWatermarkVisible(prefs.watermark);
6274
+ if (prefs.replayWatermark !== this.replayWatermarkOn) this.setReplayWatermarkVisible(prefs.replayWatermark);
6271
6275
  }
6272
6276
  /** The LIVE chart of this cell — never cache it across a layout change (the cell's
6273
6277
  * identity is what endures; the chart dies with the cell). */
@@ -7139,6 +7143,55 @@ function occupancyGrid(def) {
7139
7143
  const cols = def.cols.length;
7140
7144
  return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `\xB7${r}x${c}`));
7141
7145
  }
7146
+ var GLYPH_MIN = 1.5;
7147
+ var GLYPH_SPAN = 13;
7148
+ function glyphStops(weights, n) {
7149
+ const w = Array.from({ length: n }, (_, i) => {
7150
+ const v = weights[i];
7151
+ return v !== void 0 && Number.isFinite(v) && v > 0 ? v : 1;
7152
+ });
7153
+ const total = w.reduce((a, b) => a + b, 0);
7154
+ const stops = [GLYPH_MIN];
7155
+ let acc = 0;
7156
+ for (const v of w) {
7157
+ acc += v;
7158
+ stops.push(Math.round((GLYPH_MIN + GLYPH_SPAN * acc / total) * 100) / 100);
7159
+ }
7160
+ return stops;
7161
+ }
7162
+ function layoutGlyph(def) {
7163
+ const grid = occupancyGrid(def);
7164
+ const rows = grid.length;
7165
+ const cols = grid.reduce((n, row) => Math.max(n, row.length), 0);
7166
+ const xs = glyphStops(def.cols, cols);
7167
+ const ys = glyphStops(def.rows, rows);
7168
+ const at = (r, c) => grid[r]?.[c];
7169
+ const seams = [];
7170
+ for (let c = 1; c < cols; c += 1) {
7171
+ let start = -1;
7172
+ for (let r = 0; r <= rows; r += 1) {
7173
+ const split = r < rows && at(r, c - 1) !== at(r, c);
7174
+ if (split && start < 0) start = r;
7175
+ if (!split && start >= 0) {
7176
+ seams.push(`M${xs[c]} ${ys[start]}V${ys[r]}`);
7177
+ start = -1;
7178
+ }
7179
+ }
7180
+ }
7181
+ for (let r = 1; r < rows; r += 1) {
7182
+ let start = -1;
7183
+ for (let c = 0; c <= cols; c += 1) {
7184
+ const split = c < cols && at(r - 1, c) !== at(r, c);
7185
+ if (split && start < 0) start = c;
7186
+ if (!split && start >= 0) {
7187
+ seams.push(`M${xs[start]} ${ys[r]}H${xs[c]}`);
7188
+ start = -1;
7189
+ }
7190
+ }
7191
+ }
7192
+ const frame = `<rect x="${GLYPH_MIN}" y="${GLYPH_MIN}" width="${GLYPH_SPAN}" height="${GLYPH_SPAN}" rx="1.5"/>`;
7193
+ return svg16(seams.length > 0 ? `${frame}<path d="${seams.join("")}"/>` : frame);
7194
+ }
7142
7195
  function gridStyles(def, trackSizes) {
7143
7196
  const cols = trackSizes?.cols?.length === def.cols.length ? trackSizes.cols : def.cols;
7144
7197
  const rows = trackSizes?.rows?.length === def.rows.length ? trackSizes.rows : def.rows;
@@ -8049,7 +8102,11 @@ var MoreDrawer = class {
8049
8102
  row(doc, label, opts) {
8050
8103
  const el = doc.createElement("div");
8051
8104
  el.className = "vela-md-row";
8052
- if (opts.icon) el.appendChild(iconEl(opts.icon, doc));
8105
+ if (opts.icon) {
8106
+ const icon2 = iconEl(opts.icon, doc);
8107
+ if (opts.glyph) icon2.innerHTML = opts.glyph;
8108
+ el.appendChild(icon2);
8109
+ }
8053
8110
  const text = doc.createElement("span");
8054
8111
  text.className = "vela-md-row-label";
8055
8112
  text.textContent = label;
@@ -8091,7 +8148,7 @@ var MoreDrawer = class {
8091
8148
  if (this.opts.layout) {
8092
8149
  const shape = this.opts.layout.shape();
8093
8150
  const value = shape ? `${shape.cols} \xD7 ${shape.rows}` : this.opts.layout.presets().find((p) => p.checked)?.label;
8094
- list.appendChild(this.row(doc, "Layout", { icon: "layout", value, chevron: true, onClick: () => this.show("layout") }));
8151
+ list.appendChild(this.row(doc, "Layout", { icon: "layout", glyph: this.opts.layout.glyph?.(), value, chevron: true, onClick: () => this.show("layout") }));
8095
8152
  }
8096
8153
  for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
8097
8154
  for (const panel of this.opts.panels()) {
@@ -8761,6 +8818,12 @@ var VelaWorkspace = class {
8761
8818
  this.historyUnsub = null;
8762
8819
  /** Favorite drawing tools — a WORKSPACE preference (one star set, every cell). */
8763
8820
  this.favs = [];
8821
+ /** Every drawing tool's remembered settings — a SHARED pref, mirrored onto every cell. */
8822
+ this.toolDefs = {};
8823
+ /** Every drawing tool's saved looks — a SHARED pref like the remembered settings. */
8824
+ this.toolTemplates = {};
8825
+ /** Re-entrancy guard while one cell's remembered settings fan out to the others. */
8826
+ this.toolDefsBusy = false;
8764
8827
  /** Favorite timeframes — the shared topbar's quick-switch chips, one set for the grid. */
8765
8828
  this.tfFavs = [];
8766
8829
  /** Live sync configuration (mutable copy of the option). */
@@ -8864,6 +8927,7 @@ var VelaWorkspace = class {
8864
8927
  this.timezone = boot?.timezone ?? opts.timezone ?? "Etc/UTC";
8865
8928
  if (boot?.favorites) this.favs = [...boot.favorites];
8866
8929
  if (boot?.timeframeFavorites) this.tfFavs = [...boot.timeframeFavorites];
8930
+ if (boot?.drawingTools) this.adoptDrawingTools(boot.drawingTools);
8867
8931
  const sync = boot?.sync ?? opts.sync;
8868
8932
  for (const kind of SYNC_KINDS) this.applySyncSetting(kind, sync?.[kind]);
8869
8933
  this.monoLayout = opts.layout === false;
@@ -8955,7 +9019,8 @@ var VelaWorkspace = class {
8955
9019
  onToggleSync: (id) => {
8956
9020
  const kind = id;
8957
9021
  this.sync.set(kind, this.syncOpts[kind] ? false : true);
8958
- }
9022
+ },
9023
+ glyph: () => layoutGlyph(this.def)
8959
9024
  },
8960
9025
  getContext: () => this.context()
8961
9026
  });
@@ -9270,6 +9335,8 @@ var VelaWorkspace = class {
9270
9335
  if (this.activeId) state.activeCellId = this.activeId;
9271
9336
  if (this.favs.length > 0) state.favorites = [...this.favs];
9272
9337
  if (this.tfFavs.length > 0) state.timeframeFavorites = [...this.tfFavs];
9338
+ const tools = this.drawingToolsState();
9339
+ if (tools) state.drawingTools = tools;
9273
9340
  if (this.trackSizes.size > 0) state.trackSizes = Object.fromEntries([...this.trackSizes].map(([k, v]) => [k, { ...v }]));
9274
9341
  const panels = this.dock.getState();
9275
9342
  if (panels) state.panels = panels;
@@ -9303,6 +9370,7 @@ var VelaWorkspace = class {
9303
9370
  const st = sanitizeState(state);
9304
9371
  if (!st) return;
9305
9372
  if (st.favorites) this.favs = [...st.favorites];
9373
+ if (st.drawingTools) this.adoptDrawingTools(st.drawingTools);
9306
9374
  if (st.timeframeFavorites) {
9307
9375
  this.tfFavs = [...st.timeframeFavorites];
9308
9376
  this.topbar.setTimeframeFavorites(this.tfFavs);
@@ -9318,6 +9386,7 @@ var VelaWorkspace = class {
9318
9386
  if (st.favorites) {
9319
9387
  for (const cell of this.cellsById.values()) cell.chart.drawings.setFavorites(this.favs);
9320
9388
  }
9389
+ if (st.drawingTools) this.pushToolDefaults(null);
9321
9390
  this.drawingLinks.clear();
9322
9391
  this.styleSyncBusy = true;
9323
9392
  try {
@@ -9454,6 +9523,7 @@ var VelaWorkspace = class {
9454
9523
  this.applyGrid();
9455
9524
  this.buildCells();
9456
9525
  this.alignNewCellStyles(preexisting);
9526
+ this.alignNewCellDrawings(preexisting);
9457
9527
  this.syncCellPresentation();
9458
9528
  this.refreshCellControls();
9459
9529
  this.topbar.setLayout(next.id);
@@ -9632,6 +9702,37 @@ var VelaWorkspace = class {
9632
9702
  refocusActive() {
9633
9703
  if (this.activeId) this.cellsById.get(this.activeId)?.chart.renderer.focus();
9634
9704
  }
9705
+ /** Take a document's drawing-tool prefs as the shared mirrors (cells pick them up as they
9706
+ * are built, or from {@link pushToolDefaults} / the active-cell projection). */
9707
+ adoptDrawingTools(tools) {
9708
+ if (tools.defaults) this.toolDefs = { ...tools.defaults };
9709
+ if (tools.templates) this.toolTemplates = { ...tools.templates };
9710
+ if (tools.magnet) this.globalSnap = tools.magnet;
9711
+ if (tools.stay !== void 0) this.globalStay = tools.stay;
9712
+ }
9713
+ /** Mirror the shared remembered settings onto every cell but `from` (the cell they came from).
9714
+ * Receiving them is not an edit: the cells' `drawing:defaults` echoes are ignored. */
9715
+ pushToolDefaults(from, cells = this.cellsById.values()) {
9716
+ this.toolDefsBusy = true;
9717
+ try {
9718
+ for (const other of cells) {
9719
+ if (other === from) continue;
9720
+ other.chart.drawings.setToolDefaults(this.toolDefs);
9721
+ other.chart.drawings.setToolTemplates(this.toolTemplates);
9722
+ }
9723
+ } finally {
9724
+ this.toolDefsBusy = false;
9725
+ }
9726
+ }
9727
+ /** The drawing-tool prefs worth saving; null when every one is at its default. */
9728
+ drawingToolsState() {
9729
+ const out = {};
9730
+ if (Object.keys(this.toolDefs).length > 0) out.defaults = { ...this.toolDefs };
9731
+ if (Object.keys(this.toolTemplates).length > 0) out.templates = { ...this.toolTemplates };
9732
+ if (this.globalSnap !== "off") out.magnet = this.globalSnap;
9733
+ if (this.globalStay) out.stay = true;
9734
+ return Object.keys(out).length > 0 ? out : null;
9735
+ }
9635
9736
  /** Debounced dirty mark: one `state:changed` (+ one storage write in persist mode)
9636
9737
  * per burst of edits, flushed hard on unload/destroy. */
9637
9738
  markStateDirty() {
@@ -9854,6 +9955,7 @@ var VelaWorkspace = class {
9854
9955
  cell.chart.renderer.setWallClock(this.clock);
9855
9956
  cell.setControlsSuspended(this.layoutCtl.current === "mobile");
9856
9957
  if (this.favs.length > 0) cell.chart.drawings.setFavorites(this.favs);
9958
+ if (Object.keys(this.toolDefs).length > 0 || Object.keys(this.toolTemplates).length > 0) this.pushToolDefaults(null, [cell]);
9857
9959
  cell.setManifest(this.manifest, pooled?.indicators == null);
9858
9960
  cell.restorePersistedExt();
9859
9961
  this.events.emit("cell:created", { id });
@@ -9870,6 +9972,7 @@ var VelaWorkspace = class {
9870
9972
  const chart = cell.chart;
9871
9973
  chart.on("indicator:error", ({ error }) => this.toastHost.show(`[${cell.id}] ${error.message}`, "error", 5e3));
9872
9974
  chart.on("script:run", (run) => this.events.emit("script:run", { ...run, cell: cell.id }));
9975
+ chart.on("priceStyle:change", ({ from, to }) => this.events.emit("cell:priceStyle", { id: cell.id, from, to }));
9873
9976
  chart.on("alert", (alert) => {
9874
9977
  const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe), alert.indicator].filter(Boolean).join(" ");
9875
9978
  this.alerts.unshift({ cellId: cell.id, source, title: alert.title ?? "Alert", message: alert.message, time: alert.time });
@@ -9885,6 +9988,18 @@ var VelaWorkspace = class {
9885
9988
  this.drawToolbar?.setFavorites(favorites);
9886
9989
  this.markStateDirty();
9887
9990
  });
9991
+ chart.on("drawing:defaults", () => {
9992
+ if (this.toolDefsBusy) return;
9993
+ this.toolDefs = chart.drawings.toolDefaults();
9994
+ this.pushToolDefaults(cell);
9995
+ this.markStateDirty();
9996
+ });
9997
+ chart.on("drawing:templates", () => {
9998
+ if (this.toolDefsBusy) return;
9999
+ this.toolTemplates = chart.drawings.toolTemplates();
10000
+ this.pushToolDefaults(cell);
10001
+ this.markStateDirty();
10002
+ });
9888
10003
  chart.renderer.onCrosshairMove((e) => this.propagateCrosshair(cell.id, e.time, e.paneKind === "price" ? e.price : null));
9889
10004
  chart.on("drawing:created", ({ id }) => {
9890
10005
  this.propagateDrawing(cell.id, id);
@@ -9906,13 +10021,17 @@ var VelaWorkspace = class {
9906
10021
  });
9907
10022
  chart.on("drawing:snap", ({ mode }) => {
9908
10023
  if (cell.id !== this.activeId) return;
10024
+ const changed = mode !== this.globalSnap;
9909
10025
  this.globalSnap = mode;
9910
10026
  this.drawToolbar?.setMagnetMode(mode);
10027
+ if (changed) this.markStateDirty();
9911
10028
  });
9912
10029
  chart.on("drawing:stay", ({ on }) => {
9913
10030
  if (cell.id !== this.activeId) return;
10031
+ const changed = on !== this.globalStay;
9914
10032
  this.globalStay = on;
9915
10033
  this.drawToolbar?.setStayMode(on);
10034
+ if (changed) this.markStateDirty();
9916
10035
  });
9917
10036
  chart.on("drawing:mode", ({ mode }) => {
9918
10037
  if (cell.id !== this.activeId) return;
@@ -9987,12 +10106,66 @@ var VelaWorkspace = class {
9987
10106
  }
9988
10107
  }
9989
10108
  /**
9990
- * Mirror an origin cell's presentation — the Canvas + Scales-and-lines slice of
9991
- * its renderer config plus its Status line tab prefs — onto its same-group
9992
- * followers (the style link). Loop-safe two ways: the busy guard eats the
9993
- * followers' SYNCHRONOUS echoes (their `applyConfig` re-fires `onConfigChanged`
9994
- * in the same tick), and the equality short-circuits leave already-converged
9995
- * followers untouched, so nothing re-emits once the group agrees.
10109
+ * Bring cells minted by a layout change into their drawings group: with the link
10110
+ * on, a NEW cell (fresh slot, or one returning from the pool that missed edits
10111
+ * while dormant) receives the drawings of a pre-existing group peer — the active
10112
+ * cell when it is one. A drawing the arriving cell already holds a linked copy of
10113
+ * is refreshed in place; any other is copied and linked like a freshly synced
10114
+ * drawing, so later edits and removals follow both ways. The arrival is the cell's
10115
+ * starting state, not an edit: it stays out of the cell's undo timeline, and the
10116
+ * busy guard keeps the copies' own events from fanning back out.
10117
+ */
10118
+ alignNewCellDrawings(preexisting) {
10119
+ const setting = this.syncOpts.drawings;
10120
+ if (!setting) return;
10121
+ const ids = [...this.cellsById.keys()];
10122
+ for (const id of ids) {
10123
+ if (preexisting.has(id)) continue;
10124
+ const cell = this.cellsById.get(id);
10125
+ if (!cell?.chart.drawings.supported) continue;
10126
+ const peers = syncTargets(id, setting, ids).filter((p) => preexisting.has(p));
10127
+ if (peers.length === 0) continue;
10128
+ const sourceId = this.activeId && peers.includes(this.activeId) ? this.activeId : peers[0];
10129
+ const source = this.cellsById.get(sourceId);
10130
+ if (!source) continue;
10131
+ const drawings = cell.chart.drawings;
10132
+ this.drawingSyncBusy = true;
10133
+ try {
10134
+ cell.history.silently(() => {
10135
+ const held = new Set(drawings.all().map((d) => d.id));
10136
+ for (const doc of source.chart.drawings.all()) {
10137
+ const group = this.drawingLinks.get(`${sourceId}\0${doc.id}`) ?? /* @__PURE__ */ new Map([[sourceId, doc.id]]);
10138
+ const peerId = group.get(id);
10139
+ if (peerId != null && held.has(peerId)) {
10140
+ drawings.update(peerId, { anchors: doc.anchors, style: doc.style, text: doc.text, props: doc.props });
10141
+ continue;
10142
+ }
10143
+ const copy = drawings.add(doc.type, {
10144
+ paneId: doc.paneId,
10145
+ anchors: doc.anchors,
10146
+ style: doc.style,
10147
+ text: doc.text,
10148
+ props: doc.props,
10149
+ zIndex: doc.zIndex
10150
+ });
10151
+ if (!copy) continue;
10152
+ if (peerId != null) this.drawingLinks.delete(`${id}\0${peerId}`);
10153
+ group.set(id, copy.id);
10154
+ for (const [cellId, dId] of group) this.drawingLinks.set(`${cellId}\0${dId}`, group);
10155
+ }
10156
+ });
10157
+ } finally {
10158
+ this.drawingSyncBusy = false;
10159
+ }
10160
+ }
10161
+ }
10162
+ /**
10163
+ * Mirror an origin cell's presentation — the style-link slice of its renderer
10164
+ * config ({@link styleConfigPatch}) plus its Status line and watermark prefs —
10165
+ * onto its same-group followers (the style link). Loop-safe two ways: the busy
10166
+ * guard eats the followers' SYNCHRONOUS echoes (their `applyConfig` re-fires
10167
+ * `onConfigChanged` in the same tick), and the equality short-circuits leave
10168
+ * already-converged followers untouched, so nothing re-emits once the group agrees.
9996
10169
  */
9997
10170
  propagateStylePrefs(originId) {
9998
10171
  if (this.styleSyncBusy || this.destroyed) return;
@@ -10000,17 +10173,15 @@ var VelaWorkspace = class {
10000
10173
  if (targets.length === 0) return;
10001
10174
  const origin = this.cellsById.get(originId);
10002
10175
  if (!origin) return;
10003
- const slice = styleConfigSlice(origin.chart.renderer.getConfig());
10004
- const sliceJson = slice ? JSON.stringify(slice) : null;
10176
+ const config = origin.chart.renderer.getConfig();
10005
10177
  const prefs = origin.statusPrefs();
10006
10178
  this.styleSyncBusy = true;
10007
10179
  try {
10008
10180
  for (const id of targets) {
10009
10181
  const cell = this.cellsById.get(id);
10010
10182
  if (!cell) continue;
10011
- if (slice && sliceJson !== JSON.stringify(styleConfigSlice(cell.chart.renderer.getConfig()))) {
10012
- cell.chart.renderer.applyConfig(slice);
10013
- }
10183
+ const patch = styleConfigPatch(config, cell.chart.renderer.getConfig());
10184
+ if (patch) cell.chart.renderer.applyConfig(patch);
10014
10185
  cell.applyStatusPrefs(prefs);
10015
10186
  }
10016
10187
  } finally {
@@ -10331,7 +10502,8 @@ var VelaWorkspace = class {
10331
10502
  onToggleSync: (id) => {
10332
10503
  const kind = id;
10333
10504
  this.sync.set(kind, this.syncOpts[kind] ? false : true);
10334
- }
10505
+ },
10506
+ glyph: () => layoutGlyph(this.def)
10335
10507
  },
10336
10508
  onOpenChange: (open) => this.trackDialog(open)
10337
10509
  }));