@luxalgo/vela 0.6.7 → 0.6.8
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.
- package/dist/{chunk-WVP4XV5A.js → chunk-N7LGMKCE.js} +303 -78
- package/dist/{chunk-6WFKTAT4.js → chunk-SQETIBFU.js} +46 -8
- package/dist/{contributions-Z12BrWCy.d.ts → contributions-DLLdV9jD.d.ts} +13 -0
- package/dist/{contributions-Hv4_cOJo.d.cts → contributions-Vw-Hm58R.d.cts} +13 -0
- package/dist/index.cjs +46 -8
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/{plugin-BXhDHTYn.d.cts → plugin-BnJgjLAy.d.cts} +1 -1
- package/dist/{plugin-9koBjc5H.d.ts → plugin-CC7rBrOY.d.ts} +1 -1
- package/dist/plugin.d.cts +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/{bottombar-Br4rzx_R.d.ts → statusline-CGkB2EOo.d.ts} +87 -4
- package/dist/{bottombar-Div5zibZ.d.cts → statusline-DqzBqy42.d.cts} +87 -4
- package/dist/vela.global.js +46 -8
- package/dist/vela.global.min.js +5 -5
- package/dist/widget.cjs +348 -85
- package/dist/widget.d.cts +10 -83
- package/dist/widget.d.ts +10 -83
- package/dist/widget.js +3 -3
- package/dist/workspace.cjs +348 -85
- package/dist/workspace.d.cts +52 -8
- package/dist/workspace.d.ts +52 -8
- package/dist/workspace.js +2 -2
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-
|
|
1
|
+
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-SQETIBFU.js';
|
|
2
2
|
import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, widgetAttachments, getDrawingType } from './chunk-L3I2CCYO.js';
|
|
3
3
|
import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-2R7BANEM.js';
|
|
4
4
|
import { Menu, Dialog, applyPlotOverlayTokens, ensureUIHost } from './chunk-OICPLNU7.js';
|
|
@@ -599,6 +599,11 @@ var Topbar = class {
|
|
|
599
599
|
/** Overrides that LEFT their native slot (default side + a declared `order`) — they
|
|
600
600
|
* render through the flow cluster like ordinary actions. */
|
|
601
601
|
this.flowingOverrides = /* @__PURE__ */ new Set();
|
|
602
|
+
/** Tooltips of the CURRENT icon-only action buttons — rebuilt with every
|
|
603
|
+
* renderActions pass (contributed buttons are replaceChildren'd away). */
|
|
604
|
+
this.actionTooltips = [];
|
|
605
|
+
/** `iconOnly` misuse warned once per action id (renderActions re-runs freely). */
|
|
606
|
+
this.warnedIconless = /* @__PURE__ */ new Set();
|
|
602
607
|
this.onHairlineSync = () => {
|
|
603
608
|
if (this.hairlineRaf) return;
|
|
604
609
|
const win = this.el.ownerDocument.defaultView;
|
|
@@ -861,6 +866,8 @@ var Topbar = class {
|
|
|
861
866
|
this.actionsHost.replaceChildren();
|
|
862
867
|
this.leftActionsHost.replaceChildren();
|
|
863
868
|
for (const pin of this.pinned.values()) pin.host.replaceChildren();
|
|
869
|
+
for (const t of this.actionTooltips) t.destroy();
|
|
870
|
+
this.actionTooltips = [];
|
|
864
871
|
const doc = this.actionsHost.ownerDocument;
|
|
865
872
|
const flowLeft = this.comp.left.includes("actions");
|
|
866
873
|
const flowRight = this.comp.right.includes("actions");
|
|
@@ -870,10 +877,20 @@ var Topbar = class {
|
|
|
870
877
|
if (!pin && builtin.has(action.id) && !this.flowingOverrides.has(action.id)) continue;
|
|
871
878
|
const left = pin ? pin.left : action.align === "left";
|
|
872
879
|
if (!pin && !(left ? flowLeft : flowRight)) continue;
|
|
880
|
+
const iconOnly = action.iconOnly === true && !!action.icon;
|
|
881
|
+
if (action.iconOnly === true && !action.icon && !this.warnedIconless.has(action.id)) {
|
|
882
|
+
this.warnedIconless.add(action.id);
|
|
883
|
+
console.warn(`[vela] widget action "${action.id}": iconOnly needs an \`icon\` \u2014 rendering the label instead.`);
|
|
884
|
+
}
|
|
873
885
|
const b = doc.createElement("button");
|
|
874
|
-
b.className = left ? "vela-widget-action-left" : "vela-widget-action";
|
|
886
|
+
b.className = left ? "vela-widget-action-left" : iconOnly ? "vela-widget-tool" : "vela-widget-action";
|
|
875
887
|
if (action.icon) b.appendChild(iconEl(action.icon, doc));
|
|
876
|
-
|
|
888
|
+
if (iconOnly) {
|
|
889
|
+
b.setAttribute("aria-label", action.label);
|
|
890
|
+
this.actionTooltips.push(new Tooltip(b, { content: action.label, triggerId: `vela-action-${action.id}`, host: this.host }));
|
|
891
|
+
} else {
|
|
892
|
+
b.appendChild(doc.createTextNode(action.label));
|
|
893
|
+
}
|
|
877
894
|
b.addEventListener("click", () => {
|
|
878
895
|
const c = this.opts.getContext?.();
|
|
879
896
|
if (c) action.run(c);
|
|
@@ -924,7 +941,7 @@ var Topbar = class {
|
|
|
924
941
|
this.tfMenu.destroy();
|
|
925
942
|
this.styleMenu.destroy();
|
|
926
943
|
this.layoutPicker?.destroy();
|
|
927
|
-
for (const t of [...this.tooltips, ...this.panelTooltips]) t.destroy();
|
|
944
|
+
for (const t of [...this.tooltips, ...this.panelTooltips, ...this.actionTooltips]) t.destroy();
|
|
928
945
|
this.el.remove();
|
|
929
946
|
}
|
|
930
947
|
/** One icon-only tool button with its kit tooltip, parked in `sink` for disposal. */
|
|
@@ -3667,19 +3684,8 @@ function indicatorLedger(i) {
|
|
|
3667
3684
|
};
|
|
3668
3685
|
}
|
|
3669
3686
|
|
|
3670
|
-
// src/workspace/sync.ts
|
|
3671
|
-
function syncTargets(originId, setting, cellIds) {
|
|
3672
|
-
if (setting == null || setting === false) return [];
|
|
3673
|
-
if (setting === true) return cellIds.filter((id) => id !== originId);
|
|
3674
|
-
const group = setting[originId];
|
|
3675
|
-
if (group == null) return [];
|
|
3676
|
-
return cellIds.filter((id) => id !== originId && setting[id] === group);
|
|
3677
|
-
}
|
|
3678
|
-
function rangesWithin(a, b, epsMs) {
|
|
3679
|
-
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
3680
|
-
}
|
|
3681
|
-
|
|
3682
3687
|
// src/state/document.ts
|
|
3688
|
+
var SYNC_KINDS = ["viewport", "symbol", "timeframe", "crosshair", "drawings", "style"];
|
|
3683
3689
|
function prefixedSymbol(cell) {
|
|
3684
3690
|
if (!cell?.symbol) return void 0;
|
|
3685
3691
|
if (cell.symbol.includes(":") || !cell.provider) return cell.symbol;
|
|
@@ -3767,7 +3773,7 @@ function sanitizeSync(raw) {
|
|
|
3767
3773
|
if (raw == null || typeof raw !== "object") return null;
|
|
3768
3774
|
const s = raw;
|
|
3769
3775
|
const out = {};
|
|
3770
|
-
for (const kind of
|
|
3776
|
+
for (const kind of SYNC_KINDS) {
|
|
3771
3777
|
const v = s[kind];
|
|
3772
3778
|
if (v === true) out[kind] = true;
|
|
3773
3779
|
else if (v != null && typeof v === "object") {
|
|
@@ -3810,6 +3816,28 @@ function sanitizeTrackSizes(raw) {
|
|
|
3810
3816
|
return Object.keys(out).length > 0 ? out : null;
|
|
3811
3817
|
}
|
|
3812
3818
|
|
|
3819
|
+
// src/workspace/sync.ts
|
|
3820
|
+
function syncTargets(originId, setting, cellIds) {
|
|
3821
|
+
if (setting == null || setting === false) return [];
|
|
3822
|
+
if (setting === true) return cellIds.filter((id) => id !== originId);
|
|
3823
|
+
const group = setting[originId];
|
|
3824
|
+
if (group == null) return [];
|
|
3825
|
+
return cellIds.filter((id) => id !== originId && setting[id] === group);
|
|
3826
|
+
}
|
|
3827
|
+
function rangesWithin(a, b, epsMs) {
|
|
3828
|
+
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
3829
|
+
}
|
|
3830
|
+
var STYLE_SYNC_CONFIG_KEYS = ["layout", "panes", "grid", "priceScale", "crosshair"];
|
|
3831
|
+
function styleConfigSlice(config) {
|
|
3832
|
+
if (config == null || typeof config !== "object") return null;
|
|
3833
|
+
const doc = config;
|
|
3834
|
+
const out = {};
|
|
3835
|
+
for (const key of STYLE_SYNC_CONFIG_KEYS) {
|
|
3836
|
+
if (doc[key] != null && typeof doc[key] === "object") out[key] = doc[key];
|
|
3837
|
+
}
|
|
3838
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
3839
|
+
}
|
|
3840
|
+
|
|
3813
3841
|
// src/workspace/persist.ts
|
|
3814
3842
|
var memoryStore = /* @__PURE__ */ new Map();
|
|
3815
3843
|
function memoryStorageAdapter() {
|
|
@@ -4645,61 +4673,144 @@ var MarketStatusTracker = class {
|
|
|
4645
4673
|
};
|
|
4646
4674
|
|
|
4647
4675
|
// src/widget/session-shading.ts
|
|
4648
|
-
|
|
4676
|
+
var DAY_MS = 864e5;
|
|
4677
|
+
var MINUTE_MS = 6e4;
|
|
4678
|
+
function parseWindow(text) {
|
|
4679
|
+
if (typeof text !== "string") return null;
|
|
4680
|
+
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(text);
|
|
4681
|
+
if (!m) return null;
|
|
4682
|
+
const start = Number(m[1]) * 60 + Number(m[2]);
|
|
4683
|
+
const end = Number(m[3]) * 60 + Number(m[4]);
|
|
4684
|
+
if (start >= end || end > 1440) return null;
|
|
4685
|
+
return { start, end };
|
|
4686
|
+
}
|
|
4687
|
+
function parseSessionSpec(si) {
|
|
4688
|
+
const session = si?.["session"];
|
|
4689
|
+
if (typeof session !== "string" || session === "" || session === "24x7") return null;
|
|
4690
|
+
const regular = parseWindow(session);
|
|
4691
|
+
if (!regular) return null;
|
|
4692
|
+
const tz = si?.["timezone"];
|
|
4693
|
+
const timezone = typeof tz === "string" && tz !== "" ? tz : "Etc/UTC";
|
|
4694
|
+
const ext = parseWindow(si?.["session_extended"]);
|
|
4695
|
+
const extended = ext && ext.start <= regular.start && ext.end >= regular.end ? ext : { start: 0, end: 1440 };
|
|
4696
|
+
return { regular, extended, timezone };
|
|
4697
|
+
}
|
|
4698
|
+
var dtfCache = /* @__PURE__ */ new Map();
|
|
4699
|
+
function civilFormatter(tz) {
|
|
4700
|
+
const cached = dtfCache.get(tz);
|
|
4701
|
+
if (cached !== void 0) return cached;
|
|
4702
|
+
let dtf = null;
|
|
4703
|
+
try {
|
|
4704
|
+
dtf = new Intl.DateTimeFormat("en-US", {
|
|
4705
|
+
timeZone: tz,
|
|
4706
|
+
hourCycle: "h23",
|
|
4707
|
+
weekday: "short",
|
|
4708
|
+
year: "numeric",
|
|
4709
|
+
month: "2-digit",
|
|
4710
|
+
day: "2-digit",
|
|
4711
|
+
hour: "2-digit",
|
|
4712
|
+
minute: "2-digit"
|
|
4713
|
+
});
|
|
4714
|
+
} catch {
|
|
4715
|
+
dtf = null;
|
|
4716
|
+
}
|
|
4717
|
+
dtfCache.set(tz, dtf);
|
|
4718
|
+
return dtf;
|
|
4719
|
+
}
|
|
4720
|
+
function civilParts(dtf, ms) {
|
|
4721
|
+
const parts = dtf.formatToParts(ms);
|
|
4722
|
+
const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
|
|
4723
|
+
return {
|
|
4724
|
+
weekday: get("weekday"),
|
|
4725
|
+
year: Number(get("year")),
|
|
4726
|
+
month: Number(get("month")),
|
|
4727
|
+
day: Number(get("day")),
|
|
4728
|
+
hour: Number(get("hour")) % 24,
|
|
4729
|
+
minute: Number(get("minute"))
|
|
4730
|
+
};
|
|
4731
|
+
}
|
|
4732
|
+
function expandSessionZones(spec, from, to) {
|
|
4649
4733
|
const pre = [];
|
|
4650
4734
|
const post = [];
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4735
|
+
const dtf = civilFormatter(spec.timezone);
|
|
4736
|
+
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post };
|
|
4737
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4738
|
+
for (let cursor = from - DAY_MS; cursor < to + DAY_MS; cursor += DAY_MS) {
|
|
4739
|
+
const civil = civilParts(dtf, cursor);
|
|
4740
|
+
const key = civil.year * 1e4 + civil.month * 100 + civil.day;
|
|
4741
|
+
if (!Number.isFinite(key) || seen.has(key)) continue;
|
|
4742
|
+
seen.add(key);
|
|
4743
|
+
if (civil.weekday === "Sat" || civil.weekday === "Sun") continue;
|
|
4744
|
+
const naive = Date.UTC(civil.year, civil.month - 1, civil.day);
|
|
4745
|
+
const noonGuess = naive + 720 * MINUTE_MS;
|
|
4746
|
+
const atNoon = civilParts(dtf, noonGuess);
|
|
4747
|
+
const offset = Date.UTC(atNoon.year, atNoon.month - 1, atNoon.day, atNoon.hour, atNoon.minute) - noonGuess;
|
|
4748
|
+
const at = (minutes) => naive + minutes * MINUTE_MS - offset;
|
|
4749
|
+
if (spec.extended.start < spec.regular.start) pre.push([at(spec.extended.start), at(spec.regular.start)]);
|
|
4750
|
+
if (spec.regular.end < spec.extended.end) post.push([at(spec.regular.end), at(spec.extended.end)]);
|
|
4659
4751
|
}
|
|
4660
4752
|
return { pre, post };
|
|
4661
4753
|
}
|
|
4662
|
-
var
|
|
4663
|
-
var MIN_LOOKBACK_MS = 3 * 864e5;
|
|
4664
|
-
var MAX_LOOKBACK_MS = 120 * 864e5;
|
|
4754
|
+
var COVER_PAD_MIN_MS = 2 * DAY_MS;
|
|
4665
4755
|
var SessionShadingTracker = class {
|
|
4666
4756
|
constructor(onZones) {
|
|
4667
4757
|
this.onZones = onZones;
|
|
4668
|
-
/** Invalidates
|
|
4758
|
+
/** Invalidates the one async step (metadata resolution) — bumped by track()/stop(). */
|
|
4669
4759
|
this.epoch = 0;
|
|
4670
|
-
|
|
4671
|
-
|
|
4760
|
+
this.spec = null;
|
|
4761
|
+
this.ready = false;
|
|
4762
|
+
this.session = "regular";
|
|
4763
|
+
this.covered = null;
|
|
4764
|
+
/** The newest range seen — viewport moves during metadata resolution (a load's fit
|
|
4765
|
+
* animation) must not be lost, so the resolution always expands the LATEST range. */
|
|
4766
|
+
this.lastRange = null;
|
|
4767
|
+
}
|
|
4768
|
+
/** (Re)bind to a chart's data surface + market and expand once metadata lands. */
|
|
4672
4769
|
track(data, symbol, opts) {
|
|
4673
4770
|
const my = ++this.epoch;
|
|
4674
|
-
|
|
4771
|
+
this.ready = false;
|
|
4772
|
+
this.spec = null;
|
|
4773
|
+
this.covered = null;
|
|
4774
|
+
this.session = opts.session;
|
|
4775
|
+
this.lastRange = opts.range;
|
|
4776
|
+
void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
|
|
4777
|
+
if (my !== this.epoch) return;
|
|
4778
|
+
this.ready = true;
|
|
4779
|
+
this.spec = parseSessionSpec(si);
|
|
4780
|
+
this.emit(this.lastRange ?? opts.range, true);
|
|
4781
|
+
});
|
|
4782
|
+
}
|
|
4783
|
+
/** Follow a pan/zoom synchronously: bands are epoch-anchored, so only a range that
|
|
4784
|
+
* leaves the last expansion's coverage needs a recompute — no fetch, no debounce. */
|
|
4785
|
+
updateRange(range) {
|
|
4786
|
+
this.lastRange = range;
|
|
4787
|
+
if (!this.ready) return;
|
|
4788
|
+
this.emit(range, false);
|
|
4675
4789
|
}
|
|
4676
4790
|
stop() {
|
|
4677
4791
|
this.epoch += 1;
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
this.onZones(null);
|
|
4792
|
+
this.ready = false;
|
|
4793
|
+
this.spec = null;
|
|
4794
|
+
this.covered = null;
|
|
4795
|
+
this.lastRange = null;
|
|
4796
|
+
}
|
|
4797
|
+
emit(range, force) {
|
|
4798
|
+
if (!this.spec) {
|
|
4799
|
+
if (force) this.onZones(null);
|
|
4687
4800
|
return;
|
|
4688
4801
|
}
|
|
4689
|
-
if (
|
|
4690
|
-
this.onZones({ pre: [], post: [] });
|
|
4802
|
+
if (this.session !== "extended") {
|
|
4803
|
+
if (force) this.onZones({ pre: [], post: [] });
|
|
4691
4804
|
return;
|
|
4692
4805
|
}
|
|
4693
|
-
const
|
|
4694
|
-
const
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
if (!regular || !extended) return;
|
|
4702
|
-
this.onZones(deriveSessionZones(regular, extended));
|
|
4806
|
+
const from = Math.min(range.from, range.to);
|
|
4807
|
+
const to = Math.max(range.from, range.to);
|
|
4808
|
+
if (!Number.isFinite(from) || !Number.isFinite(to)) return;
|
|
4809
|
+
if (!force && this.covered && from >= this.covered.from && to <= this.covered.to) return;
|
|
4810
|
+
const pad = Math.max(to - from, COVER_PAD_MIN_MS);
|
|
4811
|
+
const covered = { from: from - pad, to: to + pad };
|
|
4812
|
+
this.covered = covered;
|
|
4813
|
+
this.onZones(expandSessionZones(this.spec, covered.from, covered.to));
|
|
4703
4814
|
}
|
|
4704
4815
|
};
|
|
4705
4816
|
|
|
@@ -4924,7 +5035,11 @@ var ChartCell = class {
|
|
|
4924
5035
|
this.deps.toast(`No registered provider serves "${symbol2}" (registered: ${list})`, "error", 6e3);
|
|
4925
5036
|
});
|
|
4926
5037
|
this.inner.on("load:start", () => this.watermark?.setLoading(true));
|
|
4927
|
-
this.inner.on("load:end", () =>
|
|
5038
|
+
this.inner.on("load:end", () => {
|
|
5039
|
+
this.watermark?.setLoading(false);
|
|
5040
|
+
this.refreshSessionShading();
|
|
5041
|
+
});
|
|
5042
|
+
this.inner.on("viewport:changed", (range) => this.sessionShading.updateRange(range));
|
|
4928
5043
|
const tz = deps.timezone();
|
|
4929
5044
|
if (tz !== "Etc/UTC") this.inner.renderer.set("timezone", tz);
|
|
4930
5045
|
this.indicatorTitlesOn = seed.indicatorTitles ?? true;
|
|
@@ -5051,14 +5166,18 @@ var ChartCell = class {
|
|
|
5051
5166
|
this.refreshSessionShading();
|
|
5052
5167
|
});
|
|
5053
5168
|
}
|
|
5054
|
-
/** (Re)derive the pre/post-market shading bands for this cell's market. The
|
|
5055
|
-
*
|
|
5169
|
+
/** (Re)derive the pre/post-market shading bands for this cell's market. The bands
|
|
5170
|
+
* expand locally from the symbol's session vocabulary, so they paint as soon as
|
|
5171
|
+
* metadata is known and follow any pan depth without provider round trips. */
|
|
5056
5172
|
refreshSessionShading() {
|
|
5057
5173
|
const chart = this.inner;
|
|
5058
5174
|
const symbol = this.state.symbol;
|
|
5059
5175
|
if (!chart || !symbol) return;
|
|
5060
|
-
const
|
|
5061
|
-
this.
|
|
5176
|
+
const now = Date.now();
|
|
5177
|
+
const requestedSpan = Math.max(this.state.bars ?? 1e3, this.rangeBars) * timeframeMs(this.state.timeframe ?? "60");
|
|
5178
|
+
const fallbackSpan = Number.isFinite(requestedSpan) ? Math.max(3 * 864e5, requestedSpan) : 3 * 864e5;
|
|
5179
|
+
const range = chart.getVisibleRange() ?? { from: now - fallbackSpan, to: now };
|
|
5180
|
+
this.sessionShading.track(chart.data, symbol, { session: this.session, range });
|
|
5062
5181
|
}
|
|
5063
5182
|
/** The session-shade colors live in the renderer CONFIG (persisted with it, edited
|
|
5064
5183
|
* live by the dialog swatch) — the cell only proxies them into its settings rows. */
|
|
@@ -5154,10 +5273,10 @@ var ChartCell = class {
|
|
|
5154
5273
|
id: "status-line",
|
|
5155
5274
|
rows: [
|
|
5156
5275
|
{ kind: "heading", label: "Status line", id: "parts" },
|
|
5157
|
-
{ kind: "toggle", label: "Symbol name", id: "name", get: () => sl.partVisible("name"), set: (v) =>
|
|
5158
|
-
{ kind: "toggle", label: "Market status", id: "market", get: () => sl.partVisible("market"), set: (v) =>
|
|
5159
|
-
{ kind: "toggle", label: "OHLC values", id: "ohlc", get: () => sl.partVisible("ohlc"), set: (v) =>
|
|
5160
|
-
{ kind: "toggle", label: "Bar change values", id: "change", get: () => sl.partVisible("change"), set: (v) =>
|
|
5276
|
+
{ kind: "toggle", label: "Symbol name", id: "name", get: () => sl.partVisible("name"), set: (v) => this.setStatuslinePart("name", v) },
|
|
5277
|
+
{ kind: "toggle", label: "Market status", id: "market", get: () => sl.partVisible("market"), set: (v) => this.setStatuslinePart("market", v) },
|
|
5278
|
+
{ kind: "toggle", label: "OHLC values", id: "ohlc", get: () => sl.partVisible("ohlc"), set: (v) => this.setStatuslinePart("ohlc", v) },
|
|
5279
|
+
{ kind: "toggle", label: "Bar change values", id: "change", get: () => sl.partVisible("change"), set: (v) => this.setStatuslinePart("change", v) },
|
|
5161
5280
|
{ kind: "heading", label: "Indicators", id: "indicators" },
|
|
5162
5281
|
{
|
|
5163
5282
|
kind: "toggle",
|
|
@@ -5192,12 +5311,40 @@ var ChartCell = class {
|
|
|
5192
5311
|
this.indicatorTitlesOn = visible;
|
|
5193
5312
|
this.inner?.renderer.set("indicatorTitles", visible);
|
|
5194
5313
|
this.deps.onStateDirty();
|
|
5314
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
5195
5315
|
}
|
|
5196
5316
|
/** Show/hide the plot values beside this cell's legend titles (persisted per cell). */
|
|
5197
5317
|
setIndicatorValuesVisible(visible) {
|
|
5198
5318
|
this.indicatorValuesOn = visible;
|
|
5199
5319
|
this.inner?.renderer.set("indicatorValues", visible);
|
|
5200
5320
|
this.deps.onStateDirty();
|
|
5321
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
5322
|
+
}
|
|
5323
|
+
/** Show/hide one status-line segment (the settings dialog's Status line tab). */
|
|
5324
|
+
setStatuslinePart(part, visible) {
|
|
5325
|
+
this.statusline?.setPartVisible(part, visible);
|
|
5326
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
5327
|
+
}
|
|
5328
|
+
/** This cell's Status line tab prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
5329
|
+
statusPrefs() {
|
|
5330
|
+
const sl = this.statusline;
|
|
5331
|
+
return {
|
|
5332
|
+
parts: sl ? { name: sl.partVisible("name"), market: sl.partVisible("market"), ohlc: sl.partVisible("ohlc"), change: sl.partVisible("change") } : null,
|
|
5333
|
+
indicatorTitles: this.indicatorTitlesOn,
|
|
5334
|
+
indicatorValues: this.indicatorValuesOn
|
|
5335
|
+
};
|
|
5336
|
+
}
|
|
5337
|
+
/** Converge this cell's Status line tab prefs to `prefs` — the follower half of
|
|
5338
|
+
* the workspace's style link. Idempotent: matching values change nothing, so a
|
|
5339
|
+
* propagated echo dies on its own. */
|
|
5340
|
+
applyStatusPrefs(prefs) {
|
|
5341
|
+
if (prefs.parts && this.statusline) {
|
|
5342
|
+
for (const part of Object.keys(prefs.parts)) {
|
|
5343
|
+
if (this.statusline.partVisible(part) !== prefs.parts[part]) this.statusline.setPartVisible(part, prefs.parts[part]);
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
5346
|
+
if (prefs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(prefs.indicatorTitles);
|
|
5347
|
+
if (prefs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(prefs.indicatorValues);
|
|
5201
5348
|
}
|
|
5202
5349
|
/** The LIVE chart of this cell — never cache it across a layout change (the cell's
|
|
5203
5350
|
* identity is what endures; the chart dies with the cell). */
|
|
@@ -7139,16 +7286,21 @@ var DrawingPill = class {
|
|
|
7139
7286
|
|
|
7140
7287
|
// src/workspace/context.ts
|
|
7141
7288
|
function buildContext(host) {
|
|
7142
|
-
const active = host.active();
|
|
7143
7289
|
return {
|
|
7144
7290
|
get chart() {
|
|
7145
7291
|
const cell = host.active();
|
|
7146
7292
|
if (!cell) throw new Error("VelaWorkspace has no active cell yet");
|
|
7147
7293
|
return cell.chart;
|
|
7148
7294
|
},
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7295
|
+
get symbol() {
|
|
7296
|
+
return host.active()?.symbol ?? "";
|
|
7297
|
+
},
|
|
7298
|
+
get timeframe() {
|
|
7299
|
+
return host.active()?.timeframe ?? "60";
|
|
7300
|
+
},
|
|
7301
|
+
get priceStyle() {
|
|
7302
|
+
return host.active()?.priceStyle ?? "candles";
|
|
7303
|
+
},
|
|
7152
7304
|
setSymbol: (symbol) => host.active()?.setSymbol(symbol),
|
|
7153
7305
|
setTimeframe: (tf) => host.active()?.setTimeframe(tf),
|
|
7154
7306
|
setPriceStyle: (style) => host.active()?.setPriceStyle(style),
|
|
@@ -7159,8 +7311,12 @@ function buildContext(host) {
|
|
|
7159
7311
|
addIndicator: (entry) => host.active()?.addExternalIndicator(entry),
|
|
7160
7312
|
addNativeIndicator: (type) => host.active()?.addNative(type),
|
|
7161
7313
|
stateChanged: () => host.stateDirty(),
|
|
7162
|
-
|
|
7163
|
-
|
|
7314
|
+
get cells() {
|
|
7315
|
+
return host.cells().map((c) => ({ id: c.id, chart: c.chart, symbol: c.symbol, timeframe: c.timeframe }));
|
|
7316
|
+
},
|
|
7317
|
+
get activeCellId() {
|
|
7318
|
+
return host.active()?.id ?? "";
|
|
7319
|
+
},
|
|
7164
7320
|
setActiveCell: (id) => host.setActiveCell(id)
|
|
7165
7321
|
};
|
|
7166
7322
|
}
|
|
@@ -7254,6 +7410,10 @@ var VelaWorkspace = class {
|
|
|
7254
7410
|
/** Same guard for the drawings link: the propagated mutations' own `drawing:*`
|
|
7255
7411
|
* events fire synchronously inside the propagation loop and must not fan out again. */
|
|
7256
7412
|
this.drawingSyncBusy = false;
|
|
7413
|
+
/** Same guard for the style link: a follower's `applyConfig` re-fires its
|
|
7414
|
+
* `onConfigChanged` in the same tick, and a state restore applies per-cell
|
|
7415
|
+
* configs that legitimately differ — neither must propagate. */
|
|
7416
|
+
this.styleSyncBusy = false;
|
|
7257
7417
|
/** LINKED drawings (the drawings sync): one map per synced set (cellId → that
|
|
7258
7418
|
* cell's drawing id), reachable from every member under its `cellId\0drawingId`
|
|
7259
7419
|
* key — any member finds its peers to push edits/removals onto. Survives a
|
|
@@ -7314,9 +7474,7 @@ var VelaWorkspace = class {
|
|
|
7314
7474
|
if (boot?.favorites) this.favs = [...boot.favorites];
|
|
7315
7475
|
if (boot?.timeframeFavorites) this.tfFavs = [...boot.timeframeFavorites];
|
|
7316
7476
|
const sync = boot?.sync ?? opts.sync;
|
|
7317
|
-
for (const kind of
|
|
7318
|
-
this.applySyncSetting(kind, sync?.[kind]);
|
|
7319
|
-
}
|
|
7477
|
+
for (const kind of SYNC_KINDS) this.applySyncSetting(kind, sync?.[kind]);
|
|
7320
7478
|
this.monoLayout = opts.layout === false;
|
|
7321
7479
|
const optLayout = opts.layout === false || opts.layout === void 0 ? "4" : opts.layout;
|
|
7322
7480
|
this.def = this.resolveLayout(this.monoLayout ? "1" : boot?.layout && ensureLayout(boot.layout) ? boot.layout : optLayout);
|
|
@@ -7400,7 +7558,8 @@ var VelaWorkspace = class {
|
|
|
7400
7558
|
syncs: () => [
|
|
7401
7559
|
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
7402
7560
|
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
7403
|
-
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true }
|
|
7561
|
+
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true },
|
|
7562
|
+
{ id: "style", label: "Style", checked: this.syncOpts.style === true }
|
|
7404
7563
|
],
|
|
7405
7564
|
onToggleSync: (id) => {
|
|
7406
7565
|
const kind = id;
|
|
@@ -7684,7 +7843,7 @@ var VelaWorkspace = class {
|
|
|
7684
7843
|
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
7685
7844
|
}
|
|
7686
7845
|
this.dock.applyState(st.panels);
|
|
7687
|
-
for (const kind of
|
|
7846
|
+
for (const kind of SYNC_KINDS) this.applySyncSetting(kind, st.sync?.[kind]);
|
|
7688
7847
|
this.trackSizes.clear();
|
|
7689
7848
|
if (st.trackSizes) for (const [id, ts] of Object.entries(st.trackSizes)) this.trackSizes.set(id, ts);
|
|
7690
7849
|
const targetDef = this.monoLayout ? this.def : ensureLayout(st.layout) ?? this.def;
|
|
@@ -7695,9 +7854,14 @@ var VelaWorkspace = class {
|
|
|
7695
7854
|
for (const cell of this.cellsById.values()) cell.chart.drawings.setFavorites(this.favs);
|
|
7696
7855
|
}
|
|
7697
7856
|
this.drawingLinks.clear();
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
this.
|
|
7857
|
+
this.styleSyncBusy = true;
|
|
7858
|
+
try {
|
|
7859
|
+
for (const [i] of this.def.cells.entries()) {
|
|
7860
|
+
const { id, ...cs } = st.charts[i];
|
|
7861
|
+
this.cellsById.get(id)?.rehydrate(cs);
|
|
7862
|
+
}
|
|
7863
|
+
} finally {
|
|
7864
|
+
this.styleSyncBusy = false;
|
|
7701
7865
|
}
|
|
7702
7866
|
if (st.timezone) this.setTimezone(st.timezone);
|
|
7703
7867
|
this.pool.clear();
|
|
@@ -7797,6 +7961,7 @@ var VelaWorkspace = class {
|
|
|
7797
7961
|
const rebuildAll = nextBackend !== this.cellBackend;
|
|
7798
7962
|
this.order = orderAfterLayout(this.order, next.cells.length, this.activeId);
|
|
7799
7963
|
const keep = new Set(this.order.slice(0, next.cells.length));
|
|
7964
|
+
const preexisting = new Set(this.cellsById.keys());
|
|
7800
7965
|
for (const [id, cell] of [...this.cellsById]) {
|
|
7801
7966
|
if (!keep.has(id) || rebuildAll) {
|
|
7802
7967
|
this.poolSet(id, cell.dehydrate());
|
|
@@ -7809,6 +7974,7 @@ var VelaWorkspace = class {
|
|
|
7809
7974
|
this.cellBackend = nextBackend;
|
|
7810
7975
|
this.applyGrid();
|
|
7811
7976
|
this.buildCells();
|
|
7977
|
+
this.alignNewCellStyles(preexisting);
|
|
7812
7978
|
this.syncCellPresentation();
|
|
7813
7979
|
this.topbar.setLayout(next.id);
|
|
7814
7980
|
const nextActive = activeAfterLayout(this.activeId, this.order.slice(0, next.cells.length));
|
|
@@ -8012,6 +8178,7 @@ var VelaWorkspace = class {
|
|
|
8012
8178
|
onMarketChanged: (id2) => this.onCellMarketChanged(id2),
|
|
8013
8179
|
onPriceStyleChanged: (id2) => this.onCellPriceStyleChanged(id2),
|
|
8014
8180
|
onIndicatorsChanged: (id2) => this.onCellIndicatorsChanged(id2),
|
|
8181
|
+
onStatusPrefsChanged: (id2) => this.propagateStylePrefs(id2),
|
|
8015
8182
|
onStateDirty: () => this.markStateDirty(),
|
|
8016
8183
|
manifestSettled: () => this.manifestSettled,
|
|
8017
8184
|
toast: (message, kind, durationMs) => this.toastHost.show(message, kind, durationMs)
|
|
@@ -8087,6 +8254,7 @@ var VelaWorkspace = class {
|
|
|
8087
8254
|
this.drawToolbar?.setEraserActive(mode === "eraser");
|
|
8088
8255
|
});
|
|
8089
8256
|
chart.on("viewport:changed", (range) => this.propagateViewport(cell.id, range));
|
|
8257
|
+
chart.renderer.onConfigChanged(() => this.propagateStylePrefs(cell.id));
|
|
8090
8258
|
chart.on("theme:changed", (t) => this.setTheme(t));
|
|
8091
8259
|
chart.renderer.onAxisLongPress((e) => {
|
|
8092
8260
|
if (this.layoutCtl.current !== "mobile") return;
|
|
@@ -8119,11 +8287,67 @@ var VelaWorkspace = class {
|
|
|
8119
8287
|
if (kind === "viewport") {
|
|
8120
8288
|
const range = this.cellsById.get(this.activeId)?.chart.getVisibleRange();
|
|
8121
8289
|
if (range) this.propagateViewport(this.activeId, range);
|
|
8290
|
+
} else if (kind === "style") {
|
|
8291
|
+
this.propagateStylePrefs(this.activeId);
|
|
8122
8292
|
} else {
|
|
8123
8293
|
this.propagateMarket(this.activeId);
|
|
8124
8294
|
}
|
|
8125
8295
|
}
|
|
8126
8296
|
}
|
|
8297
|
+
/**
|
|
8298
|
+
* Align cells minted by a layout change to their style group: with the link on, a
|
|
8299
|
+
* NEW cell (fresh slot or one returning from the pool, which missed edits while
|
|
8300
|
+
* dormant) inherits the presentation of a pre-existing group peer — the active
|
|
8301
|
+
* cell when it is one — instead of sitting on its own state beside a styled
|
|
8302
|
+
* group. Propagation runs FROM the peer, so a newborn's defaults never overwrite
|
|
8303
|
+
* the group, and the equality short-circuits keep converged peers untouched.
|
|
8304
|
+
*/
|
|
8305
|
+
alignNewCellStyles(preexisting) {
|
|
8306
|
+
const setting = this.syncOpts.style;
|
|
8307
|
+
if (!setting) return;
|
|
8308
|
+
const ids = [...this.cellsById.keys()];
|
|
8309
|
+
const propagated = /* @__PURE__ */ new Set();
|
|
8310
|
+
for (const id of ids) {
|
|
8311
|
+
if (preexisting.has(id)) continue;
|
|
8312
|
+
const peers = syncTargets(id, setting, ids).filter((p) => preexisting.has(p));
|
|
8313
|
+
if (peers.length === 0) continue;
|
|
8314
|
+
const source = this.activeId && peers.includes(this.activeId) ? this.activeId : peers[0];
|
|
8315
|
+
if (propagated.has(source)) continue;
|
|
8316
|
+
propagated.add(source);
|
|
8317
|
+
this.propagateStylePrefs(source);
|
|
8318
|
+
}
|
|
8319
|
+
}
|
|
8320
|
+
/**
|
|
8321
|
+
* Mirror an origin cell's presentation — the Canvas + Scales-and-lines slice of
|
|
8322
|
+
* its renderer config plus its Status line tab prefs — onto its same-group
|
|
8323
|
+
* followers (the style link). Loop-safe two ways: the busy guard eats the
|
|
8324
|
+
* followers' SYNCHRONOUS echoes (their `applyConfig` re-fires `onConfigChanged`
|
|
8325
|
+
* in the same tick), and the equality short-circuits leave already-converged
|
|
8326
|
+
* followers untouched, so nothing re-emits once the group agrees.
|
|
8327
|
+
*/
|
|
8328
|
+
propagateStylePrefs(originId) {
|
|
8329
|
+
if (this.styleSyncBusy || this.destroyed) return;
|
|
8330
|
+
const targets = syncTargets(originId, this.syncOpts.style, [...this.cellsById.keys()]);
|
|
8331
|
+
if (targets.length === 0) return;
|
|
8332
|
+
const origin = this.cellsById.get(originId);
|
|
8333
|
+
if (!origin) return;
|
|
8334
|
+
const slice = styleConfigSlice(origin.chart.renderer.getConfig());
|
|
8335
|
+
const sliceJson = slice ? JSON.stringify(slice) : null;
|
|
8336
|
+
const prefs = origin.statusPrefs();
|
|
8337
|
+
this.styleSyncBusy = true;
|
|
8338
|
+
try {
|
|
8339
|
+
for (const id of targets) {
|
|
8340
|
+
const cell = this.cellsById.get(id);
|
|
8341
|
+
if (!cell) continue;
|
|
8342
|
+
if (slice && sliceJson !== JSON.stringify(styleConfigSlice(cell.chart.renderer.getConfig()))) {
|
|
8343
|
+
cell.chart.renderer.applyConfig(slice);
|
|
8344
|
+
}
|
|
8345
|
+
cell.applyStatusPrefs(prefs);
|
|
8346
|
+
}
|
|
8347
|
+
} finally {
|
|
8348
|
+
this.styleSyncBusy = false;
|
|
8349
|
+
}
|
|
8350
|
+
}
|
|
8127
8351
|
/**
|
|
8128
8352
|
* Mirror an origin cell's pointer time onto its same-group followers as GHOST
|
|
8129
8353
|
* crosshairs (`renderer.setExternalCrosshair`). The horizontal price level rides
|
|
@@ -8431,7 +8655,8 @@ var VelaWorkspace = class {
|
|
|
8431
8655
|
syncs: () => [
|
|
8432
8656
|
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
8433
8657
|
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
8434
|
-
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true }
|
|
8658
|
+
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true },
|
|
8659
|
+
{ id: "style", label: "Style", checked: this.syncOpts.style === true }
|
|
8435
8660
|
],
|
|
8436
8661
|
onToggleSync: (id) => {
|
|
8437
8662
|
const kind = id;
|