@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.
- package/dist/{DataProvider-COGDs9Xg.d.cts → DataProvider-Bw58CjYy.d.cts} +1 -1
- package/dist/{DataProvider-DpOWZD8F.d.ts → DataProvider-Byo8pEv0.d.ts} +1 -1
- package/dist/{chunk-DPAA3SRT.js → chunk-ALTGHKIM.js} +836 -32
- package/dist/{chunk-27UTYE7T.js → chunk-AUOB7A5Y.js} +49 -7
- package/dist/{chunk-HKNO24GD.js → chunk-I2U4NZD2.js} +56 -1
- package/dist/{chunk-R2JMNIHY.js → chunk-MOJ4T26N.js} +341 -169
- package/dist/{chunk-T3DZIR5Y.js → chunk-UNUVPQFW.js} +2214 -886
- package/dist/{chunk-NLDTVFOI.js → chunk-WY5H4S4I.js} +129 -10
- package/dist/{contributions-Cgm9RJYP.d.cts → contributions-BY3zSYiZ.d.ts} +98 -7
- package/dist/{contributions-bWgwyiVi.d.ts → contributions-DOm40g5-.d.cts} +98 -7
- package/dist/index.cjs +3066 -893
- package/dist/index.d.cts +36 -22
- package/dist/index.d.ts +36 -22
- package/dist/index.js +5 -5
- package/dist/{options-NAUHiQEC.d.ts → options-CSXbjMM5.d.cts} +129 -4
- package/dist/{options-NAUHiQEC.d.cts → options-CSXbjMM5.d.ts} +129 -4
- package/dist/{plugin-CqxWoDKS.d.ts → plugin-BDxUhGO3.d.ts} +4 -4
- package/dist/{plugin-CjjzZX2F.d.cts → plugin-CiQVyETg.d.cts} +4 -4
- package/dist/plugin.cjs +573 -17
- package/dist/plugin.d.cts +5 -5
- package/dist/plugin.d.ts +5 -5
- package/dist/plugin.js +3 -3
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{side-panel-wMNfgyZb.d.cts → side-panel-_2UK1gSm.d.cts} +45 -3
- package/dist/{side-panel-wMNfgyZb.d.ts → side-panel-_2UK1gSm.d.ts} +45 -3
- package/dist/{statusline-model-Dih6FGWP.d.ts → statusline-model-C9tYXuux.d.ts} +19 -4
- package/dist/{statusline-model-Dpfpq54A.d.cts → statusline-model-DUBX6Fib.d.cts} +19 -4
- package/dist/ui.cjs +224 -14
- package/dist/ui.d.cts +119 -87
- package/dist/ui.d.ts +119 -87
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +3073 -900
- package/dist/vela.global.min.js +158 -54
- package/dist/widget.cjs +3640 -1119
- package/dist/widget.d.cts +24 -10
- package/dist/widget.d.ts +24 -10
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +3639 -1119
- package/dist/workspace.d.cts +61 -20
- package/dist/workspace.d.ts +61 -20
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
- /package/dist/{chunk-JU4CWEBF.js → chunk-W5YPYZ2P.js} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES,
|
|
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-
|
|
3
|
-
import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-
|
|
4
|
-
import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-
|
|
5
|
-
import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, announceSurface, icon, categoricalColor } from './chunk-
|
|
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-
|
|
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
|
|
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.
|
|
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 +
|
|
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
|
-
|
|
853
|
-
|
|
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.
|
|
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.
|
|
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 = [
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
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
|
-
|
|
4105
|
+
const block = asRecord(from[key]);
|
|
4106
|
+
if (block && JSON.stringify(block) !== JSON.stringify(to[key])) patch[key] = block;
|
|
4052
4107
|
}
|
|
4053
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
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
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
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.
|
|
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
|
|
6261
|
-
* the workspace's style link. Idempotent: matching values change
|
|
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)
|
|
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
|
-
*
|
|
9991
|
-
*
|
|
9992
|
-
*
|
|
9993
|
-
*
|
|
9994
|
-
* in
|
|
9995
|
-
*
|
|
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
|
|
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
|
-
|
|
10012
|
-
|
|
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
|
}));
|